@brandlab-digital/conformance 3.2.0 → 3.3.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/dist/analytics-e2e.d.ts.map +1 -1
- package/dist/analytics-e2e.js +8 -3
- package/dist/analytics-e2e.js.map +1 -1
- package/dist/contact-e2e.d.ts.map +1 -1
- package/dist/contact-e2e.js +3 -1
- package/dist/contact-e2e.js.map +1 -1
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/engineering.d.ts +2 -0
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +197 -7
- package/dist/engineering.js.map +1 -1
- package/dist/estate.d.ts +7 -19
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +38 -2
- package/dist/estate.js.map +1 -1
- package/dist/knip.d.ts.map +1 -1
- package/dist/knip.js +3 -0
- package/dist/knip.js.map +1 -1
- package/dist/rule-registry.d.ts +5 -0
- package/dist/rule-registry.d.ts.map +1 -1
- package/dist/rule-registry.js +1 -0
- package/dist/rule-registry.js.map +1 -1
- package/dist/static-rules.d.ts.map +1 -1
- package/dist/static-rules.js +35 -1
- package/dist/static-rules.js.map +1 -1
- package/dist/verification-policy.js +1 -1
- package/dist/verification-policy.js.map +1 -1
- package/package.json +1 -1
package/dist/estate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estate.js","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared technical-estate journey for a Brandlab production build:\n *\n * // tests/e2e/estate.spec.ts\n * import { defineBrandlabEstateTests } from '@brandlab-digital/conformance/estate'\n * import { site } from '../../src/config/site'\n * import { routes } from '../../src/data/routes'\n * import { redirects } from '../../src/data/redirects'\n *\n * defineBrandlabEstateTests({ site, routes, redirects })\n *\n * It asserts the estate and its routes: the sitemap, each route's status,\n * canonical and indexing, internal links, redirects, unknown slugs, the page\n * families rendered on request and robots.txt. Page and structured-data\n * quality is reported, never enforced,\n * by defineBrandlabPageQualityTests: a backend release keeps production's pages\n * as they are, which the comparison harness proves. Counts are always derived\n * from the current catalogue, never a historic constant.\n */\nimport { expect, test, type APIRequestContext } from '@playwright/test';\nimport { extractSearchCertificationHtmlSnapshot } from './search-certification.js';\n\nexport interface BrandlabEstateRoute {\n path: string;\n canonicalPath?: string;\n indexing: 'index' | 'noindex-follow' | 'noindex-nofollow';\n}\n\nexport interface BrandlabEstateRedirect {\n source: string;\n destination: string;\n status: 301 | 302 | 303 | 307 | 308;\n /** Host-scoped alias rules are proved by the comparison harness host probes. */\n host?: string;\n}\n\n/**\n * A page family the site renders on request outside the catalogue: a dynamic\n * route that prerenders nothing and renders every page of its space (every\n * combination of its values) when it is first requested.\n */\nexport interface BrandlabEstateFamily {\n /** The app route, e.g. `/services/[service]/[location]`. */\n pattern: string;\n /** Every page of the family's space; catalogued pages of the route may be among them. */\n paths: readonly string[];\n /** The indexing its pages outside the catalogue render. */\n indexing: BrandlabEstateRoute['indexing'];\n}\n\nexport interface BrandlabEstateTestOptions {\n site: { canonicalUrl: string | URL };\n /** The route catalogue. A `/404` route is Next's not-found page and must answer 404. */\n routes: readonly BrandlabEstateRoute[];\n /**\n * The redirect table. A source with parameters (`/old/:slug`, `:path*`) is\n * requested with one sample segment per parameter and checked against its\n * destination filled in the same way; a parameter with its own regular\n * expression cannot be sampled and is listed as skipped.\n */\n redirects?: readonly BrandlabEstateRedirect[];\n /** Paths that must answer 404; defaults to one unknown slug under every catalogue parent. */\n notFoundPaths?: readonly string[];\n /** Also prove `<source>/` redirects straight to the destination (the shared helper emits these). */\n trailingSlashRedirects?: boolean;\n /**\n * Page families rendered on request outside the catalogue. Links into a\n * family's space count as live pages; one page of each family (outside the\n * catalogue where it has one) is checked for its status, canonical and\n * indexing; and a path of its pattern outside the space, one unknown value\n * per dynamic segment, must answer 404 with the site's not-found page rendered\n * on the server: the text an unknown top-level path's 404 page shows.\n */\n families?: readonly BrandlabEstateFamily[];\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityTestOptions {\n site: { canonicalUrl: string | URL };\n routes: readonly BrandlabEstateRoute[];\n /** The @id every indexable page's graph references; defaults to `<origin>/#business`, `false` skips it. */\n businessId?: string | false;\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityFinding {\n path: string;\n kind: 'headings' | 'title' | 'description' | 'og-url' | 'implementation-wording' | 'structured-data';\n detail: string;\n}\n\nconst BUSINESS_TYPES = new Set([\n 'LocalBusiness', 'HomeAndConstructionBusiness', 'GeneralContractor', 'Plumber', 'Electrician',\n 'RoofingContractor', 'HousePainter', 'Locksmith', 'MovingCompany', 'HVACBusiness', 'ProfessionalService',\n]);\nconst UNKNOWN_SLUG = 'brandlab-estate-unknown-page';\nconst NOT_FOUND_ROUTE = '/404';\nconst PATTERN_SAMPLE = 'brandlab-estate-sample';\nconst ASSET_PATH = /\\.[a-z0-9]{2,5}$/i;\n\nexport function defineBrandlabEstateTests(options: BrandlabEstateTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const routes = options.routes;\n const knownPaths = new Set(routes.map((route) => normalisePath(route.path)));\n const redirects = (options.redirects ?? []).filter((rule) => !rule.host);\n const redirectBySource = new Map(\n redirects.filter((rule) => !isRedirectPattern(rule.source)).map((rule) => [normalisePath(rule.source), rule]),\n );\n const patternRedirects = redirects.flatMap((rule) => {\n const matcher = isRedirectPattern(rule.source) ? redirectSourceMatcher(rule.source) : undefined;\n return matcher ? [{ rule, matcher }] : [];\n });\n const findRedirect = (path: string) =>\n redirectBySource.get(path) ?? patternRedirects.find(({ matcher }) => matcher.test(path))?.rule;\n const timeout = options.timeout ?? 240_000;\n const families = options.families ?? [];\n const familyPaths = new Set(families.flatMap((family) => family.paths.map(normalisePath)));\n\n test.describe('Brandlab technical estate', () => {\n test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {\n test.setTimeout(timeout);\n const listed = await readSitemap(request, '/sitemap.xml', origin);\n const expected = routes\n .filter((route) => route.indexing === 'index')\n .map((route) => canonical(route.canonicalPath ?? route.path));\n expect(listed.length, 'the sitemap repeats a URL').toBe(new Set(listed).size);\n expect([...listed].sort()).toEqual([...new Set(expected)].sort());\n });\n\n test('every catalogue route serves its canonical identity and links only to live pages', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const internalLinks = new Map<string, string>();\n const checked: { path: string; canonical: string | null; jsonLd: number }[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const [get, head] = await Promise.all([\n request.get(route.path, { maxRedirects: 0 }),\n request.head(route.path, { maxRedirects: 0 }),\n ]);\n // Next serves its not-found page at /404 with a 404 status.\n const notFound = normalisePath(route.path) === NOT_FOUND_ROUTE;\n const expectedStatus = notFound ? 404 : 200;\n if (get.status() !== expectedStatus || head.status() !== expectedStatus) {\n failures.push(`${route.path}: GET ${get.status()}, HEAD ${head.status()} (expected ${expectedStatus})`);\n return;\n }\n const html = await get.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${get.headers()['x-robots-tag'] ?? ''}`;\n if (notFound) {\n if (!/noindex/i.test(robots)) failures.push(`${route.path}: the not-found page is indexable`);\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n return;\n }\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const canonicalTags = html.match(/<link\\b[^>]*\\brel=[\"']canonical[\"'][^>]*>/gi) ?? [];\n if (canonicalTags.length !== 1) failures.push(`${route.path}: ${canonicalTags.length} canonical links`);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${route.path}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (route.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${route.path}: indexable route is noindex`);\n if (route.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${route.path}: ${route.indexing} route is indexable`);\n for (const href of extractLinks(html)) {\n const path = internalPath(href, expectedCanonical, origin);\n if (path && !internalLinks.has(path)) internalLinks.set(path, route.path);\n }\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n });\n for (const [path, from] of [...internalLinks].sort()) {\n if (knownPaths.has(path) || familyPaths.has(path)) continue;\n const redirect = findRedirect(path);\n if (redirect && isExternalDestination(redirect.destination)) continue;\n failures.push(redirect\n ? `${from}: links to ${path}, which redirects; link to ${redirect.destination} directly`\n : `${from}: links to ${path}, which is not a catalogue route`);\n }\n await testInfo.attach('technical-estate.json', {\n body: JSON.stringify({ routes: checked.sort((a, b) => a.path.localeCompare(b.path)), internalLinks: [...internalLinks.keys()].sort() }, null, 2),\n contentType: 'application/json',\n });\n expect(failures).toEqual([]);\n });\n\n test('every redirect resolves in one hop and keeps the query string', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const skipped: string[] = [];\n for (const rule of redirects) {\n const probe = redirectProbe(rule);\n if (!probe) {\n skipped.push(`${rule.source} → ${rule.destination}`);\n continue;\n }\n const sources = [probe.source, ...(options.trailingSlashRedirects === false || probe.source === '/' || probe.source.endsWith('/') ? [] : [`${probe.source}/`])];\n for (const source of sources) {\n for (const method of ['GET', 'HEAD'] as const) {\n const response = await request.fetch(`${source}?brandlab_estate=1`, { method, maxRedirects: 0 });\n const location = response.headers().location;\n const expectedStatus: number[] = source === probe.source ? [rule.status] : [301, 308];\n if (!expectedStatus.includes(response.status()) || !location) {\n failures.push(`${method} ${source}: ${response.status()} (expected ${expectedStatus.join(' or ')} to ${probe.destination})`);\n continue;\n }\n const target = new URL(location, origin);\n if (isExternalDestination(probe.destination)) {\n if (!target.href.startsWith(new URL(probe.destination, origin).href.replace(/\\/$/, ''))) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n continue;\n }\n if (target.pathname !== new URL(probe.destination, origin).pathname) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n if (target.searchParams.get('brandlab_estate') !== '1') failures.push(`${method} ${source}: the query string was dropped`);\n }\n }\n // A destination with parameters names many pages; the sample fills it with a page that does not exist.\n if (!isExternalDestination(probe.destination) && !probe.sampled) {\n const destination = await request.get(probe.destination, { maxRedirects: 0 });\n if (destination.status() !== 200) failures.push(`${rule.source}: destination ${probe.destination} answers ${destination.status()} (a chain or a missing page)`);\n }\n }\n if (skipped.length > 0) {\n testInfo.annotations.push({ type: 'skipped-redirects', description: `${skipped.length} redirect(s) with a parameter pattern that cannot be sampled: ${skipped.join('; ')}` });\n }\n expect(failures).toEqual([]);\n });\n\n if (families.length > 0) {\n test('every page family rendered on request serves its pages and answers 404 outside its space', async ({ request }) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const reference = await request.get(`/${UNKNOWN_SLUG}`, { maxRedirects: 0 });\n const notFoundText = reference.status() === 404 ? bodyText(await reference.text()) : '';\n if (!notFoundText) failures.push(`/${UNKNOWN_SLUG}: ${reference.status()} with no server-rendered not-found page to compare with`);\n for (const family of families) {\n const sample = familySamplePath(family, knownPaths);\n if (!sample) {\n failures.push(`${family.pattern}: the family lists no page`);\n continue;\n }\n const response = await request.get(sample, { maxRedirects: 0 });\n if (response.status() !== 200) {\n failures.push(`${sample}: ${response.status()} (expected 200 for a page of ${family.pattern})`);\n } else {\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${response.headers()['x-robots-tag'] ?? ''}`;\n const expectedCanonical = canonical(sample);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${sample}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (family.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${sample}: the family's pages are indexable, this one is noindex`);\n if (family.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${sample}: the family's pages are ${family.indexing}, this one is indexable`);\n }\n for (const outside of familyNotFoundPaths(family)) {\n const miss = await request.get(outside, { maxRedirects: 0 });\n if (miss.status() !== 404) {\n failures.push(`${outside}: ${miss.status()} (expected 404 outside ${family.pattern}'s space)`);\n continue;\n }\n const text = bodyText(await miss.text());\n if (notFoundText && text !== notFoundText) {\n failures.push(`${outside}: the 404 is not the site's not-found page rendered on the server (${text ? `it shows \"${text.slice(0, 80)}\"` : 'its body is empty until the browser renders it'})`);\n }\n }\n }\n expect(failures).toEqual([]);\n });\n }\n\n test('unknown slugs answer 404 instead of inventing pages', async ({ request }) => {\n const paths = options.notFoundPaths ?? defaultNotFoundPaths(routes);\n const failures: string[] = [];\n for (const path of paths) {\n const response = await request.get(path, { maxRedirects: 0 });\n if (response.status() !== 404) failures.push(`${path}: ${response.status()} (expected 404)`);\n }\n expect(failures).toEqual([]);\n });\n\n test('robots.txt allows crawling and names the sitemap', async ({ request }) => {\n const response = await request.get('/robots.txt');\n expect(response.status()).toBe(200);\n const body = await response.text();\n expect(body).toMatch(new RegExp(`^sitemap:\\\\s*${escapeRegExp(`${origin}/sitemap.xml`)}\\\\s*$`, 'im'));\n expect(body).not.toMatch(/^disallow:\\s*\\/\\s*$/im);\n });\n });\n}\n\n/**\n * Every `<loc>` in a sitemap, following one level of sitemap index. Nothing is\n * followed through a redirect, and with `origin` every nested sitemap must be\n * on that origin (it is then fetched from the build under test).\n */\nexport async function readSitemap(request: APIRequestContext, path: string, origin?: string): Promise<string[]> {\n const read = async (target: string) => {\n const response = await request.get(target, { maxRedirects: 0 });\n if (response.status() !== 200) throw new Error(`${target} answers ${response.status()}; a sitemap must answer 200 without redirecting.`);\n return response.text();\n };\n const xml = await read(path);\n const locations = extractSitemapLocations(xml);\n if (!/<sitemapindex\\b/i.test(xml)) return locations;\n const nested: string[] = [];\n for (const location of locations) {\n const url = new URL(location);\n if (origin && url.origin !== new URL(origin).origin) {\n throw new Error(`The sitemap index lists ${location}, which is not on ${new URL(origin).origin}.`);\n }\n nested.push(...extractSitemapLocations(await read(`${url.pathname}${url.search}`)));\n }\n return nested;\n}\n\nexport function extractSitemapLocations(xml: string): string[] {\n return [...xml.matchAll(/<loc>\\s*([^<\\s]+)\\s*<\\/loc>/gi)].map((match) =>\n new URL(decodeXml(match[1]!)).href);\n}\n\nexport function defaultNotFoundPaths(routes: readonly BrandlabEstateRoute[]): string[] {\n const parents = new Set(['']);\n for (const route of routes) {\n const segments = normalisePath(route.path).split('/').filter(Boolean);\n if (segments.length > 1) parents.add(`/${segments.slice(0, -1).join('/')}`);\n if (segments.length === 1) parents.add(`/${segments[0]}`);\n }\n return [...parents].sort().map((parent) => `${parent}/${UNKNOWN_SLUG}`);\n}\n\n/** The page of a family its estate check opens: the first one outside the catalogue, else its first page. */\nexport function familySamplePath(family: Pick<BrandlabEstateFamily, 'paths'>, catalogue: ReadonlySet<string>): string | undefined {\n return family.paths.find((path) => !catalogue.has(normalisePath(path))) ?? family.paths[0];\n}\n\n/**\n * Paths of a family's pattern outside its space: a page of the family with one\n * dynamic segment at a time replaced by an unknown slug.\n */\nexport function familyNotFoundPaths(family: Pick<BrandlabEstateFamily, 'pattern' | 'paths'>): string[] {\n const first = family.paths[0];\n if (!first) return [];\n const parts = normalisePath(first).split('/');\n const segments = family.pattern.split('/');\n const paths = segments.flatMap((segment, index) => (/^\\[[^.\\]]/.test(segment) ? [parts.map((part, position) => (position === index ? UNKNOWN_SLUG : part)).join('/')] : []));\n const space = new Set(family.paths.map(normalisePath));\n return [...new Set(paths)].filter((path) => !space.has(path));\n}\n\n/**\n * The text a page shows before any script runs: its body without scripts,\n * styles and templates, tags removed and whitespace collapsed. Empty for the\n * document Next sends when it leaves a page to the browser to render.\n */\nexport function bodyText(html: string): string {\n const body = /<body\\b[^>]*>([\\s\\S]*?)(?:<\\/body>|$)/i.exec(html)?.[1] ?? (/<html\\b/i.test(html) ? '' : html);\n return decodeXml(body\n .replace(/<(script|style|template|noscript)\\b[\\s\\S]*?<\\/\\1>/gi, ' ')\n .replace(/<[^>]+>/g, ' '))\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */\nexport function isRedirectPattern(path: string): boolean {\n return /:[A-Za-z_]\\w*|\\(/.test(path.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, ''));\n}\n\n/**\n * The request that proves a redirect rule: a pattern gets one sample segment\n * per parameter, in its source and destination alike. Undefined when a\n * parameter carries its own regular expression, which no sample can satisfy\n * with certainty.\n */\nexport function redirectProbe(rule: Pick<BrandlabEstateRedirect, 'source' | 'destination'>): { source: string; destination: string; sampled: boolean } | undefined {\n const sample = (path: string) => {\n if (!isRedirectPattern(path)) return path;\n // Keep an external destination's scheme and host; parameters live in the path.\n const origin = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(path)?.[0] ?? '';\n const rest = path.slice(origin.length);\n if (rest.includes('(')) return undefined;\n return `${origin}${rest.replace(/:[A-Za-z_]\\w*[*+?]?/g, PATTERN_SAMPLE)}`;\n };\n const source = sample(rule.source);\n const destination = sample(rule.destination);\n if (source === undefined || destination === undefined) return undefined;\n return { source, destination, sampled: isRedirectPattern(rule.destination) };\n}\n\n/** A matcher for paths a pattern source redirects, for internal-link checks. */\nfunction redirectSourceMatcher(source: string): RegExp | undefined {\n if (source.includes('(')) return undefined;\n const pattern = normalisePath(source).split(/(:[A-Za-z_]\\w*[*+?]?)/).map((part) => {\n const parameter = /^:[A-Za-z_]\\w*([*+?]?)$/.exec(part);\n if (!parameter) return escapeRegExp(part);\n return parameter[1] === '*' || parameter[1] === '+' ? '.+' : parameter[1] === '?' ? '[^/]*' : '[^/]+';\n }).join('');\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * The advisory page-quality journey: headings, title, description, og:url,\n * implementation wording and structured-data identity for every catalogue\n * page. It reports what it finds (page-quality.json and an annotation) and\n * never fails, because a backend release keeps production's pages as they\n * are; the comparison harness proves them unchanged.\n *\n * // tests/e2e/page-quality.spec.ts\n * defineBrandlabPageQualityTests({ site, routes })\n */\nexport function defineBrandlabPageQualityTests(options: BrandlabPageQualityTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const businessId = options.businessId ?? `${origin}/#business`;\n const timeout = options.timeout ?? 240_000;\n const routes = options.routes.filter((route) => normalisePath(route.path) !== NOT_FOUND_ROUTE);\n\n test.describe('Brandlab page quality (advisory)', () => {\n test('reports page and structured-data quality without failing', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const findings: BrandlabPageQualityFinding[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const response = await request.get(route.path, { maxRedirects: 0 });\n // The estate journey owns status codes; quality is read from the pages that load.\n if (response.status() !== 200) return;\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const add = (kind: BrandlabPageQualityFinding['kind'], detail: string) => findings.push({ path: route.path, kind, detail });\n const headings = html.match(/<h1(?:\\s|>)/gi)?.length ?? 0;\n if (headings !== 1) add('headings', `${headings} h1 headings`);\n if (!snapshot.title) add('title', 'no title');\n if (!snapshot.description) add('description', 'no meta description');\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const ogUrl = snapshot.openGraph['og:url'];\n if (ogUrl && new URL(ogUrl, origin).href !== expectedCanonical) add('og-url', `og:url ${ogUrl} is not the canonical URL`);\n if (snapshot.implementationCopyMatches.length > 0) add('implementation-wording', snapshot.implementationCopyMatches.join(', '));\n for (const failure of inspectStructuredData(route, snapshot.jsonLd, businessId)) {\n add('structured-data', failure.slice(route.path.length + 2));\n }\n });\n findings.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));\n const byKind = Object.fromEntries(\n [...new Set(findings.map((finding) => finding.kind))].sort().map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]),\n );\n await testInfo.attach('page-quality.json', {\n body: JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),\n contentType: 'application/json',\n });\n testInfo.annotations.push({\n type: 'advisory',\n 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.`,\n });\n });\n });\n}\n\nfunction inspectStructuredData(route: BrandlabEstateRoute, payloads: readonly unknown[], businessId: string | false): string[] {\n const failures: string[] = [];\n if (payloads.some((payload) => (payload as { parseError?: boolean }).parseError)) failures.push(`${route.path}: invalid JSON-LD`);\n if (route.indexing === 'index' && payloads.length === 0) failures.push(`${route.path}: no JSON-LD`);\n const ids: string[] = [];\n let referencesBusiness = false;\n const visit = (value: unknown) => {\n if (!value || typeof value !== 'object') return;\n if (Array.isArray(value)) { value.forEach(visit); return; }\n const node = value as Record<string, unknown>;\n const types = ([] as unknown[]).concat(node['@type'] ?? []).map(String);\n if (typeof node['@id'] === 'string') {\n if (node['@id'] === businessId) referencesBusiness = true;\n if (types.length > 0) ids.push(node['@id']);\n }\n if (businessId && types.some((type) => BUSINESS_TYPES.has(type)) && node['@id'] !== businessId) {\n failures.push(`${route.path}: business node @id ${String(node['@id'] ?? 'missing')} (expected ${businessId})`);\n }\n Object.values(node).forEach(visit);\n };\n payloads.forEach(visit);\n const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);\n if (duplicate) failures.push(`${route.path}: JSON-LD repeats node ${duplicate}`);\n if (businessId && route.indexing === 'index' && payloads.length > 0 && !referencesBusiness) failures.push(`${route.path}: JSON-LD does not reference ${businessId}`);\n return failures;\n}\n\nfunction extractLinks(html: string): string[] {\n return [...html.matchAll(/<a\\b[^>]*\\bhref=[\"']([^\"']+)[\"']/gi)].map((match) => decodeXml(match[1]!));\n}\n\nfunction internalPath(href: string, base: string, origin: string): string | null {\n if (/^(?:mailto|tel|sms|javascript):/i.test(href) || href.startsWith('#')) return null;\n let url: URL;\n try { url = new URL(href, base); } catch { return null; }\n if (url.origin !== origin && !['127.0.0.1', 'localhost'].includes(url.hostname)) return null;\n if (ASSET_PATH.test(url.pathname) || url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/')) return null;\n return normalisePath(url.pathname);\n}\n\nfunction normalisePath(path: string): string {\n const pathname = path.split(/[?#]/)[0] || '/';\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : '/';\n}\n\n/**\n * Absolute (`https://host`) and protocol-relative (`//host`) destinations leave\n * the site; browsers also read `/\\host` and `\\\\host` as protocol-relative.\n */\nexport function isExternalDestination(destination: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?[\\\\/]{2}/i.test(destination.trim());\n}\n\nfunction decodeXml(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, \"'\");\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nasync function eachWithConcurrency<T>(items: readonly T[], limit: number, task: (item: T) => Promise<void>) {\n const queue = [...items];\n await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, queue.length)) }, async () => {\n for (let item = queue.shift(); item !== undefined; item = queue.shift()) await task(item);\n }));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"estate.js","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared technical-estate journey for a Brandlab production build:\n *\n * // tests/e2e/estate.spec.ts\n * import { defineBrandlabEstateTests } from '@brandlab-digital/conformance/estate'\n * import { site } from '../../src/config/site'\n * import { routes } from '../../src/data/routes'\n * import { redirects } from '../../src/data/redirects'\n *\n * defineBrandlabEstateTests({ site, routes, redirects })\n *\n * It asserts the estate and its routes: the sitemap, each route's status,\n * canonical and indexing, internal links, redirects, unknown slugs, the page\n * families rendered on request and robots.txt. Page and structured-data\n * quality is reported, never enforced,\n * by defineBrandlabPageQualityTests: a backend release keeps production's pages\n * as they are, which the comparison harness proves. Counts are always derived\n * from the current catalogue, never a historic constant.\n */\nimport { request as httpRequest } from 'node:http';\nimport { expect, test, type APIRequestContext } from '@playwright/test';\nimport { extractSearchCertificationHtmlSnapshot } from './search-certification.js';\n\nexport interface BrandlabEstateRoute {\n path: string;\n canonicalPath?: string;\n indexing: 'index' | 'noindex-follow' | 'noindex-nofollow';\n}\n\nexport interface BrandlabEstateRedirect {\n source: string;\n destination: string;\n status: 301 | 302 | 303 | 307 | 308;\n /** Host-scoped alias rules are proved by the comparison harness host probes. */\n host?: string;\n}\n\n/**\n * A page family the site renders on request outside the catalogue: a dynamic\n * route that prerenders nothing and renders every page of its space (every\n * combination of its values) when it is first requested.\n */\nexport interface BrandlabEstateFamily {\n /** The app route, e.g. `/services/[service]/[location]`. */\n pattern: string;\n /** Every page of the family's space; catalogued pages of the route may be among them. */\n paths: readonly string[];\n /** The indexing its pages outside the catalogue render. */\n indexing: BrandlabEstateRoute['indexing'];\n}\n\nexport interface BrandlabEstateTestOptions {\n site: { canonicalUrl: string | URL };\n /** The route catalogue. A `/404` route is Next's not-found page and must answer 404. */\n routes: readonly BrandlabEstateRoute[];\n /**\n * The redirect table. A source with parameters (`/old/:slug`, `:path*`) is\n * requested with one sample segment per parameter and checked against its\n * destination filled in the same way; a parameter with its own regular\n * expression cannot be sampled and is listed as skipped.\n */\n redirects?: readonly BrandlabEstateRedirect[];\n /** Paths that must answer 404; defaults to one unknown slug under every catalogue parent. */\n notFoundPaths?: readonly string[];\n /** Also prove `<source>/` redirects straight to the destination (the shared helper emits these). */\n trailingSlashRedirects?: boolean;\n /**\n * Page families rendered on request outside the catalogue. Links into a\n * family's space count as live pages; one page of each family (outside the\n * catalogue where it has one) is checked for its status, canonical and\n * indexing; and a path of its pattern outside the space, one unknown value\n * per dynamic segment, must answer 404 with the site's not-found page rendered\n * on the server: the text an unknown top-level path's 404 page shows.\n */\n families?: readonly BrandlabEstateFamily[];\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityTestOptions {\n site: { canonicalUrl: string | URL };\n routes: readonly BrandlabEstateRoute[];\n /** The @id every indexable page's graph references; defaults to `<origin>/#business`, `false` skips it. */\n businessId?: string | false;\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityFinding {\n path: string;\n kind: 'headings' | 'title' | 'description' | 'og-url' | 'implementation-wording' | 'structured-data';\n detail: string;\n}\n\nconst BUSINESS_TYPES = new Set([\n 'LocalBusiness', 'HomeAndConstructionBusiness', 'GeneralContractor', 'Plumber', 'Electrician',\n 'RoofingContractor', 'HousePainter', 'Locksmith', 'MovingCompany', 'HVACBusiness', 'ProfessionalService',\n]);\nconst UNKNOWN_SLUG = 'brandlab-estate-unknown-page';\nconst NOT_FOUND_ROUTE = '/404';\nconst PATTERN_SAMPLE = 'brandlab-estate-sample';\nconst ASSET_PATH = /\\.[a-z0-9]{2,5}$/i;\n\nexport function defineBrandlabEstateTests(options: BrandlabEstateTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const routes = options.routes;\n const knownPaths = new Set(routes.map((route) => normalisePath(route.path)));\n const redirects = (options.redirects ?? []).filter((rule) => !rule.host);\n const redirectBySource = new Map(\n redirects.filter((rule) => !isRedirectPattern(rule.source)).map((rule) => [normalisePath(rule.source), rule]),\n );\n const patternRedirects = redirects.flatMap((rule) => {\n const matcher = isRedirectPattern(rule.source) ? redirectSourceMatcher(rule.source) : undefined;\n return matcher ? [{ rule, matcher }] : [];\n });\n const findRedirect = (path: string) =>\n redirectBySource.get(path) ?? patternRedirects.find(({ matcher }) => matcher.test(path))?.rule;\n const timeout = options.timeout ?? 240_000;\n const families = options.families ?? [];\n const familyPaths = new Set(families.flatMap((family) => family.paths.map(normalisePath)));\n\n test.describe('Brandlab technical estate', () => {\n test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {\n test.setTimeout(timeout);\n const listed = await readSitemap(request, '/sitemap.xml', origin);\n const expected = routes\n .filter((route) => route.indexing === 'index')\n .map((route) => canonical(route.canonicalPath ?? route.path));\n expect(listed.length, 'the sitemap repeats a URL').toBe(new Set(listed).size);\n expect([...listed].sort()).toEqual([...new Set(expected)].sort());\n });\n\n test('every catalogue route serves its canonical identity and links only to live pages', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const internalLinks = new Map<string, string>();\n const checked: { path: string; canonical: string | null; jsonLd: number }[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const [get, head] = await Promise.all([\n request.get(route.path, { maxRedirects: 0 }),\n request.head(route.path, { maxRedirects: 0 }),\n ]);\n // Next serves its not-found page at /404 with a 404 status.\n const notFound = normalisePath(route.path) === NOT_FOUND_ROUTE;\n const expectedStatus = notFound ? 404 : 200;\n if (get.status() !== expectedStatus || head.status() !== expectedStatus) {\n failures.push(`${route.path}: GET ${get.status()}, HEAD ${head.status()} (expected ${expectedStatus})`);\n return;\n }\n const html = await get.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${get.headers()['x-robots-tag'] ?? ''}`;\n if (notFound) {\n if (!/noindex/i.test(robots)) failures.push(`${route.path}: the not-found page is indexable`);\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n return;\n }\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const canonicalTags = html.match(/<link\\b[^>]*\\brel=[\"']canonical[\"'][^>]*>/gi) ?? [];\n if (canonicalTags.length !== 1) failures.push(`${route.path}: ${canonicalTags.length} canonical links`);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${route.path}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (route.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${route.path}: indexable route is noindex`);\n if (route.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${route.path}: ${route.indexing} route is indexable`);\n for (const href of extractLinks(html)) {\n const path = internalPath(href, expectedCanonical, origin);\n if (path && !internalLinks.has(path)) internalLinks.set(path, route.path);\n }\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n });\n for (const [path, from] of [...internalLinks].sort()) {\n if (knownPaths.has(path) || familyPaths.has(path)) continue;\n const redirect = findRedirect(path);\n if (redirect && isExternalDestination(redirect.destination)) continue;\n failures.push(redirect\n ? `${from}: links to ${path}, which redirects; link to ${redirect.destination} directly`\n : `${from}: links to ${path}, which is not a catalogue route`);\n }\n await testInfo.attach('technical-estate.json', {\n body: JSON.stringify({ routes: checked.sort((a, b) => a.path.localeCompare(b.path)), internalLinks: [...internalLinks.keys()].sort() }, null, 2),\n contentType: 'application/json',\n });\n expect(failures).toEqual([]);\n });\n\n test('every redirect resolves in one hop and keeps the query string', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const skipped: string[] = [];\n for (const rule of redirects) {\n const probe = redirectProbe(rule);\n if (!probe) {\n skipped.push(`${rule.source} → ${rule.destination}`);\n continue;\n }\n const sources = [probe.source, ...(options.trailingSlashRedirects === false || probe.source === '/' || probe.source.endsWith('/') ? [] : [`${probe.source}/`])];\n for (const source of sources) {\n for (const method of ['GET', 'HEAD'] as const) {\n const response = await request.fetch(`${source}?brandlab_estate=1`, { method, maxRedirects: 0 });\n const location = response.headers().location;\n const expectedStatus: number[] = source === probe.source ? [rule.status] : [301, 308];\n if (!expectedStatus.includes(response.status()) || !location) {\n failures.push(`${method} ${source}: ${response.status()} (expected ${expectedStatus.join(' or ')} to ${probe.destination})`);\n continue;\n }\n const target = new URL(location, origin);\n if (isExternalDestination(probe.destination)) {\n if (!target.href.startsWith(new URL(probe.destination, origin).href.replace(/\\/$/, ''))) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n continue;\n }\n if (target.pathname !== new URL(probe.destination, origin).pathname) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n if (target.searchParams.get('brandlab_estate') !== '1') failures.push(`${method} ${source}: the query string was dropped`);\n }\n }\n // A destination with parameters names many pages; the sample fills it with a page that does not exist.\n if (!isExternalDestination(probe.destination) && !probe.sampled) {\n const destination = await request.get(probe.destination, { maxRedirects: 0 });\n if (destination.status() !== 200) failures.push(`${rule.source}: destination ${probe.destination} answers ${destination.status()} (a chain or a missing page)`);\n }\n }\n if (skipped.length > 0) {\n testInfo.annotations.push({ type: 'skipped-redirects', description: `${skipped.length} redirect(s) with a parameter pattern that cannot be sampled: ${skipped.join('; ')}` });\n }\n expect(failures).toEqual([]);\n });\n\n if (families.length > 0) {\n test('every page family rendered on request serves its pages and answers 404 outside its space', async ({ request }) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const reference = await request.get(`/${UNKNOWN_SLUG}`, { maxRedirects: 0 });\n const notFoundText = reference.status() === 404 ? bodyText(await reference.text()) : '';\n if (!notFoundText) failures.push(`/${UNKNOWN_SLUG}: ${reference.status()} with no server-rendered not-found page to compare with`);\n for (const family of families) {\n const sample = familySamplePath(family, knownPaths);\n if (!sample) {\n failures.push(`${family.pattern}: the family lists no page`);\n continue;\n }\n const response = await request.get(sample, { maxRedirects: 0 });\n if (response.status() !== 200) {\n failures.push(`${sample}: ${response.status()} (expected 200 for a page of ${family.pattern})`);\n } else {\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${response.headers()['x-robots-tag'] ?? ''}`;\n const expectedCanonical = canonical(sample);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${sample}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (family.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${sample}: the family's pages are indexable, this one is noindex`);\n if (family.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${sample}: the family's pages are ${family.indexing}, this one is indexable`);\n }\n for (const outside of familyNotFoundPaths(family)) {\n const miss = await request.get(outside, { maxRedirects: 0 });\n if (miss.status() !== 404) {\n failures.push(`${outside}: ${miss.status()} (expected 404 outside ${family.pattern}'s space)`);\n continue;\n }\n const text = bodyText(await miss.text());\n if (notFoundText && text !== notFoundText) {\n failures.push(`${outside}: the 404 is not the site's not-found page rendered on the server (${text ? `it shows \"${text.slice(0, 80)}\"` : 'its body is empty until the browser renders it'})`);\n }\n }\n }\n expect(failures).toEqual([]);\n });\n }\n\n test('unknown slugs answer 404 instead of inventing pages', async ({ request }, testInfo) => {\n // A default probe a redirect rule covers (a wildcard such as /blog/:slug) is the redirect's to answer; the\n // redirect journey proves it. Paths the site lists itself are always probed.\n const { probes: paths, redirected } = options.notFoundPaths\n ? { probes: options.notFoundPaths, redirected: [] }\n : unknownSlugProbes(defaultNotFoundPaths(routes), (path) => Boolean(findRedirect(path)));\n for (const path of redirected) testInfo.annotations.push({ type: 'skipped', description: `${path}: a redirect rule answers it` });\n const failures: string[] = [];\n for (const path of paths) {\n const response = await request.get(path, { maxRedirects: 0 });\n if (response.status() !== 404) failures.push(`${path}: ${response.status()} (expected 404)`);\n }\n expect(failures).toEqual([]);\n });\n\n test('robots.txt allows crawling and names the sitemap', async ({ request }) => {\n const response = await request.get('/robots.txt');\n expect(response.status()).toBe(200);\n const body = await response.text();\n expect(body).toMatch(new RegExp(`^sitemap:\\\\s*${escapeRegExp(`${origin}/sitemap.xml`)}\\\\s*$`, 'im'));\n expect(body).not.toMatch(/^disallow:\\s*\\/\\s*$/im);\n });\n\n test('a production deployment tells crawlers not to index any host but its canonical one', async ({ baseURL }) => {\n test.skip(process.env.VERCEL_ENV !== 'production', 'Only a production build (VERCEL_ENV=production, as CI builds it) answers other hosts with noindex.');\n const server = new URL(baseURL ?? 'http://127.0.0.1:3100/');\n const canonicalHost = new URL(origin).hostname;\n // The production *.vercel.app alias serves the full site; its pages must not be indexed.\n const alias = `${canonicalHost.split('.')[0]}-brandlab-e2e.vercel.app`;\n expect(await responseHeader(server, alias, '/', 'x-robots-tag'), `${alias}: X-Robots-Tag`).toBe('noindex');\n expect(await responseHeader(server, canonicalHost, '/', 'x-robots-tag'), `${canonicalHost}: X-Robots-Tag`).not.toMatch(/noindex/);\n });\n });\n}\n\n/** One response header for a request that names its own Host (the Playwright request context always sends the server's). */\nexport function responseHeader(server: URL, host: string, path: string, name: string): Promise<string | undefined> {\n return new Promise((resolve, reject) => {\n const request = httpRequest({ hostname: server.hostname, port: server.port || 80, path, method: 'GET', headers: { host, 'x-forwarded-proto': 'https' } }, (response) => {\n response.resume();\n const value = response.headers[name.toLowerCase()];\n resolve(Array.isArray(value) ? value.join(', ') : value);\n });\n request.on('error', reject);\n request.end();\n });\n}\n\n/**\n * Every `<loc>` in a sitemap, following one level of sitemap index. Nothing is\n * followed through a redirect, and with `origin` every nested sitemap must be\n * on that origin (it is then fetched from the build under test).\n */\nexport async function readSitemap(request: APIRequestContext, path: string, origin?: string): Promise<string[]> {\n const read = async (target: string) => {\n const response = await request.get(target, { maxRedirects: 0 });\n if (response.status() !== 200) throw new Error(`${target} answers ${response.status()}; a sitemap must answer 200 without redirecting.`);\n return response.text();\n };\n const xml = await read(path);\n const locations = extractSitemapLocations(xml);\n if (!/<sitemapindex\\b/i.test(xml)) return locations;\n const nested: string[] = [];\n for (const location of locations) {\n const url = new URL(location);\n if (origin && url.origin !== new URL(origin).origin) {\n throw new Error(`The sitemap index lists ${location}, which is not on ${new URL(origin).origin}.`);\n }\n nested.push(...extractSitemapLocations(await read(`${url.pathname}${url.search}`)));\n }\n return nested;\n}\n\nexport function extractSitemapLocations(xml: string): string[] {\n return [...xml.matchAll(/<loc>\\s*([^<\\s]+)\\s*<\\/loc>/gi)].map((match) =>\n new URL(decodeXml(match[1]!)).href);\n}\n\nexport function defaultNotFoundPaths(routes: readonly BrandlabEstateRoute[]): string[] {\n const parents = new Set(['']);\n for (const route of routes) {\n const segments = normalisePath(route.path).split('/').filter(Boolean);\n if (segments.length > 1) parents.add(`/${segments.slice(0, -1).join('/')}`);\n if (segments.length === 1) parents.add(`/${segments[0]}`);\n }\n return [...parents].sort().map((parent) => `${parent}/${UNKNOWN_SLUG}`);\n}\n\n/** The unknown-slug probes to request, apart from those a redirect rule answers. */\nexport function unknownSlugProbes(paths: readonly string[], redirects: (path: string) => boolean): { probes: string[]; redirected: string[] } {\n const probes: string[] = [];\n const redirected: string[] = [];\n for (const path of paths) (redirects(normalisePath(path)) ? redirected : probes).push(path);\n return { probes, redirected };\n}\n\n/** The page of a family its estate check opens: the first one outside the catalogue, else its first page. */\nexport function familySamplePath(family: Pick<BrandlabEstateFamily, 'paths'>, catalogue: ReadonlySet<string>): string | undefined {\n return family.paths.find((path) => !catalogue.has(normalisePath(path))) ?? family.paths[0];\n}\n\n/**\n * Paths of a family's pattern outside its space: a page of the family with one\n * dynamic segment at a time replaced by an unknown slug.\n */\nexport function familyNotFoundPaths(family: Pick<BrandlabEstateFamily, 'pattern' | 'paths'>): string[] {\n const first = family.paths[0];\n if (!first) return [];\n const parts = normalisePath(first).split('/');\n const segments = family.pattern.split('/');\n const paths = segments.flatMap((segment, index) => (/^\\[[^.\\]]/.test(segment) ? [parts.map((part, position) => (position === index ? UNKNOWN_SLUG : part)).join('/')] : []));\n const space = new Set(family.paths.map(normalisePath));\n return [...new Set(paths)].filter((path) => !space.has(path));\n}\n\n/**\n * The text a page shows before any script runs: its body without scripts,\n * styles and templates, tags removed and whitespace collapsed. Empty for the\n * document Next sends when it leaves a page to the browser to render.\n */\nexport function bodyText(html: string): string {\n const body = /<body\\b[^>]*>([\\s\\S]*?)(?:<\\/body>|$)/i.exec(html)?.[1] ?? (/<html\\b/i.test(html) ? '' : html);\n return decodeXml(body\n .replace(/<(script|style|template|noscript)\\b[\\s\\S]*?<\\/\\1>/gi, ' ')\n .replace(/<[^>]+>/g, ' '))\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */\nexport function isRedirectPattern(path: string): boolean {\n return /:[A-Za-z_]\\w*|\\(/.test(path.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, ''));\n}\n\n/**\n * The request that proves a redirect rule: a pattern gets one sample segment\n * per parameter, in its source and destination alike. Undefined when a\n * parameter carries its own regular expression, which no sample can satisfy\n * with certainty.\n */\nexport function redirectProbe(rule: Pick<BrandlabEstateRedirect, 'source' | 'destination'>): { source: string; destination: string; sampled: boolean } | undefined {\n const sample = (path: string) => {\n if (!isRedirectPattern(path)) return path;\n // Keep an external destination's scheme and host; parameters live in the path.\n const origin = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(path)?.[0] ?? '';\n const rest = path.slice(origin.length);\n if (rest.includes('(')) return undefined;\n return `${origin}${rest.replace(/:[A-Za-z_]\\w*[*+?]?/g, PATTERN_SAMPLE)}`;\n };\n const source = sample(rule.source);\n const destination = sample(rule.destination);\n if (source === undefined || destination === undefined) return undefined;\n return { source, destination, sampled: isRedirectPattern(rule.destination) };\n}\n\n/** A matcher for paths a pattern source redirects, for internal-link checks. */\nfunction redirectSourceMatcher(source: string): RegExp | undefined {\n if (source.includes('(')) return undefined;\n const pattern = normalisePath(source).split(/(:[A-Za-z_]\\w*[*+?]?)/).map((part) => {\n const parameter = /^:[A-Za-z_]\\w*([*+?]?)$/.exec(part);\n if (!parameter) return escapeRegExp(part);\n return parameter[1] === '*' || parameter[1] === '+' ? '.+' : parameter[1] === '?' ? '[^/]*' : '[^/]+';\n }).join('');\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * The advisory page-quality journey: headings, title, description, og:url,\n * implementation wording and structured-data identity for every catalogue\n * page. It reports what it finds (page-quality.json and an annotation) and\n * never fails, because a backend release keeps production's pages as they\n * are; the comparison harness proves them unchanged.\n *\n * // tests/e2e/page-quality.spec.ts\n * defineBrandlabPageQualityTests({ site, routes })\n */\nexport function defineBrandlabPageQualityTests(options: BrandlabPageQualityTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const businessId = options.businessId ?? `${origin}/#business`;\n const timeout = options.timeout ?? 240_000;\n const routes = options.routes.filter((route) => normalisePath(route.path) !== NOT_FOUND_ROUTE);\n\n test.describe('Brandlab page quality (advisory)', () => {\n test('reports page and structured-data quality without failing', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const findings: BrandlabPageQualityFinding[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const response = await request.get(route.path, { maxRedirects: 0 });\n // The estate journey owns status codes; quality is read from the pages that load.\n if (response.status() !== 200) return;\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const add = (kind: BrandlabPageQualityFinding['kind'], detail: string) => findings.push({ path: route.path, kind, detail });\n const headings = html.match(/<h1(?:\\s|>)/gi)?.length ?? 0;\n if (headings !== 1) add('headings', `${headings} h1 headings`);\n if (!snapshot.title) add('title', 'no title');\n if (!snapshot.description) add('description', 'no meta description');\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const ogUrl = snapshot.openGraph['og:url'];\n if (ogUrl && new URL(ogUrl, origin).href !== expectedCanonical) add('og-url', `og:url ${ogUrl} is not the canonical URL`);\n if (snapshot.implementationCopyMatches.length > 0) add('implementation-wording', snapshot.implementationCopyMatches.join(', '));\n for (const failure of inspectStructuredData(route, snapshot.jsonLd, businessId)) {\n add('structured-data', failure.slice(route.path.length + 2));\n }\n });\n findings.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));\n const byKind = Object.fromEntries(\n [...new Set(findings.map((finding) => finding.kind))].sort().map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]),\n );\n await testInfo.attach('page-quality.json', {\n body: JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),\n contentType: 'application/json',\n });\n testInfo.annotations.push({\n type: 'advisory',\n 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.`,\n });\n });\n });\n}\n\nfunction inspectStructuredData(route: BrandlabEstateRoute, payloads: readonly unknown[], businessId: string | false): string[] {\n const failures: string[] = [];\n if (payloads.some((payload) => (payload as { parseError?: boolean }).parseError)) failures.push(`${route.path}: invalid JSON-LD`);\n if (route.indexing === 'index' && payloads.length === 0) failures.push(`${route.path}: no JSON-LD`);\n const ids: string[] = [];\n let referencesBusiness = false;\n const visit = (value: unknown) => {\n if (!value || typeof value !== 'object') return;\n if (Array.isArray(value)) { value.forEach(visit); return; }\n const node = value as Record<string, unknown>;\n const types = ([] as unknown[]).concat(node['@type'] ?? []).map(String);\n if (typeof node['@id'] === 'string') {\n if (node['@id'] === businessId) referencesBusiness = true;\n if (types.length > 0) ids.push(node['@id']);\n }\n if (businessId && types.some((type) => BUSINESS_TYPES.has(type)) && node['@id'] !== businessId) {\n failures.push(`${route.path}: business node @id ${String(node['@id'] ?? 'missing')} (expected ${businessId})`);\n }\n Object.values(node).forEach(visit);\n };\n payloads.forEach(visit);\n const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);\n if (duplicate) failures.push(`${route.path}: JSON-LD repeats node ${duplicate}`);\n if (businessId && route.indexing === 'index' && payloads.length > 0 && !referencesBusiness) failures.push(`${route.path}: JSON-LD does not reference ${businessId}`);\n return failures;\n}\n\nfunction extractLinks(html: string): string[] {\n return [...html.matchAll(/<a\\b[^>]*\\bhref=[\"']([^\"']+)[\"']/gi)].map((match) => decodeXml(match[1]!));\n}\n\nfunction internalPath(href: string, base: string, origin: string): string | null {\n if (/^(?:mailto|tel|sms|javascript):/i.test(href) || href.startsWith('#')) return null;\n let url: URL;\n try { url = new URL(href, base); } catch { return null; }\n if (url.origin !== origin && !['127.0.0.1', 'localhost'].includes(url.hostname)) return null;\n if (ASSET_PATH.test(url.pathname) || url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/')) return null;\n return normalisePath(url.pathname);\n}\n\nfunction normalisePath(path: string): string {\n const pathname = path.split(/[?#]/)[0] || '/';\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : '/';\n}\n\n/**\n * Absolute (`https://host`) and protocol-relative (`//host`) destinations leave\n * the site; browsers also read `/\\host` and `\\\\host` as protocol-relative.\n */\nexport function isExternalDestination(destination: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?[\\\\/]{2}/i.test(destination.trim());\n}\n\nfunction decodeXml(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, \"'\");\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nasync function eachWithConcurrency<T>(items: readonly T[], limit: number, task: (item: T) => Promise<void>) {\n const queue = [...items];\n await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, queue.length)) }, async () => {\n for (let item = queue.shift(); item !== undefined; item = queue.shift()) await task(item);\n }));\n}\n"]}
|
package/dist/knip.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knip.d.ts","sourceRoot":"","sources":["../src/knip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAE9E,yFAAyF;AACzF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uBAAuB,EAAE,OAAO,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,oBAAoB,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,MAAM,0BAA0B,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,KAAK,kBAAkB,CAAC;AAEzF,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"knip.d.ts","sourceRoot":"","sources":["../src/knip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAE9E,yFAAyF;AACzF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uBAAuB,EAAE,OAAO,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,oBAAoB,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,MAAM,0BAA0B,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,KAAK,kBAAkB,CAAC;AAEzF,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAeD,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,0BAA0B,CA2C1F;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQtD"}
|
package/dist/knip.js
CHANGED
|
@@ -17,6 +17,9 @@ const PRODUCTION_ENTRIES = [
|
|
|
17
17
|
'src/data/redirects.ts!',
|
|
18
18
|
// Read statically by brandlab-verify; it is part of the project, not dead code.
|
|
19
19
|
'brandlab.config.ts!',
|
|
20
|
+
// The business facts with their evidence (3.2.1): server-only, read by brandlab-verify and the tests, and imported
|
|
21
|
+
// by server code only where it needs the evidence, so a site whose pages read the display values never imports it.
|
|
22
|
+
'src/data/businessEvidence.ts!',
|
|
20
23
|
];
|
|
21
24
|
const SOURCE_EXTENSIONS = ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'mts', 'cts'];
|
|
22
25
|
export function brandlabKnip(options = {}) {
|
package/dist/knip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knip.js","sourceRoot":"","sources":["../src/knip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiCH,MAAM,kBAAkB,GAAG;IACzB,kFAAkF;IAClF,4EAA4E;IAC5E,wBAAwB;IACxB,gFAAgF;IAChF,qBAAqB;
|
|
1
|
+
{"version":3,"file":"knip.js","sourceRoot":"","sources":["../src/knip.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared Knip preset for Brandlab Next.js projects:\n *\n * // knip.ts\n * import { brandlabKnip } from '@brandlab-digital/conformance/knip'\n * export default brandlabKnip({ mdx: true })\n *\n * It keeps Knip's own Next.js, Playwright, Vitest and ESLint plugin entries\n * (overriding them would replace the maintained defaults) and adds only what\n * those plugins cannot see in a Brandlab project: production entries Next loads\n * outside the app graph, CSS-only dependencies and, when enabled, MDX articles.\n * Knip calls the returned function with its command-line mode.\n */\n\nexport type BrandlabKnipCompiler = (text: string, filename: string) => string;\n\n/** The subset of Knip's configuration the preset produces; it satisfies `KnipConfig`. */\nexport interface BrandlabKnipConfig {\n entry: string[];\n ignoreExportsUsedInFile: boolean;\n compilers: Record<string, true | BrandlabKnipCompiler>;\n project?: string[];\n ignoreDependencies?: string[];\n}\n\n/** The parsed Knip arguments Knip passes to a function configuration. */\nexport interface BrandlabKnipMode {\n production?: boolean;\n strict?: boolean;\n}\n\n/** A Knip function configuration: Knip calls it with its parsed arguments. */\nexport type BrandlabKnipConfigFunction = (mode?: BrandlabKnipMode) => BrandlabKnipConfig;\n\nexport interface BrandlabKnipOptions {\n /** The project renders long-form MDX articles from src/content/. */\n mdx?: boolean;\n /**\n * Additional real entry files that no plugin can discover. Suffix a pattern\n * with `!` when it is part of the production graph. Never list whole source\n * directories to silence findings.\n */\n entry?: readonly string[];\n}\n\nconst PRODUCTION_ENTRIES = [\n // Knip's production mode excludes next.config by design, so the redirect table it\n // imports (the fixed 3.0 owner) would look unused although Next applies it.\n 'src/data/redirects.ts!',\n // Read statically by brandlab-verify; it is part of the project, not dead code.\n 'brandlab.config.ts!',\n // The business facts with their evidence (3.2.1): server-only, read by brandlab-verify and the tests, and imported\n // by server code only where it needs the evidence, so a site whose pages read the display values never imports it.\n 'src/data/businessEvidence.ts!',\n];\n\nconst SOURCE_EXTENSIONS = ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'mts', 'cts'];\n\nexport function brandlabKnip(options: BrandlabKnipOptions = {}): BrandlabKnipConfigFunction {\n const entry = [...PRODUCTION_ENTRIES];\n const compilers: BrandlabKnipConfig['compilers'] = { css: compileCssImports };\n if (options.mdx) {\n // Articles are loaded by slug through dynamic imports that Knip cannot follow.\n entry.push('src/content/**/*.{md,mdx}!');\n // Knip's MDX compiler exposes each article's imports to the module graph.\n compilers.mdx = true;\n }\n for (const pattern of options.entry ?? []) {\n if (typeof pattern !== 'string' || !pattern.trim()) {\n throw new TypeError('brandlabKnip entry patterns must be non-empty strings.');\n }\n if (/^(?:src\\/?)?\\*\\*(?:\\/\\*)?!?$/.test(pattern.trim())) {\n throw new Error(`brandlabKnip entry \"${pattern}\" marks all source as used; list the real entry files instead.`);\n }\n entry.push(pattern.trim());\n }\n const extensions = `{${[...SOURCE_EXTENSIONS, ...Object.keys(compilers), ...(options.mdx ? ['md'] : [])].join(',')}}`;\n const config: BrandlabKnipConfig = {\n entry: [...new Set(entry)],\n // Exports that are also consumed inside their own module are not dead code.\n ignoreExportsUsedInFile: true,\n compilers,\n };\n // Otherwise no ignoreDependencies: optional peers of the shared packages\n // (eslint-config-next for configs.next) already count as used, and anything else\n // must stay visible.\n return (mode = {}) => {\n const copy = { ...config, entry: [...config.entry], compilers: { ...config.compilers } };\n if (mode.production !== true && mode.strict !== true) return copy;\n return {\n ...copy,\n // Knip's default project marks every file as production (**/*!), so production mode\n // reported test tooling (vitest.setup.ts, tests/ helpers) as unused files. The\n // application lives in src/; root production files are entries (brandlab.config.ts)\n // or belong to a plugin (next.config.ts, mdx-components.tsx).\n project: [`src/**/*.${extensions}!`],\n // Knip's production mode never reads next.config (not even as an entry), where the\n // MDX wrapper imports @next/mdx; the default graph still checks that import.\n ...(options.mdx ? { ignoreDependencies: ['@next/mdx'] } : {}),\n };\n };\n}\n\nconst CSS_COMMENT = /\\/\\*[\\s\\S]*?\\*\\//g;\nconst CSS_DIRECTIVE =\n /@(?:import|config|plugin|reference)\\s+(?:url\\(\\s*)?(?:\"([^\"]+)\"|'([^']+)'|([^'\"\\s;)]+))\\s*\\)?[^;]*;/g;\n\n/**\n * Expose CSS-only dependencies (`@import \"tailwindcss\"`, `@plugin`, `@config`,\n * `@reference`, local `@import`) to Knip as module imports. Remote URLs and\n * data URIs are not dependencies and are skipped.\n */\nexport function compileCssImports(text: string): string {\n const imports: string[] = [];\n for (const match of text.replace(CSS_COMMENT, '').matchAll(CSS_DIRECTIVE)) {\n const specifier = match[1] ?? match[2] ?? match[3];\n if (!specifier || /^(?:[a-z][a-z0-9+.-]*:|\\/\\/|#)/i.test(specifier)) continue;\n imports.push(`import ${JSON.stringify(specifier)};`);\n }\n return imports.join('\\n');\n}\n"]}
|
package/dist/rule-registry.d.ts
CHANGED
|
@@ -186,6 +186,11 @@ export declare const BRANDLAB_STATIC_RULES: readonly [{
|
|
|
186
186
|
readonly surface: "analytics";
|
|
187
187
|
readonly checkId: "analytics-conversion";
|
|
188
188
|
readonly summary: "Exactly one BrandlabGoogleTag, rendered by the root layout.";
|
|
189
|
+
}, {
|
|
190
|
+
readonly ruleId: "brandlab-analytics-google-tag-consent";
|
|
191
|
+
readonly surface: "analytics";
|
|
192
|
+
readonly checkId: "analytics-privacy";
|
|
193
|
+
readonly summary: "BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding: no adUserData attribute.";
|
|
189
194
|
}, {
|
|
190
195
|
readonly ruleId: "brandlab-analytics-contract";
|
|
191
196
|
readonly surface: "analytics";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,2BAA2B,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,2BAA2B,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDgB,CAAC;AAEnD,6FAA6F;AAC7F,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAM3F"}
|
package/dist/rule-registry.js
CHANGED
|
@@ -34,6 +34,7 @@ export const BRANDLAB_STATIC_RULES = [
|
|
|
34
34
|
{ ruleId: "brandlab-source-retired-analytics-event", surface: "analytics", checkId: "analytics-conversion", summary: "Only current event names are sent: contact_form for accepted enquiries and phone_link_click as a tap diagnostic.", },
|
|
35
35
|
{ ruleId: "brandlab-source-local-google-tag-bootstrap", surface: "analytics", checkId: "analytics-conversion", summary: "The Google tag bootstrap is owned by the shared runtime.", },
|
|
36
36
|
{ ruleId: "brandlab-analytics-google-tag-owner", surface: "analytics", checkId: "analytics-conversion", summary: "Exactly one BrandlabGoogleTag, rendered by the root layout.", },
|
|
37
|
+
{ ruleId: "brandlab-analytics-google-tag-consent", surface: "analytics", checkId: "analytics-privacy", summary: "BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding: no adUserData attribute.", },
|
|
37
38
|
{ ruleId: "brandlab-analytics-contract", surface: "analytics", checkId: "analytics-conversion", summary: "Exactly one module declares the analytics binding with defineBrandlabAnalytics.", },
|
|
38
39
|
{ ruleId: "brandlab-analytics-instrumentation", surface: "analytics", checkId: "analytics-conversion", summary: "src/instrumentation-client.ts calls installBrandlabInstrumentation, which reports one phone_link_click per tap.", },
|
|
39
40
|
{ ruleId: "brandlab-analytics-retired-phone-api", surface: "analytics", checkId: "analytics-conversion", summary: "No trackPhoneCall; taps are reported once as phone_link_click.", },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.js","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabVerificationCheckId } from \"./verification-policy.js\";\n\n/**\n * Every static rule the evaluator can run, the check it evaluates and what it\n * proves. `<placeholder>` segments stand for the concrete package, capability,\n * key or state named in each finding. The generated rule reference is built\n * from this list, and tests keep it identical to the evaluator.\n */\nexport interface BrandlabStaticRule {\n ruleId: string;\n surface: string;\n checkId: BrandlabVerificationCheckId;\n summary: string;\n}\n\nexport const BRANDLAB_STATIC_RULES = [\n { ruleId: \"brandlab-release-package-json-missing\", surface: \"release\", checkId: \"release-consistency\", summary: \"package.json is present and readable.\", },\n { ruleId: \"brandlab-release-installed-version-unresolved\", surface: \"release\", checkId: \"release-consistency\", summary: \"All four Brandlab packages are pinned to one exact release.\", },\n { ruleId: \"brandlab-release-unsupported\", surface: \"release\", checkId: \"release-consistency\", summary: \"The installed release is the one this checker certifies.\", },\n { ruleId: \"brandlab-release-declared-installed-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The declared and detected releases agree.\", },\n { ruleId: \"brandlab-release-package-lock-<state>\", surface: \"release\", checkId: \"release-consistency\", summary: \"A valid npm lockfile v2 or v3 is committed.\", },\n { ruleId: \"brandlab-release-package-pin-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"Each package is declared once and locked at the exact release.\", },\n { ruleId: \"brandlab-release-package-resolution-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The lockfile resolves each package from the canonical npm registry with integrity.\", },\n { ruleId: \"brandlab-release-package-integrity-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"With --release-manifest, lockfile integrity equals the immutable release record.\", },\n { ruleId: \"brandlab-runtime-<package>\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next, react, react-dom and typescript use versions the release permits.\", },\n { ruleId: \"brandlab-runtime-react-pair\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"react and react-dom are declared and locked at the same version.\", },\n { ruleId: \"brandlab-runtime-node\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"engines.node matches the release in package.json and the lockfile.\", },\n { ruleId: \"brandlab-runtime-package-manager\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"packageManager is the release's npm version.\", },\n { ruleId: \"brandlab-runtime-sharp\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"Sharp is pinned only by overrides.sharp with one lockfile resolution.\", },\n { ruleId: \"brandlab-runtime-image-initialisation\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config imports the shared image-runtime guard first.\", },\n { ruleId: \"brandlab-runtime-next-config\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config exports defineBrandlabNextConfig, which owns headers, image formats and Preview noindex.\", },\n { ruleId: \"brandlab-environment-<capability>-<key>\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example documents each variable an enabled capability reads, including the contract's recipient key.\", },\n { ruleId: \"brandlab-environment-retired-contact-state\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example lists no retired contact state variables.\", },\n { ruleId: \"brandlab-source-dangerously-set-inner-html\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No project-owned dangerouslySetInnerHTML; only SchemaScript and SafeRichText inject markup.\", },\n { ruleId: \"brandlab-source-public-secret\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No API key, secret or token behind a NEXT_PUBLIC_ name.\", },\n { ruleId: \"brandlab-config-missing\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts exists.\", },\n { ruleId: \"brandlab-config-invalid\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts is a literal defineBrandlabProject with projectId.\", },\n { ruleId: \"brandlab-routes-catalogue\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the route catalogue with defineRouteCatalog.\", },\n { ruleId: \"brandlab-routes-location-estate\", surface: \"routes\", checkId: \"route-identity\", summary: \"A project that publishes locations declares them in src/data/locations/estate.ts with defineLocationEstate.\", },\n { ruleId: \"brandlab-redirects-table\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the redirect table with defineRedirects.\", },\n { ruleId: \"brandlab-contact-recipient-env\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"The contact contract declares delivery.recipientEnv as a literal key.\", },\n { ruleId: \"brandlab-source-local-contact-delivery\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Email delivery is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-email-renderer\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact email rendering is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-attachments\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Attachment preparation is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-contact-draft-persistence\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact details are never persisted in browser storage.\", },\n { ruleId: \"brandlab-source-retired-contact-state\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"No retired contact state service variables in source.\", },\n { ruleId: \"brandlab-contact-provider-route\", surface: \"contact\", checkId: \"contact-provider-errors\", summary: \"POST /api/contact is created by the shared handler, so provider errors are handled by the runtime.\", },\n { ruleId: \"brandlab-analytics-pii-payload\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"No lead fields, messages or attachment details in analytics payloads, and no project-set user_data.\", },\n { ruleId: \"brandlab-source-retired-analytics-event\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Only current event names are sent: contact_form for accepted enquiries and phone_link_click as a tap diagnostic.\", },\n { ruleId: \"brandlab-source-local-google-tag-bootstrap\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"The Google tag bootstrap is owned by the shared runtime.\", },\n { ruleId: \"brandlab-analytics-google-tag-owner\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one BrandlabGoogleTag, rendered by the root layout.\", },\n { ruleId: \"brandlab-analytics-contract\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one module declares the analytics binding with defineBrandlabAnalytics.\", },\n { ruleId: \"brandlab-analytics-instrumentation\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"src/instrumentation-client.ts calls installBrandlabInstrumentation, which reports one phone_link_click per tap.\", },\n { ruleId: \"brandlab-analytics-retired-phone-api\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No trackPhoneCall; taps are reported once as phone_link_click.\", },\n { ruleId: \"brandlab-analytics-web-vitals\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No web-vitals reporter, web_vital event or webVitals flag.\", },\n { ruleId: \"brandlab-robots-package-route\", surface: \"robots\", checkId: \"search-routing\", summary: \"robots is a thin binding of @brandlab-digital/site-next/robots with no static public/robots.txt.\", },\n { ruleId: \"brandlab-sitemap-package-route\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"The sitemap route uses the shared sitemap API over the route catalogue, with no static public/sitemap.xml.\", },\n { ruleId: \"brandlab-sitemap-split-mode\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"/sitemaps/[chunk] exists exactly when brandlab.config sets sitemap.mode: 'split'.\", },\n { ruleId: \"brandlab-metadata-route-builder\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Page metadata comes from createRouteMetadataBuilder(site, routes).\", },\n { ruleId: \"brandlab-site-identity\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Exactly one module declares the site identity (canonical URL, locale, titles) with defineBrandlabSite.\", },\n { ruleId: \"brandlab-source-raw-json-ld\", surface: \"schema\", checkId: \"structured-data\", summary: \"JSON-LD is rendered only by the shared schema component.\", },\n { ruleId: \"brandlab-source-self-serving-rating-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No self-serving AggregateRating schema.\", },\n { ruleId: \"brandlab-source-synthetic-offer-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No invented Offer markup.\", },\n { ruleId: \"brandlab-source-local-google-reviews-fetch\", surface: \"reviews\", checkId: \"structured-data\", summary: \"Google Places reviews are fetched only by the shared review runtime.\", },\n { ruleId: \"brandlab-business-facts\", surface: \"business\", checkId: \"business-identity\", summary: \"Exactly one module declares the business facts with defineBrandlabBusiness.\", },\n { ruleId: \"brandlab-content-implementation-wording\", surface: \"content\", checkId: \"public-output-safety\", summary: \"Everything that renders publicly (pages, components, metadata, schema, alt text, MDX, Markdown and JSON content) carries finished copy, not template, helper, scaffold or placeholder wording.\", },\n { ruleId: \"brandlab-source-root-missing\", surface: \"source\", checkId: \"source-safety\", summary: \"Project source sits under a recognised root so the source rules can run.\", },\n { ruleId: \"brandlab-source-platform-reimplementation\", surface: \"source\", checkId: \"source-safety\", summary: \"Protected platform surfaces (SchemaScript, SafeRichText, contact, sitemap, analytics APIs) are imported, not redeclared.\", },\n { ruleId: \"brandlab-source-retired-api\", surface: \"source\", checkId: \"source-safety\", summary: \"No API, option or configuration key removed in 3.0 (TrackedTelephoneLink, shouldIndexSite, buildRobotsMetadata, buildSeoMetadata and the duplicate metadata builders, isBrandlabProductionDeployment, resolveBrowserAnalyticsConfig, resolveBrandlabGoogleTagConfig, the contact submission controller and its lead-tracking helpers (use useBrandlabContactForm), header profiles and isProduction, xRobotsTag, contentSecurityPolicyMode, leadDiagnostics, ALLOW_INDEXING, NEXT_PUBLIC_SITE_URL, profile, routes.aliases).\", },\n { ruleId: \"brandlab-ci-workflow-pinned\", surface: \"project-verification\", checkId: \"project-build\", summary: \"Every reused workflow and action is pinned to a 40-character commit SHA.\", },\n { ruleId: \"brandlab-deployment-vercelignore-lockfile\", surface: \"deployment\", checkId: \"deployment-binding\", summary: \".vercelignore never excludes package.json or package-lock.json.\", },\n] as const satisfies readonly BrandlabStaticRule[];\n\n/** Whether a concrete rule ID (from a finding or evaluation) belongs to a registry entry. */\nexport function matchesBrandlabStaticRule(rule: BrandlabStaticRule, ruleId: string): boolean {\n if (rule.ruleId === ruleId) return true;\n if (!rule.ruleId.includes(\"<\")) return false;\n const stem = rule.ruleId.slice(0, rule.ruleId.indexOf(\"-<\"));\n const pattern = new RegExp(`^${rule.ruleId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/<[a-z]+>/g, \"[a-z0-9-]+\")}$`);\n return ruleId === stem || pattern.test(ruleId);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rule-registry.js","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabVerificationCheckId } from \"./verification-policy.js\";\n\n/**\n * Every static rule the evaluator can run, the check it evaluates and what it\n * proves. `<placeholder>` segments stand for the concrete package, capability,\n * key or state named in each finding. The generated rule reference is built\n * from this list, and tests keep it identical to the evaluator.\n */\nexport interface BrandlabStaticRule {\n ruleId: string;\n surface: string;\n checkId: BrandlabVerificationCheckId;\n summary: string;\n}\n\nexport const BRANDLAB_STATIC_RULES = [\n { ruleId: \"brandlab-release-package-json-missing\", surface: \"release\", checkId: \"release-consistency\", summary: \"package.json is present and readable.\", },\n { ruleId: \"brandlab-release-installed-version-unresolved\", surface: \"release\", checkId: \"release-consistency\", summary: \"All four Brandlab packages are pinned to one exact release.\", },\n { ruleId: \"brandlab-release-unsupported\", surface: \"release\", checkId: \"release-consistency\", summary: \"The installed release is the one this checker certifies.\", },\n { ruleId: \"brandlab-release-declared-installed-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The declared and detected releases agree.\", },\n { ruleId: \"brandlab-release-package-lock-<state>\", surface: \"release\", checkId: \"release-consistency\", summary: \"A valid npm lockfile v2 or v3 is committed.\", },\n { ruleId: \"brandlab-release-package-pin-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"Each package is declared once and locked at the exact release.\", },\n { ruleId: \"brandlab-release-package-resolution-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The lockfile resolves each package from the canonical npm registry with integrity.\", },\n { ruleId: \"brandlab-release-package-integrity-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"With --release-manifest, lockfile integrity equals the immutable release record.\", },\n { ruleId: \"brandlab-runtime-<package>\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next, react, react-dom and typescript use versions the release permits.\", },\n { ruleId: \"brandlab-runtime-react-pair\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"react and react-dom are declared and locked at the same version.\", },\n { ruleId: \"brandlab-runtime-node\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"engines.node matches the release in package.json and the lockfile.\", },\n { ruleId: \"brandlab-runtime-package-manager\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"packageManager is the release's npm version.\", },\n { ruleId: \"brandlab-runtime-sharp\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"Sharp is pinned only by overrides.sharp with one lockfile resolution.\", },\n { ruleId: \"brandlab-runtime-image-initialisation\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config imports the shared image-runtime guard first.\", },\n { ruleId: \"brandlab-runtime-next-config\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config exports defineBrandlabNextConfig, which owns headers, image formats and Preview noindex.\", },\n { ruleId: \"brandlab-environment-<capability>-<key>\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example documents each variable an enabled capability reads, including the contract's recipient key.\", },\n { ruleId: \"brandlab-environment-retired-contact-state\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example lists no retired contact state variables.\", },\n { ruleId: \"brandlab-source-dangerously-set-inner-html\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No project-owned dangerouslySetInnerHTML; only SchemaScript and SafeRichText inject markup.\", },\n { ruleId: \"brandlab-source-public-secret\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No API key, secret or token behind a NEXT_PUBLIC_ name.\", },\n { ruleId: \"brandlab-config-missing\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts exists.\", },\n { ruleId: \"brandlab-config-invalid\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts is a literal defineBrandlabProject with projectId.\", },\n { ruleId: \"brandlab-routes-catalogue\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the route catalogue with defineRouteCatalog.\", },\n { ruleId: \"brandlab-routes-location-estate\", surface: \"routes\", checkId: \"route-identity\", summary: \"A project that publishes locations declares them in src/data/locations/estate.ts with defineLocationEstate.\", },\n { ruleId: \"brandlab-redirects-table\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the redirect table with defineRedirects.\", },\n { ruleId: \"brandlab-contact-recipient-env\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"The contact contract declares delivery.recipientEnv as a literal key.\", },\n { ruleId: \"brandlab-source-local-contact-delivery\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Email delivery is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-email-renderer\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact email rendering is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-attachments\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Attachment preparation is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-contact-draft-persistence\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact details are never persisted in browser storage.\", },\n { ruleId: \"brandlab-source-retired-contact-state\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"No retired contact state service variables in source.\", },\n { ruleId: \"brandlab-contact-provider-route\", surface: \"contact\", checkId: \"contact-provider-errors\", summary: \"POST /api/contact is created by the shared handler, so provider errors are handled by the runtime.\", },\n { ruleId: \"brandlab-analytics-pii-payload\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"No lead fields, messages or attachment details in analytics payloads, and no project-set user_data.\", },\n { ruleId: \"brandlab-source-retired-analytics-event\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Only current event names are sent: contact_form for accepted enquiries and phone_link_click as a tap diagnostic.\", },\n { ruleId: \"brandlab-source-local-google-tag-bootstrap\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"The Google tag bootstrap is owned by the shared runtime.\", },\n { ruleId: \"brandlab-analytics-google-tag-owner\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one BrandlabGoogleTag, rendered by the root layout.\", },\n { ruleId: \"brandlab-analytics-google-tag-consent\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding: no adUserData attribute.\", },\n { ruleId: \"brandlab-analytics-contract\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one module declares the analytics binding with defineBrandlabAnalytics.\", },\n { ruleId: \"brandlab-analytics-instrumentation\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"src/instrumentation-client.ts calls installBrandlabInstrumentation, which reports one phone_link_click per tap.\", },\n { ruleId: \"brandlab-analytics-retired-phone-api\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No trackPhoneCall; taps are reported once as phone_link_click.\", },\n { ruleId: \"brandlab-analytics-web-vitals\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No web-vitals reporter, web_vital event or webVitals flag.\", },\n { ruleId: \"brandlab-robots-package-route\", surface: \"robots\", checkId: \"search-routing\", summary: \"robots is a thin binding of @brandlab-digital/site-next/robots with no static public/robots.txt.\", },\n { ruleId: \"brandlab-sitemap-package-route\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"The sitemap route uses the shared sitemap API over the route catalogue, with no static public/sitemap.xml.\", },\n { ruleId: \"brandlab-sitemap-split-mode\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"/sitemaps/[chunk] exists exactly when brandlab.config sets sitemap.mode: 'split'.\", },\n { ruleId: \"brandlab-metadata-route-builder\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Page metadata comes from createRouteMetadataBuilder(site, routes).\", },\n { ruleId: \"brandlab-site-identity\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Exactly one module declares the site identity (canonical URL, locale, titles) with defineBrandlabSite.\", },\n { ruleId: \"brandlab-source-raw-json-ld\", surface: \"schema\", checkId: \"structured-data\", summary: \"JSON-LD is rendered only by the shared schema component.\", },\n { ruleId: \"brandlab-source-self-serving-rating-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No self-serving AggregateRating schema.\", },\n { ruleId: \"brandlab-source-synthetic-offer-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No invented Offer markup.\", },\n { ruleId: \"brandlab-source-local-google-reviews-fetch\", surface: \"reviews\", checkId: \"structured-data\", summary: \"Google Places reviews are fetched only by the shared review runtime.\", },\n { ruleId: \"brandlab-business-facts\", surface: \"business\", checkId: \"business-identity\", summary: \"Exactly one module declares the business facts with defineBrandlabBusiness.\", },\n { ruleId: \"brandlab-content-implementation-wording\", surface: \"content\", checkId: \"public-output-safety\", summary: \"Everything that renders publicly (pages, components, metadata, schema, alt text, MDX, Markdown and JSON content) carries finished copy, not template, helper, scaffold or placeholder wording.\", },\n { ruleId: \"brandlab-source-root-missing\", surface: \"source\", checkId: \"source-safety\", summary: \"Project source sits under a recognised root so the source rules can run.\", },\n { ruleId: \"brandlab-source-platform-reimplementation\", surface: \"source\", checkId: \"source-safety\", summary: \"Protected platform surfaces (SchemaScript, SafeRichText, contact, sitemap, analytics APIs) are imported, not redeclared.\", },\n { ruleId: \"brandlab-source-retired-api\", surface: \"source\", checkId: \"source-safety\", summary: \"No API, option or configuration key removed in 3.0 (TrackedTelephoneLink, shouldIndexSite, buildRobotsMetadata, buildSeoMetadata and the duplicate metadata builders, isBrandlabProductionDeployment, resolveBrowserAnalyticsConfig, resolveBrandlabGoogleTagConfig, the contact submission controller and its lead-tracking helpers (use useBrandlabContactForm), header profiles and isProduction, xRobotsTag, contentSecurityPolicyMode, leadDiagnostics, ALLOW_INDEXING, NEXT_PUBLIC_SITE_URL, profile, routes.aliases).\", },\n { ruleId: \"brandlab-ci-workflow-pinned\", surface: \"project-verification\", checkId: \"project-build\", summary: \"Every reused workflow and action is pinned to a 40-character commit SHA.\", },\n { ruleId: \"brandlab-deployment-vercelignore-lockfile\", surface: \"deployment\", checkId: \"deployment-binding\", summary: \".vercelignore never excludes package.json or package-lock.json.\", },\n] as const satisfies readonly BrandlabStaticRule[];\n\n/** Whether a concrete rule ID (from a finding or evaluation) belongs to a registry entry. */\nexport function matchesBrandlabStaticRule(rule: BrandlabStaticRule, ruleId: string): boolean {\n if (rule.ruleId === ruleId) return true;\n if (!rule.ruleId.includes(\"<\")) return false;\n const stem = rule.ruleId.slice(0, rule.ruleId.indexOf(\"-<\"));\n const pattern = new RegExp(`^${rule.ruleId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/<[a-z]+>/g, \"[a-z0-9-]+\")}$`);\n return ruleId === stem || pattern.test(ruleId);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-rules.d.ts","sourceRoot":"","sources":["../src/static-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AASpD,OAAO,EAcL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,sBAAsB,EAAE,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;CACxC,GAAG,eAAe,CAalB;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,sBAAsB,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,GAAG,OAAO,EAAE,MAAM,EAAE,QAGrB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"static-rules.d.ts","sourceRoot":"","sources":["../src/static-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AASpD,OAAO,EAcL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,sBAAsB,EAAE,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;CACxC,GAAG,eAAe,CAalB;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,sBAAsB,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,GAAG,OAAO,EAAE,MAAM,EAAE,QAGrB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,QAgB5D;AAED,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;CACxC;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,2BAA2B,CAgBrG;AAssBD,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAajE;AAID,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,sFAAsF;AACtF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,mBAAmB,GAC1B,wBAAwB,EAAE,CAU5B;AA0BD,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBnE"}
|
package/dist/static-rules.js
CHANGED
|
@@ -32,6 +32,7 @@ export function inspectStaticRules(context) {
|
|
|
32
32
|
inspectContactProviderRoute(context, project);
|
|
33
33
|
inspectAnalyticsPrivacy(context);
|
|
34
34
|
inspectGoogleTagOwner(context);
|
|
35
|
+
inspectGoogleTagConsent(context);
|
|
35
36
|
inspectSearchRouting(context, project);
|
|
36
37
|
inspectBusinessFacts(context);
|
|
37
38
|
inspectSharedBindings(context);
|
|
@@ -234,6 +235,39 @@ function inspectGoogleTagOwner(context) {
|
|
|
234
235
|
remediation,
|
|
235
236
|
}));
|
|
236
237
|
}
|
|
238
|
+
// --- analytics-privacy: the Google tag's consent default follows the contract --
|
|
239
|
+
function inspectGoogleTagConsent(context) {
|
|
240
|
+
if (context.source.files.length === 0)
|
|
241
|
+
return;
|
|
242
|
+
const ruleId = "brandlab-analytics-google-tag-consent";
|
|
243
|
+
recordEvaluation(context.evaluations, "analytics", ruleId);
|
|
244
|
+
for (const file of context.source.files) {
|
|
245
|
+
const locals = new Set(importedLocalNames(file, SITE_NEXT_ANY, "BrandlabGoogleTag"));
|
|
246
|
+
if (locals.size === 0)
|
|
247
|
+
continue;
|
|
248
|
+
const visit = (node) => {
|
|
249
|
+
if ((ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) &&
|
|
250
|
+
ts.isIdentifier(node.tagName) &&
|
|
251
|
+
locals.has(node.tagName.text)) {
|
|
252
|
+
for (const attribute of node.attributes.properties) {
|
|
253
|
+
if (!ts.isJsxAttribute(attribute) || !ts.isIdentifier(attribute.name) || attribute.name.text !== "adUserData")
|
|
254
|
+
continue;
|
|
255
|
+
context.findings.push(staticFinding({
|
|
256
|
+
ruleId,
|
|
257
|
+
surface: "analytics",
|
|
258
|
+
path: file.path,
|
|
259
|
+
line: lineOfNode(file, attribute),
|
|
260
|
+
expected: "BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding ({...analytics.googleTag})",
|
|
261
|
+
actual: "BrandlabGoogleTag sets adUserData itself, so the Google tag's ad_user_data default no longer follows the analytics contract.",
|
|
262
|
+
remediation: "Remove the adUserData attribute. Only googleAds.enhancedConversions: { mode: 'disclosed' } in the analytics contract grants it by default, once the privacy policy discloses it.",
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
ts.forEachChild(node, visit);
|
|
267
|
+
};
|
|
268
|
+
visit(parseBrandlabSourceFile(file));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
237
271
|
// --- search-routing ----------------------------------------------------------
|
|
238
272
|
function inspectSearchRouting(context, project) {
|
|
239
273
|
if (context.source.directoryRoots.length === 0)
|
|
@@ -354,7 +388,7 @@ function inspectBusinessFacts(context) {
|
|
|
354
388
|
surface: "business",
|
|
355
389
|
module: SITE_CORE("business"),
|
|
356
390
|
name: "defineBrandlabBusiness",
|
|
357
|
-
expectedPath: "src/data/
|
|
391
|
+
expectedPath: "src/data/businessEvidence.ts",
|
|
358
392
|
subject: "Business facts",
|
|
359
393
|
});
|
|
360
394
|
}
|