From b68760e6bfcfa9e15ecf0b995965f7be82f17d55 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Sat, 26 Apr 2025 12:52:08 -0500 Subject: [PATCH 1/5] feat: accept scan eol flags by default Previously, the command `npx @herodevs/cli@beta --json` would fail. With this change, users pass in flags and apply them to the `scan eol` command by default. --- bin/dev.js | 11 ++------ bin/main.js | 29 ++++++++++++++++++++ bin/run.js | 11 ++------ e2e/scan/eol.test.ts | 65 +++++++++++++++++++++++++++++--------------- 4 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 bin/main.js diff --git a/bin/dev.js b/bin/dev.js index 80b056e2..db852c83 100755 --- a/bin/dev.js +++ b/bin/dev.js @@ -1,7 +1,5 @@ #!/usr/bin/env node -import { execute } from '@oclif/core'; - // Localhost // process.env.GRAPHQL_HOST = 'http://localhost:3000'; @@ -11,11 +9,6 @@ process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com'; // Prod // process.env.GRAPHQL_HOST = 'https://api.nes.herodevs.com'; -// If no command is provided, default to scan:eol -t -// See https://github.com/oclif/oclif/issues/277#issuecomment-657352674 for more info -if (process.argv.length === 2) { - process.argv[2] = 'scan:eol'; - process.argv[3] = '-t'; -} +import main from './main.js'; -await execute({ development: true, dir: import.meta.url }); +main(false).catch(console.error); diff --git a/bin/main.js b/bin/main.js new file mode 100644 index 00000000..30cf6b96 --- /dev/null +++ b/bin/main.js @@ -0,0 +1,29 @@ +import { execute } from '@oclif/core'; + +async function main(isProduction = false) { + // If no command is provided, default to scan:eol -t + // See https://github.com/oclif/oclif/issues/277#issuecomment-657352674 for more info + if (process.argv.length === 2) { + process.argv[2] = 'scan:eol'; + process.argv[3] = '-t'; + } else if (process.argv.length > 2) { + const firstArg = process.argv[2]; + const isFlag = firstArg.startsWith('-'); + + // If it's a flag or not a valid command, insert scan:eol + if (isFlag || (!firstArg.includes(':') && process.argv.length === 3)) { + process.argv.splice(2, 0, 'scan:eol'); + // Add -t flag if no other flags are present + if (!process.argv.some((arg) => arg.startsWith('-'))) { + process.argv.splice(3, 0, '-t'); + } + } + } + + await execute({ + development: !isProduction, + dir: new URL('./dev.js', import.meta.url), + }); +} + +export default main; diff --git a/bin/run.js b/bin/run.js index 9d90bd7c..deb5b834 100755 --- a/bin/run.js +++ b/bin/run.js @@ -1,12 +1,5 @@ #!/usr/bin/env node -import { execute } from '@oclif/core'; +import main from './main.js'; -// If no command is provided, default to scan:eol -t -// See https://github.com/oclif/oclif/issues/277#issuecomment-657352674 for more info -if (process.argv.length === 2) { - process.argv[2] = 'scan:eol'; - process.argv[3] = '-t'; -} - -await execute({ dir: import.meta.url }); +await main(); diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index bef3e8f1..5299b036 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -13,6 +13,49 @@ const execAsync = promisify(exec); const GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com'; +describe('default arguments', () => { + it('defaults to scan:eol -t when no arguments are provided', async () => { + // Run the CLI directly with no arguments + const { stdout } = await execAsync('node bin/run.js', { + env: { ...process.env, GRAPHQL_HOST }, + }); + + // Match table header + match(stdout, /┌.*┬.*┬.*┬.*┬.*┐/, 'Should show table top border'); + match(stdout, /│ NAME\s*│ VERSION\s*│ EOL\s*│ DAYS EOL\s*│ TYPE\s*│ # OF VULNS*|/, 'Should show table headers'); + match(stdout, /├.*┼.*┼.*┼.*┼.*┤/, 'Should show table header separator'); + + // Match table content + match( + stdout, + /│ bootstrap\s*│ 3\.1\.1\s*│ 2019-07-24\s*│ \d+\s*│ npm\s*│/, + 'Should show bootstrap package in table', + ); + + // Match table footer + match(stdout, /└.*┴.*┴.*┴.*┴.*┘/, 'Should show table bottom border'); + }); + + it('runs scan:eol -a -t when -a -t is passed in', async () => { + const { stdout, stderr } = await execAsync('node bin/run.js -a -t', { + env: { ...process.env, GRAPHQL_HOST }, + }); + + // Verify command executed successfully + match(stdout, /components scanned/, 'Should show components scanned message'); + }); + + it('runs scan:eol --json when --json is passed in', async () => { + // Run the CLI with --json flag + const { stdout } = await execAsync('node bin/run.js --json', { + env: { ...process.env, GRAPHQL_HOST }, + }); + + // Verify JSON output + doesNotMatch(stdout, /Here are the results of the scan:/, 'Should not show results header'); + doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); + }); +}); describe('scan:eol e2e', () => { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesDir = path.resolve(__dirname, '../fixtures'); @@ -46,28 +89,6 @@ describe('scan:eol e2e', () => { return output; } - it('defaults to scan:eol -t when no arguments are provided', async () => { - // Run the CLI directly with no arguments - const { stdout } = await execAsync('node bin/run.js', { - env: { ...process.env, GRAPHQL_HOST }, - }); - - // Match table header - match(stdout, /┌.*┬.*┬.*┬.*┬.*┐/, 'Should show table top border'); - match(stdout, /│ NAME\s*│ VERSION\s*│ EOL\s*│ DAYS EOL\s*│ TYPE\s*│ # OF VULNS*|/, 'Should show table headers'); - match(stdout, /├.*┼.*┼.*┼.*┼.*┤/, 'Should show table header separator'); - - // Match table content - match( - stdout, - /│ bootstrap\s*│ 3\.1\.1\s*│ 2019-07-24\s*│ \d+\s*│ npm\s*│/, - 'Should show bootstrap package in table', - ); - - // Match table footer - match(stdout, /└.*┴.*┴.*┴.*┴.*┘/, 'Should show table bottom border'); - }); - it('scans existing SBOM for EOL components', async () => { const cmd = `scan:eol --file ${simpleSbom}`; const { stdout } = await run(cmd); From 30a3947bae456a587180b456c619b9a4f350b32b Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Sat, 26 Apr 2025 12:57:54 -0500 Subject: [PATCH 2/5] chore: ensure help option works --- bin/main.js | 2 +- e2e/scan/eol.test.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/main.js b/bin/main.js index 30cf6b96..70d9e03a 100644 --- a/bin/main.js +++ b/bin/main.js @@ -11,7 +11,7 @@ async function main(isProduction = false) { const isFlag = firstArg.startsWith('-'); // If it's a flag or not a valid command, insert scan:eol - if (isFlag || (!firstArg.includes(':') && process.argv.length === 3)) { + if (isFlag && !['--help', '-h'].includes(firstArg)) { process.argv.splice(2, 0, 'scan:eol'); // Add -t flag if no other flags are present if (!process.argv.some((arg) => arg.startsWith('-'))) { diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index 5299b036..906f1525 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -55,6 +55,17 @@ describe('default arguments', () => { doesNotMatch(stdout, /Here are the results of the scan:/, 'Should not show results header'); doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); }); + + it('shows help when --help is passed in', async () => { + const { stdout } = await execAsync('node bin/run.js --help', { + env: { ...process.env, GRAPHQL_HOST }, + }); + + // Verify help output + match(stdout, /USAGE/, 'Should show usage section'); + match(stdout, /TOPICS/, 'Should show topics section'); + match(stdout, /COMMANDS/, 'Should show commands section'); + }); }); describe('scan:eol e2e', () => { const __dirname = path.dirname(fileURLToPath(import.meta.url)); From 64cdbf7843e7e96fa968ea83454b3ce1966f8aea Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Sat, 26 Apr 2025 13:08:15 -0500 Subject: [PATCH 3/5] chore: improve async handling --- bin/dev.js | 6 +++++- bin/main.js | 12 ++++++++---- bin/run.js | 6 +++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bin/dev.js b/bin/dev.js index db852c83..36cdccdc 100755 --- a/bin/dev.js +++ b/bin/dev.js @@ -11,4 +11,8 @@ process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com'; import main from './main.js'; -main(false).catch(console.error); +try { + await main(false); +} catch (error) { + process.exit(1); +} diff --git a/bin/main.js b/bin/main.js index 70d9e03a..eba2229d 100644 --- a/bin/main.js +++ b/bin/main.js @@ -20,10 +20,14 @@ async function main(isProduction = false) { } } - await execute({ - development: !isProduction, - dir: new URL('./dev.js', import.meta.url), - }); + try { + await execute({ + development: !isProduction, + dir: new URL('./dev.js', import.meta.url), + }); + } catch (error) { + process.exit(1); + } } export default main; diff --git a/bin/run.js b/bin/run.js index deb5b834..0273cafe 100755 --- a/bin/run.js +++ b/bin/run.js @@ -2,4 +2,8 @@ import main from './main.js'; -await main(); +try { + await main(true); +} catch (error) { + process.exit(1); +} From d20dfbb26358596127f665d31efcca9506a7a59b Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 28 Apr 2025 11:41:48 -0500 Subject: [PATCH 4/5] chore: use node util for parsing args --- bin/main.js | 29 +++++++++++++---------------- e2e/scan/eol.test.ts | 13 ++++++++++++- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/bin/main.js b/bin/main.js index eba2229d..cf11ba98 100644 --- a/bin/main.js +++ b/bin/main.js @@ -1,23 +1,20 @@ +import { parseArgs } from 'node:util'; import { execute } from '@oclif/core'; async function main(isProduction = false) { - // If no command is provided, default to scan:eol -t - // See https://github.com/oclif/oclif/issues/277#issuecomment-657352674 for more info - if (process.argv.length === 2) { - process.argv[2] = 'scan:eol'; - process.argv[3] = '-t'; - } else if (process.argv.length > 2) { - const firstArg = process.argv[2]; - const isFlag = firstArg.startsWith('-'); + const { positionals } = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, + strict: false, // Don't validate flags + }); - // If it's a flag or not a valid command, insert scan:eol - if (isFlag && !['--help', '-h'].includes(firstArg)) { - process.argv.splice(2, 0, 'scan:eol'); - // Add -t flag if no other flags are present - if (!process.argv.some((arg) => arg.startsWith('-'))) { - process.argv.splice(3, 0, '-t'); - } - } + // If no arguments at all, default to scan:eol -t + if (positionals.length === 0) { + process.argv.splice(2, 0, 'scan:eol', '-t'); + } + // If only flags are provided, set scan:eol as the command for those flags + else if (positionals.length === 1 && positionals[0].startsWith('-')) { + process.argv.splice(2, 0, 'scan:eol'); } try { diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index 906f1525..329fece9 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -56,11 +56,22 @@ describe('default arguments', () => { doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); }); - it('shows help when --help is passed in', async () => { + it('shows help for scan:eol when --help is passed in', async () => { const { stdout } = await execAsync('node bin/run.js --help', { env: { ...process.env, GRAPHQL_HOST }, }); + // Verify help output + match(stdout, /USAGE/, 'Should show usage section'); + match(stdout, /FLAGS/, 'Should show flags section'); + match(stdout, /EXAMPLES/, 'Should show examples section'); + }); + + it('shows global help when help is passed in', async () => { + const { stdout } = await execAsync('node bin/run.js help', { + env: { ...process.env, GRAPHQL_HOST }, + }); + // Verify help output match(stdout, /USAGE/, 'Should show usage section'); match(stdout, /TOPICS/, 'Should show topics section'); From 720e67f12a4aa8154b0f74c91e245b7058e0292d Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 28 Apr 2025 11:47:54 -0500 Subject: [PATCH 5/5] chore: simplify main script Delete unnecessary config: args array of argument strings. Default: process.argv with execPath and filename removed. --- bin/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/main.js b/bin/main.js index cf11ba98..7a92fedf 100644 --- a/bin/main.js +++ b/bin/main.js @@ -3,7 +3,6 @@ import { execute } from '@oclif/core'; async function main(isProduction = false) { const { positionals } = parseArgs({ - args: process.argv.slice(2), allowPositionals: true, strict: false, // Don't validate flags });