@brandlab-digital/conformance 3.13.0 → 3.14.0

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 (78) hide show
  1. package/README.md +9 -0
  2. package/dist/analytics-e2e.d.ts +10 -16
  3. package/dist/analytics-e2e.d.ts.map +1 -1
  4. package/dist/analytics-e2e.js +61 -3
  5. package/dist/analytics-e2e.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +4 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/current-release.js +5 -5
  10. package/dist/current-release.js.map +1 -1
  11. package/dist/estate.d.ts +28 -0
  12. package/dist/estate.d.ts.map +1 -1
  13. package/dist/estate.js +35 -12
  14. package/dist/estate.js.map +1 -1
  15. package/dist/project.d.ts +20 -0
  16. package/dist/project.d.ts.map +1 -1
  17. package/dist/project.js +92 -2
  18. package/dist/project.js.map +1 -1
  19. package/dist/ready-cli.d.ts.map +1 -1
  20. package/dist/ready-cli.js +8 -3
  21. package/dist/ready-cli.js.map +1 -1
  22. package/dist/rule-registry.d.ts +5 -0
  23. package/dist/rule-registry.d.ts.map +1 -1
  24. package/dist/rule-registry.js +1 -0
  25. package/dist/rule-registry.js.map +1 -1
  26. package/dist/site-system/checks.d.ts +26 -1
  27. package/dist/site-system/checks.d.ts.map +1 -1
  28. package/dist/site-system/checks.js +3 -0
  29. package/dist/site-system/checks.js.map +1 -1
  30. package/dist/site-system/report.d.ts.map +1 -1
  31. package/dist/site-system/report.js +19 -5
  32. package/dist/site-system/report.js.map +1 -1
  33. package/dist/site-system/rule-modules.d.ts.map +1 -1
  34. package/dist/site-system/rule-modules.js +5 -0
  35. package/dist/site-system/rule-modules.js.map +1 -1
  36. package/dist/site-system/rules/delivery/h13-inbox.d.ts +23 -0
  37. package/dist/site-system/rules/delivery/h13-inbox.d.ts.map +1 -0
  38. package/dist/site-system/rules/delivery/h13-inbox.js +208 -0
  39. package/dist/site-system/rules/delivery/h13-inbox.js.map +1 -0
  40. package/dist/site-system/rules/delivery/index.d.ts +12 -0
  41. package/dist/site-system/rules/delivery/index.d.ts.map +1 -0
  42. package/dist/site-system/rules/delivery/index.js +7 -0
  43. package/dist/site-system/rules/delivery/index.js.map +1 -0
  44. package/dist/site-system/rules/full/e1-pattern.d.ts +4 -0
  45. package/dist/site-system/rules/full/e1-pattern.d.ts.map +1 -0
  46. package/dist/site-system/rules/full/e1-pattern.js +47 -0
  47. package/dist/site-system/rules/full/e1-pattern.js.map +1 -0
  48. package/dist/site-system/rules/full/estate.d.ts +16 -1
  49. package/dist/site-system/rules/full/estate.d.ts.map +1 -1
  50. package/dist/site-system/rules/full/estate.js +23 -4
  51. package/dist/site-system/rules/full/estate.js.map +1 -1
  52. package/dist/site-system/rules/full/geography.d.ts +8 -2
  53. package/dist/site-system/rules/full/geography.d.ts.map +1 -1
  54. package/dist/site-system/rules/full/geography.js +6 -2
  55. package/dist/site-system/rules/full/geography.js.map +1 -1
  56. package/dist/site-system/rules/full/index.d.ts +2 -2
  57. package/dist/site-system/rules/full/index.d.ts.map +1 -1
  58. package/dist/site-system/rules/full/index.js +15 -4
  59. package/dist/site-system/rules/full/index.js.map +1 -1
  60. package/dist/site-system/rules/full/k12-slots.d.ts +4 -0
  61. package/dist/site-system/rules/full/k12-slots.d.ts.map +1 -0
  62. package/dist/site-system/rules/full/k12-slots.js +40 -0
  63. package/dist/site-system/rules/full/k12-slots.js.map +1 -0
  64. package/dist/site-system/rules/full/loc-index.d.ts +5 -0
  65. package/dist/site-system/rules/full/loc-index.d.ts.map +1 -1
  66. package/dist/site-system/rules/full/loc-index.js +43 -2
  67. package/dist/site-system/rules/full/loc-index.js.map +1 -1
  68. package/dist/site-system/rules/full/strategy.d.ts +4 -0
  69. package/dist/site-system/rules/full/strategy.d.ts.map +1 -1
  70. package/dist/site-system/rules/full/strategy.js +19 -3
  71. package/dist/site-system/rules/full/strategy.js.map +1 -1
  72. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +2 -2
  73. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
  74. package/dist/site-system/rules/sections/k2-skin-coverage.js +6 -2
  75. package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
  76. package/dist/site-system/runners.js +1 -1
  77. package/dist/site-system/runners.js.map +1 -1
  78. package/package.json +2 -2
@@ -5,7 +5,7 @@ import { type FullGeography } from "./geography.js";
5
5
  export { FULL_LISTED_FAILURES, type FullContext } from "./common.js";
6
6
  export { headingPhrase } from "./e10-phrases.js";
7
7
  export { readEstate, type Estate, type EstatePage, type LocationCopy, type ManifestData } from "./estate.js";
8
- export { defineFullGeography, loadFullGeography, placeOfPath, type FullGeography, type FullGeographyInput } from "./geography.js";
8
+ export { defineFullGeography, loadFullGeography, placeOfPath, type FullGeography, type FullGeographyInput, type PathAliases } from "./geography.js";
9
9
  export { K12_FAMILIES, isDataBackedReason } from "./k12-manifests.js";
10
10
  export { L3_MAX_LIST, L3_MIN_LIST, L3_TOLERANCE_KM } from "./l3-neighbours.js";
11
11
  export { L4_MIN_ROTATION_PAGES, L4_MIN_TEMPLATE_WORDS } from "./l4-local-copy.js";
@@ -15,7 +15,7 @@ export { sydneyDay } from "./loc-index.js";
15
15
  export { W3_FAMILIES, W3_MIN_REVALIDATE_SECONDS, refreshInterval } from "./w3-refresh.js";
16
16
  export { FULL_STRATEGY_FILE, WAVE_MAX_GAP_DAYS, WAVE_MIN_GAP_DAYS, matchesWaveRoute, normalisePhrase, readFullStrategy, waveScheduleProblems } from "./strategy.js";
17
17
  /** The full-tier checks this module runs, in the order their findings are returned. */
