ohrisk 0.127.1 → 0.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +4 -4
- package/dist/cli.js +191 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.128.0 - 2026-06-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Yarn Berry `yarn.lock` files are now parsed alongside Yarn classic lockfiles,
|
|
8
|
+
including `npm:` protocol descriptors, patched npm packages, and workspace
|
|
9
|
+
package roots.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Real-world Yarn workspace scans now ignore local `workspace:` packages as npm
|
|
14
|
+
package evidence while still scanning each workspace package manifest as a
|
|
15
|
+
dependency root.
|
|
16
|
+
|
|
3
17
|
## 0.127.1 - 2026-06-20
|
|
4
18
|
|
|
5
19
|
### Fixed
|
package/README.md
CHANGED
|
@@ -62,18 +62,18 @@ Ohrisk is distributed as an npm package, and the packaged CLI runs on Node.js
|
|
|
62
62
|
`>=20.0.0`. Bun is used for Ohrisk development, tests, and packaging, but users
|
|
63
63
|
do not need Bun installed to run the published CLI.
|
|
64
64
|
|
|
65
|
-
Ohrisk scans Bun, npm package-lock/shrinkwrap, pnpm, Deno npm, and Yarn
|
|
65
|
+
Ohrisk scans Bun, npm package-lock/shrinkwrap, pnpm, Deno npm, and Yarn
|
|
66
66
|
lockfiles regardless of which package manager you use to install the CLI.
|
|
67
67
|
|
|
68
68
|
## Current Scope
|
|
69
69
|
|
|
70
70
|
The current implementation is the first npm-style vertical slice:
|
|
71
71
|
|
|
72
|
-
- Bun `bun.lock`, npm `package-lock.json`, npm `npm-shrinkwrap.json`, pnpm `pnpm-lock.yaml`, Deno `deno.lock`, and Yarn
|
|
72
|
+
- Bun `bun.lock`, npm `package-lock.json`, npm `npm-shrinkwrap.json`, pnpm `pnpm-lock.yaml`, Deno `deno.lock`, and Yarn classic/Berry `yarn.lock` project discovery
|
|
73
73
|
- Node-compatible packaged CLI entrypoint for npm, pnpm, Yarn, npx, pnpm dlx, and yarn dlx users
|
|
74
74
|
- explicit lockfile selection with `--lockfile <path>` for projects that contain more than one supported lockfile
|
|
75
75
|
- direct and transitive dependency graph extraction
|
|
76
|
-
- Bun, npm, pnpm, and Yarn
|
|
76
|
+
- Bun, npm, pnpm, and Yarn classic/Berry workspace projects are scanned from every workspace/importer package root
|
|
77
77
|
- Deno `deno.lock` projects are scanned for npm package dependencies recorded in `npm:` specifiers; remote URL imports and JSR packages are not scanned yet
|
|
78
78
|
- npm alias dependency resolution, including pnpm alias package keys, with alias context preserved in dependency paths
|
|
79
79
|
- production, development, optional, and peer dependency classification
|
|
@@ -159,7 +159,7 @@ Supported lockfiles:
|
|
|
159
159
|
- `npm-shrinkwrap.json` with the same package-lock parser support
|
|
160
160
|
- `pnpm-lock.yaml` with `importers`, `packages`, and `snapshots` sections
|
|
161
161
|
- `deno.lock` npm package entries from Deno v3/v4-style lockfiles
|
|
162
|
-
- Yarn
|
|
162
|
+
- Yarn classic/Berry `yarn.lock` with root and workspace dependency sets from `package.json` manifests
|
|
163
163
|
|
|
164
164
|
Select a specific lockfile when a project contains more than one supported lockfile:
|
|
165
165
|
|
package/dist/cli.js
CHANGED
|
@@ -15175,7 +15175,7 @@ function parseDiffArgs(argv) {
|
|
|
15175
15175
|
}
|
|
15176
15176
|
|
|
15177
15177
|
// src/cli/version.ts
|
|
15178
|
-
var OHRISK_VERSION = "0.
|
|
15178
|
+
var OHRISK_VERSION = "0.128.0";
|
|
15179
15179
|
|
|
15180
15180
|
// src/diff/compare.ts
|
|
15181
15181
|
function diffRiskFindings(input) {
|
|
@@ -17266,6 +17266,17 @@ function resolveNpmDependencyReference(requestedName, range) {
|
|
|
17266
17266
|
aliased: alias.name !== requestedName
|
|
17267
17267
|
};
|
|
17268
17268
|
}
|
|
17269
|
+
if (range.startsWith("npm:")) {
|
|
17270
|
+
const bareRange = range.slice("npm:".length);
|
|
17271
|
+
if (bareRange !== "") {
|
|
17272
|
+
return {
|
|
17273
|
+
requestedName,
|
|
17274
|
+
lookupName: requestedName,
|
|
17275
|
+
lookupRange: bareRange,
|
|
17276
|
+
aliased: false
|
|
17277
|
+
};
|
|
17278
|
+
}
|
|
17279
|
+
}
|
|
17269
17280
|
return {
|
|
17270
17281
|
requestedName,
|
|
17271
17282
|
lookupName: requestedName,
|
|
@@ -19141,17 +19152,20 @@ function readPackageName2(packageJson) {
|
|
|
19141
19152
|
return typeof packageJson.name === "string" && packageJson.name !== "" ? packageJson.name : undefined;
|
|
19142
19153
|
}
|
|
19143
19154
|
function parseLockfile(input, lockfilePath) {
|
|
19155
|
+
if (hasMergeConflictMarkers(input)) {
|
|
19156
|
+
return err(createError({
|
|
19157
|
+
code: "YARN_LOCK_PARSE_FAILED",
|
|
19158
|
+
category: "unsupported_input",
|
|
19159
|
+
message: "Failed to parse yarn.lock because it contains unresolved merge conflicts.",
|
|
19160
|
+
details: {
|
|
19161
|
+
lockfilePath
|
|
19162
|
+
}
|
|
19163
|
+
}));
|
|
19164
|
+
}
|
|
19165
|
+
if (isYarnBerryLockfile(input)) {
|
|
19166
|
+
return parseBerryLockfile(input, lockfilePath);
|
|
19167
|
+
}
|
|
19144
19168
|
try {
|
|
19145
|
-
if (hasMergeConflictMarkers(input)) {
|
|
19146
|
-
return err(createError({
|
|
19147
|
-
code: "YARN_LOCK_PARSE_FAILED",
|
|
19148
|
-
category: "unsupported_input",
|
|
19149
|
-
message: "Failed to parse yarn.lock because it contains unresolved merge conflicts.",
|
|
19150
|
-
details: {
|
|
19151
|
-
lockfilePath
|
|
19152
|
-
}
|
|
19153
|
-
}));
|
|
19154
|
-
}
|
|
19155
19169
|
const parsed = yarnLockfile.parse(input);
|
|
19156
19170
|
if (parsed.type === "conflict") {
|
|
19157
19171
|
return err(createError({
|
|
@@ -19163,12 +19177,47 @@ function parseLockfile(input, lockfilePath) {
|
|
|
19163
19177
|
}
|
|
19164
19178
|
}));
|
|
19165
19179
|
}
|
|
19166
|
-
return ok(
|
|
19180
|
+
return ok({
|
|
19181
|
+
format: "classic",
|
|
19182
|
+
entries: parsed.object
|
|
19183
|
+
});
|
|
19184
|
+
} catch (cause) {
|
|
19185
|
+
return err(createError({
|
|
19186
|
+
code: "YARN_LOCK_PARSE_FAILED",
|
|
19187
|
+
category: "unsupported_input",
|
|
19188
|
+
message: "Failed to parse yarn.lock. Ohrisk expects a Yarn classic or Berry lockfile.",
|
|
19189
|
+
details: {
|
|
19190
|
+
lockfilePath,
|
|
19191
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
19192
|
+
}
|
|
19193
|
+
}));
|
|
19194
|
+
}
|
|
19195
|
+
}
|
|
19196
|
+
function isYarnBerryLockfile(input) {
|
|
19197
|
+
return /^__metadata:\s*$/m.test(input);
|
|
19198
|
+
}
|
|
19199
|
+
function parseBerryLockfile(input, lockfilePath) {
|
|
19200
|
+
try {
|
|
19201
|
+
const parsed = $parse(input);
|
|
19202
|
+
if (!isObjectRecord8(parsed)) {
|
|
19203
|
+
throw new Error("Expected a YAML mapping at the document root.");
|
|
19204
|
+
}
|
|
19205
|
+
const entries = {};
|
|
19206
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
19207
|
+
if (key === "__metadata" || !isObjectRecord8(value)) {
|
|
19208
|
+
continue;
|
|
19209
|
+
}
|
|
19210
|
+
entries[key] = value;
|
|
19211
|
+
}
|
|
19212
|
+
return ok({
|
|
19213
|
+
format: "berry",
|
|
19214
|
+
entries
|
|
19215
|
+
});
|
|
19167
19216
|
} catch (cause) {
|
|
19168
19217
|
return err(createError({
|
|
19169
19218
|
code: "YARN_LOCK_PARSE_FAILED",
|
|
19170
19219
|
category: "unsupported_input",
|
|
19171
|
-
message: "Failed to parse
|
|
19220
|
+
message: "Failed to parse Yarn Berry lockfile.",
|
|
19172
19221
|
details: {
|
|
19173
19222
|
lockfilePath,
|
|
19174
19223
|
cause: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -19181,12 +19230,12 @@ function hasMergeConflictMarkers(input) {
|
|
|
19181
19230
|
}
|
|
19182
19231
|
function parsePackageRecords4(lockfile) {
|
|
19183
19232
|
const records = [];
|
|
19184
|
-
for (const [key, entry] of Object.entries(lockfile)) {
|
|
19233
|
+
for (const [key, entry] of Object.entries(lockfile.entries)) {
|
|
19185
19234
|
if (typeof entry.version !== "string" || entry.version === "") {
|
|
19186
19235
|
continue;
|
|
19187
19236
|
}
|
|
19188
19237
|
const descriptors = splitDescriptorKey(key);
|
|
19189
|
-
const identity2 = descriptors.map(parseDescriptor).find(Boolean);
|
|
19238
|
+
const identity2 = lockfile.format === "berry" ? readBerryPackageIdentity({ key, entry }) : descriptors.map(parseDescriptor).find(Boolean);
|
|
19190
19239
|
if (!identity2) {
|
|
19191
19240
|
continue;
|
|
19192
19241
|
}
|
|
@@ -19194,7 +19243,10 @@ function parsePackageRecords4(lockfile) {
|
|
|
19194
19243
|
const integrity = typeof entry.integrity === "string" && entry.integrity !== "" ? entry.integrity : undefined;
|
|
19195
19244
|
records.push({
|
|
19196
19245
|
key,
|
|
19197
|
-
descriptors
|
|
19246
|
+
descriptors: descriptorIndexKeys({
|
|
19247
|
+
descriptors,
|
|
19248
|
+
format: lockfile.format
|
|
19249
|
+
}),
|
|
19198
19250
|
name: identity2.name,
|
|
19199
19251
|
version: entry.version,
|
|
19200
19252
|
id: `${identity2.name}@${entry.version}`,
|
|
@@ -19208,8 +19260,26 @@ function parsePackageRecords4(lockfile) {
|
|
|
19208
19260
|
function splitDescriptorKey(key) {
|
|
19209
19261
|
return key.split(/,\s*/).map((descriptor) => descriptor.trim()).filter(Boolean);
|
|
19210
19262
|
}
|
|
19263
|
+
function descriptorIndexKeys(input) {
|
|
19264
|
+
const keys = new Set;
|
|
19265
|
+
for (const descriptor of input.descriptors) {
|
|
19266
|
+
const unquoted = unquoteDescriptor(descriptor);
|
|
19267
|
+
keys.add(unquoted);
|
|
19268
|
+
const parsed = input.format === "berry" ? parseBerryDescriptor(unquoted) : parseDescriptor(unquoted);
|
|
19269
|
+
if (!parsed) {
|
|
19270
|
+
continue;
|
|
19271
|
+
}
|
|
19272
|
+
keys.add(`${parsed.name}@${parsed.range}`);
|
|
19273
|
+
keys.add(`${parsed.name}@npm:${parsed.range}`);
|
|
19274
|
+
}
|
|
19275
|
+
return [...keys];
|
|
19276
|
+
}
|
|
19211
19277
|
function parseDescriptor(descriptor) {
|
|
19212
|
-
const unquoted = descriptor
|
|
19278
|
+
const unquoted = unquoteDescriptor(descriptor);
|
|
19279
|
+
const berryDescriptor = parseBerryDescriptor(unquoted);
|
|
19280
|
+
if (berryDescriptor) {
|
|
19281
|
+
return berryDescriptor;
|
|
19282
|
+
}
|
|
19213
19283
|
const aliasMarker = "@npm:";
|
|
19214
19284
|
const aliasIndex = unquoted.indexOf(aliasMarker);
|
|
19215
19285
|
if (aliasIndex > 0) {
|
|
@@ -19225,6 +19295,89 @@ function parseDescriptor(descriptor) {
|
|
|
19225
19295
|
}
|
|
19226
19296
|
return { name: parsed.name, range: parsed.reference };
|
|
19227
19297
|
}
|
|
19298
|
+
function parseBerryDescriptor(descriptor) {
|
|
19299
|
+
const npmLocator = parseBerryNpmLocator(descriptor);
|
|
19300
|
+
if (npmLocator) {
|
|
19301
|
+
const alias = parseNpmPackageReference(`npm:${npmLocator.reference}`);
|
|
19302
|
+
return alias ? { name: alias.name, range: alias.reference } : { name: npmLocator.name, range: npmLocator.reference };
|
|
19303
|
+
}
|
|
19304
|
+
const patchLocator = parseBerryPatchLocator(descriptor);
|
|
19305
|
+
if (patchLocator) {
|
|
19306
|
+
return patchLocator;
|
|
19307
|
+
}
|
|
19308
|
+
return;
|
|
19309
|
+
}
|
|
19310
|
+
function readBerryPackageIdentity(input) {
|
|
19311
|
+
const resolution = typeof input.entry.resolution === "string" ? input.entry.resolution : undefined;
|
|
19312
|
+
const parsedResolution = resolution ? parseBerryResolution(resolution) : undefined;
|
|
19313
|
+
if (parsedResolution) {
|
|
19314
|
+
return parsedResolution;
|
|
19315
|
+
}
|
|
19316
|
+
const descriptorIdentity = splitDescriptorKey(input.key).map((descriptor) => parseBerryDescriptor(unquoteDescriptor(descriptor))).find(Boolean);
|
|
19317
|
+
return descriptorIdentity ? { name: descriptorIdentity.name, version: descriptorIdentity.range } : undefined;
|
|
19318
|
+
}
|
|
19319
|
+
function parseBerryResolution(value) {
|
|
19320
|
+
const unquoted = unquoteDescriptor(value);
|
|
19321
|
+
const npmLocator = parseBerryNpmLocator(unquoted);
|
|
19322
|
+
if (npmLocator) {
|
|
19323
|
+
return {
|
|
19324
|
+
name: npmLocator.name,
|
|
19325
|
+
version: npmLocator.reference
|
|
19326
|
+
};
|
|
19327
|
+
}
|
|
19328
|
+
const patchLocator = parseBerryPatchLocator(unquoted);
|
|
19329
|
+
if (patchLocator) {
|
|
19330
|
+
return {
|
|
19331
|
+
name: patchLocator.name,
|
|
19332
|
+
version: patchLocator.range
|
|
19333
|
+
};
|
|
19334
|
+
}
|
|
19335
|
+
return;
|
|
19336
|
+
}
|
|
19337
|
+
function parseBerryNpmLocator(value) {
|
|
19338
|
+
const marker = "@npm:";
|
|
19339
|
+
const index = value.indexOf(marker);
|
|
19340
|
+
if (index <= 0) {
|
|
19341
|
+
return;
|
|
19342
|
+
}
|
|
19343
|
+
const name = value.slice(0, index);
|
|
19344
|
+
const reference = value.slice(index + marker.length);
|
|
19345
|
+
if (!isValidNpmPackageName(name) || reference === "") {
|
|
19346
|
+
return;
|
|
19347
|
+
}
|
|
19348
|
+
return { name, reference };
|
|
19349
|
+
}
|
|
19350
|
+
function parseBerryPatchLocator(value) {
|
|
19351
|
+
const marker = "@patch:";
|
|
19352
|
+
const index = value.indexOf(marker);
|
|
19353
|
+
if (index <= 0) {
|
|
19354
|
+
return;
|
|
19355
|
+
}
|
|
19356
|
+
const patchedLocator = value.slice(index + marker.length).split("#")[0]?.split("::")[0];
|
|
19357
|
+
if (!patchedLocator) {
|
|
19358
|
+
return;
|
|
19359
|
+
}
|
|
19360
|
+
const decodedLocator = safeDecodeURIComponent(patchedLocator);
|
|
19361
|
+
const npmLocator = parseBerryNpmLocator(decodedLocator);
|
|
19362
|
+
if (!npmLocator) {
|
|
19363
|
+
return;
|
|
19364
|
+
}
|
|
19365
|
+
const alias = parseNpmPackageReference(`npm:${npmLocator.reference}`);
|
|
19366
|
+
return alias ? { name: alias.name, range: alias.reference } : { name: npmLocator.name, range: npmLocator.reference };
|
|
19367
|
+
}
|
|
19368
|
+
function safeDecodeURIComponent(value) {
|
|
19369
|
+
try {
|
|
19370
|
+
return decodeURIComponent(value);
|
|
19371
|
+
} catch {
|
|
19372
|
+
return value;
|
|
19373
|
+
}
|
|
19374
|
+
}
|
|
19375
|
+
function unquoteDescriptor(value) {
|
|
19376
|
+
return value.replace(/^"|"$/g, "");
|
|
19377
|
+
}
|
|
19378
|
+
function isValidNpmPackageName(value) {
|
|
19379
|
+
return /^(?:@[^/]+\/)?[^/@][^@]*$/.test(value);
|
|
19380
|
+
}
|
|
19228
19381
|
function collectRootDependencies5(packageJson) {
|
|
19229
19382
|
return [
|
|
19230
19383
|
...dependencyEntries4(packageJson.dependencies, "production"),
|
|
@@ -19277,10 +19430,28 @@ function indexPackagesByName3(records) {
|
|
|
19277
19430
|
return index;
|
|
19278
19431
|
}
|
|
19279
19432
|
function resolvePackageRecord4(input) {
|
|
19280
|
-
const descriptor = `${input.name}@${input.range}`;
|
|
19281
19433
|
const reference = resolveNpmDependencyReference(input.name, input.range);
|
|
19282
19434
|
const candidates = input.nameIndex.get(reference.lookupName) ?? [];
|
|
19283
|
-
return
|
|
19435
|
+
return dependencyDescriptorCandidates({
|
|
19436
|
+
name: input.name,
|
|
19437
|
+
range: input.range,
|
|
19438
|
+
reference
|
|
19439
|
+
}).map((descriptor) => input.descriptorIndex.get(descriptor)).find((record) => record !== undefined) ?? (candidates.length === 1 ? candidates[0] : undefined) ?? candidates.find((candidate) => candidate.version === reference.lookupRange) ?? undefined;
|
|
19440
|
+
}
|
|
19441
|
+
function dependencyDescriptorCandidates(input) {
|
|
19442
|
+
const candidates = new Set;
|
|
19443
|
+
candidates.add(`${input.name}@${input.range}`);
|
|
19444
|
+
candidates.add(`${input.reference.lookupName}@${input.reference.lookupRange}`);
|
|
19445
|
+
candidates.add(`${input.name}@npm:${input.range}`);
|
|
19446
|
+
candidates.add(`${input.reference.lookupName}@npm:${input.reference.lookupRange}`);
|
|
19447
|
+
if (input.range.startsWith("npm:")) {
|
|
19448
|
+
const bareRange = input.range.slice("npm:".length);
|
|
19449
|
+
candidates.add(`${input.name}@${bareRange}`);
|
|
19450
|
+
candidates.add(`${input.name}@npm:${bareRange}`);
|
|
19451
|
+
candidates.add(`${input.reference.lookupName}@${bareRange}`);
|
|
19452
|
+
candidates.add(`${input.reference.lookupName}@npm:${bareRange}`);
|
|
19453
|
+
}
|
|
19454
|
+
return [...candidates].filter((candidate) => !candidate.endsWith("@"));
|
|
19284
19455
|
}
|
|
19285
19456
|
function walkDependency5(input) {
|
|
19286
19457
|
if (input.seen.has(input.record.key)) {
|
|
@@ -21200,7 +21371,7 @@ var KNOWN_PROJECT_MANIFESTS = [
|
|
|
21200
21371
|
"deno.json",
|
|
21201
21372
|
"deno.jsonc"
|
|
21202
21373
|
];
|
|
21203
|
-
var SUPPORTED_LOCKFILE_MESSAGE = "Ohrisk currently supports bun.lock, package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, deno.lock, and Yarn
|
|
21374
|
+
var SUPPORTED_LOCKFILE_MESSAGE = "Ohrisk currently supports bun.lock, package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, deno.lock, and Yarn classic/Berry yarn.lock.";
|
|
21204
21375
|
function discoverProject(options = {}) {
|
|
21205
21376
|
const startDir = path12.resolve(options.cwd ?? process.cwd());
|
|
21206
21377
|
try {
|