@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,75 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
// Reads a gzipped npm tarball in memory. Nothing touches the disk, so a hostile path cannot escape.
|
|
3
|
+
// Ustar prefixes, pax `path` records and GNU long names are all read; strips the first path segment,
|
|
4
|
+
// which npm writes as `package/` but some publishers name differently.
|
|
5
|
+
export function readTarball(tgz, keep) {
|
|
6
|
+
const buf = gunzipSync(tgz);
|
|
7
|
+
const out = new Map();
|
|
8
|
+
let offset = 0;
|
|
9
|
+
let paxPath;
|
|
10
|
+
let longName;
|
|
11
|
+
while (offset + 512 <= buf.length) {
|
|
12
|
+
const header = buf.subarray(offset, offset + 512);
|
|
13
|
+
if (header.every((b) => b === 0))
|
|
14
|
+
break;
|
|
15
|
+
const name = cstr(header.subarray(0, 100));
|
|
16
|
+
const size = parseOctal(header.subarray(124, 136));
|
|
17
|
+
const type = String.fromCharCode(header[156] ?? 48);
|
|
18
|
+
const magic = cstr(header.subarray(257, 263));
|
|
19
|
+
const prefix = magic.startsWith("ustar")
|
|
20
|
+
? cstr(header.subarray(345, 500))
|
|
21
|
+
: "";
|
|
22
|
+
const dataStart = offset + 512;
|
|
23
|
+
const data = buf.subarray(dataStart, dataStart + size);
|
|
24
|
+
offset = dataStart + Math.ceil(size / 512) * 512;
|
|
25
|
+
if (type === "x") {
|
|
26
|
+
paxPath = parsePax(data).path ?? paxPath;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (type === "g")
|
|
30
|
+
continue;
|
|
31
|
+
if (type === "L") {
|
|
32
|
+
longName = cstr(data);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const full = paxPath ?? longName ?? (prefix ? `${prefix}/${name}` : name);
|
|
36
|
+
paxPath = undefined;
|
|
37
|
+
longName = undefined;
|
|
38
|
+
if (type !== "0" && type !== "\0" && type !== "7")
|
|
39
|
+
continue;
|
|
40
|
+
const rel = full.replace(/^\/+/, "").split("/").slice(1).join("/");
|
|
41
|
+
if (!rel || rel.split("/").includes(".."))
|
|
42
|
+
continue;
|
|
43
|
+
if (keep(rel))
|
|
44
|
+
out.set(rel, Buffer.from(data));
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function cstr(b) {
|
|
49
|
+
const end = b.indexOf(0);
|
|
50
|
+
return Buffer.from(end < 0 ? b : b.subarray(0, end)).toString("utf8");
|
|
51
|
+
}
|
|
52
|
+
function parseOctal(b) {
|
|
53
|
+
// base-256 for sizes above 8 GiB never occurs in npm tarballs
|
|
54
|
+
const s = cstr(b).trim();
|
|
55
|
+
return s ? parseInt(s, 8) : 0;
|
|
56
|
+
}
|
|
57
|
+
function parsePax(data) {
|
|
58
|
+
const out = {};
|
|
59
|
+
const text = Buffer.from(data).toString("utf8");
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < text.length) {
|
|
62
|
+
const space = text.indexOf(" ", i);
|
|
63
|
+
if (space < 0)
|
|
64
|
+
break;
|
|
65
|
+
const len = parseInt(text.slice(i, space), 10);
|
|
66
|
+
if (!len)
|
|
67
|
+
break;
|
|
68
|
+
const record = text.slice(space + 1, i + len - 1);
|
|
69
|
+
const eq = record.indexOf("=");
|
|
70
|
+
if (eq > 0)
|
|
71
|
+
out[record.slice(0, eq)] = record.slice(eq + 1);
|
|
72
|
+
i += len;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Ctx } from "../options.ts";
|
|
2
|
+
import type { PackumentVersion } from "./packument.ts";
|
|
3
|
+
import { type RegistryConfig } from "./npmrc.ts";
|
|
4
|
+
export declare const CHANGELOG_FILE: RegExp;
|
|
5
|
+
export declare function isKeptFile(path: string): boolean;
|
|
6
|
+
export type TarballResult = {
|
|
7
|
+
ok: true;
|
|
8
|
+
files: Map<string, Buffer>;
|
|
9
|
+
integrity: string;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
reason: "offline-uncached" | "http-error" | "integrity-mismatch";
|
|
13
|
+
detail?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function getTarballFiles(ctx: Ctx, cfg: RegistryConfig, pv: PackumentVersion): Promise<TarballResult>;
|
|
16
|
+
//# sourceMappingURL=tarball.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tarball.d.ts","sourceRoot":"","sources":["../../src/registry/tarball.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,YAAY,CAAA;AAG7D,eAAO,MAAM,cAAc,QACgD,CAAA;AAE3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhD;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3D;IACE,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,kBAAkB,GAAG,YAAY,GAAG,oBAAoB,CAAA;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAIL,wBAAgB,eAAe,CAC7B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,gBAAgB,GACnB,OAAO,CAAC,aAAa,CAAC,CAaxB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readTarball } from "./tar.js";
|
|
2
|
+
import { OfflineError } from "../infra/http.js";
|
|
3
|
+
import { authHeaders } from "./npmrc.js";
|
|
4
|
+
import { integrityHex, sha1, verifyIntegrity } from "../infra/hash.js";
|
|
5
|
+
export const CHANGELOG_FILE = /^(changelog|changes|history|releases?|news)(\.(md|markdown|txt|rst))?$/i;
|
|
6
|
+
export function isKeptFile(path) {
|
|
7
|
+
return (path === "package.json" ||
|
|
8
|
+
/\.d\.[cm]?ts$/.test(path) ||
|
|
9
|
+
CHANGELOG_FILE.test(path));
|
|
10
|
+
}
|
|
11
|
+
const memory = new Map();
|
|
12
|
+
export function getTarballFiles(ctx, cfg, pv) {
|
|
13
|
+
const integrity = pv.dist.integrity ??
|
|
14
|
+
(pv.dist.shasum
|
|
15
|
+
? `sha1-${Buffer.from(pv.dist.shasum, "hex").toString("base64")}`
|
|
16
|
+
: "");
|
|
17
|
+
const id = integrityHex(integrity) ?? sha1(pv.dist.tarball);
|
|
18
|
+
let p = memory.get(id);
|
|
19
|
+
if (!p) {
|
|
20
|
+
p = load(ctx, cfg, pv, integrity, id);
|
|
21
|
+
memory.set(id, p);
|
|
22
|
+
}
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
async function load(ctx, cfg, pv, integrity, id) {
|
|
26
|
+
const key = `tarballs/${id}.tgz`;
|
|
27
|
+
let bytes = await ctx.cache.getBytes(key);
|
|
28
|
+
if (!bytes) {
|
|
29
|
+
if (ctx.offline)
|
|
30
|
+
return { ok: false, reason: "offline-uncached" };
|
|
31
|
+
try {
|
|
32
|
+
const res = await ctx.net(() => ctx.http.get({
|
|
33
|
+
url: pv.dist.tarball,
|
|
34
|
+
headers: authHeaders(cfg, pv.dist.tarball),
|
|
35
|
+
}));
|
|
36
|
+
if (res.status !== 200)
|
|
37
|
+
return { ok: false, reason: "http-error", detail: `HTTP ${res.status}` };
|
|
38
|
+
if (integrity && !verifyIntegrity(res.body, integrity))
|
|
39
|
+
return { ok: false, reason: "integrity-mismatch" };
|
|
40
|
+
bytes = res.body;
|
|
41
|
+
await ctx.cache.setBytes(key, bytes);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof OfflineError)
|
|
45
|
+
return { ok: false, reason: "offline-uncached" };
|
|
46
|
+
return { ok: false, reason: "http-error", detail: String(error) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return { ok: true, files: readTarball(bytes, isKeptFile), integrity };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: "http-error",
|
|
56
|
+
detail: `unreadable tarball: ${String(error)}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Brief, PackageBrief, Site } from "../model.ts";
|
|
2
|
+
export declare function oneNetOnly(p: PackageBrief): boolean;
|
|
3
|
+
export interface Groups {
|
|
4
|
+
detailed: PackageBrief[];
|
|
5
|
+
quiet: PackageBrief[];
|
|
6
|
+
unseen: PackageBrief[];
|
|
7
|
+
}
|
|
8
|
+
export declare function groupBriefs(brief: Brief): Groups;
|
|
9
|
+
export declare function isUnseenOnly(p: PackageBrief): boolean;
|
|
10
|
+
export declare function sitesFor(p: PackageBrief, names: string[]): Site[];
|
|
11
|
+
export declare function siteLabel(s: Site): string;
|
|
12
|
+
export declare function ago(iso: string, now: number): string;
|
|
13
|
+
export declare function opaqueLabel(p: PackageBrief): string;
|
|
14
|
+
export declare function coverageLabel(p: PackageBrief): string;
|
|
15
|
+
export declare function surfaceLabel(p: PackageBrief): string;
|
|
16
|
+
//# sourceMappingURL=groups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/render/groups.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAa5D,wBAAgB,UAAU,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAInD;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,MAAM,EAAE,YAAY,EAAE,CAAA;CACvB;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAUhD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAMrD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAOjE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAIzC;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAanD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAkBrD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAkBpD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { UNSEEN_LABEL } from "../labels.js";
|
|
2
|
+
// reasons that say "the tool cannot see", as opposed to "something changed that you use"
|
|
3
|
+
const UNSEEN = new Set([
|
|
4
|
+
"opaque-usage",
|
|
5
|
+
"not-referenced",
|
|
6
|
+
"blind-spots",
|
|
7
|
+
"no-evidence",
|
|
8
|
+
"flagged-install",
|
|
9
|
+
"surface-incomplete",
|
|
10
|
+
]);
|
|
11
|
+
// a quiet brief that stood on one net: said next to its name, not only in the JSON
|
|
12
|
+
export function oneNetOnly(p) {
|
|
13
|
+
return p.reasons.some((r) => r.code === "caveat-no-types" || r.code === "caveat-no-notes");
|
|
14
|
+
}
|
|
15
|
+
// Review briefs whose only reason is "cannot see how you use it" are listed on one line each: a full
|
|
16
|
+
// block per linter plugin would bury the packages that actually changed under you.
|
|
17
|
+
export function groupBriefs(brief) {
|
|
18
|
+
const order = { blocked: 0, review: 1, quiet: 2 };
|
|
19
|
+
const sorted = [...brief.packages].sort((a, b) => order[a.verdict] - order[b.verdict] || a.pkg.localeCompare(b.pkg));
|
|
20
|
+
return {
|
|
21
|
+
detailed: sorted.filter((p) => p.verdict !== "quiet" && !isUnseenOnly(p)),
|
|
22
|
+
quiet: sorted.filter((p) => p.verdict === "quiet"),
|
|
23
|
+
unseen: sorted.filter((p) => p.verdict !== "quiet" && isUnseenOnly(p)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function isUnseenOnly(p) {
|
|
27
|
+
return (p.verdict === "review" &&
|
|
28
|
+
p.reasons.every((r) => UNSEEN.has(r.code)) &&
|
|
29
|
+
p.reasons.some((r) => r.code !== "no-evidence"));
|
|
30
|
+
}
|
|
31
|
+
export function sitesFor(p, names) {
|
|
32
|
+
const seen = new Map();
|
|
33
|
+
for (const n of names)
|
|
34
|
+
for (const s of p.usage.byName[n] ?? [])
|
|
35
|
+
seen.set(`${s.file}:${s.line}`, s);
|
|
36
|
+
return [...seen.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
37
|
+
}
|
|
38
|
+
export function siteLabel(s) {
|
|
39
|
+
const via = s.via && s.via.length > 0 ? ` (via ${s.via[s.via.length - 1]})` : "";
|
|
40
|
+
return `${s.file}:${s.line}${s.typeOnly ? " type" : ""}${via}`;
|
|
41
|
+
}
|
|
42
|
+
export function ago(iso, now) {
|
|
43
|
+
const ms = now - Date.parse(iso);
|
|
44
|
+
if (!Number.isFinite(ms))
|
|
45
|
+
return "unknown";
|
|
46
|
+
if (ms < 3_600_000)
|
|
47
|
+
return `${Math.max(1, Math.round(ms / 60_000))}m ago`;
|
|
48
|
+
if (ms < 172_800_000)
|
|
49
|
+
return `${Math.round(ms / 3_600_000)}h ago`;
|
|
50
|
+
return `${Math.round(ms / 86_400_000)}d ago`;
|
|
51
|
+
}
|
|
52
|
+
export function opaqueLabel(p) {
|
|
53
|
+
const kinds = [
|
|
54
|
+
...p.usage.opaque.map((o) => o.kind),
|
|
55
|
+
...p.usage.blindSpots.map((b) => b.kind),
|
|
56
|
+
];
|
|
57
|
+
const labels = [...new Set(kinds.map((k) => UNSEEN_LABEL[k] ?? k))];
|
|
58
|
+
if (p.reasons.some((r) => r.code === "not-referenced"))
|
|
59
|
+
labels.push("not referenced by any scanned file");
|
|
60
|
+
for (const code of ["flagged-install", "surface-incomplete"]) {
|
|
61
|
+
const r = p.reasons.find((x) => x.code === code);
|
|
62
|
+
if (r)
|
|
63
|
+
labels.push(r.detail);
|
|
64
|
+
}
|
|
65
|
+
return labels.join("; ");
|
|
66
|
+
}
|
|
67
|
+
export function coverageLabel(p) {
|
|
68
|
+
const n = p.notes;
|
|
69
|
+
if (n.coverage === "disabled")
|
|
70
|
+
return "notes disabled";
|
|
71
|
+
const found = n.perVersion.filter((v) => v.status === "found").length;
|
|
72
|
+
const total = n.perVersion.length;
|
|
73
|
+
const sources = [
|
|
74
|
+
...new Set(n.perVersion.map((v) => v.source?.kind).filter(Boolean)),
|
|
75
|
+
].join(", ");
|
|
76
|
+
if (n.coverage === "complete")
|
|
77
|
+
return `notes for ${total}/${total} versions${sources ? ` (${sources})` : ""}`;
|
|
78
|
+
if (n.coverage === "partial")
|
|
79
|
+
return `notes for ${found}/${total} versions${sources ? ` (${sources})` : ""}`;
|
|
80
|
+
if (n.coverage === "none-published")
|
|
81
|
+
return "this author publishes no notes; behaviour changes are invisible here";
|
|
82
|
+
const reasons = [
|
|
83
|
+
...new Set(n.perVersion.map((v) => v.reason).filter(Boolean)),
|
|
84
|
+
].join(", ");
|
|
85
|
+
return `notes unavailable${reasons ? ` (${reasons})` : ""}`;
|
|
86
|
+
}
|
|
87
|
+
export function surfaceLabel(p) {
|
|
88
|
+
const s = p.surface;
|
|
89
|
+
switch (s.status) {
|
|
90
|
+
case "computed":
|
|
91
|
+
return `${s.changes ?? 0}`;
|
|
92
|
+
case "no-types":
|
|
93
|
+
return `unknown (${s.detail ?? "no types"})`;
|
|
94
|
+
case "types-from-@types":
|
|
95
|
+
return "unknown (types live in @types)";
|
|
96
|
+
case "types-elsewhere":
|
|
97
|
+
return "unknown (types re-exported from another package)";
|
|
98
|
+
case "disabled":
|
|
99
|
+
return s.detail ?? "not computed (--no-surface)";
|
|
100
|
+
case "offline-uncached":
|
|
101
|
+
return "unknown (offline, not cached)";
|
|
102
|
+
case "failed":
|
|
103
|
+
return `unknown (${s.detail ?? "extraction failed"})`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,GAAG,EACH,aAAa,EACb,WAAW,EACX,KAAK,MAAM,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,YAAY,GACb,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Building blocks for a renderer of your own: how briefs are grouped and labelled, shared by the
|
|
2
|
+
// markdown renderer here and the terminal renderer of the CLI.
|
|
3
|
+
export { ago, coverageLabel, groupBriefs, isUnseenOnly, oneNetOnly, opaqueLabel, siteLabel, sitesFor, surfaceLabel, } from "./groups.js";
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Brief, VerdictReason } from "../model.ts";
|
|
2
|
+
export interface SiteV1 {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
typeOnly: boolean;
|
|
7
|
+
code: string;
|
|
8
|
+
via?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface CannotSeeV1 {
|
|
11
|
+
kind: string;
|
|
12
|
+
count: number;
|
|
13
|
+
examples: SiteV1[];
|
|
14
|
+
}
|
|
15
|
+
export interface PackageV1 {
|
|
16
|
+
name: string;
|
|
17
|
+
from: string;
|
|
18
|
+
to: string;
|
|
19
|
+
bump: "major" | "minor" | "patch";
|
|
20
|
+
publishedAt: string;
|
|
21
|
+
verdict: "quiet" | "review" | "blocked";
|
|
22
|
+
reasons: VerdictReason[];
|
|
23
|
+
versionSource: string;
|
|
24
|
+
manifests: string[];
|
|
25
|
+
types: {
|
|
26
|
+
status: string;
|
|
27
|
+
detail?: string;
|
|
28
|
+
changes?: number;
|
|
29
|
+
added?: number;
|
|
30
|
+
touched: {
|
|
31
|
+
path: string;
|
|
32
|
+
alsoAt: string[];
|
|
33
|
+
change: "removed" | "changed" | "deprecated";
|
|
34
|
+
certainty: "exact" | "by-name";
|
|
35
|
+
before?: string[];
|
|
36
|
+
after?: string[];
|
|
37
|
+
sites: SiteV1[];
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
notes: {
|
|
41
|
+
coverage: string;
|
|
42
|
+
entries: number;
|
|
43
|
+
versions: {
|
|
44
|
+
version: string;
|
|
45
|
+
status: string;
|
|
46
|
+
source?: {
|
|
47
|
+
kind: string;
|
|
48
|
+
url: string;
|
|
49
|
+
};
|
|
50
|
+
reason?: string;
|
|
51
|
+
}[];
|
|
52
|
+
matched: {
|
|
53
|
+
version: string;
|
|
54
|
+
title: string;
|
|
55
|
+
breaking: boolean;
|
|
56
|
+
direct: boolean;
|
|
57
|
+
refs: string[];
|
|
58
|
+
names: {
|
|
59
|
+
name: string;
|
|
60
|
+
certainty: "exact" | "by-name";
|
|
61
|
+
where: string;
|
|
62
|
+
}[];
|
|
63
|
+
}[];
|
|
64
|
+
breakingWithoutApi: {
|
|
65
|
+
version: string;
|
|
66
|
+
title: string;
|
|
67
|
+
refs: string[];
|
|
68
|
+
}[];
|
|
69
|
+
};
|
|
70
|
+
usage: {
|
|
71
|
+
files: number;
|
|
72
|
+
sites: number;
|
|
73
|
+
names: {
|
|
74
|
+
exact: string[];
|
|
75
|
+
byName: string[];
|
|
76
|
+
};
|
|
77
|
+
sitesByName: Record<string, SiteV1[]>;
|
|
78
|
+
cannotSee: CannotSeeV1[];
|
|
79
|
+
};
|
|
80
|
+
heldBack: {
|
|
81
|
+
version: string;
|
|
82
|
+
reason: string;
|
|
83
|
+
publishedAt?: string;
|
|
84
|
+
}[];
|
|
85
|
+
alsoAvailable?: {
|
|
86
|
+
version: string;
|
|
87
|
+
bump: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface BriefV1 {
|
|
91
|
+
schemaVersion: 1;
|
|
92
|
+
tool: {
|
|
93
|
+
name: "dep-radius";
|
|
94
|
+
version: string;
|
|
95
|
+
typescript: string;
|
|
96
|
+
};
|
|
97
|
+
root: string;
|
|
98
|
+
generatedAt: string;
|
|
99
|
+
exitCode: 0 | 1 | 2;
|
|
100
|
+
summary: {
|
|
101
|
+
manifests: number;
|
|
102
|
+
withUpdate: number;
|
|
103
|
+
upToDate: number;
|
|
104
|
+
quiet: number;
|
|
105
|
+
review: number;
|
|
106
|
+
blocked: number;
|
|
107
|
+
notAnalyzed: number;
|
|
108
|
+
};
|
|
109
|
+
packages: PackageV1[];
|
|
110
|
+
notAnalyzed: {
|
|
111
|
+
name: string;
|
|
112
|
+
reason: string;
|
|
113
|
+
}[];
|
|
114
|
+
wholeProject: CannotSeeV1[];
|
|
115
|
+
limits: string[];
|
|
116
|
+
}
|
|
117
|
+
export declare function toJsonV1(brief: Brief): BriefV1;
|
|
118
|
+
export declare function renderJson(brief: Brief): string;
|
|
119
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/render/json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export function toJsonV1(brief) {
|
|
2
|
+
const count = (v) => brief.packages.filter((p) => p.verdict === v).length;
|
|
3
|
+
return {
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
tool: {
|
|
6
|
+
name: "dep-radius",
|
|
7
|
+
version: brief.tool.version,
|
|
8
|
+
typescript: brief.tool.typescript,
|
|
9
|
+
},
|
|
10
|
+
root: brief.root,
|
|
11
|
+
generatedAt: brief.generatedAt,
|
|
12
|
+
exitCode: brief.exitCode,
|
|
13
|
+
summary: {
|
|
14
|
+
manifests: brief.manifests,
|
|
15
|
+
withUpdate: brief.packages.length,
|
|
16
|
+
upToDate: brief.upToDate,
|
|
17
|
+
quiet: count("quiet"),
|
|
18
|
+
review: count("review"),
|
|
19
|
+
blocked: count("blocked"),
|
|
20
|
+
notAnalyzed: brief.notAnalyzed.length,
|
|
21
|
+
},
|
|
22
|
+
packages: brief.packages.map(packageV1),
|
|
23
|
+
notAnalyzed: brief.notAnalyzed.map((n) => ({
|
|
24
|
+
name: n.pkg,
|
|
25
|
+
reason: n.reason,
|
|
26
|
+
})),
|
|
27
|
+
wholeProject: brief.global.map(countedV1),
|
|
28
|
+
limits: brief.limits,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function renderJson(brief) {
|
|
32
|
+
return `${JSON.stringify(toJsonV1(brief), null, 2)}\n`;
|
|
33
|
+
}
|
|
34
|
+
function packageV1(p) {
|
|
35
|
+
return {
|
|
36
|
+
name: p.pkg,
|
|
37
|
+
from: p.from,
|
|
38
|
+
to: p.to,
|
|
39
|
+
bump: p.bump,
|
|
40
|
+
publishedAt: p.publishedAt,
|
|
41
|
+
verdict: p.verdict,
|
|
42
|
+
reasons: p.reasons.map((r) => ({ code: r.code, detail: r.detail })),
|
|
43
|
+
versionSource: p.versionSource,
|
|
44
|
+
manifests: p.manifests,
|
|
45
|
+
types: {
|
|
46
|
+
status: p.surface.status,
|
|
47
|
+
...(p.surface.detail !== undefined ? { detail: p.surface.detail } : {}),
|
|
48
|
+
...(p.surface.changes !== undefined
|
|
49
|
+
? { changes: p.surface.changes }
|
|
50
|
+
: {}),
|
|
51
|
+
...(p.surface.added !== undefined ? { added: p.surface.added } : {}),
|
|
52
|
+
touched: p.surface.touched.map((t) => ({
|
|
53
|
+
path: t.change.path,
|
|
54
|
+
alsoAt: t.change.alsoAt,
|
|
55
|
+
change: t.bucket,
|
|
56
|
+
certainty: t.strength === "strong" ? "exact" : "by-name",
|
|
57
|
+
...(t.change.before ? { before: t.change.before } : {}),
|
|
58
|
+
...(t.change.after ? { after: t.change.after } : {}),
|
|
59
|
+
sites: t.sites.map(siteV1),
|
|
60
|
+
})),
|
|
61
|
+
},
|
|
62
|
+
notes: {
|
|
63
|
+
coverage: p.notes.coverage,
|
|
64
|
+
entries: p.notes.total,
|
|
65
|
+
versions: p.notes.perVersion.map((v) => ({
|
|
66
|
+
version: v.version,
|
|
67
|
+
status: v.status,
|
|
68
|
+
...(v.source
|
|
69
|
+
? { source: { kind: v.source.kind, url: v.source.url } }
|
|
70
|
+
: {}),
|
|
71
|
+
...(v.reason ? { reason: v.reason } : {}),
|
|
72
|
+
})),
|
|
73
|
+
matched: p.notes.matched.map((m) => ({
|
|
74
|
+
version: m.entry.version,
|
|
75
|
+
title: m.entry.title,
|
|
76
|
+
breaking: m.entry.breakingMarker,
|
|
77
|
+
direct: m.direct,
|
|
78
|
+
refs: m.entry.refs,
|
|
79
|
+
names: m.hits.map((h) => ({
|
|
80
|
+
name: h.name,
|
|
81
|
+
certainty: h.strength === "strong" ? "exact" : "by-name",
|
|
82
|
+
where: h.region,
|
|
83
|
+
})),
|
|
84
|
+
})),
|
|
85
|
+
breakingWithoutApi: p.notes.unattributedBreaking.map(entryV1),
|
|
86
|
+
},
|
|
87
|
+
usage: {
|
|
88
|
+
files: p.usage.files,
|
|
89
|
+
sites: p.usage.sites.length,
|
|
90
|
+
names: { exact: p.usage.strongNames, byName: p.usage.weakNames },
|
|
91
|
+
sitesByName: Object.fromEntries(Object.entries(p.usage.byName).map(([name, sites]) => [
|
|
92
|
+
name,
|
|
93
|
+
sites.map(siteV1),
|
|
94
|
+
])),
|
|
95
|
+
cannotSee: [
|
|
96
|
+
...p.usage.opaque.map(countedV1),
|
|
97
|
+
...p.usage.blindSpots.map(countedV1),
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
heldBack: p.skippedNewer.map((s) => ({
|
|
101
|
+
version: s.version,
|
|
102
|
+
reason: s.reason,
|
|
103
|
+
...(s.publishedAt ? { publishedAt: s.publishedAt } : {}),
|
|
104
|
+
})),
|
|
105
|
+
...(p.alsoAvailable
|
|
106
|
+
? {
|
|
107
|
+
alsoAvailable: {
|
|
108
|
+
version: p.alsoAvailable.version,
|
|
109
|
+
bump: p.alsoAvailable.bump,
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
: {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function siteV1(s) {
|
|
116
|
+
return {
|
|
117
|
+
file: s.file,
|
|
118
|
+
line: s.line,
|
|
119
|
+
column: s.col,
|
|
120
|
+
typeOnly: s.typeOnly,
|
|
121
|
+
code: s.text,
|
|
122
|
+
...(s.via && s.via.length > 0 ? { via: s.via } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function countedV1(c) {
|
|
126
|
+
return { kind: c.kind, count: c.count, examples: c.examples.map(siteV1) };
|
|
127
|
+
}
|
|
128
|
+
function entryV1(e) {
|
|
129
|
+
return { version: e.version, title: e.title, refs: e.refs };
|
|
130
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/render/limits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGtD,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,GACtB,MAAM,EAAE,CAoDV"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Only the limits that bore on this run, so the footer stays read.
|
|
2
|
+
export function limitsFor(packages, global) {
|
|
3
|
+
const out = [];
|
|
4
|
+
if (packages.length === 0)
|
|
5
|
+
return out;
|
|
6
|
+
out.push("Behaviour changes are seen only through release notes; the types and the notes are counted separately, never merged.");
|
|
7
|
+
if (packages.some((p) => p.usage.weakNames.length > 0))
|
|
8
|
+
out.push("No type checker runs over your code: members reached through values from other files or callbacks are matched by name only.");
|
|
9
|
+
if (packages.some((p) => p.surface.status === "no-types" ||
|
|
10
|
+
p.surface.status === "types-from-@types"))
|
|
11
|
+
out.push("Packages without their own types are judged from release notes alone; @types packages are analysed as their own dependency.");
|
|
12
|
+
if (packages.some((p) => p.notes.coverage !== "complete" && p.notes.coverage !== "disabled"))
|
|
13
|
+
out.push("Release notes are read from the package's changelog and GitHub only; other hosts count as unavailable.");
|
|
14
|
+
const g = new Map(global.map((x) => [x.kind, x.count]));
|
|
15
|
+
const dyn = (g.get("dynamic-import-nonliteral") ?? 0) +
|
|
16
|
+
(g.get("require-nonliteral") ?? 0);
|
|
17
|
+
if (dyn > 0)
|
|
18
|
+
out.push(`${dyn} import() or require() calls with a computed name could not be attributed to any package.`);
|
|
19
|
+
if (g.get("unparseable-file"))
|
|
20
|
+
out.push(`${g.get("unparseable-file")} files did not parse cleanly; names in them may be missing.`);
|
|
21
|
+
if (packages.some((p) => p.usage.opaque.some((o) => o.kind === "convention-framework")))
|
|
22
|
+
out.push("Frameworks used through file conventions are never judged quiet: their imports show little of what depends on them.");
|
|
23
|
+
out.push("Local names that shadow an import are not analysed, and transitive dependencies are out of scope.");
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/render/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,aAAa,CAAA;AAatD,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAmChE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ago, coverageLabel, groupBriefs, oneNetOnly, opaqueLabel, siteLabel, sitesFor, surfaceLabel, } from "./groups.js";
|
|
2
|
+
// A pull request comment: one table, then a section per package that needs a look.
|
|
3
|
+
export function renderMarkdown(brief, now) {
|
|
4
|
+
const g = groupBriefs(brief);
|
|
5
|
+
const out = [];
|
|
6
|
+
const icon = { blocked: "๐ blocked", review: "๐ review", quiet: "โ
quiet" };
|
|
7
|
+
out.push("### radius");
|
|
8
|
+
out.push("");
|
|
9
|
+
if (brief.packages.length === 0) {
|
|
10
|
+
out.push("No dependency has an eligible update.");
|
|
11
|
+
return `${out.join("\n")}\n`;
|
|
12
|
+
}
|
|
13
|
+
out.push("| package | update | verdict | surface changes | changes you touch | notes mentioning you |");
|
|
14
|
+
out.push("| --- | --- | --- | --- | --- | --- |");
|
|
15
|
+
for (const p of [...g.detailed, ...g.unseen, ...g.quiet]) {
|
|
16
|
+
const direct = p.notes.matched.filter((m) => m.direct).length;
|
|
17
|
+
const possibly = p.notes.matched.length - direct;
|
|
18
|
+
out.push(`| \`${p.pkg}\` | ${p.from} โ ${p.to} (${p.bump}) | ${icon[p.verdict]}${oneNetOnly(p) ? " (one net)" : ""} | ${surfaceLabel(p)} | ${p.surface.status === "computed" ? p.surface.touched.length : "n/a"} | ${p.notes.coverage === "disabled" ? "not read" : `${direct}${possibly ? ` (+${possibly} possibly)` : ""}`} |`);
|
|
19
|
+
}
|
|
20
|
+
out.push("");
|
|
21
|
+
for (const p of g.detailed)
|
|
22
|
+
out.push(...section(p, now), "");
|
|
23
|
+
if (g.unseen.length > 0) {
|
|
24
|
+
out.push("<details><summary>Cannot see how you use these</summary>", "");
|
|
25
|
+
for (const p of g.unseen)
|
|
26
|
+
out.push(`- \`${p.pkg}\` ${p.from} โ ${p.to}: ${opaqueLabel(p)}`);
|
|
27
|
+
out.push("", "</details>", "");
|
|
28
|
+
}
|
|
29
|
+
if (brief.limits.length > 0) {
|
|
30
|
+
out.push("<details><summary>Limits</summary>", "");
|
|
31
|
+
for (const l of brief.limits)
|
|
32
|
+
out.push(`- ${l}`);
|
|
33
|
+
out.push("", "</details>");
|
|
34
|
+
}
|
|
35
|
+
return `${out.join("\n")}\n`;
|
|
36
|
+
}
|
|
37
|
+
function section(p, now) {
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push(`#### \`${p.pkg}\` ${p.from} โ ${p.to}`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(`Published ${ago(p.publishedAt, now)} ยท ${coverageLabel(p)} ยท used in ${p.usage.files} files`);
|
|
42
|
+
lines.push("");
|
|
43
|
+
for (const t of p.surface.touched) {
|
|
44
|
+
lines.push(`- **${t.bucket}** \`${t.change.path}\`${t.strength === "weak" ? " (by member name)" : ""}`);
|
|
45
|
+
for (const s of t.sites.slice(0, 5))
|
|
46
|
+
lines.push(` - \`${siteLabel(s)}\``);
|
|
47
|
+
}
|
|
48
|
+
for (const m of p.notes.matched) {
|
|
49
|
+
const names = [...new Set(m.hits.map((h) => h.name))];
|
|
50
|
+
lines.push(`- ${m.entry.version}: ${m.entry.title}${m.direct ? "" : " _(possibly)_"}. You use ${names.map((n) => `\`${n}\``).join(", ")}`);
|
|
51
|
+
const strong = m.hits
|
|
52
|
+
.filter((h) => h.strength === "strong")
|
|
53
|
+
.map((h) => h.name);
|
|
54
|
+
for (const s of sitesFor(p, strong.length > 0 ? strong : names).slice(0, m.direct ? 3 : 1))
|
|
55
|
+
lines.push(` - \`${siteLabel(s)}\``);
|
|
56
|
+
}
|
|
57
|
+
for (const e of p.notes.unattributedBreaking)
|
|
58
|
+
lines.push(`- ${e.version}: ${e.title} _(breaking, names no API)_`);
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(`<sub>${p.reasons.map((r) => r.detail).join(" ยท ")}</sub>`);
|
|
61
|
+
return lines;
|
|
62
|
+
}
|