From 6f260149ed8614b798bb2a6e1da5aa9d8793c338 Mon Sep 17 00:00:00 2001 From: jdalton Date: Fri, 27 Jun 2025 12:16:25 -0400 Subject: [PATCH] Cleanup meow use --- patches/meow#13.2.0.patch | 109 ++++++++++++++ src/cli.mts | 8 +- src/commands/fix/cmd-fix.mts | 138 +++++++++--------- src/commands/fix/handle-fix.mts | 71 +++++---- src/commands/manifest/cmd-manifest-cdxgen.mts | 1 - src/commands/npm/cmd-npm.mts | 1 - src/commands/npx/cmd-npx.mts | 1 - src/commands/raw-npm/cmd-raw-npm.mts | 1 - src/commands/raw-npx/cmd-raw-npx.mts | 1 - src/utils/meow-with-subcommands.mts | 30 ++-- 10 files changed, 239 insertions(+), 122 deletions(-) create mode 100644 patches/meow#13.2.0.patch diff --git a/patches/meow#13.2.0.patch b/patches/meow#13.2.0.patch new file mode 100644 index 000000000..3989f40eb --- /dev/null +++ b/patches/meow#13.2.0.patch @@ -0,0 +1,109 @@ +Index: /meow/build/index.d.ts +=================================================================== +--- /meow/build/index.d.ts ++++ /meow/build/index.d.ts +@@ -1313,8 +1313,15 @@ + */ + readonly allowUnknownFlags?: boolean; + + /** ++ Whether to collect unknown flags or not. ++ ++ @default false ++ */ ++ readonly collectUnknownFlags?: boolean; ++ ++ /** + The number of spaces to use for indenting the help text. + + @default 2 + */ +@@ -1353,8 +1360,13 @@ + Flags converted to camelCase excluding aliases. + */ + flags: CamelCasedProperties> & Record; + ++ /** ++ Collection of unknown flags. ++ */ ++ unknownFlags: string[] ++ + /** + Flags converted camelCase including aliases. + */ + unnormalizedFlags: TypedFlags & Record; +Index: /meow/build/index.js +=================================================================== +--- /meow/build/index.js ++++ /meow/build/index.js +@@ -1,9 +1,9 @@ + import process from 'node:process'; + import { y as yargsParser, t as trimNewlines, r as redent, n as normalizePackageData, c as camelcaseKeys } from './dependencies.js'; + import { buildOptions } from './options.js'; + import { buildParserOptions } from './parser.js'; +-import { checkUnknownFlags, validate, checkMissingRequiredFlags } from './validate.js'; ++import { checkUnknownFlags, validate, checkMissingRequiredFlags, collectUnknownFlags } from './validate.js'; + + const buildResult = (options, parserOptions) => { + const {pkg: package_} = options; + const argv = yargsParser(options.argv, parserOptions); +@@ -54,8 +54,9 @@ + checkUnknownFlags(input); + } + + const flags = camelcaseKeys(argv, {exclude: ['--', /^\w$/]}); ++ const unknownFlags = options.collectUnknownFlags ? collectUnknownFlags(input) : []; + const unnormalizedFlags = {...flags}; + + validate(flags, options); + +@@ -73,8 +74,9 @@ + + return { + input, + flags, ++ unknownFlags, + unnormalizedFlags, + pkg: package_, + help, + showHelp, +Index: /meow/build/parser.js +=================================================================== +--- /meow/build/parser.js ++++ /meow/build/parser.js +@@ -72,9 +72,9 @@ + if (parserOptions['--']) { + parserOptions.configuration['populate--'] = true; + } + +- if (!options.allowUnknownFlags) { ++ if (!options.allowUnknownFlags || options.collectUnknownFlags) { + // Collect unknown options in `argv._` to be checked later. + parserOptions.configuration['unknown-options-as-args'] = true; + } + +Index: /meow/build/validate.js +=================================================================== +--- /meow/build/validate.js ++++ /meow/build/validate.js +@@ -67,10 +67,12 @@ + ...unknownFlags, + ].join('\n')); + }; + ++const collectUnknownFlags = input => input.filter(item => typeof item === 'string' && item.startsWith('-')); ++ + const checkUnknownFlags = input => { +- const unknownFlags = input.filter(item => typeof item === 'string' && item.startsWith('-')); ++ const unknownFlags = collectUnknownFlags(input); + if (unknownFlags.length > 0) { + reportUnknownFlags(unknownFlags); + process.exit(2); + } +@@ -118,5 +120,5 @@ + process.exit(2); + } + }; + +-export { checkMissingRequiredFlags, checkUnknownFlags, validate }; ++export { checkMissingRequiredFlags, checkUnknownFlags, collectUnknownFlags, validate }; diff --git a/src/cli.mts b/src/cli.mts index a8a0d4af1..2b0aeccae 100755 --- a/src/cli.mts +++ b/src/cli.mts @@ -185,16 +185,14 @@ void (async () => { debugFn('Uncaught error (BAD!):') debugFn(e) - // Try to parse the flags, find out if --json or --markdown is set + // Try to parse the flags, find out if --json or --markdown is set. let isJson = false try { const cli = meow(``, { argv: process.argv.slice(2), - importMeta: { url: `${pathToFileURL(__filename)}` } as ImportMeta, - flags: {}, - // Do not strictly check for flags here. - allowUnknownFlags: true, autoHelp: false, + flags: {}, + importMeta: { url: `${pathToFileURL(__filename)}` } as ImportMeta, }) isJson = !!cli.flags['json'] } catch {} diff --git a/src/commands/fix/cmd-fix.mts b/src/commands/fix/cmd-fix.mts index 635706fd9..1d1739fbd 100644 --- a/src/commands/fix/cmd-fix.mts +++ b/src/commands/fix/cmd-fix.mts @@ -20,76 +20,78 @@ import type { RangeStyle } from '../../utils/semver.mts' const { DRY_RUN_NOT_SAVING } = constants +const flags: CliCommandConfig['flags'] = { + ...commonFlags, + autoMerge: { + type: 'boolean', + default: false, + description: `Enable auto-merge for pull requests that Socket opens.\n See ${terminalLink( + 'GitHub documentation', + 'https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository', + )} for managing auto-merge for pull requests in your repository.`, + }, + autopilot: { + type: 'boolean', + default: false, + description: `Shorthand for --autoMerge --test`, + }, + ghsa: { + type: 'string', + default: [], + description: `Provide a list of ${terminalLink( + 'GHSA IDs', + 'https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids', + )} to compute fixes for, as either a comma separated value or as multiple flags.\n Use '--ghsa auto' to automatically lookup GHSA IDs and compute fixes for them.`, + isMultiple: true, + }, + limit: { + type: 'number', + default: Infinity, + description: 'The number of fixes to attempt at a time', + }, + purl: { + type: 'string', + default: [], + description: `Provide a list of ${terminalLink( + 'PURLs', + 'https://github.com/package-url/purl-spec?tab=readme-ov-file#purl', + )} to compute fixes for, as either a comma separated value or as multiple flags,\n instead of querying the Socket API`, + isMultiple: true, + shortFlag: 'p', + }, + rangeStyle: { + type: 'string', + default: 'preserve', + description: ` + Define how updated dependency versions should be written in package.json. + Available styles: + * caret - Use ^ range for compatible updates (e.g. ^1.2.3) + * gt - Use > to allow any newer version (e.g. >1.2.3) + * gte - Use >= to allow any newer version (e.g. >=1.2.3) + * lt - Use < to allow only lower versions (e.g. <1.2.3) + * lte - Use <= to allow only lower versions (e.g. <=1.2.3) + * pin - Use the exact version (e.g. 1.2.3) + * preserve - Retain the existing version range style as-is + * tilde - Use ~ range for patch/minor updates (e.g. ~1.2.3) + `.trim(), + }, + test: { + type: 'boolean', + default: false, + description: 'Verify the fix by running unit tests', + }, + testScript: { + type: 'string', + default: 'test', + description: 'The test script to run for each fix attempt', + }, +} + const config: CliCommandConfig = { commandName: 'fix', description: 'Update dependencies with "fixable" Socket alerts', hidden: false, - flags: { - ...commonFlags, - autoMerge: { - type: 'boolean', - default: false, - description: `Enable auto-merge for pull requests that Socket opens.\n See ${terminalLink( - 'GitHub documentation', - 'https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository', - )} for managing auto-merge for pull requests in your repository.`, - }, - autopilot: { - type: 'boolean', - default: false, - description: `Shorthand for --autoMerge --test`, - }, - ghsa: { - type: 'string', - default: [], - description: `Provide a list of ${terminalLink( - 'GHSA IDs', - 'https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids', - )} to compute fixes for, as either a comma separated value or as multiple flags.\n Use '--ghsa auto' to automatically lookup GHSA IDs and compute fixes for them.`, - isMultiple: true, - }, - limit: { - type: 'number', - default: Infinity, - description: 'The number of fixes to attempt at a time', - }, - purl: { - type: 'string', - default: [], - description: `Provide a list of ${terminalLink( - 'PURLs', - 'https://github.com/package-url/purl-spec?tab=readme-ov-file#purl', - )} to compute fixes for, as either a comma separated value or as multiple flags,\n instead of querying the Socket API`, - isMultiple: true, - shortFlag: 'p', - }, - rangeStyle: { - type: 'string', - default: 'preserve', - description: ` - Define how updated dependency versions should be written in package.json. - Available styles: - * caret - Use ^ range for compatible updates (e.g. ^1.2.3) - * gt - Use > to allow any newer version (e.g. >1.2.3) - * gte - Use >= to allow any newer version (e.g. >=1.2.3) - * lt - Use < to allow only lower versions (e.g. <1.2.3) - * lte - Use <= to allow only lower versions (e.g. <=1.2.3) - * pin - Use the exact version (e.g. 1.2.3) - * preserve - Retain the existing version range style as-is - * tilde - Use ~ range for patch/minor updates (e.g. ~1.2.3) - `.trim(), - }, - test: { - type: 'boolean', - default: false, - description: 'Verify the fix by running unit tests', - }, - testScript: { - type: 'string', - default: 'test', - description: 'The test script to run for each fix attempt', - }, - }, + flags, help: (command, config) => ` Usage $ ${command} [options] [CWD=.] @@ -167,8 +169,9 @@ async function run( : Infinity) || Infinity const purls = cmdFlagValueToArray(cli.flags['purl']) const testScript = String(cli.flags['testScript'] || 'test') + const { unknownFlags } = cli - await handleFix(argv, { + await handleFix({ autoMerge, cwd, ghsas, @@ -178,5 +181,6 @@ async function run( rangeStyle, test, testScript, + unknownFlags, }) } diff --git a/src/commands/fix/handle-fix.mts b/src/commands/fix/handle-fix.mts index 2dba70a1e..ad25e06a3 100644 --- a/src/commands/fix/handle-fix.mts +++ b/src/commands/fix/handle-fix.mts @@ -21,28 +21,27 @@ export type HandleFixOptions = Remap< FixOptions & { ghsas: string[] outputKind: OutputKind + unknownFlags: string[] } > -export async function handleFix( - argv: string[] | readonly string[], - { - autoMerge, - cwd, - ghsas, - limit, - outputKind, - purls, - rangeStyle, - test, - testScript, - }: HandleFixOptions, -) { +export async function handleFix({ + autoMerge, + cwd, + ghsas, + limit, + outputKind, + purls, + rangeStyle, + test, + testScript, + unknownFlags, +}: HandleFixOptions) { + // Lazily access constants.spinner. + const { spinner } = constants + let { length: ghsasCount } = ghsas if (ghsasCount) { - // Lazily access constants.spinner. - const { spinner } = constants - spinner.start('Fetching GHSA IDs...') if (ghsasCount === 1 && ghsas[0] === 'auto') { @@ -50,6 +49,9 @@ export async function handleFix( ['compute-fixes-and-upgrade-purls', cwd], { cwd, spinner }, ) + + spinner.stop() + if (autoCResult.ok) { ghsas = cmdFlagValueToArray( /(?<=Vulnerabilities found: )[^\n]+/.exec( @@ -65,25 +67,34 @@ export async function handleFix( ghsas = [] ghsasCount = 0 } + + spinner.start() } if (ghsasCount) { spinner.info(`Found ${ghsasCount} GHSA ${pluralize('ID', ghsasCount)}.`) - await outputFixResult( - await spawnCoana( - [ - 'compute-fixes-and-upgrade-purls', - cwd, - '--apply-fixes-to', - ...ghsas, - ...argv, - ], - { cwd, spinner }, - ), - outputKind, + const applyFixesCResult = await spawnCoana( + [ + 'compute-fixes-and-upgrade-purls', + cwd, + '--apply-fixes-to', + ...ghsas, + ...unknownFlags, + ], + { cwd, spinner }, ) + spinner.stop() + + if (!applyFixesCResult.ok) { + debugFn('coana fail:', { + message: applyFixesCResult.message, + cause: applyFixesCResult.cause, + }) + } + + await outputFixResult(applyFixesCResult, outputKind) return } @@ -138,8 +149,6 @@ export async function handleFix( return } - // Lazily access spinner. - const { spinner } = constants const fixer = agent === NPM ? npmFix : pnpmFix await outputFixResult( diff --git a/src/commands/manifest/cmd-manifest-cdxgen.mts b/src/commands/manifest/cmd-manifest-cdxgen.mts index 4c37da3f3..12b6e49fb 100644 --- a/src/commands/manifest/cmd-manifest-cdxgen.mts +++ b/src/commands/manifest/cmd-manifest-cdxgen.mts @@ -238,7 +238,6 @@ async function run( { parentName }: { parentName: string }, ): Promise { const cli = meowOrExit({ - allowUnknownFlags: true, // Don't let meow take over --help. argv: argv.filter(a => !isHelpFlag(a)), config, diff --git a/src/commands/npm/cmd-npm.mts b/src/commands/npm/cmd-npm.mts index f6e7b14ba..6501e8eb2 100644 --- a/src/commands/npm/cmd-npm.mts +++ b/src/commands/npm/cmd-npm.mts @@ -50,7 +50,6 @@ async function run( { parentName }: { parentName: string }, ): Promise { const cli = meowOrExit({ - allowUnknownFlags: true, argv, config, importMeta, diff --git a/src/commands/npx/cmd-npx.mts b/src/commands/npx/cmd-npx.mts index 1a5481ff6..1488c018d 100644 --- a/src/commands/npx/cmd-npx.mts +++ b/src/commands/npx/cmd-npx.mts @@ -50,7 +50,6 @@ async function run( { parentName }: { parentName: string }, ): Promise { const cli = meowOrExit({ - allowUnknownFlags: true, argv, config, importMeta, diff --git a/src/commands/raw-npm/cmd-raw-npm.mts b/src/commands/raw-npm/cmd-raw-npm.mts index 39a45d1af..751fc3c46 100644 --- a/src/commands/raw-npm/cmd-raw-npm.mts +++ b/src/commands/raw-npm/cmd-raw-npm.mts @@ -41,7 +41,6 @@ async function run( { parentName }: { parentName: string }, ): Promise { const cli = meowOrExit({ - allowUnknownFlags: true, argv, config, importMeta, diff --git a/src/commands/raw-npx/cmd-raw-npx.mts b/src/commands/raw-npx/cmd-raw-npx.mts index 0e8d7a1d0..5bff21cb7 100644 --- a/src/commands/raw-npx/cmd-raw-npx.mts +++ b/src/commands/raw-npx/cmd-raw-npx.mts @@ -41,7 +41,6 @@ async function run( { parentName }: { parentName: string }, ): Promise { const cli = meowOrExit({ - allowUnknownFlags: true, argv, config, importMeta, diff --git a/src/utils/meow-with-subcommands.mts b/src/utils/meow-with-subcommands.mts index 6b88d1085..c6a09f9ee 100644 --- a/src/utils/meow-with-subcommands.mts +++ b/src/utils/meow-with-subcommands.mts @@ -287,8 +287,8 @@ export async function meowWithSubcommands( if (commands.size) { logger.fail( 'Found commands in the list that were not marked as public or not defined at all:', - // Node < 22 will print 'Object (n)' before the array. So to have - // consistent test snapshots we use joinAnd. + // Node < 22 will print 'Object (n)' before the array. So to have consistent + // test snapshots we use joinAnd. joinAnd( Array.from(commands) .sort(naturalCompare) @@ -392,10 +392,11 @@ ${isRootCommand ? ` $ ${name} scan create --json` : ''}${isRootCommand ? `\ flags, // Do not strictly check for flags here. allowUnknownFlags: true, - booleanDefault: undefined, // We want to detect whether a bool flag is given at all. - // We will emit help when we're ready + // We will emit help when we're ready. // Plus, if we allow this then meow() can just exit here. autoHelp: false, + // We want to detect whether a bool flag is given at all. + booleanDefault: undefined, }, ) @@ -419,14 +420,15 @@ ${isRootCommand ? ` $ ${name} scan create --json` : ''}${isRootCommand ? `\ * Note: meow will exit immediately if it calls its .showHelp() */ export function meowOrExit({ - // allowUnknownFlags, // commands that pass-through args need to allow this argv, + collectUnknownFlags = true, config, importMeta, parentName, }: { allowUnknownFlags?: boolean | undefined argv: readonly string[] + collectUnknownFlags?: boolean | undefined config: CliCommandConfig parentName: string importMeta: ImportMeta @@ -437,13 +439,13 @@ export function meowOrExit({ // This exits if .printHelp() is called either by meow itself or by us. const cli = meow({ argv, + autoHelp: false, // meow will exit(0) before printing the banner. + booleanDefault: undefined, // We want to detect whether a bool flag is given at all. + collectUnknownFlags, description: config.description, + flags: config.flags, help: config.help(command, config), importMeta, - flags: config.flags, - allowUnknownFlags: true, // meow will exit(1) before printing the banner - booleanDefault: undefined, // We want to detect whether a bool flag is given at all. - autoHelp: false, // meow will exit(0) before printing the banner }) if (!cli.flags['nobanner']) { @@ -453,18 +455,18 @@ export function meowOrExit({ } // As per https://github.com/sindresorhus/meow/issues/178 - // Setting allowUnknownFlags:true makes it reject camel cased flags... + // Setting `allowUnknownFlags: false` makes it reject camel cased flags. // if (!allowUnknownFlags) { // // Run meow specifically with the flag setting. It will exit(2) if an // // invalid flag is set and print a message. // meow({ // argv, + // allowUnknownFlags: false, + // autoHelp: false, // description: config.description, + // flags: config.flags, // help: config.help(command, config), // importMeta, - // flags: config.flags, - // allowUnknownFlags: false, - // autoHelp: false, // }) // } @@ -482,7 +484,7 @@ export function meowOrExit({ importMeta, flags: config.flags, // As per https://github.com/sindresorhus/meow/issues/178 - // Setting allowUnknownFlags:true makes it reject camel cased flags... + // Setting `allowUnknownFlags: false` makes it reject camel cased flags. // allowUnknownFlags: Boolean(allowUnknownFlags), autoHelp: false, })