18
- export declare const FULL_TIER_CHECK_IDS: readonly ["K12", "E5", "E10", "L3", "L4", "L6", "L7", "L8", "L9", "L10", "S4", "LOC-INDEX", "W3"];
18
+ export declare const FULL_TIER_CHECK_IDS: readonly ["K12", "K12-slots", "E1-pattern", "E5", "E10", "L3", "L4", "L6", "L7", "L8", "L9", "L10", "S4", "LOC-INDEX", "W3"];
19
19
  export interface FullTierRuleOptions {
20
20
  /** The day waves are judged on. Defaults to now. */
21
21
  readonly asOf?: Date;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAYvE,OAAO,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAClI,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEpK,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,mGAA2I,CAAC;AAE5K,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1C,uGAAuG;IACvG,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;CACrD;AAwCD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,uBAAuB,EAC9B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAgDvC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,EAAqB,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAC;AAazF,OAAO,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACpJ,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEpK,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,8HAAsK,CAAC;AAEvM,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1C,uGAAuG;IACvG,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;CACrD;AA8CD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,uBAAuB,EAC9B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAoDvC"}
@@ -2,16 +2,18 @@
2
2
  * The full-tier rules (release 3.12.0): page families, location estates, section manifests, links and the local data
3
3
  * contract. They read the production build's route catalogue and pages (`.next`), the project's strategy record and
4
4
  * source, and the site's installed site-core (geography and section orders), and return findings for the ready report:
5
- * K12 (families), E5, E10, L3, L4, L6, L7, L8, L9, L10 (location), S4, the location indexing guard and, from 3.13.0,
5
+ * K12 (families), K12-slots and E1-pattern (3.14.0), E5, E10, L3, L4, L6, L7, L8, L9, L10 (location), S4, the location indexing guard and, from 3.13.0,
6
6
  * W3 (estate refresh). The runner has the signature the ready gate registers.
7
7
  */
8
8
  import { join } from "node:path";
9
9
  import { readSiteSystemProjectConfig } from "../../config.js";
10
10
  import {} from "./common.js";
11
+ import { checkE1Pattern } from "./e1-pattern.js";
11
12
  import { checkE10 } from "./e10-phrases.js";
12
13
  import { readEstate } from "./estate.js";
13
14
  import { loadFullGeography } from "./geography.js";
14
15
  import { checkK12 } from "./k12-manifests.js";
16
+ import { checkK12Slots } from "./k12-slots.js";
15
17
  import { checkL3 } from "./l3-neighbours.js";
16
18
  import { checkL4 } from "./l4-local-copy.js";
17
19
  import { checkL9 } from "./l9-data-contract.js";
@@ -34,7 +36,7 @@ export { sydneyDay } from "./loc-index.js";
34
36
  export { W3_FAMILIES, W3_MIN_REVALIDATE_SECONDS, refreshInterval } from "./w3-refresh.js";
35
37
  export { FULL_STRATEGY_FILE, WAVE_MAX_GAP_DAYS, WAVE_MIN_GAP_DAYS, matchesWaveRoute, normalisePhrase, readFullStrategy, waveScheduleProblems } from "./strategy.js";
36
38
  /** The full-tier checks this module runs, in the order their findings are returned. */
37
- export const FULL_TIER_CHECK_IDS = ["K12", "E5", "E10", "L3", "L4", "L6", "L7", "L8", "L9", "L10", "S4", "LOC-INDEX", "W3"];
39
+ export const FULL_TIER_CHECK_IDS = ["K12", "K12-slots", "E1-pattern", "E5", "E10", "L3", "L4", "L6", "L7", "L8", "L9", "L10", "S4", "LOC-INDEX", "W3"];
38
40
  // Variable specifiers, so the package compiles without site-core; they resolve to the site's installed copy.
39
41
  const SECTION_ORDER_MODULE = "@brandlab-digital/site-core/section-order";
40
42
  async function loadDefaultOrder() {
@@ -72,6 +74,11 @@ function projectSettings(projectRoot) {
72
74
  return { tier: null, service: undefined, refreshExceptions: [] };
73
75
  }
74
76
  }
77
+ /** The estate plan's public slugs as path aliases: public slug → registry slug. */
78
+ function pathAliases(plan) {
79
+ const flip = (renames) => new Map(Object.entries(renames ?? {}).map(([registry, slug]) => [slug, registry]));
80
+ return { regions: flip(plan.slugs?.regions), suburbs: flip(plan.slugs?.suburbs) };
81
+ }
75
82
  /**
76
83
  * Runs the full-tier rules over the built estate at `projectRoot`. A site that declares a lower tier (or none) gets no
77
84
  * findings: the report shows the full checks as not run, which never blocks below the full tier.
@@ -95,18 +102,20 @@ export async function runFullTierRules(input, options = {}) {
95
102
  geographyProblem = `site-core's geography could not be imported: ${error instanceof Error ? error.message : String(error)}`;
96
103
  }
97
104
  }
105
+ const today = sydneyDay(options.asOf ?? new Date());
106
+ const strategy = readFullStrategy(input.projectRoot, today);
98
107
  const estate = readEstate({
99
108
  pages: input.pages,
100
109
  buildDir: options.buildDir ?? input.buildDir ?? join(input.projectRoot, ".next"),
101
110
  geography,
102
111
  ...(settings.service ? { seeds: { [settings.service]: "service" } } : {}),
112
+ ...(strategy.estate?.slugs ? { aliases: pathAliases(strategy.estate) } : {}),
103
113
  });
104
- const today = sydneyDay(options.asOf ?? new Date());
105
114
  const context = {
106
115
  estate,
107
116
  geography,
108
117
  geographyProblem,
109
- strategy: readFullStrategy(input.projectRoot, today),
118
+ strategy,
110
119
  projectRoot: input.projectRoot,
111
120
  refreshExceptions: settings.refreshExceptions,
112
121
  today,
@@ -115,6 +124,8 @@ export async function runFullTierRules(input, options = {}) {
115
124
  const inbound = inboundLinks(estate);
116
125
  const findings = [
117
126
  ...checkK12(context),
127
+ ...checkK12Slots(context),
128
+ ...checkE1Pattern(context),
118
129
  ...checkE5(context, inbound),
119
130
  ...checkE10(context),
120
131
  ...checkL3(context),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The full-tier rules (release 3.12.0): page families, location estates, section manifests, links and the local data\n * contract. They read the production build's route catalogue and pages (`.next`), the project's strategy record and\n * source, and the site's installed site-core (geography and section orders), and return findings for the ready report:\n * K12 (families), E5, E10, L3, L4, L6, L7, L8, L9, L10 (location), S4, the location indexing guard and, from 3.13.0,\n * W3 (estate refresh). The runner has the signature the ready gate registers.\n */\nimport { join } from \"node:path\";\nimport { readSiteSystemProjectConfig, type SiteSystemRefreshException } from \"../../config.js\";\nimport type { SiteSystemCheckId, SiteSystemFinding } from \"../../checks.js\";\nimport type { SiteSystemRenderedInput } from \"../../runners.js\";\nimport { type FullContext } from \"./common.js\";\nimport { checkE10 } from \"./e10-phrases.js\";\nimport { readEstate } from \"./estate.js\";\nimport { loadFullGeography, type FullGeography } from \"./geography.js\";\nimport { checkK12 } from \"./k12-manifests.js\";\nimport { checkL3 } from \"./l3-neighbours.js\";\nimport { checkL4 } from \"./l4-local-copy.js\";\nimport { checkL9 } from \"./l9-data-contract.js\";\nimport { checkL10 } from \"./l10-location-copy.js\";\nimport { checkE5, checkL6, checkL7, checkL8, inboundLinks } from \"./links.js\";\nimport { checkLocationIndexing, sydneyDay } from \"./loc-index.js\";\nimport { checkS4 } from \"./s4-route-files.js\";\nimport { readFullStrategy } from \"./strategy.js\";\nimport { checkW3 } from \"./w3-refresh.js\";\n\nexport { FULL_LISTED_FAILURES, type FullContext } from \"./common.js\";\nexport { headingPhrase } from \"./e10-phrases.js\";\nexport { readEstate, type Estate, type EstatePage, type LocationCopy, type ManifestData } from \"./estate.js\";\nexport { defineFullGeography, loadFullGeography, placeOfPath, type FullGeography, type FullGeographyInput } from \"./geography.js\";\nexport { K12_FAMILIES, isDataBackedReason } from \"./k12-manifests.js\";\nexport { L3_MAX_LIST, L3_MIN_LIST, L3_TOLERANCE_KM } from \"./l3-neighbours.js\";\nexport { L4_MIN_ROTATION_PAGES, L4_MIN_TEMPLATE_WORDS } from \"./l4-local-copy.js\";\nexport { yearStamp } from \"./l10-location-copy.js\";\nexport { L6_MIN_INBOUND, L6_MIN_PAGE_TYPES, L7_MAX_DEPTH, clickDepths } from \"./links.js\";\nexport { sydneyDay } from \"./loc-index.js\";\nexport { W3_FAMILIES, W3_MIN_REVALIDATE_SECONDS, refreshInterval } from \"./w3-refresh.js\";\nexport { FULL_STRATEGY_FILE, WAVE_MAX_GAP_DAYS, WAVE_MIN_GAP_DAYS, matchesWaveRoute, normalisePhrase, readFullStrategy, waveScheduleProblems } from \"./strategy.js\";\n\n/** The full-tier checks this module runs, in the order their findings are returned. */\nexport const FULL_TIER_CHECK_IDS = [\"K12\", \"E5\", \"E10\", \"L3\", \"L4\", \"L6\", \"L7\", \"L8\", \"L9\", \"L10\", \"S4\", \"LOC-INDEX\", \"W3\"] as const satisfies readonly SiteSystemCheckId[];\n\nexport interface FullTierRuleOptions {\n /** The day waves are judged on. Defaults to now. */\n readonly asOf?: Date;\n /** The build directory; defaults to `input.buildDir`, then `<projectRoot>/.next`. */\n readonly buildDir?: string;\n /** Places for a test; a real run loads the site's site-core geography. Null runs without places. */\n readonly geography?: FullGeography | null;\n /** Canonical section ids per family for a test; a real run loads site-core's `defaultSectionOrder`. */\n readonly defaultOrder?: FullContext[\"defaultOrder\"];\n}\n\n// Variable specifiers, so the package compiles without site-core; they resolve to the site's installed copy.\nconst SECTION_ORDER_MODULE: string = \"@brandlab-digital/site-core/section-order\";\n\nasync function loadDefaultOrder(): Promise<FullContext[\"defaultOrder\"]> {\n try {\n const module = (await import(SECTION_ORDER_MODULE)) as {\n defaultSectionOrder?: (family: string, trade?: string) => { cap: number; entries: readonly { id: string; requirement: string }[] };\n };\n const defaults = module.defaultSectionOrder;\n if (typeof defaults !== \"function\") return \"the installed site-core has no defaultSectionOrder (3.11.0 or later is needed)\";\n return (family, trade) => {\n try {\n const order = defaults(family, trade);\n return { cap: order.cap, entries: order.entries.map((entry) => ({ id: entry.id, requirement: entry.requirement })) };\n } catch {\n return undefined;\n }\n };\n } catch (error) {\n return `site-core's section orders could not be imported: ${error instanceof Error ? error.message : String(error)}`;\n }\n}\n\n/** The declared tier, the representative service route and the W3 exceptions; null tier when the config cannot be read in full. */\nfunction projectSettings(projectRoot: string): { tier: string | null; service: string | undefined; refreshExceptions: readonly SiteSystemRefreshException[] } {\n try {\n const config = readSiteSystemProjectConfig(projectRoot);\n // A config the gate cannot read in full could hide a full tier, so the checks run.\n return {\n tier: config.complete ? (config.siteSystem?.tier ?? \"none\") : null,\n service: config.representativeRoutes[\"service\"],\n refreshExceptions: config.siteSystem?.refreshExceptions ?? [],\n };\n } catch {\n return { tier: null, service: undefined, refreshExceptions: [] };\n }\n}\n\n/**\n * Runs the full-tier rules over the built estate at `projectRoot`. A site that declares a lower tier (or none) gets no\n * findings: the report shows the full checks as not run, which never blocks below the full tier.\n */\nexport async function runFullTierRules(\n input: SiteSystemRenderedInput,\n options: FullTierRuleOptions = {},\n): Promise<readonly SiteSystemFinding[]> {\n const settings = projectSettings(input.projectRoot);\n if (settings.tier !== null && settings.tier !== \"full\") return [];\n let geography: FullGeography | null = null;\n let geographyProblem = \"\";\n if (options.geography !== undefined) {\n geography = options.geography;\n if (!geography) geographyProblem = \"no geography was supplied\";\n } else {\n try {\n geography = await loadFullGeography();\n } catch (error) {\n geographyProblem = `site-core's geography could not be imported: ${error instanceof Error ? error.message : String(error)}`;\n }\n }\n const estate = readEstate({\n pages: input.pages,\n buildDir: options.buildDir ?? input.buildDir ?? join(input.projectRoot, \".next\"),\n geography,\n ...(settings.service ? { seeds: { [settings.service]: \"service\" } } : {}),\n });\n const today = sydneyDay(options.asOf ?? new Date());\n const context: FullContext = {\n estate,\n geography,\n geographyProblem,\n strategy: readFullStrategy(input.projectRoot, today),\n projectRoot: input.projectRoot,\n refreshExceptions: settings.refreshExceptions,\n today,\n defaultOrder: options.defaultOrder ?? (await loadDefaultOrder()),\n };\n const inbound = inboundLinks(estate);\n const findings = [\n ...checkK12(context),\n ...checkE5(context, inbound),\n ...checkE10(context),\n ...checkL3(context),\n ...checkL4(context),\n ...checkL6(context, inbound),\n ...checkL7(context),\n ...checkL8(context),\n ...checkL9(context),\n ...checkL10(context),\n ];\n // The copy is only for L3, L4 and L10; let it go before the source scan.\n for (const page of estate.pages) page.copy = undefined;\n return [...findings, ...checkS4(context), ...checkLocationIndexing(context), ...checkW3(context)];\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The full-tier rules (release 3.12.0): page families, location estates, section manifests, links and the local data\n * contract. They read the production build's route catalogue and pages (`.next`), the project's strategy record and\n * source, and the site's installed site-core (geography and section orders), and return findings for the ready report:\n * K12 (families), K12-slots and E1-pattern (3.14.0), E5, E10, L3, L4, L6, L7, L8, L9, L10 (location), S4, the location indexing guard and, from 3.13.0,\n * W3 (estate refresh). The runner has the signature the ready gate registers.\n */\nimport { join } from \"node:path\";\nimport { readSiteSystemProjectConfig, type SiteSystemRefreshException } from \"../../config.js\";\nimport type { SiteSystemCheckId, SiteSystemFinding } from \"../../checks.js\";\nimport type { SiteSystemRenderedInput } from \"../../runners.js\";\nimport { type FullContext } from \"./common.js\";\nimport { checkE1Pattern } from \"./e1-pattern.js\";\nimport { checkE10 } from \"./e10-phrases.js\";\nimport { readEstate } from \"./estate.js\";\nimport { loadFullGeography, type FullGeography, type PathAliases } from \"./geography.js\";\nimport { checkK12 } from \"./k12-manifests.js\";\nimport { checkK12Slots } from \"./k12-slots.js\";\nimport { checkL3 } from \"./l3-neighbours.js\";\nimport { checkL4 } from \"./l4-local-copy.js\";\nimport { checkL9 } from \"./l9-data-contract.js\";\nimport { checkL10 } from \"./l10-location-copy.js\";\nimport { checkE5, checkL6, checkL7, checkL8, inboundLinks } from \"./links.js\";\nimport { checkLocationIndexing, sydneyDay } from \"./loc-index.js\";\nimport { checkS4 } from \"./s4-route-files.js\";\nimport { readFullStrategy } from \"./strategy.js\";\nimport { checkW3 } from \"./w3-refresh.js\";\n\nexport { FULL_LISTED_FAILURES, type FullContext } from \"./common.js\";\nexport { headingPhrase } from \"./e10-phrases.js\";\nexport { readEstate, type Estate, type EstatePage, type LocationCopy, type ManifestData } from \"./estate.js\";\nexport { defineFullGeography, loadFullGeography, placeOfPath, type FullGeography, type FullGeographyInput, type PathAliases } from \"./geography.js\";\nexport { K12_FAMILIES, isDataBackedReason } from \"./k12-manifests.js\";\nexport { L3_MAX_LIST, L3_MIN_LIST, L3_TOLERANCE_KM } from \"./l3-neighbours.js\";\nexport { L4_MIN_ROTATION_PAGES, L4_MIN_TEMPLATE_WORDS } from \"./l4-local-copy.js\";\nexport { yearStamp } from \"./l10-location-copy.js\";\nexport { L6_MIN_INBOUND, L6_MIN_PAGE_TYPES, L7_MAX_DEPTH, clickDepths } from \"./links.js\";\nexport { sydneyDay } from \"./loc-index.js\";\nexport { W3_FAMILIES, W3_MIN_REVALIDATE_SECONDS, refreshInterval } from \"./w3-refresh.js\";\nexport { FULL_STRATEGY_FILE, WAVE_MAX_GAP_DAYS, WAVE_MIN_GAP_DAYS, matchesWaveRoute, normalisePhrase, readFullStrategy, waveScheduleProblems } from \"./strategy.js\";\n\n/** The full-tier checks this module runs, in the order their findings are returned. */\nexport const FULL_TIER_CHECK_IDS = [\"K12\", \"K12-slots\", \"E1-pattern\", \"E5\", \"E10\", \"L3\", \"L4\", \"L6\", \"L7\", \"L8\", \"L9\", \"L10\", \"S4\", \"LOC-INDEX\", \"W3\"] as const satisfies readonly SiteSystemCheckId[];\n\nexport interface FullTierRuleOptions {\n /** The day waves are judged on. Defaults to now. */\n readonly asOf?: Date;\n /** The build directory; defaults to `input.buildDir`, then `<projectRoot>/.next`. */\n readonly buildDir?: string;\n /** Places for a test; a real run loads the site's site-core geography. Null runs without places. */\n readonly geography?: FullGeography | null;\n /** Canonical section ids per family for a test; a real run loads site-core's `defaultSectionOrder`. */\n readonly defaultOrder?: FullContext[\"defaultOrder\"];\n}\n\n// Variable specifiers, so the package compiles without site-core; they resolve to the site's installed copy.\nconst SECTION_ORDER_MODULE: string = \"@brandlab-digital/site-core/section-order\";\n\nasync function loadDefaultOrder(): Promise<FullContext[\"defaultOrder\"]> {\n try {\n const module = (await import(SECTION_ORDER_MODULE)) as {\n defaultSectionOrder?: (family: string, trade?: string) => { cap: number; entries: readonly { id: string; requirement: string }[] };\n };\n const defaults = module.defaultSectionOrder;\n if (typeof defaults !== \"function\") return \"the installed site-core has no defaultSectionOrder (3.11.0 or later is needed)\";\n return (family, trade) => {\n try {\n const order = defaults(family, trade);\n return { cap: order.cap, entries: order.entries.map((entry) => ({ id: entry.id, requirement: entry.requirement })) };\n } catch {\n return undefined;\n }\n };\n } catch (error) {\n return `site-core's section orders could not be imported: ${error instanceof Error ? error.message : String(error)}`;\n }\n}\n\n/** The declared tier, the representative service route and the W3 exceptions; null tier when the config cannot be read in full. */\nfunction projectSettings(projectRoot: string): { tier: string | null; service: string | undefined; refreshExceptions: readonly SiteSystemRefreshException[] } {\n try {\n const config = readSiteSystemProjectConfig(projectRoot);\n // A config the gate cannot read in full could hide a full tier, so the checks run.\n return {\n tier: config.complete ? (config.siteSystem?.tier ?? \"none\") : null,\n service: config.representativeRoutes[\"service\"],\n refreshExceptions: config.siteSystem?.refreshExceptions ?? [],\n };\n } catch {\n return { tier: null, service: undefined, refreshExceptions: [] };\n }\n}\n\n/** The estate plan's public slugs as path aliases: public slug → registry slug. */\nfunction pathAliases(plan: NonNullable<FullContext[\"strategy\"][\"estate\"]>): PathAliases {\n const flip = (renames: Readonly<Record<string, string>> | undefined) => new Map(Object.entries(renames ?? {}).map(([registry, slug]) => [slug, registry]));\n return { regions: flip(plan.slugs?.regions), suburbs: flip(plan.slugs?.suburbs) };\n}\n\n/**\n * Runs the full-tier rules over the built estate at `projectRoot`. A site that declares a lower tier (or none) gets no\n * findings: the report shows the full checks as not run, which never blocks below the full tier.\n */\nexport async function runFullTierRules(\n input: SiteSystemRenderedInput,\n options: FullTierRuleOptions = {},\n): Promise<readonly SiteSystemFinding[]> {\n const settings = projectSettings(input.projectRoot);\n if (settings.tier !== null && settings.tier !== \"full\") return [];\n let geography: FullGeography | null = null;\n let geographyProblem = \"\";\n if (options.geography !== undefined) {\n geography = options.geography;\n if (!geography) geographyProblem = \"no geography was supplied\";\n } else {\n try {\n geography = await loadFullGeography();\n } catch (error) {\n geographyProblem = `site-core's geography could not be imported: ${error instanceof Error ? error.message : String(error)}`;\n }\n }\n const today = sydneyDay(options.asOf ?? new Date());\n const strategy = readFullStrategy(input.projectRoot, today);\n const estate = readEstate({\n pages: input.pages,\n buildDir: options.buildDir ?? input.buildDir ?? join(input.projectRoot, \".next\"),\n geography,\n ...(settings.service ? { seeds: { [settings.service]: \"service\" } } : {}),\n ...(strategy.estate?.slugs ? { aliases: pathAliases(strategy.estate) } : {}),\n });\n const context: FullContext = {\n estate,\n geography,\n geographyProblem,\n strategy,\n projectRoot: input.projectRoot,\n refreshExceptions: settings.refreshExceptions,\n today,\n defaultOrder: options.defaultOrder ?? (await loadDefaultOrder()),\n };\n const inbound = inboundLinks(estate);\n const findings = [\n ...checkK12(context),\n ...checkK12Slots(context),\n ...checkE1Pattern(context),\n ...checkE5(context, inbound),\n ...checkE10(context),\n ...checkL3(context),\n ...checkL4(context),\n ...checkL6(context, inbound),\n ...checkL7(context),\n ...checkL8(context),\n ...checkL9(context),\n ...checkL10(context),\n ];\n // The copy is only for L3, L4 and L10; let it go before the source scan.\n for (const page of estate.pages) page.copy = undefined;\n return [...findings, ...checkS4(context), ...checkLocationIndexing(context), ...checkW3(context)];\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { SiteSystemFinding } from "../../checks.js";
2
+ import { type FullContext } from "./common.js";
3
+ export declare function checkK12Slots(context: FullContext): SiteSystemFinding[];
4
+ //# sourceMappingURL=k12-slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k12-slots.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-slots.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAG9F,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CA8BvE"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * K12-slots (3.14.0): the lines a trade requires inside a section (site-core `SECTION_TRADE_PROFILES`, matrix part
3
+ * B), such as the tree trade's council permit note in `LocalFacts` on suburb and service-in-suburb pages. The trade is
4
+ * the one `defineSectionOrder` names in strategy.ts. Wherever the section renders, it carries the slot
5
+ * (`data-bl-slot="permit"`), or lists it in `data-bl-slot-skip` with a reason drawn from its data
6
+ * (`permit:no-current-council-record`), which passes with a warning so the record gets re-checked. A section that
7
+ * does not render is K12's business.
8
+ */
9
+ import { requiredSectionSlots } from "@brandlab-digital/site-core/section-order";
10
+ import { fail, notApplicable, outcome, plural, warning } from "./common.js";
11
+ import { isDataBackedReason } from "./k12-manifests.js";
12
+ export function checkK12Slots(context) {
13
+ const trade = context.strategy.trade;
14
+ const failures = [];
15
+ let checked = 0;
16
+ for (const page of context.estate.pages) {
17
+ if (!page.family)
18
+ continue;
19
+ const required = requiredSectionSlots(page.family, trade);
20
+ for (const { section, slot } of required) {
21
+ if (!page.sections.includes(section))
22
+ continue;
23
+ checked += 1;
24
+ const found = page.slots?.get(section);
25
+ if (found?.present.includes(slot))
26
+ continue;
27
+ const skip = found?.skipped.find((entry) => entry.slot === slot);
28
+ if (skip && isDataBackedReason(skip.reason)) {
29
+ failures.push(warning("K12-slots", page.path, `${page.path} leaves the ${slot} line out of its "${section}" section (${skip.reason}); bring the record up to date so it shows again.`));
30
+ continue;
31
+ }
32
+ failures.push(fail("K12-slots", page.path, `${page.path} renders "${section}" without the ${slot} line the ${trade} trade requires on ${page.family} pages, and gives no data-backed reason (${skip ? skip.reason : "none"}).`));
33
+ }
34
+ }
35
+ if (checked === 0) {
36
+ return [notApplicable("K12-slots", trade ? `The ${trade} trade requires no line inside the sections these pages render.` : "strategy.ts names no trade in defineSectionOrder, so no trade lines are required.")];
37
+ }
38
+ return outcome("K12-slots", failures, `${plural(checked, "required trade line")} ${checked === 1 ? "is" : "are"} in place.`);
39
+ }
40
+ //# sourceMappingURL=k12-slots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k12-slots.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,oBAAoB,EAA+C,MAAM,2CAA2C,CAAC;AAE9H,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAoB,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,UAAU,aAAa,CAAC,OAAoB;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAkC,CAAC;IAClE,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,SAAS;QAC3B,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,MAA4B,EAAE,KAAK,CAAC,CAAC;QAChF,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC/C,OAAO,IAAI,CAAC,CAAC;YACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACvC,IAAI,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC5C,MAAM,IAAI,GAAG,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YACjE,IAAI,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,eAAe,IAAI,qBAAqB,OAAO,cAAc,IAAI,CAAC,MAAM,mDAAmD,CAAC,CAAC,CAAC;gBACxL,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,IAAI,CACX,IAAI,CACF,WAAW,EACX,IAAI,CAAC,IAAI,EACT,GAAG,IAAI,CAAC,IAAI,aAAa,OAAO,iBAAiB,IAAI,aAAa,KAAK,sBAAsB,IAAI,CAAC,MAAM,4CAA4C,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CACpL,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,iEAAiE,CAAC,CAAC,CAAC,mFAAmF,CAAC,CAAC,CAAC;IACnN,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,qBAAqB,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC;AAC/H,CAAC","sourcesContent":["/**\n * K12-slots (3.14.0): the lines a trade requires inside a section (site-core `SECTION_TRADE_PROFILES`, matrix part\n * B), such as the tree trade's council permit note in `LocalFacts` on suburb and service-in-suburb pages. The trade is\n * the one `defineSectionOrder` names in strategy.ts. Wherever the section renders, it carries the slot\n * (`data-bl-slot=\"permit\"`), or lists it in `data-bl-slot-skip` with a reason drawn from its data\n * (`permit:no-current-council-record`), which passes with a warning so the record gets re-checked. A section that\n * does not render is K12's business.\n */\nimport { requiredSectionSlots, type BrandlabPageFamily, type BrandlabTrade } from \"@brandlab-digital/site-core/section-order\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, outcome, plural, warning, type FullContext } from \"./common.js\";\nimport { isDataBackedReason } from \"./k12-manifests.js\";\n\nexport function checkK12Slots(context: FullContext): SiteSystemFinding[] {\n const trade = context.strategy.trade as BrandlabTrade | undefined;\n const failures: SiteSystemFinding[] = [];\n let checked = 0;\n for (const page of context.estate.pages) {\n if (!page.family) continue;\n const required = requiredSectionSlots(page.family as BrandlabPageFamily, trade);\n for (const { section, slot } of required) {\n if (!page.sections.includes(section)) continue;\n checked += 1;\n const found = page.slots?.get(section);\n if (found?.present.includes(slot)) continue;\n const skip = found?.skipped.find((entry) => entry.slot === slot);\n if (skip && isDataBackedReason(skip.reason)) {\n failures.push(warning(\"K12-slots\", page.path, `${page.path} leaves the ${slot} line out of its \"${section}\" section (${skip.reason}); bring the record up to date so it shows again.`));\n continue;\n }\n failures.push(\n fail(\n \"K12-slots\",\n page.path,\n `${page.path} renders \"${section}\" without the ${slot} line the ${trade} trade requires on ${page.family} pages, and gives no data-backed reason (${skip ? skip.reason : \"none\"}).`,\n ),\n );\n }\n }\n if (checked === 0) {\n return [notApplicable(\"K12-slots\", trade ? `The ${trade} trade requires no line inside the sections these pages render.` : \"strategy.ts names no trade in defineSectionOrder, so no trade lines are required.\")];\n }\n return outcome(\"K12-slots\", failures, `${plural(checked, \"required trade line\")} ${checked === 1 ? \"is\" : \"are\"} in place.`);\n}\n"]}
@@ -5,6 +5,11 @@
5
5
  * sitemap. A wave that is due has every page it lists built, indexable and in the sitemap. The schedule itself goes
