From 4a6b0d7f8a29e0445daa2172710d6c0cb9f6dbb3 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 8 Mar 2026 22:02:54 -0400 Subject: [PATCH] feat(cli) add input dedup, --no-cache flag, and subcommand fallback Add `uniqueNames()` to `fetch-list.js` to deduplicate package names from JSON and text inputs, handling both plain strings and `{name}` objects. Wire up the existing `--no-cache` CLI flag to actually pass `{ cache: false }` through to the packument client. Refactor `importCommand` in `index.js` to gracefully handle missing subcommands by attempting to load the command's `index.js` module before falling back to a usage error. Co-Authored-By: Claude Opus 4.6 --- cli/cli/src/cmd/packument/fetch-list.js | 42 ++++++++++++++++++++++--- cli/cli/src/index.js | 31 +++++++++++++----- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/cli/cli/src/cmd/packument/fetch-list.js b/cli/cli/src/cmd/packument/fetch-list.js index a6573b9..aa6a8d6 100644 --- a/cli/cli/src/cmd/packument/fetch-list.js +++ b/cli/cli/src/cmd/packument/fetch-list.js @@ -42,19 +42,49 @@ function parseTextFile(filepath) { return packages; } +/** + * Extract unique package names from a parsed list. + * Handles both plain strings and {name, ...} objects. + * @param {Array} items - Raw items from input + * @returns {string[]} Deduplicated array of package names + */ +function uniqueNames(items) { + const seen = new Set(); + const names = []; + + for (const item of items) { + const name = typeof item === 'string' ? item + : (item && typeof item.name === 'string') ? item.name + : null; + + if (!name) { + console.warn(`Warning: Skipping unrecognized entry: ${JSON.stringify(item)}`); + continue; + } + + if (!seen.has(name)) { + seen.add(name); + names.push(name); + } + } + + return names; +} + /** * Load package names from input file * @param {string} fullpath - Absolute path to input file - * @returns {Promise} Array of package names + * @returns {Promise} Deduplicated array of package names */ async function loadPackageNames(fullpath) { const ext = extname(fullpath).toLowerCase(); if (ext === '.json') { const { default: jsonData } = await import(fullpath, { with: { type: 'json' } }); - return Array.isArray(jsonData) ? jsonData : [jsonData]; + const items = Array.isArray(jsonData) ? jsonData : [jsonData]; + return uniqueNames(items); } else if (ext === '.txt' || ext === '.text' || ext === '') { - return parseTextFile(fullpath); + return uniqueNames(parseTextFile(fullpath)); } else { throw new Error(`Unsupported file type: ${ext}. Use .json or .txt files.`); } @@ -211,6 +241,10 @@ export const command = async cli => { env }); + // Resolve cache flag: --no-cache sets cli.values.cache to false + const useCache = cli.values.cache !== false; + const requestOptions = useCache ? {} : { cache: false }; + // Track progress with atomic counter for concurrent access let processedCount = 0; let interrupted = false; @@ -247,7 +281,7 @@ export const command = async cli => { } try { - const entry = await client.request(name); + const entry = await client.request(name, requestOptions); const cached = entry?.hit ?? false; if (useCheckpoint) { diff --git a/cli/cli/src/index.js b/cli/cli/src/index.js index 77e181f..da36aa4 100644 --- a/cli/cli/src/index.js +++ b/cli/cli/src/index.js @@ -52,14 +52,31 @@ async function importCommand(cmd, subcmd) { process.exit(1); } - const cmdpath = `${cmd}/${subcmd}`; + if (subcmd) { + const cmdpath = `${cmd}/${subcmd}`; + try { + return await import(`./cmd/${cmdpath}.js`); + } catch (err) { + throw error('Failed to load command', { + found: cmdpath, + cause: err + }); + } + } + + // No subcommand provided: try loading the command's index module try { - return await import(`./cmd/${cmdpath}.js`); - } catch (err) { - throw error('Failed to load command', { - found: cmdpath, - cause: err - }); + return await import(`./cmd/${cmd}/index.js`); + } catch { + // No index.js exists for this command; return a stub so --help + // can still print general CLI usage via output.js fallback + return { + command: () => { + console.error(`Error: No subcommand provided for '${cmd}'`); + console.error(cli.usage()); + process.exit(1); + } + }; } }