@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.
- package/README.md +32 -1
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/edge-probe-cli.d.ts +57 -0
- package/dist/edge-probe-cli.d.ts.map +1 -0
- package/dist/edge-probe-cli.js +134 -0
- package/dist/edge-probe-cli.js.map +1 -0
- package/dist/engineering.d.ts +4 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +19 -14
- package/dist/engineering.js.map +1 -1
- package/dist/estate-sample.d.ts +60 -0
- package/dist/estate-sample.d.ts.map +1 -0
- package/dist/estate-sample.js +144 -0
- package/dist/estate-sample.js.map +1 -0
- package/dist/estate.d.ts +17 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +47 -10
- package/dist/estate.js.map +1 -1
- package/dist/origin-region.d.ts +14 -0
- package/dist/origin-region.d.ts.map +1 -0
- package/dist/origin-region.js +46 -0
- package/dist/origin-region.js.map +1 -0
- package/dist/prefetch-probe-cli.d.ts +36 -0
- package/dist/prefetch-probe-cli.d.ts.map +1 -0
- package/dist/prefetch-probe-cli.js +163 -0
- package/dist/prefetch-probe-cli.js.map +1 -0
- package/dist/site-system/checks.d.ts +36 -0
- package/dist/site-system/checks.d.ts.map +1 -1
- package/dist/site-system/checks.js +6 -0
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +27 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +50 -1
- package/dist/site-system/config.js.map +1 -1
- package/dist/site-system/index.d.ts +1 -1
- package/dist/site-system/index.d.ts.map +1 -1
- package/dist/site-system/index.js +1 -1
- package/dist/site-system/index.js.map +1 -1
- package/dist/site-system/journeys/plan.d.ts +9 -2
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +11 -1
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/prefetch-measure.d.ts +28 -0
- package/dist/site-system/journeys/prefetch-measure.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-measure.js +94 -0
- package/dist/site-system/journeys/prefetch-measure.js.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts +85 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.js +152 -0
- package/dist/site-system/journeys/prefetch-policy.js.map +1 -0
- package/dist/site-system/journeys/prefetch.d.ts +3 -0
- package/dist/site-system/journeys/prefetch.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch.js +37 -0
- package/dist/site-system/journeys/prefetch.js.map +1 -0
- package/dist/site-system/journeys/variants.d.ts +34 -0
- package/dist/site-system/journeys/variants.d.ts.map +1 -0
- package/dist/site-system/journeys/variants.js +139 -0
- package/dist/site-system/journeys/variants.js.map +1 -0
- package/dist/site-system/rule-modules.d.ts.map +1 -1
- package/dist/site-system/rule-modules.js +5 -0
- package/dist/site-system/rule-modules.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +3 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -1
- package/dist/site-system/rules/full/common.js.map +1 -1
- package/dist/site-system/rules/full/estate.d.ts +4 -0
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +11 -2
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +2 -1
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +14 -7
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/w3-refresh.d.ts +22 -0
- package/dist/site-system/rules/full/w3-refresh.d.ts.map +1 -0
- package/dist/site-system/rules/full/w3-refresh.js +70 -0
- package/dist/site-system/rules/full/w3-refresh.js.map +1 -0
- package/dist/site-system/rules/guards/index.d.ts +22 -0
- package/dist/site-system/rules/guards/index.d.ts.map +1 -0
- package/dist/site-system/rules/guards/index.js +109 -0
- package/dist/site-system/rules/guards/index.js.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts +55 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.js +182 -0
- package/dist/site-system/rules/guards/w10-crawlers.js.map +1 -0
- package/dist/site-system/rules/guards/w9-params.d.ts +19 -0
- package/dist/site-system/rules/guards/w9-params.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w9-params.js +84 -0
- package/dist/site-system/rules/guards/w9-params.js.map +1 -0
- package/dist/site-system-e2e.d.ts +1 -0
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +8 -2
- package/dist/site-system-e2e.js.map +1 -1
- 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.
|
package/dist/estate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
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))
|