Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions cli/cli/src/cmd/packument/fetch-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string|{name: string}>} 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<string[]>} Array of package names
* @returns {Promise<string[]>} 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.`);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
31 changes: 24 additions & 7 deletions cli/cli/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
}
}

Expand Down