@nodesecure/cli
Version:
Node.js security CLI
198 lines (167 loc) • 5.53 kB
JavaScript
// Import Node.js Dependencies
import fs from "node:fs";
import path from "node:path";
import events from "node:events";
// Import Third-party Dependencies
import kleur from "kleur";
import filenamify from "filenamify";
import { Spinner } from "@topcli/spinner";
import ms from "ms";
import * as i18n from "@nodesecure/i18n";
import * as Scanner from "@nodesecure/scanner";
// Import Internal Dependencies
import * as http from "./http.js";
import { appCache } from "../cache.js";
export async function auto(spec, options) {
const { keep, ...commandOptions } = options;
const payloadFile = await (
typeof spec === "string" ?
from(spec, commandOptions) :
cwd(commandOptions)
);
try {
if (payloadFile !== null) {
const developer = Boolean(commandOptions.developer);
await http.start(void 0, {
developer
});
await events.once(process, "SIGINT");
}
}
finally {
if (!keep && payloadFile !== null) {
try {
fs.unlinkSync(payloadFile);
}
catch (error) {
if (error.code !== "ENOENT") {
// eslint-disable-next-line no-unsafe-finally
throw error;
}
}
}
}
}
export async function cwd(options) {
const {
depth: maxDepth = Infinity,
output,
nolock,
full,
vulnerabilityStrategy,
silent
} = options;
const payload = await Scanner.cwd(
process.cwd(),
{ maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy },
initLogger(void 0, !silent)
);
return await logAndWrite(payload, output, { local: true });
}
export async function from(spec, options) {
const { depth: maxDepth = Infinity, output, silent } = options;
const payload = await Scanner.from(
spec,
{ maxDepth },
initLogger(spec, !silent)
);
return await logAndWrite(payload, output);
}
function initLogger(spec, verbose = true) {
const spinner = {
walkTree: new Spinner({ verbose }),
tarball: new Spinner({ verbose }),
registry: new Spinner({ verbose }),
fetchManifest: new Spinner({ verbose }),
i18n: {
start: {
fetchManifest: "cli.commands.from.searching",
walkTree: "depWalker.fetch_and_walk_deps",
tarball: "depWalker.waiting_tarball",
registry: "depWalker.fetch_on_registry"
},
tick: {
tarball: "depWalker.fetch_metadata",
registry: "depWalker.fetch_on_registry"
},
end: {
fetchManifest: "cli.commands.from.fetched",
walkTree: "depWalker.success_fetch_deptree",
tarball: "depWalker.success_tarball",
registry: "depWalker.success_registry_metadata"
}
}
};
const logger = new Scanner.Logger();
logger.on("start", (eventName) => {
if (!(eventName in spinner)) {
return;
}
if (eventName === "fetchManifest") {
spinner[eventName]
.start(kleur.white().bold(i18n.getTokenSync(spinner.i18n.start[eventName], kleur.green().bold(spec))));
}
else {
spinner[eventName]
.start(kleur.white().bold(i18n.getTokenSync(spinner.i18n.start[eventName])));
}
});
logger.on("tick", (eventName) => {
if (!(eventName in spinner) || eventName === "walkTree") {
return;
}
const stats = kleur.gray().bold(`[${kleur.yellow().bold(logger.count(eventName))}/${logger.count("walkTree")}]`);
spinner[eventName].text = kleur.white().bold(`${i18n.getTokenSync(spinner.i18n.tick[eventName])} ${stats}`);
});
logger.on("end", (eventName) => {
if (!(eventName in spinner)) {
return;
}
const spin = spinner[eventName];
const tokenName = spinner.i18n.end[eventName];
const execTime = kleur.cyan().bold(ms(Number(spin.elapsedTime.toFixed(2))));
if (eventName === "walkTree") {
spin.succeed(kleur.white().bold(
i18n.getTokenSync(tokenName, kleur.yellow().bold(i18n.getTokenSync("depWalker.dep_tree")), execTime)));
}
else if (eventName === "registry") {
spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName)));
}
else if (eventName === "tarball") {
spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName, kleur.green().bold(logger.count("walkTree")), execTime)));
}
else if (eventName === "fetchManifest") {
spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName, kleur.green().bold(spec), execTime)));
console.log("");
}
});
return logger;
}
async function logAndWrite(payload, output = "nsecure-result", options = {}) {
const { local = false } = options;
if (payload === null) {
console.log(i18n.getTokenSync("cli.no_dep_to_proceed"));
return null;
}
if (payload.warnings.length > 0) {
console.log(`\n ${kleur.yellow().underline().bold("Global Warning:")}\n`);
for (const warning of payload.warnings) {
console.log(kleur.red().bold(warning));
}
}
const ret = JSON.stringify(payload, null, 2);
if (local) {
// FIXME: would it make more sense to manage this directly within Scanner?
Object.assign(ret, { local });
}
const fileName = path.extname(output) === ".json" ?
filenamify(output) :
`${filenamify(output)}.json`;
const filePath = path.join(process.cwd(), fileName);
fs.writeFileSync(filePath, ret);
console.log("");
console.log(kleur.white().bold(i18n.getTokenSync("cli.successfully_written_json", kleur.green().bold(filePath))));
console.log("");
await appCache.setRootPayload(payload, { logging: false, local });
return filePath;
}