@brandlab-digital/conformance 3.17.1 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,19 +23,19 @@ export const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze({
23
23
  "kind": "brandlab-runtime-release-contract",
24
24
  "packages": {
25
25
  "@brandlab-digital/conformance": {
26
- "version": "3.17.1"
26
+ "version": "3.18.0"
27
27
  },
28
28
  "@brandlab-digital/eslint-plugin": {
29
- "version": "3.17.1"
29
+ "version": "3.18.0"
30
30
  },
31
31
  "@brandlab-digital/site-core": {
32
- "version": "3.17.1"
32
+ "version": "3.18.0"
33
33
  },
34
34
  "@brandlab-digital/site-next": {
35
- "version": "3.17.1"
35
+ "version": "3.18.0"
36
36
  }
37
37
  },
38
- "releaseVersion": "3.17.1",
38
+ "releaseVersion": "3.18.0",
39
39
  "runtime": {
40
40
  "bundler": "turbopack",
41
41
  "cacheComponents": false,
@@ -1 +1 @@
1
- {"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,QAAQ;SACpB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;KACF;IACD,gBAAgB,EAAE,QAAQ;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.17.1\"\n },\n \"@brandlab-digital/eslint-plugin\": {\n \"version\": \"3.17.1\"\n },\n \"@brandlab-digital/site-core\": {\n \"version\": \"3.17.1\"\n },\n \"@brandlab-digital/site-next\": {\n \"version\": \"3.17.1\"\n }\n },\n \"releaseVersion\": \"3.17.1\",\n \"runtime\": {\n \"bundler\": \"turbopack\",\n \"cacheComponents\": false,\n \"next\": \"16.4.2\",\n \"nextVersionPolicy\": \"exact\",\n \"node\": \"24.x\",\n \"packageManager\": \"npm@11.14.1\",\n \"react\": \"19.2.8\",\n \"reactDom\": \"19.2.8\",\n \"router\": \"app\",\n \"sharp\": \"0.35.5\",\n \"typescript\": \"6.0.3\"\n },\n \"schemaVersion\": \"brandlab.runtime-release-contract.v1\"\n},\n) as unknown as BrandlabRuntimeReleaseContract;\n\nvalidateBrandlabRuntimeReleaseContract(BRANDLAB_CURRENT_RUNTIME_RELEASE);\n"]}
1
+ {"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,QAAQ;SACpB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;KACF;IACD,gBAAgB,EAAE,QAAQ;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.18.0\"\n },\n \"@brandlab-digital/eslint-plugin\": {\n \"version\": \"3.18.0\"\n },\n \"@brandlab-digital/site-core\": {\n \"version\": \"3.18.0\"\n },\n \"@brandlab-digital/site-next\": {\n \"version\": \"3.18.0\"\n }\n },\n \"releaseVersion\": \"3.18.0\",\n \"runtime\": {\n \"bundler\": \"turbopack\",\n \"cacheComponents\": false,\n \"next\": \"16.4.2\",\n \"nextVersionPolicy\": \"exact\",\n \"node\": \"24.x\",\n \"packageManager\": \"npm@11.14.1\",\n \"react\": \"19.2.8\",\n \"reactDom\": \"19.2.8\",\n \"router\": \"app\",\n \"sharp\": \"0.35.5\",\n \"typescript\": \"6.0.3\"\n },\n \"schemaVersion\": \"brandlab.runtime-release-contract.v1\"\n},\n) as unknown as BrandlabRuntimeReleaseContract;\n\nvalidateBrandlabRuntimeReleaseContract(BRANDLAB_CURRENT_RUNTIME_RELEASE);\n"]}
@@ -16,7 +16,10 @@ import { type FullContext } from "./common.js";
16
16
  import type { EstatePage, ManifestData } from "./estate.js";
17
17
  /** The families that carry a manifest (site-core BRANDLAB_PAGE_FAMILIES without the contact page). */
18
18
  export declare const K12_FAMILIES: readonly ["home", "service", "service-in-suburb", "region", "suburb", "cost-guide", "services-hub", "locations-hub", "projects-hub", "project", "gallery", "reviews", "about", "faqs", "guides-hub", "guide", "council", "emergency"];
19
- /** As site-core's isDataBackedSkipReason: the section's own data is absent, or its content is in the hero checklist. */
19
+ /**
20
+ * As site-core's isDataBackedSkipReason: the section's own data is absent, its content is in the hero checklist, or
21
+ * (3.18.0) the nearby suburbs are the area map's place list.
22
+ */
20
23
  export declare function isDataBackedReason(reason: string): boolean;
21
24
  export declare function manifestProblems(page: EstatePage, manifest: ManifestData, context: FullContext): string[];
22
25
  export declare function checkK12(context: FullContext): SiteSystemFinding[];
@@ -1 +1 @@
1
- {"version":3,"file":"k12-manifests.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-manifests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAyD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D,sGAAsG;AACtG,eAAO,MAAM,YAAY,uOAmBf,CAAC;AAEX,wHAAwH;AACxH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1D;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE,CAoEzG;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CAoClE"}
1
+ {"version":3,"file":"k12-manifests.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-manifests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAyD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D,sGAAsG;AACtG,eAAO,MAAM,YAAY,uOAmBf,CAAC;AAEX;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1D;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE,CAyEzG;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CAoClE"}
@@ -20,9 +20,12 @@ export const K12_FAMILIES = [
20
20
  "council",
21
21
  "emergency",
22
22
  ];
23
- /** As site-core's isDataBackedSkipReason: the section's own data is absent, or its content is in the hero checklist. */
23
+ /**
24
+ * As site-core's isDataBackedSkipReason: the section's own data is absent, its content is in the hero checklist, or
25
+ * (3.18.0) the nearby suburbs are the area map's place list.
26
+ */
24
27
  export function isDataBackedReason(reason) {
25
- return reason === "merge-into-hero-checklist" || /^no-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(reason);
28
+ return reason === "merge-into-hero-checklist" || reason === "merge-into-area-map" || /^no-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(reason);
26
29
  }
27
30
  function sameList(left, right) {
28
31
  return left.length === right.length && left.every((item, index) => item === right[index]);
@@ -82,6 +85,13 @@ export function manifestProblems(page, manifest, context) {
82
85
  problems.push(`${where} renders the sections ${page.sections.join(", ") || "(none)"}, but its manifest lists ${listed.join(", ") || "(none)"}.`);
83
86
  }
84
87
  const renderedIds = new Set(manifest.rendered.map((entry) => entry.id));
88
+ // merge-into-area-map (3.18.0): only the nearby suburbs merge, and only into an area map the page renders.
89
+ for (const skip of manifest.skipped.filter((item) => item.reason === "merge-into-area-map")) {
90
+ if (skip.id !== "nearby-areas")
91
+ problems.push(`${where} gives "${skip.id}" the reason merge-into-area-map; only nearby-areas merges into the area map.`);
92
+ else if (!renderedIds.has("area-map"))
93
+ problems.push(`${where} merges its nearby suburbs into the area map, but renders no area map.`);
94
+ }
85
95
  for (const entry of manifest.planned) {
86
96
  if (entry.requirement !== "R" && entry.requirement !== "C")
87
97
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"k12-manifests.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-manifests.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K12 on every family (Site System v2 §3.3, §13.2): each page of a family carries one section manifest\n * (`<script type=\"application/json\" data-bl-manifest>`, written by SectionSequence). Every required section renders\n * or is skipped with a reason drawn from its data (`no-…`), never the sequencer's `not-provided`, `cap` or\n * `duplicate`; every conditional section that does not render says its data is absent; the planned order is the\n * family's canonical one or the alternative recorded in strategy.ts; and the page renders exactly the sections the\n * manifest lists, in that order. A page over its cap (`cap-exceeded`) is a warning, not a failure. The contact page\n * has its own check (K12-contact).\n *\n * 3.16.0: signature sections take the place the site declares, so they are left out of the order walk (an `after`\n * must name a planned section; K13 checks the rest). An optional or recommended section with data that drops for the\n * cap is a warning, so a clean gate still shows when the site's content was cut.\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, listOf, notApplicable, outcome, plural, warning, type FullContext } from \"./common.js\";\nimport type { EstatePage, ManifestData } from \"./estate.js\";\n\n/** The families that carry a manifest (site-core BRANDLAB_PAGE_FAMILIES without the contact page). */\nexport const K12_FAMILIES = [\n \"home\",\n \"service\",\n \"service-in-suburb\",\n \"region\",\n \"suburb\",\n \"cost-guide\",\n \"services-hub\",\n \"locations-hub\",\n \"projects-hub\",\n \"project\",\n \"gallery\",\n \"reviews\",\n \"about\",\n \"faqs\",\n \"guides-hub\",\n \"guide\",\n \"council\",\n \"emergency\",\n] as const;\n\n/** As site-core's isDataBackedSkipReason: the section's own data is absent, or its content is in the hero checklist. */\nexport function isDataBackedReason(reason: string): boolean {\n return reason === \"merge-into-hero-checklist\" || /^no-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(reason);\n}\n\nfunction sameList(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((item, index) => item === right[index]);\n}\n\nexport function manifestProblems(page: EstatePage, manifest: ManifestData, context: FullContext): string[] {\n const problems: string[] = [];\n const where = page.path;\n const planned = manifest.planned.map((entry) => entry.id);\n\n if (!(K12_FAMILIES as readonly string[]).includes(manifest.family)) {\n return [`The manifest on ${where} names \"${manifest.family}\", which is not a page family that carries one.`];\n }\n if (manifest.order === \"recorded\") {\n const recorded = context.strategy.recordedOrders.get(manifest.family);\n if (!recorded) problems.push(`The manifest on ${where} says its order is recorded, but strategy.ts records no order for ${manifest.family} pages.`);\n else if (!sameList(planned, recorded)) problems.push(`The sections planned on ${where} (${planned.join(\", \")}) are not the order recorded for ${manifest.family} pages (${recorded.join(\", \")}).`);\n }\n const canonical = typeof context.defaultOrder === \"string\" ? undefined : context.defaultOrder(manifest.family, context.strategy.trade);\n if (canonical) {\n const ids = canonical.entries.map((entry) => entry.id);\n if (manifest.order !== \"recorded\" && !sameList(planned, ids)) {\n problems.push(`The sections planned on ${where} (${planned.join(\", \")}) are not the canonical order for ${manifest.family} pages (${ids.join(\", \")}).`);\n }\n // Each planned section keeps the requirement the family gives it, whatever the order.\n const requirementOf = new Map(canonical.entries.map((entry) => [entry.id, entry.requirement]));\n for (const entry of manifest.planned) {\n const expected = requirementOf.get(entry.id);\n if (expected !== undefined && expected !== entry.requirement) {\n problems.push(`The manifest on ${where} marks \"${entry.id}\" as ${entry.requirement}; on ${manifest.family} pages it is ${expected}.`);\n }\n }\n if (manifest.cap !== canonical.cap) problems.push(`The manifest on ${where} gives a cap of ${manifest.cap}; ${manifest.family} pages have a cap of ${canonical.cap}.`);\n }\n if (manifest.flags.includes(\"cap-exceeded\") !== manifest.counted > manifest.cap) {\n problems.push(`The manifest on ${where} counts ${manifest.counted} sections against a cap of ${manifest.cap} but ${manifest.flags.includes(\"cap-exceeded\") ? \"flags\" : \"does not flag\"} cap-exceeded.`);\n }\n\n // What renders follows the plan, and the page shows exactly what the manifest lists.\n let position = -1;\n for (const entry of manifest.rendered) {\n if (entry.id === \"signature\") {\n if (entry.after !== undefined && !planned.includes(entry.after)) {\n problems.push(`The manifest on ${where} places the signature \"${entry.signature ?? \"\"}\" after \"${entry.after}\", which is not a planned section.`);\n }\n continue;\n }\n const index = planned.indexOf(entry.id, position + 1);\n if (index === -1) {\n problems.push(`The manifest on ${where} lists \"${entry.id}\" as rendered out of its planned place.`);\n break;\n }\n position = index;\n }\n const listed = manifest.rendered.map((entry) => entry.section);\n if (!sameList(page.sections, listed)) {\n problems.push(`${where} renders the sections ${page.sections.join(\", \") || \"(none)\"}, but its manifest lists ${listed.join(\", \") || \"(none)\"}.`);\n }\n\n const renderedIds = new Set(manifest.rendered.map((entry) => entry.id));\n for (const entry of manifest.planned) {\n if (entry.requirement !== \"R\" && entry.requirement !== \"C\") continue;\n if (renderedIds.has(entry.id)) continue;\n const reasons = manifest.skipped.filter((skip) => skip.id === entry.id).map((skip) => skip.reason);\n if (reasons.some(isDataBackedReason)) continue;\n const reason = reasons[0] ?? \"no reason recorded\";\n problems.push(\n entry.requirement === \"R\"\n ? `${where} skips its required \"${entry.id}\" section without a reason drawn from its data (${reason}).`\n : `${where} leaves out its conditional \"${entry.id}\" section without saying its data is absent (${reason}).`,\n );\n }\n return problems;\n}\n\nexport function checkK12(context: FullContext): SiteSystemFinding[] {\n const failures: SiteSystemFinding[] = [];\n let checked = 0;\n if (typeof context.defaultOrder === \"string\") {\n failures.push(fail(\"K12\", undefined, `The canonical section orders could not be loaded (${context.defaultOrder}), so default orders were not compared.`));\n }\n for (const problem of context.strategy.problems.orders) failures.push(fail(\"K12\", \"src/data/strategy.ts\", problem));\n for (const page of context.estate.pages) {\n if (!(K12_FAMILIES as readonly string[]).includes(page.family ?? \"\")) continue;\n checked += 1;\n const state = page.manifest;\n if (state.kind === \"none\") {\n failures.push(fail(\"K12\", page.path, `${page.path} is a ${page.family} page with no section manifest; its sections go through SectionSequence.`));\n continue;\n }\n if (state.kind === \"many\") {\n failures.push(fail(\"K12\", page.path, `${page.path} carries ${state.count} section manifests; a page has one.`));\n continue;\n }\n if (state.kind === \"invalid\") {\n failures.push(fail(\"K12\", page.path, `The section manifest on ${page.path} ${state.reason}.`));\n continue;\n }\n for (const problem of manifestProblems(page, state.manifest, context)) failures.push(fail(\"K12\", page.path, problem));\n for (const skip of state.manifest.skipped) {\n if (skip.reason === \"cap\" && (skip.requirement === \"O\" || skip.requirement === \"Rec\")) {\n failures.push(warning(\"K12\", page.path, `${page.path} drops its ${skip.requirement === \"O\" ? \"optional\" : \"recommended\"} \"${skip.section}\" section for the cap of ${state.manifest.cap}, although its data is there.`));\n }\n }\n if (state.manifest.flags.includes(\"cap-exceeded\")) {\n failures.push(warning(\"K12\", page.path, `${page.path} renders ${state.manifest.counted} sections against a cap of ${state.manifest.cap}, because its required and conditional sections all have data.`));\n }\n }\n if (checked === 0) return [...failures, notApplicable(\"K12\", \"No page of a family that carries a section manifest was found.\")];\n const families = [...new Set(context.estate.pages.flatMap((page) => ((K12_FAMILIES as readonly string[]).includes(page.family ?? \"\") ? [page.family!] : [])))];\n return outcome(\"K12\", failures, `${plural(checked, \"page\")} across ${listOf(families)} carry a section manifest with every required and conditional section accounted for, in the canonical or recorded order.`);\n}\n"]}
1
+ {"version":3,"file":"k12-manifests.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k12-manifests.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K12 on every family (Site System v2 §3.3, §13.2): each page of a family carries one section manifest\n * (`<script type=\"application/json\" data-bl-manifest>`, written by SectionSequence). Every required section renders\n * or is skipped with a reason drawn from its data (`no-…`), never the sequencer's `not-provided`, `cap` or\n * `duplicate`; every conditional section that does not render says its data is absent; the planned order is the\n * family's canonical one or the alternative recorded in strategy.ts; and the page renders exactly the sections the\n * manifest lists, in that order. A page over its cap (`cap-exceeded`) is a warning, not a failure. The contact page\n * has its own check (K12-contact).\n *\n * 3.16.0: signature sections take the place the site declares, so they are left out of the order walk (an `after`\n * must name a planned section; K13 checks the rest). An optional or recommended section with data that drops for the\n * cap is a warning, so a clean gate still shows when the site's content was cut.\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, listOf, notApplicable, outcome, plural, warning, type FullContext } from \"./common.js\";\nimport type { EstatePage, ManifestData } from \"./estate.js\";\n\n/** The families that carry a manifest (site-core BRANDLAB_PAGE_FAMILIES without the contact page). */\nexport const K12_FAMILIES = [\n \"home\",\n \"service\",\n \"service-in-suburb\",\n \"region\",\n \"suburb\",\n \"cost-guide\",\n \"services-hub\",\n \"locations-hub\",\n \"projects-hub\",\n \"project\",\n \"gallery\",\n \"reviews\",\n \"about\",\n \"faqs\",\n \"guides-hub\",\n \"guide\",\n \"council\",\n \"emergency\",\n] as const;\n\n/**\n * As site-core's isDataBackedSkipReason: the section's own data is absent, its content is in the hero checklist, or\n * (3.18.0) the nearby suburbs are the area map's place list.\n */\nexport function isDataBackedReason(reason: string): boolean {\n return reason === \"merge-into-hero-checklist\" || reason === \"merge-into-area-map\" || /^no-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(reason);\n}\n\nfunction sameList(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((item, index) => item === right[index]);\n}\n\nexport function manifestProblems(page: EstatePage, manifest: ManifestData, context: FullContext): string[] {\n const problems: string[] = [];\n const where = page.path;\n const planned = manifest.planned.map((entry) => entry.id);\n\n if (!(K12_FAMILIES as readonly string[]).includes(manifest.family)) {\n return [`The manifest on ${where} names \"${manifest.family}\", which is not a page family that carries one.`];\n }\n if (manifest.order === \"recorded\") {\n const recorded = context.strategy.recordedOrders.get(manifest.family);\n if (!recorded) problems.push(`The manifest on ${where} says its order is recorded, but strategy.ts records no order for ${manifest.family} pages.`);\n else if (!sameList(planned, recorded)) problems.push(`The sections planned on ${where} (${planned.join(\", \")}) are not the order recorded for ${manifest.family} pages (${recorded.join(\", \")}).`);\n }\n const canonical = typeof context.defaultOrder === \"string\" ? undefined : context.defaultOrder(manifest.family, context.strategy.trade);\n if (canonical) {\n const ids = canonical.entries.map((entry) => entry.id);\n if (manifest.order !== \"recorded\" && !sameList(planned, ids)) {\n problems.push(`The sections planned on ${where} (${planned.join(\", \")}) are not the canonical order for ${manifest.family} pages (${ids.join(\", \")}).`);\n }\n // Each planned section keeps the requirement the family gives it, whatever the order.\n const requirementOf = new Map(canonical.entries.map((entry) => [entry.id, entry.requirement]));\n for (const entry of manifest.planned) {\n const expected = requirementOf.get(entry.id);\n if (expected !== undefined && expected !== entry.requirement) {\n problems.push(`The manifest on ${where} marks \"${entry.id}\" as ${entry.requirement}; on ${manifest.family} pages it is ${expected}.`);\n }\n }\n if (manifest.cap !== canonical.cap) problems.push(`The manifest on ${where} gives a cap of ${manifest.cap}; ${manifest.family} pages have a cap of ${canonical.cap}.`);\n }\n if (manifest.flags.includes(\"cap-exceeded\") !== manifest.counted > manifest.cap) {\n problems.push(`The manifest on ${where} counts ${manifest.counted} sections against a cap of ${manifest.cap} but ${manifest.flags.includes(\"cap-exceeded\") ? \"flags\" : \"does not flag\"} cap-exceeded.`);\n }\n\n // What renders follows the plan, and the page shows exactly what the manifest lists.\n let position = -1;\n for (const entry of manifest.rendered) {\n if (entry.id === \"signature\") {\n if (entry.after !== undefined && !planned.includes(entry.after)) {\n problems.push(`The manifest on ${where} places the signature \"${entry.signature ?? \"\"}\" after \"${entry.after}\", which is not a planned section.`);\n }\n continue;\n }\n const index = planned.indexOf(entry.id, position + 1);\n if (index === -1) {\n problems.push(`The manifest on ${where} lists \"${entry.id}\" as rendered out of its planned place.`);\n break;\n }\n position = index;\n }\n const listed = manifest.rendered.map((entry) => entry.section);\n if (!sameList(page.sections, listed)) {\n problems.push(`${where} renders the sections ${page.sections.join(\", \") || \"(none)\"}, but its manifest lists ${listed.join(\", \") || \"(none)\"}.`);\n }\n\n const renderedIds = new Set(manifest.rendered.map((entry) => entry.id));\n // merge-into-area-map (3.18.0): only the nearby suburbs merge, and only into an area map the page renders.\n for (const skip of manifest.skipped.filter((item) => item.reason === \"merge-into-area-map\")) {\n if (skip.id !== \"nearby-areas\") problems.push(`${where} gives \"${skip.id}\" the reason merge-into-area-map; only nearby-areas merges into the area map.`);\n else if (!renderedIds.has(\"area-map\")) problems.push(`${where} merges its nearby suburbs into the area map, but renders no area map.`);\n }\n for (const entry of manifest.planned) {\n if (entry.requirement !== \"R\" && entry.requirement !== \"C\") continue;\n if (renderedIds.has(entry.id)) continue;\n const reasons = manifest.skipped.filter((skip) => skip.id === entry.id).map((skip) => skip.reason);\n if (reasons.some(isDataBackedReason)) continue;\n const reason = reasons[0] ?? \"no reason recorded\";\n problems.push(\n entry.requirement === \"R\"\n ? `${where} skips its required \"${entry.id}\" section without a reason drawn from its data (${reason}).`\n : `${where} leaves out its conditional \"${entry.id}\" section without saying its data is absent (${reason}).`,\n );\n }\n return problems;\n}\n\nexport function checkK12(context: FullContext): SiteSystemFinding[] {\n const failures: SiteSystemFinding[] = [];\n let checked = 0;\n if (typeof context.defaultOrder === \"string\") {\n failures.push(fail(\"K12\", undefined, `The canonical section orders could not be loaded (${context.defaultOrder}), so default orders were not compared.`));\n }\n for (const problem of context.strategy.problems.orders) failures.push(fail(\"K12\", \"src/data/strategy.ts\", problem));\n for (const page of context.estate.pages) {\n if (!(K12_FAMILIES as readonly string[]).includes(page.family ?? \"\")) continue;\n checked += 1;\n const state = page.manifest;\n if (state.kind === \"none\") {\n failures.push(fail(\"K12\", page.path, `${page.path} is a ${page.family} page with no section manifest; its sections go through SectionSequence.`));\n continue;\n }\n if (state.kind === \"many\") {\n failures.push(fail(\"K12\", page.path, `${page.path} carries ${state.count} section manifests; a page has one.`));\n continue;\n }\n if (state.kind === \"invalid\") {\n failures.push(fail(\"K12\", page.path, `The section manifest on ${page.path} ${state.reason}.`));\n continue;\n }\n for (const problem of manifestProblems(page, state.manifest, context)) failures.push(fail(\"K12\", page.path, problem));\n for (const skip of state.manifest.skipped) {\n if (skip.reason === \"cap\" && (skip.requirement === \"O\" || skip.requirement === \"Rec\")) {\n failures.push(warning(\"K12\", page.path, `${page.path} drops its ${skip.requirement === \"O\" ? \"optional\" : \"recommended\"} \"${skip.section}\" section for the cap of ${state.manifest.cap}, although its data is there.`));\n }\n }\n if (state.manifest.flags.includes(\"cap-exceeded\")) {\n failures.push(warning(\"K12\", page.path, `${page.path} renders ${state.manifest.counted} sections against a cap of ${state.manifest.cap}, because its required and conditional sections all have data.`));\n }\n }\n if (checked === 0) return [...failures, notApplicable(\"K12\", \"No page of a family that carries a section manifest was found.\")];\n const families = [...new Set(context.estate.pages.flatMap((page) => ((K12_FAMILIES as readonly string[]).includes(page.family ?? \"\") ? [page.family!] : [])))];\n return outcome(\"K12\", failures, `${plural(checked, \"page\")} across ${listOf(families)} carry a section manifest with every required and conditional section accounted for, in the canonical or recorded order.`);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/budgets.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACqC,KAAK,gBAAgB,EAAE,KAAK,YAAY,EACnF,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AAEH,iHAAiH;AACjH,eAAO,MAAM,iCAAiC,QAAa,CAAC;AAC5D,yFAAyF;AACzF,eAAO,MAAM,iCAAiC,QAAa,CAAC;AAC5D,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAC5C,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAClD,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AA6CnE,UAAU,OAAO;IACf,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,GAAG,IAAI,CAaxD;AAeD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAUlE;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,gBA+B5B,CAAC;AA4EF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,iCAAiC;AACjC,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqFlH"}
1
+ {"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/budgets.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACqC,KAAK,gBAAgB,EAAE,KAAK,YAAY,EACnF,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AAEH,iHAAiH;AACjH,eAAO,MAAM,iCAAiC,QAAa,CAAC;AAC5D,yFAAyF;AACzF,eAAO,MAAM,iCAAiC,QAAa,CAAC;AAC5D,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAC5C,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAClD,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AA6CnE,UAAU,OAAO;IACf,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,GAAG,IAAI,CAaxD;AAeD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAUlE;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,gBAgC5B,CAAC;AAgFF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,iCAAiC;AACjC,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqFlH"}
@@ -125,7 +125,8 @@ export const readBuiltAsset = async (url, { projectRoot, baseUrl }) => {
125
125
  if (!given || !/^https?:$/.test(url.protocol))
126
126
  return undefined;
127
127
  try {
128
- const response = await fetch(url, { signal: AbortSignal.timeout(15_000) });
128
+ // A browser asks for WebP, and Next's optimiser answers in the format asked for (3.18.0).
129
+ const response = await fetch(url, { headers: { accept: "image/webp,image/*;q=0.8" }, signal: AbortSignal.timeout(15_000) });
129
130
  return response.ok ? new Uint8Array(await response.arrayBuffer()) : undefined;
130
131
  }
131
132
  catch {
@@ -182,15 +183,18 @@ function srcsetCandidates(srcset) {
182
183
  return srcset.split(",").map((part) => part.trim().split(/\s+/)).filter((parts) => parts[0])
183
184
  .map(([url, descriptor]) => ({ url: url, width: descriptor?.endsWith("w") ? Number.parseInt(descriptor, 10) : descriptor?.endsWith("x") ? Number.parseFloat(descriptor) * 1000 : 0 }));
184
185
  }
186
+ /** Below this rendered width an image is an avatar, icon or badge, never the hero's photo (3.18.0). */
187
+ const B1_HERO_MIN_IMAGE_PX = 160;
185
188
  /** The image the hero shows on a wide screen: the smallest candidate at least 1440 px wide, else the widest. */
186
189
  function heroImage(page) {
187
190
  const hero = page.hero;
188
191
  if (!hero)
189
192
  return null;
190
- const images = findAll(hero, (element) => element.tag === "img" && !page.headers.some((header) => isWithin(element, header)) && element.attrs["data-bl"] !== "logo");
193
+ const width = (image) => Number.parseInt(image.attrs.width ?? "0", 10) || 0;
194
+ const images = findAll(hero, (element) => element.tag === "img" && !page.headers.some((header) => isWithin(element, header)) && element.attrs["data-bl"] !== "logo")
195
+ .filter((image) => image.attrs.fetchpriority === "high" || width(image) === 0 || width(image) >= B1_HERO_MIN_IMAGE_PX);
191
196
  if (images.length === 0)
192
197
  return null;
193
- const width = (image) => Number.parseInt(image.attrs.width ?? "0", 10) || 0;
194
198
  const element = images.find((image) => image.attrs.fetchpriority === "high") ?? images.reduce((best, image) => (width(image) > width(best) ? image : best), images[0]);
195
199
  const candidates = srcsetCandidates(element.attrs.srcset ?? "");
196
200
  const wide = candidates.filter((candidate) => candidate.width >= B1_HERO_VIEWPORT_PX).sort((left, right) => left.width - right.width)[0]
@@ -1 +1 @@
1
- {"version":3,"file":"budgets.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/budgets.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport { gzipSync } from \"node:zlib\";\nimport { descendants, findAll, isWithin, type HtmlElement } from \"./html.js\";\nimport type { ShellPage } from \"./page.js\";\nimport {\n failFinding, passFinding, warningFinding, type ShellAssetReader, type ShellFinding,\n} from \"./types.js\";\n\n/**\n * B1: lab budgets read from the built page HTML (spec 5.8): JavaScript, third-party requests, the hero image and the\n * HTML itself, with no chat widget, pixel, heatmap or tag manager unless the project records the decision. Script and\n * image sizes come from the project's build output, then the base URL; a size that can't be read is left out of the\n * total and said so, never guessed.\n */\n\n/** First-party JavaScript per page, gzip: a warning from here. Starting value, calibrated in the R1 canaries. */\nexport const B1_FIRST_PARTY_JS_GZIP_WARN_BYTES = 180 * 1024;\n/** First-party JavaScript per page, gzip: the spec's starting budget; over it blocks. */\nexport const B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES = 250 * 1024;\nexport const B1_HTML_MAX_BYTES = 500 * 1024;\nexport const B1_HERO_IMAGE_MAX_BYTES = 200 * 1024;\n/** The viewport width the hero image is judged at. */\nexport const B1_HERO_VIEWPORT_PX = 1440;\n\ntype ThirdPartyKind = \"chat\" | \"pixel\" | \"heatmap\" | \"tag-manager\";\n\ninterface Category {\n kind: ThirdPartyKind;\n label: string;\n hosts: readonly string[];\n}\n\nconst CATEGORIES: readonly Category[] = [\n { kind: \"chat\", label: \"a chat widget\", hosts: [\n \"widget.intercom.io\", \"js.intercomcdn.com\", \"intercom.io\", \"tawk.to\", \"crisp.chat\", \"livechatinc.com\", \"tidio.co\",\n \"driftt.com\", \"drift.com\", \"usemessages.com\", \"zdassets.com\", \"freshchat.com\", \"olark.com\", \"smartsuppchat.com\",\n \"chatra.io\", \"jivosite.com\", \"manychat.com\", \"chatwoot.com\",\n ] },\n { kind: \"pixel\", label: \"an advertising pixel\", hosts: [\n \"connect.facebook.net\", \"snap.licdn.com\", \"px.ads.linkedin.com\", \"static.ads-twitter.com\", \"analytics.twitter.com\",\n \"analytics.tiktok.com\", \"s.pinimg.com\", \"bat.bing.com\", \"sc-static.net\", \"tr.snapchat.com\", \"adroll.com\",\n \"criteo.net\", \"outbrain.com\", \"taboola.com\",\n ] },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", hosts: [\n \"hotjar.com\", \"hotjar.io\", \"clarity.ms\", \"mouseflow.com\", \"crazyegg.com\", \"fullstory.com\", \"logrocket.com\",\n \"lr-ingest.io\", \"luckyorange.com\", \"luckyorange.net\", \"smartlook.com\", \"inspectlet.com\", \"heapanalytics.com\",\n ] },\n { kind: \"tag-manager\", label: \"a tag manager\", hosts: [\n \"tags.tiqcdn.com\", \"tiqcdn.com\", \"cdn.segment.com\", \"assets.adobedtm.com\", \"cdn.tealium.com\",\n ] },\n];\n\n/** Hosts the shared analytics, Ads, maps and form-protection setup uses (see the Content-Security-Policy in site-next). */\nconst ALLOWED_HOSTS: readonly string[] = [\n \"google-analytics.com\", \"analytics.google.com\", \"googleadservices.com\", \"doubleclick.net\", \"googlesyndication.com\",\n \"maps.googleapis.com\", \"maps.gstatic.com\", \"gstatic.com\", \"challenges.cloudflare.com\",\n];\n\nconst INLINE_SIGNATURES: readonly { kind: ThirdPartyKind; label: string; regex: RegExp }[] = [\n { kind: \"pixel\", label: \"a Meta pixel\", regex: /\\bfbq\\s*\\(/ },\n { kind: \"tag-manager\", label: \"a tag manager\", regex: /gtm\\.start|googletagmanager\\.com\\/gtm\\.js/ },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", regex: /\\bhj\\s*\\(\\s*['\"]|static\\.hotjar\\.com|clarity\\.ms|\\bclarity\\s*\\(/ },\n { kind: \"chat\", label: \"a chat widget\", regex: /\\bTawk_API\\b|\\$crisp\\b|window\\.Intercom|\\bIntercom\\s*\\(|drift\\.load|LiveChatWidget|tidioChatApi|\\bzE\\s*\\(|\\bfcWidget\\b/ },\n];\n\nfunction hostMatches(host: string, domain: string): boolean {\n return host === domain || host.endsWith(`.${domain}`);\n}\n\ninterface Verdict {\n kind: ThirdPartyKind | \"other\";\n label: string;\n}\n\n/** What a third-party URL is, or null when the shared setup allows it. */\nexport function judgeThirdParty(url: URL): Verdict | null {\n const host = url.hostname.toLowerCase();\n if (hostMatches(host, \"googletagmanager.com\")) {\n return /^\\/gtag\\/js/.test(url.pathname) ? null : { kind: \"tag-manager\", label: \"a tag manager\" };\n }\n if (host === \"www.google.com\" || host === \"google.com\") {\n return /^\\/(?:maps|recaptcha)\\//.test(url.pathname) ? null : { kind: \"other\", label: \"a third-party request\" };\n }\n for (const category of CATEGORIES) {\n if (category.hosts.some((domain) => hostMatches(host, domain))) return { kind: category.kind, label: category.label };\n }\n if (ALLOWED_HOSTS.some((domain) => hostMatches(host, domain))) return null;\n return { kind: \"other\", label: \"a third-party request\" };\n}\n\nfunction isRecorded(recorded: readonly string[], url: URL, verdict: Verdict): boolean {\n const host = url.hostname.toLowerCase();\n return recorded.some((entry) => {\n const value = entry.trim().toLowerCase();\n return value !== \"\" && (value === verdict.kind || hostMatches(host, value));\n });\n}\n\nfunction sameSite(host: string, baseHost: string): boolean {\n const strip = (value: string) => value.replace(/^www\\./, \"\");\n return strip(host.toLowerCase()) === strip(baseHost.toLowerCase());\n}\n\n/**\n * The origin standing in for the site when there is no base URL (build-output mode): relative asset URLs resolve to it,\n * so they count as first-party and are read from the project's build output alone.\n */\nexport const BUILD_OUTPUT_ORIGIN = \"https://build-output.invalid\";\n\nfunction parseBase(baseUrl: string): URL | null {\n try {\n return new URL(baseUrl);\n } catch {\n return null;\n }\n}\n\n/**\n * The default reader: the project's build output for its own paths (`.next/static`, `public`, `out`), then the base\n * URL when there is one. Never reads other hosts.\n */\nexport const readBuiltAsset: ShellAssetReader = async (url, { projectRoot, baseUrl }) => {\n const given = parseBase(baseUrl);\n const base = given ?? new URL(BUILD_OUTPUT_ORIGIN);\n if (!sameSite(url.hostname, base.hostname)) return undefined;\n let pathname = url.pathname;\n try {\n pathname = decodeURIComponent(pathname);\n } catch {\n // Keep the encoded path.\n }\n const root = resolve(projectRoot);\n const candidates = pathname.startsWith(\"/_next/static/\")\n ? [join(root, \".next\", pathname.slice(\"/_next/\".length))]\n : pathname.startsWith(\"/_next/\") ? [] : [join(root, \"public\", pathname), join(root, \"out\", pathname)];\n for (const candidate of candidates) {\n const resolved = resolve(candidate);\n if (resolved.startsWith(root + sep) && existsSync(resolved)) {\n try {\n return new Uint8Array(readFileSync(resolved));\n } catch {\n // Fall through to the network.\n }\n }\n }\n if (!given || !/^https?:$/.test(url.protocol)) return undefined;\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(15_000) });\n return response.ok ? new Uint8Array(await response.arrayBuffer()) : undefined;\n } catch {\n return undefined;\n }\n};\n\ninterface Resource {\n url: URL;\n kind: \"script\" | \"other\";\n}\n\nfunction resolveUrl(value: string | undefined, page: URL): URL | null {\n if (!value || /^\\s*(?:data|blob|about|javascript|mailto|tel):/i.test(value)) return null;\n try {\n return new URL(value.trim(), page);\n } catch {\n return null;\n }\n}\n\n/** The scripts, frames and stylesheets a page requests. */\nfunction pageResources(page: ShellPage, pageUrl: URL): Resource[] {\n const resources: Resource[] = [];\n for (const element of descendants(page.doc.root)) {\n const { tag, attrs } = element;\n if (tag === \"script\" && attrs.src) {\n if (attrs.nomodule !== undefined) continue;\n const url = resolveUrl(attrs.src, pageUrl);\n if (url) resources.push({ url, kind: \"script\" });\n } else if (tag === \"link\") {\n const rel = (attrs.rel ?? \"\").toLowerCase().split(/\\s+/);\n const wantsScript = rel.includes(\"modulepreload\") || (rel.includes(\"preload\") && attrs.as === \"script\");\n const wantsOther = rel.includes(\"stylesheet\") || (rel.includes(\"preload\") && attrs.as !== \"script\" && attrs.as !== undefined && attrs.as !== \"image\");\n if (wantsScript || wantsOther) {\n const url = resolveUrl(attrs.href, pageUrl);\n if (url) resources.push({ url, kind: wantsScript ? \"script\" : \"other\" });\n }\n } else if (tag === \"iframe\" || tag === \"embed\" || tag === \"object\") {\n const url = resolveUrl(attrs.src ?? attrs.data, pageUrl);\n if (url) resources.push({ url, kind: \"other\" });\n }\n }\n return resources;\n}\n\nfunction inlineScripts(page: ShellPage): string[] {\n return findAll(page.doc.root, (element) =>\n element.tag === \"script\" && !element.attrs.src && !/(?:json|importmap|speculationrules)/i.test(element.attrs.type ?? \"\"))\n .map((script) => script.children.map((child) => (child.kind === \"text\" ? child.text : \"\")).join(\"\"))\n // The framework's streamed page data holds the page's words, not code that runs a widget.\n .filter((text) => !text.includes(\"__next_f\"));\n}\n\nfunction srcsetCandidates(srcset: string): { url: string; width: number }[] {\n return srcset.split(\",\").map((part) => part.trim().split(/\\s+/)).filter((parts) => parts[0])\n .map(([url, descriptor]) => ({ url: url!, width: descriptor?.endsWith(\"w\") ? Number.parseInt(descriptor, 10) : descriptor?.endsWith(\"x\") ? Number.parseFloat(descriptor) * 1000 : 0 }));\n}\n\n/** The image the hero shows on a wide screen: the smallest candidate at least 1440 px wide, else the widest. */\nfunction heroImage(page: ShellPage): { element: HtmlElement; source: string | undefined } | null {\n const hero = page.hero;\n if (!hero) return null;\n const images = findAll(hero, (element) => element.tag === \"img\" && !page.headers.some((header) => isWithin(element, header)) && element.attrs[\"data-bl\"] !== \"logo\");\n if (images.length === 0) return null;\n const width = (image: HtmlElement) => Number.parseInt(image.attrs.width ?? \"0\", 10) || 0;\n const element = images.find((image) => image.attrs.fetchpriority === \"high\") ?? images.reduce((best, image) => (width(image) > width(best) ? image : best), images[0]!);\n const candidates = srcsetCandidates(element.attrs.srcset ?? \"\");\n const wide = candidates.filter((candidate) => candidate.width >= B1_HERO_VIEWPORT_PX).sort((left, right) => left.width - right.width)[0]\n ?? candidates.sort((left, right) => right.width - left.width)[0];\n return { element, source: wide?.url ?? element.attrs.src };\n}\n\nfunction gzipBytes(bytes: Uint8Array): number {\n return gzipSync(Buffer.from(bytes)).length;\n}\n\nfunction kilobytes(bytes: number): string {\n return `${Math.round(bytes / 1024)} KB`;\n}\n\nexport interface BudgetOptions {\n baseUrl: string;\n projectRoot: string;\n recordedThirdParties?: readonly string[];\n readAsset?: ShellAssetReader;\n}\n\n/** B1 over the sampled pages. */\nexport async function checkLabBudgets(pages: readonly ShellPage[], options: BudgetOptions): Promise<ShellFinding[]> {\n const read = options.readAsset ?? readBuiltAsset;\n const recorded = options.recordedThirdParties ?? [];\n const cache = new Map<string, Promise<Uint8Array | undefined>>();\n const readCached = (url: URL) => {\n const key = url.href;\n if (!cache.has(key)) cache.set(key, read(url, { projectRoot: options.projectRoot, baseUrl: options.baseUrl }).catch(() => undefined));\n return cache.get(key)!;\n };\n const base = parseBase(options.baseUrl) ?? new URL(BUILD_OUTPUT_ORIGIN);\n const failures: ShellFinding[] = [];\n const warnings: ShellFinding[] = [];\n const hostsSeen = new Set<string>();\n let largestJs = 0;\n let jsPages = 0;\n\n for (const page of pages) {\n const pageUrl = new URL(page.path, base);\n const htmlBytes = Buffer.byteLength(page.html);\n if (htmlBytes > B1_HTML_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} is ${kilobytes(htmlBytes)} of HTML, over the ${kilobytes(B1_HTML_MAX_BYTES)} budget.`));\n }\n\n const resources = pageResources(page, pageUrl);\n const flagged = new Set<string>();\n for (const resource of resources) {\n if (sameSite(resource.url.hostname, base.hostname)) continue;\n hostsSeen.add(resource.url.hostname);\n const verdict = judgeThirdParty(resource.url);\n if (!verdict || isRecorded(recorded, resource.url, verdict)) continue;\n const key = `${resource.url.hostname}\\u0000${verdict.kind}`;\n if (flagged.has(key)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, verdict.kind === \"other\"\n ? `The page ${page.path} requests ${resource.url.hostname}, a third party outside analytics, maps and the reviews feed, with no recorded decision.`\n : `The page ${page.path} loads ${verdict.label} from ${resource.url.hostname} with no recorded decision.`));\n }\n for (const script of inlineScripts(page)) {\n for (const signature of INLINE_SIGNATURES) {\n const key = `inline\\u0000${signature.kind}`;\n if (flagged.has(key) || !signature.regex.test(script)) continue;\n if (recorded.some((entry) => entry.trim().toLowerCase() === signature.kind)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} runs ${signature.label} from an inline script with no recorded decision.`));\n }\n }\n\n const scripts = [...new Map(resources.filter((resource) => resource.kind === \"script\" && sameSite(resource.url.hostname, base.hostname)).map((resource) => [resource.url.href, resource.url])).values()];\n if (scripts.length > 0) {\n const sizes = await Promise.all(scripts.map(readCached));\n const measured = sizes.filter((bytes): bytes is Uint8Array => bytes !== undefined);\n const total = measured.reduce((sum, bytes) => sum + gzipBytes(bytes), 0);\n if (measured.length > 0) {\n jsPages += 1;\n largestJs = Math.max(largestJs, total);\n const unread = scripts.length - measured.length;\n const note = unread > 0 ? ` (${unread} of ${scripts.length} script file${scripts.length === 1 ? \"\" : \"s\"} could not be read)` : \"\";\n if (total > B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES)} budget.`));\n } else if (total > B1_FIRST_PARTY_JS_GZIP_WARN_BYTES) {\n warnings.push(warningFinding(\"B1\", `Warning: the page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_WARN_BYTES)} warning threshold.`, page.path));\n }\n }\n }\n\n const hero = heroImage(page);\n if (hero) {\n const { element, source } = hero;\n if (element.attrs.loading === \"lazy\" || element.attrs.fetchpriority === \"low\" || /\\blazy(?:load|loaded)?\\b/.test(element.attrs.class ?? \"\")) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is set to load lazily; the first-screen image must load straight away.`));\n }\n const url = resolveUrl(source, pageUrl);\n if (url && sameSite(url.hostname, base.hostname)) {\n const bytes = await readCached(url);\n if (bytes && bytes.length > B1_HERO_IMAGE_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is ${kilobytes(bytes.length)}, over the ${kilobytes(B1_HERO_IMAGE_MAX_BYTES)} budget at ${B1_HERO_VIEWPORT_PX} px.`));\n }\n }\n }\n }\n if (failures.length > 0) return [...failures, ...warnings];\n const jsNote = jsPages > 0 ? ` The heaviest page loads ${kilobytes(largestJs)} of first-party JavaScript (gzip).` : \" Script sizes could not be read from the build output or the base URL, so the JavaScript budget was not measured.\";\n const hostNote = hostsSeen.size > 0 ? ` Third-party hosts seen: ${[...hostsSeen].sort().join(\", \")}.` : \" No third-party requests.\";\n const within = pages.length === 1 ? \"The sampled page is\" : `The ${pages.length} sampled pages are`;\n return [passFinding(\"B1\", `${within} within the HTML, hero image and third-party budgets.${jsNote}${hostNote}`), ...warnings];\n}\n"]}
1
+ {"version":3,"file":"budgets.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/budgets.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport { gzipSync } from \"node:zlib\";\nimport { descendants, findAll, isWithin, type HtmlElement } from \"./html.js\";\nimport type { ShellPage } from \"./page.js\";\nimport {\n failFinding, passFinding, warningFinding, type ShellAssetReader, type ShellFinding,\n} from \"./types.js\";\n\n/**\n * B1: lab budgets read from the built page HTML (spec 5.8): JavaScript, third-party requests, the hero image and the\n * HTML itself, with no chat widget, pixel, heatmap or tag manager unless the project records the decision. Script and\n * image sizes come from the project's build output, then the base URL; a size that can't be read is left out of the\n * total and said so, never guessed.\n */\n\n/** First-party JavaScript per page, gzip: a warning from here. Starting value, calibrated in the R1 canaries. */\nexport const B1_FIRST_PARTY_JS_GZIP_WARN_BYTES = 180 * 1024;\n/** First-party JavaScript per page, gzip: the spec's starting budget; over it blocks. */\nexport const B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES = 250 * 1024;\nexport const B1_HTML_MAX_BYTES = 500 * 1024;\nexport const B1_HERO_IMAGE_MAX_BYTES = 200 * 1024;\n/** The viewport width the hero image is judged at. */\nexport const B1_HERO_VIEWPORT_PX = 1440;\n\ntype ThirdPartyKind = \"chat\" | \"pixel\" | \"heatmap\" | \"tag-manager\";\n\ninterface Category {\n kind: ThirdPartyKind;\n label: string;\n hosts: readonly string[];\n}\n\nconst CATEGORIES: readonly Category[] = [\n { kind: \"chat\", label: \"a chat widget\", hosts: [\n \"widget.intercom.io\", \"js.intercomcdn.com\", \"intercom.io\", \"tawk.to\", \"crisp.chat\", \"livechatinc.com\", \"tidio.co\",\n \"driftt.com\", \"drift.com\", \"usemessages.com\", \"zdassets.com\", \"freshchat.com\", \"olark.com\", \"smartsuppchat.com\",\n \"chatra.io\", \"jivosite.com\", \"manychat.com\", \"chatwoot.com\",\n ] },\n { kind: \"pixel\", label: \"an advertising pixel\", hosts: [\n \"connect.facebook.net\", \"snap.licdn.com\", \"px.ads.linkedin.com\", \"static.ads-twitter.com\", \"analytics.twitter.com\",\n \"analytics.tiktok.com\", \"s.pinimg.com\", \"bat.bing.com\", \"sc-static.net\", \"tr.snapchat.com\", \"adroll.com\",\n \"criteo.net\", \"outbrain.com\", \"taboola.com\",\n ] },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", hosts: [\n \"hotjar.com\", \"hotjar.io\", \"clarity.ms\", \"mouseflow.com\", \"crazyegg.com\", \"fullstory.com\", \"logrocket.com\",\n \"lr-ingest.io\", \"luckyorange.com\", \"luckyorange.net\", \"smartlook.com\", \"inspectlet.com\", \"heapanalytics.com\",\n ] },\n { kind: \"tag-manager\", label: \"a tag manager\", hosts: [\n \"tags.tiqcdn.com\", \"tiqcdn.com\", \"cdn.segment.com\", \"assets.adobedtm.com\", \"cdn.tealium.com\",\n ] },\n];\n\n/** Hosts the shared analytics, Ads, maps and form-protection setup uses (see the Content-Security-Policy in site-next). */\nconst ALLOWED_HOSTS: readonly string[] = [\n \"google-analytics.com\", \"analytics.google.com\", \"googleadservices.com\", \"doubleclick.net\", \"googlesyndication.com\",\n \"maps.googleapis.com\", \"maps.gstatic.com\", \"gstatic.com\", \"challenges.cloudflare.com\",\n];\n\nconst INLINE_SIGNATURES: readonly { kind: ThirdPartyKind; label: string; regex: RegExp }[] = [\n { kind: \"pixel\", label: \"a Meta pixel\", regex: /\\bfbq\\s*\\(/ },\n { kind: \"tag-manager\", label: \"a tag manager\", regex: /gtm\\.start|googletagmanager\\.com\\/gtm\\.js/ },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", regex: /\\bhj\\s*\\(\\s*['\"]|static\\.hotjar\\.com|clarity\\.ms|\\bclarity\\s*\\(/ },\n { kind: \"chat\", label: \"a chat widget\", regex: /\\bTawk_API\\b|\\$crisp\\b|window\\.Intercom|\\bIntercom\\s*\\(|drift\\.load|LiveChatWidget|tidioChatApi|\\bzE\\s*\\(|\\bfcWidget\\b/ },\n];\n\nfunction hostMatches(host: string, domain: string): boolean {\n return host === domain || host.endsWith(`.${domain}`);\n}\n\ninterface Verdict {\n kind: ThirdPartyKind | \"other\";\n label: string;\n}\n\n/** What a third-party URL is, or null when the shared setup allows it. */\nexport function judgeThirdParty(url: URL): Verdict | null {\n const host = url.hostname.toLowerCase();\n if (hostMatches(host, \"googletagmanager.com\")) {\n return /^\\/gtag\\/js/.test(url.pathname) ? null : { kind: \"tag-manager\", label: \"a tag manager\" };\n }\n if (host === \"www.google.com\" || host === \"google.com\") {\n return /^\\/(?:maps|recaptcha)\\//.test(url.pathname) ? null : { kind: \"other\", label: \"a third-party request\" };\n }\n for (const category of CATEGORIES) {\n if (category.hosts.some((domain) => hostMatches(host, domain))) return { kind: category.kind, label: category.label };\n }\n if (ALLOWED_HOSTS.some((domain) => hostMatches(host, domain))) return null;\n return { kind: \"other\", label: \"a third-party request\" };\n}\n\nfunction isRecorded(recorded: readonly string[], url: URL, verdict: Verdict): boolean {\n const host = url.hostname.toLowerCase();\n return recorded.some((entry) => {\n const value = entry.trim().toLowerCase();\n return value !== \"\" && (value === verdict.kind || hostMatches(host, value));\n });\n}\n\nfunction sameSite(host: string, baseHost: string): boolean {\n const strip = (value: string) => value.replace(/^www\\./, \"\");\n return strip(host.toLowerCase()) === strip(baseHost.toLowerCase());\n}\n\n/**\n * The origin standing in for the site when there is no base URL (build-output mode): relative asset URLs resolve to it,\n * so they count as first-party and are read from the project's build output alone.\n */\nexport const BUILD_OUTPUT_ORIGIN = \"https://build-output.invalid\";\n\nfunction parseBase(baseUrl: string): URL | null {\n try {\n return new URL(baseUrl);\n } catch {\n return null;\n }\n}\n\n/**\n * The default reader: the project's build output for its own paths (`.next/static`, `public`, `out`), then the base\n * URL when there is one. Never reads other hosts.\n */\nexport const readBuiltAsset: ShellAssetReader = async (url, { projectRoot, baseUrl }) => {\n const given = parseBase(baseUrl);\n const base = given ?? new URL(BUILD_OUTPUT_ORIGIN);\n if (!sameSite(url.hostname, base.hostname)) return undefined;\n let pathname = url.pathname;\n try {\n pathname = decodeURIComponent(pathname);\n } catch {\n // Keep the encoded path.\n }\n const root = resolve(projectRoot);\n const candidates = pathname.startsWith(\"/_next/static/\")\n ? [join(root, \".next\", pathname.slice(\"/_next/\".length))]\n : pathname.startsWith(\"/_next/\") ? [] : [join(root, \"public\", pathname), join(root, \"out\", pathname)];\n for (const candidate of candidates) {\n const resolved = resolve(candidate);\n if (resolved.startsWith(root + sep) && existsSync(resolved)) {\n try {\n return new Uint8Array(readFileSync(resolved));\n } catch {\n // Fall through to the network.\n }\n }\n }\n if (!given || !/^https?:$/.test(url.protocol)) return undefined;\n try {\n // A browser asks for WebP, and Next's optimiser answers in the format asked for (3.18.0).\n const response = await fetch(url, { headers: { accept: \"image/webp,image/*;q=0.8\" }, signal: AbortSignal.timeout(15_000) });\n return response.ok ? new Uint8Array(await response.arrayBuffer()) : undefined;\n } catch {\n return undefined;\n }\n};\n\ninterface Resource {\n url: URL;\n kind: \"script\" | \"other\";\n}\n\nfunction resolveUrl(value: string | undefined, page: URL): URL | null {\n if (!value || /^\\s*(?:data|blob|about|javascript|mailto|tel):/i.test(value)) return null;\n try {\n return new URL(value.trim(), page);\n } catch {\n return null;\n }\n}\n\n/** The scripts, frames and stylesheets a page requests. */\nfunction pageResources(page: ShellPage, pageUrl: URL): Resource[] {\n const resources: Resource[] = [];\n for (const element of descendants(page.doc.root)) {\n const { tag, attrs } = element;\n if (tag === \"script\" && attrs.src) {\n if (attrs.nomodule !== undefined) continue;\n const url = resolveUrl(attrs.src, pageUrl);\n if (url) resources.push({ url, kind: \"script\" });\n } else if (tag === \"link\") {\n const rel = (attrs.rel ?? \"\").toLowerCase().split(/\\s+/);\n const wantsScript = rel.includes(\"modulepreload\") || (rel.includes(\"preload\") && attrs.as === \"script\");\n const wantsOther = rel.includes(\"stylesheet\") || (rel.includes(\"preload\") && attrs.as !== \"script\" && attrs.as !== undefined && attrs.as !== \"image\");\n if (wantsScript || wantsOther) {\n const url = resolveUrl(attrs.href, pageUrl);\n if (url) resources.push({ url, kind: wantsScript ? \"script\" : \"other\" });\n }\n } else if (tag === \"iframe\" || tag === \"embed\" || tag === \"object\") {\n const url = resolveUrl(attrs.src ?? attrs.data, pageUrl);\n if (url) resources.push({ url, kind: \"other\" });\n }\n }\n return resources;\n}\n\nfunction inlineScripts(page: ShellPage): string[] {\n return findAll(page.doc.root, (element) =>\n element.tag === \"script\" && !element.attrs.src && !/(?:json|importmap|speculationrules)/i.test(element.attrs.type ?? \"\"))\n .map((script) => script.children.map((child) => (child.kind === \"text\" ? child.text : \"\")).join(\"\"))\n // The framework's streamed page data holds the page's words, not code that runs a widget.\n .filter((text) => !text.includes(\"__next_f\"));\n}\n\nfunction srcsetCandidates(srcset: string): { url: string; width: number }[] {\n return srcset.split(\",\").map((part) => part.trim().split(/\\s+/)).filter((parts) => parts[0])\n .map(([url, descriptor]) => ({ url: url!, width: descriptor?.endsWith(\"w\") ? Number.parseInt(descriptor, 10) : descriptor?.endsWith(\"x\") ? Number.parseFloat(descriptor) * 1000 : 0 }));\n}\n\n/** Below this rendered width an image is an avatar, icon or badge, never the hero's photo (3.18.0). */\nconst B1_HERO_MIN_IMAGE_PX = 160;\n\n/** The image the hero shows on a wide screen: the smallest candidate at least 1440 px wide, else the widest. */\nfunction heroImage(page: ShellPage): { element: HtmlElement; source: string | undefined } | null {\n const hero = page.hero;\n if (!hero) return null;\n const width = (image: HtmlElement) => Number.parseInt(image.attrs.width ?? \"0\", 10) || 0;\n const images = findAll(hero, (element) => element.tag === \"img\" && !page.headers.some((header) => isWithin(element, header)) && element.attrs[\"data-bl\"] !== \"logo\")\n .filter((image) => image.attrs.fetchpriority === \"high\" || width(image) === 0 || width(image) >= B1_HERO_MIN_IMAGE_PX);\n if (images.length === 0) return null;\n const element = images.find((image) => image.attrs.fetchpriority === \"high\") ?? images.reduce((best, image) => (width(image) > width(best) ? image : best), images[0]!);\n const candidates = srcsetCandidates(element.attrs.srcset ?? \"\");\n const wide = candidates.filter((candidate) => candidate.width >= B1_HERO_VIEWPORT_PX).sort((left, right) => left.width - right.width)[0]\n ?? candidates.sort((left, right) => right.width - left.width)[0];\n return { element, source: wide?.url ?? element.attrs.src };\n}\n\nfunction gzipBytes(bytes: Uint8Array): number {\n return gzipSync(Buffer.from(bytes)).length;\n}\n\nfunction kilobytes(bytes: number): string {\n return `${Math.round(bytes / 1024)} KB`;\n}\n\nexport interface BudgetOptions {\n baseUrl: string;\n projectRoot: string;\n recordedThirdParties?: readonly string[];\n readAsset?: ShellAssetReader;\n}\n\n/** B1 over the sampled pages. */\nexport async function checkLabBudgets(pages: readonly ShellPage[], options: BudgetOptions): Promise<ShellFinding[]> {\n const read = options.readAsset ?? readBuiltAsset;\n const recorded = options.recordedThirdParties ?? [];\n const cache = new Map<string, Promise<Uint8Array | undefined>>();\n const readCached = (url: URL) => {\n const key = url.href;\n if (!cache.has(key)) cache.set(key, read(url, { projectRoot: options.projectRoot, baseUrl: options.baseUrl }).catch(() => undefined));\n return cache.get(key)!;\n };\n const base = parseBase(options.baseUrl) ?? new URL(BUILD_OUTPUT_ORIGIN);\n const failures: ShellFinding[] = [];\n const warnings: ShellFinding[] = [];\n const hostsSeen = new Set<string>();\n let largestJs = 0;\n let jsPages = 0;\n\n for (const page of pages) {\n const pageUrl = new URL(page.path, base);\n const htmlBytes = Buffer.byteLength(page.html);\n if (htmlBytes > B1_HTML_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} is ${kilobytes(htmlBytes)} of HTML, over the ${kilobytes(B1_HTML_MAX_BYTES)} budget.`));\n }\n\n const resources = pageResources(page, pageUrl);\n const flagged = new Set<string>();\n for (const resource of resources) {\n if (sameSite(resource.url.hostname, base.hostname)) continue;\n hostsSeen.add(resource.url.hostname);\n const verdict = judgeThirdParty(resource.url);\n if (!verdict || isRecorded(recorded, resource.url, verdict)) continue;\n const key = `${resource.url.hostname}\\u0000${verdict.kind}`;\n if (flagged.has(key)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, verdict.kind === \"other\"\n ? `The page ${page.path} requests ${resource.url.hostname}, a third party outside analytics, maps and the reviews feed, with no recorded decision.`\n : `The page ${page.path} loads ${verdict.label} from ${resource.url.hostname} with no recorded decision.`));\n }\n for (const script of inlineScripts(page)) {\n for (const signature of INLINE_SIGNATURES) {\n const key = `inline\\u0000${signature.kind}`;\n if (flagged.has(key) || !signature.regex.test(script)) continue;\n if (recorded.some((entry) => entry.trim().toLowerCase() === signature.kind)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} runs ${signature.label} from an inline script with no recorded decision.`));\n }\n }\n\n const scripts = [...new Map(resources.filter((resource) => resource.kind === \"script\" && sameSite(resource.url.hostname, base.hostname)).map((resource) => [resource.url.href, resource.url])).values()];\n if (scripts.length > 0) {\n const sizes = await Promise.all(scripts.map(readCached));\n const measured = sizes.filter((bytes): bytes is Uint8Array => bytes !== undefined);\n const total = measured.reduce((sum, bytes) => sum + gzipBytes(bytes), 0);\n if (measured.length > 0) {\n jsPages += 1;\n largestJs = Math.max(largestJs, total);\n const unread = scripts.length - measured.length;\n const note = unread > 0 ? ` (${unread} of ${scripts.length} script file${scripts.length === 1 ? \"\" : \"s\"} could not be read)` : \"\";\n if (total > B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES)} budget.`));\n } else if (total > B1_FIRST_PARTY_JS_GZIP_WARN_BYTES) {\n warnings.push(warningFinding(\"B1\", `Warning: the page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_WARN_BYTES)} warning threshold.`, page.path));\n }\n }\n }\n\n const hero = heroImage(page);\n if (hero) {\n const { element, source } = hero;\n if (element.attrs.loading === \"lazy\" || element.attrs.fetchpriority === \"low\" || /\\blazy(?:load|loaded)?\\b/.test(element.attrs.class ?? \"\")) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is set to load lazily; the first-screen image must load straight away.`));\n }\n const url = resolveUrl(source, pageUrl);\n if (url && sameSite(url.hostname, base.hostname)) {\n const bytes = await readCached(url);\n if (bytes && bytes.length > B1_HERO_IMAGE_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is ${kilobytes(bytes.length)}, over the ${kilobytes(B1_HERO_IMAGE_MAX_BYTES)} budget at ${B1_HERO_VIEWPORT_PX} px.`));\n }\n }\n }\n }\n if (failures.length > 0) return [...failures, ...warnings];\n const jsNote = jsPages > 0 ? ` The heaviest page loads ${kilobytes(largestJs)} of first-party JavaScript (gzip).` : \" Script sizes could not be read from the build output or the base URL, so the JavaScript budget was not measured.\";\n const hostNote = hostsSeen.size > 0 ? ` Third-party hosts seen: ${[...hostsSeen].sort().join(\", \")}.` : \" No third-party requests.\";\n const within = pages.length === 1 ? \"The sampled page is\" : `The ${pages.length} sampled pages are`;\n return [passFinding(\"B1\", `${within} within the HTML, hero image and third-party budgets.${jsNote}${hostNote}`), ...warnings];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandlab-digital/conformance",
3
- "version": "3.17.1",
3
+ "version": "3.18.0",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "typecheck": "tsc --noEmit -p tsconfig.json"
30
30
  },
31
31
  "dependencies": {
32
- "@brandlab-digital/site-core": "3.17.1",
32
+ "@brandlab-digital/site-core": "3.18.0",
33
33
  "typescript": "6.0.3"
34
34
  },
35
35
  "peerDependencies": {