From 02c9761f3d40327916ab745b9bcf71a06ed20f31 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Fri, 1 Mar 2024 22:37:37 -0600 Subject: [PATCH 01/15] move to typescript --- package.json | 23 +++++++++++++++++-- rollup.config.js | 12 +++++++--- source/{index.js => index.ts} | 0 source/{options.js => options.ts} | 0 source/{parser.js => parser.ts} | 0 source/{index.d.ts => types.ts} | 0 source/{utils.js => utils.ts} | 0 source/{validate.js => validate.ts} | 0 test/{_utils.js => _utils.ts} | 2 +- test/{build.js => build.ts} | 0 ...ture-with-help.js => fixture-with-help.ts} | 2 +- .../{fixture.js => fixture.ts} | 2 +- test/fixtures/{build.js => build.ts} | 2 +- test/fixtures/{fixture.js => fixture.ts} | 2 +- test/fixtures/help/{fixture.js => fixture.ts} | 2 +- test/fixtures/help/package.json | 2 +- ... fixture-conditional-required-multiple.ts} | 2 +- ...nction.js => fixture-required-function.ts} | 2 +- ...ltiple.js => fixture-required-multiple.ts} | 2 +- .../required/{fixture.js => fixture.ts} | 2 +- .../version/{fixture.js => fixture.ts} | 2 +- test/fixtures/version/package.json | 2 +- .../fixture.js => custom-bin/fixture.ts} | 2 +- .../with-package-json/custom-bin/package.json | 2 +- .../fixture.js => default/fixture.ts} | 2 +- .../with-package-json/default/package.json | 2 +- .../{default/fixture.js => no-bin/fixture.ts} | 2 +- test/flags/{_utils.js => _utils.ts} | 0 test/flags/{aliases.js => aliases.ts} | 0 ...nknown-flags.js => allow-unknown-flags.ts} | 4 ++-- ...{boolean-default.js => boolean-default.ts} | 0 test/flags/{choices.js => choices.ts} | 0 test/flags/{is-multiple.js => is-multiple.ts} | 0 test/flags/{is-required.js => is-required.ts} | 8 +++---- test/flags/{short-flag.js => short-flag.ts} | 0 test/flags/{test.js => test.ts} | 0 test/options/{help.js => help.ts} | 4 ++-- .../{import-meta.js => import-meta.ts} | 0 test/options/{infer-type.js => infer-type.ts} | 0 test/options/{pkg.js => pkg.ts} | 6 ++--- test/options/{version.js => version.ts} | 2 +- test/{test.js => test.ts} | 0 tsconfig.json | 22 +++++------------- 43 files changed, 66 insertions(+), 51 deletions(-) rename source/{index.js => index.ts} (100%) rename source/{options.js => options.ts} (100%) rename source/{parser.js => parser.ts} (100%) rename source/{index.d.ts => types.ts} (100%) rename source/{utils.js => utils.ts} (100%) rename source/{validate.js => validate.ts} (100%) rename test/{_utils.js => _utils.ts} (97%) rename test/{build.js => build.ts} (100%) rename test/fixtures/allow-unknown-flags/{fixture-with-help.js => fixture-with-help.ts} (94%) rename test/fixtures/allow-unknown-flags/{fixture.js => fixture.ts} (96%) rename test/fixtures/{build.js => build.ts} (96%) rename test/fixtures/{fixture.js => fixture.ts} (96%) rename test/fixtures/help/{fixture.js => fixture.ts} (93%) rename test/fixtures/required/{fixture-conditional-required-multiple.js => fixture-conditional-required-multiple.ts} (93%) rename test/fixtures/required/{fixture-required-function.js => fixture-required-function.ts} (96%) rename test/fixtures/required/{fixture-required-multiple.js => fixture-required-multiple.ts} (93%) rename test/fixtures/required/{fixture.js => fixture.ts} (95%) rename test/fixtures/version/{fixture.js => fixture.ts} (95%) rename test/fixtures/with-package-json/{no-bin/fixture.js => custom-bin/fixture.ts} (87%) rename test/fixtures/with-package-json/{custom-bin/fixture.js => default/fixture.ts} (87%) rename test/fixtures/with-package-json/{default/fixture.js => no-bin/fixture.ts} (87%) rename test/flags/{_utils.js => _utils.ts} (100%) rename test/flags/{aliases.js => aliases.ts} (100%) rename test/flags/{allow-unknown-flags.js => allow-unknown-flags.ts} (95%) rename test/flags/{boolean-default.js => boolean-default.ts} (100%) rename test/flags/{choices.js => choices.ts} (100%) rename test/flags/{is-multiple.js => is-multiple.ts} (100%) rename test/flags/{is-required.js => is-required.ts} (96%) rename test/flags/{short-flag.js => short-flag.ts} (100%) rename test/flags/{test.js => test.ts} (100%) rename test/options/{help.js => help.ts} (97%) rename test/options/{import-meta.js => import-meta.ts} (100%) rename test/options/{infer-type.js => infer-type.ts} (100%) rename test/options/{pkg.js => pkg.ts} (89%) rename test/options/{version.js => version.ts} (95%) rename test/{test.js => test.ts} (100%) diff --git a/package.json b/package.json index f000643..8c21a8d 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,12 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@sindresorhus/tsconfig": "^5.0.0", + "@types/common-tags": "^1.8.4", "@types/minimist": "^1.2.5", - "ava": "^6.1.1", + "@types/node": "18", + "@types/yargs-parser": "^21.0.3", + "ava": "^6.1.2", "camelcase-keys": "^9.1.3", "common-tags": "^2.0.0-alpha.1", "decamelize": "^6.0.0", @@ -81,9 +85,11 @@ "rollup": "^4.12.0", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-license": "^3.2.0", + "rollup-plugin-ts": "^3.4.5", "stack-utils": "^2.0.6", "trim-newlines": "^5.0.0", "tsd": "^0.30.7", + "tsimp": "^2.0.11", "type-fest": "^4.10.3", "typescript": "~5.3.3", "xo": "^0.57.0", @@ -92,10 +98,23 @@ "xo": { "rules": { "unicorn/no-process-exit": "off", - "unicorn/error-message": "off" + "unicorn/error-message": "off", + "@typescript-eslint/no-dynamic-delete": "off" }, "ignores": [ "build" ] + }, + "ava": { + "extensions": { + "ts": "module", + "js": true + }, + "nodeArguments": [ + "--import=tsimp" + ], + "environmentVariables": { + "TSIMP_DIAG": "ignore" + } } } diff --git a/rollup.config.js b/rollup.config.js index 534a397..68f4201 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,13 +1,14 @@ import fs from 'node:fs/promises'; +import {defineConfig} from 'rollup'; import {nodeResolve} from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import typescript from 'rollup-plugin-ts'; import json from '@rollup/plugin-json'; import license from 'rollup-plugin-license'; import {dts} from 'rollup-plugin-dts'; import {globby} from 'globby'; import {createTag, replaceResultTransformer} from 'common-tags'; import {delete_comments as deleteComments} from 'delete_comments'; -import {defineConfig} from 'rollup'; /** Matches empty lines: https://stackoverflow.com/q/16369642/10292952 */ const emptyLineRegex = /^\s*[\r\n]/gm; @@ -21,7 +22,9 @@ const sourceDirectory = 'source'; const outputDirectory = 'build'; const config = defineConfig({ - input: await globby(`${sourceDirectory}/**/*.js`), + input: await globby(`./${sourceDirectory}/**/*.ts`, { + ignore: [`./${sourceDirectory}/*.d.ts`, `./${sourceDirectory}/types.ts`], + }), output: { dir: outputDirectory, interop: 'esModule', @@ -51,6 +54,9 @@ const config = defineConfig({ include: 'node_modules/**', }), json(), + typescript({ + tsconfig: resolvedConfig => ({...resolvedConfig, declaration: false}), + }), license({ thirdParty: { output: `${outputDirectory}/licenses.md`, @@ -60,7 +66,7 @@ const config = defineConfig({ }); const dtsConfig = defineConfig({ - input: `./${sourceDirectory}/index.d.ts`, + input: `./${sourceDirectory}/index.ts`, output: { file: `./${outputDirectory}/index.d.ts`, format: 'es', diff --git a/source/index.js b/source/index.ts similarity index 100% rename from source/index.js rename to source/index.ts diff --git a/source/options.js b/source/options.ts similarity index 100% rename from source/options.js rename to source/options.ts diff --git a/source/parser.js b/source/parser.ts similarity index 100% rename from source/parser.js rename to source/parser.ts diff --git a/source/index.d.ts b/source/types.ts similarity index 100% rename from source/index.d.ts rename to source/types.ts diff --git a/source/utils.js b/source/utils.ts similarity index 100% rename from source/utils.js rename to source/utils.ts diff --git a/source/validate.js b/source/validate.ts similarity index 100% rename from source/validate.js rename to source/validate.ts diff --git a/test/_utils.js b/test/_utils.ts similarity index 97% rename from test/_utils.js rename to test/_utils.ts index 31cec2b..7dde13c 100644 --- a/test/_utils.js +++ b/test/_utils.ts @@ -6,7 +6,7 @@ import {readPackage} from 'read-pkg'; import {createTag, stripIndentTransformer, trimResultTransformer} from 'common-tags'; import StackUtils from 'stack-utils'; -export const defaultFixture = 'fixture.js'; +export const defaultFixture = 'fixture.ts'; const getFixture = fixture => fileURLToPath(new URL(`fixtures/${fixture}`, import.meta.url)); diff --git a/test/build.js b/test/build.ts similarity index 100% rename from test/build.js rename to test/build.ts diff --git a/test/fixtures/allow-unknown-flags/fixture-with-help.js b/test/fixtures/allow-unknown-flags/fixture-with-help.ts similarity index 94% rename from test/fixtures/allow-unknown-flags/fixture-with-help.js rename to test/fixtures/allow-unknown-flags/fixture-with-help.ts index f80eb4f..248e50c 100755 --- a/test/fixtures/allow-unknown-flags/fixture-with-help.js +++ b/test/fixtures/allow-unknown-flags/fixture-with-help.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/allow-unknown-flags/fixture.js b/test/fixtures/allow-unknown-flags/fixture.ts similarity index 96% rename from test/fixtures/allow-unknown-flags/fixture.js rename to test/fixtures/allow-unknown-flags/fixture.ts index f35939f..bfc11d5 100755 --- a/test/fixtures/allow-unknown-flags/fixture.js +++ b/test/fixtures/allow-unknown-flags/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../../source/index.js'; diff --git a/test/fixtures/build.js b/test/fixtures/build.ts similarity index 96% rename from test/fixtures/build.js rename to test/fixtures/build.ts index 83f7222..073a0d2 100755 --- a/test/fixtures/build.js +++ b/test/fixtures/build.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../build/index.js'; diff --git a/test/fixtures/fixture.js b/test/fixtures/fixture.ts similarity index 96% rename from test/fixtures/fixture.js rename to test/fixtures/fixture.ts index b51220c..4108704 100755 --- a/test/fixtures/fixture.js +++ b/test/fixtures/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../source/index.js'; diff --git a/test/fixtures/help/fixture.js b/test/fixtures/help/fixture.ts similarity index 93% rename from test/fixtures/help/fixture.js rename to test/fixtures/help/fixture.ts index 54dbf9a..bea97a8 100755 --- a/test/fixtures/help/fixture.js +++ b/test/fixtures/help/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/help/package.json b/test/fixtures/help/package.json index 7f75ec9..0341170 100644 --- a/test/fixtures/help/package.json +++ b/test/fixtures/help/package.json @@ -1,6 +1,6 @@ { "name": "foo", "version": "1.0.0", - "bin": "./fixture.js", + "bin": "./fixture.ts", "type": "module" } diff --git a/test/fixtures/required/fixture-conditional-required-multiple.js b/test/fixtures/required/fixture-conditional-required-multiple.ts similarity index 93% rename from test/fixtures/required/fixture-conditional-required-multiple.js rename to test/fixtures/required/fixture-conditional-required-multiple.ts index 43fbf56..571610e 100755 --- a/test/fixtures/required/fixture-conditional-required-multiple.js +++ b/test/fixtures/required/fixture-conditional-required-multiple.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/required/fixture-required-function.js b/test/fixtures/required/fixture-required-function.ts similarity index 96% rename from test/fixtures/required/fixture-required-function.js rename to test/fixtures/required/fixture-required-function.ts index 2a91b76..f3287b7 100755 --- a/test/fixtures/required/fixture-required-function.js +++ b/test/fixtures/required/fixture-required-function.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/required/fixture-required-multiple.js b/test/fixtures/required/fixture-required-multiple.ts similarity index 93% rename from test/fixtures/required/fixture-required-multiple.js rename to test/fixtures/required/fixture-required-multiple.ts index 51439ff..a7d2a80 100755 --- a/test/fixtures/required/fixture-required-multiple.js +++ b/test/fixtures/required/fixture-required-multiple.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/required/fixture.js b/test/fixtures/required/fixture.ts similarity index 95% rename from test/fixtures/required/fixture.js rename to test/fixtures/required/fixture.ts index d2f165c..86d6cda 100755 --- a/test/fixtures/required/fixture.js +++ b/test/fixtures/required/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import meow from '../../../source/index.js'; const cli = meow({ diff --git a/test/fixtures/version/fixture.js b/test/fixtures/version/fixture.ts similarity index 95% rename from test/fixtures/version/fixture.js rename to test/fixtures/version/fixture.ts index b28b332..ab3a244 100755 --- a/test/fixtures/version/fixture.js +++ b/test/fixtures/version/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../../source/index.js'; diff --git a/test/fixtures/version/package.json b/test/fixtures/version/package.json index 7f75ec9..0341170 100644 --- a/test/fixtures/version/package.json +++ b/test/fixtures/version/package.json @@ -1,6 +1,6 @@ { "name": "foo", "version": "1.0.0", - "bin": "./fixture.js", + "bin": "./fixture.ts", "type": "module" } diff --git a/test/fixtures/with-package-json/no-bin/fixture.js b/test/fixtures/with-package-json/custom-bin/fixture.ts similarity index 87% rename from test/fixtures/with-package-json/no-bin/fixture.js rename to test/fixtures/with-package-json/custom-bin/fixture.ts index a2fa84f..6250c62 100755 --- a/test/fixtures/with-package-json/no-bin/fixture.js +++ b/test/fixtures/with-package-json/custom-bin/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../../../source/index.js'; diff --git a/test/fixtures/with-package-json/custom-bin/package.json b/test/fixtures/with-package-json/custom-bin/package.json index 7a04580..81a2f43 100644 --- a/test/fixtures/with-package-json/custom-bin/package.json +++ b/test/fixtures/with-package-json/custom-bin/package.json @@ -1,7 +1,7 @@ { "name": "foo", "bin": { - "bar": "./fixture.js" + "bar": "./fixture.ts" }, "type": "module" } diff --git a/test/fixtures/with-package-json/custom-bin/fixture.js b/test/fixtures/with-package-json/default/fixture.ts similarity index 87% rename from test/fixtures/with-package-json/custom-bin/fixture.js rename to test/fixtures/with-package-json/default/fixture.ts index a2fa84f..6250c62 100755 --- a/test/fixtures/with-package-json/custom-bin/fixture.js +++ b/test/fixtures/with-package-json/default/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../../../source/index.js'; diff --git a/test/fixtures/with-package-json/default/package.json b/test/fixtures/with-package-json/default/package.json index e1bfb8d..eac038e 100644 --- a/test/fixtures/with-package-json/default/package.json +++ b/test/fixtures/with-package-json/default/package.json @@ -1,5 +1,5 @@ { "name": "foo", - "bin": "./fixture.js", + "bin": "./fixture.ts", "type": "module" } diff --git a/test/fixtures/with-package-json/default/fixture.js b/test/fixtures/with-package-json/no-bin/fixture.ts similarity index 87% rename from test/fixtures/with-package-json/default/fixture.js rename to test/fixtures/with-package-json/no-bin/fixture.ts index a2fa84f..6250c62 100755 --- a/test/fixtures/with-package-json/default/fixture.js +++ b/test/fixtures/with-package-json/no-bin/fixture.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env tsimp import process from 'node:process'; import meow from '../../../../source/index.js'; diff --git a/test/flags/_utils.js b/test/flags/_utils.ts similarity index 100% rename from test/flags/_utils.js rename to test/flags/_utils.ts diff --git a/test/flags/aliases.js b/test/flags/aliases.ts similarity index 100% rename from test/flags/aliases.js rename to test/flags/aliases.ts diff --git a/test/flags/allow-unknown-flags.js b/test/flags/allow-unknown-flags.ts similarity index 95% rename from test/flags/allow-unknown-flags.js rename to test/flags/allow-unknown-flags.ts index 4a459ba..cc2ff4d 100644 --- a/test/flags/allow-unknown-flags.js +++ b/test/flags/allow-unknown-flags.ts @@ -4,8 +4,8 @@ import {_verifyCli, stripIndentTrim, meowVersion} from '../_utils.js'; const fixtureFolder = 'allow-unknown-flags'; -const allowUnknownFlags = `${fixtureFolder}/fixture.js`; -const allowUnknownFlagsWithHelp = `${fixtureFolder}/fixture-with-help.js`; +const allowUnknownFlags = `${fixtureFolder}/fixture.ts`; +const allowUnknownFlagsWithHelp = `${fixtureFolder}/fixture-with-help.ts`; const verifyFlags = _verifyCli(allowUnknownFlags); diff --git a/test/flags/boolean-default.js b/test/flags/boolean-default.ts similarity index 100% rename from test/flags/boolean-default.js rename to test/flags/boolean-default.ts diff --git a/test/flags/choices.js b/test/flags/choices.ts similarity index 100% rename from test/flags/choices.js rename to test/flags/choices.ts diff --git a/test/flags/is-multiple.js b/test/flags/is-multiple.ts similarity index 100% rename from test/flags/is-multiple.js rename to test/flags/is-multiple.ts diff --git a/test/flags/is-required.js b/test/flags/is-required.ts similarity index 96% rename from test/flags/is-required.js rename to test/flags/is-required.ts index 1810585..4dadd88 100644 --- a/test/flags/is-required.js +++ b/test/flags/is-required.ts @@ -3,10 +3,10 @@ import {_verifyCli, stripIndentTrim} from '../_utils.js'; const fixtureFolder = 'required'; -const required = `${fixtureFolder}/fixture.js`; -const requiredFunction = `${fixtureFolder}/fixture-required-function.js`; -const requiredMultiple = `${fixtureFolder}/fixture-required-multiple.js`; -const conditionalRequiredMultiple = `${fixtureFolder}/fixture-conditional-required-multiple.js`; +const required = `${fixtureFolder}/fixture.ts`; +const requiredFunction = `${fixtureFolder}/fixture-required-function.ts`; +const requiredMultiple = `${fixtureFolder}/fixture-required-multiple.ts`; +const conditionalRequiredMultiple = `${fixtureFolder}/fixture-conditional-required-multiple.ts`; const verifyFlags = _verifyCli(required); diff --git a/test/flags/short-flag.js b/test/flags/short-flag.ts similarity index 100% rename from test/flags/short-flag.js rename to test/flags/short-flag.ts diff --git a/test/flags/test.js b/test/flags/test.ts similarity index 100% rename from test/flags/test.js rename to test/flags/test.ts diff --git a/test/options/help.js b/test/options/help.ts similarity index 97% rename from test/options/help.js rename to test/options/help.ts index f708d2b..ebcad63 100644 --- a/test/options/help.js +++ b/test/options/help.ts @@ -124,7 +124,7 @@ test('exits with code 0 by default', verifyCli, { }); test('showHelp exits with code 2 by default', verifyCli, { - fixture: 'help/fixture.js', + fixture: 'help/fixture.ts', args: '--show-help', error: { message: stripIndent` @@ -136,7 +136,7 @@ test('showHelp exits with code 2 by default', verifyCli, { }); test('showHelp exits with given code', verifyCli, { - fixture: 'help/fixture.js', + fixture: 'help/fixture.ts', args: '--show-help --code=0', expected: stripIndent` diff --git a/test/options/import-meta.js b/test/options/import-meta.ts similarity index 100% rename from test/options/import-meta.js rename to test/options/import-meta.ts diff --git a/test/options/infer-type.js b/test/options/infer-type.ts similarity index 100% rename from test/options/infer-type.js rename to test/options/infer-type.ts diff --git a/test/options/pkg.js b/test/options/pkg.ts similarity index 89% rename from test/options/pkg.js rename to test/options/pkg.ts index 3259a8f..adfb8c1 100644 --- a/test/options/pkg.js +++ b/test/options/pkg.ts @@ -4,7 +4,7 @@ import meow from '../../source/index.js'; const importMeta = import.meta; -const verifyPackage = _verifyCli('with-package-json/default/fixture.js'); +const verifyPackage = _verifyCli('with-package-json/default/fixture.ts'); test('description', t => { const cli = meow({ @@ -46,12 +46,12 @@ test('process title - bin default', verifyPackage, { }); test('process title - bin custom', verifyPackage, { - fixture: 'with-package-json/custom-bin/fixture.js', + fixture: 'with-package-json/custom-bin/fixture.ts', expected: 'bar', }); test('process title - name backup', verifyPackage, { - fixture: 'with-package-json/no-bin/fixture.js', + fixture: 'with-package-json/no-bin/fixture.ts', expected: 'foo', }); diff --git a/test/options/version.js b/test/options/version.ts similarity index 95% rename from test/options/version.js rename to test/options/version.ts index 576a6a6..0449dba 100644 --- a/test/options/version.js +++ b/test/options/version.ts @@ -47,7 +47,7 @@ test('manual showVersion', verifyVersion, { }); test('no version fallback message', verifyVersion, { - fixture: 'with-package-json/default/fixture.js', + fixture: 'with-package-json/default/fixture.ts', args: '--version', expected: 'No version found', }); diff --git a/test/test.js b/test/test.ts similarity index 100% rename from test/test.js rename to test/test.ts diff --git a/tsconfig.json b/tsconfig.json index 3a0a364..4f830c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,9 @@ { - "include": [ - "source/index.d.ts", - "test-d", - ], + "extends": "@sindresorhus/tsconfig", + "exclude": ["build", "node_modules"], + "include": ["source", "test", "test-d"], "compilerOptions": { - "strict": true, - "jsx": "react", - "target": "ES2021", // Node.js 16 - "lib": [ - "ES2021" - ], - "module": "ES2020", - "moduleResolution": "node", - "noImplicitAny": true, - "strictNullChecks": true, - "noUnusedLocals": true - } + "moduleResolution": "Bundler", + "module": "ES2022", + }, } From 648f69c7cd1ef42db2fb6fbf6714340b1be41c34 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Sun, 3 Mar 2024 11:20:49 -0600 Subject: [PATCH 02/15] add types --- .gitignore | 1 + package.json | 2 + rollup.config.js | 5 +- source/index.ts | 98 +++++++++++++++---- source/minimist-options.d.ts | 67 +++++++++++++ source/options.ts | 49 ++++++---- source/parser.ts | 49 ++++++---- source/types.ts | 76 +++++--------- source/utils.ts | 4 +- source/validate.ts | 58 ++++++----- test-d/index.ts | 11 +-- test-d/types.ts | 1 + test/_utils.ts | 39 ++++++-- .../required/fixture-required-function.ts | 9 +- test/fixtures/tsconfig.json | 6 ++ test/fixtures/version/fixture.ts | 4 +- test/flags/_utils.ts | 20 +++- test/flags/boolean-default.ts | 1 + test/flags/choices.ts | 2 + test/flags/short-flag.ts | 1 + test/flags/test.ts | 4 +- test/options/help.ts | 16 ++- test/options/import-meta.ts | 11 ++- test/options/infer-type.ts | 16 ++- test/options/version.ts | 6 +- 25 files changed, 386 insertions(+), 170 deletions(-) create mode 100644 source/minimist-options.d.ts create mode 100644 test-d/types.ts create mode 100644 test/fixtures/tsconfig.json diff --git a/.gitignore b/.gitignore index 547aa64..a6d40a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules build yarn.lock test-d/build.ts +.tsimp diff --git a/package.json b/package.json index 8c21a8d..947a671 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,12 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@sindresorhus/is": "^6.2.0", "@sindresorhus/tsconfig": "^5.0.0", "@types/common-tags": "^1.8.4", "@types/minimist": "^1.2.5", "@types/node": "18", + "@types/stack-utils": "^2.0.3", "@types/yargs-parser": "^21.0.3", "ava": "^6.1.2", "camelcase-keys": "^9.1.3", diff --git a/rollup.config.js b/rollup.config.js index 68f4201..1caf60d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -79,9 +79,10 @@ const dtsConfig = defineConfig({ name: 'copy-tsd', async generateBundle() { let tsdFile = await fs.readFile('./test-d/index.ts', 'utf8'); + tsdFile = tsdFile.replace( - `import meow from '../${sourceDirectory}/index.js'`, - `import meow from '../${outputDirectory}/index.js'`, + `../${sourceDirectory}/index.js`, + `../${outputDirectory}/index.js`, ); await fs.writeFile(`./test-d/${outputDirectory}.ts`, tsdFile); diff --git a/source/index.ts b/source/index.ts index cc1be39..9bcd825 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,14 +1,20 @@ import process from 'node:process'; -import parseArguments from 'yargs-parser'; +import parseArguments, {type Options as ParserOptions} from 'yargs-parser'; import camelCaseKeys from 'camelcase-keys'; import {trimNewlines} from 'trim-newlines'; import redent from 'redent'; import {buildOptions} from './options.js'; import {buildParserOptions} from './parser.js'; import {validate, checkUnknownFlags, checkMissingRequiredFlags} from './validate.js'; - -const buildResult = ({pkg: packageJson, ...options}, parserOptions) => { - const argv = parseArguments(options.argv, parserOptions); +import type { + Options, + ParsedOptions, + Result, + AnyFlags, +} from './types.js'; + +const buildResult = ({pkg: packageJson, ...options}: ParsedOptions, parserOptions: ParserOptions): Result => { + const {_: input, ...argv} = parseArguments(options.argv as string[], parserOptions); let help = ''; if (options.help) { @@ -32,7 +38,7 @@ const buildResult = ({pkg: packageJson, ...options}, parserOptions) => { help += '\n'; - const showHelp = code => { + const showHelp = (code?: number) => { console.log(help); process.exit(typeof code === 'number' ? code : 2); // Default to code 2 for incorrect usage (#47) }; @@ -42,17 +48,14 @@ const buildResult = ({pkg: packageJson, ...options}, parserOptions) => { process.exit(0); }; - if (argv._.length === 0 && options.argv.length === 1) { - if (argv.version === true && options.autoVersion) { + if (input.length === 0 && options.argv.length === 1) { + if (argv['version'] === true && options.autoVersion) { showVersion(); - } else if (argv.help === true && options.autoHelp) { + } else if (argv['help'] === true && options.autoHelp) { showHelp(0); } } - const input = argv._; - delete argv._; - if (!options.allowUnknownFlags) { checkUnknownFlags(input); } @@ -69,12 +72,14 @@ const buildResult = ({pkg: packageJson, ...options}, parserOptions) => { } } - delete flags[flagValue.shortFlag]; + if (flagValue.shortFlag) { + delete flags[flagValue.shortFlag]; + } } - checkMissingRequiredFlags(options.flags, flags, input); + checkMissingRequiredFlags(options.flags, flags, input as string[]); - return { + const result = { input, flags, unnormalizedFlags, @@ -83,16 +88,67 @@ const buildResult = ({pkg: packageJson, ...options}, parserOptions) => { showHelp, showVersion, }; + + return result as unknown as Result; }; -const meow = (helpText, options = {}) => { - const parsedOptions = buildOptions(helpText, options); +/** +@param helpMessage - Shortcut for the `help` option. + +@example +``` +#!/usr/bin/env node +import meow from 'meow'; +import foo from './index.js'; + +const cli = meow(` + Usage + $ foo + + Options + --rainbow, -r Include a rainbow + + Examples + $ foo unicorns --rainbow + 🌈 unicorns 🌈 +`, { + importMeta: import.meta, // This is required + flags: { + rainbow: { + type: 'boolean', + shortFlag: 'r' + } + } +}); + +//{ +// input: ['unicorns'], +// flags: {rainbow: true}, +// ... +//} + +foo(cli.input.at(0), cli.flags); +``` +*/ +export default function meow(helpMessage: string, options: Options): Result; +export default function meow(options: Options): Result; + +export default function meow(helpMessage: string | Options, options?: Options): Result { + if (typeof helpMessage !== 'string') { + options = helpMessage; + helpMessage = ''; + } + + const parsedOptions = buildOptions(helpMessage, options!); const parserOptions = buildParserOptions(parsedOptions); - const result = buildResult(parsedOptions, parserOptions); + const result = buildResult(parsedOptions, parserOptions); - process.title = result.pkg.bin ? Object.keys(result.pkg.bin).at(0) : result.pkg.name; + const packageTitle = result.pkg.bin ? Object.keys(result.pkg.bin).at(0) : result.pkg.name; - return result; -}; + // TODO: move to separate PR? + if (packageTitle) { + process.title = packageTitle; + } -export default meow; + return result; +} diff --git a/source/minimist-options.d.ts b/source/minimist-options.d.ts new file mode 100644 index 0000000..bcf4364 --- /dev/null +++ b/source/minimist-options.d.ts @@ -0,0 +1,67 @@ +/* eslint-disable import/no-extraneous-dependencies */ +declare module 'minimist-options' { + import type {Opts as MinimistOptions} from 'minimist'; + + export type {Opts as MinimistOptions} from 'minimist'; + + export type OptionType = 'string' | 'boolean' | 'number' | 'array' | 'string-array' | 'boolean-array' | 'number-array'; + + export type BaseOption< + TypeOptionType extends OptionType, + DefaultOptionType, + > = { + /** + * The data type the option should be parsed to. + */ + readonly type?: TypeOptionType; + + /** + * An alias/list of aliases for the option. + */ + readonly alias?: string | readonly string[]; + + /** + * The default value for the option. + */ + readonly default?: DefaultOptionType; + }; + + export type StringOption = BaseOption<'string', string>; + export type BooleanOption = BaseOption<'boolean', boolean>; + export type NumberOption = BaseOption<'number', number>; + export type DefaultArrayOption = BaseOption<'array', readonly string[]>; + export type StringArrayOption = BaseOption<'string-array', readonly string[]>; + export type BooleanArrayOption = BaseOption<'boolean-array', readonly boolean[]>; + export type NumberArrayOption = BaseOption<'number-array', readonly number[]>; + + export type AnyOption = ( + | StringOption + | BooleanOption + | NumberOption + | DefaultArrayOption + | StringArrayOption + | BooleanArrayOption + | NumberArrayOption + ); + + export type MinimistOption = Pick; + + export type Options = MinimistOption & { + [key: string]: ( + | OptionType + | StringOption + | BooleanOption + | NumberOption + | DefaultArrayOption + | StringArrayOption + | BooleanArrayOption + | NumberArrayOption + ); + arguments?: string; + }; + + /** + * Write options for [minimist](https://npmjs.org/package/minimist) in a comfortable way. Support string, boolean, number and array options. + */ + export default function buildOptions(options?: Options): MinimistOptions; +} diff --git a/source/options.ts b/source/options.ts index 6531f29..87ef76c 100644 --- a/source/options.ts +++ b/source/options.ts @@ -1,12 +1,27 @@ import process from 'node:process'; import {dirname} from 'node:path'; import {fileURLToPath} from 'node:url'; -import {readPackageUpSync} from 'read-package-up'; +import {readPackageUpSync, type PackageJson} from 'read-package-up'; import normalizePackageData from 'normalize-package-data'; import {decamelizeFlagKey, joinFlagKeys} from './utils.js'; +import type { + Options, + ParsedOptions, + AnyFlag, + AnyFlags, +} from './types.js'; -const validateOptions = options => { - const invalidOptionFilters = { +type InvalidOptionFilter = { + filter: (flag: [flagKey: string, flag: AnyFlag]) => boolean; + message: (flagKeys: string[]) => string; +}; + +type InvalidOptionFilters = { + flags: Record; +}; + +const validateOptions = (options: ParsedOptions): void => { + const invalidOptionFilters: InvalidOptionFilters = { flags: { keyContainsDashes: { filter: ([flagKey]) => flagKey.includes('-') && flagKey !== '--', @@ -21,22 +36,23 @@ const validateOptions = options => { message: flagKeys => `The option \`choices\` must be an array. Invalid flags: ${joinFlagKeys(flagKeys)}`, }, choicesNotMatchFlagType: { - filter: ([, flag]) => flag.type && Array.isArray(flag.choices) && flag.choices.some(choice => typeof choice !== flag.type), + filter: ([, flag]) => flag.type !== undefined && Array.isArray(flag.choices) && flag.choices.some(choice => typeof choice !== flag.type), message(flagKeys) { - const flagKeysAndTypes = flagKeys.map(flagKey => `(\`${decamelizeFlagKey(flagKey)}\`, type: '${options.flags[flagKey].type}')`); + const flagKeysAndTypes = flagKeys.map(flagKey => `(\`${decamelizeFlagKey(flagKey)}\`, type: '${options.flags[flagKey]!.type}')`); return `Each value of the option \`choices\` must be of the same type as its flag. Invalid flags: ${flagKeysAndTypes.join(', ')}`; }, }, defaultNotInChoices: { - filter: ([, flag]) => flag.default && Array.isArray(flag.choices) && ![flag.default].flat().every(value => flag.choices.includes(value)), + filter: ([, flag]) => flag.default !== undefined && Array.isArray(flag.choices) && ![flag.default].flat().every(value => flag.choices!.includes(value as never)), // TODO: never? message: flagKeys => `Each value of the option \`default\` must exist within the option \`choices\`. Invalid flags: ${joinFlagKeys(flagKeys)}`, }, }, }; const errorMessages = []; + type Entry = ['flags', Record]; - for (const [optionKey, filters] of Object.entries(invalidOptionFilters)) { + for (const [optionKey, filters] of Object.entries(invalidOptionFilters) as Entry[]) { const optionEntries = Object.entries(options[optionKey]); for (const {filter, message} of Object.values(filters)) { @@ -54,17 +70,12 @@ const validateOptions = options => { } }; -export const buildOptions = (helpText, options) => { - if (typeof helpText !== 'string') { - options = helpText; - helpText = ''; - } - - if (!options.importMeta?.url) { +export const buildOptions = (helpMessage: string, options: Options): ParsedOptions => { + if (!options?.importMeta?.url) { throw new TypeError('The `importMeta` option is required. Its value must be `import.meta`.'); } - const foundPackage = options.pkg ?? readPackageUpSync({ + const foundPackage = options.pkg as PackageJson ?? readPackageUpSync({ cwd: dirname(fileURLToPath(options.importMeta.url)), normalize: false, })?.packageJson; @@ -73,14 +84,14 @@ export const buildOptions = (helpText, options) => { const pkg = foundPackage ?? {}; normalizePackageData(pkg); - const parsedOptions = { + const parsedOptions: ParsedOptions = { argv: process.argv.slice(2), flags: {}, inferType: false, - input: 'string', + input: 'string', // TODO: undocumented option? description: pkg.description ?? false, - help: helpText, - version: pkg.version || 'No version found', + help: helpMessage, + version: pkg.version || 'No version found', // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing autoHelp: true, autoVersion: true, booleanDefault: false, diff --git a/source/parser.ts b/source/parser.ts index 7dc7e68..accbb10 100644 --- a/source/parser.ts +++ b/source/parser.ts @@ -1,11 +1,21 @@ -import constructParserOptions from 'minimist-options'; +import constructParserOptions, { + type Options as ParserFlags, + type MinimistOptions, + type AnyOption as ParserFlag, +} from 'minimist-options'; +import type {Options as YargsOptions} from 'yargs-parser'; import decamelizeKeys from 'decamelize-keys'; +import type {Writable} from 'type-fest'; +import type {ParsedOptions, AnyFlag} from './types.js'; -const buildParserFlags = ({flags, booleanDefault}) => { - const parserFlags = {}; +type ParserOptions = YargsOptions & MinimistOptions; + +const buildParserFlags = ({flags, booleanDefault}: ParsedOptions): ParserFlags => { + const parserFlags: ParserFlags = {}; for (const [flagKey, flagValue] of Object.entries(flags)) { - const flag = {...flagValue}; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const flag = {...flagValue} as Writable; // `minimist-options` expects `flag.alias` if (flag.shortFlag) { @@ -14,10 +24,14 @@ const buildParserFlags = ({flags, booleanDefault}) => { } if (booleanDefault !== undefined && flag.type === 'boolean' && !Object.hasOwn(flag, 'default')) { + // TODO: + // @ts-expect-error: not sure flag.default = flag.isMultiple ? [booleanDefault] : booleanDefault; } if (flag.isMultiple) { + // TODO: + // @ts-expect-error: doesn't allow array types? flag.type = flag.type ? `${flag.type}-array` : 'array'; flag.default ??= []; delete flag.isMultiple; @@ -25,41 +39,40 @@ const buildParserFlags = ({flags, booleanDefault}) => { if (Array.isArray(flag.aliases)) { if (flag.alias) { - flag.aliases.push(flag.alias); + flag.aliases.push(flag.alias as string); } flag.alias = flag.aliases; delete flag.aliases; } - parserFlags[flagKey] = flag; + parserFlags[flagKey] = flag as ParserFlag; } return parserFlags; }; -export const buildParserOptions = options => { - let parserOptions = buildParserFlags(options); - parserOptions.arguments = options.input; - - parserOptions = decamelizeKeys(parserOptions, {separator: '-', exclude: ['stopEarly', '--']}); +export const buildParserOptions = (options: ParsedOptions): YargsOptions => { + let parserFlags = buildParserFlags(options); - if (options.inferType) { - delete parserOptions.arguments; + if (!options.inferType) { + parserFlags.arguments = options.input; } + parserFlags = decamelizeKeys(parserFlags, {separator: '-', exclude: ['stopEarly', '--']}); + // Add --help and --version to known flags if autoHelp or autoVersion are set if (!options.allowUnknownFlags) { - if (options.autoHelp && !parserOptions.help) { - parserOptions.help = {type: 'boolean'}; + if (options.autoHelp && !parserFlags['help']) { + parserFlags['help'] = {type: 'boolean'}; } - if (options.autoVersion && !parserOptions.version) { - parserOptions.version = {type: 'boolean'}; + if (options.autoVersion && !parserFlags['version']) { + parserFlags['version'] = {type: 'boolean'}; } } - parserOptions = constructParserOptions(parserOptions); + const parserOptions = constructParserOptions(parserFlags) as ParserOptions; parserOptions.configuration = { ...parserOptions.configuration, diff --git a/source/types.ts b/source/types.ts index 6299975..e74109f 100644 --- a/source/types.ts +++ b/source/types.ts @@ -5,6 +5,9 @@ import type { export type FlagType = 'string' | 'boolean' | 'number'; +export type ParsedFlag = string | boolean | number; +export type ParsedFlags = Readonly>; + /** Callback function to determine if a flag is required during runtime. @@ -108,8 +111,8 @@ export type Flag = { type StringFlag = Flag<'string', string> | Flag<'string', string[], true>; type BooleanFlag = Flag<'boolean', boolean> | Flag<'boolean', boolean[], true>; type NumberFlag = Flag<'number', number> | Flag<'number', number[], true>; -type AnyFlag = StringFlag | BooleanFlag | NumberFlag; -type AnyFlags = Record; +export type AnyFlag = StringFlag | BooleanFlag | NumberFlag; +export type AnyFlags = Record; export type Options = { /** @@ -315,21 +318,28 @@ export type Options = { readonly helpIndent?: number; }; +type BooleanDefault = Pick, 'booleanDefault'>; + +export type ParsedOptions = Required, 'booleanDefault' | 'hardRejection'>> & BooleanDefault & { + input: string; + allowParentFlags: boolean; +}; + type TypedFlag = - Flag extends {type: 'number'} - ? number - : Flag extends {type: 'string'} - ? string - : Flag extends {type: 'boolean'} - ? boolean - : unknown; + Flag extends {type: 'number'} + ? number + : Flag extends {type: 'string'} + ? string + : Flag extends {type: 'boolean'} + ? boolean + : unknown; type PossiblyOptionalFlag = - Flag extends {isRequired: true} + Flag extends {isRequired: true} + ? FlagType + : Flag extends {default: any} ? FlagType - : Flag extends {default: any} - ? FlagType - : FlagType | undefined; + : FlagType | undefined; export type TypedFlags = { [F in keyof Flags]: Flags[F] extends {isMultiple: true} @@ -375,43 +385,3 @@ export type Result = { */ showVersion: () => void; }; -/** -@param helpMessage - Shortcut for the `help` option. - -@example -``` -#!/usr/bin/env node -import meow from 'meow'; -import foo from './index.js'; - -const cli = meow(` - Usage - $ foo - - Options - --rainbow, -r Include a rainbow - - Examples - $ foo unicorns --rainbow - 🌈 unicorns 🌈 -`, { - importMeta: import.meta, // This is required - flags: { - rainbow: { - type: 'boolean', - shortFlag: 'r' - } - } -}); - -//{ -// input: ['unicorns'], -// flags: {rainbow: true}, -// ... -//} - -foo(cli.input.at(0), cli.flags); -``` -*/ -export default function meow(helpMessage: string, options: Options): Result; -export default function meow(options: Options): Result; diff --git a/source/utils.ts b/source/utils.ts index 6325b1b..caa73e3 100644 --- a/source/utils.ts +++ b/source/utils.ts @@ -1,5 +1,5 @@ import decamelize from 'decamelize'; -export const decamelizeFlagKey = flagKey => `--${decamelize(flagKey, {separator: '-'})}`; +export const decamelizeFlagKey = (flagKey: string) => `--${decamelize(flagKey, {separator: '-'})}`; -export const joinFlagKeys = (flagKeys, prefix = '--') => `\`${prefix}${flagKeys.join(`\`, \`${prefix}`)}\``; +export const joinFlagKeys = (flagKeys: string[], prefix = '--') => `\`${prefix}${flagKeys.join(`\`, \`${prefix}`)}\``; diff --git a/source/validate.ts b/source/validate.ts index effba37..58d1be6 100644 --- a/source/validate.ts +++ b/source/validate.ts @@ -1,7 +1,19 @@ import process from 'node:process'; import {decamelizeFlagKey} from './utils.js'; - -const validateFlags = (flags, options) => { +import type { + AnyFlag, + AnyFlags, + ParsedOptions, + ParsedFlags, +} from './types.js'; + +type FlagInput = string | number; +type Flags = Record; +type Options = Omit; +type DefinedFlags = Options['flags']; +type RequiredFlag = DefinedFlags[keyof DefinedFlags] & {key: string}; + +const validateFlags = (flags: Flags, options: Options): void => { for (const [flagKey, flagValue] of Object.entries(options.flags)) { if (flagKey !== '--' && !flagValue.isMultiple && Array.isArray(flags[flagKey])) { throw new Error(`The flag --${flagKey} can only be set once.`); @@ -9,7 +21,7 @@ const validateFlags = (flags, options) => { } }; -const validateChoicesByFlag = (flagKey, flagValue, receivedInput) => { +const validateChoicesByFlag = (flagKey: string, flagValue: AnyFlag, receivedInput: FlagInput | FlagInput[] | undefined): string | void => { const {choices, isRequired} = flagValue; if (!choices) { @@ -27,19 +39,19 @@ const validateChoicesByFlag = (flagKey, flagValue, receivedInput) => { } if (Array.isArray(receivedInput)) { - const unknownValues = receivedInput.filter(index => !choices.includes(index)); + const unknownValues = receivedInput.filter(index => !choices.includes(index as never)); // TODO: never? if (unknownValues.length > 0) { const valuesText = unknownValues.length > 1 ? 'values' : 'value'; return `Unknown ${valuesText} for flag \`${decamelizeFlagKey(flagKey)}\`: \`${unknownValues.join('`, `')}\`. ${valueMustBeOneOf}`; } - } else if (!choices.includes(receivedInput)) { + } else if (!choices.includes(receivedInput as never)) { return `Unknown value for flag \`${decamelizeFlagKey(flagKey)}\`: \`${receivedInput}\`. ${valueMustBeOneOf}`; } }; -const validateChoices = (flags, receivedFlags) => { +const validateChoices = (flags: DefinedFlags, receivedFlags: Flags): void => { const errors = []; for (const [flagKey, flagValue] of Object.entries(flags)) { @@ -56,60 +68,62 @@ const validateChoices = (flags, receivedFlags) => { } }; -export const validate = (flags, options) => { +export const validate = (flags: Flags, options: Options): void => { validateFlags(flags, options); validateChoices(options.flags, flags); }; -const reportUnknownFlags = unknownFlags => { +const reportUnknownFlags = (unknownFlags: string[]): void => { console.error([ `Unknown flag${unknownFlags.length > 1 ? 's' : ''}`, ...unknownFlags, ].join('\n')); }; -export const checkUnknownFlags = input => { - const unknownFlags = input.filter(item => typeof item === 'string' && item.startsWith('-')); +export const checkUnknownFlags = (input: Array): void => { + const unknownFlags = input.filter((item): item is string => typeof item === 'string' && item.startsWith('-')); if (unknownFlags.length > 0) { reportUnknownFlags(unknownFlags); process.exit(2); } }; -const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { - const flag = definedFlags[flagName]; +const isFlagMissing = (flagName: string, flag: AnyFlag, receivedFlags: ParsedFlags, input: string[]): boolean => { let isFlagRequired = true; if (typeof flag.isRequired === 'function') { - isFlagRequired = flag.isRequired(receivedFlags, input); + isFlagRequired = flag.isRequired(receivedFlags as AnyFlags, input); if (typeof isFlagRequired !== 'boolean') { throw new TypeError(`Return value for isRequired callback should be of type boolean, but ${typeof isFlagRequired} was returned.`); } } - if (receivedFlags[flagName] === undefined) { + const receivedFlag = receivedFlags[flagName]; + + if (receivedFlag === undefined) { return isFlagRequired; } - return flag.isMultiple && receivedFlags[flagName].length === 0 && isFlagRequired; + return Boolean(flag.isMultiple) && (receivedFlag as any[]).length === 0 && isFlagRequired; }; -const reportMissingRequiredFlags = missingRequiredFlags => { +const reportMissingRequiredFlags = (missingRequiredFlags: RequiredFlag[]): void => { console.error(`Missing required flag${missingRequiredFlags.length > 1 ? 's' : ''}`); for (const flag of missingRequiredFlags) { console.error(`\t${decamelizeFlagKey(flag.key)}${flag.shortFlag ? `, -${flag.shortFlag}` : ''}`); } }; -export const checkMissingRequiredFlags = (flags, receivedFlags, input) => { - const missingRequiredFlags = []; +export const checkMissingRequiredFlags = (flags: DefinedFlags, receivedFlags: ParsedFlags, input: string[]): void => { if (flags === undefined) { - return []; + return; } - for (const flagName of Object.keys(flags)) { - if (flags[flagName].isRequired && isFlagMissing(flagName, flags, receivedFlags, input)) { - missingRequiredFlags.push({key: flagName, ...flags[flagName]}); + const missingRequiredFlags: RequiredFlag[] = []; + + for (const [flagName, flag] of Object.entries(flags)) { + if (flag.isRequired && isFlagMissing(flagName, flag, receivedFlags, input)) { + missingRequiredFlags.push({key: flagName, ...flag}); } } diff --git a/test-d/index.ts b/test-d/index.ts index 842272d..3080517 100644 --- a/test-d/index.ts +++ b/test-d/index.ts @@ -1,8 +1,7 @@ import {expectAssignable, expectError, expectType} from 'tsd'; import type {PackageJson} from 'type-fest'; -import meow, {type Result} from '../source/index.js'; - -type AnyFlag = NonNullable[0]>['flags']>[string]; +import meow from '../source/index.js'; +import type {Result, AnyFlag} from './types.js'; const importMeta = import.meta; @@ -71,10 +70,10 @@ expectType(result.flags.bar); expectType(result.flags.abc); expectType(result.flags.baz); expectType(result.unnormalizedFlags.foo); -expectType(result.unnormalizedFlags.f); +expectType(result.unnormalizedFlags['f']); expectType(result.unnormalizedFlags['foo-bar']); -expectType(result.unnormalizedFlags.foobar); -expectType(result.unnormalizedFlags.fooBar); +expectType(result.unnormalizedFlags['foobar']); +expectType(result.unnormalizedFlags['fooBar']); expectType(result.unnormalizedFlags.bar); expectType(result.unnormalizedFlags.abc); expectType(result.unnormalizedFlags.baz); diff --git a/test-d/types.ts b/test-d/types.ts new file mode 100644 index 0000000..a068f77 --- /dev/null +++ b/test-d/types.ts @@ -0,0 +1 @@ +export type {Result, AnyFlag} from '../source/types.js'; diff --git a/test/_utils.ts b/test/_utils.ts index 7dde13c..270a433 100644 --- a/test/_utils.ts +++ b/test/_utils.ts @@ -1,16 +1,26 @@ /* eslint-disable ava/no-ignored-test-files */ import {fileURLToPath} from 'node:url'; import test from 'ava'; -import {execa} from 'execa'; +import { + execa, + type ExecaChildProcess, + type ExecaReturnValue, + type Options as ExecaOptions, +} from 'execa'; import {readPackage} from 'read-pkg'; import {createTag, stripIndentTransformer, trimResultTransformer} from 'common-tags'; import StackUtils from 'stack-utils'; +import type {RequireOneOrNone} from 'type-fest'; +import type {Options, AnyFlags} from '../source/types.js'; export const defaultFixture = 'fixture.ts'; -const getFixture = fixture => fileURLToPath(new URL(`fixtures/${fixture}`, import.meta.url)); +const getFixture = (fixture: string): string => fileURLToPath(new URL(`fixtures/${fixture}`, import.meta.url)); -export const spawnFixture = async (fixture = defaultFixture, arguments_ = [], options = {}) => { +export async function spawnFixture(arguments_: string[]): Promise; +export async function spawnFixture(fixture: string, arguments_?: string[], options?: ExecaOptions): Promise; + +export async function spawnFixture(fixture: string | string[] = defaultFixture, arguments_: string[] = [], options: ExecaOptions = {}): Promise { // Allow calling with arguments first if (Array.isArray(fixture)) { arguments_ = fixture; @@ -18,7 +28,7 @@ export const spawnFixture = async (fixture = defaultFixture, arguments_ = [], op } return execa(getFixture(fixture), arguments_, options); -}; +} export {stripIndent} from 'common-tags'; @@ -33,13 +43,28 @@ export const meowVersion = meowPackage.version; const stackUtils = new StackUtils(); -export const stackToErrorMessage = stack => stackUtils.clean(stack).split('\n').at(0); +export const stackToErrorMessage = (stack: string) => stackUtils.clean(stack).split('\n').at(0); + +export type MeowOptions = Omit, 'importMeta'>; + +type VerifyCliMacroArguments = [{ + fixture?: string; + args?: string; + execaOptions?: ExecaOptions; +} & RequireOneOrNone<{ + expected: string; + error: string | { + message: string; + code: number; + clean?: boolean; + }; +}, 'expected' | 'error'>]; -export const _verifyCli = (baseFixture = defaultFixture) => test.macro( +export const _verifyCli = (baseFixture = defaultFixture) => test.macro( async (t, {fixture = baseFixture, args, execaOptions, expected, error}) => { const assertions = await t.try(async tt => { const arguments_ = args ? args.split(' ') : []; - const {all: output, exitCode} = await spawnFixture(fixture, arguments_, {reject: false, all: true, ...execaOptions}); + const {all: output, exitCode} = await spawnFixture(fixture, arguments_, {reject: false, all: true, ...execaOptions}) as ExecaReturnValue & {all: string}; tt.log('args:', arguments_); if (error) { diff --git a/test/fixtures/required/fixture-required-function.ts b/test/fixtures/required/fixture-required-function.ts index f3287b7..ec89d24 100755 --- a/test/fixtures/required/fixture-required-function.ts +++ b/test/fixtures/required/fixture-required-function.ts @@ -15,6 +15,8 @@ const cli = meow({ }, withTrigger: { type: 'string', + // TODO: change type to allow truthy / falsy values? + // @ts-expect-error: falsy trigger is still boolean isRequired: (flags, _) => flags.trigger, }, allowError: { @@ -23,11 +25,12 @@ const cli = meow({ }, shouldError: { type: 'boolean', - isRequired: (flags, _) => - flags.allowError ? 'should error' : false - , + // @ts-expect-error: invalid string return + isRequired: (flags, _) => flags.allowError ? 'should error' : false, }, }, }); +// TODO: errors above make flags untyped +// eslint-disable-next-line @typescript-eslint/restrict-template-expressions console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`); diff --git a/test/fixtures/tsconfig.json b/test/fixtures/tsconfig.json new file mode 100644 index 0000000..80e8fa8 --- /dev/null +++ b/test/fixtures/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/test/fixtures/version/fixture.ts b/test/fixtures/version/fixture.ts index ab3a244..3ec4e8b 100755 --- a/test/fixtures/version/fixture.ts +++ b/test/fixtures/version/fixture.ts @@ -2,14 +2,14 @@ import process from 'node:process'; import meow from '../../../source/index.js'; -const version = process.env.VERSION === 'false' ? false : process.env.VERSION; +const version = (process.env.VERSION === 'false' ? false : process.env.VERSION) as string | undefined; const options = { importMeta: import.meta, version, autoVersion: !process.argv.includes('--no-auto-version'), flags: { - showVersion: {type: 'boolean'}, + showVersion: {type: 'boolean'} as const, }, }; diff --git a/test/flags/_utils.ts b/test/flags/_utils.ts index 69f20c9..df7219c 100644 --- a/test/flags/_utils.ts +++ b/test/flags/_utils.ts @@ -1,13 +1,27 @@ /* eslint-disable ava/no-ignored-test-files */ import test from 'ava'; +import type {RequireOneOrNone} from 'type-fest'; +import type {MeowOptions as _MeowOptions} from '../_utils.js'; +import type {AnyFlags} from '../../source/types.js'; import meow from '../../source/index.js'; -export const _verifyFlags = importMeta => test.macro(async (t, {flags = {}, args, expected, error, ...meowOptions}) => { +type MeowOptions = Omit<_MeowOptions, 'argv' | 'flags'>; + +type VerifyFlagsMacroArguments = [{ + [option: string]: MeowOptions[keyof MeowOptions]; + flags?: AnyFlags; + args?: string; +} & RequireOneOrNone<{ + expected: Record; + error: string; +}, 'expected' | 'error'>]; + +export const _verifyFlags = (importMeta: ImportMeta) => test.macro(async (t, {flags = {}, args, expected, error, ..._meowOptions}) => { const assertions = await t.try(async tt => { const arguments_ = args?.split(' ') ?? []; - meowOptions = { - ...meowOptions, + const meowOptions = { + ..._meowOptions, importMeta, argv: arguments_, flags, diff --git a/test/flags/boolean-default.ts b/test/flags/boolean-default.ts index ac54256..70359d4 100644 --- a/test/flags/boolean-default.ts +++ b/test/flags/boolean-default.ts @@ -46,6 +46,7 @@ test('boolean args are false by default', verifyFlags, { }, }); +// @ts-expect-error: invalid booleanDefault test('throws if default is null', verifyFlags, { booleanDefault: null, flags: { diff --git a/test/flags/choices.ts b/test/flags/choices.ts index f210c2c..9f2a228 100644 --- a/test/flags/choices.ts +++ b/test/flags/choices.ts @@ -38,6 +38,7 @@ test('throws if input does not match choices', verifyChoices, { `, }); +// @ts-expect-error: invalid choices test('throws if choices is not array', verifyChoices, { flags: { animal: { @@ -123,6 +124,7 @@ test('throws with multiple flags', verifyChoices, { `, }); +// @ts-expect-error: invalid choices test('choices must be of the same type', verifyChoices, { flags: { number: { diff --git a/test/flags/short-flag.ts b/test/flags/short-flag.ts index 311de00..1c961e4 100644 --- a/test/flags/short-flag.ts +++ b/test/flags/short-flag.ts @@ -59,6 +59,7 @@ test('grouped flags work', t => { }); }); +// @ts-expect-error: unknown key alias test('suggests renaming alias to shortFlag', verifyFlags, { flags: { foo: { diff --git a/test/flags/test.ts b/test/flags/test.ts index ae57a1b..afcd82a 100644 --- a/test/flags/test.ts +++ b/test/flags/test.ts @@ -31,6 +31,7 @@ test('supports negation via --no', verifyFlags, { }, }); +// @ts-expect-error: invalid default test('throws if default value is not of the correct type', verifyFlags, { flags: { foo: { @@ -80,7 +81,7 @@ test('default - no flag', verifyFlags, { test('single character flag casing should be preserved', verifyFlags, { args: '-F', expected: { - F: true, + F: true, // eslint-disable-line @typescript-eslint/naming-convention }, }); @@ -103,6 +104,7 @@ test('single flag set more than once is an error', verifyFlags, { error: 'The flag --foo can only be set once.', }); +// @ts-expect-error: invalid choices, unknown key alias test('options - multiple validation errors', verifyFlags, { flags: { animal: { diff --git a/test/options/help.ts b/test/options/help.ts index ebcad63..7856006 100644 --- a/test/options/help.ts +++ b/test/options/help.ts @@ -1,16 +1,26 @@ import test from 'ava'; import indentString from 'indent-string'; import meow from '../../source/index.js'; -import {_verifyCli, stripIndent, stripIndentTrim} from '../_utils.js'; +import { + _verifyCli, + type MeowOptions, + stripIndent, + stripIndentTrim, +} from '../_utils.js'; const importMeta = import.meta; const verifyCli = _verifyCli(); -const verifyHelp = test.macro(async (t, {cli: cliArguments, expected}) => { +type VerifyHelpMacroArguments = [{ + cli: MeowOptions | [helpMessage: string, options?: MeowOptions]; + expected: string; +}]; + +const verifyHelp = test.macro(async (t, {cli: cliArguments, expected}) => { const assertions = await t.try(async tt => { const cli = Array.isArray(cliArguments) - ? meow(cliArguments.at(0), {importMeta, ...cliArguments.at(1)}) + ? meow(cliArguments[0], {importMeta, ...cliArguments[1]}) : meow({importMeta, ...cliArguments}); tt.log('help text:\n', cli.help); diff --git a/test/options/import-meta.ts b/test/options/import-meta.ts index e0f72b0..ea47f62 100644 --- a/test/options/import-meta.ts +++ b/test/options/import-meta.ts @@ -1,7 +1,12 @@ import test from 'ava'; import meow from '../../source/index.js'; -const verifyImportMeta = test.macro((t, {cli, error}) => { +type MacroArguments = [{ + cli: () => ReturnType; + error?: true; +}]; + +const verifyImportMeta = test.macro((t, {cli, error}) => { if (error) { t.throws(cli, {message: 'The `importMeta` option is required. Its value must be `import.meta`.'}); } else { @@ -25,21 +30,25 @@ test('with help shortcut', verifyImportMeta, { }); test('invalid package url', verifyImportMeta, { + // @ts-expect-error: invalid importMeta cli: () => meow({importMeta: '/path/to/package'}), error: true, }); test('throws if unset', verifyImportMeta, { + // @ts-expect-error: missing importMeta cli: () => meow('foo', {}), error: true, }); test('throws if unset - options only', verifyImportMeta, { + // @ts-expect-error: missing importMeta cli: () => meow({}), error: true, }); test('throws if unset - help shortcut only', verifyImportMeta, { + // @ts-expect-error: missing options cli: () => meow('foo'), error: true, }); diff --git a/test/options/infer-type.ts b/test/options/infer-type.ts index a96ffe5..c5e9545 100644 --- a/test/options/infer-type.ts +++ b/test/options/infer-type.ts @@ -1,7 +1,13 @@ import test from 'ava'; import meow from '../../source/index.js'; +import type {MeowOptions} from '../_utils.js'; -const verifyTypeInference = test.macro((t, {cli: cliArguments, expected}) => { +type MacroArguments = [{ + cli: MeowOptions; + expected: string | number; +}]; + +const verifyTypeInference = test.macro((t, {cli: cliArguments, expected}) => { const cli = meow({importMeta: import.meta, ...cliArguments}); t.like(cli.input, [expected]); }); @@ -22,10 +28,10 @@ test('type inference', verifyTypeInference, { }); test('with input type', verifyTypeInference, { - cli: { + cli: { // eslint-disable-line @typescript-eslint/consistent-type-assertions argv: ['5'], input: 'number', - }, + } as MeowOptions, expected: 5, }); @@ -33,7 +39,9 @@ test('works with flags', verifyTypeInference, { cli: { argv: ['5'], inferType: true, - flags: {foo: 'string'}, + flags: { + foo: {type: 'string'}, + }, }, expected: 5, }); diff --git a/test/options/version.ts b/test/options/version.ts index 0449dba..0a2a19f 100644 --- a/test/options/version.ts +++ b/test/options/version.ts @@ -1,7 +1,7 @@ import test from 'ava'; import {_verifyCli, defaultFixture, stripIndentTrim} from '../_utils.js'; -const verifyVersion = _verifyCli('version/fixture.js'); +const verifyVersion = _verifyCli('version/fixture.ts'); test('spawn cli and show version', verifyVersion, { args: '--version', @@ -31,13 +31,13 @@ test('spawn cli and not show version', verifyVersion, { test('custom version', verifyVersion, { args: '--version', - execaOptions: {env: {VERSION: 'beta'}}, + execaOptions: {env: {VERSION: 'beta'}}, // eslint-disable-line @typescript-eslint/naming-convention expected: 'beta', }); test('version = false has no effect', verifyVersion, { args: '--version', - execaOptions: {env: {VERSION: 'false'}}, + execaOptions: {env: {VERSION: 'false'}}, // eslint-disable-line @typescript-eslint/naming-convention expected: 'false', }); From 8825f9623e36f6bfb4ac7a3d90831c0c33876d8b Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 12:08:53 -0600 Subject: [PATCH 03/15] update inferred type test --- source/options.ts | 2 +- test/options/infer-type.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/options.ts b/source/options.ts index 87ef76c..ed993f8 100644 --- a/source/options.ts +++ b/source/options.ts @@ -88,7 +88,7 @@ export const buildOptions = (helpMessage: string, options: Options): P argv: process.argv.slice(2), flags: {}, inferType: false, - input: 'string', // TODO: undocumented option? + input: 'string', description: pkg.description ?? false, help: helpMessage, version: pkg.version || 'No version found', // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing diff --git a/test/options/infer-type.ts b/test/options/infer-type.ts index c5e9545..86330bb 100644 --- a/test/options/infer-type.ts +++ b/test/options/infer-type.ts @@ -3,7 +3,9 @@ import meow from '../../source/index.js'; import type {MeowOptions} from '../_utils.js'; type MacroArguments = [{ - cli: MeowOptions; + cli: MeowOptions & { + input?: 'string' | 'number'; + }; expected: string | number; }]; @@ -28,10 +30,10 @@ test('type inference', verifyTypeInference, { }); test('with input type', verifyTypeInference, { - cli: { // eslint-disable-line @typescript-eslint/consistent-type-assertions + cli: { argv: ['5'], input: 'number', - } as MeowOptions, + }, expected: 5, }); From 01eeabc18620725d4746b1724c436597fd35d074 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 12:09:09 -0600 Subject: [PATCH 04/15] fix(?) required function fixture --- test/fixtures/required/fixture-required-function.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/fixtures/required/fixture-required-function.ts b/test/fixtures/required/fixture-required-function.ts index ec89d24..0fe0e51 100755 --- a/test/fixtures/required/fixture-required-function.ts +++ b/test/fixtures/required/fixture-required-function.ts @@ -16,8 +16,7 @@ const cli = meow({ withTrigger: { type: 'string', // TODO: change type to allow truthy / falsy values? - // @ts-expect-error: falsy trigger is still boolean - isRequired: (flags, _) => flags.trigger, + isRequired: (flags, _) => Boolean(flags.trigger), }, allowError: { type: 'boolean', @@ -25,12 +24,9 @@ const cli = meow({ }, shouldError: { type: 'boolean', - // @ts-expect-error: invalid string return - isRequired: (flags, _) => flags.allowError ? 'should error' : false, + isRequired: (flags, _) => (flags.allowError ? 'should error' : false) as boolean, }, }, }); -// TODO: errors above make flags untyped -// eslint-disable-next-line @typescript-eslint/restrict-template-expressions console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`); From f71b9402a70ebe6c092b232ea16922a75d252601 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 12:38:13 -0600 Subject: [PATCH 05/15] fix type and build tests --- .gitignore | 1 + rollup.config.js | 3 +++ source/index.ts | 8 ++++---- source/minimist-options.d.ts | 21 ++++++++++----------- test/{build.ts => build.js} | 2 +- test/fixtures/{build.ts => build.js} | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) rename test/{build.ts => build.js} (94%) rename test/fixtures/{build.ts => build.js} (96%) diff --git a/.gitignore b/.gitignore index a6d40a9..fea6302 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules build yarn.lock test-d/build.ts +test-d/*.d.ts .tsimp diff --git a/rollup.config.js b/rollup.config.js index 1caf60d..c4856f2 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -86,6 +86,9 @@ const dtsConfig = defineConfig({ ); await fs.writeFile(`./test-d/${outputDirectory}.ts`, tsdFile); + + const minimistFile = await fs.readFile(`./${sourceDirectory}/minimist-options.d.ts`, 'utf8'); + await fs.writeFile('./test-d/minimist-options.d.ts', minimistFile); }, }, ], diff --git a/source/index.ts b/source/index.ts index 9bcd825..760b603 100644 --- a/source/index.ts +++ b/source/index.ts @@ -13,7 +13,7 @@ import type { AnyFlags, } from './types.js'; -const buildResult = ({pkg: packageJson, ...options}: ParsedOptions, parserOptions: ParserOptions): Result => { +const buildResult = ({pkg: packageJson, ...options}: ParsedOptions, parserOptions: ParserOptions): Result => { const {_: input, ...argv} = parseArguments(options.argv as string[], parserOptions); let help = ''; @@ -130,10 +130,10 @@ const cli = meow(` foo(cli.input.at(0), cli.flags); ``` */ -export default function meow(helpMessage: string, options: Options): Result; -export default function meow(options: Options): Result; +export default function meow(helpMessage: string, options: Options): Result; +export default function meow(options: Options): Result; -export default function meow(helpMessage: string | Options, options?: Options): Result { +export default function meow(helpMessage: string | Options, options?: Options): Result { if (typeof helpMessage !== 'string') { options = helpMessage; helpMessage = ''; diff --git a/source/minimist-options.d.ts b/source/minimist-options.d.ts index bcf4364..636d866 100644 --- a/source/minimist-options.d.ts +++ b/source/minimist-options.d.ts @@ -46,17 +46,16 @@ declare module 'minimist-options' { export type MinimistOption = Pick; - export type Options = MinimistOption & { - [key: string]: ( - | OptionType - | StringOption - | BooleanOption - | NumberOption - | DefaultArrayOption - | StringArrayOption - | BooleanArrayOption - | NumberArrayOption - ); + export type Options = MinimistOption & Record & { arguments?: string; }; diff --git a/test/build.ts b/test/build.js similarity index 94% rename from test/build.ts rename to test/build.js index 28411f8..cd75d56 100644 --- a/test/build.ts +++ b/test/build.js @@ -2,7 +2,7 @@ import test from 'ava'; import meow from '../build/index.js'; import {_verifyCli, meowVersion} from './_utils.js'; -const verifyCli = _verifyCli(); +const verifyCli = _verifyCli('build.js'); test('main', t => { const cli = meow(` diff --git a/test/fixtures/build.ts b/test/fixtures/build.js similarity index 96% rename from test/fixtures/build.ts rename to test/fixtures/build.js index 073a0d2..83f7222 100755 --- a/test/fixtures/build.ts +++ b/test/fixtures/build.js @@ -1,4 +1,4 @@ -#!/usr/bin/env tsimp +#!/usr/bin/env node import process from 'node:process'; import meow from '../../build/index.js'; From df0017f708f9622456e624863aa7255e4045ccf1 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 17:10:21 -0600 Subject: [PATCH 06/15] pin `type-fest` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 947a671..5ceec03 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "trim-newlines": "^5.0.0", "tsd": "^0.30.7", "tsimp": "^2.0.11", - "type-fest": "^4.10.3", + "type-fest": "4.10.3", "typescript": "~5.3.3", "xo": "^0.57.0", "yargs-parser": "^21.1.1" From 085e4f576ccc3f274ed3dedfaf30d05405f73bf6 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 17:37:04 -0600 Subject: [PATCH 07/15] tweaks --- package.json | 2 +- test/test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ceec03..52de064 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@sindresorhus/tsconfig": "^5.0.0", "@types/common-tags": "^1.8.4", "@types/minimist": "^1.2.5", - "@types/node": "18", + "@types/node": "18.x", "@types/stack-utils": "^2.0.3", "@types/yargs-parser": "^21.0.3", "ava": "^6.1.2", diff --git a/test/test.ts b/test/test.ts index c589ad0..ef9480a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -17,7 +17,7 @@ test('return object', t => { flags: { unicorn: {shortFlag: 'u'}, meow: {default: 'dog'}, - '--': true, + '--': true as never, }, }); From 777a4572dea35bde3fe0d595371c524195925544 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 18:05:16 -0600 Subject: [PATCH 08/15] use semaphore to fix test time outs --- package.json | 2 ++ test/_utils.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/package.json b/package.json index 52de064..0fc0720 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@shopify/semaphore": "^3.0.2", "@sindresorhus/is": "^6.2.0", "@sindresorhus/tsconfig": "^5.0.0", "@types/common-tags": "^1.8.4", @@ -116,6 +117,7 @@ "--import=tsimp" ], "environmentVariables": { + "concurrency": "20", "TSIMP_DIAG": "ignore" } } diff --git a/test/_utils.ts b/test/_utils.ts index 270a433..d93dc64 100644 --- a/test/_utils.ts +++ b/test/_utils.ts @@ -1,6 +1,8 @@ /* eslint-disable ava/no-ignored-test-files */ +import process from 'node:process'; import {fileURLToPath} from 'node:url'; import test from 'ava'; +import {Semaphore} from '@shopify/semaphore'; import { execa, type ExecaChildProcess, @@ -60,8 +62,12 @@ type VerifyCliMacroArguments = [{ }; }, 'expected' | 'error'>]; +const semaphore = new Semaphore(Number(process.env['concurrency']) || 5); + export const _verifyCli = (baseFixture = defaultFixture) => test.macro( async (t, {fixture = baseFixture, args, execaOptions, expected, error}) => { + const permit = await semaphore.acquire(); + const assertions = await t.try(async tt => { const arguments_ = args ? args.split(' ') : []; const {all: output, exitCode} = await spawnFixture(fixture, arguments_, {reject: false, all: true, ...execaOptions}) as ExecaReturnValue & {all: string}; @@ -91,5 +97,6 @@ export const _verifyCli = (baseFixture = defaultFixture) => test.macro Date: Mon, 4 Mar 2024 19:20:01 -0600 Subject: [PATCH 09/15] increase concurrency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fc0720..5090d2f 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "--import=tsimp" ], "environmentVariables": { - "concurrency": "20", + "concurrency": "25", "TSIMP_DIAG": "ignore" } } From a0a8558fc9ef5b76db219d454ed17247bcc2c32b Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 19:20:20 -0600 Subject: [PATCH 10/15] tweaks --- source/options.ts | 2 +- source/validate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/options.ts b/source/options.ts index ed993f8..af24a76 100644 --- a/source/options.ts +++ b/source/options.ts @@ -43,7 +43,7 @@ const validateOptions = (options: ParsedOptions): void => { }, }, defaultNotInChoices: { - filter: ([, flag]) => flag.default !== undefined && Array.isArray(flag.choices) && ![flag.default].flat().every(value => flag.choices!.includes(value as never)), // TODO: never? + filter: ([, flag]) => flag.default !== undefined && Array.isArray(flag.choices) && ![flag.default].flat().every(value => flag.choices!.includes(value as never)), message: flagKeys => `Each value of the option \`default\` must exist within the option \`choices\`. Invalid flags: ${joinFlagKeys(flagKeys)}`, }, }, diff --git a/source/validate.ts b/source/validate.ts index 58d1be6..f079a9a 100644 --- a/source/validate.ts +++ b/source/validate.ts @@ -39,7 +39,7 @@ const validateChoicesByFlag = (flagKey: string, flagValue: AnyFlag, receivedInpu } if (Array.isArray(receivedInput)) { - const unknownValues = receivedInput.filter(index => !choices.includes(index as never)); // TODO: never? + const unknownValues = receivedInput.filter(input => !choices.includes(input as never)); if (unknownValues.length > 0) { const valuesText = unknownValues.length > 1 ? 'values' : 'value'; From 0c58a48e729e89a0baeb5b7bec985d26e14a8e8a Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 4 Mar 2024 20:54:56 -0600 Subject: [PATCH 11/15] try using `registerCompletionHandler` --- package.json | 2 -- test/_utils.ts | 12 +++++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5090d2f..52de064 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", - "@shopify/semaphore": "^3.0.2", "@sindresorhus/is": "^6.2.0", "@sindresorhus/tsconfig": "^5.0.0", "@types/common-tags": "^1.8.4", @@ -117,7 +116,6 @@ "--import=tsimp" ], "environmentVariables": { - "concurrency": "25", "TSIMP_DIAG": "ignore" } } diff --git a/test/_utils.ts b/test/_utils.ts index d93dc64..01c4469 100644 --- a/test/_utils.ts +++ b/test/_utils.ts @@ -1,8 +1,7 @@ -/* eslint-disable ava/no-ignored-test-files */ + import process from 'node:process'; import {fileURLToPath} from 'node:url'; -import test from 'ava'; -import {Semaphore} from '@shopify/semaphore'; +import test, {registerCompletionHandler} from 'ava'; import { execa, type ExecaChildProcess, @@ -62,12 +61,12 @@ type VerifyCliMacroArguments = [{ }; }, 'expected' | 'error'>]; -const semaphore = new Semaphore(Number(process.env['concurrency']) || 5); +registerCompletionHandler(() => { + process.exit(0); +}); export const _verifyCli = (baseFixture = defaultFixture) => test.macro( async (t, {fixture = baseFixture, args, execaOptions, expected, error}) => { - const permit = await semaphore.acquire(); - const assertions = await t.try(async tt => { const arguments_ = args ? args.split(' ') : []; const {all: output, exitCode} = await spawnFixture(fixture, arguments_, {reject: false, all: true, ...execaOptions}) as ExecaReturnValue & {all: string}; @@ -97,6 +96,5 @@ export const _verifyCli = (baseFixture = defaultFixture) => test.macro Date: Tue, 5 Mar 2024 12:41:31 -0600 Subject: [PATCH 12/15] revert to ava 5 --- package.json | 2 +- test/_utils.ts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 52de064..1c660bc 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/node": "18.x", "@types/stack-utils": "^2.0.3", "@types/yargs-parser": "^21.0.3", - "ava": "^6.1.2", + "ava": "^5.3.1", "camelcase-keys": "^9.1.3", "common-tags": "^2.0.0-alpha.1", "decamelize": "^6.0.0", diff --git a/test/_utils.ts b/test/_utils.ts index 01c4469..270a433 100644 --- a/test/_utils.ts +++ b/test/_utils.ts @@ -1,7 +1,6 @@ - -import process from 'node:process'; +/* eslint-disable ava/no-ignored-test-files */ import {fileURLToPath} from 'node:url'; -import test, {registerCompletionHandler} from 'ava'; +import test from 'ava'; import { execa, type ExecaChildProcess, @@ -61,10 +60,6 @@ type VerifyCliMacroArguments = [{ }; }, 'expected' | 'error'>]; -registerCompletionHandler(() => { - process.exit(0); -}); - export const _verifyCli = (baseFixture = defaultFixture) => test.macro( async (t, {fixture = baseFixture, args, execaOptions, expected, error}) => { const assertions = await t.try(async tt => { From 64f0915b8aa0ed42b9a04fc4bc7b814e125b57d3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 19 Mar 2024 21:43:50 +0700 Subject: [PATCH 13/15] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1c660bc..3515e9c 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ ] }, "ava": { + "timeout": "1m", "extensions": { "ts": "module", "js": true From 5c8d5c23c18d15350700bf7a7b429c620f52b3ed Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 19 Mar 2024 21:55:57 +0700 Subject: [PATCH 14/15] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3515e9c..3f8053c 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ ] }, "ava": { - "timeout": "1m", + "timeout": "10m", "extensions": { "ts": "module", "js": true From 5e1aa57cfa3beed595938a77726f8e3243095259 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 20 Mar 2024 12:56:51 +0700 Subject: [PATCH 15/15] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a768beb..90e2d62 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: matrix: node-version: - 21 - - 20 + # - 20 - 18 steps: - uses: actions/checkout@v4