diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1870cf..3b8aa86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,12 @@ jobs: fail-fast: false matrix: node-version: + - 16 - 14 - 12 - - 10 steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/estest/index.js b/estest/index.js index b4d1af9..2992614 100644 --- a/estest/index.js +++ b/estest/index.js @@ -1,6 +1,4 @@ -import {createRequire} from 'module'; - -const meow = createRequire(import.meta.url)('../index.js'); +import meow from '../index.js'; meow(` Usage diff --git a/index.d.ts b/index.d.ts index 59492ad..ec5c613 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,25 +1,24 @@ import {PackageJson} from 'type-fest'; -declare namespace meow { - type FlagType = 'string' | 'boolean' | 'number'; +export type FlagType = 'string' | 'boolean' | 'number'; - /** - Callback function to determine if a flag is required during runtime. +/** +Callback function to determine if a flag is required during runtime. - @param flags - Contains the flags converted to camel-case excluding aliases. - @param input - Contains the non-flag arguments. +@param flags - Contains the flags converted to camel-case excluding aliases. +@param input - Contains the non-flag arguments. - @returns True if the flag is required, otherwise false. - */ - type IsRequiredPredicate = (flags: Readonly, input: readonly string[]) => boolean; - - interface Flag { - readonly type?: Type; - readonly alias?: string; - readonly default?: Default; - readonly isRequired?: boolean | IsRequiredPredicate; - readonly isMultiple?: boolean; - } +@returns True if the flag is required, otherwise false. +*/ +export type IsRequiredPredicate = (flags: Readonly, input: readonly string[]) => boolean; + +export interface Flag { + readonly type?: Type; + readonly alias?: string; + readonly default?: Default; + readonly isRequired?: boolean | IsRequiredPredicate; + readonly isMultiple?: boolean; +} type StringFlag = Flag<'string', string>; type BooleanFlag = Flag<'boolean', boolean>; @@ -28,189 +27,189 @@ declare namespace meow { type AnyFlag = StringFlag | BooleanFlag | NumberFlag; type AnyFlags = Record; - interface Options { - /** - Define argument flags. +export interface Options { + /** + Define argument flags. - The key is the flag name in camel-case and the value is an object with any of: + The key is the flag name in camel-case and the value is an object with any of: - - `type`: Type of value. (Possible values: `string` `boolean` `number`) - - `alias`: Usually used to define a short flag alias. - - `default`: Default value when the flag is not specified. - - `isRequired`: Determine if the flag is required. - If it's only known at runtime whether the flag is required or not you can pass a Function instead of a boolean, which based on the given flags and other non-flag arguments should decide if the flag is required. - - `isMultiple`: Indicates a flag can be set multiple times. Values are turned into an array. (Default: false) - Multiple values are provided by specifying the flag multiple times, for example, `$ foo -u rainbow -u cat`. Space- or comma-separated values are *not* supported. + - `type`: Type of value. (Possible values: `string` `boolean` `number`) + - `alias`: Usually used to define a short flag alias. + - `default`: Default value when the flag is not specified. + - `isRequired`: Determine if the flag is required. + If it's only known at runtime whether the flag is required or not you can pass a Function instead of a boolean, which based on the given flags and other non-flag arguments should decide if the flag is required. + - `isMultiple`: Indicates a flag can be set multiple times. Values are turned into an array. (Default: false) + Multiple values are provided by specifying the flag multiple times, for example, `$ foo -u rainbow -u cat`. Space- or comma-separated values are *not* supported. - Note that flags are always defined using a camel-case key (`myKey`), but will match arguments in kebab-case (`--my-key`). + Note that flags are always defined using a camel-case key (`myKey`), but will match arguments in kebab-case (`--my-key`). - @example - ``` - flags: { - unicorn: { - type: 'string', - alias: 'u', - default: ['rainbow', 'cat'], - isMultiple: true, - isRequired: (flags, input) => { - if (flags.otherFlag) { - return true; - } - - return false; + @example + ``` + flags: { + unicorn: { + type: 'string', + alias: 'u', + default: ['rainbow', 'cat'], + isMultiple: true, + isRequired: (flags, input) => { + if (flags.otherFlag) { + return true; } + + return false; } } - ``` - */ - readonly flags?: Flags; + } + ``` + */ + readonly flags?: Flags; - /** - Description to show above the help text. Default: The package.json `"description"` property. + /** + Description to show above the help text. Default: The package.json `"description"` property. - Set it to `false` to disable it altogether. - */ - readonly description?: string | false; + Set it to `false` to disable it altogether. + */ + readonly description?: string | false; - /** - The help text you want shown. + /** + The help text you want shown. - The input is reindented and starting/ending newlines are trimmed which means you can use a [template literal](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/template_strings) without having to care about using the correct amount of indent. + The input is reindented and starting/ending newlines are trimmed which means you can use a [template literal](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/template_strings) without having to care about using the correct amount of indent. - The description will be shown above your help text automatically. + The description will be shown above your help text automatically. - Set it to `false` to disable it altogether. - */ - readonly help?: string | false; + Set it to `false` to disable it altogether. + */ + readonly help?: string | false; - /** - Set a custom version output. Default: The package.json `"version"` property. + /** + Set a custom version output. Default: The package.json `"version"` property. - Set it to `false` to disable it altogether. - */ - readonly version?: string | false; + Set it to `false` to disable it altogether. + */ + readonly version?: string | false; - /** - Automatically show the help text when the `--help` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own help text. + /** + Automatically show the help text when the `--help` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own help text. - This option is only considered when there is only one argument in `process.argv`. - */ - readonly autoHelp?: boolean; + This option is only considered when there is only one argument in `process.argv`. + */ + readonly autoHelp?: boolean; - /** - Automatically show the version text when the `--version` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own version text. + /** + Automatically show the version text when the `--version` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own version text. - This option is only considered when there is only one argument in `process.argv`. - */ - readonly autoVersion?: boolean; + This option is only considered when there is only one argument in `process.argv`. + */ + readonly autoVersion?: boolean; - /** - `package.json` as an `Object`. Default: Closest `package.json` upwards. + /** + `package.json` as an `Object`. Default: Closest `package.json` upwards. - _You most likely don't need this option._ - */ - readonly pkg?: Record; + _You most likely don't need this option._ + */ + readonly pkg?: Record; - /** - Custom arguments object. + /** + Custom arguments object. - @default process.argv.slice(2) - */ - readonly argv?: readonly string[]; + @default process.argv.slice(2) + */ + readonly argv?: readonly string[]; - /** - Infer the argument type. + /** + Infer the argument type. - By default, the argument `5` in `$ foo 5` becomes a string. Enabling this would infer it as a number. + By default, the argument `5` in `$ foo 5` becomes a string. Enabling this would infer it as a number. - @default false - */ - readonly inferType?: boolean; + @default false + */ + readonly inferType?: boolean; - /** - Value of `boolean` flags not defined in `argv`. + /** + Value of `boolean` flags not defined in `argv`. - If set to `undefined`, the flags not defined in `argv` will be excluded from the result. The `default` value set in `boolean` flags take precedence over `booleanDefault`. + If set to `undefined`, the flags not defined in `argv` will be excluded from the result. The `default` value set in `boolean` flags take precedence over `booleanDefault`. - _Note: If used in conjunction with `isMultiple`, the default flag value is set to `[]`._ + _Note: If used in conjunction with `isMultiple`, the default flag value is set to `[]`._ - __Caution: Explicitly specifying `undefined` for `booleanDefault` has different meaning from omitting key itself.__ + __Caution: Explicitly specifying `undefined` for `booleanDefault` has different meaning from omitting key itself.__ - @example - ``` - import meow = require('meow'); + @example + ``` + import meow from 'meow'; - const cli = meow(` - Usage - $ foo + const cli = meow(` + Usage + $ foo - Options - --rainbow, -r Include a rainbow - --unicorn, -u Include a unicorn - --no-sparkles Exclude sparkles + Options + --rainbow, -r Include a rainbow + --unicorn, -u Include a unicorn + --no-sparkles Exclude sparkles - Examples - $ foo - 🌈 unicorns✨🌈 - `, { - booleanDefault: undefined, - flags: { - rainbow: { - type: 'boolean', - default: true, - alias: 'r' - }, - unicorn: { - type: 'boolean', - default: false, - alias: 'u' - }, - cake: { - type: 'boolean', - alias: 'c' - }, - sparkles: { - type: 'boolean', - default: true - } + Examples + $ foo + 🌈 unicorns✨🌈 + `, { + booleanDefault: undefined, + flags: { + rainbow: { + type: 'boolean', + default: true, + alias: 'r' + }, + unicorn: { + type: 'boolean', + default: false, + alias: 'u' + }, + cake: { + type: 'boolean', + alias: 'c' + }, + sparkles: { + type: 'boolean', + default: true } - }); - - //{ - // flags: { - // rainbow: true, - // unicorn: false, - // sparkles: true - // }, - // unnormalizedFlags: { - // rainbow: true, - // r: true, - // unicorn: false, - // u: false, - // sparkles: true - // }, - // … - //} - ``` - */ - readonly booleanDefault?: boolean | null | undefined; - - /** - Whether to use [hard-rejection](https://github.com/sindresorhus/hard-rejection) or not. Disabling this can be useful if you need to handle `process.on('unhandledRejection')` yourself. - - @default true - */ - readonly hardRejection?: boolean; - - /** - Whether to allow unknown flags or not. - - @default true - */ - readonly allowUnknownFlags?: boolean; - } + } + }); + + //{ + // flags: { + // rainbow: true, + // unicorn: false, + // sparkles: true + // }, + // unnormalizedFlags: { + // rainbow: true, + // r: true, + // unicorn: false, + // u: false, + // sparkles: true + // }, + // … + //} + ``` + */ + readonly booleanDefault?: boolean | null | undefined; + + /** + Whether to use [hard-rejection](https://github.com/sindresorhus/hard-rejection) or not. Disabling this can be useful if you need to handle `process.on('unhandledRejection')` yourself. + + @default true + */ + readonly hardRejection?: boolean; - type TypedFlag = + /** + Whether to allow unknown flags or not. + + @default true + */ + readonly allowUnknownFlags?: boolean; +} + +export type TypedFlag = Flag extends {type: 'number'} ? number : Flag extends {type: 'string'} @@ -219,57 +218,56 @@ declare namespace meow { ? boolean : unknown; - type PossiblyOptionalFlag = +export type PossiblyOptionalFlag = Flag extends {isRequired: true} ? FlagType : Flag extends {default: any} ? FlagType : FlagType | undefined; - type TypedFlags = { - [F in keyof Flags]: Flags[F] extends {isMultiple: true} - ? PossiblyOptionalFlag>> - : PossiblyOptionalFlag> - }; - - interface Result { - /** - Non-flag arguments. - */ - input: string[]; - - /** - Flags converted to camelCase excluding aliases. - */ - flags: TypedFlags & Record; - - /** - Flags converted camelCase including aliases. - */ - unnormalizedFlags: TypedFlags & Record; - - /** - The `package.json` object. - */ - pkg: PackageJson; - - /** - The help text used with `--help`. - */ - help: string; - - /** - Show the help text and exit with code. - - @param exitCode - The exit code to use. Default: `2`. - */ - showHelp: (exitCode?: number) => void; - - /** - Show the version text and exit. - */ - showVersion: () => void; - } +export type TypedFlags = { + [F in keyof Flags]: Flags[F] extends {isMultiple: true} + ? PossiblyOptionalFlag>> + : PossiblyOptionalFlag> +}; + +export interface Result { + /** + Non-flag arguments. + */ + input: string[]; + + /** + Flags converted to camelCase excluding aliases. + */ + flags: TypedFlags & Record; + + /** + Flags converted camelCase including aliases. + */ + unnormalizedFlags: TypedFlags & Record; + + /** + The `package.json` object. + */ + pkg: PackageJson; + + /** + The help text used with `--help`. + */ + help: string; + + /** + Show the help text and exit with code. + + @param exitCode - The exit code to use. Default: `2`. + */ + showHelp: (exitCode?: number) => void; + + /** + Show the version text and exit. + */ + showVersion: () => void; } /** @param helpMessage - Shortcut for the `help` option. @@ -277,9 +275,8 @@ declare namespace meow { @example ``` #!/usr/bin/env node -'use strict'; -import meow = require('meow'); -import foo = require('.'); +import meow from 'meow'; +import foo from './index.js'; const cli = meow(` Usage @@ -309,7 +306,7 @@ const cli = meow(` foo(cli.input[0], cli.flags); ``` */ -declare function meow(helpMessage: string, options?: meow.Options): meow.Result; -declare function meow(options?: meow.Options): meow.Result; -export = meow; +export default function meow(helpMessage: string, options?: Options): Result; +// eslint-disable-next-line no-redeclare +export default function meow(options?: Options): Result; diff --git a/index.js b/index.js index f9a6423..123782a 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,13 @@ -'use strict'; -const path = require('path'); -const buildParserOptions = require('minimist-options'); -const parseArguments = require('yargs-parser'); -const camelCaseKeys = require('camelcase-keys'); -const decamelize = require('decamelize'); -const decamelizeKeys = require('decamelize-keys'); -const trimNewlines = require('trim-newlines'); -const redent = require('redent'); -const readPkgUp = require('read-pkg-up'); -const hardRejection = require('hard-rejection'); -const normalizePackageData = require('normalize-package-data'); - -// Prevent caching of this module so module.parent is always accurate -delete require.cache[__filename]; -const parentDir = path.dirname(module.parent && module.parent.filename ? module.parent.filename : '.'); +import buildParserOptions from 'minimist-options'; +import parseArguments from 'yargs-parser'; +import camelCaseKeys from 'camelcase-keys'; +import decamelize from 'decamelize'; +import decamelizeKeys from 'decamelize-keys'; +import trimNewlines from 'trim-newlines'; +import redent from 'redent'; +import {readPackageUpSync} from 'read-pkg-up'; +import hardRejection from 'hard-rejection'; +import normalizePackageData from 'normalize-package-data'; const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { const flag = definedFlags[flagName]; @@ -109,8 +103,7 @@ const meow = (helpText, options) => { helpText = ''; } - const foundPkg = readPkgUp.sync({ - cwd: parentDir, + const foundPkg = readPackageUpSync({ normalize: false }); @@ -231,4 +224,4 @@ const meow = (helpText, options) => { }; }; -module.exports = meow; +export default meow; diff --git a/index.test-d.ts b/index.test-d.ts index 4e7da4a..5c154d2 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,7 +1,6 @@ import {expectAssignable, expectType} from 'tsd'; import {PackageJson} from 'type-fest'; -import meow = require('.'); -import {Result} from '.'; +import meow, {Result} from './index.js'; expectType>(meow('Help text')); expectType>(meow('Help text', {hardRejection: false})); diff --git a/package.json b/package.json index 6875491..d4caf34 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, + "type": "module", + "exports": "./index.js", "engines": { - "node": ">=10" + "node": ">=12" }, "scripts": { "test": "xo && ava && tsd" @@ -47,18 +49,19 @@ "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", + "read-pkg-up": "^8.0.0", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" }, "devDependencies": { - "ava": "^2.4.0", + "ava": "^3.15.0", "execa": "^4.1.0", "indent-string": "^4.0.0", + "read-pkg": "^6.0.0", "tsd": "^0.13.1", - "xo": "^0.34.1" + "xo": "^0.39.1" }, "xo": { "rules": { diff --git a/test/allow-unkonwn-flags.js b/test/allow-unkonwn-flags.js index 9631bb7..fc723df 100644 --- a/test/allow-unkonwn-flags.js +++ b/test/allow-unkonwn-flags.js @@ -1,7 +1,9 @@ -import path from 'path'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; import test from 'ava'; import execa from 'execa'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixtureAllowUnknownFlags = path.join(__dirname, 'fixtures', 'fixture-allow-unknown-flags.js'); test('spawn CLI and test specifying unknown flags', async t => { diff --git a/test/fixtures/fixture-allow-unknown-flags.js b/test/fixtures/fixture-allow-unknown-flags.js index 131e9e9..eb0c100 100755 --- a/test/fixtures/fixture-allow-unknown-flags.js +++ b/test/fixtures/fixture-allow-unknown-flags.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -'use strict'; -const meow = require('../..'); +import meow from '../../index.js'; const cli = meow({ description: 'Custom description', diff --git a/test/fixtures/fixture-required-function.js b/test/fixtures/fixture-required-function.js index ef3c443..c509254 100755 --- a/test/fixtures/fixture-required-function.js +++ b/test/fixtures/fixture-required-function.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -'use strict'; -const meow = require('../..'); +import meow from '../../index.js'; const cli = meow({ description: 'Custom description', diff --git a/test/fixtures/fixture-required-multiple.js b/test/fixtures/fixture-required-multiple.js index fa424e2..907ffc4 100755 --- a/test/fixtures/fixture-required-multiple.js +++ b/test/fixtures/fixture-required-multiple.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -'use strict'; -const meow = require('../..'); +import meow from '../../index.js'; const cli = meow({ description: 'Custom description', diff --git a/test/fixtures/fixture-required.js b/test/fixtures/fixture-required.js index 2f3690e..ad9341a 100755 --- a/test/fixtures/fixture-required.js +++ b/test/fixtures/fixture-required.js @@ -1,6 +1,6 @@ #!/usr/bin/env node 'use strict'; -const meow = require('../..'); +import meow from '../../index.js'; const cli = meow({ description: 'Custom description', diff --git a/test/fixtures/fixture.js b/test/fixtures/fixture.js index 6917bae..5fc0f17 100755 --- a/test/fixtures/fixture.js +++ b/test/fixtures/fixture.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -'use strict'; -const meow = require('../..'); +import meow from '../../index.js'; const cli = meow({ description: 'Custom description', @@ -18,9 +17,9 @@ const cli = meow({ }); if (cli.flags.camelCaseOption === 'foo') { - Object.keys(cli.flags).forEach(x => { + for (const x of Object.keys(cli.flags)) { console.log(x); - }); + } } else { console.log(cli.flags.camelCaseOption); } diff --git a/test/is-required-flag.js b/test/is-required-flag.js index 4586b28..45f4ca9 100644 --- a/test/is-required-flag.js +++ b/test/is-required-flag.js @@ -1,7 +1,9 @@ +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; import test from 'ava'; import execa from 'execa'; -const path = require('path'); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixtureRequiredPath = path.join(__dirname, 'fixtures', 'fixture-required.js'); const fixtureRequiredFunctionPath = path.join(__dirname, 'fixtures', 'fixture-required-function.js'); const fixtureRequiredMultiplePath = path.join(__dirname, 'fixtures', 'fixture-required-multiple.js'); diff --git a/test/test.js b/test/test.js index c8dc19d..0487543 100644 --- a/test/test.js +++ b/test/test.js @@ -1,10 +1,12 @@ +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; import test from 'ava'; import indentString from 'indent-string'; import execa from 'execa'; -import path from 'path'; -import pkg from '../package.json'; -import meow from '..'; +import {readPackageAsync} from 'read-pkg'; +import meow from '../index.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturePath = path.join(__dirname, 'fixtures', 'fixture.js'); const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; @@ -40,6 +42,7 @@ test('support help shortcut', t => { }); test('spawn cli and show version', async t => { + const pkg = await readPackageAsync(); const {stdout} = await execa(fixturePath, ['--version']); t.is(stdout, pkg.version); });