diff --git a/bin/dev.js b/bin/dev.js index 80b056e2..36cdccdc 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,10 @@ 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 }); +try { + await main(false); +} catch (error) { + process.exit(1); +} diff --git a/bin/main.js b/bin/main.js new file mode 100644 index 00000000..7a92fedf --- /dev/null +++ b/bin/main.js @@ -0,0 +1,29 @@ +import { parseArgs } from 'node:util'; +import { execute } from '@oclif/core'; + +async function main(isProduction = false) { + const { positionals } = parseArgs({ + allowPositionals: true, + strict: false, // Don't validate flags + }); + + // 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 { + 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 9d90bd7c..0273cafe 100755 --- a/bin/run.js +++ b/bin/run.js @@ -1,12 +1,9 @@ #!/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'; +try { + await main(true); +} catch (error) { + process.exit(1); } - -await execute({ dir: import.meta.url }); diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index bef3e8f1..329fece9 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -13,6 +13,71 @@ 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'); + }); + + 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'); + match(stdout, /COMMANDS/, 'Should show commands section'); + }); +}); describe('scan:eol e2e', () => { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesDir = path.resolve(__dirname, '../fixtures'); @@ -46,28 +111,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);