6
6
  * out in date order, 14 to 28 days apart, so it never holds a page open-ended. Nothing here suggests cutting, capping
7
7
  * or noindexing a page.
8
+ *
9
+ * From 3.14.0 an existing estate can record its plan (`estate` in strategy.ts): the pages indexed now, return waves
10
+ * (`kind: 'return'`: pages live now that come back to the index on the day, so they must be in the build already), the
11
+ * family paths and the public slugs. With a plan, every location page is indexed now or in a wave, a page indexed now
12
+ * never waits for a wave, and a region page that takes a suburb's slug sends that suburb to "{slug}-suburb".
8
13
  */
9
14
  import type { SiteSystemFinding } from "../../checks.js";
10
15
  import { type FullContext } from "./common.js";
@@ -1 +1 @@
1
- {"version":3,"file":"loc-index.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/loc-index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAoD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjG,wHAAwH;AACxH,eAAO,MAAM,kBAAkB,gFAAiF,CAAC;AAGjH,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CA4C/E"}
1
+ {"version":3,"file":"loc-index.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/loc-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAoD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjG,wHAAwH;AACxH,eAAO,MAAM,kBAAkB,gFAAiF,CAAC;AAGjH,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CAgF/E"}
@@ -23,15 +23,27 @@ export function checkLocationIndexing(context) {
23
23
  if (estate.sitemap === null && pages.length > 0) {
24
24
  failures.push(fail("LOC-INDEX", undefined, "The build holds no sitemap file, so whether every location page is listed could not be checked."));
25
25
  }
26
+ const plan = strategy.estate;
27
+ const indexedNow = (path) => plan?.indexedNow.some((route) => matchesWaveRoute(path, route)) ?? false;
26
28
  let live = 0;
27
29
  let scheduled = 0;
28
30
  for (const page of pages) {
29
31
  const wave = waveOf(page.path);
30
- if (wave && wave.releaseOn > today) {
32
+ if (plan && !wave && !indexedNow(page.path) && page.family !== "locations-hub" && page.family !== "council") {
33
+ failures.push(fail("LOC-INDEX", page.path, `${page.path} is a location page the estate plan does not account for; list it in estate.indexedNow or in a wave.`));
34
+ }
35
+ const now = indexedNow(page.path);
36
+ const waiting = wave !== undefined && wave.releaseOn > today;
37
+ if (waiting && !now) {
31
38
  scheduled += 1;
32
39
  continue;
33
40
  }
34
- const dated = wave ? `its wave was due on ${wave.releaseOn}` : "no upcoming wave in the recorded schedule dates it";
41
+ // A page indexed now is judged now, even when a wave's pattern also covers it; a wave naming a page the plan's
42
+ // pattern indexes now is a contradiction (the same path in both lists is reported with the plan's problems).
43
+ if (waiting && wave.routes.includes(page.path) && !plan?.indexedNow.includes(page.path)) {
44
+ failures.push(fail("LOC-INDEX", page.path, `${page.path} is indexed now in the estate plan, but the wave of ${wave.releaseOn} still lists it; a page indexed now never waits for a wave.`));
45
+ }
46
+ const dated = waiting ? "it is indexed now in the estate plan" : wave ? `its wave was due on ${wave.releaseOn}` : "no upcoming wave in the recorded schedule dates it";
35
47
  const problems = [];
36
48
  if (!page.indexable)
37
49
  problems.push("is marked noindex");
@@ -53,6 +65,35 @@ export function checkLocationIndexing(context) {
53
65
  }
54
66
  }
55
67
  }
68
+ // A return wave brings back pages that are live now, so its pages are in the build before the day.
69
+ for (const wave of waves) {
70
+ if (wave.kind !== "return" || wave.releaseOn <= today)
71
+ continue;
72
+ for (const route of wave.routes) {
73
+ const matched = route.includes("*") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);
74
+ if (!matched)
75
+ failures.push(fail("LOC-INDEX", route, `The return wave of ${wave.releaseOn} brings back ${route}, but the build has no ${route.includes("*") ? "page matching it" : "such page"}; a return wave lists pages that are live now.`));
76
+ }
77
+ }
78
+ if (plan) {
79
+ for (const route of plan.indexedNow) {
80
+ const matched = route.includes("*") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);
81
+ if (!matched)
82
+ failures.push(fail("LOC-INDEX", route, `estate.indexedNow lists ${route}, but the build has no ${route.includes("*") ? "page matching it" : "such page"}.`));
83
+ }
84
+ const parent = (template) => template?.slice(0, template.lastIndexOf("/"));
85
+ const shared = plan.paths?.region !== undefined && parent(plan.paths.region) === parent(plan.paths.suburb);
86
+ for (const page of shared ? pages : []) {
87
+ if (page.place?.kind !== "region")
88
+ continue;
89
+ const slug = page.path.slice(page.path.lastIndexOf("/") + 1);
90
+ if (!context.geography?.suburbs.has(slug) || page.path !== `${parent(plan.paths?.region)}/${slug}`)
91
+ continue;
92
+ if (plan.slugs?.suburbs?.[slug] !== `${slug}-suburb`) {
93
+ failures.push(fail("LOC-INDEX", page.path, `The region page ${page.path} takes the slug of the suburb ${slug}; that suburb's pages take "${slug}-suburb" (estate.slugs.suburbs).`));
94
+ }
95
+ }
96
+ }
56
97
  const upcoming = scheduled > 0 ? `; ${plural(scheduled, "more page")} ${scheduled === 1 ? "is" : "are"} dated by upcoming waves` : "";
