Skip to content

Commit

Permalink
Feat: Include components from pnpm-lock.yaml importers (#1377)
Browse files Browse the repository at this point in the history
* add logic to find additional components and deps

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* utilize just the last part of component name

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* update according to purl spec

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* fix

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* fix comment

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* fix .

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* add requested changes lint

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* use replaceAll instead of replace

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* fix type

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* add suggested changes except testcase

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

* add test file and test cases

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>

---------

Signed-off-by: Aryan Rajoria <aryanrajoria1003@gmail.com>
  • Loading branch information
aryan-rajoria authored Sep 22, 2024
1 parent 8d33fd3 commit 9e36e8e
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 29 deletions.
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(ajv@8.14.0)
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
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(ajv@8.14.0)
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(ajv@8.14.0)
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(ajv@8.14.0)
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/pnpm9@1.0.0",
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/node@16.11.47:
resolution: {integrity: sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g==}
dev: true

/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true

/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true

/colors@1.4.0:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
dev: false

/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true

/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true

/glob@7.2.3:
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

/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true

/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true

/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true

/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true

/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true

/rimraf@2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.3
dev: true

/typescript@3.0.3:
resolution: {integrity: sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

/typescript@4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

/whatwg-fetch@3.6.2:
resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
dev: false

/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true

time:
/@types/node@16.11.47: '2022-07-30T21:03:20.126Z'
/colors@1.4.0: '2019-09-22T23:46:07.522Z'
/rimraf@2.7.1: '2019-08-14T16:53:32.844Z'
/typescript@3.0.3: '2018-08-29T21:59:20.079Z'
/typescript@4.7.4: '2022-06-17T18:21:36.833Z'
/whatwg-fetch@3.6.2: '2021-02-27T18:45:53.796Z'

0 comments on commit 9e36e8e

Please sign in to comment.