@mark1russell7/site-pages 0.2.4 → 0.2.6

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 CHANGED
@@ -126,6 +126,37 @@ The check runs the probe on the first page, in the first scheme, at each width.
126
126
 
127
127
  The check finds a browser in this order: `--browser`, `$SITE_CHECK_BROWSER`, the newest Chromium of Playwright, and then Chrome or Edge. The folder `src/check` imports only Node.js. Thus Node.js 22.18 or later can start `src/check/main.ts` with no install. The workflow `reusable-pages.yml` of the template uses it this way.
128
128
 
129
+ ## The freshness of a measured page
130
+
131
+ A measured page shows numbers from a run. The numbers become old when the run becomes old or when an engine has a new release. A site keeps one file of data stamps. Each stamp names the page, its runs, the date of each run and the engine versions of each run. The gate reads this file:
132
+
133
+ ```sh
134
+ site-pages freshness --stamps data/stamps.json
135
+ ```
136
+
137
+ ```json
138
+ {
139
+ "schema": "data-stamp/1",
140
+ "pages": [
141
+ {
142
+ "page": "index.html",
143
+ "runs": [{ "id": "101", "url": "https://github.com/owner/repo/actions/runs/101", "date": "2026-11-02T10:00:00Z", "engines": { "@scope/engine": "0.7.0" } }],
144
+ "inFlight": { "id": "102", "url": "https://github.com/owner/repo/actions/runs/102", "date": "2026-11-04T10:00:00Z", "engines": { "@scope/engine": "0.8.0" } }
145
+ }
146
+ ]
147
+ }
148
+ ```
149
+
150
+ The gate has three rules. A fault of one rule gives the exit code 1.
151
+
152
+ 1. Each page names a run, and each run has an id, a link, a date and its engine versions.
153
+ 2. The data of a page is at most 3 days old (`MAX_DATA_AGE_DAYS`), or a run on newer engines is in flight. The stamp of that run is at most 3 days old, so an old stamp does not hold the gate open.
154
+ 3. The engines of the newest run are at most 1 minor version behind npm at the start of the run (`MAX_MINORS_BEHIND`). A release during a long run does not fail the gate.
155
+
156
+ When npm does not answer, the gate prints the rule 3 under `Not run` and does not fail. A workflow turns the gate on with the input `data-stamps` of `reusable-pages.yml`. The gate then runs from the template commit, so it needs no install.
157
+
158
+ `renderDataStamp(stamp)` gives the line that the page shows with its data: the run, its date, its engine versions and the run in flight.
159
+
129
160
  ## The releases
130
161
 
131
162
  The command merges four sources by version:
@@ -387,14 +418,15 @@ interface KpiFile {
387
418
  project: string;
388
419
  at: string; // the ISO time of the data in UTC, never the time of the build
389
420
  commit: string; // the full commit of the build
390
- measures: Array<{ id: string; value: number; unit?: string }>;
421
+ measures: Array<{ id: string; value: number; unit?: string; at?: string }>;
391
422
  engines?: Array<{ name: string; version: string }>; // the Status page shows them
392
423
  }
