@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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/analyze/brief.d.ts +18 -0
  4. package/dist/analyze/brief.d.ts.map +1 -0
  5. package/dist/analyze/brief.js +89 -0
  6. package/dist/analyze/verdict.d.ts +16 -0
  7. package/dist/analyze/verdict.d.ts.map +1 -0
  8. package/dist/analyze/verdict.js +134 -0
  9. package/dist/context.d.ts +4 -0
  10. package/dist/context.d.ts.map +1 -0
  11. package/dist/context.js +36 -0
  12. package/dist/debug.d.ts +2 -0
  13. package/dist/debug.d.ts.map +1 -0
  14. package/dist/debug.js +130 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/infra/cache.d.ts +18 -0
  19. package/dist/infra/cache.d.ts.map +1 -0
  20. package/dist/infra/cache.js +66 -0
  21. package/dist/infra/hash.d.ts +4 -0
  22. package/dist/infra/hash.d.ts.map +1 -0
  23. package/dist/infra/hash.js +18 -0
  24. package/dist/infra/http.d.ts +24 -0
  25. package/dist/infra/http.d.ts.map +1 -0
  26. package/dist/infra/http.js +44 -0
  27. package/dist/infra/limit.d.ts +4 -0
  28. package/dist/infra/limit.d.ts.map +1 -0
  29. package/dist/infra/limit.js +34 -0
  30. package/dist/infra/log.d.ts +8 -0
  31. package/dist/infra/log.d.ts.map +1 -0
  32. package/dist/infra/log.js +13 -0
  33. package/dist/inventory/installed.d.ts +15 -0
  34. package/dist/inventory/installed.d.ts.map +1 -0
  35. package/dist/inventory/installed.js +241 -0
  36. package/dist/inventory/lockfiles/bun.d.ts +3 -0
  37. package/dist/inventory/lockfiles/bun.d.ts.map +1 -0
  38. package/dist/inventory/lockfiles/bun.js +24 -0
  39. package/dist/inventory/lockfiles/npm.d.ts +3 -0
  40. package/dist/inventory/lockfiles/npm.d.ts.map +1 -0
  41. package/dist/inventory/lockfiles/npm.js +29 -0
  42. package/dist/inventory/lockfiles/pnpm.d.ts +3 -0
  43. package/dist/inventory/lockfiles/pnpm.d.ts.map +1 -0
  44. package/dist/inventory/lockfiles/pnpm.js +84 -0
  45. package/dist/inventory/lockfiles/types.d.ts +9 -0
  46. package/dist/inventory/lockfiles/types.d.ts.map +1 -0
  47. package/dist/inventory/lockfiles/types.js +1 -0
  48. package/dist/inventory/lockfiles/yarn.d.ts +3 -0
  49. package/dist/inventory/lockfiles/yarn.d.ts.map +1 -0
  50. package/dist/inventory/lockfiles/yarn.js +46 -0
  51. package/dist/inventory/manifests.d.ts +9 -0
  52. package/dist/inventory/manifests.d.ts.map +1 -0
  53. package/dist/inventory/manifests.js +135 -0
  54. package/dist/inventory/specifiers.d.ts +8 -0
  55. package/dist/inventory/specifiers.d.ts.map +1 -0
  56. package/dist/inventory/specifiers.js +53 -0
  57. package/dist/labels.d.ts +2 -0
  58. package/dist/labels.d.ts.map +1 -0
  59. package/dist/labels.js +18 -0
  60. package/dist/model.d.ts +276 -0
  61. package/dist/model.d.ts.map +1 -0
  62. package/dist/model.js +2 -0
  63. package/dist/notes/changelog.d.ts +11 -0
  64. package/dist/notes/changelog.d.ts.map +1 -0
  65. package/dist/notes/changelog.js +60 -0
  66. package/dist/notes/collect.d.ts +13 -0
  67. package/dist/notes/collect.d.ts.map +1 -0
  68. package/dist/notes/collect.js +225 -0
  69. package/dist/notes/entries.d.ts +3 -0
  70. package/dist/notes/entries.d.ts.map +1 -0
  71. package/dist/notes/entries.js +209 -0
  72. package/dist/notes/github.d.ts +25 -0
  73. package/dist/notes/github.d.ts.map +1 -0
  74. package/dist/notes/github.js +132 -0
  75. package/dist/notes/match.d.ts +13 -0
  76. package/dist/notes/match.d.ts.map +1 -0
  77. package/dist/notes/match.js +75 -0
  78. package/dist/notes/repo-url.d.ts +9 -0
  79. package/dist/notes/repo-url.d.ts.map +1 -0
  80. package/dist/notes/repo-url.js +46 -0
  81. package/dist/options.d.ts +53 -0
  82. package/dist/options.d.ts.map +1 -0
  83. package/dist/options.js +27 -0
  84. package/dist/registry/candidates.d.ts +24 -0
  85. package/dist/registry/candidates.d.ts.map +1 -0
  86. package/dist/registry/candidates.js +139 -0
  87. package/dist/registry/npmrc.d.ts +13 -0
  88. package/dist/registry/npmrc.d.ts.map +1 -0
  89. package/dist/registry/npmrc.js +99 -0
  90. package/dist/registry/packument.d.ts +34 -0
  91. package/dist/registry/packument.d.ts.map +1 -0
  92. package/dist/registry/packument.js +85 -0
  93. package/dist/registry/tar.d.ts +2 -0
  94. package/dist/registry/tar.d.ts.map +1 -0
  95. package/dist/registry/tar.js +75 -0
  96. package/dist/registry/tarball.d.ts +16 -0
  97. package/dist/registry/tarball.d.ts.map +1 -0
  98. package/dist/registry/tarball.js +59 -0
  99. package/dist/render/groups.d.ts +16 -0
  100. package/dist/render/groups.d.ts.map +1 -0
  101. package/dist/render/groups.js +105 -0
  102. package/dist/render/index.d.ts +2 -0
  103. package/dist/render/index.d.ts.map +1 -0
  104. package/dist/render/index.js +3 -0
  105. package/dist/render/json.d.ts +119 -0
  106. package/dist/render/json.d.ts.map +1 -0
  107. package/dist/render/json.js +130 -0
  108. package/dist/render/limits.d.ts +3 -0
  109. package/dist/render/limits.d.ts.map +1 -0
  110. package/dist/render/limits.js +25 -0
  111. package/dist/render/markdown.d.ts +3 -0
  112. package/dist/render/markdown.d.ts.map +1 -0
  113. package/dist/render/markdown.js +62 -0
  114. package/dist/run.d.ts +10 -0
  115. package/dist/run.d.ts.map +1 -0
  116. package/dist/run.js +218 -0
  117. package/dist/surface/analyze.d.ts +13 -0
  118. package/dist/surface/analyze.d.ts.map +1 -0
  119. package/dist/surface/analyze.js +166 -0
  120. package/dist/surface/delta.d.ts +5 -0
  121. package/dist/surface/delta.d.ts.map +1 -0
  122. package/dist/surface/delta.js +173 -0
  123. package/dist/surface/entries.d.ts +16 -0
  124. package/dist/surface/entries.d.ts.map +1 -0
  125. package/dist/surface/entries.js +187 -0
  126. package/dist/surface/extract.d.ts +15 -0
  127. package/dist/surface/extract.d.ts.map +1 -0
  128. package/dist/surface/extract.js +460 -0
  129. package/dist/surface/signature.d.ts +6 -0
  130. package/dist/surface/signature.d.ts.map +1 -0
  131. package/dist/surface/signature.js +229 -0
  132. package/dist/surface/vfs-host.d.ts +5 -0
  133. package/dist/surface/vfs-host.d.ts.map +1 -0
  134. package/dist/surface/vfs-host.js +54 -0
  135. package/dist/symbol-path.d.ts +28 -0
  136. package/dist/symbol-path.d.ts.map +1 -0
  137. package/dist/symbol-path.js +223 -0
  138. package/dist/usage/files.d.ts +18 -0
  139. package/dist/usage/files.d.ts.map +1 -0
  140. package/dist/usage/files.js +83 -0
  141. package/dist/usage/link.d.ts +23 -0
  142. package/dist/usage/link.d.ts.map +1 -0
  143. package/dist/usage/link.js +588 -0
  144. package/dist/usage/opaque.d.ts +7 -0
  145. package/dist/usage/opaque.d.ts.map +1 -0
  146. package/dist/usage/opaque.js +193 -0
  147. package/dist/usage/parse.d.ts +73 -0
  148. package/dist/usage/parse.d.ts.map +1 -0
  149. package/dist/usage/parse.js +608 -0
  150. package/dist/usage/resolve-local.d.ts +35 -0
  151. package/dist/usage/resolve-local.d.ts.map +1 -0
  152. package/dist/usage/resolve-local.js +340 -0
  153. package/dist/usage/scan.d.ts +4 -0
  154. package/dist/usage/scan.d.ts.map +1 -0
  155. package/dist/usage/scan.js +92 -0
  156. package/package.json +79 -0
  157. package/schema/brief-v1.schema.json +319 -0
