@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 prakticode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @dep-radius/core
|
|
2
|
+
|
|
3
|
+
The engine behind [`dep-radius`](../cli/README.md): which changes in a dependency update land on
|
|
4
|
+
code you actually wrote. Use it to build your own integration; for the command line, install
|
|
5
|
+
`dep-radius`.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createCtx, defaultCacheDir, renderJson, run, type Options } from "@dep-radius/core"
|
|
9
|
+
|
|
10
|
+
const options: Options = {
|
|
11
|
+
root: process.cwd(),
|
|
12
|
+
specs: [],
|
|
13
|
+
format: "json",
|
|
14
|
+
offline: false,
|
|
15
|
+
minAgeMs: undefined,
|
|
16
|
+
latest: false,
|
|
17
|
+
notes: true,
|
|
18
|
+
surface: true,
|
|
19
|
+
prod: false,
|
|
20
|
+
concurrency: 8,
|
|
21
|
+
cacheDir: defaultCacheDir(process.env),
|
|
22
|
+
verbose: false,
|
|
23
|
+
now: Date.now(),
|
|
24
|
+
color: false,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const brief = await run(options, createCtx(options), (event) => {
|
|
28
|
+
if (event.type === "package") console.error(`${event.done}/${event.total}`)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
process.stdout.write(renderJson(brief))
|
|
32
|
+
process.exitCode = brief.exitCode
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Entry points
|
|
36
|
+
|
|
37
|
+
| Import | Contains |
|
|
38
|
+
| ---------------------------------------------- | ------------------------------------------------------------------- |
|
|
39
|
+
| `@dep-radius/core` | `run`, `createCtx`, the brief types, `renderJson`, `renderMarkdown` |
|
|
40
|
+
| `@dep-radius/core/render` | Grouping and labels, to write another renderer |
|
|
41
|
+
| `@dep-radius/core/schema/brief-v1.schema.json` | The JSON Schema of `renderJson`'s output |
|
|
42
|
+
|
|
43
|
+
The JSON output is a stable contract (`schemaVersion: 1`). The TypeScript types of the internal
|
|
44
|
+
brief may still change before 1.0.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MatchResult } from "../notes/match.ts";
|
|
2
|
+
import type { CollectedNotes } from "../notes/collect.ts";
|
|
3
|
+
import type { Candidate, InstalledDep, PackageBrief, PackageUsage, Site } from "../model.ts";
|
|
4
|
+
export interface BriefParts {
|
|
5
|
+
dep: InstalledDep;
|
|
6
|
+
candidate: Candidate;
|
|
7
|
+
usage: PackageUsage | undefined;
|
|
8
|
+
surface: PackageBrief["surface"] & {
|
|
9
|
+
truncatedOnUsedEntry?: boolean;
|
|
10
|
+
};
|
|
11
|
+
notes: CollectedNotes | undefined;
|
|
12
|
+
match: MatchResult | undefined;
|
|
13
|
+
notesDisabled: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildPackageBrief(parts: BriefParts): PackageBrief;
|
|
16
|
+
export declare function sitesByName(usage: PackageUsage | undefined): Record<string, Site[]>;
|
|
17
|
+
export declare function uniqueSites(sites: Site[]): Site[];
|
|
18
|
+
//# sourceMappingURL=brief.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../src/analyze/brief.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,IAAI,EACL,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,YAAY,CAAA;IACjB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IAC/B,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG;QAAE,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IACrE,KAAK,EAAE,cAAc,GAAG,SAAS,CAAA;IACjC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CA6DjE;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,GAAG,SAAS,GAC9B,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAcxB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAUjD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
import { decide } from "./verdict.js";
|
|
3
|
+
import { level0Names } from "../symbol-path.js";
|
|
4
|
+
export function buildPackageBrief(parts) {
|
|
5
|
+
const { dep, candidate, usage, surface } = parts;
|
|
6
|
+
const notes = parts.notesDisabled
|
|
7
|
+
? {
|
|
8
|
+
coverage: "disabled",
|
|
9
|
+
perVersion: [],
|
|
10
|
+
total: 0,
|
|
11
|
+
matched: [],
|
|
12
|
+
unattributedBreaking: [],
|
|
13
|
+
}
|
|
14
|
+
: {
|
|
15
|
+
coverage: parts.notes?.coverage ?? "unavailable",
|
|
16
|
+
perVersion: parts.notes?.perVersion ?? [],
|
|
17
|
+
total: parts.match?.total ?? 0,
|
|
18
|
+
matched: parts.match?.matched ?? [],
|
|
19
|
+
unattributedBreaking: parts.match?.unattributedBreaking ?? [],
|
|
20
|
+
};
|
|
21
|
+
const from = semver.parse(candidate.from);
|
|
22
|
+
const to = semver.parse(candidate.to);
|
|
23
|
+
const zeroMajor = !!from &&
|
|
24
|
+
!!to &&
|
|
25
|
+
from.major === 0 &&
|
|
26
|
+
to.major === 0 &&
|
|
27
|
+
candidate.bump !== "patch";
|
|
28
|
+
const { verdict, reasons } = decide({
|
|
29
|
+
bump: candidate.bump,
|
|
30
|
+
zeroMajor: zeroMajor && candidate.bump === "major",
|
|
31
|
+
flags: dep.flags,
|
|
32
|
+
usage,
|
|
33
|
+
surface,
|
|
34
|
+
notes,
|
|
35
|
+
});
|
|
36
|
+
const { truncatedOnUsedEntry: _t, ...surfaceOut } = surface;
|
|
37
|
+
return {
|
|
38
|
+
pkg: dep.name,
|
|
39
|
+
from: candidate.from,
|
|
40
|
+
to: candidate.to,
|
|
41
|
+
bump: candidate.bump,
|
|
42
|
+
publishedAt: candidate.publishedAt,
|
|
43
|
+
versionSource: dep.versionSource,
|
|
44
|
+
manifests: [...new Set(dep.declaredBy.map((d) => d.manifest))].sort(),
|
|
45
|
+
level: surface.status === "computed" ? 1 : 0,
|
|
46
|
+
verdict,
|
|
47
|
+
reasons,
|
|
48
|
+
surface: surfaceOut,
|
|
49
|
+
notes,
|
|
50
|
+
usage: {
|
|
51
|
+
files: usage?.files ?? 0,
|
|
52
|
+
sites: uniqueSites(usage?.refs.map((r) => r.site) ?? []),
|
|
53
|
+
byName: sitesByName(usage),
|
|
54
|
+
strongNames: usage?.strongNames ?? [],
|
|
55
|
+
weakNames: usage?.weakNames ?? [],
|
|
56
|
+
opaque: usage?.opaque ?? [],
|
|
57
|
+
blindSpots: usage?.blindSpots ?? [],
|
|
58
|
+
},
|
|
59
|
+
skippedNewer: candidate.skippedNewer,
|
|
60
|
+
...(candidate.alsoAvailable
|
|
61
|
+
? { alsoAvailable: candidate.alsoAvailable }
|
|
62
|
+
: {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function sitesByName(usage) {
|
|
66
|
+
const map = new Map();
|
|
67
|
+
for (const r of usage?.refs ?? []) {
|
|
68
|
+
const n = level0Names(r);
|
|
69
|
+
for (const name of [...n.strong, ...n.weak]) {
|
|
70
|
+
const list = map.get(name) ?? [];
|
|
71
|
+
list.push(r.site);
|
|
72
|
+
map.set(name, list);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const name of [...map.keys()].sort())
|
|
77
|
+
out[name] = uniqueSites(map.get(name));
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
export function uniqueSites(sites) {
|
|
81
|
+
const seen = new Map();
|
|
82
|
+
for (const s of sites) {
|
|
83
|
+
const k = `${s.file}:${s.line}`;
|
|
84
|
+
const prev = seen.get(k);
|
|
85
|
+
if (!prev || (prev.typeOnly && !s.typeOnly))
|
|
86
|
+
seen.set(k, s);
|
|
87
|
+
}
|
|
88
|
+
return [...seen.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
89
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InstalledDep, PackageBrief, PackageUsage, Verdict, VerdictReason } from "../model.ts";
|
|
2
|
+
export interface VerdictInput {
|
|
3
|
+
bump: PackageBrief["bump"];
|
|
4
|
+
zeroMajor: boolean;
|
|
5
|
+
flags: InstalledDep["flags"];
|
|
6
|
+
usage: PackageUsage | undefined;
|
|
7
|
+
surface: PackageBrief["surface"] & {
|
|
8
|
+
truncatedOnUsedEntry?: boolean;
|
|
9
|
+
};
|
|
10
|
+
notes: PackageBrief["notes"];
|
|
11
|
+
}
|
|
12
|
+
export declare function decide(input: VerdictInput): {
|
|
13
|
+
verdict: Verdict;
|
|
14
|
+
reasons: VerdictReason[];
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=verdict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../../src/analyze/verdict.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,aAAa,EACd,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;IAC5B,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IAC/B,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG;QAAE,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IACrE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;CAC7B;AAID,wBAAgB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB,CA8IA"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { UNSEEN_LABEL } from "../labels.js";
|
|
2
|
+
// Evaluated in order, every rule that fires is kept as a reason. The one outcome that must never
|
|
3
|
+
// happen is a wrong "quiet", so anything the tool cannot see pushes towards review.
|
|
4
|
+
export function decide(input) {
|
|
5
|
+
const reasons = [];
|
|
6
|
+
const { usage, surface, notes } = input;
|
|
7
|
+
const strongRemoved = surface.touched.filter((t) => t.bucket === "removed" && t.strength === "strong");
|
|
8
|
+
if (surface.status === "computed" && strongRemoved.length > 0) {
|
|
9
|
+
reasons.push({
|
|
10
|
+
code: "removed-touched",
|
|
11
|
+
detail: `${strongRemoved.length} removed ${plural(strongRemoved.length, "export")} you use: ${names(strongRemoved.map((t) => t.change.path))}`,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const changed = surface.touched.filter((t) => t.bucket === "changed" && t.strength === "strong");
|
|
15
|
+
if (changed.length > 0)
|
|
16
|
+
reasons.push({
|
|
17
|
+
code: "changed-touched",
|
|
18
|
+
detail: `${changed.length} changed ${plural(changed.length, "signature")} you use: ${names(changed.map((t) => t.change.path))}`,
|
|
19
|
+
});
|
|
20
|
+
const deprecated = surface.touched.filter((t) => t.bucket === "deprecated" && t.strength === "strong");
|
|
21
|
+
if (deprecated.length > 0)
|
|
22
|
+
reasons.push({
|
|
23
|
+
code: "deprecated-touched",
|
|
24
|
+
detail: `${deprecated.length} newly deprecated: ${names(deprecated.map((t) => t.change.path))}`,
|
|
25
|
+
});
|
|
26
|
+
const weak = surface.touched.filter((t) => t.strength === "weak");
|
|
27
|
+
if (weak.length > 0)
|
|
28
|
+
reasons.push({
|
|
29
|
+
code: "possibly-touched",
|
|
30
|
+
detail: `${weak.length} changed ${plural(weak.length, "member")} you may reach by name: ${names(weak.map((t) => t.change.path))}`,
|
|
31
|
+
});
|
|
32
|
+
if (notes.matched.length > 0) {
|
|
33
|
+
const direct = notes.matched.filter((m) => m.direct).length;
|
|
34
|
+
reasons.push({
|
|
35
|
+
code: "notes-match",
|
|
36
|
+
detail: `${notes.matched.length} release ${plural(notes.matched.length, "note")} ${notes.matched.length === 1 ? "mentions" : "mention"} names you use (${direct} by name in the text)`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (notes.unattributedBreaking.length > 0) {
|
|
40
|
+
reasons.push({
|
|
41
|
+
code: "unattributed-breaking",
|
|
42
|
+
detail: `${notes.unattributedBreaking.length} breaking ${plural(notes.unattributedBreaking.length, "note")} naming no API, which can apply to anyone`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const blind = usage?.blindSpots ?? [];
|
|
46
|
+
if (blind.length > 0)
|
|
47
|
+
reasons.push({
|
|
48
|
+
code: "blind-spots",
|
|
49
|
+
detail: `usage not fully visible: ${blind.map((b) => `${b.count} ${UNSEEN_LABEL[b.kind] ?? b.kind}`).join(", ")}`,
|
|
50
|
+
});
|
|
51
|
+
const opaque = usage?.opaque ?? [];
|
|
52
|
+
if (opaque.length > 0)
|
|
53
|
+
reasons.push({
|
|
54
|
+
code: "opaque-usage",
|
|
55
|
+
detail: `used in ways names cannot show: ${opaque.map((o) => UNSEEN_LABEL[o.kind] ?? o.kind).join(", ")}`,
|
|
56
|
+
});
|
|
57
|
+
if (!usage ||
|
|
58
|
+
(usage.refs.length === 0 && opaque.length === 0 && blind.length === 0)) {
|
|
59
|
+
reasons.push({
|
|
60
|
+
code: "not-referenced",
|
|
61
|
+
detail: "not referenced by any scanned file; used by a tool, loaded by name, or unused",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const flagged = input.flags.filter((f) => f === "not-in-registry" || f === "patched" || f === "range-guess");
|
|
65
|
+
if (flagged.length > 0)
|
|
66
|
+
reasons.push({
|
|
67
|
+
code: "flagged-install",
|
|
68
|
+
detail: `the installed copy is ${flagged.join(", ")}`,
|
|
69
|
+
});
|
|
70
|
+
// a wrapper's shape lives in the package it wraps: no types of its own is not the same as no shape
|
|
71
|
+
if (surface.status === "failed" ||
|
|
72
|
+
surface.status === "offline-uncached" ||
|
|
73
|
+
surface.status === "types-elsewhere" ||
|
|
74
|
+
surface.truncatedOnUsedEntry) {
|
|
75
|
+
reasons.push({
|
|
76
|
+
code: "surface-incomplete",
|
|
77
|
+
detail: surface.detail ?? `type surface ${surface.status}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const notesComplete = notes.coverage === "complete";
|
|
81
|
+
const surfaceComputed = surface.status === "computed";
|
|
82
|
+
if (!notesComplete && !surfaceComputed) {
|
|
83
|
+
reasons.push({
|
|
84
|
+
code: "no-evidence",
|
|
85
|
+
detail: `neither net is complete: notes ${notes.coverage}, types ${surface.status}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const bothClean = notesComplete &&
|
|
89
|
+
surfaceComputed &&
|
|
90
|
+
surface.touched.length === 0 &&
|
|
91
|
+
notes.matched.length === 0 &&
|
|
92
|
+
notes.unattributedBreaking.length === 0;
|
|
93
|
+
if ((input.bump === "major" || input.zeroMajor) && !bothClean) {
|
|
94
|
+
reasons.push({
|
|
95
|
+
code: "major-unproven",
|
|
96
|
+
detail: `a ${input.zeroMajor ? "0.x minor, a major by convention" : "major"} is only quiet when both nets are complete and clean`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
let verdict = "quiet";
|
|
100
|
+
if (reasons.some((r) => r.code === "removed-touched"))
|
|
101
|
+
verdict = "blocked";
|
|
102
|
+
else if (reasons.length > 0)
|
|
103
|
+
verdict = "review";
|
|
104
|
+
else {
|
|
105
|
+
if (notesComplete)
|
|
106
|
+
reasons.push({
|
|
107
|
+
code: "notes-complete-no-match",
|
|
108
|
+
detail: "every release note read, none mentions what you use",
|
|
109
|
+
});
|
|
110
|
+
if (surfaceComputed)
|
|
111
|
+
reasons.push({
|
|
112
|
+
code: "surface-clean",
|
|
113
|
+
detail: "no changed or removed export you use",
|
|
114
|
+
});
|
|
115
|
+
if (!surfaceComputed)
|
|
116
|
+
reasons.push({
|
|
117
|
+
code: "caveat-no-types",
|
|
118
|
+
detail: "no type surface: shape changes were judged from the notes alone",
|
|
119
|
+
});
|
|
120
|
+
if (!notesComplete)
|
|
121
|
+
reasons.push({
|
|
122
|
+
code: "caveat-no-notes",
|
|
123
|
+
detail: "no release notes: behaviour changes are invisible here",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return { verdict, reasons };
|
|
127
|
+
}
|
|
128
|
+
function plural(n, word) {
|
|
129
|
+
return n === 1 ? word : `${word}s`;
|
|
130
|
+
}
|
|
131
|
+
function names(paths) {
|
|
132
|
+
const shown = paths.slice(0, 4).join(", ");
|
|
133
|
+
return paths.length > 4 ? `${shown} and ${paths.length - 4} more` : shown;
|
|
134
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAa,KAAK,UAAU,EAAiB,MAAM,iBAAiB,CAAA;AAI3E,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,GAAG,CAc/D"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { promisify } from "node:util";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { DiskCache } from "./infra/cache.js";
|
|
4
|
+
import { createLogger } from "./infra/log.js";
|
|
5
|
+
import { createLimiter } from "./infra/limit.js";
|
|
6
|
+
import { FetchHttp, NoNetworkHttp } from "./infra/http.js";
|
|
7
|
+
const run = promisify(execFile);
|
|
8
|
+
export function createCtx(opts, http) {
|
|
9
|
+
let token;
|
|
10
|
+
return {
|
|
11
|
+
http: http ?? (opts.offline ? new NoNetworkHttp() : new FetchHttp()),
|
|
12
|
+
cache: new DiskCache(opts.cacheDir),
|
|
13
|
+
now: opts.now,
|
|
14
|
+
offline: opts.offline,
|
|
15
|
+
log: createLogger(opts.verbose),
|
|
16
|
+
net: createLimiter(opts.concurrency),
|
|
17
|
+
github: createLimiter(Math.min(4, opts.concurrency)),
|
|
18
|
+
heavy: createLimiter(2),
|
|
19
|
+
githubToken: () => (token ??= findGithubToken()),
|
|
20
|
+
env: process.env,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Sent to api.github.com only.
|
|
24
|
+
async function findGithubToken() {
|
|
25
|
+
if (process.env.GITHUB_TOKEN)
|
|
26
|
+
return process.env.GITHUB_TOKEN;
|
|
27
|
+
if (process.env.GH_TOKEN)
|
|
28
|
+
return process.env.GH_TOKEN;
|
|
29
|
+
try {
|
|
30
|
+
const { stdout } = await run("gh", ["auth", "token"], { timeout: 2000 });
|
|
31
|
+
return stdout.trim() || undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/debug.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAgBA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAkH9D"}
|
package/dist/debug.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { createCtx } from "./context.js";
|
|
3
|
+
import { scanUsage } from "./usage/scan.js";
|
|
4
|
+
import { collectNotes } from "./notes/collect.js";
|
|
5
|
+
import { diffSurfaces } from "./surface/delta.js";
|
|
6
|
+
import { defaultCacheDir } from "./infra/cache.js";
|
|
7
|
+
import { extractSurface } from "./surface/extract.js";
|
|
8
|
+
import { getPackument } from "./registry/packument.js";
|
|
9
|
+
import { buildInventory } from "./inventory/installed.js";
|
|
10
|
+
import { selectCandidate } from "./registry/candidates.js";
|
|
11
|
+
import { listProjectFiles } from "./inventory/manifests.js";
|
|
12
|
+
import { DEFAULT_MIN_AGE_MS } from "./options.js";
|
|
13
|
+
import { isAgeExcluded, loadRegistryConfig } from "./registry/npmrc.js";
|
|
14
|
+
// Each stage on its own, as JSON, for looking at one step without the rest of the pipeline.
|
|
15
|
+
export async function runDebug(args) {
|
|
16
|
+
const [cmd, ...rest] = args;
|
|
17
|
+
const root = resolve(process.env.RADIUS_ROOT ?? process.cwd());
|
|
18
|
+
const now = process.env.RADIUS_NOW
|
|
19
|
+
? Date.parse(process.env.RADIUS_NOW)
|
|
20
|
+
: Date.now();
|
|
21
|
+
const opts = {
|
|
22
|
+
root,
|
|
23
|
+
specs: [],
|
|
24
|
+
format: "json",
|
|
25
|
+
offline: process.env.RADIUS_OFFLINE === "1",
|
|
26
|
+
minAgeMs: undefined,
|
|
27
|
+
latest: false,
|
|
28
|
+
notes: true,
|
|
29
|
+
surface: true,
|
|
30
|
+
prod: false,
|
|
31
|
+
concurrency: 8,
|
|
32
|
+
cacheDir: process.env.RADIUS_CACHE_DIR ?? defaultCacheDir(process.env),
|
|
33
|
+
verbose: false,
|
|
34
|
+
now,
|
|
35
|
+
color: false,
|
|
36
|
+
};
|
|
37
|
+
const ctx = createCtx(opts);
|
|
38
|
+
const cfg = loadRegistryConfig(root, process.env);
|
|
39
|
+
const print = (v) => process.stdout.write(`${JSON.stringify(v, null, 2)}\n`);
|
|
40
|
+
if (cmd === "inventory") {
|
|
41
|
+
const inv = await buildInventory(root, { prod: false });
|
|
42
|
+
print({ ...inv, manifests: inv.manifests.map((m) => m.path) });
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
if (cmd === "candidates") {
|
|
46
|
+
const inv = await buildInventory(root, { prod: false });
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const d of inv.installed) {
|
|
49
|
+
const p = await getPackument(ctx, cfg, d.name);
|
|
50
|
+
if (!p.ok) {
|
|
51
|
+
out.push({ pkg: d.name, error: p.reason });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
out.push({
|
|
55
|
+
pkg: d.name,
|
|
56
|
+
installed: d.version,
|
|
57
|
+
...selectCandidate(p.packument, d.version, {
|
|
58
|
+
now,
|
|
59
|
+
minAgeMs: cfg.minimumReleaseAgeMs ?? DEFAULT_MIN_AGE_MS,
|
|
60
|
+
ageExcluded: (v) => isAgeExcluded(cfg, d.name, v),
|
|
61
|
+
latest: false,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
print(out);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
if (cmd === "usage" && rest[0]) {
|
|
69
|
+
const files = await listProjectFiles(root);
|
|
70
|
+
const inv = await buildInventory(root, { prod: false }, files);
|
|
71
|
+
const u = await scanUsage(ctx, root, files, inv);
|
|
72
|
+
print({
|
|
73
|
+
scannedFiles: u.scannedFiles,
|
|
74
|
+
global: u.global,
|
|
75
|
+
packages: Object.values(u.packages).filter((p) => p.pkg === rest[0]),
|
|
76
|
+
});
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
if (cmd === "notes" && rest[0] && rest[1] && rest[2]) {
|
|
80
|
+
const p = await getPackument(ctx, cfg, rest[0]);
|
|
81
|
+
if (!p.ok)
|
|
82
|
+
return fail(p.reason);
|
|
83
|
+
const c = selectCandidate(p.packument, rest[1], {
|
|
84
|
+
now,
|
|
85
|
+
minAgeMs: 0,
|
|
86
|
+
ageExcluded: () => true,
|
|
87
|
+
latest: false,
|
|
88
|
+
explicit: rest[2],
|
|
89
|
+
});
|
|
90
|
+
if (c.kind !== "candidate")
|
|
91
|
+
return fail(JSON.stringify(c));
|
|
92
|
+
print(await collectNotes(ctx, cfg, p.packument, c.candidate));
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (cmd === "surface" && rest[0] && rest[1] && rest[2]) {
|
|
96
|
+
const p = await getPackument(ctx, cfg, rest[0]);
|
|
97
|
+
if (!p.ok)
|
|
98
|
+
return fail(p.reason);
|
|
99
|
+
const a = await extractSurface(ctx, cfg, p.packument, rest[1]);
|
|
100
|
+
const b = await extractSurface(ctx, cfg, p.packument, rest[2]);
|
|
101
|
+
if (!a.ok || !b.ok)
|
|
102
|
+
return fail(JSON.stringify({ from: a.ok || a, to: b.ok || b }));
|
|
103
|
+
const delta = diffSurfaces(a.surface, b.surface);
|
|
104
|
+
print({
|
|
105
|
+
from: {
|
|
106
|
+
version: a.surface.version,
|
|
107
|
+
symbols: Object.keys(a.surface.symbols).length,
|
|
108
|
+
entries: a.surface.entries,
|
|
109
|
+
flags: a.surface.flags,
|
|
110
|
+
},
|
|
111
|
+
to: {
|
|
112
|
+
version: b.surface.version,
|
|
113
|
+
symbols: Object.keys(b.surface.symbols).length,
|
|
114
|
+
entries: b.surface.entries,
|
|
115
|
+
flags: b.surface.flags,
|
|
116
|
+
},
|
|
117
|
+
counts: Object.fromEntries(Object.entries(delta).map(([k, v]) => [
|
|
118
|
+
k,
|
|
119
|
+
v.length,
|
|
120
|
+
])),
|
|
121
|
+
delta,
|
|
122
|
+
});
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
return fail("usage: radius debug inventory | candidates | usage <pkg> | notes <pkg> <from> <to> | surface <pkg> <from> <to>");
|
|
126
|
+
}
|
|
127
|
+
function fail(msg) {
|
|
128
|
+
process.stderr.write(`radius debug: ${msg}\n`);
|
|
129
|
+
return 3;
|
|
130
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createCtx } from "./context.ts";
|
|
2
|
+
export { defaultCacheDir } from "./infra/cache.ts";
|
|
3
|
+
export type { HttpClient, HttpRequest, HttpResponse } from "./infra/http.ts";
|
|
4
|
+
export type * from "./model.ts";
|
|
5
|
+
export { type Ctx, DEFAULT_MIN_AGE_MS, type Format, formatAge, type Options, parseDuration, type RunEvent, } from "./options.ts";
|
|
6
|
+
export { type BriefV1, type CannotSeeV1, type PackageV1, renderJson, type SiteV1, toJsonV1, } from "./render/json.ts";
|
|
7
|
+
export { renderMarkdown } from "./render/markdown.ts";
|
|
8
|
+
export { parseSpec, run, type Spec, toolVersion } from "./run.ts";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC5E,mBAAmB,YAAY,CAAA;AAC/B,OAAO,EACL,KAAK,GAAG,EACR,kBAAkB,EAClB,KAAK,MAAM,EACX,SAAS,EACT,KAAK,OAAO,EACZ,aAAa,EACb,KAAK,QAAQ,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,UAAU,EACV,KAAK,MAAM,EACX,QAAQ,GACT,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The public API of the engine. Everything a consumer needs to analyse a project and render the
|
|
2
|
+
// result; the modules behind it are free to move.
|
|
3
|
+
export { createCtx } from "./context.js";
|
|
4
|
+
export { defaultCacheDir } from "./infra/cache.js";
|
|
5
|
+
export { DEFAULT_MIN_AGE_MS, formatAge, parseDuration, } from "./options.js";
|
|
6
|
+
export { renderJson, toJsonV1, } from "./render/json.js";
|
|
7
|
+
export { renderMarkdown } from "./render/markdown.js";
|
|
8
|
+
export { parseSpec, run, toolVersion } from "./run.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function defaultCacheDir(env: NodeJS.ProcessEnv): string;
|
|
2
|
+
export interface CacheEntry<T> {
|
|
3
|
+
value: T;
|
|
4
|
+
storedAt: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class DiskCache {
|
|
7
|
+
readonly dir: string;
|
|
8
|
+
constructor(dir: string);
|
|
9
|
+
path(key: string): string;
|
|
10
|
+
getJson<T>(key: string): Promise<CacheEntry<T> | undefined>;
|
|
11
|
+
setJson<T>(key: string, value: T, now: number): Promise<void>;
|
|
12
|
+
getBytes(key: string): Promise<Buffer | undefined>;
|
|
13
|
+
setBytes(key: string, bytes: Uint8Array): Promise<void>;
|
|
14
|
+
has(key: string): Promise<boolean>;
|
|
15
|
+
private writeAtomic;
|
|
16
|
+
}
|
|
17
|
+
export declare function safeKey(s: string): string;
|
|
18
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/infra/cache.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAQ9D;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAA;IACR,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;gBACR,GAAG,EAAE,MAAM;IAIvB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAInB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAS3D,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAQlD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAU1B,WAAW;CAU1B;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
export function defaultCacheDir(env) {
|
|
5
|
+
if (env.RADIUS_CACHE_DIR)
|
|
6
|
+
return env.RADIUS_CACHE_DIR;
|
|
7
|
+
if (env.XDG_CACHE_HOME)
|
|
8
|
+
return join(env.XDG_CACHE_HOME, "dep-radius");
|
|
9
|
+
if (process.platform === "darwin")
|
|
10
|
+
return join(homedir(), "Library", "Caches", "dep-radius");
|
|
11
|
+
if (process.platform === "win32" && env.LOCALAPPDATA)
|
|
12
|
+
return join(env.LOCALAPPDATA, "dep-radius", "Cache");
|
|
13
|
+
return join(homedir(), ".cache", "dep-radius");
|
|
14
|
+
}
|
|
15
|
+
export class DiskCache {
|
|
16
|
+
dir;
|
|
17
|
+
constructor(dir) {
|
|
18
|
+
this.dir = dir;
|
|
19
|
+
}
|
|
20
|
+
path(key) {
|
|
21
|
+
return join(this.dir, key);
|
|
22
|
+
}
|
|
23
|
+
async getJson(key) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(this.path(key), "utf8");
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async setJson(key, value, now) {
|
|
33
|
+
await this.writeAtomic(key, JSON.stringify({ value, storedAt: now }));
|
|
34
|
+
}
|
|
35
|
+
async getBytes(key) {
|
|
36
|
+
try {
|
|
37
|
+
return await readFile(this.path(key));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async setBytes(key, bytes) {
|
|
44
|
+
await this.writeAtomic(key, bytes);
|
|
45
|
+
}
|
|
46
|
+
async has(key) {
|
|
47
|
+
try {
|
|
48
|
+
await stat(this.path(key));
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// A crash mid-write must never leave a truncated entry that a later offline run trusts.
|
|
56
|
+
async writeAtomic(key, data) {
|
|
57
|
+
const target = this.path(key);
|
|
58
|
+
await mkdir(dirname(target), { recursive: true });
|
|
59
|
+
const tmp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
60
|
+
await writeFile(tmp, data);
|
|
61
|
+
await rename(tmp, target);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function safeKey(s) {
|
|
65
|
+
return s.replace(/[^a-zA-Z0-9._@-]+/g, "_");
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/infra/hash.ts"],"names":[],"mappings":"AAEA,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAEtD;AAGD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKlE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAO7E"}
|