393
424
  ```
394
425
 
395
426
  - Give `at` from the record of the data. The writer has no default for this field, because a build time hides old data.
427
+ - Give `at` on a measure when its data is from another time than the file. The Status page then shows a date for each measure.
396
428
  - Without `commit`, the plugin takes `GITHUB_SHA`, else the head of the repository.
397
- - A fault in the data stops the build, and the old site stays. A fault is a wrong form, a value that is not finite, a repeated id or a time after the check.
429
+ - A fault in the data stops the build, and the old site stays. A fault is a wrong form, a value that is not finite, a repeated id or a time after the check. The time rules apply to the file and to each measure.
398
430
  - A site with no Vite build calls `writeKpi(out, input)` from the root entry, before `site-pages build`.
399
431
  - `site-pages build` reads the file from the output folder. It adds the file to `data/status.json`, and the Status page shows the measures, the data date and the engines.
400
432
 
@@ -1414,7 +1414,7 @@ function listText(parts) {
1414
1414
  * The dates of the pages. The pages show each date in UTC, so the build and the browser agree.
1415
1415
  */
1416
1416
  /** The milliseconds of one day. */
1417
- const DAY_MS = 864e5;
1417
+ const DAY_MS$1 = 864e5;
1418
1418
  const MONTHS = [
1419
1419
  "Jan",
1420
1420
  "Feb",
@@ -1431,7 +1431,7 @@ const MONTHS = [
1431
1431
  ];
1432
1432
  /** This function gives the start of the UTC day of a time. */
1433
1433
  function utcDay(time) {
1434
- return Math.floor(time / DAY_MS) * DAY_MS;
1434
+ return Math.floor(time / DAY_MS$1) * DAY_MS$1;
1435
1435
  }
1436
1436
  /** This function gives "Oct 18": the UTC day of a time or of an ISO date. */
1437
1437
  function shortDate(date) {
@@ -1688,13 +1688,13 @@ function timelineLayout(releases, options) {
1688
1688
  counts.set(day, (counts.get(day) ?? 0) + 1);
1689
1689
  }
1690
1690
  const raw = [];
1691
- for (let day = utcDay(Date.parse(first.date)); day <= utcDay(Date.parse(last.date)); day += DAY_MS) {
1691
+ for (let day = utcDay(Date.parse(first.date)); day <= utcDay(Date.parse(last.date)); day += DAY_MS$1) {
1692
1692
  const count = counts.get(day) ?? 0;
1693
1693
  const previous = raw[raw.length - 1];
1694
- if (count === 0 && previous?.empty) previous.end = day + DAY_MS;
1694
+ if (count === 0 && previous?.empty) previous.end = day + DAY_MS$1;
1695
1695
  else raw.push({
1696
1696
  start: day,
1697
- end: day + DAY_MS,
1697
+ end: day + DAY_MS$1,
1698
1698
  count,
1699
1699
  empty: count === 0,
1700
1700
  weight: count === 0 ? emptyWeight : 1
@@ -2138,14 +2138,19 @@ function openWork(data) {
2138
2138
  const doneList = done.length === 0 ? `<p class="sp-done sp-small"><span aria-hidden="true">✓</span> No item is done.</p>` : `<details class="sp-done sp-c-done" data-sp-done><summary><span aria-hidden="true">✓</span> ${doneText} <span data-sp-done-label>Show the done items</span></summary><section class="sp-group sp-c-done" data-sp-group data-class="done" aria-label="The done items">${itemTable(done, data, columns)}<p class="sp-callout" data-sp-done-empty hidden>No done item matches the filters.</p></section></details>`;
2139
2139
  return `<section class="sp-section" aria-labelledby="open-work" data-sp-board><h2 id="open-work">Open work</h2><p class="sp-small sp-muted">Each row of a table of ${link(roadmapUrl(data), `<code>${escapeHtml(file)}</code>`)} with an ID and a status that is not done. The build reads the file, so this list follows each commit.</p>` + meter(items) + (open.length > 0 ? filters : "") + list + doneList + "</section>";
2140
2140
  }
2141
- /** This function renders the measures of `kpi.json`: their values, the date of the data and the engines that made it. */
2141
+ /**
2142
+ * This function renders the measures of `kpi.json`: their values, the date of the data and the engines that made it.
2143
+ * A measure with its own date shows that date in a column, so that the line above the table stays true.
2144
+ */
2142
2145
  function measures(data) {
2143
2146
  const { kpi } = data;
2144
2147
  if (kpi === void 0) return "";
2145
- const rows = kpi.measures.map((measure) => `<tr><th scope="row">${escapeHtml(measure.id)}</th><td>${measure.value}${measure.unit === void 0 ? "" : ` ${escapeHtml(measure.unit)}`}</td></tr>`).join("");
2148
+ const ownDates = kpi.measures.some((measure) => measure.at !== void 0);
2149
+ const dateCell = (at) => ownDates ? `<td><time datetime="${escapeHtml(at)}">${escapeHtml(longDate(at))}</time></td>` : "";
2150
+ const rows = kpi.measures.map((measure) => `<tr><th scope="row">${escapeHtml(measure.id)}</th><td>${measure.value}${measure.unit === void 0 ? "" : ` ${escapeHtml(measure.unit)}`}</td>${dateCell(measure.at ?? kpi.at)}</tr>`).join("");
2146
2151
  const engines = kpi.engines ?? [];
2147
2152
  const commit = link(`https://github.com/${data.source.github}/commit/${kpi.commit}`, `<code>${kpi.commit.slice(0, 7)}</code>`);
