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
134 changes: 105 additions & 29 deletions lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,18 @@ function _markTreeOptional(
}
}

function getVersionNumPnpm(depPkg) {
let version = depPkg;
if (typeof version === "object" && depPkg.version) {
version = depPkg.version;
}
// version: 3.0.1([email protected])
if (version?.includes("(")) {
version = version.split("(")[0];
}
return version;
}

/**
* Parse nodejs pnpm lock file
*
Expand All @@ -1737,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 @@ -1769,31 +1783,24 @@ 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 (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 >= 9
lockfileVersion >= 6
? yamlObj.importers["."]?.dependencies || {}
: yamlObj.dependencies || {};
const rootDevDeps =
lockfileVersion >= 9
lockfileVersion >= 6
? yamlObj.importers["."]?.devDependencies || {}
: {};
const rootOptionalDeps =
lockfileVersion >= 9
lockfileVersion >= 6
? yamlObj.importers["."]?.optionalDependencies || {}
: {};
const ddeplist = [];
// Find the root optional dependencies
for (const rdk of Object.keys(rootDevDeps)) {
let version = rootDevDeps[rdk];
if (typeof version === "object" && version.version) {
version = version.version;
}
// version: 3.0.1([email protected])
if (version?.includes("(")) {
version = version.split("(")[0];
}
const version = getVersionNumPnpm(rootDevDeps[rdk]);
const dpurl = new PackageURL(
"npm",
"",
Expand All @@ -1805,14 +1812,7 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
for (const rdk of Object.keys(rootOptionalDeps)) {
let version = rootOptionalDeps[rdk];
if (typeof version === "object" && version.version) {
version = version.version;
}
// version: 3.0.1([email protected])
if (version?.includes("(")) {
version = version.split("(")[0];
}
const version = getVersionNumPnpm(rootOptionalDeps[rdk]);
const dpurl = new PackageURL(
"npm",
"",
Expand All @@ -1824,14 +1824,7 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
}
for (const dk of Object.keys(rootDirectDeps)) {
let version = rootDirectDeps[dk];
if (typeof version === "object" && version.version) {
version = version.version;
}
// version: 3.0.1([email protected])
if (version?.includes("(")) {
version = version.split("(")[0];
}
const version = getVersionNumPnpm(rootDirectDeps[dk]);
const dpurl = new PackageURL(
"npm",
"",
Expand All @@ -1841,15 +1834,98 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
null,
).toString();
ddeplist.push(decodeURIComponent(dpurl));
if (lockfileVersion >= 9) {
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",
"",
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;
}
}
}
const packages = yamlObj.packages || {};
// snapshots is a new key under lockfile version 9
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
154 changes: 154 additions & 0 deletions test/data/pnpm-lock6b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
lockfileVersion: '6.0'

settings:
autoInstallPeers: false
excludeLinksFromLockfile: false

importers:

.: {}

../../apps/my-app:
dependencies:
my-controls:
specifier: workspace:^1.0.0
version: link:../../libraries/my-controls
whatwg-fetch:
specifier: ^3.6.2
version: 3.6.2
devDependencies:
my-toolchain:
specifier: workspace:^1.0.0
version: link:../../tools/my-toolchain
typescript:
specifier: ^3.0.3
version: 3.0.3

../../libraries/my-controls:
devDependencies:
my-toolchain:
specifier: workspace:^1.0.0
version: link:../../tools/my-toolchain
typescript:
specifier: ^3.0.3
version: 3.0.3

../../tools/my-toolchain:
dependencies:
colors:
specifier: ^1.4.0
version: 1.4.0
devDependencies:
'@types/node':
specifier: 16.11.47
version: 16.11.47
rimraf:
specifier: ^2.7.1
version: 2.7.1
typescript:
specifier: ^4.7.4
version: 4.7.4

packages:

/@types/[email protected]:
resolution: {integrity: sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g==}
dev: true

/[email protected]:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true

/[email protected]:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true

/[email protected]:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
dev: false

/[email protected]:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true

/[email protected]:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true

/[email protected]:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true

/[email protected]:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true

/[email protected]:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true

/[email protected]:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true

/[email protected]:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true

/[email protected]:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true

/[email protected]:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.3
dev: true

/[email protected]:
resolution: {integrity: sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

/[email protected]:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

/[email protected]:
resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
dev: false

/[email protected]:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true

time:
/@types/[email protected]: '2022-07-30T21:03:20.126Z'
/[email protected]: '2019-09-22T23:46:07.522Z'
/[email protected]: '2019-08-14T16:53:32.844Z'
/[email protected]: '2018-08-29T21:59:20.079Z'
/[email protected]: '2022-06-17T18:21:36.833Z'
/[email protected]: '2021-02-27T18:45:53.796Z'
Loading