@brandlab-digital/conformance 3.11.0 → 3.12.1

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.
Files changed (71) hide show
  1. package/dist/current-release.js +6 -6
  2. package/dist/current-release.js.map +1 -1
  3. package/dist/ready-cli.d.ts.map +1 -1
  4. package/dist/ready-cli.js +1 -0
  5. package/dist/ready-cli.js.map +1 -1
  6. package/dist/site-system/checks.d.ts +1 -1
  7. package/dist/site-system/checks.js +1 -1
  8. package/dist/site-system/checks.js.map +1 -1
  9. package/dist/site-system/rule-modules.d.ts.map +1 -1
  10. package/dist/site-system/rule-modules.js +5 -0
  11. package/dist/site-system/rule-modules.js.map +1 -1
  12. package/dist/site-system/rules/full/common.d.ts +37 -0
  13. package/dist/site-system/rules/full/common.d.ts.map +1 -0
  14. package/dist/site-system/rules/full/common.js +51 -0
  15. package/dist/site-system/rules/full/common.js.map +1 -0
  16. package/dist/site-system/rules/full/e10-phrases.d.ts +14 -0
  17. package/dist/site-system/rules/full/e10-phrases.d.ts.map +1 -0
  18. package/dist/site-system/rules/full/e10-phrases.js +69 -0
  19. package/dist/site-system/rules/full/e10-phrases.js.map +1 -0
  20. package/dist/site-system/rules/full/estate.d.ts +118 -0
  21. package/dist/site-system/rules/full/estate.d.ts.map +1 -0
  22. package/dist/site-system/rules/full/estate.js +447 -0
  23. package/dist/site-system/rules/full/estate.js.map +1 -0
  24. package/dist/site-system/rules/full/geography.d.ts +60 -0
  25. package/dist/site-system/rules/full/geography.d.ts.map +1 -0
  26. package/dist/site-system/rules/full/geography.js +111 -0
  27. package/dist/site-system/rules/full/geography.js.map +1 -0
  28. package/dist/site-system/rules/full/index.d.ts +33 -0
  29. package/dist/site-system/rules/full/index.d.ts.map +1 -0
  30. package/dist/site-system/rules/full/index.js +126 -0
  31. package/dist/site-system/rules/full/index.js.map +1 -0
  32. package/dist/site-system/rules/full/k12-manifests.d.ts +19 -0
  33. package/dist/site-system/rules/full/k12-manifests.d.ts.map +1 -0
  34. package/dist/site-system/rules/full/k12-manifests.js +130 -0
  35. package/dist/site-system/rules/full/k12-manifests.js.map +1 -0
  36. package/dist/site-system/rules/full/l10-location-copy.d.ts +15 -0
  37. package/dist/site-system/rules/full/l10-location-copy.d.ts.map +1 -0
  38. package/dist/site-system/rules/full/l10-location-copy.js +59 -0
  39. package/dist/site-system/rules/full/l10-location-copy.js.map +1 -0
  40. package/dist/site-system/rules/full/l3-neighbours.d.ts +16 -0
  41. package/dist/site-system/rules/full/l3-neighbours.d.ts.map +1 -0
  42. package/dist/site-system/rules/full/l3-neighbours.js +78 -0
  43. package/dist/site-system/rules/full/l3-neighbours.js.map +1 -0
  44. package/dist/site-system/rules/full/l4-local-copy.d.ts +27 -0
  45. package/dist/site-system/rules/full/l4-local-copy.d.ts.map +1 -0
  46. package/dist/site-system/rules/full/l4-local-copy.js +150 -0
  47. package/dist/site-system/rules/full/l4-local-copy.js.map +1 -0
  48. package/dist/site-system/rules/full/l9-data-contract.d.ts +15 -0
  49. package/dist/site-system/rules/full/l9-data-contract.d.ts.map +1 -0
  50. package/dist/site-system/rules/full/l9-data-contract.js +65 -0
  51. package/dist/site-system/rules/full/l9-data-contract.js.map +1 -0
  52. package/dist/site-system/rules/full/links.d.ts +23 -0
  53. package/dist/site-system/rules/full/links.d.ts.map +1 -0
  54. package/dist/site-system/rules/full/links.js +131 -0
  55. package/dist/site-system/rules/full/links.js.map +1 -0
  56. package/dist/site-system/rules/full/loc-index.d.ts +16 -0
  57. package/dist/site-system/rules/full/loc-index.d.ts.map +1 -0
  58. package/dist/site-system/rules/full/loc-index.js +59 -0
  59. package/dist/site-system/rules/full/loc-index.js.map +1 -0
  60. package/dist/site-system/rules/full/s4-route-files.d.ts +4 -0
  61. package/dist/site-system/rules/full/s4-route-files.d.ts.map +1 -0
  62. package/dist/site-system/rules/full/s4-route-files.js +125 -0
  63. package/dist/site-system/rules/full/s4-route-files.js.map +1 -0
  64. package/dist/site-system/rules/full/strategy.d.ts +43 -0
  65. package/dist/site-system/rules/full/strategy.d.ts.map +1 -0
  66. package/dist/site-system/rules/full/strategy.js +147 -0
  67. package/dist/site-system/rules/full/strategy.js.map +1 -0
  68. package/dist/site-system/runners.d.ts +2 -0
  69. package/dist/site-system/runners.d.ts.map +1 -1
  70. package/dist/site-system/runners.js.map +1 -1
  71. package/package.json +2 -2
