diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a00268fd..782f23dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,6 +52,8 @@ jobs: run: yarn install --frozen-lockfile - name: Build run: yarn build --define:process.env.CRAFT_BUILD_SHA='"'${{ github.sha }}'"' + - name: Smoke Test + run: ./dist/craft --help - name: NPM Pack run: npm pack - name: Docs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dd901ec..f779838e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,10 @@ name: Release on: workflow_dispatch: inputs: + craft_version: + description: Craft version to release + required: true + default: "latest" version: description: Version to release required: false @@ -33,3 +37,4 @@ jobs: with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} + craft_version: ${{ github.event.inputs.craft_version }} diff --git a/package.json b/package.json index 1b7b2a70..03da03f8 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@types/shell-quote": "^1.6.0", "@types/tar": "^4.0.0", "@types/tmp": "^0.0.33", - "@types/yargs": "^15.0.3", + "@types/yargs": "^17", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", "ajv": "6.12.6", @@ -86,12 +86,12 @@ "tmp": "0.2.4", "ts-jest": "^29.1.1", "typescript": "^5.1.6", - "yargs": "15.4.1" + "yargs": "^18" }, "scripts": { "build:fat": "yarn run compile-config-schema && tsc -p tsconfig.build.json", "build:watch": "yarn run compile-config-schema && tsc -p tsconfig.build.json --watch", - "build": "yarn compile-config-schema && esbuild src/index.ts --sourcemap --bundle --platform=node --target=node20 --inject:./src/utils/import-meta-url.js --define:import.meta.url=import_meta_url --outfile=dist/craft --minify", + "build": "yarn compile-config-schema && esbuild src/index.ts --sourcemap --bundle --platform=node --target=node22 --inject:./src/utils/import-meta-url.js --define:import.meta.url=import_meta_url --outfile=dist/craft", "precli": "yarn build", "cli": "node -r source-map-support/register dist/craft", "clean": "rimraf dist coverage", diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 642eadb6..a9cb9e7e 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,3 +1,79 @@ -test('it works', () => { - expect(true).toBeTruthy(); +import { execFile } from 'child_process'; +import { promisify } from 'util'; +import { resolve } from 'path'; + +const execFileAsync = promisify(execFile); + +// Path to the TypeScript source file - we use ts-node to run it directly +const CLI_ENTRY = resolve(__dirname, '../index.ts'); + +describe('CLI smoke tests', () => { + // Increase timeout for CLI tests as they spawn processes + jest.setTimeout(30000); + + test('CLI starts and shows help without runtime errors', async () => { + // This catches issues like: + // - Missing dependencies + // - Syntax errors + // - Runtime initialization errors (e.g., yargs singleton usage in v18) + const { stdout, stderr } = await execFileAsync( + 'npx', + ['ts-node', '--transpile-only', CLI_ENTRY, '--help'], + { env: { ...process.env, NODE_ENV: 'test' } } + ); + + expect(stdout).toMatch(//); + expect(stdout).toContain('prepare NEW-VERSION'); + expect(stdout).toContain('publish NEW-VERSION'); + expect(stdout).toContain('--help'); + // Ensure no error output (warnings are acceptable) + expect(stderr).not.toContain('Error'); + expect(stderr).not.toContain('TypeError'); + }); + + test('CLI shows version without errors', async () => { + const { stdout } = await execFileAsync( + 'npx', + ['ts-node', '--transpile-only', CLI_ENTRY, '--version'], + { env: { ...process.env, NODE_ENV: 'test' } } + ); + + // Version should be a semver-like string + expect(stdout.trim()).toMatch(/^\d+\.\d+\.\d+/); + }); + + test('CLI exits with error for unknown command', async () => { + // This ensures yargs command parsing works and async handlers are awaited + await expect( + execFileAsync( + 'npx', + ['ts-node', '--transpile-only', CLI_ENTRY, 'nonexistent-command'], + { env: { ...process.env, NODE_ENV: 'test' } } + ) + ).rejects.toMatchObject({ + code: 1, + }); + }); + + test('async command handler completes properly', async () => { + // The 'targets' command has an async handler and requires a .craft.yml + // Without proper await on parse(), this would exit before completing + // We expect it to fail due to missing config, but it should fail gracefully + // not due to premature exit + try { + await execFileAsync( + 'npx', + ['ts-node', '--transpile-only', CLI_ENTRY, 'targets'], + { + env: { ...process.env, NODE_ENV: 'test' }, + cwd: '/tmp', // No .craft.yml here + } + ); + } catch (error: any) { + // Should fail with a config error, not a silent exit or unhandled promise + expect(error.stderr || error.stdout).toMatch( + /Cannot find configuration file|craft\.yml|config/i + ); + } + }); }); diff --git a/src/index.ts b/src/index.ts index fb6c2e50..bc3c25dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,7 +65,7 @@ function fixGlobalBooleanFlags(argv: string[]): string[] { /** * Main entrypoint */ -function main(): void { +async function main(): Promise { printVersion(); readEnvironmentConfig(); @@ -74,7 +74,7 @@ function main(): void { const argv = fixGlobalBooleanFlags(process.argv.slice(2)); - yargs + await yargs() .parserConfiguration({ 'boolean-negation': false, }) diff --git a/yarn.lock b/yarn.lock index 508d81dd..e654e11e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2492,14 +2492,7 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^15.0.3": - version "15.0.20" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz" - integrity sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.8": +"@types/yargs@^17", "@types/yargs@^17.0.8": version "17.0.35" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz" integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== @@ -2692,7 +2685,7 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.1.0: +ansi-styles@^6.1.0, ansi-styles@^6.2.1: version "6.2.3" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== @@ -3060,15 +3053,6 @@ cli-table@0.3.1: dependencies: colors "1.0.3" -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - cliui@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" @@ -3078,6 +3062,15 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +cliui@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-9.0.1.tgz#6f7890f386f6f1f79953adc1f78dec46fcc2d291" + integrity sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w== + dependencies: + string-width "^7.2.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" @@ -3212,11 +3205,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - dedent@^1.0.0: version "1.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz" @@ -3321,6 +3309,11 @@ emittery@^0.13.1: resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== +emoji-regex@^10.3.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" + integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -3874,11 +3867,16 @@ gensync@^1.0.0-beta.2: resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.0, get-caller-file@^2.0.1, get-caller-file@^2.0.5: +get-caller-file@^2.0.0, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz#9bc4caa131702b4b61729cb7e42735bc550c9ee6" + integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== + get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" @@ -5513,11 +5511,6 @@ require-in-the-middle@^8.0.0: debug "^4.3.5" module-details-from-path "^1.0.3" -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" @@ -5620,11 +5613,6 @@ semver@^7.2.1, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3: resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - set-value@>=2.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" @@ -5788,6 +5776,15 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string-width@^7.0.0, string-width@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -5816,7 +5813,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: +strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== @@ -6161,11 +6158,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" @@ -6192,15 +6184,6 @@ wordwrap@^1.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" @@ -6219,6 +6202,15 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" +wrap-ansi@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" + integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -6252,11 +6244,6 @@ xtend@^4.0.0: resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - y18n@^5.0.5: version "5.0.8" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" @@ -6272,28 +6259,11 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@>=18.1.3, yargs-parser@^18.1.2, yargs-parser@^21.1.1: +yargs-parser@>=18.1.3, yargs-parser@^21.1.1, yargs-parser@^22.0.0: version "22.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8" integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw== -yargs@15.4.1: - version "15.4.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - yargs@^17.3.1: version "17.7.2" resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" @@ -6307,6 +6277,18 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" +yargs@^18: + version "18.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-18.0.0.tgz#6c84259806273a746b09f579087b68a3c2d25bd1" + integrity sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg== + dependencies: + cliui "^9.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + string-width "^7.2.0" + y18n "^5.0.5" + yargs-parser "^22.0.0" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"