diff --git a/.changeset/stats-tracking-pacote-extract.md b/.changeset/stats-tracking-pacote-extract.md new file mode 100644 index 00000000..d5209f64 --- /dev/null +++ b/.changeset/stats-tracking-pacote-extract.md @@ -0,0 +1,8 @@ +--- +"@nodesecure/tarball": minor +"@nodesecure/scanner": minor +--- + +feat: add stats tracking on pacote.extract through extractAndResolve + +Add support for dependency injection of extractFn in extractAndResolve to enable tracking of pacote.extract calls using StatsCollector. This allows measuring extraction time for each package during scanning. diff --git a/workspaces/scanner/src/depWalker.ts b/workspaces/scanner/src/depWalker.ts index dd45d8cb..91787583 100644 --- a/workspaces/scanner/src/depWalker.ts +++ b/workspaces/scanner/src/depWalker.ts @@ -8,7 +8,8 @@ import * as npmRegistrySDK from "@nodesecure/npm-registry-sdk"; import { Mutex, MutexRelease } from "@openally/mutex"; import { extractAndResolve, - scanDirOrArchive + scanDirOrArchive, + type PacoteProvider } from "@nodesecure/tarball"; import * as Vulnera from "@nodesecure/vulnera"; import { npm } from "@nodesecure/tree-walker"; @@ -116,6 +117,16 @@ export async function depWalker( } = options; const statsCollector = new StatsCollector(); + + const pacoteProvider: PacoteProvider = { + async extract(spec, dest, opts): Promise { + await statsCollector.track( + `pacote.extract ${spec}`, + () => pacote.extract(spec, dest, opts) + ); + } + }; + const isRemoteScanning = typeof location === "undefined"; const tokenStore = new RegistryTokenStore(npmRcConfig, NPM_TOKEN.token); @@ -265,7 +276,8 @@ export async function depWalker( location, isRootNode: scanRootNode && name === manifest.name, registry, - statsCollector + statsCollector, + pacoteProvider }; operationsQueue.push( scanDirOrArchiveEx(name, version, locker, tempDir, logger, scanDirOptions) @@ -394,6 +406,7 @@ async function scanDirOrArchiveEx( location: string | undefined; ref: any; statsCollector: StatsCollector; + pacoteProvider?: PacoteProvider; } ) { using _ = await locker.acquire(); @@ -406,14 +419,16 @@ async function scanDirOrArchiveEx( location = process.cwd(), isRootNode, ref, - statsCollector + statsCollector, + pacoteProvider } = options; const mama = await (isRootNode ? ManifestManager.fromPackageJSON(location) : extractAndResolve(tempDir.location, { spec, - registry + registry, + pacoteProvider }) ); diff --git a/workspaces/tarball/src/tarball.ts b/workspaces/tarball/src/tarball.ts index be2eb3fe..0d8fad37 100644 --- a/workspaces/tarball/src/tarball.ts +++ b/workspaces/tarball/src/tarball.ts @@ -200,19 +200,28 @@ export async function scanPackage( }; } +export interface PacoteProvider { + extract( + spec: string, + destination: string, + options: pacote.Options + ): Promise; +} + export interface TarballResolutionOptions { spec: string; registry?: string; + pacoteProvider?: PacoteProvider; } export async function extractAndResolve( location: string, options: TarballResolutionOptions ): Promise { - const { spec, registry } = options; + const { spec, registry, pacoteProvider = pacote } = options; const tarballLocation = path.join(location, spec.replaceAll("/", "_")); - await pacote.extract( + await pacoteProvider.extract( spec, tarballLocation, {