diff --git a/package.json b/package.json index bc65eac..612265a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "type": "module", "exports": { - "types": "./index.d.ts", + "types": "./build/index.d.ts", "default": "./build/index.js" }, "sideEffects": false, @@ -22,11 +22,10 @@ "scripts": { "prepare": "npm run build", "build": "rollup --config", - "test": "xo && tsd && npm run build && ava" + "test": "xo && npm run build && ava && tsd --typings build/index.d.ts" }, "files": [ - "build", - "index.d.ts" + "build" ], "keywords": [ "cli", @@ -82,10 +81,12 @@ "read-pkg-up": "^10.0.0", "redent": "^4.0.0", "rollup": "^3.27.0", + "rollup-plugin-dts": "^6.0.0", "rollup-plugin-license": "^3.0.1", "trim-newlines": "^5.0.0", "tsd": "^0.28.1", - "type-fest": "^4.2.0", + "type-fest": "^4.3.1", + "typescript": "~5.1.6", "xo": "^0.56.0", "yargs-parser": "^21.1.1" }, diff --git a/rollup.config.js b/rollup.config.js index 55f7ab0..39f5760 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,7 @@ import {nodeResolve} from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; 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'; @@ -17,7 +18,7 @@ const stripComments = createTag( const outputDirectory = 'build'; -export default defineConfig({ +const config = defineConfig({ input: await globby('source/**/*.js'), output: { dir: outputDirectory, @@ -55,3 +56,19 @@ export default defineConfig({ }), ], }); + +const dtsConfig = defineConfig({ + input: './source/index.d.ts', + output: { + file: `./${outputDirectory}/index.d.ts`, + format: 'es', + }, + plugins: [ + dts({ + respectExternal: true, + }), + ], +}); + +// eslint-disable-next-line import/no-anonymous-default-export +export default [config, dtsConfig]; diff --git a/index.d.ts b/source/index.d.ts similarity index 100% rename from index.d.ts rename to source/index.d.ts diff --git a/test-d/build.test-d.ts b/test-d/build.test-d.ts new file mode 100644 index 0000000..ca9d8ee --- /dev/null +++ b/test-d/build.test-d.ts @@ -0,0 +1,132 @@ +import {expectAssignable, expectError, expectType} from 'tsd'; +import type {PackageJson} from 'type-fest'; +import meow, {type Result} from '../build/index.js'; + +type AnyFlag = NonNullable[0]>['flags']>[string]; + +const importMeta = import.meta; + +expectType>(meow('Help text')); +expectType>(meow('Help text', {importMeta, hardRejection: false})); +expectAssignable<{flags: {foo: number}}>( + meow({importMeta: import.meta, flags: {foo: {type: 'number', isRequired: true}}}), +); +expectAssignable<{flags: {foo: string}}>( + meow({importMeta, flags: {foo: {type: 'string', isRequired: true}}}), +); +expectAssignable<{flags: {foo: boolean}}>( + meow({importMeta, flags: {foo: {type: 'boolean', isRequired: true}}}), +); +expectAssignable<{flags: {foo: number | undefined}}>( + meow({importMeta, flags: {foo: {type: 'number'}}}), +); +expectAssignable<{flags: {foo: string | undefined}}>( + meow({importMeta, flags: {foo: {type: 'string'}}}), +); +expectAssignable<{flags: {foo: boolean | undefined}}>( + meow({importMeta, flags: {foo: {type: 'boolean'}}}), +); +expectAssignable<{flags: {foo: number[] | undefined}}>( + meow({importMeta, flags: {foo: {type: 'number', isMultiple: true}}}), +); +expectAssignable<{flags: {foo: string[] | undefined}}>( + meow({importMeta, flags: {foo: {type: 'string', isMultiple: true}}}), +); +expectAssignable<{flags: {foo: boolean[] | undefined}}>( + meow({importMeta, flags: {foo: {type: 'boolean', isMultiple: true}}}), +); +expectType>(meow({importMeta, description: 'foo'})); +expectType>(meow({importMeta, description: false})); +expectType>(meow({importMeta, help: 'foo'})); +expectType>(meow({importMeta, help: false})); +expectType>(meow({importMeta, version: 'foo'})); +expectType>(meow({importMeta, version: false})); +expectType>(meow({importMeta, autoHelp: false})); +expectType>(meow({importMeta, autoVersion: false})); +expectType>(meow({importMeta, pkg: {foo: 'bar'}})); +expectType>(meow({importMeta, argv: ['foo', 'bar']})); +expectType>(meow({importMeta, inferType: true})); +expectType>(meow({importMeta, booleanDefault: true})); +expectType>(meow({importMeta, booleanDefault: null})); +expectType>(meow({importMeta, booleanDefault: undefined})); +expectType>(meow({importMeta, hardRejection: false})); + +const result = meow('Help text', { + importMeta, + flags: { + foo: {type: 'boolean', shortFlag: 'f'}, + 'foo-bar': {type: 'number', aliases: ['foobar', 'fooBar']}, + bar: {type: 'string', default: ''}, + abc: {type: 'string', isMultiple: true}, + baz: {type: 'string', choices: ['rainbow', 'cat', 'unicorn']}, + }, +}); + +expectType(result.input); +expectType(result.pkg); +expectType(result.help); + +expectType(result.flags.foo); +expectType(result.flags.fooBar); +expectType(result.flags.bar); +expectType(result.flags.abc); +expectType(result.flags.baz); +expectType(result.unnormalizedFlags.foo); +expectType(result.unnormalizedFlags.f); +expectType(result.unnormalizedFlags['foo-bar']); +expectType(result.unnormalizedFlags.foobar); +expectType(result.unnormalizedFlags.fooBar); +expectType(result.unnormalizedFlags.bar); +expectType(result.unnormalizedFlags.abc); +expectType(result.unnormalizedFlags.baz); + +result.showHelp(); +result.showHelp(1); +result.showVersion(); + +const options = { + importMeta, + flags: { + rainbow: { + type: 'boolean', + shortFlag: 'r', + }, + }, +} as const; + +meow('', options); + +expectAssignable({type: 'string', default: 'cat'}); +expectAssignable({type: 'number', default: 42}); +expectAssignable({type: 'boolean', default: true}); + +expectAssignable({type: 'string', default: undefined}); +expectAssignable({type: 'number', default: undefined}); +expectAssignable({type: 'boolean', default: undefined}); + +expectAssignable({type: 'string', isMultiple: true, default: ['cat']}); +expectAssignable({type: 'number', isMultiple: true, default: [42]}); +expectAssignable({type: 'boolean', isMultiple: true, default: [false]}); + +expectError({type: 'string', isMultiple: true, default: 'cat'}); +expectError({type: 'number', isMultiple: true, default: 42}); +expectError({type: 'boolean', isMultiple: true, default: false}); + +expectAssignable({type: 'string', choices: ['cat', 'unicorn']}); +expectAssignable({type: 'number', choices: [1, 2]}); +expectAssignable({type: 'boolean', choices: [true, false]}); +expectAssignable({type: 'string', isMultiple: true, choices: ['cat']}); +expectAssignable({type: 'string', isMultiple: false, choices: ['cat']}); + +expectError({type: 'string', choices: 'cat'}); +expectError({type: 'number', choices: 1}); +expectError({type: 'boolean', choices: true}); + +expectError({type: 'string', choices: [1]}); +expectError({type: 'number', choices: ['cat']}); +expectError({type: 'boolean', choices: ['cat']}); + +expectAssignable({choices: ['cat']}); +expectAssignable({choices: [1]}); +expectAssignable({choices: [true]}); +expectError({choices: ['cat', 1, true]}); diff --git a/index.test-d.ts b/test-d/index.test-d.ts similarity index 97% rename from index.test-d.ts rename to test-d/index.test-d.ts index 09250a7..aa5bf91 100644 --- a/index.test-d.ts +++ b/test-d/index.test-d.ts @@ -1,6 +1,8 @@ import {expectAssignable, expectError, expectType} from 'tsd'; import type {PackageJson} from 'type-fest'; -import meow, {type Result, type AnyFlag} from './index.js'; +import meow, {type Result} from '../source/index.js'; + +type AnyFlag = NonNullable[0]>['flags']>[string]; const importMeta = import.meta; diff --git a/tsconfig.json b/tsconfig.json index a97b4b7..3a0a364 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { - "files": [ - "index.d.ts", - "index.test-d.ts", + "include": [ + "source/index.d.ts", + "test-d", ], "compilerOptions": { "strict": true,