@brandlab-digital/conformance 3.12.1 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +32 -1
  2. package/dist/current-release.js +5 -5
  3. package/dist/current-release.js.map +1 -1
  4. package/dist/edge-probe-cli.d.ts +57 -0
  5. package/dist/edge-probe-cli.d.ts.map +1 -0
  6. package/dist/edge-probe-cli.js +134 -0
  7. package/dist/edge-probe-cli.js.map +1 -0
  8. package/dist/engineering.d.ts +4 -1
  9. package/dist/engineering.d.ts.map +1 -1
  10. package/dist/engineering.js +19 -14
  11. package/dist/engineering.js.map +1 -1
  12. package/dist/estate-sample.d.ts +60 -0
  13. package/dist/estate-sample.d.ts.map +1 -0
  14. package/dist/estate-sample.js +144 -0
  15. package/dist/estate-sample.js.map +1 -0
  16. package/dist/estate.d.ts +17 -0
  17. package/dist/estate.d.ts.map +1 -1
  18. package/dist/estate.js +47 -10
  19. package/dist/estate.js.map +1 -1
  20. package/dist/origin-region.d.ts +14 -0
  21. package/dist/origin-region.d.ts.map +1 -0
  22. package/dist/origin-region.js +46 -0
  23. package/dist/origin-region.js.map +1 -0
  24. package/dist/prefetch-probe-cli.d.ts +36 -0
  25. package/dist/prefetch-probe-cli.d.ts.map +1 -0
  26. package/dist/prefetch-probe-cli.js +163 -0
  27. package/dist/prefetch-probe-cli.js.map +1 -0
  28. package/dist/site-system/checks.d.ts +36 -0
  29. package/dist/site-system/checks.d.ts.map +1 -1
  30. package/dist/site-system/checks.js +6 -0
  31. package/dist/site-system/checks.js.map +1 -1
  32. package/dist/site-system/config.d.ts +27 -0
  33. package/dist/site-system/config.d.ts.map +1 -1
  34. package/dist/site-system/config.js +50 -1
  35. package/dist/site-system/config.js.map +1 -1
  36. package/dist/site-system/index.d.ts +1 -1
  37. package/dist/site-system/index.d.ts.map +1 -1
  38. package/dist/site-system/index.js +1 -1
  39. package/dist/site-system/index.js.map +1 -1
  40. package/dist/site-system/journeys/plan.d.ts +9 -2
  41. package/dist/site-system/journeys/plan.d.ts.map +1 -1
  42. package/dist/site-system/journeys/plan.js +11 -1
  43. package/dist/site-system/journeys/plan.js.map +1 -1
  44. package/dist/site-system/journeys/prefetch-measure.d.ts +28 -0
  45. package/dist/site-system/journeys/prefetch-measure.d.ts.map +1 -0
  46. package/dist/site-system/journeys/prefetch-measure.js +94 -0
  47. package/dist/site-system/journeys/prefetch-measure.js.map +1 -0
  48. package/dist/site-system/journeys/prefetch-policy.d.ts +85 -0
  49. package/dist/site-system/journeys/prefetch-policy.d.ts.map +1 -0
  50. package/dist/site-system/journeys/prefetch-policy.js +152 -0
  51. package/dist/site-system/journeys/prefetch-policy.js.map +1 -0
  52. package/dist/site-system/journeys/prefetch.d.ts +3 -0
  53. package/dist/site-system/journeys/prefetch.d.ts.map +1 -0
  54. package/dist/site-system/journeys/prefetch.js +37 -0
  55. package/dist/site-system/journeys/prefetch.js.map +1 -0
  56. package/dist/site-system/journeys/variants.d.ts +34 -0
  57. package/dist/site-system/journeys/variants.d.ts.map +1 -0
  58. package/dist/site-system/journeys/variants.js +139 -0
  59. package/dist/site-system/journeys/variants.js.map +1 -0
  60. package/dist/site-system/rule-modules.d.ts.map +1 -1
  61. package/dist/site-system/rule-modules.js +5 -0
  62. package/dist/site-system/rule-modules.js.map +1 -1
  63. package/dist/site-system/rules/full/common.d.ts +3 -0
  64. package/dist/site-system/rules/full/common.d.ts.map +1 -1
  65. package/dist/site-system/rules/full/common.js.map +1 -1
  66. package/dist/site-system/rules/full/estate.d.ts +4 -0
  67. package/dist/site-system/rules/full/estate.d.ts.map +1 -1
  68. package/dist/site-system/rules/full/estate.js +11 -2
  69. package/dist/site-system/rules/full/estate.js.map +1 -1
  70. package/dist/site-system/rules/full/index.d.ts +2 -1
  71. package/dist/site-system/rules/full/index.d.ts.map +1 -1
  72. package/dist/site-system/rules/full/index.js +14 -7
  73. package/dist/site-system/rules/full/index.js.map +1 -1
  74. package/dist/site-system/rules/full/w3-refresh.d.ts +22 -0
  75. package/dist/site-system/rules/full/w3-refresh.d.ts.map +1 -0
  76. package/dist/site-system/rules/full/w3-refresh.js +70 -0
  77. package/dist/site-system/rules/full/w3-refresh.js.map +1 -0
  78. package/dist/site-system/rules/guards/index.d.ts +22 -0
  79. package/dist/site-system/rules/guards/index.d.ts.map +1 -0
  80. package/dist/site-system/rules/guards/index.js +109 -0
  81. package/dist/site-system/rules/guards/index.js.map +1 -0
  82. package/dist/site-system/rules/guards/w10-crawlers.d.ts +55 -0
  83. package/dist/site-system/rules/guards/w10-crawlers.d.ts.map +1 -0
  84. package/dist/site-system/rules/guards/w10-crawlers.js +182 -0
  85. package/dist/site-system/rules/guards/w10-crawlers.js.map +1 -0
  86. package/dist/site-system/rules/guards/w9-params.d.ts +19 -0
  87. package/dist/site-system/rules/guards/w9-params.d.ts.map +1 -0
  88. package/dist/site-system/rules/guards/w9-params.js +84 -0
  89. package/dist/site-system/rules/guards/w9-params.js.map +1 -0
  90. package/dist/site-system-e2e.d.ts +1 -0
  91. package/dist/site-system-e2e.d.ts.map +1 -1
  92. package/dist/site-system-e2e.js +8 -2
  93. package/dist/site-system-e2e.js.map +1 -1
  94. package/package.json +6 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estate-sample.js","sourceRoot":"","sources":["../src/estate-sample.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The sampled estate crawl (3.13.0, client CI v2). A draft pull request checks a\n * sample of the catalogue instead of every route: the hubs, the first and last\n * page of each route kind, the routes a changed data file feeds and a fixed-seed\n * sample of the rest. The full crawl still runs when the pull request is ready\n * for review and before merge; the sample only makes drafts cheaper.\n *\n * CI chooses the scope with `BRANDLAB_ESTATE_SCOPE` (`full`, the default, or\n * `sample`) and lists the changed files in the file `BRANDLAB_CHANGED_FILES`\n * names, one path per line. Any other scope value runs the full crawl.\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\nexport type BrandlabEstateScope = 'full' | 'sample';\n\n/** The route fields the sample reads; a catalogue route has them. */\nexport interface BrandlabEstateSampleRoute {\n path: string;\n kind?: string;\n id?: string;\n}\n\nexport interface BrandlabEstateSampleOptions {\n /** How many more routes the fixed seed picks beyond hubs, kind ends and changed routes (default 50). */\n size?: number;\n /** The seed; the same seed and catalogue always give the same sample (default `brandlab-estate-sample`). */\n seed?: string;\n /**\n * The routes a changed file feeds, as paths. By default a route is fed by a file\n * named after its id or its last path segment (`src/data/locations/bondi.ts`\n * feeds `/locations/bondi`; an `index` file is named by its folder).\n */\n routesForFile?: (file: string, routes: readonly BrandlabEstateSampleRoute[]) => readonly string[];\n}\n\nexport interface BrandlabEstateSample<T extends BrandlabEstateSampleRoute> {\n scope: BrandlabEstateScope;\n /** The routes to crawl, in catalogue order. */\n routes: T[];\n total: number;\n counts: { hubs: number; kindEnds: number; changed: number; seeded: number };\n /** One line for the test annotation. */\n summary: string;\n}\n\n/** Hubs: home, every index page, the contact page and the not-found page. */\nconst HUB_KINDS = new Set(['home', 'service-index', 'location-index', 'blog-index', 'project-index', 'contact']);\nconst DEFAULT_SIZE = 50;\nconst DEFAULT_SEED = 'brandlab-estate-sample';\nconst GENERIC_STEMS = new Set(['index', 'data', 'content', 'page']);\n\n/** The scope CI asked for. Anything but `sample` is the full crawl, so a typo never thins a check. */\nexport function estateScopeFromEnv(env: NodeJS.ProcessEnv = process.env): { scope: BrandlabEstateScope; note?: string } {\n const value = (env.BRANDLAB_ESTATE_SCOPE ?? '').trim();\n if (value === 'sample') return { scope: 'sample' };\n if (value === '' || value === 'full') return { scope: 'full' };\n return { scope: 'full', note: `BRANDLAB_ESTATE_SCOPE=\"${value}\" is not full or sample; the full estate was crawled` };\n}\n\n/** The changed files CI listed (`BRANDLAB_CHANGED_FILES` names the list). Unreadable or unset lists none. */\nexport function changedFilesFromEnv(env: NodeJS.ProcessEnv = process.env): string[] {\n const listPath = env.BRANDLAB_CHANGED_FILES?.trim();\n if (!listPath) return [];\n try {\n return readFileSync(listPath, 'utf8').split('\\n').map((line) => line.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n/** The name a data file gives the routes it feeds: its file name without extensions, or its folder's for an index file. */\nexport function changedFileStem(file: string): string | undefined {\n const parts = file.replace(/\\\\/g, '/').split('/').filter(Boolean);\n const stemOf = (part: string | undefined) => part?.replace(/(?:\\.[a-z0-9]+)+$/i, '').toLowerCase() ?? '';\n const own = stemOf(parts.at(-1));\n const stem = GENERIC_STEMS.has(own) ? stemOf(parts.at(-2)) : own;\n return stem && !GENERIC_STEMS.has(stem) ? stem : undefined;\n}\n\nexport function defaultRoutesForFile(file: string, routes: readonly BrandlabEstateSampleRoute[]): string[] {\n const stem = changedFileStem(file);\n if (!stem) return [];\n return routes\n .filter((route) => route.id?.toLowerCase() === stem || lastSegment(route.path) === stem)\n .map((route) => route.path);\n}\n\n/**\n * The routes a sampled crawl checks. The full scope returns every route. The\n * sample keeps the hubs and the not-found page, the first and last route of each\n * kind in catalogue order, the routes the changed files feed and `size` more\n * chosen by a hash of the seed and the path, so the same catalogue always gives\n * the same sample.\n */\nexport function sampleEstateRoutes<T extends BrandlabEstateSampleRoute>(\n routes: readonly T[],\n input: { scope: BrandlabEstateScope; changedFiles?: readonly string[] } & BrandlabEstateSampleOptions,\n): BrandlabEstateSample<T> {\n const total = routes.length;\n if (input.scope === 'full') {\n return { scope: 'full', routes: [...routes], total, counts: { hubs: 0, kindEnds: 0, changed: 0, seeded: 0 }, summary: `full: all ${total} routes` };\n }\n const chosen = new Set<string>();\n const add = (path: string) => {\n const key = normalise(path);\n if (chosen.has(key)) return false;\n chosen.add(key);\n return true;\n };\n let hubs = 0;\n for (const route of routes) {\n if (HUB_KINDS.has(route.kind ?? '') || normalise(route.path) === '/' || normalise(route.path) === '/404') if (add(route.path)) hubs += 1;\n }\n const byKind = new Map<string, T[]>();\n for (const route of routes) {\n if (HUB_KINDS.has(route.kind ?? '')) continue;\n const kind = route.kind ?? '';\n byKind.set(kind, [...(byKind.get(kind) ?? []), route]);\n }\n let kindEnds = 0;\n for (const group of byKind.values()) {\n for (const route of [group[0], group.at(-1)]) if (route && add(route.path)) kindEnds += 1;\n }\n const known = new Set(routes.map((route) => normalise(route.path)));\n const feeds = input.routesForFile ?? defaultRoutesForFile;\n let changed = 0;\n for (const file of input.changedFiles ?? []) {\n for (const path of feeds(file, routes)) if (known.has(normalise(path)) && add(path)) changed += 1;\n }\n const seed = input.seed ?? DEFAULT_SEED;\n const size = Math.max(0, Math.floor(input.size ?? DEFAULT_SIZE));\n const rest = routes\n .filter((route) => !chosen.has(normalise(route.path)))\n .map((route) => ({ path: route.path, rank: seededRank(seed, normalise(route.path)) }))\n .sort((a, b) => a.rank.localeCompare(b.rank) || a.path.localeCompare(b.path))\n .slice(0, size);\n for (const { path } of rest) add(path);\n const sampled = routes.filter((route) => chosen.has(normalise(route.path)));\n // A path the catalogue repeats is checked once.\n const unique = sampled.filter((route, index) => sampled.findIndex((other) => normalise(other.path) === normalise(route.path)) === index);\n return {\n scope: 'sample',\n routes: unique,\n total,\n counts: { hubs, kindEnds, changed, seeded: rest.length },\n summary: `sample: ${unique.length} of ${total} routes (hubs ${hubs}, first and last of each kind ${kindEnds}, from changed files ${changed}, fixed-seed ${rest.length}); the full estate is crawled when the pull request is ready for review`,\n };\n}\n\n/** A fixed-seed sample of other items (redirect rules), in their original order. */\nexport function sampleBySeed<T>(items: readonly T[], keyOf: (item: T) => string, input: { size?: number; seed?: string } = {}): T[] {\n const seed = input.seed ?? DEFAULT_SEED;\n const size = Math.max(0, Math.floor(input.size ?? DEFAULT_SIZE));\n const picked = new Set(\n items\n .map((item, index) => ({ index, rank: seededRank(seed, keyOf(item)) }))\n .sort((a, b) => a.rank.localeCompare(b.rank) || a.index - b.index)\n .slice(0, size)\n .map(({ index }) => index),\n );\n return items.filter((_, index) => picked.has(index));\n}\n\nfunction seededRank(seed: string, key: string): string {\n return createHash('sha256').update(`${seed}\\u0000${key}`).digest('hex');\n}\n\nfunction lastSegment(path: string): string {\n return normalise(path).split('/').filter(Boolean).at(-1)?.toLowerCase() ?? '';\n}\n\nfunction normalise(path: string): string {\n const pathname = path.split(/[?#]/)[0] || '/';\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : '/';\n}\n"]}
package/dist/estate.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { type APIRequestContext } from '@playwright/test';
2
2
  import { type LocationCapacityRecord, type SitemapFile, type SuburbCentreLookup } from './page-standard.js';
3
+ import { type BrandlabEstateSampleOptions } from './estate-sample.js';
4
+ export { changedFilesFromEnv, changedFileStem, defaultRoutesForFile, estateScopeFromEnv, sampleBySeed, sampleEstateRoutes, type BrandlabEstateSample, type BrandlabEstateSampleOptions, type BrandlabEstateSampleRoute, type BrandlabEstateScope, } from './estate-sample.js';
3
5
  export interface BrandlabEstateRoute {
4
6
  path: string;
5
7
  canonicalPath?: string;
@@ -79,6 +81,11 @@ export interface BrandlabEstateTestOptions {
79
81
  centreOf?: SuburbCentreLookup;
80
82
  /** The most inline markup one service-area map may carry (default 60 000 bytes, 3.6.0). */
81
83
  areaMapMaxBytes?: number;
84
+ /**
85
+ * The sampled crawl a draft pull request runs (`BRANDLAB_ESTATE_SCOPE=sample`, 3.13.0): how many routes and
86
+ * redirects the fixed seed adds, the seed, and which routes a changed file feeds.
87
+ */
88
+ sample?: BrandlabEstateSampleOptions;
82
89
  concurrency?: number;
83
90
  timeout?: number;
84
91
  }
@@ -167,6 +174,16 @@ export declare function redirectProbe(rule: Pick<BrandlabEstateRedirect, 'source
167
174
  * defineBrandlabPageQualityTests({ site, routes })
168
175
  */
169
176
  export declare function defineBrandlabPageQualityTests(options: BrandlabPageQualityTestOptions): void;
177
+ /**
178
+ * Whether the advisory page-quality crawl runs (3.13.0, client CI v2). CI v2 runs it on its own when a pull request
179
+ * is ready for review, on a manual run and monthly for large estates, and sets `BRANDLAB_PAGE_QUALITY=skip` for the required journeys so
180
+ * drafts and merges do not crawl every page twice. `BRANDLAB_PAGE_QUALITY_DIR` also writes its reports there.
181
+ */
182
+ export declare function pageQualityRunFromEnv(env?: NodeJS.ProcessEnv): {
183
+ run: boolean;
184
+ reason: string;
185
+ directory?: string;
186
+ };
170
187
  /**
171
188
  * Absolute (`https://host`) and protocol-relative (`//host`) destinations leave
172
189
  * the site; browsers also read `/\host` and `\\host` as protocol-relative.
@@ -1 +1 @@
1
- {"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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"}
package/dist/estate.js CHANGED
@@ -35,11 +35,21 @@
35
35
  * links from indexable pages to noindex location pages, centerpiece
36
36
  * similarity (warn at 0.5, review list at 0.7) and the location review list
37
37
  * (location-review.json); pages on that list stay indexed.
38
+ *
39
+ * From 3.13.0 (client CI v2) a draft pull request sets BRANDLAB_ESTATE_SCOPE=sample:
40
+ * the catalogue and redirect journeys check the hubs, the first and last route of
41
+ * each kind, the routes changed files feed and a fixed-seed sample (estate-sample.ts),
42
+ * and say so in an `estate-scope` annotation. Every other run crawls the full estate.
43
+ * BRANDLAB_PAGE_QUALITY=skip skips the advisory crawl, which CI v2 runs on its own.
38
44
  */
45
+ import { mkdirSync, writeFileSync } from 'node:fs';
39
46
  import { request as httpRequest } from 'node:http';
47
+ import { join } from 'node:path';
40
48
  import { expect, test } from '@playwright/test';
41
49
  import { areaMapProblems, buildLocationReviewList, centerpieceSimilarity, checkLocationCapacity, descriptionLengthProblem, HTML_MAX_BYTES, headingTexts, loadBrandlabGeography, neighbourOrderProblems, parseSitemapEntries, SIMILARITY_REVIEW, SIMILARITY_WARN, sitemapFamilyProblems, sitemapLastmodProblems, structuredDataProblems, textBlocks, titleFormatProblems, titleHeadingProblems, } from './page-standard.js';
42
50
  import { extractSearchCertificationHtmlSnapshot } from './search-certification.js';
51
+ import { changedFilesFromEnv, estateScopeFromEnv, sampleBySeed, sampleEstateRoutes } from './estate-sample.js';
52
+ export { changedFilesFromEnv, changedFileStem, defaultRoutesForFile, estateScopeFromEnv, sampleBySeed, sampleEstateRoutes, } from './estate-sample.js';
43
53
  const LOCATION_KINDS = new Set(['location', 'service-location']);
44
54
  /** A location route's place slug: the last segment of its path. */
45
55
  function routePlace(route) {
@@ -72,6 +82,11 @@ export function defineBrandlabEstateTests(options) {
72
82
  const familyPaths = new Set(families.flatMap((family) => family.paths.map(normalisePath)));
73
83
  const routeByPath = new Map(routes.map((route) => [normalisePath(route.path), route]));
74
84
  const homePath = normalisePath(routes.find((route) => route.kind === 'home')?.path ?? '/');
85
+ // A draft pull request crawls a sample (BRANDLAB_ESTATE_SCOPE=sample); ready for review crawls every route.
86
+ const scope = estateScopeFromEnv();
87
+ const sample = sampleEstateRoutes(routes, { scope: scope.scope, changedFiles: changedFilesFromEnv(), ...options.sample });
88
+ const scopeNote = [sample.summary, scope.note].filter(Boolean).join('; ');
89
+ const crawledRedirects = scope.scope === 'sample' ? sampleBySeed(redirects, (rule) => rule.source, options.sample) : redirects;
75
90
  test.describe('Brandlab technical estate', () => {
76
91
  test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {
77
92
  test.setTimeout(timeout);
@@ -107,7 +122,8 @@ export function defineBrandlabEstateTests(options) {
107
122
  const internalLinks = new Map();
108
123
  const checked = [];
109
124
  const areaMapLayers = new Map();
110
- await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {
125
+ testInfo.annotations.push({ type: 'estate-scope', description: scopeNote });
126
+ await eachWithConcurrency(sample.routes, options.concurrency ?? 8, async (route) => {
111
127
  const [get, head] = await Promise.all([
112
128
  request.get(route.path, { maxRedirects: 0 }),
113
129
  request.head(route.path, { maxRedirects: 0 }),
@@ -223,7 +239,10 @@ export function defineBrandlabEstateTests(options) {
223
239
  test.setTimeout(timeout);
224
240
  const failures = [];
225
241
  const skipped = [];
226
- for (const rule of redirects) {
242
+ if (scope.scope === 'sample') {
243
+ testInfo.annotations.push({ type: 'estate-scope', description: `sample: ${crawledRedirects.length} of ${redirects.length} redirects (fixed seed); every redirect is checked when the pull request is ready for review` });
244
+ }
245
+ for (const rule of crawledRedirects) {
227
246
  const probe = redirectProbe(rule);
228
247
  if (!probe) {
229
248
  skipped.push(`${rule.source} → ${rule.destination}`);
@@ -556,8 +575,10 @@ export function defineBrandlabPageQualityTests(options) {
556
575
  const timeout = options.timeout ?? 240_000;
557
576
  const routes = options.routes.filter((route) => normalisePath(route.path) !== NOT_FOUND_ROUTE);
558
577
  const routeByPath = new Map(routes.map((route) => [normalisePath(route.path), route]));
578
+ const run = pageQualityRunFromEnv();
559
579
  test.describe('Brandlab page quality (advisory)', () => {
560
580
  test('reports page and structured-data quality without failing', async ({ request }, testInfo) => {
581
+ test.skip(!run.run, run.reason);
561
582
  test.setTimeout(timeout);
562
583
  const findings = [];
563
584
  const geography = await loadBrandlabGeography();
@@ -673,19 +694,23 @@ export function defineBrandlabPageQualityTests(options) {
673
694
  findings.push({ path: item.path, kind: 'location-review', detail: item.reasons.join('; ') });
674
695
  findings.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));
675
696
  const byKind = Object.fromEntries([...new Set(findings.map((finding) => finding.kind))].sort().map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]));
676
- await testInfo.attach('page-quality.json', {
677
- body: JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),
678
- contentType: 'application/json',
679
- });
680
- await testInfo.attach('location-review.json', {
681
- body: JSON.stringify({
697
+ const reports = {
698
+ 'page-quality.json': JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),
699
+ 'location-review.json': JSON.stringify({
682
700
  reviewed: review.length,
683
701
  note: 'Pages on the review list stay indexed. Each review ends in adding evidence, merging into the nearest owner or hiding with noindex, follow; merging and hiding are Romie\'s decision.',
684
702
  similarity: similarity.sort((a, b) => b.score - a.score),
685
703
  review,
686
704
  }, null, 2),
687
- contentType: 'application/json',
688
- });
705
+ };
706
+ for (const [name, body] of Object.entries(reports))
707
+ await testInfo.attach(name, { body, contentType: 'application/json' });
708
+ // CI's page-quality run keeps the reports beside the ready report (BRANDLAB_PAGE_QUALITY_DIR=docs/ready).
709
+ if (run.directory) {
710
+ mkdirSync(run.directory, { recursive: true });
711
+ for (const [name, body] of Object.entries(reports))
712
+ writeFileSync(join(run.directory, name), `${body}\n`);
713
+ }
689
714
  testInfo.annotations.push({
690
715
  type: 'advisory',
691
716
  description: `${findings.length} page-quality finding(s) across ${routes.length} page(s)${findings.length ? `: ${Object.entries(byKind).map(([kind, count]) => `${kind} ${count}`).join(', ')}` : ''}; see page-quality.json.`,
@@ -693,6 +718,18 @@ export function defineBrandlabPageQualityTests(options) {
693
718
  });
694
719
  });
695
720
  }
721
+ /**
722
+ * Whether the advisory page-quality crawl runs (3.13.0, client CI v2). CI v2 runs it on its own when a pull request
723
+ * is ready for review, on a manual run and monthly for large estates, and sets `BRANDLAB_PAGE_QUALITY=skip` for the required journeys so
724
+ * drafts and merges do not crawl every page twice. `BRANDLAB_PAGE_QUALITY_DIR` also writes its reports there.
725
+ */
726
+ export function pageQualityRunFromEnv(env = process.env) {
727
+ const directory = env.BRANDLAB_PAGE_QUALITY_DIR?.trim() || undefined;
728
+ if ((env.BRANDLAB_PAGE_QUALITY ?? '').trim() === 'skip') {
729
+ return { run: false, reason: 'The advisory page-quality crawl runs on its own in CI (ready for review, manual runs and monthly for large estates); BRANDLAB_PAGE_QUALITY=skip.' };
730
+ }
731
+ return { run: true, reason: '', ...(directory ? { directory } : {}) };
732
+ }
696
733
  function inspectStructuredData(route, payloads, businessId) {
697
734
  const failures = [];
698
735
  if (payloads.some((payload) => payload.parseError))