@mark1russell7/site-pages 0.2.1 → 0.2.3

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
@@ -368,6 +368,36 @@ The theme `auto` removes `data-theme`, and the CSS of the site then obeys `prefe
368
368
  </script>
369
369
  ```
370
370
 
371
+ ## The measures of a site
372
+
373
+ The file `kpi.json` at the root of a site holds the measures of the project, in the format `kpi/1`. A reader outside the site takes the numbers from this file.
374
+
375
+ ```ts
376
+ import { defineConfig } from "vite";
377
+ import { siteKpi } from "@mark1russell7/site-pages/vite";
378
+
379
+ export default defineConfig({
380
+ plugins: [siteKpi({ project: "acme", at: data.at, measures: [{ id: "cases", value: data.cases.length, unit: "cases" }] })],
381
+ });
382
+ ```
383
+
384
+ ```ts
385
+ interface KpiFile {
386
+ format: "kpi/1";
387
+ project: string;
388
+ at: string; // the ISO time of the data in UTC, never the time of the build
389
+ commit: string; // the full commit of the build
390
+ measures: Array<{ id: string; value: number; unit?: string }>;
391
+ engines?: Array<{ name: string; version: string }>; // the Status page shows them
392
+ }
393
+ ```
394
+
395
+ - Give `at` from the record of the data. The writer has no default for this field, because a build time hides old data.
396
+ - 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.
398
+ - A site with no Vite build calls `writeKpi(out, input)` from the root entry, before `site-pages build`.
399
+ - `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
+
371
401
  ## The shell of a site
372
402
 
373
403
  The shell is the part of a page that is not its content. The package has two parts of the shell, and each part is optional.
@@ -1,5 +1,5 @@
1
+ import { d as headingDate, f as isShallow, l as gitTagDates, p as originRepository, r as KpiError, s as readKpi, t as KPI_FILE, u as headCommit } from "./kpi.js";
1
2
  import { stripTypeScriptTypes } from "node:module";
2
- import { execFileSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, posix, resolve } from "node:path";
5
5
  import { parseArgs } from "node:util";
@@ -496,65 +496,6 @@ async function npmPublishTimes(options) {
496
496
  problems
497
497
  };
498
498
  }
499
- // src/data/git.ts
500
- /**
501
- * The git source: the tags with their dates, the commit of the build and the GitHub remote. Each
502
- * function gives an empty result when git or the repository is not available.
503
- */
504
- /** This function starts git in a folder and gives its output, or null when git fails. */
505
- function git(repoDir, ...args) {
506
- try {
507
- return execFileSync("git", args, {
508
- cwd: repoDir,
509
- encoding: "utf8",
510
- stdio: [
511
- "ignore",
512
- "pipe",
513
- "ignore"
514
- ],
515
- windowsHide: true
516
- });
517
- } catch {
518
- return null;
519
- }
520
- }
521
- /** This function tells if the clone is shallow. A shallow clone has not all the tags and commits. */
522
- function isShallow(repoDir) {
523
- return git(repoDir, "rev-parse", "--is-shallow-repository")?.trim() === "true";
524
- }
525
- /** This function gives each tag of the repository with its date. An annotated tag has its own date. */
526
- function gitTagDates(repoDir) {
527
- const output = git(repoDir, "for-each-ref", "--format=%(refname:strip=2) %(creatordate:iso-strict)", "refs/tags");
528
- if (!output) return [];
529
- const tags = [];
530
- for (const line of output.split(/\r?\n/)) {
531
- const match = /^(\S+) (\S+)$/.exec(line.trim());
532
- if (match?.[1] && match[2]) tags.push({
533
- tag: match[1],
534
- date: match[2]
535
- });
536
- }
537
- return tags;
538
- }
539
- /**
540
- * This function gives the date of the oldest commit that added a version heading to a changelog,
541
- * or null. The path of the changelog starts at `repoDir`.
542
- */
543
- function headingDate(repoDir, version, file) {
544
- const dates = (git(repoDir, "log", "--format=%cI", `-S## ${version}`, "--", file) ?? "").trim().split(/\r?\n/).filter(Boolean);
545
- return dates[dates.length - 1] ?? null;
546
- }
547
- /** This function gives the full SHA of `HEAD`, or null. */
548
- function headCommit(repoDir) {
549
- const sha = git(repoDir, "rev-parse", "HEAD")?.trim();
550
- return sha && /^[0-9a-f]{40}$/.test(sha) ? sha : null;
551
- }
552
- /** This function gives "owner/name" of the GitHub remote "origin", or null. */
553
- function originRepository(repoDir) {
554
- const url = git(repoDir, "remote", "get-url", "origin")?.trim() ?? "";
555
- const match = /github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(url);
556
- return match ? `${match[1]}/${match[2]}` : null;
557
- }
558
499
  // src/data/glob.ts
