@brandlab-digital/conformance 3.14.0 → 3.15.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.
- package/README.md +60 -3
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +18 -2
- package/dist/engineering.js.map +1 -1
- package/dist/estate-snapshot.d.ts +54 -0
- package/dist/estate-snapshot.d.ts.map +1 -0
- package/dist/estate-snapshot.js +164 -0
- package/dist/estate-snapshot.js.map +1 -0
- package/dist/estate.d.ts +10 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +36 -16
- package/dist/estate.js.map +1 -1
- package/dist/media-snapshot.d.ts +53 -0
- package/dist/media-snapshot.d.ts.map +1 -0
- package/dist/media-snapshot.js +66 -0
- package/dist/media-snapshot.js.map +1 -0
- package/dist/site-system/checks.d.ts +2 -2
- package/dist/site-system/checks.js +2 -2
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +13 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +30 -1
- package/dist/site-system/config.js.map +1 -1
- package/dist/site-system/index.d.ts +1 -1
- package/dist/site-system/index.d.ts.map +1 -1
- package/dist/site-system/index.js +1 -1
- package/dist/site-system/index.js.map +1 -1
- package/dist/site-system/journeys/conversion.d.ts.map +1 -1
- package/dist/site-system/journeys/conversion.js +15 -2
- package/dist/site-system/journeys/conversion.js.map +1 -1
- package/dist/site-system/journeys/menu.d.ts.map +1 -1
- package/dist/site-system/journeys/menu.js +24 -1
- package/dist/site-system/journeys/menu.js.map +1 -1
- package/dist/site-system/journeys/page-helpers.d.ts.map +1 -1
- package/dist/site-system/journeys/page-helpers.js +1 -1
- package/dist/site-system/journeys/page-helpers.js.map +1 -1
- package/dist/site-system/journeys/plan.d.ts +13 -3
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +12 -2
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/shell.d.ts +1 -1
- package/dist/site-system/journeys/shell.js +2 -2
- package/dist/site-system/journeys/shell.js.map +1 -1
- package/dist/site-system/report.d.ts.map +1 -1
- package/dist/site-system/report.js +24 -4
- package/dist/site-system/report.js.map +1 -1
- package/dist/site-system/rules/form/c7-thank-you.d.ts +7 -1
- package/dist/site-system/rules/form/c7-thank-you.d.ts.map +1 -1
- package/dist/site-system/rules/form/c7-thank-you.js +26 -3
- package/dist/site-system/rules/form/c7-thank-you.js.map +1 -1
- package/dist/site-system/rules/form/index.d.ts.map +1 -1
- package/dist/site-system/rules/form/index.js +11 -1
- package/dist/site-system/rules/form/index.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +2 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -1
- package/dist/site-system/rules/full/common.js.map +1 -1
- package/dist/site-system/rules/full/estate.d.ts +10 -2
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +35 -5
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/geography.d.ts +7 -0
- package/dist/site-system/rules/full/geography.d.ts.map +1 -1
- package/dist/site-system/rules/full/geography.js +9 -0
- package/dist/site-system/rules/full/geography.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +2 -1
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +5 -1
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/l10-location-copy.d.ts +1 -1
- package/dist/site-system/rules/full/l10-location-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l10-location-copy.js +5 -2
- package/dist/site-system/rules/full/l10-location-copy.js.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.d.ts +3 -2
- package/dist/site-system/rules/full/l3-neighbours.d.ts.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.js +4 -1
- package/dist/site-system/rules/full/l3-neighbours.js.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts +2 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.js +8 -3
- package/dist/site-system/rules/full/l4-local-copy.js.map +1 -1
- package/dist/site-system/rules/full/l9-data-contract.js +2 -2
- package/dist/site-system/rules/full/l9-data-contract.js.map +1 -1
- package/dist/site-system/rules/full/links.d.ts +3 -1
- package/dist/site-system/rules/full/links.d.ts.map +1 -1
- package/dist/site-system/rules/full/links.js +15 -5
- package/dist/site-system/rules/full/links.js.map +1 -1
- package/dist/site-system/rules/full/route-kinds.d.ts +15 -0
- package/dist/site-system/rules/full/route-kinds.d.ts.map +1 -0
- package/dist/site-system/rules/full/route-kinds.js +52 -0
- package/dist/site-system/rules/full/route-kinds.js.map +1 -0
- package/dist/site-system/rules/full/strategy.d.ts +2 -0
- package/dist/site-system/rules/full/strategy.d.ts.map +1 -1
- package/dist/site-system/rules/full/strategy.js +86 -14
- package/dist/site-system/rules/full/strategy.js.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +2 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.js +9 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.d.ts.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.js +69 -5
- package/dist/site-system/rules/sections/t6-media-entries.js.map +1 -1
- package/dist/site-system-e2e.d.ts +1 -1
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +17 -4
- package/dist/site-system-e2e.js.map +1 -1
- package/package.json +10 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/links.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AACzG,OAAO,EAAE,gBAAgB,EAAgC,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,sDAAsD;AACtD,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAsB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC,CAAC,CAAC;IACzG,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAI,GAAG,GAAG;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACpB,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;YAChC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC;gBAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;oBAAE,SAAS;gBAC/D,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAoB,EAAE,OAAiD;IAC7F,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAwB,OAAO,CAAC,MAAM,CAAC,WAAW;SAC7D,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;SAChC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,+EAA+E,CAAC,CAAC,CAAC;IACrI,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAC7F,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,8CAA8C,CAAC,CAAC,CAAC;QACnG,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC,gDAAgD,CAAC,CAAC;AAC7I,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAoB,EAAE,OAAiD;IAC7F,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,mBAAmB,CAAC,CAAC,CAAC;IACzI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,+DAA+D,CAAC,CAAC,CAAC;IACtH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChH,IAAI,OAAO,CAAC,MAAM,GAAG,cAAc,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACxE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAChB,IAAI,EACJ,IAAI,CAAC,IAAI,EACT,GAAG,IAAI,CAAC,IAAI,QAAQ,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,2BAA2B,cAAc,iBAAiB,iBAAiB,sEAAsE,CACtS,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,mCAAmC,CAAC,SAAS,cAAc,+BAA+B,iBAAiB,sBAAsB,CAAC,CAAC;AAChM,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAoB;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACrG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,4CAA4C,CAAC,CAAC,CAAC;IACnG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,gEAAgE,CAAC,CAAC,CAAC;IAChI,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,kDAAkD,CAAC,CAAC,CAAC;aACzH,IAAI,KAAK,GAAG,YAAY;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,OAAO,KAAK,yCAAyC,YAAY,YAAY,CAAC,CAAC,CAAC;IACjK,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,YAAY,6BAA6B,CAAC,CAAC;AAChI,CAAC;AAED,kGAAkG;AAClG,SAAS,aAAa,CAAC,IAAY,EAAE,MAAc;IACjD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAoB;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,IAAI,CAAC,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,iDAAiD,OAAO,CAAC,gBAAgB,kCAAkC,CAAC,CAAC,CAAC;IAC5J,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqC,CAAC;IAC9D,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,mBAAmB,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,QAAQ;YAAE,SAAS;QACpI,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAC9D,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAwB,CAAC;QACjF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5D,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,mFAAmF,CAAC,CAAC,CAAC;IAC3I,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,QAAQ,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACzG,IAAI,GAAG,EAAE,SAAS,EAAE,CAAC;gBACnB,IAAI,IAAI,CAAC,CAAC;gBACV,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAChB,IAAI,EACJ,UAAU,CAAC,CAAC,CAAC,EACb,GAAG;gBACD,CAAC,CAAC,WAAW,GAAG,CAAC,IAAI,YAAY,IAAI,aAAa,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,0EAA0E;gBACxJ,CAAC,CAAC,OAAO,IAAI,eAAe,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,gCAAgC,UAAU,CAAC,CAAC,CAAC,IAAI,CAC/I,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,sEAAsE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AACtL,CAAC","sourcesContent":["/**\n * The link graph of the built estate (Site System v2 §4.4 step 7), computed from the route catalogue as built and the\n * links in every page's server HTML, never by crawling:\n * - E5: every indexable page but home has at least one link from another page;\n * - L6: each indexable suburb and service-in-suburb page has 6 or more inbound links from 3 or more page types;\n * - L7: each indexable location page is 3 clicks or fewer from home;\n * - L8: each family of suburb pages has a hub in every region it serves (`{stem}` with the region, or `{region}-area`).\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, listOf, needsBuild, notApplicable, outcome, plural, type FullContext } from \"./common.js\";\nimport { isLocationFamily, type Estate, type EstatePage } from \"./estate.js\";\nimport { placeName } from \"./geography.js\";\n\nexport const L6_MIN_INBOUND = 6;\nexport const L6_MIN_PAGE_TYPES = 3;\nexport const L7_MAX_DEPTH = 3;\n\n/** For each page, the other pages that link to it. */\nexport function inboundLinks(estate: Estate): Map<string, Set<string>> {\n const inbound = new Map<string, Set<string>>(estate.pages.map((page) => [page.path, new Set<string>()]));\n for (const page of estate.pages) {\n for (const target of page.links) inbound.get(target)?.add(page.path);\n }\n return inbound;\n}\n\n/** Clicks from home to every page it reaches. */\nexport function clickDepths(estate: Estate, home = \"/\"): Map<string, number> {\n const depths = new Map<string, number>();\n if (!estate.byPath.has(home)) return depths;\n depths.set(home, 0);\n let frontier = [home];\n while (frontier.length > 0) {\n const next: string[] = [];\n for (const path of frontier) {\n const depth = depths.get(path)!;\n for (const target of estate.byPath.get(path)?.links ?? []) {\n if (!estate.byPath.has(target) || depths.has(target)) continue;\n depths.set(target, depth + 1);\n next.push(target);\n }\n }\n frontier = next;\n }\n return depths;\n}\n\nexport function checkE5(context: FullContext, inbound: ReadonlyMap<string, ReadonlySet<string>>): SiteSystemFinding[] {\n const skip = needsBuild(\"E5\", context.estate);\n if (skip) return [skip];\n const failures: SiteSystemFinding[] = context.estate.missingHtml\n .filter((item) => !item.location)\n .map((item) => fail(\"E5\", item.path, `${item.path} is a prerendered route with no HTML in the build, so nothing can link to it.`));\n const indexable = context.estate.pages.filter((page) => page.indexable && page.path !== \"/\");\n for (const page of indexable) {\n if ((inbound.get(page.path)?.size ?? 0) === 0) {\n failures.push(fail(\"E5\", page.path, `${page.path} is indexable but no other page links to it.`));\n }\n }\n return outcome(\"E5\", failures, `Every one of ${plural(indexable.length, \"indexable page\")} other than home has a link from another page.`);\n}\n\nexport function checkL6(context: FullContext, inbound: ReadonlyMap<string, ReadonlySet<string>>): SiteSystemFinding[] {\n const skip = needsBuild(\"L6\", context.estate);\n if (skip) return [skip];\n const failures: SiteSystemFinding[] = [];\n const pages = context.estate.pages.filter((page) => page.indexable && (page.family === \"suburb\" || page.family === \"service-in-suburb\"));\n if (pages.length === 0) return [notApplicable(\"L6\", \"The build has no indexable suburb or service-in-suburb pages.\")];\n for (const page of pages) {\n const sources = [...(inbound.get(page.path) ?? [])];\n const types = [...new Set(sources.flatMap((source) => context.estate.byPath.get(source)?.family ?? []))].sort();\n if (sources.length < L6_MIN_INBOUND || types.length < L6_MIN_PAGE_TYPES) {\n failures.push(fail(\n \"L6\",\n page.path,\n `${page.path} has ${plural(sources.length, \"inbound link\")} from ${plural(types.length, \"page type\")}${types.length > 0 ? ` (${listOf(types)})` : \"\"}; a location page needs ${L6_MIN_INBOUND} or more from ${L6_MIN_PAGE_TYPES} or more, such as its hub, suburb page, neighbours and service page.`,\n ));\n }\n }\n return outcome(\"L6\", failures, `All ${plural(pages.length, \"suburb and service-in-suburb page\")} have ${L6_MIN_INBOUND} or more inbound links from ${L6_MIN_PAGE_TYPES} or more page types.`);\n}\n\nexport function checkL7(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"L7\", context.estate);\n if (skip) return [skip];\n const pages = context.estate.pages.filter((page) => page.indexable && isLocationFamily(page.family));\n if (pages.length === 0) return [notApplicable(\"L7\", \"The build has no indexable location pages.\")];\n if (!context.estate.byPath.has(\"/\")) return [fail(\"L7\", \"/\", \"The build has no home page, so click depth cannot be measured.\")];\n const depths = clickDepths(context.estate);\n const failures: SiteSystemFinding[] = [];\n for (const page of pages) {\n const depth = depths.get(page.path);\n if (depth === undefined) failures.push(fail(\"L7\", page.path, `${page.path} cannot be reached from home by following links.`));\n else if (depth > L7_MAX_DEPTH) failures.push(fail(\"L7\", page.path, `${page.path} is ${depth} clicks from home; location pages are ${L7_MAX_DEPTH} or fewer.`));\n }\n return outcome(\"L7\", failures, `All ${plural(pages.length, \"location page\")} are ${L7_MAX_DEPTH} clicks or fewer from home.`);\n}\n\n/** The hub path for a stem and region: `{stem}` with the region slug, or with `{region}-area`. */\nfunction hubCandidates(stem: string, region: string): string[] {\n return [stem.replace(\"{place}\", region), stem.replace(\"{place}\", `${region}-area`)];\n}\n\nexport function checkL8(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"L8\", context.estate);\n if (skip) return [skip];\n const geography = context.geography;\n if (!geography) return [fail(\"L8\", undefined, `The regions of the suburbs could not be read (${context.geographyProblem}), so hubs could not be checked.`)];\n const families = new Map<string, Map<string, EstatePage[]>>();\n for (const page of context.estate.pages) {\n if (!page.indexable || (page.family !== \"suburb\" && page.family !== \"service-in-suburb\") || page.place?.kind !== \"suburb\") continue;\n const region = geography.suburbs.get(page.place.slug)?.region;\n if (!region) continue;\n const regions = families.get(page.place.stem) ?? new Map<string, EstatePage[]>();\n regions.set(region, [...(regions.get(region) ?? []), page]);\n families.set(page.place.stem, regions);\n }\n if (families.size === 0) return [notApplicable(\"L8\", \"The build has no indexable suburb or service-in-suburb pages in registry suburbs.\")];\n const failures: SiteSystemFinding[] = [];\n let hubs = 0;\n for (const [stem, regions] of families) {\n for (const [region, pages] of regions) {\n const candidates = hubCandidates(stem, region);\n const hub = candidates.map((path) => context.estate.byPath.get(path)).find((page) => page !== undefined);\n if (hub?.indexable) {\n hubs += 1;\n continue;\n }\n failures.push(fail(\n \"L8\",\n candidates[0],\n hub\n ? `The hub ${hub.path} for the ${stem} pages in ${placeName(region, geography)} is noindex; every family has an indexable hub in each region it serves.`\n : `The ${stem} pages have ${plural(pages.length, \"page\")} in ${placeName(region, geography)} but no hub for that region (${candidates[0]}).`,\n ));\n }\n }\n return outcome(\"L8\", failures, `Every family of location pages has a hub in each region it serves (${plural(hubs, \"hub\")} across ${plural(families.size, \"family\", \"families\")}).`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/links.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The link graph of the built estate (Site System v2 §4.4 step 7), computed from the route catalogue as built and the\n * links in every page's server HTML, never by crawling:\n * - E5: every indexable page but home has at least one link from another page;\n * - L6: each indexable suburb and service-in-suburb page has 6 or more inbound links from 3 or more page types (a\n * linking page's type is its manifest family, else its catalogue hub kind as `locations-hub` or `services-hub`, else\n * the family the estate inferred);\n * - L7: each indexable location page is 3 clicks or fewer from home;\n * - L8: each family of suburb pages has a hub in every region it serves (`{stem}` with the region, or `{region}-area`).\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, listOf, needsBuild, notApplicable, outcome, plural, type FullContext } from \"./common.js\";\nimport { isLocationFamily, type Estate, type EstatePage } from \"./estate.js\";\nimport { placeName } from \"./geography.js\";\nimport { linkingPageType } from \"./route-kinds.js\";\n\nexport const L6_MIN_INBOUND = 6;\nexport const L6_MIN_PAGE_TYPES = 3;\nexport const L7_MAX_DEPTH = 3;\n\n/** For each page, the other pages that link to it. */\nexport function inboundLinks(estate: Estate): Map<string, Set<string>> {\n const inbound = new Map<string, Set<string>>(estate.pages.map((page) => [page.path, new Set<string>()]));\n for (const page of estate.pages) {\n for (const target of page.links) inbound.get(target)?.add(page.path);\n }\n return inbound;\n}\n\n/** Clicks from home to every page it reaches. */\nexport function clickDepths(estate: Estate, home = \"/\"): Map<string, number> {\n const depths = new Map<string, number>();\n if (!estate.byPath.has(home)) return depths;\n depths.set(home, 0);\n let frontier = [home];\n while (frontier.length > 0) {\n const next: string[] = [];\n for (const path of frontier) {\n const depth = depths.get(path)!;\n for (const target of estate.byPath.get(path)?.links ?? []) {\n if (!estate.byPath.has(target) || depths.has(target)) continue;\n depths.set(target, depth + 1);\n next.push(target);\n }\n }\n frontier = next;\n }\n return depths;\n}\n\nexport function checkE5(context: FullContext, inbound: ReadonlyMap<string, ReadonlySet<string>>): SiteSystemFinding[] {\n const skip = needsBuild(\"E5\", context.estate);\n if (skip) return [skip];\n const failures: SiteSystemFinding[] = context.estate.missingHtml\n .filter((item) => !item.location)\n .map((item) => fail(\"E5\", item.path, `${item.path} is a prerendered route with no HTML in the build, so nothing can link to it.`));\n const indexable = context.estate.pages.filter((page) => page.indexable && page.path !== \"/\");\n for (const page of indexable) {\n if ((inbound.get(page.path)?.size ?? 0) === 0) {\n failures.push(fail(\"E5\", page.path, `${page.path} is indexable but no other page links to it.`));\n }\n }\n return outcome(\"E5\", failures, `Every one of ${plural(indexable.length, \"indexable page\")} other than home has a link from another page.`);\n}\n\nexport function checkL6(context: FullContext, inbound: ReadonlyMap<string, ReadonlySet<string>>): SiteSystemFinding[] {\n const skip = needsBuild(\"L6\", context.estate);\n if (skip) return [skip];\n const failures: SiteSystemFinding[] = [];\n const pages = context.estate.pages.filter((page) => page.indexable && (page.family === \"suburb\" || page.family === \"service-in-suburb\"));\n if (pages.length === 0) return [notApplicable(\"L6\", \"The build has no indexable suburb or service-in-suburb pages.\")];\n for (const page of pages) {\n const sources = [...(inbound.get(page.path) ?? [])];\n // A hub without a section manifest is typed by its catalogue kind (AA's /locations, the location-index).\n const kinds = context.catalogueKinds ?? new Map<string, string>();\n const types = [...new Set(sources.flatMap((source) => {\n const linking = context.estate.byPath.get(source);\n return (linking ? linkingPageType(linking, kinds) : undefined) ?? [];\n }))].sort();\n if (sources.length < L6_MIN_INBOUND || types.length < L6_MIN_PAGE_TYPES) {\n failures.push(fail(\n \"L6\",\n page.path,\n `${page.path} has ${plural(sources.length, \"inbound link\")} from ${plural(types.length, \"page type\")}${types.length > 0 ? ` (${listOf(types)})` : \"\"}; a location page needs ${L6_MIN_INBOUND} or more from ${L6_MIN_PAGE_TYPES} or more, such as its hub, suburb page, neighbours and service page.`,\n ));\n }\n }\n return outcome(\"L6\", failures, `All ${plural(pages.length, \"suburb and service-in-suburb page\")} have ${L6_MIN_INBOUND} or more inbound links from ${L6_MIN_PAGE_TYPES} or more page types.`);\n}\n\nexport function checkL7(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"L7\", context.estate);\n if (skip) return [skip];\n const pages = context.estate.pages.filter((page) => page.indexable && isLocationFamily(page.family));\n if (pages.length === 0) return [notApplicable(\"L7\", \"The build has no indexable location pages.\")];\n if (!context.estate.byPath.has(\"/\")) return [fail(\"L7\", \"/\", \"The build has no home page, so click depth cannot be measured.\")];\n const depths = clickDepths(context.estate);\n const failures: SiteSystemFinding[] = [];\n for (const page of pages) {\n const depth = depths.get(page.path);\n if (depth === undefined) failures.push(fail(\"L7\", page.path, `${page.path} cannot be reached from home by following links.`));\n else if (depth > L7_MAX_DEPTH) failures.push(fail(\"L7\", page.path, `${page.path} is ${depth} clicks from home; location pages are ${L7_MAX_DEPTH} or fewer.`));\n }\n return outcome(\"L7\", failures, `All ${plural(pages.length, \"location page\")} are ${L7_MAX_DEPTH} clicks or fewer from home.`);\n}\n\n/**\n * The hub path for a stem and region: `{stem}` with the public slug the estate plan gives the region\n * (`estate.slugs.regions`, as AA's `sydney-city` → `sydney-cbd`), then with the region slug, then `{region}-area`.\n */\nfunction hubCandidates(stem: string, region: string, renames: Readonly<Record<string, string>> | undefined): string[] {\n const renamed = renames && Object.hasOwn(renames, region) ? renames[region] : undefined;\n return [...new Set([...(renamed ? [stem.replace(\"{place}\", renamed)] : []), stem.replace(\"{place}\", region), stem.replace(\"{place}\", `${region}-area`)])];\n}\n\nexport function checkL8(context: FullContext): SiteSystemFinding[] {\n const skip = needsBuild(\"L8\", context.estate);\n if (skip) return [skip];\n const geography = context.geography;\n if (!geography) return [fail(\"L8\", undefined, `The regions of the suburbs could not be read (${context.geographyProblem}), so hubs could not be checked.`)];\n const families = new Map<string, Map<string, EstatePage[]>>();\n for (const page of context.estate.pages) {\n if (!page.indexable || (page.family !== \"suburb\" && page.family !== \"service-in-suburb\") || page.place?.kind !== \"suburb\") continue;\n const region = geography.suburbs.get(page.place.slug)?.region;\n if (!region) continue;\n const regions = families.get(page.place.stem) ?? new Map<string, EstatePage[]>();\n regions.set(region, [...(regions.get(region) ?? []), page]);\n families.set(page.place.stem, regions);\n }\n if (families.size === 0) return [notApplicable(\"L8\", \"The build has no indexable suburb or service-in-suburb pages in registry suburbs.\")];\n const failures: SiteSystemFinding[] = [];\n let hubs = 0;\n for (const [stem, regions] of families) {\n for (const [region, pages] of regions) {\n const candidates = hubCandidates(stem, region, context.strategy.estate?.slugs?.regions);\n const hub = candidates.map((path) => context.estate.byPath.get(path)).find((page) => page !== undefined);\n if (hub?.indexable) {\n hubs += 1;\n continue;\n }\n failures.push(fail(\n \"L8\",\n candidates[0],\n hub\n ? `The hub ${hub.path} for the ${stem} pages in ${placeName(region, geography)} is noindex; every family has an indexable hub in each region it serves.`\n : `The ${stem} pages have ${plural(pages.length, \"page\")} in ${placeName(region, geography)} but no hub for that region (${candidates[0]}).`,\n ));\n }\n }\n return outcome(\"L8\", failures, `Every family of location pages has a hub in each region it serves (${plural(hubs, \"hub\")} across ${plural(families.size, \"family\", \"families\")}).`);\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const ROUTE_CATALOGUE_FILE = "src/data/routes.ts";
|
|
2
|
+
/** Catalogue hub kinds and the page family their pages are (site-core's section-order hub families). */
|
|
3
|
+
export declare const CATALOGUE_HUB_FAMILIES: Readonly<Record<string, string>>;
|
|
4
|
+
/** Path → kind for every literal catalogue entry; empty when the file is missing or unreadable. */
|
|
5
|
+
export declare function readRouteCatalogueKinds(projectRoot: string): ReadonlyMap<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* A page's type for L6: its section manifest's family first, then the hub family its catalogue kind gives it, then
|
|
8
|
+
* the family the estate inferred (the estate plan's paths, the sitemap, the route or the stem).
|
|
9
|
+
*/
|
|
10
|
+
export declare function linkingPageType(page: {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly family: string | undefined;
|
|
13
|
+
readonly familySource: string | undefined;
|
|
14
|
+
}, catalogueKinds: ReadonlyMap<string, string>): string | undefined;
|
|
15
|
+
//# sourceMappingURL=route-kinds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-kinds.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/route-kinds.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAEzD,wGAAwG;AACxG,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGlE,CAAC;AAEH,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1C,MAAM,GAAG,SAAS,CAGpB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route catalogue's kinds, read from `src/data/routes.ts` without running it (3.15.0). Only entries written as
|
|
3
|
+
* literals are read: hubs are (`{ kind: 'location-index', path: '/locations' }`), while generated entries such as
|
|
4
|
+
* `...suburbs.map(...)` are skipped. The full tier uses them to type hub pages that carry no section manifest, as
|
|
5
|
+
* AA's /locations hub, which links every indexed suburb.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { normalisePath } from "../enquiry/common.js";
|
|
10
|
+
import { LiteralReader, isLiteralObject } from "../enquiry/literal.js";
|
|
11
|
+
export const ROUTE_CATALOGUE_FILE = "src/data/routes.ts";
|
|
12
|
+
/** Catalogue hub kinds and the page family their pages are (site-core's section-order hub families). */
|
|
13
|
+
export const CATALOGUE_HUB_FAMILIES = Object.freeze({
|
|
14
|
+
"location-index": "locations-hub",
|
|
15
|
+
"service-index": "services-hub",
|
|
16
|
+
});
|
|
17
|
+
/** Path → kind for every literal catalogue entry; empty when the file is missing or unreadable. */
|
|
18
|
+
export function readRouteCatalogueKinds(projectRoot) {
|
|
19
|
+
const kinds = new Map();
|
|
20
|
+
const file = join(projectRoot, ROUTE_CATALOGUE_FILE);
|
|
21
|
+
if (!existsSync(file))
|
|
22
|
+
return kinds;
|
|
23
|
+
let reader;
|
|
24
|
+
try {
|
|
25
|
+
reader = new LiteralReader(readFileSync(file, "utf8"), ROUTE_CATALOGUE_FILE);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return kinds;
|
|
29
|
+
}
|
|
30
|
+
const argument = reader.callArguments("defineRouteCatalog")[0] ?? reader.constant("routes");
|
|
31
|
+
const value = argument ? reader.evaluate(argument) : undefined;
|
|
32
|
+
if (!Array.isArray(value))
|
|
33
|
+
return kinds;
|
|
34
|
+
for (const entry of value) {
|
|
35
|
+
if (!isLiteralObject(entry))
|
|
36
|
+
continue;
|
|
37
|
+
const { kind, path } = entry;
|
|
38
|
+
if (typeof kind === "string" && typeof path === "string" && path.startsWith("/"))
|
|
39
|
+
kinds.set(normalisePath(path), kind);
|
|
40
|
+
}
|
|
41
|
+
return kinds;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A page's type for L6: its section manifest's family first, then the hub family its catalogue kind gives it, then
|
|
45
|
+
* the family the estate inferred (the estate plan's paths, the sitemap, the route or the stem).
|
|
46
|
+
*/
|
|
47
|
+
export function linkingPageType(page, catalogueKinds) {
|
|
48
|
+
if (page.familySource === "manifest")
|
|
49
|
+
return page.family;
|
|
50
|
+
return CATALOGUE_HUB_FAMILIES[catalogueKinds.get(page.path) ?? ""] ?? page.family;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=route-kinds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-kinds.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/route-kinds.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAgB,MAAM,uBAAuB,CAAC;AAErF,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAEzD,wGAAwG;AACxG,MAAM,CAAC,MAAM,sBAAsB,GAAqC,MAAM,CAAC,MAAM,CAAC;IACpF,gBAAgB,EAAE,eAAe;IACjC,eAAe,EAAE,cAAc;CAChC,CAAC,CAAC;AAEH,mGAAmG;AACnG,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACrD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,aAAa,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAwB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;YAAE,SAAS;QACtC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;QAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACzH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAA+G,EAC/G,cAA2C;IAE3C,IAAI,IAAI,CAAC,YAAY,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACzD,OAAO,sBAAsB,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;AACpF,CAAC","sourcesContent":["/**\n * The route catalogue's kinds, read from `src/data/routes.ts` without running it (3.15.0). Only entries written as\n * literals are read: hubs are (`{ kind: 'location-index', path: '/locations' }`), while generated entries such as\n * `...suburbs.map(...)` are skipped. The full tier uses them to type hub pages that carry no section manifest, as\n * AA's /locations hub, which links every indexed suburb.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { normalisePath } from \"../enquiry/common.js\";\nimport { LiteralReader, isLiteralObject, type Literal } from \"../enquiry/literal.js\";\n\nexport const ROUTE_CATALOGUE_FILE = \"src/data/routes.ts\";\n\n/** Catalogue hub kinds and the page family their pages are (site-core's section-order hub families). */\nexport const CATALOGUE_HUB_FAMILIES: Readonly<Record<string, string>> = Object.freeze({\n \"location-index\": \"locations-hub\",\n \"service-index\": \"services-hub\",\n});\n\n/** Path → kind for every literal catalogue entry; empty when the file is missing or unreadable. */\nexport function readRouteCatalogueKinds(projectRoot: string): ReadonlyMap<string, string> {\n const kinds = new Map<string, string>();\n const file = join(projectRoot, ROUTE_CATALOGUE_FILE);\n if (!existsSync(file)) return kinds;\n let reader: LiteralReader;\n try {\n reader = new LiteralReader(readFileSync(file, \"utf8\"), ROUTE_CATALOGUE_FILE);\n } catch {\n return kinds;\n }\n const argument = reader.callArguments(\"defineRouteCatalog\")[0] ?? reader.constant(\"routes\");\n const value: Literal | undefined = argument ? reader.evaluate(argument) : undefined;\n if (!Array.isArray(value)) return kinds;\n for (const entry of value) {\n if (!isLiteralObject(entry)) continue;\n const { kind, path } = entry;\n if (typeof kind === \"string\" && typeof path === \"string\" && path.startsWith(\"/\")) kinds.set(normalisePath(path), kind);\n }\n return kinds;\n}\n\n/**\n * A page's type for L6: its section manifest's family first, then the hub family its catalogue kind gives it, then\n * the family the estate inferred (the estate plan's paths, the sitemap, the route or the stem).\n */\nexport function linkingPageType(\n page: { readonly path: string; readonly family: string | undefined; readonly familySource: string | undefined },\n catalogueKinds: ReadonlyMap<string, string>,\n): string | undefined {\n if (page.familySource === \"manifest\") return page.family;\n return CATALOGUE_HUB_FAMILIES[catalogueKinds.get(page.path) ?? \"\"] ?? page.family;\n}\n"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ESTATE_PLAN_SNAPSHOT_FILE } from "../../../estate-snapshot.js";
|
|
1
2
|
import { type SiteEstatePlan } from "@brandlab-digital/site-core/strategy";
|
|
2
3
|
export declare const FULL_STRATEGY_FILE = "src/data/strategy.ts";
|
|
4
|
+
export { ESTATE_PLAN_SNAPSHOT_FILE };
|
|
3
5
|
/** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */
|
|
4
6
|
export declare const WAVE_MIN_GAP_DAYS = 14;
|
|
5
7
|
export declare const WAVE_MAX_GAP_DAYS = 28;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/strategy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/strategy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,yBAAyB,EAAwE,MAAM,6BAA6B,CAAC;AAC9I,OAAO,EAAsB,KAAK,cAAc,EAAwB,MAAM,sCAAsC,CAAC;AAGrH,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAGrC,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,CAgHjF;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,9 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
+
import { ESTATE_PLAN_SNAPSHOT_FILE, ESTATE_PLAN_SNAPSHOT_KIND, canonicalEstatePlan, estatePlanSourceHash } from "../../../estate-snapshot.js";
|
|
9
10
|
import { estatePlanProblems } from "@brandlab-digital/site-core/strategy";
|
|
10
11
|
import { LiteralReader, isFullyResolved, isLiteralObject } from "../enquiry/literal.js";
|
|
11
12
|
export const FULL_STRATEGY_FILE = "src/data/strategy.ts";
|
|
13
|
+
export { ESTATE_PLAN_SNAPSHOT_FILE };
|
|
14
|
+
const SNAPSHOT_REGENERATE = "run the site's tests (writeEstatePlanSnapshot) to write it again, and commit it";
|
|
12
15
|
/** As site-core's SITE_WAVE_MIN_GAP_DAYS and SITE_WAVE_MAX_GAP_DAYS. */
|
|
13
16
|
export const WAVE_MIN_GAP_DAYS = 14;
|
|
14
17
|
export const WAVE_MAX_GAP_DAYS = 28;
|
|
@@ -60,21 +63,55 @@ export function readFullStrategy(projectRoot, today) {
|
|
|
60
63
|
phrases = { mainTrade: rawPhrases["mainTrade"], owners };
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
// The waves and the estate plan: the literal record, or the snapshot a site with a computed plan records.
|
|
64
67
|
const rawWaves = record?.["waves"];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
const rawEstate = record?.["estate"];
|
|
69
|
+
const wavesReadable = rawWaves === undefined ? Boolean(record) : Array.isArray(rawWaves) && isFullyResolved(rawWaves);
|
|
70
|
+
const estateReadable = rawEstate === undefined ? Boolean(record) : isLiteralObject(rawEstate) && isFullyResolved(rawEstate) && Array.isArray(rawEstate["indexedNow"]);
|
|
71
|
+
const snapshot = readEstateSnapshot(projectRoot);
|
|
72
|
+
let plan;
|
|
73
|
+
if (snapshot === null) {
|
|
74
|
+
plan = { rawWaves, rawEstate, source: FULL_STRATEGY_FILE };
|
|
75
|
+
const hint = `write it as a literal, or record it with writeEstatePlanSnapshot from @brandlab-digital/conformance/estate-snapshot (${ESTATE_PLAN_SNAPSHOT_FILE})`;
|
|
76
|
+
if (rawWaves !== undefined && !wavesReadable)
|
|
77
|
+
problems.waves.push(`The waves in ${FULL_STRATEGY_FILE} are not a plain list, so the release schedule cannot be read; ${hint}.`);
|
|
78
|
+
if (rawEstate !== undefined && !estateReadable) {
|
|
79
|
+
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; ${hint}.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if ("problem" in snapshot) {
|
|
83
|
+
plan = { rawWaves: undefined, rawEstate: undefined, source: ESTATE_PLAN_SNAPSHOT_FILE };
|
|
84
|
+
problems.waves.push(snapshot.problem);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
plan = { rawWaves: snapshot.value["waves"], rawEstate: snapshot.value["estate"], source: ESTATE_PLAN_SNAPSHOT_FILE };
|
|
88
|
+
problems.waves.push(...snapshot.stale);
|
|
89
|
+
if (record && wavesReadable && estateReadable) {
|
|
90
|
+
const literal = canonicalEstatePlan({
|
|
91
|
+
...(rawWaves !== undefined ? { waves: rawWaves } : {}),
|
|
92
|
+
...(rawEstate !== undefined ? { estate: rawEstate } : {}),
|
|
93
|
+
});
|
|
94
|
+
const recorded = { ...(snapshot.value["waves"] !== undefined ? { waves: snapshot.value["waves"] } : {}), ...(snapshot.value["estate"] !== undefined ? { estate: snapshot.value["estate"] } : {}) };
|
|
95
|
+
if (JSON.stringify(literal) !== JSON.stringify(recorded)) {
|
|
96
|
+
problems.waves.push(`${ESTATE_PLAN_SNAPSHOT_FILE} does not match the waves and estate plan in ${FULL_STRATEGY_FILE}, so it is stale; ${SNAPSHOT_REGENERATE}.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const waves = [];
|
|
101
|
+
if (plan.rawWaves !== undefined) {
|
|
102
|
+
if (!Array.isArray(plan.rawWaves) || !isFullyResolved(plan.rawWaves)) {
|
|
103
|
+
if (plan.source === ESTATE_PLAN_SNAPSHOT_FILE)
|
|
104
|
+
problems.waves.push(`The waves in ${ESTATE_PLAN_SNAPSHOT_FILE} are not a list; ${SNAPSHOT_REGENERATE}.`);
|
|
68
105
|
}
|
|
69
106
|
else {
|
|
70
|
-
for (const [index, wave] of rawWaves.entries()) {
|
|
107
|
+
for (const [index, wave] of plan.rawWaves.entries()) {
|
|
71
108
|
if (!isLiteralObject(wave) || typeof wave["releaseOn"] !== "string" || !Array.isArray(wave["routes"]) || !wave["routes"].every((route) => typeof route === "string")) {
|
|
72
|
-
problems.waves.push(`Wave ${index + 1} in ${
|
|
109
|
+
problems.waves.push(`Wave ${index + 1} in ${plan.source} needs a releaseOn date and a list of routes.`);
|
|
73
110
|
continue;
|
|
74
111
|
}
|
|
75
112
|
const kind = wave["kind"];
|
|
76
113
|
if (kind !== undefined && kind !== "release" && kind !== "return") {
|
|
77
|
-
problems.waves.push(`Wave ${index + 1} in ${
|
|
114
|
+
problems.waves.push(`Wave ${index + 1} in ${plan.source} has the kind "${String(kind)}"; a wave is a release or a return.`);
|
|
78
115
|
}
|
|
79
116
|
waves.push({ releaseOn: wave["releaseOn"], routes: wave["routes"], ...(kind === "return" || kind === "release" ? { kind } : {}) });
|
|
80
117
|
}
|
|
@@ -82,19 +119,20 @@ export function readFullStrategy(projectRoot, today) {
|
|
|
82
119
|
}
|
|
83
120
|
}
|
|
84
121
|
let estate = null;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
if (plan.rawEstate !== undefined) {
|
|
123
|
+
if (!isLiteralObject(plan.rawEstate) || !isFullyResolved(plan.rawEstate) || !Array.isArray(plan.rawEstate["indexedNow"])) {
|
|
124
|
+
if (plan.source === ESTATE_PLAN_SNAPSHOT_FILE)
|
|
125
|
+
problems.waves.push(`The estate plan in ${ESTATE_PLAN_SNAPSHOT_FILE} has no indexedNow list; ${SNAPSHOT_REGENERATE}.`);
|
|
89
126
|
}
|
|
90
127
|
else {
|
|
91
|
-
estate = rawEstate;
|
|
92
|
-
problems.waves.push(...estatePlanProblems(estate, waves, today).map((problem) => `${
|
|
128
|
+
estate = plan.rawEstate;
|
|
129
|
+
problems.waves.push(...estatePlanProblems(estate, waves, today).map((problem) => `${plan.source}: ${problem}`));
|
|
93
130
|
}
|
|
94
131
|
}
|
|
95
132
|
if (argument && !record) {
|
|
96
133
|
problems.phrases.push(`${FULL_STRATEGY_FILE} is not plain data, so phrase ownership cannot be read.`);
|
|
97
|
-
|
|
134
|
+
if (snapshot === null)
|
|
135
|
+
problems.waves.push(`${FULL_STRATEGY_FILE} is not plain data, so the release schedule cannot be read.`);
|
|
98
136
|
}
|
|
99
137
|
let trade;
|
|
100
138
|
const recordedOrders = new Map();
|
|
@@ -160,4 +198,38 @@ export function waveScheduleProblems(waves, today) {
|
|
|
160
198
|
}
|
|
161
199
|
return problems;
|
|
162
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* The estate plan snapshot (`writeEstatePlanSnapshot` in ./estate-snapshot, 3.15.0), or null when the site has none. `stale` lists
|
|
203
|
+
* why it no longer follows its sources: the files it hashed have changed since it was written.
|
|
204
|
+
*/
|
|
205
|
+
function readEstateSnapshot(projectRoot) {
|
|
206
|
+
const file = join(projectRoot, ESTATE_PLAN_SNAPSHOT_FILE);
|
|
207
|
+
if (!existsSync(file))
|
|
208
|
+
return null;
|
|
209
|
+
let value;
|
|
210
|
+
try {
|
|
211
|
+
value = JSON.parse(readFileSync(file, "utf8"));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE} is not valid JSON; ${SNAPSHOT_REGENERATE}.` };
|
|
215
|
+
}
|
|
216
|
+
const record = value;
|
|
217
|
+
const sources = isLiteralObject(record) ? record["sources"] : undefined;
|
|
218
|
+
if (!isLiteralObject(record) || record["kind"] !== ESTATE_PLAN_SNAPSHOT_KIND || !isLiteralObject(sources) || typeof sources["sha256"] !== "string"
|
|
219
|
+
|| !Array.isArray(sources["paths"]) || !sources["paths"].every((path) => typeof path === "string")) {
|
|
220
|
+
return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE} is not an estate plan snapshot (no kind "${ESTATE_PLAN_SNAPSHOT_KIND}" with its sources); ${SNAPSHOT_REGENERATE}.` };
|
|
221
|
+
}
|
|
222
|
+
const paths = sources["paths"];
|
|
223
|
+
let current;
|
|
224
|
+
try {
|
|
225
|
+
current = estatePlanSourceHash(projectRoot, paths);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE}: ${error instanceof Error ? error.message : String(error)}` };
|
|
229
|
+
}
|
|
230
|
+
const stale = current === sources["sha256"]
|
|
231
|
+
? []
|
|
232
|
+
: [`${ESTATE_PLAN_SNAPSHOT_FILE} is older than the files it was written from (${paths.join(", ")} changed since), so it may not match the plan; ${SNAPSHOT_REGENERATE}.`];
|
|
233
|
+
return { value: record, stale };
|
|
234
|
+
}
|
|
163
235
|
//# sourceMappingURL=strategy.js.map
|
|
@@ -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 { 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
|
+
{"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 { ESTATE_PLAN_SNAPSHOT_FILE, ESTATE_PLAN_SNAPSHOT_KIND, canonicalEstatePlan, estatePlanSourceHash } from \"../../../estate-snapshot.js\";\nimport { estatePlanProblems, type SiteEstatePlan, type SiteReleaseWave } 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\";\nexport { ESTATE_PLAN_SNAPSHOT_FILE };\nconst SNAPSHOT_REGENERATE = \"run the site's tests (writeEstatePlanSnapshot) to write it again, and commit it\";\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 // The waves and the estate plan: the literal record, or the snapshot a site with a computed plan records.\n const rawWaves = record?.[\"waves\"];\n const rawEstate = record?.[\"estate\"];\n const wavesReadable = rawWaves === undefined ? Boolean(record) : Array.isArray(rawWaves) && isFullyResolved(rawWaves);\n const estateReadable = rawEstate === undefined ? Boolean(record) : isLiteralObject(rawEstate) && isFullyResolved(rawEstate) && Array.isArray(rawEstate[\"indexedNow\"]);\n const snapshot = readEstateSnapshot(projectRoot);\n let plan: { rawWaves: Literal | undefined; rawEstate: Literal | undefined; source: string };\n if (snapshot === null) {\n plan = { rawWaves, rawEstate, source: FULL_STRATEGY_FILE };\n const hint = `write it as a literal, or record it with writeEstatePlanSnapshot from @brandlab-digital/conformance/estate-snapshot (${ESTATE_PLAN_SNAPSHOT_FILE})`;\n if (rawWaves !== undefined && !wavesReadable) problems.waves.push(`The waves in ${FULL_STRATEGY_FILE} are not a plain list, so the release schedule cannot be read; ${hint}.`);\n if (rawEstate !== undefined && !estateReadable) {\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; ${hint}.`);\n }\n } else if (\"problem\" in snapshot) {\n plan = { rawWaves: undefined, rawEstate: undefined, source: ESTATE_PLAN_SNAPSHOT_FILE };\n problems.waves.push(snapshot.problem);\n } else {\n plan = { rawWaves: snapshot.value[\"waves\"], rawEstate: snapshot.value[\"estate\"], source: ESTATE_PLAN_SNAPSHOT_FILE };\n problems.waves.push(...snapshot.stale);\n if (record && wavesReadable && estateReadable) {\n const literal = canonicalEstatePlan({\n ...(rawWaves !== undefined ? { waves: rawWaves as unknown as SiteReleaseWave[] } : {}),\n ...(rawEstate !== undefined ? { estate: rawEstate as unknown as SiteEstatePlan } : {}),\n });\n const recorded = { ...(snapshot.value[\"waves\"] !== undefined ? { waves: snapshot.value[\"waves\"] } : {}), ...(snapshot.value[\"estate\"] !== undefined ? { estate: snapshot.value[\"estate\"] } : {}) };\n if (JSON.stringify(literal) !== JSON.stringify(recorded)) {\n problems.waves.push(`${ESTATE_PLAN_SNAPSHOT_FILE} does not match the waves and estate plan in ${FULL_STRATEGY_FILE}, so it is stale; ${SNAPSHOT_REGENERATE}.`);\n }\n }\n }\n\n const waves: { releaseOn: string; routes: string[]; kind?: \"release\" | \"return\" }[] = [];\n if (plan.rawWaves !== undefined) {\n if (!Array.isArray(plan.rawWaves) || !isFullyResolved(plan.rawWaves)) {\n if (plan.source === ESTATE_PLAN_SNAPSHOT_FILE) problems.waves.push(`The waves in ${ESTATE_PLAN_SNAPSHOT_FILE} are not a list; ${SNAPSHOT_REGENERATE}.`);\n } else {\n for (const [index, wave] of plan.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 ${plan.source} 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 ${plan.source} 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 if (plan.rawEstate !== undefined) {\n if (!isLiteralObject(plan.rawEstate) || !isFullyResolved(plan.rawEstate) || !Array.isArray(plan.rawEstate[\"indexedNow\"])) {\n if (plan.source === ESTATE_PLAN_SNAPSHOT_FILE) problems.waves.push(`The estate plan in ${ESTATE_PLAN_SNAPSHOT_FILE} has no indexedNow list; ${SNAPSHOT_REGENERATE}.`);\n } else {\n estate = plan.rawEstate as unknown as SiteEstatePlan;\n problems.waves.push(...estatePlanProblems(estate, waves, today).map((problem) => `${plan.source}: ${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 if (snapshot === null) 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\n/**\n * The estate plan snapshot (`writeEstatePlanSnapshot` in ./estate-snapshot, 3.15.0), or null when the site has none. `stale` lists\n * why it no longer follows its sources: the files it hashed have changed since it was written.\n */\nfunction readEstateSnapshot(projectRoot: string): { value: { [key: string]: Literal }; stale: string[] } | { problem: string } | null {\n const file = join(projectRoot, ESTATE_PLAN_SNAPSHOT_FILE);\n if (!existsSync(file)) return null;\n let value: unknown;\n try {\n value = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE} is not valid JSON; ${SNAPSHOT_REGENERATE}.` };\n }\n const record = value as { [key: string]: Literal };\n const sources = isLiteralObject(record) ? record[\"sources\"] : undefined;\n if (!isLiteralObject(record) || record[\"kind\"] !== ESTATE_PLAN_SNAPSHOT_KIND || !isLiteralObject(sources) || typeof sources[\"sha256\"] !== \"string\"\n || !Array.isArray(sources[\"paths\"]) || !sources[\"paths\"].every((path) => typeof path === \"string\")) {\n return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE} is not an estate plan snapshot (no kind \"${ESTATE_PLAN_SNAPSHOT_KIND}\" with its sources); ${SNAPSHOT_REGENERATE}.` };\n }\n const paths = sources[\"paths\"] as string[];\n let current: string;\n try {\n current = estatePlanSourceHash(projectRoot, paths);\n } catch (error) {\n return { problem: `${ESTATE_PLAN_SNAPSHOT_FILE}: ${error instanceof Error ? error.message : String(error)}` };\n }\n const stale = current === sources[\"sha256\"]\n ? []\n : [`${ESTATE_PLAN_SNAPSHOT_FILE} is older than the files it was written from (${paths.join(\", \")} changed since), so it may not match the plan; ${SNAPSHOT_REGENERATE}.`];\n return { value: record, stale };\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", "eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs"];
|
|
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", "quote-checklist"];
|
|
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", "hero", "services"];
|
|
6
|
+
export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services", "projects", "gallery", "before-after", "licences", "emergency"];
|
|
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":"
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,0WAOnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,iLAItB,CAAC;AAEX,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"}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star
|
|
8
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
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.
|
|
10
|
+
* from its skin stylesheet) join the lists; from 3.15.0 the proof sections (projects, gallery, before-after,
|
|
11
|
+
* licences, emergency) and the quote checklist.
|
|
11
12
|
* - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an
|
|
12
13
|
* element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a
|
|
13
14
|
* component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the
|
|
@@ -25,9 +26,15 @@ export const K2_SKINNED_HOOKS = [
|
|
|
25
26
|
"quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table",
|
|
26
27
|
// 3.14.0 primitives
|
|
27
28
|
"eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs",
|
|
29
|
+
// 3.15.0
|
|
30
|
+
"quote-checklist",
|
|
28
31
|
];
|
|
29
32
|
/** `data-bl-section` ids of the sections the library renders and skins. */
|
|
30
|
-
export const K2_SKINNED_SECTIONS = [
|
|
33
|
+
export const K2_SKINNED_SECTIONS = [
|
|
34
|
+
"reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services",
|
|
35
|
+
// 3.15.0 proof sections
|
|
36
|
+
"projects", "gallery", "before-after", "licences", "emergency",
|
|
37
|
+
];
|
|
31
38
|
export const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;
|
|
32
39
|
function classesOf(element) {
|
|
33
40
|
return (attr(element, "class") ?? "").split(/\s+/).filter(Boolean);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the\n * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them\n * from its skin stylesheet) join the lists.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n // 3.14.0 primitives\n \"eyebrow\", \"card\", \"badge\", \"chip\", \"stat\", \"tick-list\", \"link-list\", \"media\", \"breadcrumbs\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\", \"hero\", \"services\"] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the\n * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them\n * from its skin stylesheet) join the lists; from 3.15.0 the proof sections (projects, gallery, before-after,\n * licences, emergency) and the quote checklist.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n // 3.14.0 primitives\n \"eyebrow\", \"card\", \"badge\", \"chip\", \"stat\", \"tick-list\", \"link-list\", \"media\", \"breadcrumbs\",\n // 3.15.0\n \"quote-checklist\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\n \"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\", \"hero\", \"services\",\n // 3.15.0 proof sections\n \"projects\", \"gallery\", \"before-after\", \"licences\", \"emergency\",\n] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"t6-media-entries.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/t6-media-entries.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"t6-media-entries.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/t6-media-entries.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAO1F,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD,eAAO,MAAM,cAAc,kDAAmD,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,uFAAuF;IACvF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,8GAA8G;AAC9G,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,GAAG,cAAc,GAAG,IAAI,CAaxG;AAyBD,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA8C9F"}
|