shipseal 0.0.6 → 0.0.8
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/README.md +1 -1
- package/dist/cli.js +207 -130
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -322,12 +322,27 @@ function deterministicCopy(facts, maxHighlights = 4) {
|
|
|
322
322
|
for (const line of breaking) addHighlight(line, "Breaking: ");
|
|
323
323
|
for (const line of features) addHighlight(line);
|
|
324
324
|
for (const line of fixes) addHighlight(line);
|
|
325
|
-
|
|
325
|
+
const copy = {
|
|
326
326
|
headline,
|
|
327
327
|
subheadline,
|
|
328
328
|
highlights,
|
|
329
329
|
cta: ctaFor(facts)
|
|
330
330
|
};
|
|
331
|
+
const snippet = facts.release?.codeSnippet?.value.code;
|
|
332
|
+
if (snippet !== void 0 && isInstallOnly(snippet)) copy.codeTitle = "Get started";
|
|
333
|
+
return copy;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Is this snippet nothing but install commands?
|
|
337
|
+
*
|
|
338
|
+
* Pairing "npm i thing" with a release headline reads as though the release was about
|
|
339
|
+
* installing, which is how v0.0.6 shipped a bug-fix headline over the README's install block.
|
|
340
|
+
* Install commands are still worth showing, they just need their own title.
|
|
341
|
+
*/
|
|
342
|
+
const INSTALL_COMMAND = /^\s*(?:\$\s*)?(?:npm\s+(?:i|install|add)|pnpm\s+(?:i|install|add|dlx)|yarn\s+(?:add|install)|bun\s+(?:a|add|install)|npx|deno\s+add|pip\s+install|cargo\s+add|go\s+get|gem\s+install|brew\s+install)\b/;
|
|
343
|
+
function isInstallOnly(code) {
|
|
344
|
+
const lines = code.split("\n").map((line) => line.replace(/\s+#.*$/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
345
|
+
return lines.length > 0 && lines.every((line) => INSTALL_COMMAND.test(line));
|
|
331
346
|
}
|
|
332
347
|
function formatCount$1(value) {
|
|
333
348
|
return value.toLocaleString("en-US");
|
|
@@ -588,7 +603,7 @@ const codeCard = {
|
|
|
588
603
|
});
|
|
589
604
|
return {
|
|
590
605
|
props: {
|
|
591
|
-
headline: copy.headline,
|
|
606
|
+
headline: copy.codeTitle ?? copy.headline,
|
|
592
607
|
headingFamily: brand.fonts.heading.family,
|
|
593
608
|
monoFamily: brand.fonts.mono.family,
|
|
594
609
|
headingWeight: brand.fonts.heading.weight,
|
|
@@ -1695,6 +1710,20 @@ async function copyForTemplate(templateId, copy, facts, theme) {
|
|
|
1695
1710
|
};
|
|
1696
1711
|
}
|
|
1697
1712
|
|
|
1713
|
+
//#endregion
|
|
1714
|
+
//#region src/facts/fact.ts
|
|
1715
|
+
function fact(value, provenance) {
|
|
1716
|
+
const fetchedAt = provenance.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1717
|
+
return {
|
|
1718
|
+
value,
|
|
1719
|
+
provenance: {
|
|
1720
|
+
source: provenance.source,
|
|
1721
|
+
ref: provenance.ref,
|
|
1722
|
+
fetchedAt
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1698
1727
|
//#endregion
|
|
1699
1728
|
//#region src/facts/merge.ts
|
|
1700
1729
|
function mergeFacts(parts) {
|
|
@@ -1767,20 +1796,6 @@ function isCompleteRelease(release) {
|
|
|
1767
1796
|
return release.version !== void 0 && release.tag !== void 0 && release.date !== void 0;
|
|
1768
1797
|
}
|
|
1769
1798
|
|
|
1770
|
-
//#endregion
|
|
1771
|
-
//#region src/facts/fact.ts
|
|
1772
|
-
function fact(value, provenance) {
|
|
1773
|
-
const fetchedAt = provenance.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1774
|
-
return {
|
|
1775
|
-
value,
|
|
1776
|
-
provenance: {
|
|
1777
|
-
source: provenance.source,
|
|
1778
|
-
ref: provenance.ref,
|
|
1779
|
-
fetchedAt
|
|
1780
|
-
}
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1799
|
//#endregion
|
|
1785
1800
|
//#region src/sources/bench-file.ts
|
|
1786
1801
|
const metricSchema = z.object({
|
|
@@ -1855,6 +1870,117 @@ async function collectBenchFile(cwd, filePath = ".shipseal/bench.json") {
|
|
|
1855
1870
|
return { bench };
|
|
1856
1871
|
}
|
|
1857
1872
|
|
|
1873
|
+
//#endregion
|
|
1874
|
+
//#region src/sources/readme.ts
|
|
1875
|
+
async function collectReadme(cwd) {
|
|
1876
|
+
let markdown;
|
|
1877
|
+
try {
|
|
1878
|
+
markdown = await readFile(join(cwd, "README.md"), "utf8");
|
|
1879
|
+
} catch {
|
|
1880
|
+
return {};
|
|
1881
|
+
}
|
|
1882
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1883
|
+
const out = {};
|
|
1884
|
+
const h1 = extractH1(markdown);
|
|
1885
|
+
const tagline = extractTagline(markdown);
|
|
1886
|
+
const snippet = extractFirstCodeFence(markdown);
|
|
1887
|
+
const project = {};
|
|
1888
|
+
if (h1 !== void 0) project.name = fact(h1, {
|
|
1889
|
+
source: "readme",
|
|
1890
|
+
ref: "README.md H1",
|
|
1891
|
+
fetchedAt
|
|
1892
|
+
});
|
|
1893
|
+
if (tagline !== void 0) project.tagline = fact(tagline, {
|
|
1894
|
+
source: "readme",
|
|
1895
|
+
ref: "README.md first paragraph",
|
|
1896
|
+
fetchedAt
|
|
1897
|
+
});
|
|
1898
|
+
if (project.name !== void 0 || project.tagline !== void 0) out.project = project;
|
|
1899
|
+
if (snippet !== void 0) out.release = { codeSnippet: fact(snippet, {
|
|
1900
|
+
source: "readme",
|
|
1901
|
+
ref: "README.md first fenced code block",
|
|
1902
|
+
fetchedAt
|
|
1903
|
+
}) };
|
|
1904
|
+
return out;
|
|
1905
|
+
}
|
|
1906
|
+
async function collectConfiguredSnippet(cwd, spec) {
|
|
1907
|
+
const parsed = parseSnippetSpec(spec);
|
|
1908
|
+
if (parsed === void 0) return {};
|
|
1909
|
+
let contents;
|
|
1910
|
+
try {
|
|
1911
|
+
contents = await readFile(join(cwd, parsed.path), "utf8");
|
|
1912
|
+
} catch {
|
|
1913
|
+
return {};
|
|
1914
|
+
}
|
|
1915
|
+
const slice = contents.split(/\r?\n/).slice(parsed.start - 1, parsed.end).join("\n");
|
|
1916
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1917
|
+
return { release: { codeSnippet: fact({
|
|
1918
|
+
code: slice,
|
|
1919
|
+
lang: langFromPath(parsed.path)
|
|
1920
|
+
}, {
|
|
1921
|
+
source: "user-config",
|
|
1922
|
+
ref: spec,
|
|
1923
|
+
fetchedAt
|
|
1924
|
+
}) } };
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Strip fenced code and HTML before reading prose out of a README.
|
|
1928
|
+
*
|
|
1929
|
+
* Skipping blocks that merely *start* with a fence is not enough. A `#` comment inside a YAML
|
|
1930
|
+
* example reads as a markdown H1, and a blank line inside a fence makes its body look like a
|
|
1931
|
+
* paragraph. Shipseal's own README hit both: the detected name became
|
|
1932
|
+
* ".github/workflows/shipseal.yml" and the tagline became a chunk of workflow YAML.
|
|
1933
|
+
*/
|
|
1934
|
+
function stripNonProse(markdown) {
|
|
1935
|
+
return markdown.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/<[^>]+>/g, "");
|
|
1936
|
+
}
|
|
1937
|
+
function extractH1(markdown) {
|
|
1938
|
+
const heading = /^#\s+(.+)$/m.exec(stripNonProse(markdown))?.[1];
|
|
1939
|
+
if (heading === void 0) return;
|
|
1940
|
+
return heading.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
|
|
1941
|
+
}
|
|
1942
|
+
function extractTagline(markdown) {
|
|
1943
|
+
const prose = stripNonProse(markdown);
|
|
1944
|
+
const h1 = /^#\s+.+$/m.exec(prose);
|
|
1945
|
+
const start = h1 === null ? 0 : (h1.index ?? 0) + h1[0].length;
|
|
1946
|
+
for (const block of prose.slice(start).split(/\n\s*\n/)) {
|
|
1947
|
+
const cleaned = stripBadges(block).trim();
|
|
1948
|
+
if (cleaned.length >= 20 && !cleaned.startsWith("#") && !cleaned.startsWith("```")) return cleaned;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
function extractFirstCodeFence(markdown) {
|
|
1952
|
+
const match = /```([a-zA-Z0-9+-]+)\n([\s\S]*?)```/.exec(markdown);
|
|
1953
|
+
if (match === null || match[1] === void 0 || match[2] === void 0) return;
|
|
1954
|
+
return {
|
|
1955
|
+
lang: match[1],
|
|
1956
|
+
code: match[2].replace(/\n$/, "")
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
function parseSnippetSpec(spec) {
|
|
1960
|
+
const match = /^(?<path>.+)#L(?<start>\d+)(?:-L(?<end>\d+))?$/.exec(spec);
|
|
1961
|
+
if (match === null || match.groups === void 0) return;
|
|
1962
|
+
const path = match.groups.path;
|
|
1963
|
+
const start = Number.parseInt(match.groups.start ?? "0", 10);
|
|
1964
|
+
const end = Number.parseInt(match.groups.end ?? match.groups.start ?? "0", 10);
|
|
1965
|
+
if (path === void 0 || start < 1 || end < start) return;
|
|
1966
|
+
return {
|
|
1967
|
+
path,
|
|
1968
|
+
start,
|
|
1969
|
+
end
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function langFromPath(path) {
|
|
1973
|
+
const ext = extname(path).replace(".", "");
|
|
1974
|
+
if (ext === "ts") return "ts";
|
|
1975
|
+
if (ext === "tsx") return "tsx";
|
|
1976
|
+
if (ext === "js" || ext === "mjs" || ext === "cjs") return "js";
|
|
1977
|
+
if (ext === "jsx") return "jsx";
|
|
1978
|
+
return ext.length > 0 ? ext : "text";
|
|
1979
|
+
}
|
|
1980
|
+
function stripBadges(text) {
|
|
1981
|
+
return text.replace(/\[!\[[^\]]*]\([^)]+\)]\([^)]+\)/g, "").replace(/!\[[^\]]*]\([^)]+\)/g, "").replace(/<img[^>]*>/gi, "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1858
1984
|
//#endregion
|
|
1859
1985
|
//#region src/sources/changelog.ts
|
|
1860
1986
|
async function collectChangelog(cwd, version, changelogPath = "CHANGELOG.md") {
|
|
@@ -1902,6 +2028,15 @@ async function collectChangelog(cwd, version, changelogPath = "CHANGELOG.md") {
|
|
|
1902
2028
|
fixes,
|
|
1903
2029
|
breaking
|
|
1904
2030
|
} };
|
|
2031
|
+
const snippet = extractFirstCodeFence(section.body);
|
|
2032
|
+
if (snippet !== void 0) out.release = {
|
|
2033
|
+
...out.release,
|
|
2034
|
+
codeSnippet: fact(snippet, {
|
|
2035
|
+
source: "changelog",
|
|
2036
|
+
ref: `${changelogPath} ${section.heading} first fenced code block`,
|
|
2037
|
+
fetchedAt
|
|
2038
|
+
})
|
|
2039
|
+
};
|
|
1905
2040
|
if (section.date !== void 0) out.release = {
|
|
1906
2041
|
...out.release,
|
|
1907
2042
|
date: fact(section.date, {
|
|
@@ -2288,6 +2423,7 @@ function lastPageFromLink(link) {
|
|
|
2288
2423
|
//#endregion
|
|
2289
2424
|
//#region src/sources/npm.ts
|
|
2290
2425
|
const API_ROOT = "https://api.npmjs.org/downloads/point/last-week";
|
|
2426
|
+
const REGISTRY_ROOT = "https://registry.npmjs.org";
|
|
2291
2427
|
const pointSchema = z.object({
|
|
2292
2428
|
downloads: z.number().int().nonnegative(),
|
|
2293
2429
|
start: z.string().optional(),
|
|
@@ -2325,6 +2461,31 @@ async function npmGet(url, fetchImpl, cache) {
|
|
|
2325
2461
|
cache.set(url, json);
|
|
2326
2462
|
return json;
|
|
2327
2463
|
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Is this name actually published?
|
|
2466
|
+
*
|
|
2467
|
+
* The downloads endpoint cannot answer this: it 404s for a package published minutes ago that
|
|
2468
|
+
* has no download data yet, which is exactly the case a first release is in. The registry
|
|
2469
|
+
* document is the authority.
|
|
2470
|
+
*
|
|
2471
|
+
* Only a definitive 404 counts as "no". A network error, a rate limit or anything else returns
|
|
2472
|
+
* undefined, so an unreachable registry never removes a call to action that was probably right.
|
|
2473
|
+
*/
|
|
2474
|
+
async function npmPackageExists(name, fetchImpl = fetch) {
|
|
2475
|
+
const trimmed = name.trim();
|
|
2476
|
+
if (trimmed.length === 0) return false;
|
|
2477
|
+
let response;
|
|
2478
|
+
try {
|
|
2479
|
+
response = await fetchImpl(`${REGISTRY_ROOT}/${encodeURIComponent(trimmed)}`, {
|
|
2480
|
+
method: "HEAD",
|
|
2481
|
+
headers: { accept: "application/json" }
|
|
2482
|
+
});
|
|
2483
|
+
} catch {
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (response.status === 404) return false;
|
|
2487
|
+
return response.ok ? true : void 0;
|
|
2488
|
+
}
|
|
2328
2489
|
|
|
2329
2490
|
//#endregion
|
|
2330
2491
|
//#region src/sources/package-json.ts
|
|
@@ -2421,117 +2582,6 @@ function normalizeGitUrl$1(url) {
|
|
|
2421
2582
|
return url.replace(/^git\+/, "").replace(/\.git$/, "");
|
|
2422
2583
|
}
|
|
2423
2584
|
|
|
2424
|
-
//#endregion
|
|
2425
|
-
//#region src/sources/readme.ts
|
|
2426
|
-
async function collectReadme(cwd) {
|
|
2427
|
-
let markdown;
|
|
2428
|
-
try {
|
|
2429
|
-
markdown = await readFile(join(cwd, "README.md"), "utf8");
|
|
2430
|
-
} catch {
|
|
2431
|
-
return {};
|
|
2432
|
-
}
|
|
2433
|
-
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2434
|
-
const out = {};
|
|
2435
|
-
const h1 = extractH1(markdown);
|
|
2436
|
-
const tagline = extractTagline(markdown);
|
|
2437
|
-
const snippet = extractFirstCodeFence(markdown);
|
|
2438
|
-
const project = {};
|
|
2439
|
-
if (h1 !== void 0) project.name = fact(h1, {
|
|
2440
|
-
source: "readme",
|
|
2441
|
-
ref: "README.md H1",
|
|
2442
|
-
fetchedAt
|
|
2443
|
-
});
|
|
2444
|
-
if (tagline !== void 0) project.tagline = fact(tagline, {
|
|
2445
|
-
source: "readme",
|
|
2446
|
-
ref: "README.md first paragraph",
|
|
2447
|
-
fetchedAt
|
|
2448
|
-
});
|
|
2449
|
-
if (project.name !== void 0 || project.tagline !== void 0) out.project = project;
|
|
2450
|
-
if (snippet !== void 0) out.release = { codeSnippet: fact(snippet, {
|
|
2451
|
-
source: "readme",
|
|
2452
|
-
ref: "README.md first fenced code block",
|
|
2453
|
-
fetchedAt
|
|
2454
|
-
}) };
|
|
2455
|
-
return out;
|
|
2456
|
-
}
|
|
2457
|
-
async function collectConfiguredSnippet(cwd, spec) {
|
|
2458
|
-
const parsed = parseSnippetSpec(spec);
|
|
2459
|
-
if (parsed === void 0) return {};
|
|
2460
|
-
let contents;
|
|
2461
|
-
try {
|
|
2462
|
-
contents = await readFile(join(cwd, parsed.path), "utf8");
|
|
2463
|
-
} catch {
|
|
2464
|
-
return {};
|
|
2465
|
-
}
|
|
2466
|
-
const slice = contents.split(/\r?\n/).slice(parsed.start - 1, parsed.end).join("\n");
|
|
2467
|
-
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2468
|
-
return { release: { codeSnippet: fact({
|
|
2469
|
-
code: slice,
|
|
2470
|
-
lang: langFromPath(parsed.path)
|
|
2471
|
-
}, {
|
|
2472
|
-
source: "user-config",
|
|
2473
|
-
ref: spec,
|
|
2474
|
-
fetchedAt
|
|
2475
|
-
}) } };
|
|
2476
|
-
}
|
|
2477
|
-
/**
|
|
2478
|
-
* Strip fenced code and HTML before reading prose out of a README.
|
|
2479
|
-
*
|
|
2480
|
-
* Skipping blocks that merely *start* with a fence is not enough. A `#` comment inside a YAML
|
|
2481
|
-
* example reads as a markdown H1, and a blank line inside a fence makes its body look like a
|
|
2482
|
-
* paragraph. Shipseal's own README hit both: the detected name became
|
|
2483
|
-
* ".github/workflows/shipseal.yml" and the tagline became a chunk of workflow YAML.
|
|
2484
|
-
*/
|
|
2485
|
-
function stripNonProse(markdown) {
|
|
2486
|
-
return markdown.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/<[^>]+>/g, "");
|
|
2487
|
-
}
|
|
2488
|
-
function extractH1(markdown) {
|
|
2489
|
-
const heading = /^#\s+(.+)$/m.exec(stripNonProse(markdown))?.[1];
|
|
2490
|
-
if (heading === void 0) return;
|
|
2491
|
-
return heading.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
|
|
2492
|
-
}
|
|
2493
|
-
function extractTagline(markdown) {
|
|
2494
|
-
const prose = stripNonProse(markdown);
|
|
2495
|
-
const h1 = /^#\s+.+$/m.exec(prose);
|
|
2496
|
-
const start = h1 === null ? 0 : (h1.index ?? 0) + h1[0].length;
|
|
2497
|
-
for (const block of prose.slice(start).split(/\n\s*\n/)) {
|
|
2498
|
-
const cleaned = stripBadges(block).trim();
|
|
2499
|
-
if (cleaned.length >= 20 && !cleaned.startsWith("#") && !cleaned.startsWith("```")) return cleaned;
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
function extractFirstCodeFence(markdown) {
|
|
2503
|
-
const match = /```([a-zA-Z0-9+-]+)\n([\s\S]*?)```/.exec(markdown);
|
|
2504
|
-
if (match === null || match[1] === void 0 || match[2] === void 0) return;
|
|
2505
|
-
return {
|
|
2506
|
-
lang: match[1],
|
|
2507
|
-
code: match[2].replace(/\n$/, "")
|
|
2508
|
-
};
|
|
2509
|
-
}
|
|
2510
|
-
function parseSnippetSpec(spec) {
|
|
2511
|
-
const match = /^(?<path>.+)#L(?<start>\d+)(?:-L(?<end>\d+))?$/.exec(spec);
|
|
2512
|
-
if (match === null || match.groups === void 0) return;
|
|
2513
|
-
const path = match.groups.path;
|
|
2514
|
-
const start = Number.parseInt(match.groups.start ?? "0", 10);
|
|
2515
|
-
const end = Number.parseInt(match.groups.end ?? match.groups.start ?? "0", 10);
|
|
2516
|
-
if (path === void 0 || start < 1 || end < start) return;
|
|
2517
|
-
return {
|
|
2518
|
-
path,
|
|
2519
|
-
start,
|
|
2520
|
-
end
|
|
2521
|
-
};
|
|
2522
|
-
}
|
|
2523
|
-
function langFromPath(path) {
|
|
2524
|
-
const ext = extname(path).replace(".", "");
|
|
2525
|
-
if (ext === "ts") return "ts";
|
|
2526
|
-
if (ext === "tsx") return "tsx";
|
|
2527
|
-
if (ext === "js" || ext === "mjs" || ext === "cjs") return "js";
|
|
2528
|
-
if (ext === "jsx") return "jsx";
|
|
2529
|
-
return ext.length > 0 ? ext : "text";
|
|
2530
|
-
}
|
|
2531
|
-
function stripBadges(text) {
|
|
2532
|
-
return text.replace(/\[!\[[^\]]*]\([^)]+\)]\([^)]+\)/g, "").replace(/!\[[^\]]*]\([^)]+\)/g, "").replace(/<img[^>]*>/gi, "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
2585
|
//#endregion
|
|
2536
2586
|
//#region src/sources/collect.ts
|
|
2537
2587
|
async function collectFacts(options) {
|
|
@@ -2546,13 +2596,15 @@ async function collectFacts(options) {
|
|
|
2546
2596
|
const remote = await collectGitRemote(options.cwd);
|
|
2547
2597
|
const changelog = await collectBestChangelog(options.cwd, version, options.changelogPath ?? "CHANGELOG.md", options.packagePath);
|
|
2548
2598
|
const readme = await collectReadme(options.cwd);
|
|
2599
|
+
const snippet = options.snippet !== void 0 && options.snippet !== null && options.snippet.length > 0 ? await collectConfiguredSnippet(options.cwd, options.snippet) : {};
|
|
2549
2600
|
const parts = [
|
|
2550
|
-
remote,
|
|
2551
2601
|
pkg,
|
|
2602
|
+
pkg.project?.npmPackage === void 0 ? await collectWorkspaceNpmPackage(options.cwd, options.skipNetwork === true ? {} : { verify: async (name) => npmPackageExists(name, options.fetchImpl ?? fetch) }) : {},
|
|
2603
|
+
remote,
|
|
2552
2604
|
git,
|
|
2553
|
-
|
|
2605
|
+
snippet,
|
|
2554
2606
|
changelog,
|
|
2555
|
-
|
|
2607
|
+
readme
|
|
2556
2608
|
];
|
|
2557
2609
|
if (options.event.kind === "bench") parts.push(await collectBenchFile(options.cwd, options.benchFile ?? ".shipseal/bench.json"));
|
|
2558
2610
|
if (options.skipNetwork !== true) {
|
|
@@ -2593,6 +2645,31 @@ async function collectBestChangelog(cwd, version, changelogPath, packagePath) {
|
|
|
2593
2645
|
const candidates = await workspaceChangelogs(cwd);
|
|
2594
2646
|
return (await Promise.all(candidates.map((candidate) => collectChangelog(cwd, version, candidate)))).find((facts) => changelogHasNotes(facts)) ?? {};
|
|
2595
2647
|
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Find the one publishable package in a workspace.
|
|
2650
|
+
*
|
|
2651
|
+
* Only when there is exactly one candidate: two publishable packages make the CTA a guess, and
|
|
2652
|
+
* guessing which one to tell people to install is worse than falling back to the repository
|
|
2653
|
+
* URL. `--package` resolves the ambiguity explicitly.
|
|
2654
|
+
*/
|
|
2655
|
+
async function collectWorkspaceNpmPackage(cwd, options = {}) {
|
|
2656
|
+
const paths = await workspaceManifests(cwd);
|
|
2657
|
+
const found = (await Promise.all(paths.map((path) => collectPackageJson(cwd, path)))).map((facts, index) => ({
|
|
2658
|
+
name: facts.project?.npmPackage?.value,
|
|
2659
|
+
path: paths[index] ?? ""
|
|
2660
|
+
})).filter((entry) => entry.name !== void 0);
|
|
2661
|
+
const only = found.length === 1 ? found[0] : void 0;
|
|
2662
|
+
if (only === void 0) return {};
|
|
2663
|
+
if (options.verify !== void 0 && await options.verify(only.name) === false) return {};
|
|
2664
|
+
return { project: { npmPackage: fact(only.name, {
|
|
2665
|
+
source: "package-json",
|
|
2666
|
+
ref: `${only.path}#name`,
|
|
2667
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2668
|
+
}) } };
|
|
2669
|
+
}
|
|
2670
|
+
async function workspaceManifests(cwd) {
|
|
2671
|
+
return (await workspaceChangelogs(cwd)).map((path) => path.replace(/CHANGELOG\.md$/, "package.json"));
|
|
2672
|
+
}
|
|
2596
2673
|
async function workspaceChangelogs(cwd) {
|
|
2597
2674
|
return (await Promise.all(["packages", "apps"].map(async (root) => {
|
|
2598
2675
|
let entries;
|