Skip to content

Commit a4336f4

Browse files
authored
build(scripts): guard the coverage page with a round trip and move the table helper into src (#476)
1 parent 2578e85 commit a4336f4

9 files changed

Lines changed: 747 additions & 309 deletions

File tree

‎.github/scripts/gen-action-docs.ts‎

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFileSync, writeFileSync } from "node:fs";
22
import { join } from "node:path";
3+
import { err, ok, type Result } from "neverthrow";
34
import {
45
Document,
56
type DocumentOptions,
@@ -14,6 +15,12 @@ import { z } from "zod";
1415
import { OUTPUT_DECLS } from "../../src/action/io.js";
1516
import type { InputDecl } from "../../src/flows/inputs.js";
1617
import { INPUT_DECLS } from "../../src/flows/inputs.js";
18+
import {
19+
renderedMismatch,
20+
renderTable,
21+
tableFault,
22+
tableRoundTrip,
23+
} from "../../src/report/markdown.js";
1724
import { UNDECLARED_POLICY_SECTIONS, type UndeclaredPolicySection } from "../../src/schema.js";
1825
import { overrideAdviceLevel } from "../../src/sections/contract/errors.js";
1926
import type { SectionMeta } from "../../src/sections/contract/module.js";
@@ -32,12 +39,7 @@ import { SECTIONS } from "../../src/sections/registry.js";
3239
import { agree } from "../../src/text.js";
3340
import type { UndeclaredPolicy } from "../../src/types.js";
3441
import { countWord } from "./lib/count-word.js";
35-
import {
36-
type GeneratedRegion,
37-
regenerateRegions,
38-
renderedMismatch,
39-
} from "./lib/generated-regions.js";
40-
import { renderTable, tableFault, tableRoundTrip } from "./lib/markdown-table.js";
42+
import { type GeneratedRegion, regenerateRegions } from "./lib/generated-regions.js";
4143

4244
const ROOT = join(import.meta.dir, "..", "..");
4345

@@ -181,8 +183,8 @@ export interface PolicyRowProse {
181183

182184
/**
183185
* Why a row's prose cannot be rendered: blank prose would render `delete ()` or a bare policy span, cells
184-
* tableCell() admits since the policy word fills them. The renderer refuses it at its own boundary and the parser
185-
* through this same check, by the trimmed-blank rule tableCell() uses, so what the renderer writes is exactly what
186+
* cellFault() admits since the policy word fills them. The renderer refuses it at its own boundary and the parser
187+
* through this same check, by the trimmed-blank rule cellFault() uses, so what the renderer writes is exactly what
186188
* the guard reads back. A structural type cannot vouch for a property a narrowing hid, so the value is inspected
187189
* as unknown.
188190
*/
@@ -228,7 +230,7 @@ export function policyTableFault(rows: readonly PolicyRow[]): string | undefined
228230
return `the "${row.section.key}" Defaults row ${fault}`;
229231
}
230232
}
231-
return tableFault(DEFAULTS_TABLE_HEADER, rows.map(policyCells));
233+
return tableFault(DEFAULTS_TABLE_HEADER, rows.map(policyCells), "keyed");
232234
}
233235

234236
export function renderPolicyDefaultsTable(
@@ -252,29 +254,31 @@ export function renderPolicyDefaultsTable(
252254

253255
/** A Defaults row read back to the section and prose it renders from; the delete-first order is then the
254256
* renderer's to check, through the byte compare. The dotAll flag keeps the two Unicode line separators
255-
* tableCell() admits inside the prose. */
257+
* cellFault() admits inside the prose. */
256258
function policyRow(
257259
cells: readonly string[],
258260
key: string,
259-
): { readonly section: KnobbedSection; readonly prose: PolicyRowProse } | string {
261+
): Result<{ readonly section: KnobbedSection; readonly prose: PolicyRowProse }, string> {
260262
const policy = /^(delete|keep)(?: \((.*)\))?$/s.exec(cells[1] ?? "");
261263
const buys = /^`(delete|keep)`: (.*)$/s.exec(cells[2] ?? "");
262264
if (policy === null) {
263-
return "states no delete or keep default";
265+
return err("states no delete or keep default");
264266
}
265267
if (buys === null) {
266-
return "names no policy span before its override prose";
268+
return err("names no policy span before its override prose");
267269
}
268270
const undeclaredDefault = policy[1] === "delete" ? "delete" : "keep";
269271
if (buys[1] !== OPPOSITE[undeclaredDefault]) {
270-
return `names \`${buys[1]}\` where the override is the opposite policy, \`${OPPOSITE[undeclaredDefault]}\``;
272+
return err(
273+
`names \`${buys[1]}\` where the override is the opposite policy, \`${OPPOSITE[undeclaredDefault]}\``,
274+
);
271275
}
272276
const row = { caveat: policy[2], override: buys[2] ?? "" };
273277
const fault = proseFault(row);
274278
if (fault !== undefined) {
275-
return `${fault}, which the renderer refuses`;
279+
return err(`${fault}, which the renderer refuses`);
276280
}
277-
return { section: { key, undeclaredDefault }, prose: row };
281+
return ok({ section: { key, undeclaredDefault }, prose: row });
278282
}
279283

280284
/** The row every secret family shares: the value is write-only, so a wrong delete is a loss, not a drift. */
@@ -499,7 +503,7 @@ export const GENERATED_REGIONS: Readonly<Record<string, readonly GeneratedRegion
499503
{
500504
name: "policy-defaults-table",
501505
placement: { kind: "under-heading", heading: "## Defaults per section" },
502-
body: tableRoundTrip(DEFAULTS_TABLE_HEADER, policyRow, (rows) =>
506+
body: tableRoundTrip(DEFAULTS_TABLE_HEADER, "keyed", policyRow, (rows) =>
503507
renderPolicyDefaultsTable(
504508
rows.map((row) => row.section),
505509
Object.fromEntries(rows.map((row) => [row.section.key, row.prose])),

0 commit comments

Comments
 (0)