diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e2ef99d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,23 @@ +pipeline { + agent any + + environment { + VERSION = VersionNumber([ + versionNumberString: + '${BUILDS_ALL_TIME}', + versionPrefix: '1.0.', + worstResultForIncrement: 'SUCCESS' + ]) + } + + stages { + stage('Building') { + steps { + script { + currentBuild.displayName = env.VERSION + } + sh 'yarn ci ${VERSION}' + } + } + } +} diff --git a/jenkins.js b/jenkins.js new file mode 100644 index 0000000..9b88533 --- /dev/null +++ b/jenkins.js @@ -0,0 +1,26 @@ +/* eslint-disable no-process-exit */ +/* eslint-disable no-console */ +/* eslint-disable no-sync */ +'use strict'; + +const fs = require ('fs'); +const child_process = require ('child_process'); + +const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8')); +[ + ,, pkg.version +] = process.argv; +fs.writeFileSync ('package.json', JSON.stringify (pkg, null, 2)); + +child_process.execSync ('yarn lint', { stdio: 'inherit' }); +child_process.execSync ('yarn test', { stdio: 'inherit' }); +child_process.execSync ('yarn compile', { stdio: 'inherit' }); + +child_process.exec ('git log -1 | grep \'\\[no publish\\]\'') + .addListener ('exit', (code) => { + if (code === 0) { + console.log ('build not marked for deployment'); + process.exit (1); + } + else { child_process.execSync ('yarn publish'); } + }); diff --git a/lib/InteractiveOptions.ts b/lib/InteractiveOptions.ts index 13409e1..bd05d37 100644 --- a/lib/InteractiveOptions.ts +++ b/lib/InteractiveOptions.ts @@ -1,8 +1,11 @@ +/* eslint-disable max-lines-per-function */ /* eslint-disable complexity */ /* eslint-disable max-statements */ /* eslint-disable no-process-env */ import { Persistent } from '@scode/modelling'; import fs from 'fs-extra'; +import yargs from 'yargs'; +import { Confirm, Input } from 'enquirer'; enum OptionType { string = 'string', @@ -20,6 +23,8 @@ interface Option { default?: unknown; alias?: string; env?: string; + description?: string; + message?: string; } interface OptionProcess extends Option { @@ -45,22 +50,24 @@ export class InteractiveOptions extends Persistent { } public async parse (): Promise { - this.get_env_options (); - this.get_args_options (); + await this.get_env_options (); + await this.get_args_options (); await this.get_interactive_options (); } - private get unfilled (): Array { - return this.options.filter ((o) => !o.filled); - } - private async assign_arg (opt: OptionProcess, value: unknown): Promise { if (opt.type === OptionType.string) { - opt.value = value; + opt.value = String (value); opt.filled = true; return; } if (opt.type === OptionType.number) { + if (![ + 'string', + 'number' + ].includes (typeof value)) + return; + const as_num = parseInt (value); const is_num = !isNaN (as_num); if (is_num) { @@ -70,10 +77,20 @@ export class InteractiveOptions extends Persistent { return; } if (opt.type === OptionType.boolean) { - const is_boo = (/^(?:true|false)$/ui).test (value); - if (is_boo) { - const as_boo = (/true/ui).test (value); - opt.value = as_boo; + if (![ + 'string', + 'boolean', + 'number' + ].includes (typeof value)) + return; + + const is_bool = [ + 0, + 1 + ].includes (value) || (/^(?:true|false)$/ui).test (value); + if (is_bool) { + const as_bool = value === 1 || (/true/ui).test (value as string); + opt.value = as_bool; opt.filled = true; } return; @@ -83,7 +100,7 @@ export class InteractiveOptions extends Persistent { || opt.type === OptionType.file || opt.type === OptionType.folder ) { - if (!await fs.pathExists (value)) + if (typeof value !== 'string' || !await fs.pathExists (value)) return; if (opt.type === OptionType.path) { opt.value = value; @@ -98,7 +115,7 @@ export class InteractiveOptions extends Persistent { } } - private async get_env_options (): void { + private async get_env_options (): Promise { await Promise.all (this.options.map ((opt) => { if ( typeof opt.env !== 'undefined' @@ -109,11 +126,64 @@ export class InteractiveOptions extends Persistent { })); } - private async get_args_options (): void { + private async get_args_options (): Promise { + const yargs_config = { + quiet: { + alias: 'q', + default: false, + type: 'boolean', + describe: 'do not ask for options interactively' + } + }; + for (const opt of this.options) { + yargs_config[opt.name] = { + alias: opt.alias, + default: opt.default, + type: opt.type === OptionType.boolean ? 'boolean' : 'string', + describe: opt.description + }; + } + const argv = yargs.options (yargs_config); + await Promise.all (this.options.map ((opt) => { + if (typeof argv[opt.name] !== 'undefined') + return this.assign_arg (opt, argv[opt.name]); + return Promise.resolve (); + })); + } + private async prompt (opt: OptionProcess): Promise { + if (opt.filled) + return; + if ( + opt.type === OptionType.string + || opt.type === OptionType.file + || opt.type === OptionType.folder + || opt.type === OptionType.path + || opt.type === OptionType.number + ) { + const value = await new Input ({ + message: opt.message, + default: opt.default + }) + .run (); + await this.assign_arg (opt, value); + return; + } + if ( + opt.type === OptionType.boolean + ) { + const value = await new Confirm ({ + message: opt.message, + default: opt.default + }) + .run (); + await this.assign_arg (opt, value); + } } private async get_interactive_options (): Promise { - + for (const opt of this.options) + // eslint-disable-next-line no-await-in-loop + await this.prompt (opt); } } diff --git a/lib/enquirer.d.ts b/lib/enquirer.d.ts new file mode 100644 index 0000000..7d722fd --- /dev/null +++ b/lib/enquirer.d.ts @@ -0,0 +1 @@ +declare module 'enquirer'; diff --git a/package.json b/package.json index c717a29..fc465d4 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@ava/typescript": "^1.1.1", "@scode/eslint-config-ts": "^1.0.31", "@types/fs-extra": "^8.1.0", + "@types/yargs": "^15.0.4", "ava": "^3.8.1", "eslint": "^6.8.0", "nyc": "^15.0.1", @@ -16,16 +17,17 @@ "scripts": { "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", "test": "echo \"no test\"", - "compile": "tsc" + "compile": "tsc", + "ci": "yarn && node jenkins.js" }, "files": [ "LICENSE", "/dist/" ], "dependencies": { - "@scode/modelling": "^1.0.24", + "@scode/modelling": "^1.0.26", "enquirer": "^2.3.5", "fs-extra": "^9.0.0", "yargs": "^15.3.1" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3c356fc..dcab126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -253,7 +253,7 @@ eslint-plugin-node "^11.0.0" eslint-plugin-sort-requires-by-path "^1.0.2" -"@scode/modelling@^1.0.24": +"@scode/modelling@^1.0.26": version "1.0.26" resolved "https://npm.scode.ovh/@scode%2fmodelling/-/modelling-1.0.26.tgz#e976a98762cb3c1b66af7d65616a50022230e5b1" integrity sha512-Pk6NXQ7HurAlCaLChvX8ycgbWZG31I2UwnKEyevvxYPS6WvNqFpEhxve1sRas1caQrVq0b626nyVjChviPZT+Q== @@ -321,6 +321,18 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^15.0.4": + version "15.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" + integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^2.26.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.31.0.tgz#942c921fec5e200b79593c71fafb1e3f57aa2e36"