559
500
  /**
560
501
  * A small glob for the changelog files. The syntax has `*` and `?` in one path segment, and `**` for
@@ -2197,6 +2138,15 @@ function openWork(data) {
2197
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>`;
2198
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>";
2199
2140
  }
2141
+ /** This function renders the measures of `kpi.json`: their values, the date of the data and the engines that made it. */
2142
+ function measures(data) {
2143
+ const { kpi } = data;
2144
+ 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("");
2146
+ const engines = kpi.engines ?? [];
2147
+ 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>`;
2149
+ }
2200
2150
  function when(date) {
2201
2151
  const text = escapeHtml(longDate(date));
2202
2152
  return `<time datetime="${escapeHtml(date)}" title="${text}" data-sp-rel>${text}</time>`;
@@ -2333,7 +2283,7 @@ function renderStatusPage(data, options) {
2333
2283
  title: "Status",
2334
2284
  description: lead,
2335
2285
  current: "status",
2336
- main: `<h1>Status</h1><p class="sp-lead">${escapeHtml(lead)}</p><p class="sp-small sp-muted">The build reads the roadmap file and GitHub again after each commit and on a schedule.</p>` + (data.source.roadmap ? openWork(data) : "") + pullRequests(data) + issues(data),
2286
+ main: `<h1>Status</h1><p class="sp-lead">${escapeHtml(lead)}</p><p class="sp-small sp-muted">The build reads the roadmap file and GitHub again after each commit and on a schedule.</p>` + measures(data) + (data.source.roadmap ? openWork(data) : "") + pullRequests(data) + issues(data),
2337
2287
  script: STATUS_SCRIPT
2338
2288
  }, options);
2339
2289
  }
@@ -2366,9 +2316,10 @@ function write(file, content, files) {
2366
2316
  }
2367
2317
  /**
2368
2318
  * This function reads the sources and writes `data/releases.json`, `data/status.json`,
2369
- * `releases/index.html` and `status/index.html` into the output folder.
2319
+ * `releases/index.html` and `status/index.html` into the output folder. When the output folder has a
2320
+ * `kpi.json`, the status data and the status page show it.
2370
2321
  *
2371
- * @throws ReleaseDataError or RoadmapParseError when a source on disk is not correct
2322
+ * @throws ReleaseDataError, RoadmapParseError or KpiError when a source on disk is not correct
2372
2323
  */
2373
2324
  async function build(options) {
2374
2325
  const { repoDir, repository, warn = () => {} } = options;
@@ -2389,7 +2340,9 @@ async function build(options) {
2389
2340
  warn,
2390
2341
  ...fetcher
2391
2342
  });
2392
- const status = await readStatusBoard({
2343
+ const kpiPath = join(out, KPI_FILE);
2344
+ const kpi = existsSync(kpiPath) ? readKpi(readFileSync(kpiPath, "utf8"), Date.parse(now)) : void 0;
2345
+ const board = await readStatusBoard({
2393
2346
  repoDir,
2394
2347
  roadmap: options.roadmap ?? null,
2395
2348
  owner,
@@ -2399,6 +2352,10 @@ async function build(options) {
2399
2352
  generatedAt: now,
2400
2353
  ...fetcher
2401
2354
  });
2355
+ const status = kpi === void 0 ? board : {
2356
+ ...board,
2357
+ kpi
2358
+ };
2402
2359
  if (!status.github.available) warn(`the status page has no GitHub data: ${status.github.reason ?? "no reason"}.`);
2403
2360
  const files = [];
2404
2361
  write(join(out, "data", "releases.json"), `${JSON.stringify(releases, null, 2)}\n`, files);
@@ -2541,13 +2498,13 @@ async function runCli(argv, io) {
2541
2498
  io.stdout(`site-pages: ${plural(result.releases.length, "release")}, ${plural(result.status.items.length, "roadmap item")} (${open} open), ${github}.\n` + result.files.map((file) => ` ${file}\n`).join(""));
2542
2499
  return 0;
2543
2500
  } catch (error) {
2544
- if (error instanceof ReleaseDataError || error instanceof RoadmapParseError || error?.code === "ENOENT") {
2501
+ if (error instanceof ReleaseDataError || error instanceof RoadmapParseError || error instanceof KpiError || error?.code === "ENOENT") {
2545
2502
  io.stderr(`site-pages: ${error.message}\n`);
2546
2503
  return 1;
2547
2504
  }
2548
2505
  throw error;
2549
2506
  }
2550
2507
  }
2551
- export { originRepository 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, isShallow as Q, plainText as R, BASE_CSS as S, longDate as T, DEFAULT_CHANGELOGS as U, splitRow as V, ReleaseDataError as W, headCommit as X, gitTagDates as Y, headingDate as Z, renderReleasesPage as _, runCli as a, githubOpenWork as at, releaseSummary as b, STATUS_SCRIPT as c, headingAnchor as ct, renderStatusPage as d, compareVersions as dt, NPM_REGISTRY as et, sectionsOf as f, kindOf as ft, releasesLead as g, RELEASES_SCRIPT as h, parseBuildArgs as i, checkStateOf as it, OPEN_CLASSES as j, inlineMarkdown as k, classCounts as l, parseChangelog as lt, statusLead as m, versionOfTag as mt, UsageError as n, GITHUB_API as nt, TYPESCRIPT_PROBE as o, githubReleases as ot, slugOf as p, parseVersion as pt, readReleases as q, build as r, GITHUB_GRAPHQL as rt, probeScript as s, firstSentence as st, HELP as t, npmPublishTimes as tt, openGroups as u, releaseBodyEntries as ut, laneCenter as v, dayRange as w, timelineLayout as x, oldestFirst as y, readStatusBoard as z };
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 };
2552
2509
 
2553
- //# sourceMappingURL=build-xJqNIzdO.js.map
2510
+ //# sourceMappingURL=build.js.map