57
98
  return outcome("LOC-INDEX", failures, `All ${plural(live, "released location page")} are indexable and in the sitemap${upcoming}.`);
58
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loc-index.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/loc-index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AAEjG,wHAAwH;AACxH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,CAAU,CAAC;AACjH,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAErE,0CAA0C;AAC1C,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC5I,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAoB;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC5C,MAAM,QAAQ,GAAwB,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/H,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAE,kBAAwC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;IACnH,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,gGAAgG,CAAC,CAAC,CAAC;IAClK,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,QAAQ,EAAE,aAAa,CAAC,WAAW,EAAE,kCAAkC,CAAC,CAAC,CAAC;IACnI,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,iGAAiG,CAAC,CAAC,CAAC;IACjJ,CAAC;IAED,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,EAAE,CAAC;YACnC,SAAS,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,uBAAuB,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,oDAAoD,CAAC;QACpH,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACxD,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC7F,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,4BAA4B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,gDAAgD,CAAC,CAAC,CAAC;QAC5K,CAAC;;YAAM,IAAI,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK;YAAE,SAAS;QACrC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,eAAe,IAAI,CAAC,SAAS,aAAa,KAAK,0BAA0B,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;YAC9K,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,0BAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;IACtI,OAAO,OAAO,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC,IAAI,EAAE,wBAAwB,CAAC,oCAAoC,QAAQ,GAAG,CAAC,CAAC;AACtI,CAAC","sourcesContent":["/**\n * The location indexing guard (Site System v2 §4.3 item 3, §4.4 step 8; D4, 20 Oct 2026): every location page stays\n * indexed. A suburb, service-in-suburb or region page is indexable and listed in the sitemap, unless a wave in the\n * recorded schedule (`waves` in strategy.ts) dates it in the future: until that day it may be noindex or out of the\n * sitemap. A wave that is due has every page it lists built, indexable and in the sitemap. The schedule itself goes\n * out in date order, 14 to 28 days apart, so it never holds a page open-ended. Nothing here suggests cutting, capping\n * or noindexing a page.\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, needsBuild, notApplicable, outcome, plural, type FullContext } from \"./common.js\";\n\n/** The families the guard covers: the location families, council pages and the locations hub (v2 §4.1, §4.3 item 3). */\nexport const LOC_INDEX_FAMILIES = [\"suburb\", \"service-in-suburb\", \"region\", \"council\", \"locations-hub\"] as const;\nimport { FULL_STRATEGY_FILE, matchesWaveRoute } from \"./strategy.js\";\n\n/** Today's date in Sydney, YYYY-MM-DD. */\nexport function sydneyDay(date: Date): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Australia/Sydney\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n}\n\nexport function checkLocationIndexing(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"LOC-INDEX\", context.estate);\n if (skip) return [skip];\n const { estate, strategy, today } = context;\n const failures: SiteSystemFinding[] = strategy.problems.waves.map((problem) => fail(\"LOC-INDEX\", FULL_STRATEGY_FILE, problem));\n const waves = strategy.waves;\n const waveOf = (path: string) => waves.find((wave) => wave.routes.some((route) => matchesWaveRoute(path, route)));\n const pages = estate.pages.filter((page) => (LOC_INDEX_FAMILIES as readonly string[]).includes(page.family ?? \"\"));\n for (const missing of estate.missingHtml.filter((item) => item.location)) {\n failures.push(fail(\"LOC-INDEX\", missing.path, `${missing.path} is a prerendered location route with no HTML in the build, so it cannot be served or indexed.`));\n }\n if (pages.length === 0 && waves.length === 0) return [...failures, notApplicable(\"LOC-INDEX\", \"The build has no location pages.\")];\n if (estate.sitemap === null && pages.length > 0) {\n failures.push(fail(\"LOC-INDEX\", undefined, \"The build holds no sitemap file, so whether every location page is listed could not be checked.\"));\n }\n\n let live = 0;\n let scheduled = 0;\n for (const page of pages) {\n const wave = waveOf(page.path);\n if (wave && wave.releaseOn > today) {\n scheduled += 1;\n continue;\n }\n const dated = wave ? `its wave was due on ${wave.releaseOn}` : \"no upcoming wave in the recorded schedule dates it\";\n const problems: string[] = [];\n if (!page.indexable) problems.push(\"is marked noindex\");\n if (estate.sitemap && !estate.sitemap.has(page.path)) problems.push(\"is not in the sitemap\");\n if (problems.length > 0) {\n failures.push(fail(\"LOC-INDEX\", page.path, `${page.path} is a location page that ${problems.join(\" and \")}, and ${dated}; every location page is indexable and listed.`));\n } else live += 1;\n }\n\n for (const wave of waves) {\n if (wave.releaseOn > today) continue;\n for (const route of wave.routes) {\n const matched = route.includes(\"*\") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);\n if (!matched) {\n failures.push(fail(\"LOC-INDEX\", route, `The wave of ${wave.releaseOn} released ${route}, but the build has no ${route.includes(\"*\") ? \"page matching it\" : \"such page\"}.`));\n }\n }\n }\n const upcoming = scheduled > 0 ? `; ${plural(scheduled, \"more page\")} ${scheduled === 1 ? \"is\" : \"are\"} dated by upcoming waves` : \"\";\n return outcome(\"LOC-INDEX\", failures, `All ${plural(live, \"released location page\")} are indexable and in the sitemap${upcoming}.`);\n}\n"]}
1
+ {"version":3,"file":"loc-index.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/loc-index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The location indexing guard (Site System v2 §4.3 item 3, §4.4 step 8; D4, 20 Oct 2026): every location page stays\n * indexed. A suburb, service-in-suburb or region page is indexable and listed in the sitemap, unless a wave in the\n * recorded schedule (`waves` in strategy.ts) dates it in the future: until that day it may be noindex or out of the\n * sitemap. A wave that is due has every page it lists built, indexable and in the sitemap. The schedule itself goes\n * out in date order, 14 to 28 days apart, so it never holds a page open-ended. Nothing here suggests cutting, capping\n * or noindexing a page.\n *\n * From 3.14.0 an existing estate can record its plan (`estate` in strategy.ts): the pages indexed now, return waves\n * (`kind: 'return'`: pages live now that come back to the index on the day, so they must be in the build already), the\n * family paths and the public slugs. With a plan, every location page is indexed now or in a wave, a page indexed now\n * never waits for a wave, and a region page that takes a suburb's slug sends that suburb to \"{slug}-suburb\".\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, needsBuild, notApplicable, outcome, plural, type FullContext } from \"./common.js\";\n\n/** The families the guard covers: the location families, council pages and the locations hub (v2 §4.1, §4.3 item 3). */\nexport const LOC_INDEX_FAMILIES = [\"suburb\", \"service-in-suburb\", \"region\", \"council\", \"locations-hub\"] as const;\nimport { FULL_STRATEGY_FILE, matchesWaveRoute } from \"./strategy.js\";\n\n/** Today's date in Sydney, YYYY-MM-DD. */\nexport function sydneyDay(date: Date): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Australia/Sydney\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n}\n\nexport function checkLocationIndexing(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"LOC-INDEX\", context.estate);\n if (skip) return [skip];\n const { estate, strategy, today } = context;\n const failures: SiteSystemFinding[] = strategy.problems.waves.map((problem) => fail(\"LOC-INDEX\", FULL_STRATEGY_FILE, problem));\n const waves = strategy.waves;\n const waveOf = (path: string) => waves.find((wave) => wave.routes.some((route) => matchesWaveRoute(path, route)));\n const pages = estate.pages.filter((page) => (LOC_INDEX_FAMILIES as readonly string[]).includes(page.family ?? \"\"));\n for (const missing of estate.missingHtml.filter((item) => item.location)) {\n failures.push(fail(\"LOC-INDEX\", missing.path, `${missing.path} is a prerendered location route with no HTML in the build, so it cannot be served or indexed.`));\n }\n if (pages.length === 0 && waves.length === 0) return [...failures, notApplicable(\"LOC-INDEX\", \"The build has no location pages.\")];\n if (estate.sitemap === null && pages.length > 0) {\n failures.push(fail(\"LOC-INDEX\", undefined, \"The build holds no sitemap file, so whether every location page is listed could not be checked.\"));\n }\n\n const plan = strategy.estate;\n const indexedNow = (path: string) => plan?.indexedNow.some((route) => matchesWaveRoute(path, route)) ?? false;\n let live = 0;\n let scheduled = 0;\n for (const page of pages) {\n const wave = waveOf(page.path);\n if (plan && !wave && !indexedNow(page.path) && page.family !== \"locations-hub\" && page.family !== \"council\") {\n failures.push(fail(\"LOC-INDEX\", page.path, `${page.path} is a location page the estate plan does not account for; list it in estate.indexedNow or in a wave.`));\n }\n const now = indexedNow(page.path);\n const waiting = wave !== undefined && wave.releaseOn > today;\n if (waiting && !now) {\n scheduled += 1;\n continue;\n }\n // A page indexed now is judged now, even when a wave's pattern also covers it; a wave naming a page the plan's\n // pattern indexes now is a contradiction (the same path in both lists is reported with the plan's problems).\n if (waiting && wave.routes.includes(page.path) && !plan?.indexedNow.includes(page.path)) {\n failures.push(fail(\"LOC-INDEX\", page.path, `${page.path} is indexed now in the estate plan, but the wave of ${wave.releaseOn} still lists it; a page indexed now never waits for a wave.`));\n }\n const dated = waiting ? \"it is indexed now in the estate plan\" : wave ? `its wave was due on ${wave.releaseOn}` : \"no upcoming wave in the recorded schedule dates it\";\n const problems: string[] = [];\n if (!page.indexable) problems.push(\"is marked noindex\");\n if (estate.sitemap && !estate.sitemap.has(page.path)) problems.push(\"is not in the sitemap\");\n if (problems.length > 0) {\n failures.push(fail(\"LOC-INDEX\", page.path, `${page.path} is a location page that ${problems.join(\" and \")}, and ${dated}; every location page is indexable and listed.`));\n } else live += 1;\n }\n\n for (const wave of waves) {\n if (wave.releaseOn > today) continue;\n for (const route of wave.routes) {\n const matched = route.includes(\"*\") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);\n if (!matched) {\n failures.push(fail(\"LOC-INDEX\", route, `The wave of ${wave.releaseOn} released ${route}, but the build has no ${route.includes(\"*\") ? \"page matching it\" : \"such page\"}.`));\n }\n }\n }\n // A return wave brings back pages that are live now, so its pages are in the build before the day.\n for (const wave of waves) {\n if (wave.kind !== \"return\" || wave.releaseOn <= today) continue;\n for (const route of wave.routes) {\n const matched = route.includes(\"*\") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);\n if (!matched) failures.push(fail(\"LOC-INDEX\", route, `The return wave of ${wave.releaseOn} brings back ${route}, but the build has no ${route.includes(\"*\") ? \"page matching it\" : \"such page\"}; a return wave lists pages that are live now.`));\n }\n }\n if (plan) {\n for (const route of plan.indexedNow) {\n const matched = route.includes(\"*\") ? estate.pages.some((page) => matchesWaveRoute(page.path, route)) : estate.byPath.has(route);\n if (!matched) failures.push(fail(\"LOC-INDEX\", route, `estate.indexedNow lists ${route}, but the build has no ${route.includes(\"*\") ? \"page matching it\" : \"such page\"}.`));\n }\n const parent = (template: string | undefined) => template?.slice(0, template.lastIndexOf(\"/\"));\n const shared = plan.paths?.region !== undefined && parent(plan.paths.region) === parent(plan.paths.suburb);\n for (const page of shared ? pages : []) {\n if (page.place?.kind !== \"region\") continue;\n const slug = page.path.slice(page.path.lastIndexOf(\"/\") + 1);\n if (!context.geography?.suburbs.has(slug) || page.path !== `${parent(plan.paths?.region)}/${slug}`) continue;\n if (plan.slugs?.suburbs?.[slug] !== `${slug}-suburb`) {\n failures.push(fail(\"LOC-INDEX\", page.path, `The region page ${page.path} takes the slug of the suburb ${slug}; that suburb's pages take \"${slug}-suburb\" (estate.slugs.suburbs).`));\n }\n }\n }\n const upcoming = scheduled > 0 ? `; ${plural(scheduled, \"more page\")} ${scheduled === 1 ? \"is\" : \"are\"} dated by upcoming waves` : \"\";\n return outcome(\"LOC-INDEX\", failures, `All ${plural(live, \"released location page\")} are indexable and in the sitemap${upcoming}.`);\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type SiteEstatePlan } from "@brandlab-digital/site-core/strategy";
1
2
  export declare const FULL_STRATEGY_FILE = "src/data/strategy.ts";
2
3
  /** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */
3
4
  export declare const WAVE_MIN_GAP_DAYS = 14;
@@ -13,7 +14,10 @@ export interface FullStrategy {
13
14
  readonly waves: readonly {
14
15
  readonly releaseOn: string;
15
16
  readonly routes: readonly string[];
17
+ readonly kind?: "release" | "return";
16
18
  }[];
19
+ /** An existing estate's plan (site-core 3.14.0 `estate`): pages indexed now, family paths and public slugs; null when none. */
20
+ readonly estate: SiteEstatePlan | null;
17
21
  /** The trade `defineSectionOrder` names, if any. */
18
22
  readonly trade: string | undefined;
19
23
  /** Recorded alternative orders by family. */
@@ -1 +1 @@
1
- {"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/strategy.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAEzD,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACzI,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC9F,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAChE,sGAAsG;IACtG,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACvG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,yHAAyH;AACzH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,gHAAgH;AAChH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOrE;AAKD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAoEjF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiClJ"}
1
+ {"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/strategy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAG/F,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAEzD,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACzI,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;KAAE,EAAE,CAAC;IACpI,+HAA+H;IAC/H,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAChE,sGAAsG;IACtG,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACvG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,yHAAyH;AACzH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,gHAAgH;AAChH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOrE;AAKD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAkFjF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiClJ"}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
+ import { estatePlanProblems } from "@brandlab-digital/site-core/strategy";
9
10
  import { LiteralReader, isFullyResolved, isLiteralObject } from "../enquiry/literal.js";
10
11
  export const FULL_STRATEGY_FILE = "src/data/strategy.ts";
11
12
  /** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */
@@ -39,7 +40,7 @@ export function readFullStrategy(projectRoot, today) {
39
40
  const empty = { phrases: [], waves: [], orders: [] };
40
41
  const file = join(projectRoot, FULL_STRATEGY_FILE);
41
42
  if (!existsSync(file)) {
42
- return { phrases: null, waves: [], trade: undefined, recordedOrders: new Map(), problems: empty, found: false };
43
+ return { phrases: null, waves: [], estate: null, trade: undefined, recordedOrders: new Map(), problems: empty, found: false };
43
44
  }
44
45
  const reader = new LiteralReader(readFileSync(file, "utf8"), FULL_STRATEGY_FILE);
45
46
  const problems = { phrases: [], waves: [], orders: [] };
@@ -71,11 +72,26 @@ export function readFullStrategy(projectRoot, today) {
71
72
  problems.waves.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} needs a releaseOn date and a list of routes.`);
72
73
  continue;
73
74
  }
74
- waves.push({ releaseOn: wave["releaseOn"], routes: wave["routes"] });
75
+ const kind = wave["kind"];
76
+ if (kind !== undefined && kind !== "release" && kind !== "return") {
77
+ problems.waves.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} has the kind "${String(kind)}"; a wave is a release or a return.`);
78
+ }
79
+ waves.push({ releaseOn: wave["releaseOn"], routes: wave["routes"], ...(kind === "return" || kind === "release" ? { kind } : {}) });
75
80
  }
