Skip to content

Commit 451101d

Browse files
committed
feat(yarn2): Somewhat handle patched packages
Locators of patched packages, e.g. [1], start with "$MODULE-ID@patch:", instead of with "$MODULE-ID@npm". This locator format is new in Yarn version 3 or 4, and handling for it hasn't yet been implemented in ORT. The implementation may run into one of the following types of issues: 1. The identifier ORT assigns to the package contains a colon in one of its components, which make a `require()` check fail. 2. The filtering of dependencies done within `resolvedDependencies()` accidentally filter out the package based on `PackageInfo.moduleName`, which returns a wrong value. Adjust the `PackageInfo.moduleName` logic such that it treats packages as if they weren't patched. This can be improved in a following change. Fixes: #10949 Note: Yarn4 does not create a `node_modules` directory anymore by default. As `getChildModuleDirs()` does rely on that directory, it is necessary to configure `nodeLinker: node-modules` in the `.yarnrc.yml`, as done in the added test project. [1]: `resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d` Signed-off-by: Frank Viernau <[email protected]>
1 parent f9e5e7a commit 451101d

File tree

7 files changed

+319
-1
lines changed

7 files changed

+319
-1
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
project:
3+
id: "Yarn2::yarn 4 project with patched dependency:0.14.0"
4+
definition_file_path: "<REPLACE_DEFINITION_FILE_PATH>"
5+
declared_licenses: []
6+
declared_licenses_processed: {}
7+
vcs:
8+
type: ""
9+
url: ""
10+
revision: ""
11+
path: ""
12+
vcs_processed:
13+
type: "Git"
14+
url: "<REPLACE_URL_PROCESSED>"
15+
revision: "<REPLACE_REVISION>"
16+
path: "<REPLACE_PATH>"
17+
homepage_url: ""
18+
scopes:
19+
- name: "dependencies"
20+
dependencies:
21+
- id: "NPM::resolve:1.22.8"
22+
dependencies:
23+
- id: "NPM::is-core-module:2.16.1"
24+
dependencies:
25+
- id: "NPM::hasown:2.0.2"
26+
dependencies:
27+
- id: "NPM::function-bind:1.1.2"
28+
- id: "NPM::path-parse:1.0.7"
29+
- id: "NPM::supports-preserve-symlinks-flag:1.0.0"
30+
packages:
31+
- id: "NPM::function-bind:1.1.2"
32+
purl: "pkg:npm/[email protected]"
33+
authors:
34+
- "Raynos"
35+
declared_licenses:
36+
- "MIT"
37+
declared_licenses_processed:
38+
spdx_expression: "MIT"
39+
description: "Implementation of Function.prototype.bind"
40+
homepage_url: "https://github.com/Raynos/function-bind"
41+
binary_artifact:
42+
url: ""
43+
hash:
44+
value: ""
45+
algorithm: ""
46+
source_artifact:
47+
url: "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
48+
hash:
49+
value: "2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
50+
algorithm: "SHA-1"
51+
vcs:
52+
type: "Git"
53+
url: "https://github.com/Raynos/function-bind.git"
54+
revision: "40197beb5f4cf89dd005f0b268256c1e4716ea81"
55+
path: ""
56+
vcs_processed:
57+
type: "Git"
58+
url: "https://github.com/Raynos/function-bind.git"
59+
revision: "40197beb5f4cf89dd005f0b268256c1e4716ea81"
60+
path: ""
61+
- id: "NPM::hasown:2.0.2"
62+
purl: "pkg:npm/[email protected]"
63+
authors:
64+
- "Jordan Harband"
65+
declared_licenses:
66+
- "MIT"
67+
declared_licenses_processed:
68+
spdx_expression: "MIT"
69+
description: "A robust, ES3 compatible, \"has own property\" predicate."
70+
homepage_url: "https://github.com/inspect-js/hasOwn#readme"
71+
binary_artifact:
72+
url: ""
73+
hash:
74+
value: ""
75+
algorithm: ""
76+
source_artifact:
77+
url: "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
78+
hash:
79+
value: "003eaf91be7adc372e84ec59dc37252cedb80003"
80+
algorithm: "SHA-1"
81+
vcs:
82+
type: "Git"
83+
url: "git+https://github.com/inspect-js/hasOwn.git"
84+
revision: "d00d35005baf16a33d691a13f8ad627f35040742"
85+
path: ""
86+
vcs_processed:
87+
type: "Git"
88+
url: "https://github.com/inspect-js/hasOwn.git"
89+
revision: "d00d35005baf16a33d691a13f8ad627f35040742"
90+
path: ""
91+
- id: "NPM::is-core-module:2.16.1"
92+
purl: "pkg:npm/[email protected]"
93+
authors:
94+
- "Jordan Harband"
95+
declared_licenses:
96+
- "MIT"
97+
declared_licenses_processed:
98+
spdx_expression: "MIT"
99+
description: "Is this specifier a node.js core module?"
100+
homepage_url: "https://github.com/inspect-js/is-core-module"
101+
binary_artifact:
102+
url: ""
103+
hash:
104+
value: ""
105+
algorithm: ""
106+
source_artifact:
107+
url: "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
108+
hash:
109+
value: "2a98801a849f43e2add644fbb6bc6229b19a4ef4"
110+
algorithm: "SHA-1"
111+
vcs:
112+
type: "Git"
113+
url: "git+https://github.com/inspect-js/is-core-module.git"
114+
revision: "7c4284853357b2fcd49d42010d5a2b8a8420905f"
115+
path: ""
116+
vcs_processed:
117+
type: "Git"
118+
url: "https://github.com/inspect-js/is-core-module.git"
119+
revision: "7c4284853357b2fcd49d42010d5a2b8a8420905f"
120+
path: ""
121+
- id: "NPM::path-parse:1.0.7"
122+
purl: "pkg:npm/[email protected]"
123+
authors:
124+
- "Javier Blanco <http://jbgutierrez.info>"
125+
declared_licenses:
126+
- "MIT"
127+
declared_licenses_processed:
128+
spdx_expression: "MIT"
129+
description: "Node.js path.parse() ponyfill"
130+
homepage_url: "https://github.com/jbgutierrez/path-parse#readme"
131+
binary_artifact:
132+
url: ""
133+
hash:
134+
value: ""
135+
algorithm: ""
136+
source_artifact:
137+
url: "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
138+
hash:
139+
value: "fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
140+
algorithm: "SHA-1"
141+
vcs:
142+
type: "Git"
143+
url: "https://github.com/jbgutierrez/path-parse.git"
144+
revision: "9f1db2802ffbc572e6b447f66126697e33b0055e"
145+
path: ""
146+
vcs_processed:
147+
type: "Git"
148+
url: "https://github.com/jbgutierrez/path-parse.git"
149+
revision: "9f1db2802ffbc572e6b447f66126697e33b0055e"
150+
path: ""
151+
- id: "NPM::resolve:1.22.8"
152+
purl: "pkg:npm/[email protected]"
153+
authors:
154+
- "James Halliday"
155+
declared_licenses:
156+
- "MIT"
157+
declared_licenses_processed:
158+
spdx_expression: "MIT"
159+
description: "resolve like require.resolve() on behalf of files asynchronously and\
160+
\ synchronously"
161+
homepage_url: "https://github.com/browserify/resolve#readme"
162+
binary_artifact:
163+
url: ""
164+
hash:
165+
value: ""
166+
algorithm: ""
167+
source_artifact:
168+
url: "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
169+
hash:
170+
value: "b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
171+
algorithm: "SHA-1"
172+
vcs:
173+
type: "Git"
174+
url: "git://github.com/browserify/resolve.git"
175+
revision: "b8298720c6ece9d3b7231ea90bd920f266a449a8"
176+
path: ""
177+
vcs_processed:
178+
type: "Git"
179+
url: "https://github.com/browserify/resolve.git"
180+
revision: "b8298720c6ece9d3b7231ea90bd920f266a449a8"
181+
path: ""
182+
- id: "NPM::supports-preserve-symlinks-flag:1.0.0"
183+
purl: "pkg:npm/[email protected]"
184+
authors:
185+
- "Jordan Harband"
186+
declared_licenses:
187+
- "MIT"
188+
declared_licenses_processed:
189+
spdx_expression: "MIT"
190+
description: "Determine if the current node version supports the `--preserve-symlinks`\
191+
\ flag."
192+
homepage_url: "https://github.com/inspect-js/node-supports-preserve-symlinks-flag#readme"
193+
binary_artifact:
194+
url: ""
195+
hash:
196+
value: ""
197+
algorithm: ""
198+
source_artifact:
199+
url: "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
200+
hash:
201+
value: "6eda4bd344a3c94aea376d4cc31bc77311039e09"
202+
algorithm: "SHA-1"
203+
vcs:
204+
type: "Git"
205+
url: "git+https://github.com/inspect-js/node-supports-preserve-symlinks-flag.git"
206+
revision: "1f7cac19c0c298cf40b3f2f3c735477ad579ac61"
207+
path: ""
208+
vcs_processed:
209+
type: "Git"
210+
url: "https://github.com/inspect-js/node-supports-preserve-symlinks-flag.git"
211+
revision: "1f7cac19c0c298cf40b3f2f3c735477ad579ac61"
212+
path: ""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "yarn 4 project with patched dependency",
3+
"version": "0.14.0",
4+
"packageManager": "[email protected]+sha512.955259c0370ab8c06d013faa7d5e7addb6914251029695675f54e04c917ea6b092c379ba8d3521556d90b3874e5037759467072f01d2295341ead43cc259f14b",
5+
"dependencies": {
6+
"resolve": "1.22.8"
7+
}
8+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This file is generated by running "yarn install" inside your project.
2+
# Manual changes might be lost - proceed with caution!
3+
4+
__metadata:
5+
version: 8
6+
cacheKey: 10c0
7+
8+
"function-bind@npm:^1.1.2":
9+
version: 1.1.2
10+
resolution: "function-bind@npm:1.1.2"
11+
checksum: d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5
12+
languageName: node
13+
linkType: hard
14+
15+
"hasown@npm:^2.0.2":
16+
version: 2.0.2
17+
resolution: "hasown@npm:2.0.2"
18+
dependencies:
19+
function-bind: "npm:^1.1.2"
20+
checksum: 3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9
21+
languageName: node
22+
linkType: hard
23+
24+
"is-core-module@npm:^2.13.0":
25+
version: 2.16.1
26+
resolution: "is-core-module@npm:2.16.1"
27+
dependencies:
28+
hasown: "npm:^2.0.2"
29+
checksum: 898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd
30+
languageName: node
31+
linkType: hard
32+
33+
"path-parse@npm:^1.0.7":
34+
version: 1.0.7
35+
resolution: "path-parse@npm:1.0.7"
36+
checksum: 11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1
37+
languageName: node
38+
linkType: hard
39+
40+
"resolve@npm:1.22.8":
41+
version: 1.22.8
42+
resolution: "resolve@npm:1.22.8"
43+
dependencies:
44+
is-core-module: "npm:^2.13.0"
45+
path-parse: "npm:^1.0.7"
46+
supports-preserve-symlinks-flag: "npm:^1.0.0"
47+
bin:
48+
resolve: bin/resolve
49+
checksum: 07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a
50+
languageName: node
51+
linkType: hard
52+
53+
"resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>":
54+
version: 1.22.8
55+
resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d"
56+
dependencies:
57+
is-core-module: "npm:^2.13.0"
58+
path-parse: "npm:^1.0.7"
59+
supports-preserve-symlinks-flag: "npm:^1.0.0"
60+
bin:
61+
resolve: bin/resolve
62+
checksum: 0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729
63+
languageName: node
64+
linkType: hard
65+
66+
"supports-preserve-symlinks-flag@npm:^1.0.0":
67+
version: 1.0.0
68+
resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
69+
checksum: 6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39
70+
languageName: node
71+
linkType: hard
72+
73+
"yarn 4 project with patched dependency@workspace:.":
74+
version: 0.0.0-use.local
75+
resolution: "yarn 4 project with patched dependency@workspace:."
76+
dependencies:
77+
resolve: "npm:1.22.8"
78+
languageName: unknown
79+
linkType: soft