2148
- return `<section class="sp-section" aria-labelledby="measures"><h2 id="measures">Measures</h2><p>The data of these numbers is from <time datetime="${escapeHtml(kpi.at)}" data-sp-data-date>${escapeHtml(longDate(kpi.at))}</time>. The build of ${commit} published them.</p>` + (engines.length > 0 ? `<p>The engines of the data: ${listText(engines.map((engine) => `<code>${escapeHtml(engine.name)}</code> ${escapeHtml(engine.version)}`))}.</p>` : "") + `<div class="sp-table-box" tabindex="0" role="region" aria-label="Measures"><table class="sp-table"><thead><tr><th scope="col">Measure</th><th scope="col">Value</th></tr></thead><tbody>${rows}</tbody></table></div></section>`;
2153
+ return `<section class="sp-section" aria-labelledby="measures"><h2 id="measures">Measures</h2><p>${ownDates ? "The data date of the file is" : "The data of these numbers is from"} <time datetime="${escapeHtml(kpi.at)}" data-sp-data-date>${escapeHtml(longDate(kpi.at))}</time>. The build of ${commit} published them.</p>` + (engines.length > 0 ? `<p>The engines of the data: ${listText(engines.map((engine) => `<code>${escapeHtml(engine.name)}</code> ${escapeHtml(engine.version)}`))}.</p>` : "") + `<div class="sp-table-box" tabindex="0" role="region" aria-label="Measures"><table class="sp-table"><thead><tr><th scope="col">Measure</th><th scope="col">Value</th>${ownDates ? "<th scope=\"col\">Data date</th>" : ""}</tr></thead><tbody>${rows}</tbody></table></div></section>`;
2149
2154
  }
