Skip to content

Commit 1895e08

Browse files
authoredFeb 27, 2025
npm workspace improvements (#1659)
* Try harder to resolve the correct workspace package name from package-lock.json Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com> --------- Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
1 parent 292a387 commit 1895e08

File tree

8 files changed

+26636
-19
lines changed

8 files changed

+26636
-19
lines changed
 

‎lib/cli/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -2488,8 +2488,19 @@ export async function createNodejsBom(path, options) {
24882488
if (mgrData) {
24892489
mgr = mgrData.split("@")[0];
24902490
}
2491+
// Try harder to identify the correct package manager
24912492
if (supPkgMgrs.includes(mgr)) {
24922493
pkgMgr = mgr;
2494+
} else if (pkgData?.engines?.yarn) {
2495+
pkgMgr = "yarn";
2496+
} else if (
2497+
isPackageManagerAllowed("yarn", ["npm", "pnpm", "rush"], options)
2498+
) {
2499+
pkgMgr = "yarn";
2500+
} else if (
2501+
isPackageManagerAllowed("pnpm", ["npm", "yarn", "rush"], options)
2502+
) {
2503+
pkgMgr = "pnpm";
24932504
}
24942505
let installCommand = "install";
24952506
if (pkgMgr === "npm" && isSecureMode && pkgJsonLockFiles?.length > 0) {
@@ -2524,6 +2535,11 @@ export async function createNodejsBom(path, options) {
25242535
installArgs.push("--package-lock");
25252536
}
25262537
}
2538+
if (pkgMgr !== "npm") {
2539+
thoughtLog(
2540+
`**PACKAGE MANAGER**: About to invoke '${pkgMgr}' with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
2541+
);
2542+
}
25272543
console.log(
25282544
`Executing '${pkgMgr} ${installArgs.join(" ")}' in`,
25292545
basePath,
@@ -2538,6 +2554,9 @@ export async function createNodejsBom(path, options) {
25382554
console.error(
25392555
`${pkgMgr} install has failed. Generated SBOM will be empty or with a lower precision.`,
25402556
);
2557+
thoughtLog(
2558+
"It looks like the install command has failed. I'm considering some troubleshooting ideas.",
2559+
);
25412560
if (DEBUG_MODE && result.stdout) {
25422561
if (result.stdout.includes("EBADENGINE Unsupported engine")) {
25432562
console.log(

‎lib/helpers/utils.js

+35-12
Original file line numberDiff line numberDiff line change
@@ -1038,11 +1038,8 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
10381038
const pkgData = JSON.parse(readFileSync(pkgJsonFile, "utf8"));
10391039
const pkgIdentifier = parsePackageJsonName(pkgData.name);
10401040
let name = pkgIdentifier.fullName || pkgData.name;
1041-
if (DEBUG_MODE && !name && !pkgJsonFile.includes("node_modules")) {
1042-
name = dirname(pkgJsonFile);
1043-
console.log(
1044-
`${pkgJsonFile} doesn't contain the package name. Consider using the 'npm init' command to create a valid package.json file for this project. Assuming the name as '${name}'.`,
1045-
);
1041+
if (!name && !pkgJsonFile.includes("node_modules")) {
1042+
name = basename(dirname(pkgJsonFile));
10461043
}
10471044
const group = pkgIdentifier.scope || "";
10481045
const purl = new PackageURL(
@@ -1232,11 +1229,23 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
12321229
"bom-ref": decodeURIComponent(purlString),
12331230
};
12341231
if (node.resolved) {
1235-
pkg.properties.push({
1236-
name: "ResolvedUrl",
1237-
value: node.resolved,
1238-
});
1239-
pkg.distribution = { url: node.resolved };
1232+
if (node.resolved.startsWith("file:")) {
1233+
pkg.properties.push({
1234+
name: "cdx:npm:resolvedPath",
1235+
value: node.realpath
1236+
? relative(dirname(pkgLockFile), node.realpath)
1237+
: relative(
1238+
dirname(pkgLockFile),
1239+
resolve(node.resolved.replace("file:", "")),
1240+
),
1241+
});
1242+
} else {
1243+
pkg.properties.push({
1244+
name: "ResolvedUrl",
1245+
value: node.resolved,
1246+
});
1247+
pkg.distribution = { url: node.resolved };
1248+
}
12401249
}
12411250
if (node.location) {
12421251
pkg.properties.push({
@@ -1328,7 +1337,6 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
13281337
});
13291338
}
13301339
pkgList.push(pkg);
1331-
13321340
// retrieve workspace node pkglists
13331341
const workspaceDependsOn = [];
13341342
if (node.fsChildren && node.fsChildren.size > 0) {
@@ -1346,7 +1354,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
13461354
);
13471355
pkgList = pkgList.concat(childPkgList);
13481356
dependenciesList = dependenciesList.concat(childDependenciesList);
1349-
const depWorkspacePurlString = decodeURIComponent(
1357+
let depWorkspacePurlString = decodeURIComponent(
13501358
new PackageURL(
13511359
"npm",
13521360
"",
@@ -1358,6 +1366,21 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
13581366
.toString()
13591367
.replace(/%2F/g, "/"),
13601368
);
1369+
let purlStringFromPkgid;
1370+
if (workspaceNode.pkgid) {
1371+
purlStringFromPkgid = `pkg:npm/${workspaceNode.pkgid.replace(`${workspaceNode.name}@npm:`, "")}`;
1372+
}
1373+
if (
1374+
purlStringFromPkgid &&
1375+
purlStringFromPkgid !== depWorkspacePurlString
1376+
) {
1377+
if (DEBUG_MODE) {
1378+
console.log(
1379+
`Internal warning: Got two different refs for this workspace node: ${depWorkspacePurlString} and ${purlStringFromPkgid}. Assuming the bom-ref as ${purlStringFromPkgid} based on pkgid.`,
1380+
);
1381+
}
1382+
depWorkspacePurlString = purlStringFromPkgid;
1383+
}
13611384
if (decodeURIComponent(purlString) !== depWorkspacePurlString) {
13621385
workspaceDependsOn.push(depWorkspacePurlString);
13631386
}

‎lib/helpers/utils.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import {
9696
toGemModuleNames,
9797
yarnLockToIdentMap,
9898
} from "./utils.js";
99+
import { validateRefs } from "./validator.js";
99100

100101
test("SSRI test", () => {
101102
// gopkg.lock hash
@@ -3393,6 +3394,21 @@ test("parsePkgLock v3", async () => {
33933394
expect(parsedList.dependenciesList.length).toEqual(161);
33943395
});
33953396

3397+
test("parsePkgLock theia", async () => {
3398+
const parsedList = await parsePkgLock(
3399+
"./test/data/package-json/theia/package-lock.json",
3400+
{},
3401+
);
3402+
expect(parsedList.pkgList.length).toEqual(2410);
3403+
expect(parsedList.dependenciesList.length).toEqual(2410);
3404+
expect(
3405+
validateRefs({
3406+
components: parsedList.pkgList,
3407+
dependencies: parsedList.dependenciesList,
3408+
}),
3409+
).toBeTruthy();
3410+
});
3411+
33963412
test("parseBowerJson", async () => {
33973413
const deps = await parseBowerJson("./test/data/bower.json");
33983414
expect(deps.length).toEqual(1);

‎lib/managers/docker.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,11 @@ export const getImage = async (fullImageName) => {
639639
}
640640
if (needsCliFallback()) {
641641
let dockerCmd = process.env.DOCKER_CMD || "docker";
642-
if (!process.env.DOCKER_CMD && detectRancherDesktop()) {
643-
dockerCmd = "nerdctl";
642+
if (!process.env.DOCKER_CMD) {
643+
detectColima() || detectRancherDesktop();
644+
if (isNerdctl) {
645+
dockerCmd = "nerdctl";
646+
}
644647
}
645648
let result = spawnSync(dockerCmd, ["pull", fullImageName], {
646649
encoding: "utf-8",
@@ -1116,8 +1119,11 @@ export const exportImage = async (fullImageName, options) => {
11161119
if (needsCliFallback()) {
11171120
const imageTarFile = join(tempDir, "image.tar");
11181121
let dockerCmd = process.env.DOCKER_CMD || "docker";
1119-
if (!process.env.DOCKER_CMD && detectRancherDesktop()) {
1120-
dockerCmd = "nerdctl";
1122+
if (!process.env.DOCKER_CMD) {
1123+
detectColima() || detectRancherDesktop();
1124+
if (isNerdctl) {
1125+
dockerCmd = "nerdctl";
1126+
}
11211127
}
11221128
console.log(
11231129
`About to export image ${fullImageName} to ${imageTarFile} using ${dockerCmd} cli`,

0 commit comments

Comments
 (0)