@dep-radius/core 0.1.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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/analyze/brief.d.ts +18 -0
- package/dist/analyze/brief.d.ts.map +1 -0
- package/dist/analyze/brief.js +89 -0
- package/dist/analyze/verdict.d.ts +16 -0
- package/dist/analyze/verdict.d.ts.map +1 -0
- package/dist/analyze/verdict.js +134 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +36 -0
- package/dist/debug.d.ts +2 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +130 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/infra/cache.d.ts +18 -0
- package/dist/infra/cache.d.ts.map +1 -0
- package/dist/infra/cache.js +66 -0
- package/dist/infra/hash.d.ts +4 -0
- package/dist/infra/hash.d.ts.map +1 -0
- package/dist/infra/hash.js +18 -0
- package/dist/infra/http.d.ts +24 -0
- package/dist/infra/http.d.ts.map +1 -0
- package/dist/infra/http.js +44 -0
- package/dist/infra/limit.d.ts +4 -0
- package/dist/infra/limit.d.ts.map +1 -0
- package/dist/infra/limit.js +34 -0
- package/dist/infra/log.d.ts +8 -0
- package/dist/infra/log.d.ts.map +1 -0
- package/dist/infra/log.js +13 -0
- package/dist/inventory/installed.d.ts +15 -0
- package/dist/inventory/installed.d.ts.map +1 -0
- package/dist/inventory/installed.js +241 -0
- package/dist/inventory/lockfiles/bun.d.ts +3 -0
- package/dist/inventory/lockfiles/bun.d.ts.map +1 -0
- package/dist/inventory/lockfiles/bun.js +24 -0
- package/dist/inventory/lockfiles/npm.d.ts +3 -0
- package/dist/inventory/lockfiles/npm.d.ts.map +1 -0
- package/dist/inventory/lockfiles/npm.js +29 -0
- package/dist/inventory/lockfiles/pnpm.d.ts +3 -0
- package/dist/inventory/lockfiles/pnpm.d.ts.map +1 -0
- package/dist/inventory/lockfiles/pnpm.js +84 -0
- package/dist/inventory/lockfiles/types.d.ts +9 -0
- package/dist/inventory/lockfiles/types.d.ts.map +1 -0
- package/dist/inventory/lockfiles/types.js +1 -0
- package/dist/inventory/lockfiles/yarn.d.ts +3 -0
- package/dist/inventory/lockfiles/yarn.d.ts.map +1 -0
- package/dist/inventory/lockfiles/yarn.js +46 -0
- package/dist/inventory/manifests.d.ts +9 -0
- package/dist/inventory/manifests.d.ts.map +1 -0
- package/dist/inventory/manifests.js +135 -0
- package/dist/inventory/specifiers.d.ts +8 -0
- package/dist/inventory/specifiers.d.ts.map +1 -0
- package/dist/inventory/specifiers.js +53 -0
- package/dist/labels.d.ts +2 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +18 -0
- package/dist/model.d.ts +276 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +2 -0
- package/dist/notes/changelog.d.ts +11 -0
- package/dist/notes/changelog.d.ts.map +1 -0
- package/dist/notes/changelog.js +60 -0
- package/dist/notes/collect.d.ts +13 -0
- package/dist/notes/collect.d.ts.map +1 -0
- package/dist/notes/collect.js +225 -0
- package/dist/notes/entries.d.ts +3 -0
- package/dist/notes/entries.d.ts.map +1 -0
- package/dist/notes/entries.js +209 -0
- package/dist/notes/github.d.ts +25 -0
- package/dist/notes/github.d.ts.map +1 -0
- package/dist/notes/github.js +132 -0
- package/dist/notes/match.d.ts +13 -0
- package/dist/notes/match.d.ts.map +1 -0
- package/dist/notes/match.js +75 -0
- package/dist/notes/repo-url.d.ts +9 -0
- package/dist/notes/repo-url.d.ts.map +1 -0
- package/dist/notes/repo-url.js +46 -0
- package/dist/options.d.ts +53 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +27 -0
- package/dist/registry/candidates.d.ts +24 -0
- package/dist/registry/candidates.d.ts.map +1 -0
- package/dist/registry/candidates.js +139 -0
- package/dist/registry/npmrc.d.ts +13 -0
- package/dist/registry/npmrc.d.ts.map +1 -0
- package/dist/registry/npmrc.js +99 -0
- package/dist/registry/packument.d.ts +34 -0
- package/dist/registry/packument.d.ts.map +1 -0
- package/dist/registry/packument.js +85 -0
- package/dist/registry/tar.d.ts +2 -0
- package/dist/registry/tar.d.ts.map +1 -0
- package/dist/registry/tar.js +75 -0
- package/dist/registry/tarball.d.ts +16 -0
- package/dist/registry/tarball.d.ts.map +1 -0
- package/dist/registry/tarball.js +59 -0
- package/dist/render/groups.d.ts +16 -0
- package/dist/render/groups.d.ts.map +1 -0
- package/dist/render/groups.js +105 -0
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +3 -0
- package/dist/render/json.d.ts +119 -0
- package/dist/render/json.d.ts.map +1 -0
- package/dist/render/json.js +130 -0
- package/dist/render/limits.d.ts +3 -0
- package/dist/render/limits.d.ts.map +1 -0
- package/dist/render/limits.js +25 -0
- package/dist/render/markdown.d.ts +3 -0
- package/dist/render/markdown.d.ts.map +1 -0
- package/dist/render/markdown.js +62 -0
- package/dist/run.d.ts +10 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +218 -0
- package/dist/surface/analyze.d.ts +13 -0
- package/dist/surface/analyze.d.ts.map +1 -0
- package/dist/surface/analyze.js +166 -0
- package/dist/surface/delta.d.ts +5 -0
- package/dist/surface/delta.d.ts.map +1 -0
- package/dist/surface/delta.js +173 -0
- package/dist/surface/entries.d.ts +16 -0
- package/dist/surface/entries.d.ts.map +1 -0
- package/dist/surface/entries.js +187 -0
- package/dist/surface/extract.d.ts +15 -0
- package/dist/surface/extract.d.ts.map +1 -0
- package/dist/surface/extract.js +460 -0
- package/dist/surface/signature.d.ts +6 -0
- package/dist/surface/signature.d.ts.map +1 -0
- package/dist/surface/signature.js +229 -0
- package/dist/surface/vfs-host.d.ts +5 -0
- package/dist/surface/vfs-host.d.ts.map +1 -0
- package/dist/surface/vfs-host.js +54 -0
- package/dist/symbol-path.d.ts +28 -0
- package/dist/symbol-path.d.ts.map +1 -0
- package/dist/symbol-path.js +223 -0
- package/dist/usage/files.d.ts +18 -0
- package/dist/usage/files.d.ts.map +1 -0
- package/dist/usage/files.js +83 -0
- package/dist/usage/link.d.ts +23 -0
- package/dist/usage/link.d.ts.map +1 -0
- package/dist/usage/link.js +588 -0
- package/dist/usage/opaque.d.ts +7 -0
- package/dist/usage/opaque.d.ts.map +1 -0
- package/dist/usage/opaque.js +193 -0
- package/dist/usage/parse.d.ts +73 -0
- package/dist/usage/parse.d.ts.map +1 -0
- package/dist/usage/parse.js +608 -0
- package/dist/usage/resolve-local.d.ts +35 -0
- package/dist/usage/resolve-local.d.ts.map +1 -0
- package/dist/usage/resolve-local.js +340 -0
- package/dist/usage/scan.d.ts +4 -0
- package/dist/usage/scan.d.ts.map +1 -0
- package/dist/usage/scan.js +92 -0
- package/package.json +79 -0
- package/schema/brief-v1.schema.json +319 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function sha1(data) {
|
|
3
|
+
return createHash("sha1").update(data).digest("hex");
|
|
4
|
+
}
|
|
5
|
+
// npm integrity strings are "sha512-<base64>"; the cache is keyed by the hex digest.
|
|
6
|
+
export function integrityHex(integrity) {
|
|
7
|
+
const m = /^sha512-(.+)$/.exec(integrity.split(/\s+/).find((s) => s.startsWith("sha512-")) ?? "");
|
|
8
|
+
return m?.[1] ? Buffer.from(m[1], "base64").toString("hex") : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function verifyIntegrity(bytes, integrity) {
|
|
11
|
+
const hex = integrityHex(integrity);
|
|
12
|
+
if (hex)
|
|
13
|
+
return createHash("sha512").update(bytes).digest("hex") === hex;
|
|
14
|
+
const sha1m = /^sha1-(.+)$/.exec(integrity);
|
|
15
|
+
if (sha1m?.[1])
|
|
16
|
+
return createHash("sha1").update(bytes).digest("base64") === sha1m[1];
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface HttpRequest {
|
|
2
|
+
url: string;
|
|
3
|
+
headers?: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export interface HttpResponse {
|
|
6
|
+
status: number;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
body: Buffer;
|
|
9
|
+
}
|
|
10
|
+
export interface HttpClient {
|
|
11
|
+
get(req: HttpRequest): Promise<HttpResponse>;
|
|
12
|
+
}
|
|
13
|
+
export declare class OfflineError extends Error {
|
|
14
|
+
constructor(url: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class FetchHttp implements HttpClient {
|
|
17
|
+
private readonly retries;
|
|
18
|
+
constructor(retries?: number);
|
|
19
|
+
get(req: HttpRequest): Promise<HttpResponse>;
|
|
20
|
+
}
|
|
21
|
+
export declare class NoNetworkHttp implements HttpClient {
|
|
22
|
+
get(req: HttpRequest): Promise<HttpResponse>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/infra/http.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CAC7C;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,GAAG,EAAE,MAAM;CAGxB;AAED,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBACpB,OAAO,SAAI;IAIjB,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAuBnD;AAED,qBAAa,aAAc,YAAW,UAAU;IACxC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAGnD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class OfflineError extends Error {
|
|
2
|
+
constructor(url) {
|
|
3
|
+
super(`offline: ${url}`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class FetchHttp {
|
|
7
|
+
retries;
|
|
8
|
+
constructor(retries = 2) {
|
|
9
|
+
this.retries = retries;
|
|
10
|
+
}
|
|
11
|
+
async get(req) {
|
|
12
|
+
let lastError;
|
|
13
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(req.url, {
|
|
16
|
+
headers: req.headers,
|
|
17
|
+
redirect: "follow",
|
|
18
|
+
});
|
|
19
|
+
const body = Buffer.from(await res.arrayBuffer());
|
|
20
|
+
const headers = {};
|
|
21
|
+
res.headers.forEach((v, k) => (headers[k.toLowerCase()] = v));
|
|
22
|
+
if (res.status >= 500 && attempt < this.retries) {
|
|
23
|
+
await delay(250 * 2 ** attempt);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
return { status: res.status, headers, body };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
lastError = error;
|
|
30
|
+
if (attempt < this.retries)
|
|
31
|
+
await delay(250 * 2 ** attempt);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw lastError;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class NoNetworkHttp {
|
|
38
|
+
async get(req) {
|
|
39
|
+
throw new OfflineError(req.url);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function delay(ms) {
|
|
43
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limit.d.ts","sourceRoot":"","sources":["../../src/infra/limit.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAE7D,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAqB1D;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAU7E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function createLimiter(concurrency) {
|
|
2
|
+
let active = 0;
|
|
3
|
+
const queue = [];
|
|
4
|
+
const next = () => {
|
|
5
|
+
if (active >= concurrency)
|
|
6
|
+
return;
|
|
7
|
+
const run = queue.shift();
|
|
8
|
+
if (run)
|
|
9
|
+
run();
|
|
10
|
+
};
|
|
11
|
+
return (fn) => new Promise((resolve, reject) => {
|
|
12
|
+
queue.push(() => {
|
|
13
|
+
active++;
|
|
14
|
+
fn()
|
|
15
|
+
.then(resolve, reject)
|
|
16
|
+
.finally(() => {
|
|
17
|
+
active--;
|
|
18
|
+
next();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
next();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function once(fn) {
|
|
25
|
+
const memo = new Map();
|
|
26
|
+
return (key) => {
|
|
27
|
+
let p = memo.get(key);
|
|
28
|
+
if (!p) {
|
|
29
|
+
p = fn(key);
|
|
30
|
+
memo.set(key, p);
|
|
31
|
+
}
|
|
32
|
+
return p;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/infra/log.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAUrD;AAED,eAAO,MAAM,YAAY,EAAE,MAAkD,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createLogger(verbose) {
|
|
2
|
+
return {
|
|
3
|
+
verbose,
|
|
4
|
+
debug(msg) {
|
|
5
|
+
if (verbose)
|
|
6
|
+
process.stderr.write(`radius: ${msg}\n`);
|
|
7
|
+
},
|
|
8
|
+
warn(msg) {
|
|
9
|
+
process.stderr.write(`radius: ${msg}\n`);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export const silentLogger = { verbose: false, debug() { }, warn() { } };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Inventory } from "../model.ts";
|
|
2
|
+
export interface InventoryOptions {
|
|
3
|
+
prod: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ResolvedInstall {
|
|
6
|
+
dir: string;
|
|
7
|
+
realDir: string;
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
local: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveInstalled(fromDir: string, key: string, stopAt: string): Promise<ResolvedInstall | undefined>;
|
|
13
|
+
export declare function projectBoundary(root: string): string;
|
|
14
|
+
export declare function buildInventory(root: string, opts: InventoryOptions, files?: string[]): Promise<Inventory>;
|
|
15
|
+
//# sourceMappingURL=installed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installed.d.ts","sourceRoot":"","sources":["../../src/inventory/installed.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAGV,SAAS,EAGV,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACf;AAGD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CA6BtC;AA8DD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQpD;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,EACtB,KAAK,CAAC,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,SAAS,CAAC,CAmFpB"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, sep } from "node:path";
|
|
3
|
+
import { readdir, readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
import { bunLock } from "./lockfiles/bun.js";
|
|
6
|
+
import { npmLock } from "./lockfiles/npm.js";
|
|
7
|
+
import { pnpmLock } from "./lockfiles/pnpm.js";
|
|
8
|
+
import { yarnLock } from "./lockfiles/yarn.js";
|
|
9
|
+
import { findManifests, listProjectFiles, relPath } from "./manifests.js";
|
|
10
|
+
// Walks up node_modules from `fromDir` exactly as Node does, stopping at the project boundary.
|
|
11
|
+
export async function resolveInstalled(fromDir, key, stopAt) {
|
|
12
|
+
let dir = fromDir;
|
|
13
|
+
for (;;) {
|
|
14
|
+
const candidate = join(dir, "node_modules", key);
|
|
15
|
+
const pj = join(candidate, "package.json");
|
|
16
|
+
if (existsSync(pj)) {
|
|
17
|
+
try {
|
|
18
|
+
const json = JSON.parse(await readFile(pj, "utf8"));
|
|
19
|
+
const realDir = await realpath(candidate);
|
|
20
|
+
const local = !realDir.split(sep).includes("node_modules");
|
|
21
|
+
return {
|
|
22
|
+
dir: candidate,
|
|
23
|
+
realDir,
|
|
24
|
+
name: json.name ?? key,
|
|
25
|
+
version: json.version ?? "0.0.0",
|
|
26
|
+
local,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (dir === stopAt)
|
|
34
|
+
return undefined;
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir)
|
|
37
|
+
return undefined;
|
|
38
|
+
dir = parent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function loadLock(root) {
|
|
42
|
+
const tries = [
|
|
43
|
+
["pnpm-lock.yaml", pnpmLock],
|
|
44
|
+
["package-lock.json", npmLock],
|
|
45
|
+
["npm-shrinkwrap.json", npmLock],
|
|
46
|
+
["yarn.lock", yarnLock],
|
|
47
|
+
["bun.lock", bunLock],
|
|
48
|
+
];
|
|
49
|
+
for (const [file, reader] of tries) {
|
|
50
|
+
const p = join(root, file);
|
|
51
|
+
if (!existsSync(p))
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const r = reader(await readFile(p, "utf8"));
|
|
55
|
+
if (r)
|
|
56
|
+
return r;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// unreadable lockfile: fall through to the next source
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
async function patchedNames(root) {
|
|
65
|
+
const names = new Set();
|
|
66
|
+
try {
|
|
67
|
+
for (const f of await readdir(join(root, "patches"))) {
|
|
68
|
+
// patch-package: name+1.2.3.patch, @scope+name+1.2.3.patch ; pnpm: name@1.2.3.patch, @scope__name@1.2.3.patch
|
|
69
|
+
const pp = /^(.+)\+\d[^+]*\.patch$/.exec(f);
|
|
70
|
+
if (pp?.[1])
|
|
71
|
+
names.add(pp[1].replace(/^(@[^+]+)\+/, "$1/"));
|
|
72
|
+
const pn = /^(.+)@\d.*\.patch$/.exec(f);
|
|
73
|
+
if (pn?.[1])
|
|
74
|
+
names.add(pn[1].replace(/^(@[^_]+)__/, "$1/"));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// no patches folder
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const pj = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
82
|
+
for (const k of Object.keys(pj.pnpm?.patchedDependencies ?? {}))
|
|
83
|
+
names.add(k.replace(/@[^@]*$/, "") || k);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// no root manifest
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const ws = await readFile(join(root, "pnpm-workspace.yaml"), "utf8");
|
|
90
|
+
const block = /^patchedDependencies:\s*\n((?:[ \t]+.*\n?)*)/m.exec(ws);
|
|
91
|
+
for (const line of block?.[1]?.split("\n") ?? []) {
|
|
92
|
+
const m = /^\s+['"]?(@?[^@'":\s]+)(?:@[^'":]*)?['"]?:/.exec(line);
|
|
93
|
+
if (m?.[1])
|
|
94
|
+
names.add(m[1]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// no workspace file
|
|
99
|
+
}
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
// Node walks to the filesystem root; the walk stops at the enclosing git root instead, so a
|
|
103
|
+
// stray ~/node_modules never answers for a project.
|
|
104
|
+
export function projectBoundary(root) {
|
|
105
|
+
let dir = root;
|
|
106
|
+
for (;;) {
|
|
107
|
+
if (existsSync(join(dir, ".git")))
|
|
108
|
+
return dir;
|
|
109
|
+
const parent = dirname(dir);
|
|
110
|
+
if (parent === dir)
|
|
111
|
+
return dir;
|
|
112
|
+
dir = parent;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function buildInventory(root, opts, files) {
|
|
116
|
+
const projectFiles = files ?? (await listProjectFiles(root));
|
|
117
|
+
const manifests = await findManifests(root, projectFiles);
|
|
118
|
+
const lock = await loadLock(root);
|
|
119
|
+
const patched = await patchedNames(root);
|
|
120
|
+
const hasPnp = existsSync(join(root, ".pnp.cjs")) || existsSync(join(root, ".pnp.js"));
|
|
121
|
+
const byId = new Map();
|
|
122
|
+
const local = new Set();
|
|
123
|
+
const notAnalyzed = new Map();
|
|
124
|
+
const workspaceNames = new Set(manifests.map((m) => m.name).filter((n) => !!n));
|
|
125
|
+
for (const m of manifests) {
|
|
126
|
+
for (const d of m.deps) {
|
|
127
|
+
if (d.field === "peerDependencies" &&
|
|
128
|
+
!m.deps.some((o) => o.key === d.key && o.field !== "peerDependencies"))
|
|
129
|
+
continue;
|
|
130
|
+
if (opts.prod && d.field === "devDependencies")
|
|
131
|
+
continue;
|
|
132
|
+
if (d.specKind === "workspace" ||
|
|
133
|
+
d.specKind === "link" ||
|
|
134
|
+
d.specKind === "file") {
|
|
135
|
+
local.add(d.key);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (d.specKind === "git" || d.specKind === "url") {
|
|
139
|
+
notAnalyzed.set(d.key, {
|
|
140
|
+
pkg: d.key,
|
|
141
|
+
reason: `installed from ${d.specKind} (${d.spec}), not from a registry`,
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const found = await resolveDep(root, m, d, lock, hasPnp);
|
|
146
|
+
if (!found) {
|
|
147
|
+
if (workspaceNames.has(d.key))
|
|
148
|
+
local.add(d.key);
|
|
149
|
+
else
|
|
150
|
+
notAnalyzed.set(d.key, {
|
|
151
|
+
pkg: d.key,
|
|
152
|
+
reason: d.specKind === "catalog"
|
|
153
|
+
? "not installed and declared through catalog:, so the version is unknown"
|
|
154
|
+
: "not installed, not in a lockfile, and the range gives no version",
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (found.local) {
|
|
159
|
+
local.add(d.key);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let dep = byId.get(found.id);
|
|
163
|
+
if (!dep) {
|
|
164
|
+
dep = found;
|
|
165
|
+
byId.set(found.id, dep);
|
|
166
|
+
}
|
|
167
|
+
dep.declaredBy.push({
|
|
168
|
+
manifest: relPath(root, m.path),
|
|
169
|
+
key: d.key,
|
|
170
|
+
field: d.field,
|
|
171
|
+
});
|
|
172
|
+
if (patched.has(dep.name) && !dep.flags.includes("patched"))
|
|
173
|
+
dep.flags.push("patched");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const dep of byId.values())
|
|
177
|
+
notAnalyzed.delete(dep.name);
|
|
178
|
+
return {
|
|
179
|
+
root,
|
|
180
|
+
manifests,
|
|
181
|
+
installed: [...byId.values()].sort((a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version)),
|
|
182
|
+
local: [...local].sort(),
|
|
183
|
+
notAnalyzed: [...notAnalyzed.values()].sort((a, b) => a.pkg.localeCompare(b.pkg)),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function resolveDep(root, m, d, lock, hasPnp) {
|
|
187
|
+
if (!hasPnp) {
|
|
188
|
+
const r = await resolveInstalled(m.dir, d.key, projectBoundary(root));
|
|
189
|
+
if (r) {
|
|
190
|
+
const flags = [];
|
|
191
|
+
if (r.name !== d.key)
|
|
192
|
+
flags.push("alias");
|
|
193
|
+
if (semver.prerelease(r.version))
|
|
194
|
+
flags.push("prerelease-installed");
|
|
195
|
+
return {
|
|
196
|
+
id: r.realDir,
|
|
197
|
+
name: r.name,
|
|
198
|
+
version: r.version,
|
|
199
|
+
versionSource: "node_modules",
|
|
200
|
+
dir: r.realDir,
|
|
201
|
+
local: r.local,
|
|
202
|
+
declaredBy: [],
|
|
203
|
+
flags,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (lock) {
|
|
208
|
+
const hit = lock.lookup(relPath(root, m.dir), d.key, d.spec);
|
|
209
|
+
if (hit) {
|
|
210
|
+
const flags = [];
|
|
211
|
+
if (hit.name !== d.key)
|
|
212
|
+
flags.push("alias");
|
|
213
|
+
return {
|
|
214
|
+
id: `${lock.source}:${hit.name}@${hit.version}`,
|
|
215
|
+
name: hit.name,
|
|
216
|
+
version: hit.version,
|
|
217
|
+
versionSource: lock.source,
|
|
218
|
+
local: false,
|
|
219
|
+
declaredBy: [],
|
|
220
|
+
flags,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const range = d.specKind === "alias" ? d.spec.replace(/^npm:.*@/, "") : d.spec;
|
|
225
|
+
if (d.specKind === "range" || d.specKind === "alias") {
|
|
226
|
+
const min = semver.validRange(range) ? semver.minVersion(range) : null;
|
|
227
|
+
if (min) {
|
|
228
|
+
const name = d.aliasOf ?? d.key;
|
|
229
|
+
return {
|
|
230
|
+
id: `manifest-range:${name}@${min.version}`,
|
|
231
|
+
name,
|
|
232
|
+
version: min.version,
|
|
233
|
+
versionSource: "manifest-range",
|
|
234
|
+
local: false,
|
|
235
|
+
declaredBy: [],
|
|
236
|
+
flags: ["range-guess", ...(d.aliasOf ? ["alias"] : [])],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../../src/inventory/lockfiles/bun.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAoB5D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
// bun.lock is JSONC: packages["<key>"] or packages["<parent>/<key>"] = ["name@version", ...].
|
|
3
|
+
export function bunLock(text) {
|
|
4
|
+
const parsed = ts.parseConfigFileTextToJson("bun.lock", text);
|
|
5
|
+
const packages = parsed.config?.packages;
|
|
6
|
+
if (!packages)
|
|
7
|
+
return undefined;
|
|
8
|
+
return {
|
|
9
|
+
source: "lockfile:bun",
|
|
10
|
+
lookup(_dir, key) {
|
|
11
|
+
const entry = packages[key];
|
|
12
|
+
const ident = Array.isArray(entry) ? entry[0] : undefined;
|
|
13
|
+
if (typeof ident !== "string")
|
|
14
|
+
return undefined;
|
|
15
|
+
const at = ident.lastIndexOf("@");
|
|
16
|
+
if (at <= 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
const version = ident.slice(at + 1);
|
|
19
|
+
return /^\d/.test(version)
|
|
20
|
+
? { version, name: ident.slice(0, at) }
|
|
21
|
+
: undefined;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../../src/inventory/lockfiles/npm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CA6B5D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// package-lock.json v2/v3 and npm-shrinkwrap.json: packages["<dir>/node_modules/<key>"].
|
|
2
|
+
export function npmLock(text) {
|
|
3
|
+
let json;
|
|
4
|
+
try {
|
|
5
|
+
json = JSON.parse(text);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const packages = json.packages;
|
|
11
|
+
if (!packages)
|
|
12
|
+
return undefined;
|
|
13
|
+
return {
|
|
14
|
+
source: "lockfile:npm",
|
|
15
|
+
lookup(manifestDir, key) {
|
|
16
|
+
let dir = manifestDir === "." ? "" : manifestDir;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const k = dir ? `${dir}/node_modules/${key}` : `node_modules/${key}`;
|
|
19
|
+
const entry = packages[k];
|
|
20
|
+
if (entry?.version && !entry.link)
|
|
21
|
+
return { version: entry.version, name: entry.name ?? key };
|
|
22
|
+
if (!dir)
|
|
23
|
+
return undefined;
|
|
24
|
+
const i = dir.lastIndexOf("/");
|
|
25
|
+
dir = i < 0 ? "" : dir.slice(0, i);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pnpm.d.ts","sourceRoot":"","sources":["../../../src/inventory/lockfiles/pnpm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAS5C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAkE7D"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// pnpm-lock.yaml v6 to v9, read by indentation rather than with a YAML parser:
|
|
2
|
+
// importers:
|
|
3
|
+
// packages/app:
|
|
4
|
+
// dependencies:
|
|
5
|
+
// left-pad:
|
|
6
|
+
// specifier: ^1.3.0
|
|
7
|
+
// version: 1.3.0
|
|
8
|
+
export function pnpmLock(text) {
|
|
9
|
+
const importers = new Map();
|
|
10
|
+
const lines = text.split(/\r?\n/);
|
|
11
|
+
let inImporters = false;
|
|
12
|
+
let importer;
|
|
13
|
+
let inDeps = false;
|
|
14
|
+
let depKey;
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (!line.trim() || line.trimStart().startsWith("#"))
|
|
17
|
+
continue;
|
|
18
|
+
const indent = line.length - line.trimStart().length;
|
|
19
|
+
const content = line.trim();
|
|
20
|
+
if (indent === 0) {
|
|
21
|
+
inImporters = content === "importers:";
|
|
22
|
+
importer = undefined;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!inImporters)
|
|
26
|
+
continue;
|
|
27
|
+
if (indent === 2) {
|
|
28
|
+
const name = unquote(content.replace(/:$/, ""));
|
|
29
|
+
importer = new Map();
|
|
30
|
+
importers.set(name, importer);
|
|
31
|
+
inDeps = false;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (indent === 4) {
|
|
35
|
+
inDeps = /^(dependencies|devDependencies|optionalDependencies):$/.test(content);
|
|
36
|
+
depKey = undefined;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!importer || !inDeps)
|
|
40
|
+
continue;
|
|
41
|
+
if (indent === 6) {
|
|
42
|
+
// v5 inline form "left-pad: 1.3.0", or v6+ block form "left-pad:"
|
|
43
|
+
const m = /^(.+?):\s*(.*)$/.exec(content);
|
|
44
|
+
if (!m?.[1])
|
|
45
|
+
continue;
|
|
46
|
+
depKey = unquote(m[1]);
|
|
47
|
+
if (m[2])
|
|
48
|
+
importer.set(depKey, m[2]);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (indent === 8 && depKey) {
|
|
52
|
+
const m = /^version:\s*(.+)$/.exec(content);
|
|
53
|
+
if (m?.[1])
|
|
54
|
+
importer.set(depKey, unquote(m[1]));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (importers.size === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
return {
|
|
60
|
+
source: "lockfile:pnpm",
|
|
61
|
+
lookup(manifestDir, key) {
|
|
62
|
+
const raw = importers.get(manifestDir)?.get(key);
|
|
63
|
+
if (!raw || raw.startsWith("link:") || raw.startsWith("file:"))
|
|
64
|
+
return undefined;
|
|
65
|
+
// "4.5.4(typescript@6.0.3)" -> 4.5.4 ; "/left-pad/1.3.0" (v5) -> 1.3.0 ; "npm:x@1.0.0" style handled below
|
|
66
|
+
let v = raw.replace(/\(.*$/, "");
|
|
67
|
+
let name = key;
|
|
68
|
+
const alias = /^(@?[^@]+)@(\d.*)$/.exec(v);
|
|
69
|
+
if (alias?.[1] && alias[2]) {
|
|
70
|
+
name = alias[1];
|
|
71
|
+
v = alias[2];
|
|
72
|
+
}
|
|
73
|
+
v = v.replace(/^\/.*\//, "");
|
|
74
|
+
return /^\d/.test(v) ? { version: v, name } : undefined;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function unquote(s) {
|
|
79
|
+
const t = s.trim();
|
|
80
|
+
return (t.startsWith("'") && t.endsWith("'")) ||
|
|
81
|
+
(t.startsWith('"') && t.endsWith('"'))
|
|
82
|
+
? t.slice(1, -1)
|
|
83
|
+
: t;
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/inventory/lockfiles/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,aAAa,CAAA;IAErB,MAAM,CACJ,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;CACjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yarn.d.ts","sourceRoot":"","sources":["../../../src/inventory/lockfiles/yarn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAmC7D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// yarn.lock v1 ("left-pad@^1.3.0", left-pad@^1.0.0:\n version "1.3.0") and berry ("left-pad@npm:^1.3.0":\n version: 1.3.0).
|
|
2
|
+
export function yarnLock(text) {
|
|
3
|
+
const byDescriptor = new Map();
|
|
4
|
+
let current = [];
|
|
5
|
+
for (const line of text.split(/\r?\n/)) {
|
|
6
|
+
if (!line.trim() || line.startsWith("#"))
|
|
7
|
+
continue;
|
|
8
|
+
if (!line.startsWith(" ") && line.endsWith(":")) {
|
|
9
|
+
current = line
|
|
10
|
+
.slice(0, -1)
|
|
11
|
+
.split(/,\s*/)
|
|
12
|
+
.map((d) => d.trim().replace(/^"|"$/g, ""));
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const m = /^\s+version:?\s+"?([^"\s]+)"?\s*$/.exec(line);
|
|
16
|
+
if (m?.[1] && current.length > 0) {
|
|
17
|
+
for (const d of current)
|
|
18
|
+
byDescriptor.set(d, m[1]);
|
|
19
|
+
current = [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (byDescriptor.size === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
return {
|
|
25
|
+
source: "lockfile:yarn",
|
|
26
|
+
lookup(_dir, key, spec) {
|
|
27
|
+
const candidates = [`${key}@${spec}`, `${key}@npm:${spec}`];
|
|
28
|
+
if (spec.startsWith("npm:"))
|
|
29
|
+
candidates.push(`${key}@${spec}`);
|
|
30
|
+
for (const c of candidates) {
|
|
31
|
+
const v = byDescriptor.get(c);
|
|
32
|
+
if (v)
|
|
33
|
+
return {
|
|
34
|
+
version: v,
|
|
35
|
+
name: spec.startsWith("npm:") ? (aliasName(spec) ?? key) : key,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function aliasName(spec) {
|
|
43
|
+
const rest = spec.slice(4);
|
|
44
|
+
const at = rest.lastIndexOf("@");
|
|
45
|
+
return at > 0 ? rest.slice(0, at) : undefined;
|
|
46
|
+
}
|