76
81
  problems.waves.push(...waveScheduleProblems(waves, today));
77
82
  }
78
83
  }
84
+ let estate = null;
85
+ const rawEstate = record?.["estate"];
86
+ if (rawEstate !== undefined) {
87
+ if (!isLiteralObject(rawEstate) || !isFullyResolved(rawEstate) || !Array.isArray(rawEstate["indexedNow"])) {
88
+ problems.waves.push(`The estate plan in ${FULL_STRATEGY_FILE} is not plain data with an indexedNow list, so the pages indexed now cannot be read.`);
89
+ }
90
+ else {
91
+ estate = rawEstate;
92
+ problems.waves.push(...estatePlanProblems(estate, waves, today).map((problem) => `${FULL_STRATEGY_FILE}: ${problem}`));
93
+ }
94
+ }
79
95
  if (argument && !record) {
80
96
  problems.phrases.push(`${FULL_STRATEGY_FILE} is not plain data, so phrase ownership cannot be read.`);
81
97
  problems.waves.push(`${FULL_STRATEGY_FILE} is not plain data, so the release schedule cannot be read.`);
@@ -101,7 +117,7 @@ export function readFullStrategy(projectRoot, today) {
101
117
  }
102
118
  }
103
119
  }
104
- return { phrases, waves, trade, recordedOrders, problems, found: true };
120
+ return { phrases, waves, estate, trade, recordedOrders, problems, found: true };
105
121
  }