@@ -0,0 +1,147 @@
1
+ /**
2
+ * What the full tier reads from `src/data/strategy.ts` without running it: phrase ownership and the wave schedule
3
+ * (`defineSiteStrategy`, site-core 3.12.0) and the section orders (`defineSectionOrder`: the trade and any recorded
4
+ * alternative order per family). The wave rules mirror site-core's, because conformance compiles without site-core;
5
+ * strategy.test.ts keeps the two equal.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { LiteralReader, isFullyResolved, isLiteralObject } from "../enquiry/literal.js";
10
+ export const FULL_STRATEGY_FILE = "src/data/strategy.ts";
11
+ /** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */
12
+ export const WAVE_MIN_GAP_DAYS = 14;
13
+ export const WAVE_MAX_GAP_DAYS = 28;
14
+ /** As site-core's normaliseSitePhrase: lowercase words, "&" read as "and", punctuation dropped, a plural "s" dropped. */
15
+ export function normalisePhrase(phrase) {
16
+ return phrase
17
+ .toLowerCase()
18
+ .replace(/&/g, " and ")
19
+ .replace(/[^a-z0-9]+/g, " ")
20
+ .trim()
21
+ .split(" ")
22
+ .map((word) => (word.length > 3 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word))
23
+ .join(" ");
24
+ }
25
+ /** As site-core's matchesWaveRoute: an exact path, or a pattern with `*` (one segment) and `**` (any depth). */
26
+ export function matchesWaveRoute(path, route) {
27
+ if (!route.includes("*"))
28
+ return path === route;
29
+ const source = route
30
+ .split(/(\*\*|\*)/)
31
+ .map((part) => (part === "**" ? ".*" : part === "*" ? "[^/]*" : part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")))
32
+ .join("");
33
+ return new RegExp(`^${source}$`).test(path);
34
+ }
35
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
36
+ const DAY_MS = 86_400_000;
37
+ /** `today` (Sydney, YYYY-MM-DD) dates the schedule's next wave, which may not wait open-ended. */
38
+ export function readFullStrategy(projectRoot, today) {
39
+ const empty = { phrases: [], waves: [], orders: [] };
40
+ const file = join(projectRoot, FULL_STRATEGY_FILE);
41
+ if (!existsSync(file)) {
42
+ return { phrases: null, waves: [], trade: undefined, recordedOrders: new Map(), problems: empty, found: false };
43
+ }
44
+ const reader = new LiteralReader(readFileSync(file, "utf8"), FULL_STRATEGY_FILE);
45
+ const problems = { phrases: [], waves: [], orders: [] };
46
+ const argument = reader.callArguments("defineSiteStrategy")[0] ?? reader.constant("strategy");
47
+ const value = argument ? reader.evaluate(argument) : undefined;
48
+ const record = isLiteralObject(value) ? value : undefined;
49
+ let phrases = null;
50
+ const rawPhrases = record?.["phrases"];
51
+ if (rawPhrases !== undefined) {
52
+ if (!isLiteralObject(rawPhrases) || !isFullyResolved(rawPhrases) || typeof rawPhrases["mainTrade"] !== "string" || !Array.isArray(rawPhrases["owners"])) {
53
+ problems.phrases.push(`The phrases in ${FULL_STRATEGY_FILE} are not plain data with a mainTrade and an owners list, so phrase ownership cannot be read.`);
54
+ }
55
+ else {
56
+ const owners = rawPhrases["owners"].flatMap((owner) => isLiteralObject(owner) && typeof owner["phrase"] === "string" && typeof owner["route"] === "string" ? [{ phrase: owner["phrase"], route: owner["route"] }] : []);
57
+ if (owners.length !== rawPhrases["owners"].length)
58
+ problems.phrases.push(`An owner in ${FULL_STRATEGY_FILE} has no phrase or route.`);
59
+ phrases = { mainTrade: rawPhrases["mainTrade"], owners };
60
+ }
61
+ }
62
+ const waves = [];
63
+ const rawWaves = record?.["waves"];
64
+ if (rawWaves !== undefined) {
65
+ if (!Array.isArray(rawWaves) || !isFullyResolved(rawWaves)) {
66
+ problems.waves.push(`The waves in ${FULL_STRATEGY_FILE} are not a plain list, so the release schedule cannot be read.`);
67
+ }
68
+ else {
69
+ for (const [index, wave] of rawWaves.entries()) {
70
+ if (!isLiteralObject(wave) || typeof wave["releaseOn"] !== "string" || !Array.isArray(wave["routes"]) || !wave["routes"].every((route) => typeof route === "string")) {
71
+ problems.waves.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} needs a releaseOn date and a list of routes.`);
72
+ continue;
73
+ }
74
+ waves.push({ releaseOn: wave["releaseOn"], routes: wave["routes"] });
75
+ }
76
+ problems.waves.push(...waveScheduleProblems(waves, today));
77
+ }
78
+ }
79
+ if (argument && !record) {
80
+ problems.phrases.push(`${FULL_STRATEGY_FILE} is not plain data, so phrase ownership cannot be read.`);
81
+ problems.waves.push(`${FULL_STRATEGY_FILE} is not plain data, so the release schedule cannot be read.`);
82
+ }
83
+ let trade;
84
+ const recordedOrders = new Map();
85
+ const orderArgument = reader.callArguments("defineSectionOrder")[0];
86
+ if (orderArgument) {
87
+ const orders = reader.evaluate(orderArgument);
88
+ if (!isLiteralObject(orders) || !isFullyResolved(orders)) {
89
+ problems.orders.push(`defineSectionOrder in ${FULL_STRATEGY_FILE} is not plain data, so recorded section orders cannot be read.`);
90
+ }
91
+ else {
92
+ if (typeof orders["trade"] === "string")
93
+ trade = orders["trade"];
94
+ const recorded = orders["recorded"];
95
+ if (isLiteralObject(recorded)) {
96
+ for (const [family, entry] of Object.entries(recorded)) {
97
+ if (isLiteralObject(entry) && Array.isArray(entry["order"]) && entry["order"].every((id) => typeof id === "string")) {
98
+ recordedOrders.set(family, entry["order"]);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return { phrases, waves, trade, recordedOrders, problems, found: true };
105
+ }
106
+ /**
107
+ * The schedule's rules, as site-core's: valid dates, in order, 14 to 28 days apart, each route in one wave, and the next
108
+ * wave still to come at most 28 days after the last released one (or after today when none is).
109
+ */
110
+ export function waveScheduleProblems(waves, today) {
111
+ const problems = [];
112
+ const seen = new Set();
113
+ let previous;
114
+ for (const [index, wave] of waves.entries()) {
115
+ const day = ISO_DAY.test(wave.releaseOn) ? Date.parse(`${wave.releaseOn}T00:00:00Z`) : Number.NaN;
116
+ if (Number.isNaN(day) || new Date(day).toISOString().slice(0, 10) !== wave.releaseOn) {
117
+ problems.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} has no valid releaseOn date ("${wave.releaseOn}").`);
118
+ continue;
119
+ }
120
+ if (wave.routes.length === 0)
121
+ problems.push(`The wave of ${wave.releaseOn} in ${FULL_STRATEGY_FILE} lists no routes.`);
122
+ for (const route of wave.routes) {
123
+ if (!route.startsWith("/"))
124
+ problems.push(`The wave of ${wave.releaseOn} lists "${route}", which is not a root-relative path.`);
125
+ if (seen.has(route))
126
+ problems.push(`"${route}" is in more than one wave in ${FULL_STRATEGY_FILE}.`);
127
+ seen.add(route);
128
+ }
129
+ if (previous) {
130
+ const gap = (day - previous.day) / DAY_MS;
131
+ if (gap < WAVE_MIN_GAP_DAYS || gap > WAVE_MAX_GAP_DAYS) {
132
+ problems.push(`The wave of ${wave.releaseOn} is ${gap} days after the wave of ${previous.on}; waves go out in date order, ${WAVE_MIN_GAP_DAYS} to ${WAVE_MAX_GAP_DAYS} days apart.`);
133
+ }
134
+ }
135
+ previous = { day, on: wave.releaseOn };
136
+ }
137
+ const next = waves.findIndex((wave) => wave.releaseOn > today);
138
+ if (next !== -1 && problems.length === 0) {
139
+ const from = next > 0 ? waves[next - 1].releaseOn : today;
140
+ const gap = (Date.parse(`${waves[next].releaseOn}T00:00:00Z`) - Date.parse(`${from}T00:00:00Z`)) / DAY_MS;
141
+ if (gap > WAVE_MAX_GAP_DAYS) {
142
+ problems.push(`The next wave, on ${waves[next].releaseOn}, is ${gap} days after ${next > 0 ? `the last released wave (${from})` : `today (${from})`}; it goes out within ${WAVE_MAX_GAP_DAYS} days, so no page waits open-ended.`);
143
+ }
144
+ }
145
+ return problems;
146
+ }
147
+ //# sourceMappingURL=strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/strategy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the full tier reads from `src/data/strategy.ts` without running it: phrase ownership and the wave schedule\n * (`defineSiteStrategy`, site-core 3.12.0) and the section orders (`defineSectionOrder`: the trade and any recorded\n * alternative order per family). The wave rules mirror site-core's, because conformance compiles without site-core;\n * strategy.test.ts keeps the two equal.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { LiteralReader, isFullyResolved, isLiteralObject, type Literal } from \"../enquiry/literal.js\";\n\nexport const FULL_STRATEGY_FILE = \"src/data/strategy.ts\";\n\n/** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */\nexport const WAVE_MIN_GAP_DAYS = 14;\nexport const WAVE_MAX_GAP_DAYS = 28;\n\nexport interface FullStrategy {\n readonly phrases: { readonly mainTrade: string; readonly owners: readonly { readonly phrase: string; readonly route: string }[] } | null;\n readonly waves: readonly { readonly releaseOn: string; readonly routes: readonly string[] }[];\n /** The trade `defineSectionOrder` names, if any. */\n readonly trade: string | undefined;\n /** Recorded alternative orders by family. */\n readonly recordedOrders: ReadonlyMap<string, readonly string[]>;\n /** What could not be read or breaks the schedule's rules; the checks that need it fail with these. */\n readonly problems: { readonly phrases: string[]; readonly waves: string[]; readonly orders: string[] };\n readonly found: boolean;\n}\n\n/** As site-core's normaliseSitePhrase: lowercase words, \"&\" read as \"and\", punctuation dropped, a plural \"s\" dropped. */\nexport function normalisePhrase(phrase: string): string {\n return phrase\n .toLowerCase()\n .replace(/&/g, \" and \")\n .replace(/[^a-z0-9]+/g, \" \")\n .trim()\n .split(\" \")\n .map((word) => (word.length > 3 && word.endsWith(\"s\") && !word.endsWith(\"ss\") ? word.slice(0, -1) : word))\n .join(\" \");\n}\n\n/** As site-core's matchesWaveRoute: an exact path, or a pattern with `*` (one segment) and `**` (any depth). */\nexport function matchesWaveRoute(path: string, route: string): boolean {\n if (!route.includes(\"*\")) return path === route;\n const source = route\n .split(/(\\*\\*|\\*)/)\n .map((part) => (part === \"**\" ? \".*\" : part === \"*\" ? \"[^/]*\" : part.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\")))\n .join(\"\");\n return new RegExp(`^${source}$`).test(path);\n}\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DAY_MS = 86_400_000;\n\n/** `today` (Sydney, YYYY-MM-DD) dates the schedule's next wave, which may not wait open-ended. */\nexport function readFullStrategy(projectRoot: string, today: string): FullStrategy {\n const empty = { phrases: [] as string[], waves: [] as string[], orders: [] as string[] };\n const file = join(projectRoot, FULL_STRATEGY_FILE);\n if (!existsSync(file)) {\n return { phrases: null, waves: [], trade: undefined, recordedOrders: new Map(), problems: empty, found: false };\n }\n const reader = new LiteralReader(readFileSync(file, \"utf8\"), FULL_STRATEGY_FILE);\n const problems = { phrases: [] as string[], waves: [] as string[], orders: [] as string[] };\n\n const argument = reader.callArguments(\"defineSiteStrategy\")[0] ?? reader.constant(\"strategy\");\n const value: Literal | undefined = argument ? reader.evaluate(argument) : undefined;\n const record = isLiteralObject(value) ? value : undefined;\n\n let phrases: FullStrategy[\"phrases\"] = null;\n const rawPhrases = record?.[\"phrases\"];\n if (rawPhrases !== undefined) {\n if (!isLiteralObject(rawPhrases) || !isFullyResolved(rawPhrases) || typeof rawPhrases[\"mainTrade\"] !== \"string\" || !Array.isArray(rawPhrases[\"owners\"])) {\n problems.phrases.push(`The phrases in ${FULL_STRATEGY_FILE} are not plain data with a mainTrade and an owners list, so phrase ownership cannot be read.`);\n } else {\n const owners = rawPhrases[\"owners\"].flatMap((owner) =>\n isLiteralObject(owner) && typeof owner[\"phrase\"] === \"string\" && typeof owner[\"route\"] === \"string\" ? [{ phrase: owner[\"phrase\"], route: owner[\"route\"] }] : []);\n if (owners.length !== rawPhrases[\"owners\"].length) problems.phrases.push(`An owner in ${FULL_STRATEGY_FILE} has no phrase or route.`);\n phrases = { mainTrade: rawPhrases[\"mainTrade\"], owners };\n }\n }\n\n const waves: { releaseOn: string; routes: string[] }[] = [];\n const rawWaves = record?.[\"waves\"];\n if (rawWaves !== undefined) {\n if (!Array.isArray(rawWaves) || !isFullyResolved(rawWaves)) {\n problems.waves.push(`The waves in ${FULL_STRATEGY_FILE} are not a plain list, so the release schedule cannot be read.`);\n } else {\n for (const [index, wave] of rawWaves.entries()) {\n if (!isLiteralObject(wave) || typeof wave[\"releaseOn\"] !== \"string\" || !Array.isArray(wave[\"routes\"]) || !wave[\"routes\"].every((route) => typeof route === \"string\")) {\n problems.waves.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} needs a releaseOn date and a list of routes.`);\n continue;\n }\n waves.push({ releaseOn: wave[\"releaseOn\"], routes: wave[\"routes\"] as string[] });\n }\n problems.waves.push(...waveScheduleProblems(waves, today));\n }\n }\n if (argument && !record) {\n problems.phrases.push(`${FULL_STRATEGY_FILE} is not plain data, so phrase ownership cannot be read.`);\n problems.waves.push(`${FULL_STRATEGY_FILE} is not plain data, so the release schedule cannot be read.`);\n }\n\n let trade: string | undefined;\n const recordedOrders = new Map<string, string[]>();\n const orderArgument = reader.callArguments(\"defineSectionOrder\")[0];\n if (orderArgument) {\n const orders = reader.evaluate(orderArgument);\n if (!isLiteralObject(orders) || !isFullyResolved(orders)) {\n problems.orders.push(`defineSectionOrder in ${FULL_STRATEGY_FILE} is not plain data, so recorded section orders cannot be read.`);\n } else {\n if (typeof orders[\"trade\"] === \"string\") trade = orders[\"trade\"];\n const recorded = orders[\"recorded\"];\n if (isLiteralObject(recorded)) {\n for (const [family, entry] of Object.entries(recorded)) {\n if (isLiteralObject(entry) && Array.isArray(entry[\"order\"]) && entry[\"order\"].every((id) => typeof id === \"string\")) {\n recordedOrders.set(family, entry[\"order\"] as string[]);\n }\n }\n }\n }\n }\n\n return { phrases, waves, trade, recordedOrders, problems, found: true };\n}\n\n/**\n * The schedule's rules, as site-core's: valid dates, in order, 14 to 28 days apart, each route in one wave, and the next\n * wave still to come at most 28 days after the last released one (or after today when none is).\n */\nexport function waveScheduleProblems(waves: readonly { readonly releaseOn: string; readonly routes: readonly string[] }[], today: string): string[] {\n const problems: string[] = [];\n const seen = new Set<string>();\n let previous: { day: number; on: string } | undefined;\n for (const [index, wave] of waves.entries()) {\n const day = ISO_DAY.test(wave.releaseOn) ? Date.parse(`${wave.releaseOn}T00:00:00Z`) : Number.NaN;\n if (Number.isNaN(day) || new Date(day).toISOString().slice(0, 10) !== wave.releaseOn) {\n problems.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} has no valid releaseOn date (\"${wave.releaseOn}\").`);\n continue;\n }\n if (wave.routes.length === 0) problems.push(`The wave of ${wave.releaseOn} in ${FULL_STRATEGY_FILE} lists no routes.`);\n for (const route of wave.routes) {\n if (!route.startsWith(\"/\")) problems.push(`The wave of ${wave.releaseOn} lists \"${route}\", which is not a root-relative path.`);\n if (seen.has(route)) problems.push(`\"${route}\" is in more than one wave in ${FULL_STRATEGY_FILE}.`);\n seen.add(route);\n }\n if (previous) {\n const gap = (day - previous.day) / DAY_MS;\n if (gap < WAVE_MIN_GAP_DAYS || gap > WAVE_MAX_GAP_DAYS) {\n problems.push(`The wave of ${wave.releaseOn} is ${gap} days after the wave of ${previous.on}; waves go out in date order, ${WAVE_MIN_GAP_DAYS} to ${WAVE_MAX_GAP_DAYS} days apart.`);\n }\n }\n previous = { day, on: wave.releaseOn };\n }\n const next = waves.findIndex((wave) => wave.releaseOn > today);\n if (next !== -1 && problems.length === 0) {\n const from = next > 0 ? waves[next - 1]!.releaseOn : today;\n const gap = (Date.parse(`${waves[next]!.releaseOn}T00:00:00Z`) - Date.parse(`${from}T00:00:00Z`)) / DAY_MS;\n if (gap > WAVE_MAX_GAP_DAYS) {\n problems.push(`The next wave, on ${waves[next]!.releaseOn}, is ${gap} days after ${next > 0 ? `the last released wave (${from})` : `today (${from})`}; it goes out within ${WAVE_MAX_GAP_DAYS} days, so no page waits open-ended.`);\n }\n }\n return problems;\n}\n"]}
@@ -21,6 +21,8 @@ export interface SiteSystemRenderedInput {
21
21
  * rather than reading the config themselves; a caller that leaves it out lets each rule read the config.
22
22
  */
23
23
  readonly siteSystem?: SiteSystemSettings | null;
24
+ /** The production build directory (`.next` unless `--build-dir` says otherwise), for rules that read the whole build. */
25
+ readonly buildDir?: string;
24
26
  }
25
27
  /** A rule module: one finding per check result (pass, fail or not-applicable), never a throw for a failing site. */
26
28
  export type SiteSystemRenderedRunner = (input: SiteSystemRenderedInput) => Promise<readonly SiteSystemFinding[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"runners.d.ts","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,uBAAuB;IACtC,yGAAyG;IACzG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACjD;AAED,oHAAoH;AACpH,MAAM,MAAM,wBAAwB,GAAG,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;AAIjH;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,SAAS,wBAAwB,EAAE,EAC5C,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC;IAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAkBhE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAW9E"}
1
+ {"version":3,"file":"runners.d.ts","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,uBAAuB;IACtC,yGAAyG;IACzG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAChD,yHAAyH;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,oHAAoH;AACpH,MAAM,MAAM,wBAAwB,GAAG,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;AAIjH;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,SAAS,wBAAwB,EAAE,EAC5C,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC;IAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAkBhE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAW9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"runners.js","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAsBzE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAA4C,EAC5C,KAA8B;IAE9B,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC;QAC5C,IAAI,OAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,2BAA2B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACvI,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,mBAAmB,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;gBACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,uFAAuF,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,OAAO,CACL,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QAC9B,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,CAAC;QAC/F,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;QACpE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CACnE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The rule-module interface of the ready gate. Each module under ./rules/ exports a runner, listed in\n * ./rule-modules.ts; `brandlab-ready` calls every runner with the sampled pages and merges what they find. Static\n * rules use the same shape and read the project through `projectRoot`. Every runner is called even when no page\n * could be read: static rules still run, and rendered rules return not-applicable for an empty page list (the gate\n * fails the run itself when home or the contact page is missing).\n */\nimport { SITE_SYSTEM_CHECKS, type SiteSystemFinding } from \"./checks.js\";\nimport type { SiteSystemSettings } from \"./config.js\";\nimport type { SiteSystemPage } from \"./samples.js\";\n\nexport interface SiteSystemRenderedInput {\n /** The running site's base URL with --base-url; empty when the pages were read from the build output. */\n readonly baseUrl: string;\n readonly pages: readonly SiteSystemPage[];\n /** Planned sample paths with no HTML, so rules can say what they could not check. */\n readonly missing: readonly string[];\n /** The project directory, for rules that read its data files. */\n readonly projectRoot: string;\n /**\n * The gate's parsed `siteSystem` settings from brandlab.config.ts (null when the site declares none). Rules use these\n * rather than reading the config themselves; a caller that leaves it out lets each rule read the config.\n */\n readonly siteSystem?: SiteSystemSettings | null;\n}\n\n/** A rule module: one finding per check result (pass, fail or not-applicable), never a throw for a failing site. */\nexport type SiteSystemRenderedRunner = (input: SiteSystemRenderedInput) => Promise<readonly SiteSystemFinding[]>;\n\nconst KNOWN_IDS = new Set<string>(SITE_SYSTEM_CHECKS.map((check) => check.id));\n\n/**\n * Runs each runner in turn. A runner that throws, or returns a finding for an unknown check, is reported as a\n * problem; the checks it would have covered then show as not run, which fails them where they block.\n */\nexport async function runSiteSystemRenderedRunners(\n runners: readonly SiteSystemRenderedRunner[],\n input: SiteSystemRenderedInput,\n): Promise<{ findings: SiteSystemFinding[]; problems: string[] }> {\n const findings: SiteSystemFinding[] = [];\n const problems: string[] = [];\n for (const runner of runners) {\n const name = runner.name || \"A rule module\";\n let results: readonly SiteSystemFinding[];\n try {\n results = await runner(input);\n } catch (error) {\n problems.push(`${name} stopped with an error (${error instanceof Error ? error.message : String(error)}), so its checks did not run.`);\n continue;\n }\n for (const finding of results) {\n if (isSiteSystemFinding(finding)) findings.push(finding);\n else problems.push(`${name} returned a result for an unknown check or with an unknown status, so it was ignored.`);\n }\n }\n return { findings, problems };\n}\n\n/** A finding with a known check id, a known status and a message; anything else is ignored. */\nexport function isSiteSystemFinding(value: unknown): value is SiteSystemFinding {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const finding = value as Record<string, unknown>;\n return (\n typeof finding.checkId === \"string\" &&\n KNOWN_IDS.has(finding.checkId) &&\n (finding.status === \"pass\" || finding.status === \"fail\" || finding.status === \"not-applicable\") &&\n typeof finding.message === \"string\" &&\n (finding.target === undefined || typeof finding.target === \"string\") &&\n (finding.severity === undefined || finding.severity === \"warning\")\n );\n}\n"]}
1
+ {"version":3,"file":"runners.js","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAwBzE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAA4C,EAC5C,KAA8B;IAE9B,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC;QAC5C,IAAI,OAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,2BAA2B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACvI,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,mBAAmB,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;gBACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,uFAAuF,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,OAAO,CACL,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QAC9B,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,CAAC;QAC/F,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;QACpE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CACnE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The rule-module interface of the ready gate. Each module under ./rules/ exports a runner, listed in\n * ./rule-modules.ts; `brandlab-ready` calls every runner with the sampled pages and merges what they find. Static\n * rules use the same shape and read the project through `projectRoot`. Every runner is called even when no page\n * could be read: static rules still run, and rendered rules return not-applicable for an empty page list (the gate\n * fails the run itself when home or the contact page is missing).\n */\nimport { SITE_SYSTEM_CHECKS, type SiteSystemFinding } from \"./checks.js\";\nimport type { SiteSystemSettings } from \"./config.js\";\nimport type { SiteSystemPage } from \"./samples.js\";\n\nexport interface SiteSystemRenderedInput {\n /** The running site's base URL with --base-url; empty when the pages were read from the build output. */\n readonly baseUrl: string;\n readonly pages: readonly SiteSystemPage[];\n /** Planned sample paths with no HTML, so rules can say what they could not check. */\n readonly missing: readonly string[];\n /** The project directory, for rules that read its data files. */\n readonly projectRoot: string;\n /**\n * The gate's parsed `siteSystem` settings from brandlab.config.ts (null when the site declares none). Rules use these\n * rather than reading the config themselves; a caller that leaves it out lets each rule read the config.\n */\n readonly siteSystem?: SiteSystemSettings | null;\n /** The production build directory (`.next` unless `--build-dir` says otherwise), for rules that read the whole build. */\n readonly buildDir?: string;\n}\n\n/** A rule module: one finding per check result (pass, fail or not-applicable), never a throw for a failing site. */\nexport type SiteSystemRenderedRunner = (input: SiteSystemRenderedInput) => Promise<readonly SiteSystemFinding[]>;\n\nconst KNOWN_IDS = new Set<string>(SITE_SYSTEM_CHECKS.map((check) => check.id));\n\n/**\n * Runs each runner in turn. A runner that throws, or returns a finding for an unknown check, is reported as a\n * problem; the checks it would have covered then show as not run, which fails them where they block.\n */\nexport async function runSiteSystemRenderedRunners(\n runners: readonly SiteSystemRenderedRunner[],\n input: SiteSystemRenderedInput,\n): Promise<{ findings: SiteSystemFinding[]; problems: string[] }> {\n const findings: SiteSystemFinding[] = [];\n const problems: string[] = [];\n for (const runner of runners) {\n const name = runner.name || \"A rule module\";\n let results: readonly SiteSystemFinding[];\n try {\n results = await runner(input);\n } catch (error) {\n problems.push(`${name} stopped with an error (${error instanceof Error ? error.message : String(error)}), so its checks did not run.`);\n continue;\n }\n for (const finding of results) {\n if (isSiteSystemFinding(finding)) findings.push(finding);\n else problems.push(`${name} returned a result for an unknown check or with an unknown status, so it was ignored.`);\n }\n }\n return { findings, problems };\n}\n\n/** A finding with a known check id, a known status and a message; anything else is ignored. */\nexport function isSiteSystemFinding(value: unknown): value is SiteSystemFinding {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const finding = value as Record<string, unknown>;\n return (\n typeof finding.checkId === \"string\" &&\n KNOWN_IDS.has(finding.checkId) &&\n (finding.status === \"pass\" || finding.status === \"fail\" || finding.status === \"not-applicable\") &&\n typeof finding.message === \"string\" &&\n (finding.target === undefined || typeof finding.target === \"string\") &&\n (finding.severity === undefined || finding.severity === \"warning\")\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandlab-digital/conformance",
3
- "version": "3.11.0",
3
+ "version": "3.12.1",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,7 +27,7 @@
27
27
  "typecheck": "tsc --noEmit -p tsconfig.json"
28
28
  },
29
29
  "dependencies": {
30
- "@brandlab-digital/site-core": "3.11.0",
30
+ "@brandlab-digital/site-core": "3.12.1",
31
31
  "typescript": "6.0.3"
32
32
  },
33
33
  "peerDependencies": {