2150
2155
  function when(date) {
2151
2156
  const text = escapeHtml(longDate(date));
@@ -2302,6 +2307,195 @@ const TYPESCRIPT_PROBE = /\.[cm]?ts$/i;
2302
2307
  function probeScript(source) {
2303
2308
  return stripTypeScriptTypes(source, { mode: "strip" }).replace(/^export \{\s*\};?[ \t]*$/m, "");
2304
2309
  }
2310
+ // src/check/freshness.ts
2311
+ /**
2312
+ * The freshness gate of a measured page. A site keeps a file of data stamps.
2313
+ * Each stamp names the page, its runs, their dates and their engine versions.
2314
+ * The gate fails on a page with no versions or no run date, and on old data with no newer run in flight.
2315
+ * The gate also fails on a run that started behind the engines on npm.
2316
+ * This file imports only Node.js and the folder `data`, so `reusable-pages.yml` starts it with no npm release of the package.
2317
+ */
2318
+ /** The schema of a file of data stamps. */
2319
+ const DATA_STAMP_SCHEMA = "data-stamp/1";
2320
+ /** The largest age of the data of a page, in days, when no newer run is in flight. */
2321
+ const MAX_DATA_AGE_DAYS = 3;
2322
+ /** The most minor versions that an engine of a run can be behind npm at the start of the run. */
2323
+ const MAX_MINORS_BEHIND = 1;
2324
+ const DAY_MS = 864e5;
2325
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2326
+ /** This function gives the faults of one run of a stamp. */
2327
+ function runFaults(raw, at) {
2328
+ const run = record(raw);
2329
+ if (run === null) return [`${at}: a run is not an object.`];
2330
+ const faults = [];
2331
+ if (typeof run["id"] !== "string" || run["id"] === "") faults.push(`${at}: a run has no id.`);
2332
+ const label = `${at}, run ${String(run["id"])}`;
2333
+ if (typeof run["url"] !== "string" || !run["url"].startsWith("https://")) faults.push(`${label}: the run has no https link.`);
2334
+ if (typeof run["date"] !== "string" || !Number.isFinite(Date.parse(run["date"]))) faults.push(`${label}: the run has no date.`);
2335
+ const engines = record(run["engines"]);
2336
+ if (engines === null || Object.keys(engines).length === 0) return [...faults, `${label}: the run names no engine versions.`];
2337
+ for (const [name, version] of Object.entries(engines)) if (typeof version !== "string" || parseVersion(version) === null) faults.push(`${label}: the version of ${name} is not a version.`);
2338
+ return faults;
2339
+ }
2340
+ /**
2341
+ * This function reads the content of a file of data stamps. It gives each fault of the content, so a page with no versions
2342
+ * or no run date fails here.
2343
+ */
2344
+ function parseDataStamps(raw) {
2345
+ const file = record(raw);
2346
+ if (file === null) return {
2347
+ ok: false,
2348
+ faults: ["The file is not an object."]
2349
+ };
2350
+ if (file["schema"] !== "data-stamp/1") return {
2351
+ ok: false,
2352
+ faults: [`The schema is not ${DATA_STAMP_SCHEMA}.`]
2353
+ };
2354
+ const pages = file["pages"];
2355
+ if (!Array.isArray(pages) || pages.length === 0) return {
2356
+ ok: false,
2357
+ faults: ["The file has no pages."]
2358
+ };
2359
+ const faults = [];
2360
+ for (const item of pages) {
2361
+ const page = record(item);
2362
+ if (page === null || typeof page["page"] !== "string" || page["page"] === "") {
2363
+ faults.push("A stamp has no page.");
2364
+ continue;
2365
+ }
2366
+ const at = page["page"];
2367
+ const runs = page["runs"];
2368
+ if (!Array.isArray(runs) || runs.length === 0) faults.push(`${at}: the page names no run.`);
2369
+ else for (const run of runs) faults.push(...runFaults(run, at));
2370
+ if (page["inFlight"] !== void 0) faults.push(...runFaults(page["inFlight"], `${at}, in flight`));
2371
+ }
2372
+ return faults.length > 0 ? {
2373
+ ok: false,
2374
+ faults
2375
+ } : {
2376
+ ok: true,
2377
+ stamps: raw
2378
+ };
2379
+ }
2380
+ /** This function gives the count of minor versions from `used` to `latest`. A newer major version counts as no limit. */
2381
+ function minorsBehind(used, latest) {
2382
+ const a = parseVersion(used);
2383
+ const b = parseVersion(latest);
2384
+ if (a === null || b === null) return Number.POSITIVE_INFINITY;
2385
+ if (a.major !== b.major) return b.major > a.major ? Number.POSITIVE_INFINITY : 0;
2386
+ return Math.max(0, b.minor - a.minor);
2387
+ }
2388
+ /** This function gives the newest stable version of a package on npm at a time, or undefined when npm has none. */
2389
+ function latestAt(times, name, date) {
2390
+ const limit = Date.parse(date);
2391
+ return times.filter((t) => t.name === name && Date.parse(t.date) <= limit && parseVersion(t.version)?.prerelease.length === 0).map((t) => t.version).sort(compareVersions).at(-1);
2392
+ }
2393
+ const ageInDays = (date, now) => (now - Date.parse(date)) / DAY_MS;
2394
+ /** This function tells if a run uses a newer version of an engine than another run. */
2395
+ function hasNewerEngine(run, than) {
2396
+ return Object.entries(run.engines).some(([name, version]) => {
2397
+ const old = than.engines[name];
2398
+ return old !== void 0 && compareVersions(version, old) > 0;
2399
+ });
2400
+ }
2401
+ /**
2402
+ * This function applies the rules of the gate to the stamps.
2403
+ *
2404
+ * - The data of each page is at most `maxAgeDays` old, or a run on newer engines is in flight.
2405
+ * - The engines of the newest run are at most `maxMinorsBehind` behind npm at the start of that run.
2406
+ * - A run in flight has the same age limit, so an old stamp does not hold the gate open.
2407
+ */
2408
+ function checkFreshness(stamps, options) {
2409
+ const { times, maxAgeDays = 3, maxMinorsBehind = 1 } = options;
2410
+ const now = Date.parse(options.now);
2411
+ const faults = [];
2412
+ const notRun = [];
2413
+ for (const stamp of stamps.pages) {
2414
+ for (const run of [...stamp.runs, ...stamp.inFlight === void 0 ? [] : [stamp.inFlight]]) if (ageInDays(run.date, now) < 0) faults.push(`${stamp.page}: the run ${run.id} has a date after the check.`);
2415
+ const flight = stamp.inFlight;
2416
+ const flying = flight !== void 0 && ageInDays(flight.date, now) <= maxAgeDays;
2417
+ if (flight !== void 0 && !flying) faults.push(`${stamp.page}: the run ${flight.id} is in flight for more than ${maxAgeDays} days.`);
2418
+ for (const run of stamp.runs) {
2419
+ const age = ageInDays(run.date, now);
2420
+ if (age > maxAgeDays && !(flying && hasNewerEngine(flight, run))) faults.push(`${stamp.page}: the run ${run.id} is ${age.toFixed(1)} days old, over the limit of ${maxAgeDays}, and no run on newer engines is in flight.`);
2421
+ }
2422
+ const newest = flight ?? [...stamp.runs].sort((a, b) => Date.parse(a.date) - Date.parse(b.date)).at(-1);
2423
+ if (newest === void 0) continue;
2424
+ for (const [name, version] of Object.entries(newest.engines)) {
2425
+ const latest = latestAt(times, name, newest.date);
2426
+ if (latest === void 0) notRun.push(`${stamp.page}: npm gives no version of ${name} at the start of the run ${newest.id}.`);
2427
+ else if (minorsBehind(version, latest) > maxMinorsBehind) faults.push(`${stamp.page}: the run ${newest.id} used ${name} ${version}, and npm had ${latest} at its start. The limit is ${maxMinorsBehind} minor version behind.`);
2428
+ }
2429
+ }
2430
+ return {
2431
+ faults,
2432
+ notRun
2433
+ };
2434
+ }
2435
+ /** The help of the command `site-pages freshness`. */
2436
+ const FRESHNESS_HELP = `Usage: site-pages freshness --stamps <file> [options]
2437
+
2438
+ Examine the data stamps of the measured pages of a site.
2439
+
2440
+ --stamps <file> The file of data stamps, schema ${DATA_STAMP_SCHEMA}. (necessary)
2441
+ --now <time> The ISO time of the check. The default is the time of the call.
2442
+ --help Show this text.
2443
+
2444
+ The exit code is 0 when the stamps pass, 1 for a fault, and 2 for an incorrect command line.
2445
+ `;
2446
+ /** This function is the command `site-pages freshness`. The `fetch` option is for a test. */
2447
+ async function runFreshnessCli(argv, io, fetcher) {
2448
+ let values;
2449
+ try {
2450
+ ({values} = parseArgs({
2451
+ args: [...argv],
2452
+ allowPositionals: false,
2453
+ options: {
2454
+ stamps: { type: "string" },
2455
+ now: { type: "string" },
2456
+ help: { type: "boolean" }
2457
+ }
2458
+ }));
2459
+ } catch (error) {
2460
+ io.stderr(`site-pages freshness: ${error instanceof Error ? error.message : String(error)}\n\n${FRESHNESS_HELP}`);
2461
+ return 2;
2462
+ }
2463
+ if (values.help === true) {
2464
+ io.stdout(FRESHNESS_HELP);
2465
+ return 0;
2466
+ }
2467
+ if (values.stamps === void 0) {
2468
+ io.stderr(`site-pages freshness: --stamps is necessary.\n\n${FRESHNESS_HELP}`);
2469
+ return 2;
2470
+ }
2471
+ const path = isAbsolute(values.stamps) ? values.stamps : join(io.cwd, values.stamps);
2472
+ let raw;
2473
+ try {
2474
+ raw = JSON.parse(readFileSync(path, "utf8"));
2475
+ } catch (error) {
2476
+ io.stderr(`site-pages freshness: the file ${values.stamps} cannot be read: ${error instanceof Error ? error.message : String(error)}\n`);
2477
+ return 1;
2478
+ }
2479
+ const parsed = parseDataStamps(raw);
2480
+ if (!parsed.ok) {
2481
+ for (const fault of parsed.faults) io.stderr(`${fault}\n`);
2482
+ return 1;
2483
+ }
2484
+ const engines = parsed.stamps.pages.flatMap((p) => [...p.runs, ...p.inFlight === void 0 ? [] : [p.inFlight]]).flatMap((r) => Object.keys(r.engines));
2485
+ const npm = await npmPublishTimes({
2486
+ packages: [...new Set(engines)],
2487
+ ...fetcher === void 0 ? {} : { fetch: fetcher }
2488
+ });
2489
+ const result = checkFreshness(parsed.stamps, {
2490
+ now: values.now ?? (/* @__PURE__ */ new Date()).toISOString(),
2491
+ times: npm.times
2492
+ });
2493
+ for (const problem of [...npm.problems, ...result.notRun]) io.stdout(`Not run: ${problem}\n`);
2494
+ for (const fault of result.faults) io.stderr(`${fault}\n`);
2495
+ if (result.faults.length > 0) return 1;
2496
+ io.stdout(`The data stamps of ${parsed.stamps.pages.length} pages pass.\n`);
2497
+ return 0;
2498
+ }
2305
2499
  // src/build.ts
2306
2500
  /**
2307
2501
  * The `site-pages build` command. It reads the sources. Then it writes the JSON data and the two
@@ -2402,6 +2596,11 @@ Usage: site-pages check (--out <dir> | --url <url>) [options]
2402
2596
 
2403
2597
  Opens the pages of the site in a headless Chromium at phone and desktop widths. Start
2404
2598
  "site-pages check --help" for its options.
2599
+
2600
+ Usage: site-pages freshness --stamps <file> [options]
2601
+
2602
+ Examine the data stamps of the measured pages of a site. See
2603
+ "site-pages freshness --help" for its options.
2405
2604
  `;
2406
2605
  /**
2407
2606
  * This function reads the command line into the options of `build`.
@@ -2505,6 +2704,6 @@ async function runCli(argv, io) {
2505
2704
  throw error;
2506
2705
  }
2507
2706
  }
2508
- export { checkStateOf as $, safeUrl as A, roadmapItems as B, pageShell as C, utcDay as D, shortDate as E, linksOf as F, mergeReleases as G, statusClassOf as H, markdownTables as I, globFiles as J, readChangelogs as K, parseRoadmap as L, RoadmapParseError as M, STATUS_CLASSES as N, escapeHtml as O, buildStatusBoard as P, GITHUB_GRAPHQL as Q, plainText as R, BASE_CSS as S, longDate as T, DEFAULT_CHANGELOGS as U, splitRow as V, ReleaseDataError as W, npmPublishTimes as X, NPM_REGISTRY as Y, GITHUB_API as Z, renderReleasesPage as _, runCli as a, releaseBodyEntries as at, releaseSummary as b, STATUS_SCRIPT as c, parseVersion as ct, renderStatusPage as d, githubOpenWork as et, sectionsOf as f, releasesLead as g, RELEASES_SCRIPT as h, parseBuildArgs as i, parseChangelog as it, OPEN_CLASSES as j, inlineMarkdown as k, classCounts as l, versionOfTag as lt, statusLead as m, UsageError as n, firstSentence as nt, TYPESCRIPT_PROBE as o, compareVersions as ot, slugOf as p, readReleases as q, build as r, headingAnchor as rt, probeScript as s, kindOf as st, HELP as t, githubReleases as tt, openGroups as u, laneCenter as v, dayRange as w, timelineLayout as x, oldestFirst as y, readStatusBoard as z };
2707
+ export { DEFAULT_CHANGELOGS as $, BASE_CSS as A, safeUrl as B, RELEASES_SCRIPT as C, oldestFirst as D, laneCenter as E, utcDay as F, linksOf as G, RoadmapParseError as H, escapeHtml as I, plainText as J, markdownTables as K, inlineMarkdown as L, dayRange as M, longDate as N, releaseSummary as O, shortDate as P, statusClassOf as Q, link as R, statusLead as S, renderReleasesPage as T, STATUS_CLASSES as U, OPEN_CLASSES as V, buildStatusBoard as W, roadmapItems as X, readStatusBoard as Y, splitRow as Z, classCounts as _, kindOf as _t, runCli as a, NPM_REGISTRY as at, sectionsOf as b, MAX_MINORS_BEHIND as c, GITHUB_GRAPHQL as ct, minorsBehind as d, githubReleases as dt, ReleaseDataError as et, parseDataStamps as f, firstSentence as ft, STATUS_SCRIPT as g, compareVersions as gt, probeScript as h, releaseBodyEntries as ht, parseBuildArgs as i, globFiles as it, pageShell as j, timelineLayout as k, checkFreshness as l, checkStateOf as lt, TYPESCRIPT_PROBE as m, parseChangelog as mt, UsageError as n, readChangelogs as nt, DATA_STAMP_SCHEMA as o, npmPublishTimes as ot, runFreshnessCli as p, headingAnchor as pt, parseRoadmap as q, build as r, readReleases as rt, MAX_DATA_AGE_DAYS as s, GITHUB_API as st, HELP as t, mergeReleases as tt, latestAt as u, githubOpenWork as ut, openGroups as v, parseVersion as vt, releasesLead as w, slugOf as x, renderStatusPage as y, versionOfTag as yt, listText as z };
2509
2708
 
2510
2709
  //# sourceMappingURL=build.js.map