plugins/package-managers/node/src/funTest/kotlin/yarn2/Yarn2FunTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,15 @@ class Yarn2FunTest : StringSpec({
6161

6262
result.toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
6363
}
64+
65+
"Resolve dependencies for a Yarn 4 project with a dependency with a patch" {
66+
val definitionFile = getAssetFile("projects/synthetic/yarn2/dependency-with-patch/package.json")
67+
val expectedResultFile = getAssetFile("projects/synthetic/yarn2/dependency-with-patch-expected-output.yml")
68+
69+
val result = Yarn2Factory
70+
.create(corepackEnabled = true)
71+
.resolveSingleProject(definitionFile, resolveScopes = true)
72+
73+
result.toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
74+
}
6475
})

plugins/package-managers/node/src/main/kotlin/yarn2/Yarn2DependencyHandler.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,13 @@ internal class Yarn2DependencyHandler(
8383

8484
internal val PackageInfo.isProject: Boolean get() = value.substringAfterLast("@").startsWith("workspace:")
8585

86-
internal val PackageInfo.moduleName: String get() = value.substringBeforeLast("@")
86+
internal val PackageInfo.moduleName: String get() {
87+
val firstComponent = value.substringBefore(":")
88+
// TODO: Handle patched packages different than non-patched ones.
89+
// Patch packages have locators as e.g. the following, where the first component ends with "@patch".
90+
// resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d
91+
return firstComponent.substringBeforeLast("@")
92+
}
8793

8894
internal val PackageInfo.moduleId: String get() = buildString {
8995
append(moduleName)

plugins/package-managers/node/src/test/kotlin/NodePackageManagerDetectionTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ class NodePackageManagerDetectionTest : WordSpec({
276276
val filteredFiles = NodePackageManagerDetection(definitionFiles).filterApplicable(YARN2)
277277

278278
filteredFiles.map { it.relativeTo(projectDir).invariantSeparatorsPath } should containExactlyInAnyOrder(
279+
"yarn2/dependency-with-patch/package.json",
279280
"yarn2/project-with-lockfile/package.json",
280281
"yarn2/workspaces/package.json"
281282
)

0 commit comments

Comments
 (0)