@mark1russell7/site-pages 0.2.14 → 0.2.19

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
@@ -199,6 +199,18 @@ The pipeline needs the full history for the tags and the commits. Use `fetch-dep
199
199
 
200
200
  For a monorepo with one version for all its packages, give `--group` and the name of one package. Then only its changelog gives the versions, and the dates come from its tags. Without `--group`, each version of each source is a version.
201
201
 
202
+ `changelogFaults(markdown, { from, fixedGroup })` examines a changelog and gives its faults:
203
+
204
+ - An empty section.
205
+ - A section with only dependency lines on a break of the version rule.
206
+ - A first statement that names only the version kind.
207
+ - A break of the version rule with no mark.
208
+ - A mark on a release with no break.
209
+
210
+ The option `from` leaves out older sections. The option `fixedGroup` passes a section with no change or with only PATCH changes, because its raise comes from another package of the group.
211
+
212
+ `sectionMarksBreak(section)` tells if the text of a section marks a break. The marks are the heading `### Breaking` and the words "has breaking changes". A page that counts breaks uses this function.
213
+
202
214
  A repository can have no changelog and no GitHub release. Then each npm version of each package of `--packages` is a release:
203
215
 
204
216
  - The label is the version when there is one package. With more packages, the label is the name without its scope and the version, for example `service@0.5.8`.
@@ -397,6 +409,14 @@ The class sets `overflow-x: auto` and the background color of the box. Set `--sc
397
409
  }
398
410
  ```
399
411
 
412
+ Markdown has no way to set the class of a table. For a Markdown page, add the plugin `rehypeScrollCue` of the entry `@mark1russell7/site-pages/vite`. It gives the class, a tab stop and a label to each table:
413
+
414
+ ```js
415
+ import { rehypeScrollCue } from "@mark1russell7/site-pages/vite";
416
+
417
+ export default defineConfig({ markdown: { rehypePlugins: [rehypeScrollCue] } });
418
+ ```
419
+
400
420
  ## The page helpers
401
421
 
402
422
  The entry `@mark1russell7/site-pages/chrome` has small helpers for the script of a page. It imports no Node.js module, so a site can bundle it for the browser. The package root does not export these helpers. Thus a Node.js program that imports the root gets no DOM types.
@@ -452,13 +472,14 @@ interface KpiFile {
452
472
  project: string;
453
473
  at: string; // the ISO time of the data in UTC, never the time of the build
454
474
  commit: string; // the full commit of the build
455
- measures: Array<{ id: string; value: number; unit?: string; at?: string }>;
475
+ measures: Array<{ id: string; value: number; unit?: string; at?: string; notCounted?: string }>;
456
476
  engines?: Array<{ name: string; version: string }>; // the Status page shows them
457
477
  }