@@ -0,0 +1,75 @@
1
+ // No dictionaries. A name that looks like code matches as a token anywhere; a plain English word
2
+ // matches only where it is written as code: `.email`, `email(`, `email<`, `<Email`.
3
+ export function isCodeShaped(name) {
4
+ return /[A-Z_$0-9]/.test(name.slice(1)) || name.length >= 12;
5
+ }
6
+ function escape(s) {
7
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+ }
9
+ function patternFor(name) {
10
+ const n = escape(name);
11
+ if (name.length <= 2)
12
+ return new RegExp(`(?<=\\.)${n}(?![\\w$])`);
13
+ if (isCodeShaped(name))
14
+ return new RegExp(`(?<![\\w$])${n}(?![\\w$])`);
15
+ return new RegExp(`(?<=\\.)${n}(?![\\w$])|(?<![\\w$.])${n}(?=\\s*[(<])|(?<=<)${n}(?![\\w$])`);
16
+ }
17
+ // A breaking entry that names an API is about that API; when it is not one of yours, it is someone
18
+ // else's break. One that names nothing ("drops Node 18", "ESM only") can be anyone's.
19
+ function namesAnApi(e) {
20
+ const headline = e.regions.filter((r) => r.kind === "title" || r.kind === "inline-code");
21
+ return headline.some((r) => r.kind === "inline-code" || /[\w$]\(\)|\.[a-z]\w*\(/i.test(r.text));
22
+ }
23
+ export const DEMOTE_SHARE = 0.25;
24
+ export function matchNotes(entries, strong, weak, options = {}) {
25
+ const share = options.demoteShare ?? DEMOTE_SHARE;
26
+ const live = entries.filter((e) => !e.noise);
27
+ const names = [
28
+ ...strong.map((name) => ({ name, strength: "strong" })),
29
+ ...weak
30
+ .filter((w) => !strong.includes(w))
31
+ .map((name) => ({ name, strength: "weak" })),
32
+ ].map((n) => ({ ...n, re: patternFor(n.name) }));
33
+ // a name found in the examples of many entries is example boilerplate, not news: `v.object()` in
34
+ // every snippet of a release says nothing about v.object
35
+ const withCode = live.filter((e) => e.regions.some((r) => r.kind === "code-block"));
36
+ const demoted = new Set();
37
+ for (const n of names) {
38
+ const inCode = withCode.filter((e) => e.regions.some((r) => r.kind === "code-block" && n.re.test(r.text))).length;
39
+ if (inCode >= 3 && inCode >= withCode.length * share)
40
+ demoted.add(n.name);
41
+ }
42
+ const matched = [];
43
+ const unattributedBreaking = [];
44
+ for (const e of live) {
45
+ const hits = [];
46
+ for (const n of names) {
47
+ let hitRegion;
48
+ for (const r of e.regions) {
49
+ if (r.kind === "code-block" &&
50
+ // demotion never silences a breaking entry: one whose only link to the code is its example
51
+ // is still a break
52
+ ((demoted.has(n.name) && !e.breakingMarker) ||
53
+ (n.strength === "weak" && !isCodeShaped(n.name))))
54
+ continue;
55
+ if (n.re.test(r.text)) {
56
+ hitRegion = r.kind;
57
+ break;
58
+ }
59
+ }
60
+ if (hitRegion)
61
+ hits.push({ name: n.name, strength: n.strength, region: hitRegion });
62
+ }
63
+ if (hits.length > 0)
64
+ matched.push({
65
+ entry: e,
66
+ hits,
67
+ direct: hits.some((h) => h.strength === "strong" && h.region !== "code-block"),
68
+ });
69
+ else if (e.breakingMarker && !namesAnApi(e))
70
+ unattributedBreaking.push(e);
71
+ }
72
+ matched.sort((a, b) => Number(b.direct) - Number(a.direct) ||
73
+ Number(b.entry.breakingMarker) - Number(a.entry.breakingMarker));
74
+ return { matched, unattributedBreaking, total: live.length };
75
+ }
@@ -0,0 +1,9 @@
1
+ export interface RepoRef {
2
+ host: "github" | "other";
3
+ owner: string;
4
+ repo: string;
5
+ directory?: string;
6
+ raw: string;
7
+ }
8
+ export declare function parseRepository(input: unknown): RepoRef | undefined;
9
+ //# sourceMappingURL=repo-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-url.d.ts","sourceRoot":"","sources":["../../src/notes/repo-url.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;CACZ;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CA8CnE"}
@@ -0,0 +1,46 @@
1
+ // Every shape `repository` takes in the wild:
2
+ // "git+https://github.com/o/r.git", "git+ssh://git@github.com/o/r.git", "github:o/r", "o/r",
3
+ // "https://github.com/o/r/tree/main/packages/x", { type, url, directory }
4
+ export function parseRepository(input) {
5
+ let url;
6
+ let directory;
7
+ if (typeof input === "string")
8
+ url = input;
9
+ else if (input && typeof input === "object") {
10
+ const o = input;
11
+ if (typeof o.url === "string")
12
+ url = o.url;
13
+ if (typeof o.directory === "string")
14
+ directory = o.directory;
15
+ }
16
+ if (!url)
17
+ return undefined;
18
+ const raw = url.trim();
19
+ const shorthand = /^(?:github:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#.*)?$/.exec(raw);
20
+ if (shorthand?.[1] &&
21
+ shorthand[2] &&
22
+ !raw.includes("://") &&
23
+ !raw.includes("@")) {
24
+ return {
25
+ host: "github",
26
+ owner: shorthand[1],
27
+ repo: shorthand[2],
28
+ ...(directory ? { directory } : {}),
29
+ raw,
30
+ };
31
+ }
32
+ if (/^(gitlab|bitbucket):/.test(raw))
33
+ return { host: "other", owner: "", repo: "", raw };
34
+ const m = /github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:\/(?:tree|blob)\/[^/]+\/(.+?))?\/?(?:#.*)?$/.exec(raw);
35
+ if (m?.[1] && m[2]) {
36
+ const dir = directory ?? m[3];
37
+ return {
38
+ host: "github",
39
+ owner: m[1],
40
+ repo: m[2],
41
+ ...(dir ? { directory: dir.replace(/\/+$/, "") } : {}),
42
+ raw,
43
+ };
44
+ }
45
+ return { host: "other", owner: "", repo: "", raw };
46
+ }
@@ -0,0 +1,53 @@
1
+ import type { Logger } from "./infra/log.ts";
2
+ import type { Limiter } from "./infra/limit.ts";
3
+ import type { DiskCache } from "./infra/cache.ts";
4
+ import type { HttpClient } from "./infra/http.ts";
5
+ export type Format = "terminal" | "json" | "markdown";
6
+ export interface Options {
7
+ root: string;
8
+ specs: string[];
9
+ format: Format;
10
+ offline: boolean;
11
+ minAgeMs: number | undefined;
12
+ latest: boolean;
13
+ notes: boolean;
14
+ surface: boolean;
15
+ prod: boolean;
16
+ concurrency: number;
17
+ cacheDir: string;
18
+ verbose: boolean;
19
+ now: number;
20
+ color: boolean;
21
+ }
22
+ export type RunEvent = {
23
+ type: "files";
24
+ done: number;
25
+ total: number;
26
+ } | {
27
+ type: "packages";
28
+ total: number;
29
+ } | {
30
+ type: "package-start";
31
+ name: string;
32
+ } | {
33
+ type: "package";
34
+ name: string;
35
+ done: number;
36
+ total: number;
37
+ };
38
+ export interface Ctx {
39
+ http: HttpClient;
40
+ cache: DiskCache;
41
+ now: number;
42
+ offline: boolean;
43
+ log: Logger;
44
+ net: Limiter;
45
+ github: Limiter;
46
+ heavy: Limiter;
47
+ githubToken: () => Promise<string | undefined>;
48
+ env: NodeJS.ProcessEnv;
49
+ }
50
+ export declare function parseDuration(input: string): number;
51
+ export declare function formatAge(ms: number): string;
52
+ export declare const DEFAULT_MIN_AGE_MS = 86400000;
53
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAA;AAErD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;CACf;AAGD,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,SAAS,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,OAAO,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAE9C,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CACvB;AAaD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,eAAO,MAAM,kBAAkB,WAAa,CAAA"}
@@ -0,0 +1,27 @@
1
+ const UNITS = {
2
+ ms: 1,
3
+ s: 1000,
4
+ m: 60_000,
5
+ min: 60_000,
6
+ h: 3_600_000,
7
+ d: 86_400_000,
8
+ w: 604_800_000,
9
+ };
10
+ // "0", "90m", "36h", "7d"; a bare number is minutes, as pnpm's minimumReleaseAge is.
11
+ export function parseDuration(input) {
12
+ const s = input.trim();
13
+ if (/^\d+(\.\d+)?$/.test(s))
14
+ return Number(s) * 60_000;
15
+ const m = /^(\d+(?:\.\d+)?)\s*(ms|s|m|min|h|d|w)$/i.exec(s);
16
+ if (!m?.[1] || !m[2])
17
+ throw new Error(`invalid duration: ${input}`);
18
+ return Number(m[1]) * (UNITS[m[2].toLowerCase()] ?? 0);
19
+ }
20
+ export function formatAge(ms) {
21
+ if (ms < 3_600_000)
22
+ return `${Math.max(1, Math.round(ms / 60_000))}m`;
23
+ if (ms < 172_800_000)
24
+ return `${Math.round(ms / 3_600_000)}h`;
25
+ return `${Math.round(ms / 86_400_000)}d`;
26
+ }
27
+ export const DEFAULT_MIN_AGE_MS = 86_400_000;
@@ -0,0 +1,24 @@
1
+ import type { Packument } from "./packument.ts";
2
+ import type { Bump, Candidate } from "../model.ts";
3
+ export interface CandidateOptions {
4
+ now: number;
5
+ minAgeMs: number;
6
+ ageExcluded: (version: string) => boolean;
7
+ latest: boolean;
8
+ explicit?: string;
9
+ }
10
+ export type CandidateResult = {
11
+ kind: "candidate";
12
+ candidate: Candidate;
13
+ } | {
14
+ kind: "up-to-date";
15
+ skippedNewer: Candidate["skippedNewer"];
16
+ alsoAvailable?: Candidate["alsoAvailable"];
17
+ } | {
18
+ kind: "error";
19
+ reason: string;
20
+ };
21
+ export declare function bumpOf(from: string, to: string): Bump;
22
+ export declare function stableRange(p: Packument, from: string, to: string): string[];
23
+ export declare function selectCandidate(p: Packument, installed: string, opts: CandidateOptions): CandidateResult;
24
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/registry/candidates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;IACzC,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAC3C;IACE,IAAI,EAAE,YAAY,CAAA;IAClB,YAAY,EAAE,SAAS,CAAC,cAAc,CAAC,CAAA;IACvC,aAAa,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,CAAA;CAC3C,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CASrD;AAaD,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAU5E;AAED,wBAAgB,eAAe,CAC7B,CAAC,EAAE,SAAS,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,GACrB,eAAe,CA2GjB"}
@@ -0,0 +1,139 @@
1
+ import semver from "semver";
2
+ export function bumpOf(from, to) {
3
+ const a = semver.parse(from);
4
+ const b = semver.parse(to);
5
+ if (!a || !b)
6
+ return "major";
7
+ if (a.major !== b.major)
8
+ return "major";
9
+ if (a.major === 0 && a.minor !== b.minor)
10
+ return "major";
11
+ if (a.minor !== b.minor)
12
+ return "minor";
13
+ if (a.major === 0 && a.minor === 0 && a.patch !== b.patch)
14
+ return "major";
15
+ return "patch";
16
+ }
17
+ // The caret line of the installed version: what semver promises is safe.
18
+ function sameLine(installed, v) {
19
+ const a = semver.parse(installed);
20
+ const b = semver.parse(v);
21
+ if (a.major !== b.major)
22
+ return false;
23
+ if (a.major > 0)
24
+ return true;
25
+ if (a.minor !== b.minor)
26
+ return false;
27
+ if (a.minor > 0)
28
+ return true;
29
+ return a.patch === b.patch;
30
+ }
31
+ export function stableRange(p, from, to) {
32
+ return Object.keys(p.versions)
33
+ .filter((v) => semver.valid(v) &&
34
+ !semver.prerelease(v) &&
35
+ semver.gt(v, from) &&
36
+ semver.lte(v, to))
37
+ .sort(semver.compare);
38
+ }
39
+ export function selectCandidate(p, installed, opts) {
40
+ if (!semver.valid(installed))
41
+ return {
42
+ kind: "error",
43
+ reason: `installed version ${installed} is not semver`,
44
+ };
45
+ if (opts.explicit) {
46
+ const target = p["dist-tags"][opts.explicit] ?? opts.explicit;
47
+ if (!p.versions[target])
48
+ return {
49
+ kind: "error",
50
+ reason: `version ${opts.explicit} does not exist`,
51
+ };
52
+ if (semver.eq(target, installed))
53
+ return { kind: "up-to-date", skippedNewer: [] };
54
+ const from = semver.lt(target, installed) ? target : installed;
55
+ const to = semver.lt(target, installed) ? installed : target;
56
+ return {
57
+ kind: "candidate",
58
+ candidate: {
59
+ pkg: p.name,
60
+ from: installed,
61
+ to: target,
62
+ bump: bumpOf(from, to),
63
+ publishedAt: p.time[target] ?? "",
64
+ range: stableRange(p, from, to),
65
+ skippedNewer: [],
66
+ },
67
+ };
68
+ }
69
+ const skipped = [];
70
+ const eligible = [];
71
+ const newer = Object.keys(p.versions)
72
+ .filter((v) => semver.valid(v) && !semver.prerelease(v) && semver.gt(v, installed))
73
+ .sort(semver.rcompare);
74
+ for (const v of newer) {
75
+ const pv = p.versions[v];
76
+ const published = p.time[v];
77
+ if (pv.deprecated) {
78
+ skipped.push({ version: v, reason: "deprecated" });
79
+ continue;
80
+ }
81
+ if (!published)
82
+ continue;
83
+ const age = opts.now - Date.parse(published);
84
+ if (age < opts.minAgeMs && !opts.ageExcluded(v)) {
85
+ skipped.push({ version: v, reason: "too-new", publishedAt: published });
86
+ continue;
87
+ }
88
+ eligible.push(v);
89
+ }
90
+ const latestTag = p["dist-tags"].latest;
91
+ const onLine = eligible.filter((v) => sameLine(installed, v));
92
+ const beyond = eligible.filter((v) => !sameLine(installed, v));
93
+ let target;
94
+ if (opts.latest) {
95
+ target = eligible[0];
96
+ }
97
+ else {
98
+ target = onLine[0];
99
+ // npm never installs above `latest` for a range, so neither does the brief
100
+ if (target &&
101
+ latestTag &&
102
+ semver.valid(latestTag) &&
103
+ sameLine(installed, latestTag) &&
104
+ semver.gt(target, latestTag)) {
105
+ for (const v of onLine)
106
+ if (semver.gt(v, latestTag))
107
+ skipped.push({ version: v, reason: "above-dist-tag" });
108
+ target = semver.gt(latestTag, installed) ? latestTag : undefined;
109
+ }
110
+ }
111
+ const skippedNewer = skipped
112
+ .filter((s) => !target || semver.gt(s.version, target))
113
+ .filter((s) => opts.latest || sameLine(installed, s.version))
114
+ .sort((a, b) => semver.compare(a.version, b.version))
115
+ .slice(0, 3);
116
+ const above = opts.latest ? undefined : beyond[0];
117
+ const alsoAvailable = above
118
+ ? { version: above, bump: bumpOf(installed, above) }
119
+ : undefined;
120
+ if (!target)
121
+ return {
122
+ kind: "up-to-date",
123
+ skippedNewer,
124
+ ...(alsoAvailable ? { alsoAvailable } : {}),
125
+ };
126
+ return {
127
+ kind: "candidate",
128
+ candidate: {
129
+ pkg: p.name,
130
+ from: installed,
131
+ to: target,
132
+ bump: bumpOf(installed, target),
133
+ publishedAt: p.time[target] ?? "",
134
+ range: stableRange(p, installed, target),
135
+ skippedNewer,
136
+ ...(alsoAvailable ? { alsoAvailable } : {}),
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,13 @@
1
+ export interface RegistryConfig {
2
+ defaultRegistry: string;
3
+ scoped: Record<string, string>;
4
+ tokens: Record<string, string>;
5
+ minimumReleaseAgeMs?: number;
6
+ minimumReleaseAgeSource?: string;
7
+ minimumReleaseAgeExclude: string[];
8
+ }
9
+ export declare function loadRegistryConfig(root: string, env: NodeJS.ProcessEnv): RegistryConfig;
10
+ export declare function registryFor(cfg: RegistryConfig, pkg: string): string;
11
+ export declare function authHeaders(cfg: RegistryConfig, url: string): Record<string, string>;
12
+ export declare function isAgeExcluded(cfg: RegistryConfig, pkg: string, version: string): boolean;
13
+ //# sourceMappingURL=npmrc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npmrc.d.ts","sourceRoot":"","sources":["../../src/registry/npmrc.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAA;CACnC;AAuBD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,cAAc,CA2ChB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAGD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAeT"}
@@ -0,0 +1,99 @@
1
+ import { join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { parseDuration } from "../options.js";
5
+ function parseIni(text, env) {
6
+ const out = {};
7
+ for (const raw of text.split(/\r?\n/)) {
8
+ const line = raw.trim();
9
+ if (!line || line.startsWith("#") || line.startsWith(";"))
10
+ continue;
11
+ const eq = line.indexOf("=");
12
+ if (eq < 0)
13
+ continue;
14
+ const key = line.slice(0, eq).trim();
15
+ const value = line
16
+ .slice(eq + 1)
17
+ .trim()
18
+ .replace(/^"(.*)"$/, "$1")
19
+ .replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
20
+ out[key] = value;
21
+ }
22
+ return out;
23
+ }
24
+ export function loadRegistryConfig(root, env) {
25
+ const merged = {};
26
+ for (const file of [join(homedir(), ".npmrc"), join(root, ".npmrc")]) {
27
+ if (existsSync(file))
28
+ Object.assign(merged, parseIni(readFileSync(file, "utf8"), env));
29
+ }
30
+ const cfg = {
31
+ defaultRegistry: trimSlash(env.npm_config_registry || merged.registry || "https://registry.npmjs.org"),
32
+ scoped: {},
33
+ tokens: {},
34
+ minimumReleaseAgeExclude: [],
35
+ };
36
+ for (const [k, v] of Object.entries(merged)) {
37
+ const scope = /^(@[^:]+):registry$/.exec(k);
38
+ if (scope?.[1])
39
+ cfg.scoped[scope[1]] = trimSlash(v);
40
+ const token = /^\/\/(.+?)\/?:_authToken$/.exec(k);
41
+ if (token?.[1] && v)
42
+ cfg.tokens[token[1].replace(/\/$/, "")] = v;
43
+ }
44
+ const npmrcAge = merged["minimum-release-age"];
45
+ if (npmrcAge) {
46
+ cfg.minimumReleaseAgeMs = parseDuration(npmrcAge);
47
+ cfg.minimumReleaseAgeSource = ".npmrc minimum-release-age";
48
+ }
49
+ const ws = join(root, "pnpm-workspace.yaml");
50
+ if (existsSync(ws)) {
51
+ const text = readFileSync(ws, "utf8");
52
+ const age = /^minimumReleaseAge:\s*(\d+)\s*$/m.exec(text);
53
+ if (age?.[1]) {
54
+ cfg.minimumReleaseAgeMs = Number(age[1]) * 60_000;
55
+ cfg.minimumReleaseAgeSource = "pnpm-workspace.yaml minimumReleaseAge";
56
+ }
57
+ const block = /^minimumReleaseAgeExclude:\s*\n((?:[ \t]+-.*\n?)*)/m.exec(text);
58
+ for (const line of block?.[1]?.split("\n") ?? []) {
59
+ const m = /^\s+-\s+['"]?([^'"\s]+)['"]?/.exec(line);
60
+ if (m?.[1])
61
+ cfg.minimumReleaseAgeExclude.push(m[1]);
62
+ }
63
+ }
64
+ return cfg;
65
+ }
66
+ export function registryFor(cfg, pkg) {
67
+ if (pkg.startsWith("@")) {
68
+ const scope = pkg.split("/")[0];
69
+ if (cfg.scoped[scope])
70
+ return cfg.scoped[scope];
71
+ }
72
+ return cfg.defaultRegistry;
73
+ }
74
+ export function authHeaders(cfg, url) {
75
+ const host = url.replace(/^https?:\/\//, "");
76
+ let best = "";
77
+ for (const prefix of Object.keys(cfg.tokens)) {
78
+ if (host.startsWith(prefix) && prefix.length > best.length)
79
+ best = prefix;
80
+ }
81
+ return best ? { authorization: `Bearer ${cfg.tokens[best]}` } : {};
82
+ }
83
+ // "lib", "lib@2.1.0" (only that version waived), "@scope/*"
84
+ export function isAgeExcluded(cfg, pkg, version) {
85
+ return cfg.minimumReleaseAgeExclude.some((pattern) => {
86
+ const at = pattern.lastIndexOf("@");
87
+ const [name, ver] = at > 0
88
+ ? [pattern.slice(0, at), pattern.slice(at + 1)]
89
+ : [pattern, undefined];
90
+ const nameOk = name.endsWith("*")
91
+ ? pkg.startsWith(name.slice(0, -1))
92
+ : pkg === name;
93
+ return (nameOk &&
94
+ (ver === undefined || ver.split("||").some((v) => v.trim() === version)));
95
+ });
96
+ }
97
+ function trimSlash(s) {
98
+ return s.replace(/\/+$/, "");
99
+ }
@@ -0,0 +1,34 @@
1
+ import type { Ctx } from "../options.ts";
2
+ import { type RegistryConfig } from "./npmrc.ts";
3
+ export interface PackumentVersion {
4
+ version: string;
5
+ deprecated?: string;
6
+ dist: {
7
+ tarball: string;
8
+ integrity?: string;
9
+ shasum?: string;
10
+ };
11
+ types?: string;
12
+ typings?: string;
13
+ exports?: unknown;
14
+ typesVersions?: Record<string, Record<string, string[]>>;
15
+ repository?: unknown;
16
+ bin?: unknown;
17
+ }
18
+ export interface Packument {
19
+ name: string;
20
+ "dist-tags": Record<string, string>;
21
+ time: Record<string, string>;
22
+ repository?: unknown;
23
+ versions: Record<string, PackumentVersion>;
24
+ }
25
+ export type PackumentResult = {
26
+ ok: true;
27
+ packument: Packument;
28
+ } | {
29
+ ok: false;
30
+ reason: "not-found" | "unpublished" | "offline-uncached" | "http-error";
31
+ detail?: string;
32
+ };
33
+ export declare function getPackument(ctx: Ctx, cfg: RegistryConfig, name: string): Promise<PackumentResult>;
34
+ //# sourceMappingURL=packument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packument.d.ts","sourceRoot":"","sources":["../../src/registry/packument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAGxC,OAAO,EAAe,KAAK,cAAc,EAAe,MAAM,YAAY,CAAA;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IACxD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAClC;IACE,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,kBAAkB,GAAG,YAAY,CAAA;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAKL,wBAAsB,YAAY,CAChC,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CA8C1B"}
@@ -0,0 +1,85 @@
1
+ import { safeKey } from "../infra/cache.js";
2
+ import { OfflineError } from "../infra/http.js";
3
+ import { authHeaders, registryFor } from "./npmrc.js";
4
+ const FRESH_MS = 60 * 60 * 1000;
5
+ // The full document, because the abbreviated one has no `time`, `repository`, `types` or `exports`.
6
+ export async function getPackument(ctx, cfg, name) {
7
+ const key = `packuments/${safeKey(name)}.json`;
8
+ const cached = await ctx.cache.getJson(key);
9
+ if (cached && (ctx.offline || ctx.now - cached.storedAt < FRESH_MS))
10
+ return { ok: true, packument: cached.value.packument };
11
+ if (ctx.offline)
12
+ return { ok: false, reason: "offline-uncached" };
13
+ const url = `${registryFor(cfg, name)}/${name.replace("/", "%2f")}`;
14
+ const headers = {
15
+ accept: "application/json",
16
+ ...authHeaders(cfg, url),
17
+ };
18
+ if (cached?.value.etag)
19
+ headers["if-none-match"] = cached.value.etag;
20
+ try {
21
+ const res = await ctx.net(() => ctx.http.get({ url, headers }));
22
+ if (res.status === 304 && cached) {
23
+ await ctx.cache.setJson(key, cached.value, ctx.now);
24
+ return { ok: true, packument: cached.value.packument };
25
+ }
26
+ if (res.status === 404)
27
+ return { ok: false, reason: "not-found" };
28
+ if (res.status !== 200) {
29
+ if (cached)
30
+ return { ok: true, packument: cached.value.packument };
31
+ return { ok: false, reason: "http-error", detail: `HTTP ${res.status}` };
32
+ }
33
+ const full = JSON.parse(res.body.toString("utf8"));
34
+ const packument = trim(full);
35
+ if (!packument.versions || Object.keys(packument.versions).length === 0) {
36
+ return {
37
+ ok: false,
38
+ reason: packument.time?.unpublished ? "unpublished" : "not-found",
39
+ };
40
+ }
41
+ await ctx.cache.setJson(key, { etag: res.headers.etag, packument }, ctx.now);
42
+ return { ok: true, packument };
43
+ }
44
+ catch (error) {
45
+ if (cached)
46
+ return { ok: true, packument: cached.value.packument };
47
+ if (error instanceof OfflineError)
48
+ return { ok: false, reason: "offline-uncached" };
49
+ return { ok: false, reason: "http-error", detail: String(error) };
50
+ }
51
+ }
52
+ function trim(full) {
53
+ const versions = {};
54
+ for (const [v, raw] of Object.entries((full.versions ?? {}))) {
55
+ const dist = (raw.dist ?? {});
56
+ const pv = {
57
+ version: v,
58
+ dist: {
59
+ tarball: dist.tarball,
60
+ integrity: dist.integrity,
61
+ shasum: dist.shasum,
62
+ },
63
+ };
64
+ if (typeof raw.deprecated === "string" && raw.deprecated)
65
+ pv.deprecated = raw.deprecated;
66
+ if (typeof raw.types === "string")
67
+ pv.types = raw.types;
68
+ if (typeof raw.typings === "string")
69
+ pv.typings = raw.typings;
70
+ if (raw.exports !== undefined)
71
+ pv.exports = raw.exports;
72
+ if (raw.typesVersions)
73
+ pv.typesVersions = raw.typesVersions;
74
+ if (raw.repository !== undefined)
75
+ pv.repository = raw.repository;
76
+ versions[v] = pv;
77
+ }
78
+ return {
79
+ name: String(full.name),
80
+ "dist-tags": (full["dist-tags"] ?? {}),
81
+ time: (full.time ?? {}),
82
+ repository: full.repository,
83
+ versions,
84
+ };
85
+ }
@@ -0,0 +1,2 @@
1
+ export declare function readTarball(tgz: Uint8Array, keep: (path: string) => boolean): Map<string, Buffer>;
2
+ //# sourceMappingURL=tar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/registry/tar.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CACzB,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC9B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAsCrB"}