106
122
  /**
107
123
  * 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
@@ -1 +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"]}
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 { estatePlanProblems, type SiteEstatePlan } from \"@brandlab-digital/site-core/strategy\";\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[]; readonly kind?: \"release\" | \"return\" }[];\n /** An existing estate's plan (site-core 3.14.0 `estate`): pages indexed now, family paths and public slugs; null when none. */\n readonly estate: SiteEstatePlan | null;\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: [], estate: null, 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[]; kind?: \"release\" | \"return\" }[] = [];\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 const kind = wave[\"kind\"];\n if (kind !== undefined && kind !== \"release\" && kind !== \"return\") {\n problems.waves.push(`Wave ${index + 1} in ${FULL_STRATEGY_FILE} has the kind \"${String(kind)}\"; a wave is a release or a return.`);\n }\n waves.push({ releaseOn: wave[\"releaseOn\"], routes: wave[\"routes\"] as string[], ...(kind === \"return\" || kind === \"release\" ? { kind } : {}) });\n }\n problems.waves.push(...waveScheduleProblems(waves, today));\n }\n }\n let estate: SiteEstatePlan | null = null;\n const rawEstate = record?.[\"estate\"];\n if (rawEstate !== undefined) {\n if (!isLiteralObject(rawEstate) || !isFullyResolved(rawEstate) || !Array.isArray(rawEstate[\"indexedNow\"])) {\n problems.waves.push(`The estate plan in ${FULL_STRATEGY_FILE} is not plain data with an indexedNow list, so the pages indexed now cannot be read.`);\n } else {\n estate = rawEstate as unknown as SiteEstatePlan;\n problems.waves.push(...estatePlanProblems(estate, waves, today).map((problem) => `${FULL_STRATEGY_FILE}: ${problem}`));\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, estate, 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"]}
@@ -1,9 +1,9 @@
1
1
  import type { SiteSystemFinding } from "../../checks.js";
