Skip to content

Commit b7115b3

Browse files
authored
feat(npm-lock-v2): handled bundled dependencies without lockfile entries (#285)
1 parent 738ac9d commit b7115b3

File tree

5 files changed

+1148
-1
lines changed

5 files changed

+1148
-1
lines changed

lib/dep-graph-builders/npm-lock-v2/extract-npm-lock-v2-pkgs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export type NpmLockPkg = {
1111
license?: string;
1212
engines?: Record<string, string>;
1313
inBundle?: boolean;
14+
bundleDependencies?: string[];
15+
bundledDependencies?: string[];
1416
};
1517

1618
export const extractPkgsFromNpmLockV2 = (

lib/dep-graph-builders/npm-lock-v2/index.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,28 @@ const getChildNode = (
247247
);
248248

249249
if (!childNodeKey) {
250+
// https://snyksec.atlassian.net/wiki/spaces/SCA/pages/3785687123/NPM+Bundled+Dependencies+Analysis
251+
// Check if this dependency is bundled in the parent package
252+
// Bundled dependencies may not have separate lockfile entries when
253+
// the package is installed from registry (they're pre-packaged in the tarball)
254+
const parentNode = ancestry[ancestry.length - 1];
255+
if (parentNode && parentNode.key) {
256+
const parentPkg = pkgs[parentNode.key];
257+
if (parentPkg && isBundledDependency(name, parentPkg)) {
258+
// This is a bundled dependency without a lockfile entry
259+
// Return a placeholder node - we don't have sub-dependency information
260+
return {
261+
id: `${name}@${depInfo.version}`,
262+
name: name,
263+
version: '', // empty version since we cannot resolve the semver for bundled deps
264+
dependencies: {},
265+
isDev: depInfo.isDev,
266+
inBundle: true,
267+
key: '',
268+
};
269+
}
270+
}
271+
250272
if (strictOutOfSync) {
251273
throw new OutOfSyncError(`${name}@${depInfo.version}`, LockfileType.npm);
252274
} else {
@@ -323,6 +345,20 @@ const getChildNode = (
323345
};
324346
};
325347

348+
// Checks if a dependency is bundled in the parent package.
349+
// Bundled dependencies are included in the parent's published tarball
350+
// and may not have separate entries in the lockfile when installed from registry.
351+
function isBundledDependency(depName: string, parentPkg: NpmLockPkg): boolean {
352+
// Check both spellings (bundleDependencies is canonical, bundledDependencies is alternative)
353+
const bundled = parentPkg.bundleDependencies || parentPkg.bundledDependencies;
354+
355+
if (!bundled || !Array.isArray(bundled)) {
356+
return false;
357+
}
358+
359+
return bundled.includes(depName);
360+
}
361+
326362
export const getChildNodeKey = (
327363
name: string,
328364
version: string,
@@ -433,7 +469,7 @@ export const getChildNodeKey = (
433469
ancestryNames.shift();
434470
}
435471

436-
// Here we go through th eancestry backwards to find the nearest
472+
// Here we go through the ancestry backwards to find the nearest
437473
// ancestor package
438474
const reversedAncestry = ancestry.reverse();
439475
for (

0 commit comments

Comments
 (0)