Skip to content

Instantly share code, notes, and snippets.

@yellowled
Last active September 15, 2025 08:52
Show Gist options
  • Save yellowled/43d03663fef238909fc1b8da7bd84984 to your computer and use it in GitHub Desktop.
Save yellowled/43d03663fef238909fc1b8da7bd84984 to your computer and use it in GitHub Desktop.
Checks a list of npm packages (optionally with versions) across a codebase and its subfolders, reporting whether each is found and listing installed versions when no exact version is specified.
/**
* Usage examples:
*
* 1. Check packages listed as CLI arguments:
* node check-deps.js chalk debug [email protected]
*
* 2. Check packages from a file (one per line):
* node check-deps.js -f packages.txt
*
* 3. Mix file and CLI arguments together:
* node check-deps.js -f packages.txt chalk debug
*
* 4. Override default directories to scan:
* node check-deps.js -d src,lib -f packages.txt
*
* Notes:
* - Supports both bare package names (any version) and package@version (exact match).
* - Default directories: ".", "ui-scanner", "ui-sspa".
* - If no version is specified, outputs all installed versions found.
*/
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
// Default folders to scan
let dirs = [".", "ui-scanner", "ui-sspa"];
// Parse CLI args
let packages = [];
const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
if (args[i] === "-f") {
const filePath = args[i + 1];
if (!filePath) {
console.error("❌ Please provide a file path after -f");
process.exit(1);
}
try {
const content = fs.readFileSync(filePath, "utf-8");
const filePackages = content
.split("\n")
.map(line => line.trim())
.filter(line => line && !line.startsWith("#")); // ignore empty lines and comments
packages.push(...filePackages);
} catch (err) {
console.error(`❌ Failed to read file: ${err.message}`);
process.exit(1);
}
i++; // skip file name in the next iteration
} else if (args[i] === "-d") {
const dirList = args[i + 1];
if (!dirList) {
console.error("❌ Please provide a comma-separated list of directories after -d");
process.exit(1);
}
dirs = dirList.split(",").map(d => d.trim()).filter(Boolean);
i++; // skip dir list in the next iteration
} else {
packages.push(args[i]);
}
}
if (packages.length === 0) {
console.error("❌ No packages found to check.");
process.exit(1);
}
// Check packages across specified dirs
packages.forEach(pkg => {
let found = false;
let versions = [];
const hasVersion = pkg.includes("@") && !pkg.startsWith("@"); // skip scoped packages like @babel/core
for (const dir of dirs) {
try {
const output = execSync(`npm ls ${pkg} --all`, {
cwd: path.resolve(dir),
stdio: "pipe"
}).toString();
found = true;
if (!hasVersion) {
const regex = new RegExp(`${pkg}@([0-9][^ ]*)`, "g");
let match;
while ((match = regex.exec(output)) !== null) {
versions.push(match[1]);
}
}
} catch {
// not found in this dir, continue
}
}
if (found) {
if (hasVersion) {
console.log(`FOUND ${pkg}`);
} else {
const uniqVersions = [...new Set(versions)].sort();
console.log(`FOUND ${pkg} → versions: ${uniqVersions.join(", ")}`);
}
} else {
console.log(`NOT FOUND ${pkg}`);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment