From 43d972b04fbac3ed2f01a58362477c236dd15633 Mon Sep 17 00:00:00 2001 From: GENTILHOMME Thomas Date: Mon, 30 Mar 2026 02:42:26 +0200 Subject: [PATCH] feat(scanner): add integrity to workingDir cacheLookup --- .changeset/six-clowns-punch.md | 5 +++++ workspaces/scanner/README.md | 3 ++- workspaces/scanner/docs/workingDir.md | 3 ++- workspaces/scanner/src/depWalker.ts | 20 +++++++++++++++----- workspaces/scanner/src/index.ts | 14 ++++++++++---- workspaces/scanner/test/workingDir.spec.ts | 10 +++++++++- 6 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 .changeset/six-clowns-punch.md diff --git a/.changeset/six-clowns-punch.md b/.changeset/six-clowns-punch.md new file mode 100644 index 00000000..9cf72d72 --- /dev/null +++ b/.changeset/six-clowns-punch.md @@ -0,0 +1,5 @@ +--- +"@nodesecure/scanner": minor +--- + +Add integrity as second argument of cacheLookup for workingDir API. diff --git a/workspaces/scanner/README.md b/workspaces/scanner/README.md index ec299aa1..899e1df9 100644 --- a/workspaces/scanner/README.md +++ b/workspaces/scanner/README.md @@ -75,7 +75,8 @@ type WorkingDirOptions = Options & { * Optional cache lookup called after reading the local package.json. */ cacheLookup?: ( - packageJSON: PackageJSON + packageJSON: PackageJSON, + integrity: string | null ) => Promise; }; diff --git a/workspaces/scanner/docs/workingDir.md b/workspaces/scanner/docs/workingDir.md index 5ad6e7e8..da223466 100644 --- a/workspaces/scanner/docs/workingDir.md +++ b/workspaces/scanner/docs/workingDir.md @@ -40,7 +40,8 @@ export type WorkingDirOptions = Options & { * If it returns a non-null Payload, the dependency walker is skipped entirely. */ cacheLookup?: ( - packageJSON: PackageJSON + packageJSON: PackageJSON, + integrity: string | null ) => Promise; }; diff --git a/workspaces/scanner/src/depWalker.ts b/workspaces/scanner/src/depWalker.ts index c2790b46..d8bd6764 100644 --- a/workspaces/scanner/src/depWalker.ts +++ b/workspaces/scanner/src/depWalker.ts @@ -95,6 +95,7 @@ type WalkerOptions = Omit & { location?: string; npmRcConfig?: Config; npmRcEntries?: Record; + integrity?: string | null; }; type InitialPayload = @@ -124,7 +125,8 @@ export async function depWalker( npmRcConfig, npmRcEntries = {}, maxConcurrency = 8, - workers + workers, + integrity: manifestIntegrity = null } = options; const statsCollector = new StatsCollector({ logger }, { isVerbose }); @@ -274,10 +276,7 @@ export async function depWalker( payload.rootDependency.integrity = integrity; } else if (isRoot) { - const isWorkspace = options.location && "workspaces" in manifest; - payload.rootDependency.integrity = isWorkspace ? - null : - fromData(JSON.stringify(manifest), { algorithms: ["sha512"] }).toString(); + payload.rootDependency.integrity = manifestIntegrity ?? getManifestIntegrity(manifest); } // If the dependency is a DevDependencies we ignore it. @@ -432,6 +431,17 @@ export async function depWalker( } } +export function getManifestIntegrity( + manifest: PackageJSON | WorkspacesPackageJSON +): string | null { + const isWorkspace = "workspaces" in manifest; + const integrity = isWorkspace ? + null : + fromData(JSON.stringify(manifest), { algorithms: ["sha512"] }).toString(); + + return integrity; +} + function extractHighlightedIdentifiers( collectables: DefaultCollectableSet[], identifiersToHighlight: Set diff --git a/workspaces/scanner/src/index.ts b/workspaces/scanner/src/index.ts index bd8ec22a..0b8a3c06 100644 --- a/workspaces/scanner/src/index.ts +++ b/workspaces/scanner/src/index.ts @@ -11,7 +11,10 @@ import type { PackageJSON } from "@nodesecure/npm-types"; import type Config from "@npmcli/config"; // Import Internal Dependencies -import { depWalker } from "./depWalker.ts"; +import { + depWalker, + getManifestIntegrity +} from "./depWalker.ts"; import { NPM_TOKEN, urlToString, @@ -44,7 +47,8 @@ export type WorkingDirOptions = Options & { */ npmRcConfig?: Config; cacheLookup?: ( - packageJSON: PackageJSON + packageJSON: PackageJSON, + integrity: string | null ) => Promise; }; @@ -80,14 +84,16 @@ export async function workingDir( logger.end(ScannerLoggerEvents.manifest.read); const packageJSON = JSON.parse(str) as PackageJSON; - const cachedPayload = await options.cacheLookup?.(packageJSON); + + const integrity = getManifestIntegrity(packageJSON); + const cachedPayload = await options.cacheLookup?.(packageJSON, integrity); if (cachedPayload) { return cachedPayload; } return depWalker( packageJSON, - finalizedOptions, + Object.assign(finalizedOptions, { integrity }), logger ); } diff --git a/workspaces/scanner/test/workingDir.spec.ts b/workspaces/scanner/test/workingDir.spec.ts index aba6ccd3..8bd8b219 100644 --- a/workspaces/scanner/test/workingDir.spec.ts +++ b/workspaces/scanner/test/workingDir.spec.ts @@ -129,9 +129,11 @@ describe("scanner.workingDir()", { concurrency: 2 }, () => { const file = path.join(kFixturePath, "non-npm-package"); const capturedPackageJSONs: PackageJSON[] = []; + const capturedIntegrities: (string | null)[] = []; const result = await workingDir(file, { - cacheLookup: async(packageJSON) => { + cacheLookup: async(packageJSON, integrity) => { capturedPackageJSONs.push(packageJSON); + capturedIntegrities.push(integrity); return fakePayload; } @@ -141,6 +143,12 @@ describe("scanner.workingDir()", { concurrency: 2 }, () => { assert.strictEqual(capturedPackageJSONs.length, 1); assert.strictEqual(capturedPackageJSONs[0].name, "non-npm-package"); assert.strictEqual(capturedPackageJSONs[0].version, "1.0.0"); + assert.strictEqual(capturedIntegrities.length, 1); + assert.strictEqual( + typeof capturedIntegrities[0], + "string", + "integrity should be a non-null string for non-workspace packages" + ); }); it("should proceed with a full scan when null is returned", async() => {