ohrisk 0.127.0 → 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 +32 -0
- package/README.md +4 -4
- package/dist/cli.js +294 -46
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
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
|
+
|
|
17
|
+
## 0.127.1 - 2026-06-20
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The default Node artifact fetcher now returns DNS lookup results in the shape
|
|
22
|
+
requested by Node's HTTP client, fixing `Invalid IP address: undefined`
|
|
23
|
+
failures when scanning real remote package tarballs.
|
|
24
|
+
- Package tarball evidence now accepts npm tarballs that use a custom
|
|
25
|
+
top-level directory, such as `bun/package.json`, instead of only
|
|
26
|
+
`package/package.json`.
|
|
27
|
+
- Registry metadata fallback now requests the exact package version endpoint
|
|
28
|
+
instead of the full package metadata document, avoiding oversized metadata
|
|
29
|
+
failures for packages with long histories such as `@types/node`.
|
|
30
|
+
- Remote package tarballs that exceed Ohrisk's size limits now produce
|
|
31
|
+
unavailable package evidence instead of aborting the whole repository scan.
|
|
32
|
+
- Package metadata now uses npm's normalized `bin` path form, avoiding publish
|
|
33
|
+
auto-correction warnings for the CLI entry.
|
|
34
|
+
|
|
3
35
|
## 0.127.0 - 2026-06-20
|
|
4
36
|
|
|
5
37
|
### 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) {
|
|
@@ -15480,7 +15480,8 @@ function collectTarballEvidence(input) {
|
|
|
15480
15480
|
tarball: unpacked.value,
|
|
15481
15481
|
maxEntries: input.maxEntries ?? PACKAGE_TARBALL_MAX_ENTRIES
|
|
15482
15482
|
});
|
|
15483
|
-
const
|
|
15483
|
+
const packageRoot = findPackageRoot(entries);
|
|
15484
|
+
const packageJsonEntry = packageRoot === undefined ? undefined : entries.find((entry) => normalizePackagePath(entry.path, packageRoot) === "package.json");
|
|
15484
15485
|
if (!packageJsonEntry) {
|
|
15485
15486
|
return err(createError({
|
|
15486
15487
|
code: "PACKAGE_JSON_PARSE_FAILED",
|
|
@@ -15498,7 +15499,7 @@ function collectTarballEvidence(input) {
|
|
|
15498
15499
|
if (!packageJson.ok) {
|
|
15499
15500
|
return err(packageJson.error);
|
|
15500
15501
|
}
|
|
15501
|
-
const files = collectTarEvidenceFiles(entries);
|
|
15502
|
+
const files = collectTarEvidenceFiles(entries, packageRoot);
|
|
15502
15503
|
const warnings = files.length === 0 ? ["No LICENSE, LICENCE, UNLICENSE, COPYING, or NOTICE file found."] : [];
|
|
15503
15504
|
return ok({
|
|
15504
15505
|
packageId: input.packageId,
|
|
@@ -15589,9 +15590,22 @@ function parseTarEntries(input) {
|
|
|
15589
15590
|
}
|
|
15590
15591
|
return entries;
|
|
15591
15592
|
}
|
|
15592
|
-
function
|
|
15593
|
+
function findPackageRoot(entries) {
|
|
15594
|
+
if (entries.some((entry) => entry.path === "package.json")) {
|
|
15595
|
+
return "";
|
|
15596
|
+
}
|
|
15597
|
+
const roots = entries.map((entry) => {
|
|
15598
|
+
const match = /^([^/]+)\/package\.json$/.exec(entry.path);
|
|
15599
|
+
return match?.[1];
|
|
15600
|
+
}).filter((root) => root !== undefined).sort();
|
|
15601
|
+
if (roots.includes("package")) {
|
|
15602
|
+
return "package";
|
|
15603
|
+
}
|
|
15604
|
+
return roots[0];
|
|
15605
|
+
}
|
|
15606
|
+
function collectTarEvidenceFiles(entries, packageRoot) {
|
|
15593
15607
|
return entries.map((entry) => {
|
|
15594
|
-
const normalized = normalizePackagePath(entry.path);
|
|
15608
|
+
const normalized = normalizePackagePath(entry.path, packageRoot);
|
|
15595
15609
|
if (!isRootPackageFile(normalized)) {
|
|
15596
15610
|
return;
|
|
15597
15611
|
}
|
|
@@ -15622,8 +15636,11 @@ function readLicenseFields2(packageJson) {
|
|
|
15622
15636
|
function isObjectRecord2(value) {
|
|
15623
15637
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15624
15638
|
}
|
|
15625
|
-
function normalizePackagePath(path2) {
|
|
15626
|
-
|
|
15639
|
+
function normalizePackagePath(path2, packageRoot) {
|
|
15640
|
+
if (packageRoot === "") {
|
|
15641
|
+
return path2;
|
|
15642
|
+
}
|
|
15643
|
+
return path2.startsWith(`${packageRoot}/`) ? path2.slice(packageRoot.length + 1) : path2;
|
|
15627
15644
|
}
|
|
15628
15645
|
function readNullTerminated(buffer, start, length) {
|
|
15629
15646
|
const slice = buffer.subarray(start, start + length);
|
|
@@ -15911,7 +15928,7 @@ function localArtifactTooLargeError(input) {
|
|
|
15911
15928
|
});
|
|
15912
15929
|
}
|
|
15913
15930
|
async function collectRegistryTarballEvidence(input) {
|
|
15914
|
-
const metadataUrl =
|
|
15931
|
+
const metadataUrl = npmRegistryPackageVersionUrl(input.node.name, input.node.version);
|
|
15915
15932
|
const metadataUrlPreflight = await preflightRemoteArtifactFetchTarget({
|
|
15916
15933
|
code: "REGISTRY_METADATA_FETCH_FAILED",
|
|
15917
15934
|
packageId: input.node.id,
|
|
@@ -16250,6 +16267,9 @@ async function collectRemoteTarballEvidence(input) {
|
|
|
16250
16267
|
}
|
|
16251
16268
|
});
|
|
16252
16269
|
if (!tarball.ok) {
|
|
16270
|
+
if (isPackageTarballTooLargeError(tarball.error)) {
|
|
16271
|
+
return ok(unavailableOversizedTarballEvidence(input.packageId));
|
|
16272
|
+
}
|
|
16253
16273
|
return err(tarball.error);
|
|
16254
16274
|
}
|
|
16255
16275
|
const verified = verifyPackageIntegrity({
|
|
@@ -16266,6 +16286,9 @@ async function collectRemoteTarballEvidence(input) {
|
|
|
16266
16286
|
tarball: tarball.value
|
|
16267
16287
|
});
|
|
16268
16288
|
if (!evidence.ok) {
|
|
16289
|
+
if (isPackageTarballTooLargeError(evidence.error)) {
|
|
16290
|
+
return ok(unavailableOversizedTarballEvidence(input.packageId));
|
|
16291
|
+
}
|
|
16269
16292
|
return err(evidence.error);
|
|
16270
16293
|
}
|
|
16271
16294
|
return ok(addIntegrityWarningWhenUnverified({
|
|
@@ -16285,6 +16308,19 @@ async function collectRemoteTarballEvidence(input) {
|
|
|
16285
16308
|
}));
|
|
16286
16309
|
}
|
|
16287
16310
|
}
|
|
16311
|
+
function isPackageTarballTooLargeError(error) {
|
|
16312
|
+
return error.code === "TARBALL_FETCH_FAILED" && error.message === "Package tarball response exceeded the maximum supported size." || error.code === "TARBALL_PARSE_FAILED" && error.message === "Failed to decompress package tarball evidence." && typeof error.details.maxUnpackedBytes === "number";
|
|
16313
|
+
}
|
|
16314
|
+
function unavailableOversizedTarballEvidence(packageId) {
|
|
16315
|
+
return {
|
|
16316
|
+
packageId,
|
|
16317
|
+
files: [],
|
|
16318
|
+
source: "unavailable",
|
|
16319
|
+
warnings: [
|
|
16320
|
+
"Package tarball evidence exceeded Ohrisk's size limit and was not scanned."
|
|
16321
|
+
]
|
|
16322
|
+
};
|
|
16323
|
+
}
|
|
16288
16324
|
function resolveExistingLocalArtifactPath(input) {
|
|
16289
16325
|
const allowedRoot = realpathSync(resolveLocalArtifactRoot(input.projectRoot));
|
|
16290
16326
|
const artifactPath = realpathSync(input.artifactPath);
|
|
@@ -16967,6 +17003,9 @@ function decodeIntegrityDigest(input) {
|
|
|
16967
17003
|
const normalizedDecoded = decoded.toString("base64").replace(/=+$/, "");
|
|
16968
17004
|
return normalizedDecoded === normalizedInput ? decoded : undefined;
|
|
16969
17005
|
}
|
|
17006
|
+
function npmRegistryPackageVersionUrl(name, version) {
|
|
17007
|
+
return `${npmRegistryPackageUrl(name)}/${encodeURIComponent(version)}`;
|
|
17008
|
+
}
|
|
16970
17009
|
function npmRegistryPackageUrl(name) {
|
|
16971
17010
|
return `https://registry.npmjs.org/${encodeURIComponent(name).replace(/^%40/, "@")}`;
|
|
16972
17011
|
}
|
|
@@ -16974,6 +17013,10 @@ function readRegistryTarballUrl(metadata, version) {
|
|
|
16974
17013
|
if (!isRecord(metadata)) {
|
|
16975
17014
|
return;
|
|
16976
17015
|
}
|
|
17016
|
+
const dist = metadata.dist;
|
|
17017
|
+
if (isRecord(dist) && typeof dist.tarball === "string") {
|
|
17018
|
+
return dist.tarball;
|
|
17019
|
+
}
|
|
16977
17020
|
const versions = metadata.versions;
|
|
16978
17021
|
if (!isRecord(versions)) {
|
|
16979
17022
|
return;
|
|
@@ -16982,11 +17025,11 @@ function readRegistryTarballUrl(metadata, version) {
|
|
|
16982
17025
|
if (!isRecord(versionMetadata)) {
|
|
16983
17026
|
return;
|
|
16984
17027
|
}
|
|
16985
|
-
const
|
|
16986
|
-
if (!isRecord(
|
|
17028
|
+
const versionDist = versionMetadata.dist;
|
|
17029
|
+
if (!isRecord(versionDist) || typeof versionDist.tarball !== "string") {
|
|
16987
17030
|
return;
|
|
16988
17031
|
}
|
|
16989
|
-
return
|
|
17032
|
+
return versionDist.tarball;
|
|
16990
17033
|
}
|
|
16991
17034
|
function isRecord(value) {
|
|
16992
17035
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -17028,29 +17071,63 @@ async function defaultArtifactHostResolver(hostname) {
|
|
|
17028
17071
|
}
|
|
17029
17072
|
function secureArtifactLookup(hostname, options, callback) {
|
|
17030
17073
|
defaultArtifactHostResolver(hostname).then((resolutions) => {
|
|
17031
|
-
|
|
17032
|
-
|
|
17074
|
+
const selection = selectSecureArtifactLookupResponse(hostname, options, resolutions);
|
|
17075
|
+
if (!selection.ok) {
|
|
17076
|
+
respondToSecureArtifactLookupError(callback, options, selection.error);
|
|
17033
17077
|
return;
|
|
17034
17078
|
}
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
if (familyResolutions.length === 0) {
|
|
17038
|
-
callback(new Error(`Artifact host ${normalizeUrlHostname(hostname)} returned no matching DNS addresses.`), "", 0);
|
|
17079
|
+
if (selection.value.all) {
|
|
17080
|
+
callback(null, selection.value.resolutions);
|
|
17039
17081
|
return;
|
|
17040
17082
|
}
|
|
17041
|
-
|
|
17042
|
-
const blockedReason = blockedRemoteArtifactHostReason(resolution.address);
|
|
17043
|
-
if (blockedReason) {
|
|
17044
|
-
callback(new Error(`Blocked artifact host resolution for ${normalizeUrlHostname(hostname)}: ${normalizeUrlHostname(resolution.address)} (${blockedReason}).`), "", 0);
|
|
17045
|
-
return;
|
|
17046
|
-
}
|
|
17047
|
-
}
|
|
17048
|
-
const selected = familyResolutions[0];
|
|
17049
|
-
callback(null, selected.address, selected.family);
|
|
17083
|
+
callback(null, selection.value.address, selection.value.family);
|
|
17050
17084
|
}).catch((cause) => {
|
|
17051
|
-
callback
|
|
17085
|
+
respondToSecureArtifactLookupError(callback, options, cause instanceof Error ? cause : new Error(String(cause)));
|
|
17086
|
+
});
|
|
17087
|
+
}
|
|
17088
|
+
function selectSecureArtifactLookupResponse(hostname, options, resolutions) {
|
|
17089
|
+
const normalizedOptions = normalizeArtifactLookupOptions(options);
|
|
17090
|
+
const normalizedHostname = normalizeUrlHostname(hostname);
|
|
17091
|
+
if (resolutions.length === 0) {
|
|
17092
|
+
return err(new Error(`Artifact host ${normalizedHostname} returned no DNS addresses.`));
|
|
17093
|
+
}
|
|
17094
|
+
const familyResolutions = normalizedOptions.family === undefined ? resolutions : resolutions.filter((resolution) => resolution.family === normalizedOptions.family);
|
|
17095
|
+
if (familyResolutions.length === 0) {
|
|
17096
|
+
return err(new Error(`Artifact host ${normalizedHostname} returned no matching DNS addresses.`));
|
|
17097
|
+
}
|
|
17098
|
+
for (const resolution of familyResolutions) {
|
|
17099
|
+
const blockedReason = blockedRemoteArtifactHostReason(resolution.address);
|
|
17100
|
+
if (blockedReason) {
|
|
17101
|
+
return err(new Error(`Blocked artifact host resolution for ${normalizedHostname}: ${normalizeUrlHostname(resolution.address)} (${blockedReason}).`));
|
|
17102
|
+
}
|
|
17103
|
+
}
|
|
17104
|
+
if (normalizedOptions.all) {
|
|
17105
|
+
return ok({
|
|
17106
|
+
all: true,
|
|
17107
|
+
resolutions: familyResolutions
|
|
17108
|
+
});
|
|
17109
|
+
}
|
|
17110
|
+
const selected = familyResolutions[0];
|
|
17111
|
+
return ok({
|
|
17112
|
+
all: false,
|
|
17113
|
+
address: selected.address,
|
|
17114
|
+
family: selected.family
|
|
17052
17115
|
});
|
|
17053
17116
|
}
|
|
17117
|
+
function normalizeArtifactLookupOptions(options) {
|
|
17118
|
+
const family = typeof options === "number" ? options : options?.family;
|
|
17119
|
+
return {
|
|
17120
|
+
all: typeof options === "object" && options?.all === true,
|
|
17121
|
+
family: family === 4 || family === 6 ? family : undefined
|
|
17122
|
+
};
|
|
17123
|
+
}
|
|
17124
|
+
function respondToSecureArtifactLookupError(callback, options, error) {
|
|
17125
|
+
if (normalizeArtifactLookupOptions(options).all) {
|
|
17126
|
+
callback(error, []);
|
|
17127
|
+
return;
|
|
17128
|
+
}
|
|
17129
|
+
callback(error, "", 0);
|
|
17130
|
+
}
|
|
17054
17131
|
function headersForIncomingMessage(headers) {
|
|
17055
17132
|
return {
|
|
17056
17133
|
get: (name) => {
|
|
@@ -17189,6 +17266,17 @@ function resolveNpmDependencyReference(requestedName, range) {
|
|
|
17189
17266
|
aliased: alias.name !== requestedName
|
|
17190
17267
|
};
|
|
17191
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
|
+
}
|
|
17192
17280
|
return {
|
|
17193
17281
|
requestedName,
|
|
17194
17282
|
lookupName: requestedName,
|
|
@@ -19064,17 +19152,20 @@ function readPackageName2(packageJson) {
|
|
|
19064
19152
|
return typeof packageJson.name === "string" && packageJson.name !== "" ? packageJson.name : undefined;
|
|
19065
19153
|
}
|
|
19066
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
|
+
}
|
|
19067
19168
|
try {
|
|
19068
|
-
if (hasMergeConflictMarkers(input)) {
|
|
19069
|
-
return err(createError({
|
|
19070
|
-
code: "YARN_LOCK_PARSE_FAILED",
|
|
19071
|
-
category: "unsupported_input",
|
|
19072
|
-
message: "Failed to parse yarn.lock because it contains unresolved merge conflicts.",
|
|
19073
|
-
details: {
|
|
19074
|
-
lockfilePath
|
|
19075
|
-
}
|
|
19076
|
-
}));
|
|
19077
|
-
}
|
|
19078
19169
|
const parsed = yarnLockfile.parse(input);
|
|
19079
19170
|
if (parsed.type === "conflict") {
|
|
19080
19171
|
return err(createError({
|
|
@@ -19086,12 +19177,47 @@ function parseLockfile(input, lockfilePath) {
|
|
|
19086
19177
|
}
|
|
19087
19178
|
}));
|
|
19088
19179
|
}
|
|
19089
|
-
return ok(
|
|
19180
|
+
return ok({
|
|
19181
|
+
format: "classic",
|
|
19182
|
+
entries: parsed.object
|
|
19183
|
+
});
|
|
19090
19184
|
} catch (cause) {
|
|
19091
19185
|
return err(createError({
|
|
19092
19186
|
code: "YARN_LOCK_PARSE_FAILED",
|
|
19093
19187
|
category: "unsupported_input",
|
|
19094
|
-
message: "Failed to parse yarn.lock. Ohrisk
|
|
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
|
+
});
|
|
19216
|
+
} catch (cause) {
|
|
19217
|
+
return err(createError({
|
|
19218
|
+
code: "YARN_LOCK_PARSE_FAILED",
|
|
19219
|
+
category: "unsupported_input",
|
|
19220
|
+
message: "Failed to parse Yarn Berry lockfile.",
|
|
19095
19221
|
details: {
|
|
19096
19222
|
lockfilePath,
|
|
19097
19223
|
cause: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -19104,12 +19230,12 @@ function hasMergeConflictMarkers(input) {
|
|
|
19104
19230
|
}
|
|
19105
19231
|
function parsePackageRecords4(lockfile) {
|
|
19106
19232
|
const records = [];
|
|
19107
|
-
for (const [key, entry] of Object.entries(lockfile)) {
|
|
19233
|
+
for (const [key, entry] of Object.entries(lockfile.entries)) {
|
|
19108
19234
|
if (typeof entry.version !== "string" || entry.version === "") {
|
|
19109
19235
|
continue;
|
|
19110
19236
|
}
|
|
19111
19237
|
const descriptors = splitDescriptorKey(key);
|
|
19112
|
-
const identity2 = descriptors.map(parseDescriptor).find(Boolean);
|
|
19238
|
+
const identity2 = lockfile.format === "berry" ? readBerryPackageIdentity({ key, entry }) : descriptors.map(parseDescriptor).find(Boolean);
|
|
19113
19239
|
if (!identity2) {
|
|
19114
19240
|
continue;
|
|
19115
19241
|
}
|
|
@@ -19117,7 +19243,10 @@ function parsePackageRecords4(lockfile) {
|
|
|
19117
19243
|
const integrity = typeof entry.integrity === "string" && entry.integrity !== "" ? entry.integrity : undefined;
|
|
19118
19244
|
records.push({
|
|
19119
19245
|
key,
|
|
19120
|
-
descriptors
|
|
19246
|
+
descriptors: descriptorIndexKeys({
|
|
19247
|
+
descriptors,
|
|
19248
|
+
format: lockfile.format
|
|
19249
|
+
}),
|
|
19121
19250
|
name: identity2.name,
|
|
19122
19251
|
version: entry.version,
|
|
19123
19252
|
id: `${identity2.name}@${entry.version}`,
|
|
@@ -19131,8 +19260,26 @@ function parsePackageRecords4(lockfile) {
|
|
|
19131
19260
|
function splitDescriptorKey(key) {
|
|
19132
19261
|
return key.split(/,\s*/).map((descriptor) => descriptor.trim()).filter(Boolean);
|
|
19133
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
|
+
}
|
|
19134
19277
|
function parseDescriptor(descriptor) {
|
|
19135
|
-
const unquoted = descriptor
|
|
19278
|
+
const unquoted = unquoteDescriptor(descriptor);
|
|
19279
|
+
const berryDescriptor = parseBerryDescriptor(unquoted);
|
|
19280
|
+
if (berryDescriptor) {
|
|
19281
|
+
return berryDescriptor;
|
|
19282
|
+
}
|
|
19136
19283
|
const aliasMarker = "@npm:";
|
|
19137
19284
|
const aliasIndex = unquoted.indexOf(aliasMarker);
|
|
19138
19285
|
if (aliasIndex > 0) {
|
|
@@ -19148,6 +19295,89 @@ function parseDescriptor(descriptor) {
|
|
|
19148
19295
|
}
|
|
19149
19296
|
return { name: parsed.name, range: parsed.reference };
|
|
19150
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
|
+
}
|
|
19151
19381
|
function collectRootDependencies5(packageJson) {
|
|
19152
19382
|
return [
|
|
19153
19383
|
...dependencyEntries4(packageJson.dependencies, "production"),
|
|
@@ -19200,10 +19430,28 @@ function indexPackagesByName3(records) {
|
|
|
19200
19430
|
return index;
|
|
19201
19431
|
}
|
|
19202
19432
|
function resolvePackageRecord4(input) {
|
|
19203
|
-
const descriptor = `${input.name}@${input.range}`;
|
|
19204
19433
|
const reference = resolveNpmDependencyReference(input.name, input.range);
|
|
19205
19434
|
const candidates = input.nameIndex.get(reference.lookupName) ?? [];
|
|
19206
|
-
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("@"));
|
|
19207
19455
|
}
|
|
19208
19456
|
function walkDependency5(input) {
|
|
19209
19457
|
if (input.seen.has(input.record.key)) {
|
|
@@ -21123,7 +21371,7 @@ var KNOWN_PROJECT_MANIFESTS = [
|
|
|
21123
21371
|
"deno.json",
|
|
21124
21372
|
"deno.jsonc"
|
|
21125
21373
|
];
|
|
21126
|
-
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.";
|
|
21127
21375
|
function discoverProject(options = {}) {
|
|
21128
21376
|
const startDir = path12.resolve(options.cwd ?? process.cwd());
|
|
21129
21377
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ohrisk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.0",
|
|
4
4
|
"description": "Catch open-source license risk before your PR ships.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"node": ">=20.0.0"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
|
-
"ohrisk": "
|
|
12
|
+
"ohrisk": "dist/cli.js"
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|