Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Include components from pnpm-lock.yaml importers #1377

Merged
merged 11 commits into from
Sep 22, 2024
240 changes: 119 additions & 121 deletions lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,8 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
const dependenciesList = [];
// For lockfile >= 9, we need to track dev and optional packages manually
// See: #1163
// Moreover, we have have changed >= 9 for >= 6
// See: discussion #1359
const possibleOptionalDeps = {};
const dependenciesMap = {};
let ppurl = "";
Expand Down Expand Up @@ -1781,151 +1783,147 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
// ignore parse errors
}
// This logic matches the pnpm list command to include only direct dependencies
if (ppurl !== "") {
if (yamlObj["importers"]) {
// In lock file version 9, direct dependencies is under importers
const rootDirectDeps =
lockfileVersion >= 9
? yamlObj.importers["."]?.dependencies || {}
: yamlObj.dependencies || {};
const rootDevDeps =
lockfileVersion >= 9
? yamlObj.importers["."]?.devDependencies || {}
: {};
const rootOptionalDeps =
lockfileVersion >= 9
? yamlObj.importers["."]?.optionalDependencies || {}
: {};
const ddeplist = [];
// Find the root optional dependencies
for (const rdk of Object.keys(rootDevDeps)) {
const version = getVersionNumPnpm(rootDevDeps[rdk]);
if (ppurl !== "" && yamlObj["importers"]) {
// In lock file version 9, direct dependencies is under importers
prabhu marked this conversation as resolved.
Show resolved Hide resolved
const rootDirectDeps =
lockfileVersion >= 6
? yamlObj.importers["."]?.dependencies || {}
: yamlObj.dependencies || {};
const rootDevDeps =
lockfileVersion >= 6
? yamlObj.importers["."]?.devDependencies || {}
: {};
const rootOptionalDeps =
lockfileVersion >= 6
? yamlObj.importers["."]?.optionalDependencies || {}
: {};
const ddeplist = [];
// Find the root optional dependencies
for (const rdk of Object.keys(rootDevDeps)) {
const version = getVersionNumPnpm(rootDevDeps[rdk]);
const dpurl = new PackageURL(
"npm",
"",
rdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
for (const rdk of Object.keys(rootOptionalDeps)) {
const version = getVersionNumPnpm(rootOptionalDeps[rdk]);
const dpurl = new PackageURL(
"npm",
"",
rdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
for (const dk of Object.keys(rootDirectDeps)) {
const version = getVersionNumPnpm(rootDirectDeps[dk]);
const dpurl = new PackageURL(
"npm",
"",
dk,
version,
null,
null,
).toString();
ddeplist.push(decodeURIComponent(dpurl));
if (lockfileVersion >= 6) {
// These are direct dependencies so cannot be optional
possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
}
}

dependenciesList.push({
ref: decodeURIComponent(ppurl),
dependsOn: ddeplist,
});

// pnpm-lock.yaml contains more than root dependencies in importers
// we do what we did above but for all the other components
for (const importedComponentName of Object.keys(yamlObj["importers"])) {
const componentDeps =
yamlObj["importers"][importedComponentName]["dependencies"] || {};
const componentDevDeps =
yamlObj["importers"][importedComponentName]["devDependencies"] || {};
const componentOptionalDeps =
yamlObj["importers"][importedComponentName]["optionalDependencies"] ||
{};

const name = importedComponentName.split("/");
const lastname = name[name.length - 1];

// let subpath = name.filter(part => part !== '.' && part !== '..').join('/');
const subpath = name
.join("/")
.replaceAll("../", "")
.replaceAll("./", "");

// if component name is '.' continue loop
if (lastname === ".") {
continue;
}

const compPurl = new PackageURL(
"npm",
parentComponent.group,
`${parentComponent.name}/${lastname}`,
parentComponent.version,
null,
subpath,
).toString();
// Find the component optional dependencies
const comDepList = [];
for (const cdk of Object.keys(componentDeps)) {
const version = getVersionNumPnpm(componentDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
rdk,
cdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
comDepList.push(decodeURIComponent(dpurl));

possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
}
for (const rdk of Object.keys(rootOptionalDeps)) {
const version = getVersionNumPnpm(rootOptionalDeps[rdk]);

dependenciesList.push({
ref: decodeURIComponent(compPurl),
dependsOn: comDepList,
});

for (const cdk of Object.keys(componentDevDeps)) {
const version = getVersionNumPnpm(componentDevDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
rdk,
cdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
for (const dk of Object.keys(rootDirectDeps)) {
const version = getVersionNumPnpm(rootDirectDeps[dk]);

for (const cdk of Object.keys(componentOptionalDeps)) {
const version = getVersionNumPnpm(componentOptionalDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
dk,
cdk,
version,
null,
null,
).toString();
ddeplist.push(decodeURIComponent(dpurl));
if (lockfileVersion >= 9) {
// These are direct dependencies so cannot be optional
possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
}
}

dependenciesList.push({
ref: decodeURIComponent(ppurl),
dependsOn: ddeplist,
});

// pnpm-lock.yaml contains more than root dependencies in importers
// we do what we did above but for all the other components
for (const importedComponentName of Object.keys(yamlObj["importers"])) {
const componentDeps =
yamlObj["importers"][importedComponentName]["dependencies"] || {};
const componentDevDeps =
yamlObj["importers"][importedComponentName]["devDependencies"] ||
{};
const componentOptionalDeps =
yamlObj["importers"][importedComponentName][
"optionalDependencies"
] || {};

const name = importedComponentName.split("/");
const lastname = name[name.length - 1];

// let subpath = name.filter(part => part !== '.' && part !== '..').join('/');
const subpath = name
.join("/")
.replaceAll("../", "")
.replaceAll("./", "");

// if component name is '.' continue loop
if (lastname === ".") {
continue;
}

const compPurl = new PackageURL(
"npm",
parentComponent.group,
`${parentComponent.name}/${lastname}`,
parentComponent.version,
null,
subpath,
).toString();
// Find the component optional dependencies
const comDepList = [];
for (const cdk of Object.keys(componentDeps)) {
const version = getVersionNumPnpm(componentDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
cdk,
version,
null,
null,
).toString();
comDepList.push(decodeURIComponent(dpurl));

possibleOptionalDeps[decodeURIComponent(dpurl)] = false;
}

dependenciesList.push({
ref: decodeURIComponent(compPurl),
dependsOn: comDepList,
});

for (const cdk of Object.keys(componentDevDeps)) {
const version = getVersionNumPnpm(componentDevDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
cdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}

for (const cdk of Object.keys(componentOptionalDeps)) {
const version = getVersionNumPnpm(componentOptionalDeps[cdk]);
const dpurl = new PackageURL(
"npm",
"",
cdk,
version,
null,
null,
).toString();
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions lib/helpers/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3159,6 +3159,39 @@ test("parsePnpmLock", async () => {
},
},
});
// Test case to see if parsePnpmLock is finding all root deps
const dummpyParent = {
name: "rush",
group: "",
purl: "pkg:npm/rush",
type: "application",
"bom-ref": "pkg:npm/rush",
};
parsedList = await parsePnpmLock(
"./test/data/pnpm-lock6b.yaml",
dummpyParent,
);
expect(parsedList.pkgList.length).toEqual(17);
// this is due to additions projects defined in importers section of pnpm-lock.yaml
expect(parsedList.dependenciesList.length).toEqual(21);
const mainRootDependency = parsedList.dependenciesList.find(
(obj) => obj["ref"] === "pkg:npm/rush",
);
const myAppRootDependency = parsedList.dependenciesList.find(
(obj) => obj["ref"] === "pkg:npm/rush/my-app#apps/my-app",
);
const myControlsRootDependency = parsedList.dependenciesList.find(
(obj) => obj["ref"] === "pkg:npm/rush/my-controls#libraries/my-controls",
);
const myToolChainRootDependency = parsedList.dependenciesList.find(
(obj) => obj["ref"] === "pkg:npm/rush/my-toolchain#tools/my-toolchain",
);

expect(mainRootDependency["dependsOn"].length).toEqual(0);
expect(myAppRootDependency["dependsOn"].length).toEqual(2);
expect(myControlsRootDependency["dependsOn"].length).toEqual(0);
expect(myToolChainRootDependency["dependsOn"].length).toEqual(1);

parsedList = await parsePnpmLock("./test/data/pnpm-lock9a.yaml", {
name: "pnpm9",
purl: "pkg:npm/[email protected]",
Expand Down
Loading
Loading