From 1ad9e69d09f8249c7edb8b7b0136dbddcff63169 Mon Sep 17 00:00:00 2001 From: cgombauld Date: Mon, 28 Jul 2025 16:38:17 +0200 Subject: [PATCH] feat(cli): add extract integrity command --- README.md | 1 + bin/index.js | 6 +++++ docs/cli/extract-integrity.md | 15 +++++++++++++ i18n/english.js | 4 ++++ i18n/french.js | 4 ++++ package.json | 1 + src/commands/extract-integrity.js | 37 +++++++++++++++++++++++++++++++ src/commands/index.js | 1 + 8 files changed, 69 insertions(+) create mode 100644 docs/cli/extract-integrity.md create mode 100644 src/commands/extract-integrity.js diff --git a/README.md b/README.md index fb2606f8..c8925c6c 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ For complete details on each command, refer to the following documents: - [`config create`](./docs/cli/config.md) - [`config`](./docs/cli/config.md) - [`cache`](./docs/cli/cache.md) +- [`extract integrity`](./docs/cli/extract-integrity.md) Each link provides access to the full documentation for the command, including additional details, options, and usage examples. diff --git a/bin/index.js b/bin/index.js index 5af87307..25c0a753 100755 --- a/bin/index.js +++ b/bin/index.js @@ -128,6 +128,12 @@ prog .describe(i18n.getTokenSync("cli.commands.cache.desc")) .action(commands.cache.main); +prog + .command("extract integrity [spec]") + .describe(i18n.getTokenSync("cli.commands.extractIntegrity.desc")) + .option("-t, --token", i18n.getTokenSync("cli.commands.extractIntegrity.option_token")) + .action(commands.extractIntegrity.main); + prog.parse(process.argv); function defaultScannerCommand(name, options = {}) { diff --git a/docs/cli/extract-integrity.md b/docs/cli/extract-integrity.md new file mode 100644 index 00000000..548acef5 --- /dev/null +++ b/docs/cli/extract-integrity.md @@ -0,0 +1,15 @@ +# 📂 Command `extract integrity` + +The `extract integrity` extract the integrity of a package from its manifest and tarball and compare the two integrities if different from one another. + +## 📜 Syntax + +```bash +$ nsecure extract integrity [spec] +``` + +## ⚙️ Available Options + +| Name | Shortcut | Default Value | Description | +|---|---|---|---| +| `--token` | `-t` | undefined | NPM token. | diff --git a/i18n/english.js b/i18n/english.js index 7ad9f000..53022da8 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -76,6 +76,10 @@ const cli = { cacheTitle: "NodeSecure Cache:", scannedPayloadsTitle: "Scanned payloads available on disk:", cleared: "Cache cleared successfully!" + }, + extractIntegrity: { + desc: "Extract the integrity of a package from its manifest and tarball and compare the two integrities if different from one another.", + option_token: "NPM token" } }, startHttp: { diff --git a/i18n/french.js b/i18n/french.js index 72497141..eaca3eaf 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -76,6 +76,10 @@ const cli = { cacheTitle: "Cache NodeSecure:", scannedPayloadsTitle: "Payloads scannés disponibles sur le disque:", cleared: "Cache nettoyé avec succès !" + }, + extractIntegrity: { + desc: "Extraire l'intégrité d'un paquet à partir de son manifeste et du tarball et comparer les deux intégrités si elles sont différentes.", + option_token: "Jeton NPM" } }, startHttp: { diff --git a/package.json b/package.json index ab0e1b1d..019cbe46 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "@topcli/spinner": "^3.0.0", "cacache": "^19.0.1", "chokidar": "^4.0.3", + "diff": "^8.0.2", "dotenv": "^17.0.0", "filenamify": "^6.0.0", "glob": "^11.0.1", diff --git a/src/commands/extract-integrity.js b/src/commands/extract-integrity.js new file mode 100644 index 00000000..1acad5ad --- /dev/null +++ b/src/commands/extract-integrity.js @@ -0,0 +1,37 @@ +// Import Third-party Dependencies +import kleur from "kleur"; +import { diffChars } from "diff"; +import { packumentVersion } from "@nodesecure/npm-registry-sdk"; +import { tarball } from "@nodesecure/scanner"; + +export async function main(spec, options) { + const [pkgName, pkgVersion] = spec.split("@"); + const { dist: { tarball: location, shasum: manifestIntegrity } } = await packumentVersion(pkgName, pkgVersion, { + token: options.token + }); + const manifestManager = await tarball.extractAndResolve(location, { + spec + }); + const tarballIntegrity = manifestManager.integrity; + if (manifestIntegrity === tarballIntegrity) { + console.log(`integrity: ${manifestIntegrity}`); + + return; + } + + console.log(`manifest integrity: ${manifestIntegrity}`); + console.log(`tarball integrity: ${tarballIntegrity}`); + process.stdout.write("integrity diff: "); + for (const { added, removed, value } of diffChars(manifestIntegrity, tarballIntegrity)) { + if (added) { + process.stdout.write(kleur.green().bold(`+${value}`)); + } + else if (removed) { + process.stdout.write(kleur.red().bold(`-${value}`)); + } + else { + process.stdout.write(value); + } + } + console.log("\n"); +} diff --git a/src/commands/index.js b/src/commands/index.js index 4abcd7c0..7d08a433 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -7,3 +7,4 @@ export * as config from "./config.js"; export * as scorecard from "./scorecard.js"; export * as report from "./report.js"; export * as cache from "./cache.js"; +export * as extractIntegrity from "./extract-integrity.js";