2
2
  import { type ParsedPage } from "../enquiry/common.js";
3
3
  /** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */
4
- export declare const K2_SKINNED_HOOKS: readonly ["header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container", "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table"];
4
+ export declare const K2_SKINNED_HOOKS: readonly ["header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container", "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table", "eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs"];
5
5
  /** `data-bl-section` ids of the sections the library renders and skins. */
6
- export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact"];
6
+ export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services"];
7
7
  export declare const K2_REFERENCE_CLASS: RegExp;
8
8
  /** Hook names the project's own stylesheets select: `[data-bl="x"]` and `[data-bl-section="x"]`. */
9
9
  export declare function hooksStyledBySite(projectRoot: string): Set<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,yPAGnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,6FAA8F,CAAC;AAE/H,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAqB9D,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA0BlE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAqC9F"}
1
+ {"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,uVAKnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,iHAAkH,CAAC;AAEnJ,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAqB9D,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA0BlE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAqC9F"}
@@ -5,7 +5,9 @@
5
5
  * What the HTML can show, defined exactly:
6
6
  * - A component is a rendered element with `data-bl="<name>"` or `data-bl-section="<id>"`, for the names the library
7
7
  * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star
8
- * row) and the site's own compositions (the hero) are not components in this sense.
8
+ * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the
9
+ * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them
10
+ * from its skin stylesheet) join the lists.
9
11
  * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an
10
12
  * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a
11
13
  * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the
@@ -21,9 +23,11 @@ import { PageProblems } from "../library/common.js";
21
23
  export const K2_SKINNED_HOOKS = [
22
24
  "header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container",
23
25
  "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table",
26
+ // 3.14.0 primitives
27
+ "eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs",
24
28
  ];
25
29
  /** `data-bl-section` ids of the sections the library renders and skins. */
26
- export const K2_SKINNED_SECTIONS = ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact"];
30
+ export const K2_SKINNED_SECTIONS = ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services"];
27
31
  export const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;
28
32
  function classesOf(element) {
29
33
  return (attr(element, "class") ?? "").split(/\s+/).filter(Boolean);