458
478
  ```
459
479
 
460
480
  - Give `at` from the record of the data. The writer has no default for this field, because a build time hides old data.
461
481
  - 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.
482
+ - Give `notCounted` on a count of faults, for example a count of old text. The text names what the count does not read, so a 0 does not seem to tell more than it measured. The Status page shows the text.
462
483
  - Without `commit`, the plugin takes `GITHUB_SHA`, else the head of the repository.
463
484
  - 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.
464
485
  - A site with no Vite build calls `writeKpi(out, input)` from the root entry, before `site-pages build`.
@@ -96,6 +96,138 @@ function parseChangelog(markdown) {
96
96
  entries: raw.map(entryOf).filter((change) => change !== null)
97
97
  }));
98
98
  }
99
+ const KIND_WORDS = /* @__PURE__ */ new Set([
100
+ "major",
101
+ "minor",
102
+ "patch"
103
+ ]);
104
+ const FILL_WORDS = /* @__PURE__ */ new Set([
105
+ "a",
106
+ "the",
107
+ "this",
108
+ "is",
109
+ "raises",
110
+ "bumps",
111
+ "release",
112
+ "version",
113
+ "bump"
114
+ ]);
115
+ const BREAK_HEADING = /^### Breaking\b/m;
116
+ const BREAK_SENTENCE = /\bhas breaking changes\b/;
117
+ /**
118
+ * This function tells if the text of a version section marks a break. The marks are the heading
119
+ * `### Breaking` and the words "has breaking changes", for a text that goes through Changesets.
120
+ *
121
+ * @remarks A page that counts breaks and {@link changelogFaults} use this one test, so they agree.
122
+ */
123
+ function sectionMarksBreak(section) {
124
+ return BREAK_HEADING.test(section) || BREAK_SENTENCE.test(section);
125
+ }
126
+ /** This function tells if a statement has only the kind of the version and words that state no change. */
127
+ function namesOnlyKind(statement) {
128
+ const words = statement.toLowerCase().match(/[\p{L}\p{N}`]+/gu) ?? [];
129
+ return words.some((word) => KIND_WORDS.has(word)) && words.every((word) => KIND_WORDS.has(word) || FILL_WORDS.has(word));
130
+ }
131
+ /** This function gives the three numbers of a version without its pre-release text, or null for another text. */
132
+ function versionCore(version) {
133
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-|$)/.exec(version);
134
+ return match ? [
135
+ Number(match[1]),
136
+ Number(match[2]),
137
+ Number(match[3])
138
+ ] : null;
139
+ }
140
+ /** This function compares two versions without their pre-release texts. */
141
+ function compareCores(a, b) {
142
+ return (a[0] ?? 0) - (b[0] ?? 0) || (a[1] ?? 0) - (b[1] ?? 0) || (a[2] ?? 0) - (b[2] ?? 0);
143
+ }
144
+ /**
145
+ * This function tells if the version rule makes a release a break. Before 1.0.0, a break raises
146
+ * MINOR. From 1.0.0, a break raises MAJOR.
147
+ */
148
+ function ruleGivesBreak([major, minor, patch]) {
149
+ if (patch !== 0) return false;
150
+ return major === 0 ? minor > 0 : minor === 0;
151
+ }
152
+ /** This function gives the version and the body of each version section. Another heading of level 1 or 2 ends a section. */
153
+ function versionSections(markdown) {
154
+ const sections = [];
155
+ let section = null;
156
+ for (const line of markdown.split(/\r?\n/)) {
157
+ const heading = VERSION_HEADING.exec(line);
158
+ if (heading?.[1] !== void 0) {
159
+ section = {
160
+ version: heading[1],
161
+ heading: line,
162
+ body: []
163
+ };
164
+ sections.push(section);
165
+ } else if (/^#{1,2}\s/.test(line)) section = null;
166
+ else section?.body.push(line);
167
+ }
168
+ return sections;
169
+ }
170
+ /**
171
+ * This function gives the first statement of a section: the first sentence of its first paragraph,
172
+ * else the first change of its lists. A dependency line is not a change.
173
+ */
174
+ function firstStatement(section) {
175
+ const first = section.body.join("\n").split(/\n[ \t]*\n/).map((block) => block.trim()).find((block) => block !== "" && !block.startsWith("#"));
176
+ if (first !== void 0 && !/^[-*+]\s/.test(first)) return firstSentence(first);
177
+ const [parsed] = parseChangelog([section.heading, ...section.body].join("\n"));
178
+ return parsed?.entries[0]?.text ?? "";
179
+ }
180
+ /** This function tells if a section of a fixed group has no change of its own above the level of PATCH. */
181
+ function raisedByGroup(text) {
182
+ if (/^### (?:Minor|Major) Changes\b/m.test(text)) return false;
183
+ return text.trim() === "" || /^### Patch Changes\b/m.test(text) || /^No changes in this release\.$/m.test(text);
184
+ }
185
+ /** This function tells if a section has a line that names a new version of a dependency. */
186
+ function hasDependencyLine(section) {
187
+ return section.body.some((line) => {
188
+ const item = /^[-*+]\s+(.*)$/.exec(line)?.[1];
189
+ return item !== void 0 && DEPENDENCY_ENTRY.test((COMMIT_ENTRY.exec(item)?.[2] ?? item).trim());
190
+ });
191
+ }
192
+ /**
193
+ * This function gives the faults of the version sections of a changelog, in the order of the file.
194
+ * A section can have two faults: one for its statement and one for its break mark.
195
+ *
196
+ * @remarks The break rule does not read a pre-release or the oldest section of the file, because
197
+ * the oldest section has no earlier release to break. A dependency line does not say why the API
198
+ * broke. Thus a section with only dependency lines has a fault on a break of the version rule. The
199
+ * option `fixedGroup` removes this fault.
200
+ */
201
+ function changelogFaults(markdown, options = {}) {
202
+ const from = options.from === void 0 ? null : versionCore(options.from);
203
+ if (options.from !== void 0 && from === null) throw new RangeError(`"${options.from}" is not a version.`);
204
+ const sections = versionSections(markdown);
205
+ const oldest = sections.map((section) => versionCore(section.version)).filter((core) => core !== null).reduce((low, core) => low === null || compareCores(core, low) < 0 ? core : low, null);
206
+ const faults = [];
207
+ for (const section of sections) {
208
+ const core = versionCore(section.version);
209
+ if (core === null || from !== null && compareCores(core, from) < 0) continue;
210
+ const statement = firstStatement(section);
211
+ const fault = (kind) => {
212
+ faults.push({
213
+ version: section.version,
214
+ kind,
215
+ statement
216
+ });
217
+ };
218
+ if (statement === "" && options.fixedGroup === true) continue;
219
+ const ruled = !section.version.includes("-") && oldest !== null && compareCores(core, oldest) !== 0;
220
+ if (statement === "" && (!hasDependencyLine(section) || ruled && ruleGivesBreak(core))) fault("no-statement");
221
+ else if (statement !== "" && namesOnlyKind(statement)) fault("version-kind-only");
222
+ if (!ruled) continue;
223
+ const text = section.body.join("\n");
224
+ const marked = sectionMarksBreak(text);
225
+ if (!ruleGivesBreak(core)) {
226
+ if (marked) fault("mark-without-break");
227
+ } else if (!marked && !(options.fixedGroup === true && raisedByGroup(text))) fault("break-unmarked");
228
+ }
229
+ return faults;
230
+ }
99
231
  /**
100
232
  * This function gives the changes of the text of a GitHub release: one change for each top-level
101
233
  * list item. Fenced code is not read.
@@ -1997,6 +2129,7 @@ function openWork(data) {
1997
2129
  /**
1998
2130
  * This function renders the measures of `kpi.json`: their values, the date of the data and the engines that made it.
1999
2131
  * A measure with its own date shows that date in a column, so that the line above the table stays true.
2132
+ * A measure that names what it does not count shows that text under the table.
2000
2133
  */
2001
2134
  function measures(data) {
2002
2135
  const { kpi } = data;
@@ -2004,9 +2137,10 @@ function measures(data) {
2004
2137
  const ownDates = kpi.measures.some((measure) => measure.at !== void 0);
2005
2138
  const dateCell = (at) => ownDates ? `<td><time datetime="${escapeHtml(at)}">${escapeHtml(longDate(at))}</time></td>` : "";
2006
2139
  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("");
2140
+ const limits = kpi.measures.filter((measure) => measure.notCounted !== void 0).map((measure) => `<p><code>${escapeHtml(measure.id)}</code> does not count: ${escapeHtml(measure.notCounted ?? "")}</p>`).join("");
2007
2141
  const engines = kpi.engines ?? [];
2008
2142
  const commit = link(`https://github.com/${data.source.github}/commit/${kpi.commit}`, `<code>${kpi.commit.slice(0, 7)}</code>`);
2009
- 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>`;
2143
+ 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>` + limits + "</section>";
2010
2144
  }
2011
2145
  function when(date) {
2012
2146
  const text = escapeHtml(longDate(date));
@@ -2371,6 +2505,6 @@ async function runCli(argv, io) {
2371
2505
  throw error;
2372
2506
  }
2373
2507
  }
2374
- export { firstSentence as $, STATUS_CLASSES as A, DEFAULT_CHANGELOGS as B, pageShell as C, utcDay as D, shortDate as E, plainText as F, releaseReading as G, mergeReleases as H, readStatusBoard as I, GITHUB_GRAPHQL as J, globFiles as K, roadmapItems as L, linksOf as M, markdownTables as N, OPEN_CLASSES as O, parseRoadmap as P, changelogVersions as Q, splitRow as R, BASE_CSS as S, longDate as T, readChangelogs as U, ReleaseDataError as V, readReleases as W, githubOpenWork as X, checkStateOf as Y, githubReleases as Z, renderReleasesPage as _, runCli as a, releaseSummary as b, STATUS_SCRIPT as c, renderStatusPage as d, headingAnchor as et, sectionsOf as f, releasesLead as g, RELEASES_SCRIPT as h, parseBuildArgs as i, buildStatusBoard as j, RoadmapParseError as k, classCounts as l, statusLead as m, UsageError as n, releaseBodyEntries as nt, TYPESCRIPT_PROBE as o, slugOf as p, GITHUB_API as q, build as r, probeScript as s, HELP as t, parseChangelog as tt, openGroups as u, laneCenter as v, dayRange as w, timelineLayout as x, oldestFirst as y, statusClassOf as z };
2508
+ export { changelogVersions as $, STATUS_CLASSES as A, DEFAULT_CHANGELOGS as B, pageShell as C, utcDay as D, shortDate as E, plainText as F, releaseReading as G, mergeReleases as H, readStatusBoard as I, GITHUB_GRAPHQL as J, globFiles as K, roadmapItems as L, linksOf as M, markdownTables as N, OPEN_CLASSES as O, parseRoadmap as P, changelogFaults as Q, splitRow as R, BASE_CSS as S, longDate as T, readChangelogs as U, ReleaseDataError as V, readReleases as W, githubOpenWork as X, checkStateOf as Y, githubReleases as Z, renderReleasesPage as _, runCli as a, releaseSummary as b, STATUS_SCRIPT as c, renderStatusPage as d, firstSentence as et, sectionsOf as f, releasesLead as g, RELEASES_SCRIPT as h, parseBuildArgs as i, sectionMarksBreak as it, buildStatusBoard as j, RoadmapParseError as k, classCounts as l, statusLead as m, UsageError as n, parseChangelog as nt, TYPESCRIPT_PROBE as o, slugOf as p, GITHUB_API as q, build as r, releaseBodyEntries as rt, probeScript as s, HELP as t, headingAnchor as tt, openGroups as u, laneCenter as v, dayRange as w, timelineLayout as x, oldestFirst as y, statusClassOf as z };
2375
2509
 
2376
2510
  //# sourceMappingURL=build.js.map