@brandlab-digital/conformance 3.12.0 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/dist/current-release.js +6 -6
- package/dist/current-release.js.map +1 -1
- package/dist/edge-probe-cli.d.ts +57 -0
- package/dist/edge-probe-cli.d.ts.map +1 -0
- package/dist/edge-probe-cli.js +134 -0
- package/dist/edge-probe-cli.js.map +1 -0
- package/dist/engineering.d.ts +4 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +19 -14
- package/dist/engineering.js.map +1 -1
- package/dist/estate-sample.d.ts +60 -0
- package/dist/estate-sample.d.ts.map +1 -0
- package/dist/estate-sample.js +144 -0
- package/dist/estate-sample.js.map +1 -0
- package/dist/estate.d.ts +17 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +47 -10
- package/dist/estate.js.map +1 -1
- package/dist/origin-region.d.ts +14 -0
- package/dist/origin-region.d.ts.map +1 -0
- package/dist/origin-region.js +46 -0
- package/dist/origin-region.js.map +1 -0
- package/dist/prefetch-probe-cli.d.ts +36 -0
- package/dist/prefetch-probe-cli.d.ts.map +1 -0
- package/dist/prefetch-probe-cli.js +163 -0
- package/dist/prefetch-probe-cli.js.map +1 -0
- package/dist/site-system/checks.d.ts +36 -0
- package/dist/site-system/checks.d.ts.map +1 -1
- package/dist/site-system/checks.js +6 -0
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +27 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +50 -1
- package/dist/site-system/config.js.map +1 -1
- package/dist/site-system/index.d.ts +1 -1
- package/dist/site-system/index.d.ts.map +1 -1
- package/dist/site-system/index.js +1 -1
- package/dist/site-system/index.js.map +1 -1
- package/dist/site-system/journeys/plan.d.ts +9 -2
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +11 -1
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/prefetch-measure.d.ts +28 -0
- package/dist/site-system/journeys/prefetch-measure.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-measure.js +94 -0
- package/dist/site-system/journeys/prefetch-measure.js.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts +85 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.js +152 -0
- package/dist/site-system/journeys/prefetch-policy.js.map +1 -0
- package/dist/site-system/journeys/prefetch.d.ts +3 -0
- package/dist/site-system/journeys/prefetch.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch.js +37 -0
- package/dist/site-system/journeys/prefetch.js.map +1 -0
- package/dist/site-system/journeys/variants.d.ts +34 -0
- package/dist/site-system/journeys/variants.d.ts.map +1 -0
- package/dist/site-system/journeys/variants.js +139 -0
- package/dist/site-system/journeys/variants.js.map +1 -0
- package/dist/site-system/rule-modules.d.ts.map +1 -1
- package/dist/site-system/rule-modules.js +5 -0
- package/dist/site-system/rule-modules.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +3 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -1
- package/dist/site-system/rules/full/common.js.map +1 -1
- package/dist/site-system/rules/full/estate.d.ts +4 -0
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +11 -2
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +2 -1
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +14 -7
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/w3-refresh.d.ts +22 -0
- package/dist/site-system/rules/full/w3-refresh.d.ts.map +1 -0
- package/dist/site-system/rules/full/w3-refresh.js +70 -0
- package/dist/site-system/rules/full/w3-refresh.js.map +1 -0
- package/dist/site-system/rules/guards/index.d.ts +22 -0
- package/dist/site-system/rules/guards/index.d.ts.map +1 -0
- package/dist/site-system/rules/guards/index.js +109 -0
- package/dist/site-system/rules/guards/index.js.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts +55 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.js +182 -0
- package/dist/site-system/rules/guards/w10-crawlers.js.map +1 -0
- package/dist/site-system/rules/guards/w9-params.d.ts +19 -0
- package/dist/site-system/rules/guards/w9-params.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w9-params.js +84 -0
- package/dist/site-system/rules/guards/w9-params.js.map +1 -0
- package/dist/site-system-e2e.d.ts +1 -0
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +8 -2
- package/dist/site-system-e2e.js.map +1 -1
- package/package.json +6 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the ready gate reads from brandlab.config.ts: the Site System tier, the exceptions record and the\n * representative routes. The file is parsed, never executed, so these fields must be written literally (as\n * `defineBrandlabProject` requires anyway).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ts from \"typescript\";\nimport { SITE_SYSTEM_TIERS, type SiteSystemTier } from \"./checks.js\";\n\nexport interface SiteSystemSettings {\n readonly tier: SiteSystemTier;\n /** A new build or redesign: D4 blocks whatever the tier. */\n readonly newBuild: boolean;\n /** Extra pages to sample for the rendered checks. */\n readonly samples: readonly string[];\n /** Quote and call-to-action labels recorded for this site, beyond the closed list (C5). */\n readonly labels: readonly string[];\n /** Whether the business offers free quotes; null when not recorded. */\n readonly freeQuotes: boolean | null;\n /** Third-party hostnames the site may load (B1). */\n readonly thirdParties: readonly string[];\n /** Extra tel: links the site legitimately shows (C10). */\n readonly phones: readonly string[];\n}\n\n/** A Romie-owned holdout; `ruleId` names the check it covers. */\nexport interface SiteSystemException {\n readonly ruleId: string;\n readonly rationale: string;\n readonly evidence: string;\n readonly expiresAt?: string;\n readonly reviewTrigger: string;\n}\n\nexport interface SiteSystemProjectConfig {\n readonly projectId: string | null;\n /** Null when the site has not adopted Site System v2: nothing in the gate blocks. */\n readonly siteSystem: SiteSystemSettings | null;\n readonly exceptions: readonly SiteSystemException[];\n /** `searchCertification.representativeRoutes` (home, contact, service, location, blog). */\n readonly representativeRoutes: Readonly<Record<string, string>>;\n /** False when defineBrandlabProject({ ... }) spreads or computes a member, so a tier could be hidden. */\n readonly complete: boolean;\n /** Fields that could not be read; the gate reports them and carries on without them. */\n readonly problems: readonly string[];\n}\n\nconst CONFIG_FILE = \"brandlab.config.ts\";\nconst SITE_SYSTEM_KEYS = new Set([\"tier\", \"newBuild\", \"samples\", \"labels\", \"freeQuotes\", \"thirdParties\", \"phones\"]);\nconst HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$/;\nconst TEL_HREF_PATTERN = /^tel:\\+?\\d{6,15}$/;\nconst NOT_LITERAL = Symbol(\"not literal\");\n\nexport function readSiteSystemProjectConfig(root: string): SiteSystemProjectConfig {\n const path = join(root, CONFIG_FILE);\n if (!existsSync(path)) throw new Error(`${CONFIG_FILE} is missing, so the ready gate cannot tell which tier applies.`);\n return parseSiteSystemProjectConfig(readFileSync(path, \"utf8\"));\n}\n\n/** Throws when siteSystem is present but unreadable or invalid: a declared tier must never be skipped silently. */\nexport function parseSiteSystemProjectConfig(text: string): SiteSystemProjectConfig {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n if (!config) {\n throw new Error(`${CONFIG_FILE} has no literal defineBrandlabProject({ ... }) call.`);\n }\n const problems: string[] = [];\n const complete = config.properties.every(isPlainProperty);\n if (!complete) {\n problems.push(`defineBrandlabProject({ ... }) in ${CONFIG_FILE} spreads or computes a member, so the ready gate cannot read it in full; write every field literally.`);\n }\n\n const projectId = literal(property(config, \"projectId\"));\n\n const siteSystemNode = property(config, \"siteSystem\");\n const siteSystem = siteSystemNode === undefined ? null : readSiteSystem(literal(siteSystemNode));\n\n const exceptionsNode = property(config, \"exceptions\");\n let exceptions: SiteSystemException[] = [];\n if (exceptionsNode !== undefined) {\n const value = literal(exceptionsNode);\n if (!Array.isArray(value)) {\n problems.push(`The exceptions in ${CONFIG_FILE} are not written as a literal list, so none were applied.`);\n } else {\n exceptions = value.flatMap((entry, index) => {\n const exception = readException(entry);\n if (!exception) problems.push(`exceptions[${index}] in ${CONFIG_FILE} has no literal ruleId, rationale, evidence and reviewTrigger, so it was not applied.`);\n return exception ? [exception] : [];\n });\n }\n }\n\n const searchCertification = literal(property(config, \"searchCertification\"));\n const routes = isRecord(searchCertification) ? searchCertification.representativeRoutes : undefined;\n const representativeRoutes: Record<string, string> = {};\n if (isRecord(routes)) {\n for (const [name, path] of Object.entries(routes)) {\n if (typeof path === \"string\" && path.startsWith(\"/\")) representativeRoutes[name] = path;\n }\n } else if (property(config, \"searchCertification\") !== undefined) {\n problems.push(`searchCertification.representativeRoutes in ${CONFIG_FILE} is not literal, so only the home page was sampled from it.`);\n }\n\n return {\n projectId: typeof projectId === \"string\" ? projectId : null,\n siteSystem,\n exceptions,\n representativeRoutes,\n complete,\n problems,\n };\n}\n\n/** `key: value` or `key` with an identifier or string key; spreads, computed keys and methods are not. */\nfunction isPlainProperty(member: ts.ObjectLiteralElementLike): boolean {\n if (ts.isShorthandPropertyAssignment(member)) return !member.objectAssignmentInitializer;\n return ts.isPropertyAssignment(member) && (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name));\n}\n\nfunction readSiteSystem(value: unknown): SiteSystemSettings {\n if (value === NOT_LITERAL || !isRecord(value)) {\n throw new Error(`siteSystem in ${CONFIG_FILE} must be written as a literal object, for example siteSystem: { tier: 'shell' }.`);\n }\n const unknown = Object.keys(value).filter((key) => !SITE_SYSTEM_KEYS.has(key));\n if (unknown.length > 0) throw new Error(`siteSystem in ${CONFIG_FILE} contains unsupported keys: ${unknown.join(\", \")}.`);\n if (!SITE_SYSTEM_TIERS.includes(value.tier as SiteSystemTier)) {\n throw new Error(`siteSystem.tier in ${CONFIG_FILE} must be ${SITE_SYSTEM_TIERS.join(\", \")}.`);\n }\n if (value.newBuild !== undefined && typeof value.newBuild !== \"boolean\") {\n throw new Error(`siteSystem.newBuild in ${CONFIG_FILE} must be true or false.`);\n }\n if (\n value.samples !== undefined &&\n (!Array.isArray(value.samples) || value.samples.some((path) => typeof path !== \"string\" || !path.startsWith(\"/\")))\n ) {\n throw new Error(`siteSystem.samples in ${CONFIG_FILE} must be a literal list of root-relative paths.`);\n }\n if (value.freeQuotes !== undefined && typeof value.freeQuotes !== \"boolean\") {\n throw new Error(`siteSystem.freeQuotes in ${CONFIG_FILE} must be true or false.`);\n }\n return {\n tier: value.tier as SiteSystemTier,\n newBuild: value.newBuild === true,\n samples: (value.samples as string[] | undefined) ?? [],\n labels: stringList(value.labels, \"labels\", \"non-empty labels\", (label) => label.trim().length > 0),\n freeQuotes: (value.freeQuotes as boolean | undefined) ?? null,\n thirdParties: stringList(value.thirdParties, \"thirdParties\", \"hostnames such as www.youtube.com\", (host) => HOSTNAME_PATTERN.test(host)),\n phones: stringList(value.phones, \"phones\", \"tel: links such as tel:+61290001234\", (href) => TEL_HREF_PATTERN.test(href)),\n };\n}\n\n/** A literal list of unique strings that each pass `valid`, or [] when absent. */\nfunction stringList(value: unknown, key: string, expected: string, valid: (item: string) => boolean): string[] {\n if (value === undefined) return [];\n if (\n !Array.isArray(value) ||\n value.some((item) => typeof item !== \"string\" || !valid(item)) ||\n new Set(value).size !== value.length\n ) {\n throw new Error(`siteSystem.${key} in ${CONFIG_FILE} must be a literal list of unique ${expected}.`);\n }\n return value as string[];\n}\n\nfunction readException(value: unknown): SiteSystemException | null {\n if (!isRecord(value)) return null;\n const { ruleId, rationale, evidence, expiresAt, reviewTrigger } = value;\n if (\n typeof ruleId !== \"string\" || !ruleId.trim() ||\n typeof rationale !== \"string\" ||\n typeof evidence !== \"string\" ||\n typeof reviewTrigger !== \"string\" ||\n (expiresAt !== undefined && typeof expiresAt !== \"string\")\n ) {\n return null;\n }\n return { ruleId, rationale, evidence, reviewTrigger, ...(expiresAt !== undefined ? { expiresAt } : {}) };\n}\n\nfunction findProjectObject(source: ts.SourceFile): ts.ObjectLiteralExpression | undefined {\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node) => {\n if (found) return;\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : null;\n const argument = node.arguments[0] ? unwrap(node.arguments[0]) : undefined;\n if (name === \"defineBrandlabProject\" && argument && ts.isObjectLiteralExpression(argument)) {\n found = argument;\n return;\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n}\n\nfunction property(object: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined {\n for (const member of object.properties) {\n if (ts.isShorthandPropertyAssignment(member) && member.name.text === name) return member.name;\n if (!ts.isPropertyAssignment(member)) continue;\n const key = member.name;\n if ((ts.isIdentifier(key) || ts.isStringLiteralLike(key)) && key.text === name) return member.initializer;\n }\n return undefined;\n}\n\nfunction unwrap(node: ts.Expression): ts.Expression {\n let current = node;\n while (\n ts.isParenthesizedExpression(current) ||\n ts.isAsExpression(current) ||\n ts.isSatisfiesExpression(current) ||\n ts.isNonNullExpression(current) ||\n ts.isTypeAssertionExpression(current)\n ) {\n current = current.expression;\n }\n return current;\n}\n\n/** The JSON value of a literal expression, or NOT_LITERAL when any part is computed. */\nfunction literal(node: ts.Expression | undefined): unknown {\n if (!node) return undefined;\n const value = unwrap(node);\n if (ts.isStringLiteralLike(value)) return value.text;\n if (ts.isNumericLiteral(value)) return Number(value.text);\n if (value.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (value.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (value.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isArrayLiteralExpression(value)) {\n const items = value.elements.map((element) => (ts.isSpreadElement(element) ? NOT_LITERAL : literal(element)));\n return items.includes(NOT_LITERAL) ? NOT_LITERAL : items;\n }\n if (ts.isObjectLiteralExpression(value)) {\n const object: Record<string, unknown> = {};\n for (const member of value.properties) {\n if (!ts.isPropertyAssignment(member)) return NOT_LITERAL;\n const key = member.name;\n if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) return NOT_LITERAL;\n const item = literal(member.initializer);\n if (item === NOT_LITERAL) return NOT_LITERAL;\n object[key.text] = item;\n }\n return object;\n }\n return NOT_LITERAL;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/site-system/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the ready gate reads from brandlab.config.ts: the Site System tier, the exceptions record and the\n * representative routes. The file is parsed, never executed, so these fields must be written literally (as\n * `defineBrandlabProject` requires anyway).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ts from \"typescript\";\nimport { SITE_SYSTEM_TIERS, type SiteSystemTier } from \"./checks.js\";\n\nexport interface SiteSystemSettings {\n readonly tier: SiteSystemTier;\n /** A new build or redesign: D4 blocks whatever the tier. */\n readonly newBuild: boolean;\n /** Extra pages to sample for the rendered checks. */\n readonly samples: readonly string[];\n /** Quote and call-to-action labels recorded for this site, beyond the closed list (C5). */\n readonly labels: readonly string[];\n /** Whether the business offers free quotes; null when not recorded. */\n readonly freeQuotes: boolean | null;\n /** Third-party hostnames the site may load (B1). */\n readonly thirdParties: readonly string[];\n /** Extra tel: links the site legitimately shows (C10). */\n readonly phones: readonly string[];\n /** Estate families allowed to refresh more often than every 7 days, with the reason and decision (W3, 3.13.0); absent when none. */\n readonly refreshExceptions?: readonly SiteSystemRefreshException[];\n}\n\n/** The estate families check W3 covers (site-core `BRANDLAB_ESTATE_REFRESH_FAMILIES`). */\nexport const SITE_SYSTEM_ESTATE_FAMILIES = [\"suburb\", \"service-in-suburb\", \"region\", \"council\"] as const;\n\n/** A recorded W3 exception: `family` may refresh as often as every `revalidateSeconds`. */\nexport interface SiteSystemRefreshException {\n readonly family: (typeof SITE_SYSTEM_ESTATE_FAMILIES)[number];\n readonly revalidateSeconds: number;\n readonly reason: string;\n readonly decisionId: string;\n}\n\n/** A Romie-owned holdout; `ruleId` names the check it covers. */\nexport interface SiteSystemException {\n readonly ruleId: string;\n readonly rationale: string;\n readonly evidence: string;\n readonly expiresAt?: string;\n readonly reviewTrigger: string;\n}\n\nexport interface SiteSystemProjectConfig {\n readonly projectId: string | null;\n /** Null when the site has not adopted Site System v2: nothing in the gate blocks. */\n readonly siteSystem: SiteSystemSettings | null;\n readonly exceptions: readonly SiteSystemException[];\n /** `searchCertification.representativeRoutes` (home, contact, service, location, blog). */\n readonly representativeRoutes: Readonly<Record<string, string>>;\n /** `hosting` (3.13.0): absent, written literally (its value) or computed (unreadable). Optional for older callers. */\n readonly hosting?: SiteSystemHostingRecord;\n /** False when defineBrandlabProject({ ... }) spreads or computes a member, so a tier could be hidden. */\n readonly complete: boolean;\n /** Fields that could not be read; the gate reports them and carries on without them. */\n readonly problems: readonly string[];\n}\n\n/** What the gate read of `hosting` in brandlab.config.ts. */\nexport type SiteSystemHostingRecord =\n | { readonly state: \"absent\" }\n | { readonly state: \"literal\"; readonly value: unknown }\n | { readonly state: \"computed\" };\n\nconst CONFIG_FILE = \"brandlab.config.ts\";\nconst SITE_SYSTEM_KEYS = new Set([\"tier\", \"newBuild\", \"samples\", \"labels\", \"freeQuotes\", \"thirdParties\", \"phones\", \"refreshExceptions\"]);\nconst HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$/;\nconst TEL_HREF_PATTERN = /^tel:\\+?\\d{6,15}$/;\nconst NOT_LITERAL = Symbol(\"not literal\");\n\nexport function readSiteSystemProjectConfig(root: string): SiteSystemProjectConfig {\n const path = join(root, CONFIG_FILE);\n if (!existsSync(path)) throw new Error(`${CONFIG_FILE} is missing, so the ready gate cannot tell which tier applies.`);\n return parseSiteSystemProjectConfig(readFileSync(path, \"utf8\"));\n}\n\n/** Throws when siteSystem is present but unreadable or invalid: a declared tier must never be skipped silently. */\nexport function parseSiteSystemProjectConfig(text: string): SiteSystemProjectConfig {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n if (!config) {\n throw new Error(`${CONFIG_FILE} has no literal defineBrandlabProject({ ... }) call.`);\n }\n const problems: string[] = [];\n const complete = config.properties.every(isPlainProperty);\n if (!complete) {\n problems.push(`defineBrandlabProject({ ... }) in ${CONFIG_FILE} spreads or computes a member, so the ready gate cannot read it in full; write every field literally.`);\n }\n\n const projectId = literal(property(config, \"projectId\"));\n\n const siteSystemNode = property(config, \"siteSystem\");\n const siteSystem = siteSystemNode === undefined ? null : readSiteSystem(literal(siteSystemNode));\n\n const exceptionsNode = property(config, \"exceptions\");\n let exceptions: SiteSystemException[] = [];\n if (exceptionsNode !== undefined) {\n const value = literal(exceptionsNode);\n if (!Array.isArray(value)) {\n problems.push(`The exceptions in ${CONFIG_FILE} are not written as a literal list, so none were applied.`);\n } else {\n exceptions = value.flatMap((entry, index) => {\n const exception = readException(entry);\n if (!exception) problems.push(`exceptions[${index}] in ${CONFIG_FILE} has no literal ruleId, rationale, evidence and reviewTrigger, so it was not applied.`);\n return exception ? [exception] : [];\n });\n }\n }\n\n const searchCertification = literal(property(config, \"searchCertification\"));\n const routes = isRecord(searchCertification) ? searchCertification.representativeRoutes : undefined;\n const representativeRoutes: Record<string, string> = {};\n if (isRecord(routes)) {\n for (const [name, path] of Object.entries(routes)) {\n if (typeof path === \"string\" && path.startsWith(\"/\")) representativeRoutes[name] = path;\n }\n } else if (property(config, \"searchCertification\") !== undefined) {\n problems.push(`searchCertification.representativeRoutes in ${CONFIG_FILE} is not literal, so only the home page was sampled from it.`);\n }\n\n return {\n projectId: typeof projectId === \"string\" ? projectId : null,\n siteSystem,\n exceptions,\n representativeRoutes,\n hosting: hostingRecord(config),\n complete,\n problems,\n };\n}\n\n/**\n * `hosting` from brandlab.config.ts text, without throwing: absent when there is no literal defineBrandlabProject\n * call or no hosting member. Engineering checks and the ready gate (W5, W10) read it this way.\n */\nexport function parseProjectHosting(text: string): SiteSystemHostingRecord {\n const source = ts.createSourceFile(CONFIG_FILE, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const config = findProjectObject(source);\n return config ? hostingRecord(config) : { state: \"absent\" };\n}\n\nfunction hostingRecord(config: ts.ObjectLiteralExpression): SiteSystemHostingRecord {\n const node = property(config, \"hosting\");\n if (node === undefined) return { state: \"absent\" };\n const value = literal(node);\n return value === NOT_LITERAL ? { state: \"computed\" } : { state: \"literal\", value };\n}\n\n/** `key: value` or `key` with an identifier or string key; spreads, computed keys and methods are not. */\nfunction isPlainProperty(member: ts.ObjectLiteralElementLike): boolean {\n if (ts.isShorthandPropertyAssignment(member)) return !member.objectAssignmentInitializer;\n return ts.isPropertyAssignment(member) && (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name));\n}\n\nfunction readSiteSystem(value: unknown): SiteSystemSettings {\n if (value === NOT_LITERAL || !isRecord(value)) {\n throw new Error(`siteSystem in ${CONFIG_FILE} must be written as a literal object, for example siteSystem: { tier: 'shell' }.`);\n }\n const unknown = Object.keys(value).filter((key) => !SITE_SYSTEM_KEYS.has(key));\n if (unknown.length > 0) throw new Error(`siteSystem in ${CONFIG_FILE} contains unsupported keys: ${unknown.join(\", \")}.`);\n if (!SITE_SYSTEM_TIERS.includes(value.tier as SiteSystemTier)) {\n throw new Error(`siteSystem.tier in ${CONFIG_FILE} must be ${SITE_SYSTEM_TIERS.join(\", \")}.`);\n }\n if (value.newBuild !== undefined && typeof value.newBuild !== \"boolean\") {\n throw new Error(`siteSystem.newBuild in ${CONFIG_FILE} must be true or false.`);\n }\n if (\n value.samples !== undefined &&\n (!Array.isArray(value.samples) || value.samples.some((path) => typeof path !== \"string\" || !path.startsWith(\"/\")))\n ) {\n throw new Error(`siteSystem.samples in ${CONFIG_FILE} must be a literal list of root-relative paths.`);\n }\n if (value.freeQuotes !== undefined && typeof value.freeQuotes !== \"boolean\") {\n throw new Error(`siteSystem.freeQuotes in ${CONFIG_FILE} must be true or false.`);\n }\n return {\n tier: value.tier as SiteSystemTier,\n newBuild: value.newBuild === true,\n samples: (value.samples as string[] | undefined) ?? [],\n labels: stringList(value.labels, \"labels\", \"non-empty labels\", (label) => label.trim().length > 0),\n freeQuotes: (value.freeQuotes as boolean | undefined) ?? null,\n thirdParties: stringList(value.thirdParties, \"thirdParties\", \"hostnames such as www.youtube.com\", (host) => HOSTNAME_PATTERN.test(host)),\n phones: stringList(value.phones, \"phones\", \"tel: links such as tel:+61290001234\", (href) => TEL_HREF_PATTERN.test(href)),\n ...(value.refreshExceptions !== undefined ? { refreshExceptions: refreshExceptions(value.refreshExceptions) } : {}),\n };\n}\n\n/** The W3 exceptions, as defineBrandlabProject validates them; anything else is a configuration error. */\nfunction refreshExceptions(value: unknown): SiteSystemRefreshException[] {\n const expected = `siteSystem.refreshExceptions in ${CONFIG_FILE} must be a literal list of { family, revalidateSeconds, reason, decisionId }`;\n if (!Array.isArray(value)) throw new Error(`${expected}.`);\n const families = new Set<string>();\n return value.map((entry, index) => {\n if (!isRecord(entry) || Object.keys(entry).some((key) => ![\"family\", \"revalidateSeconds\", \"reason\", \"decisionId\"].includes(key))) {\n throw new Error(`${expected}; entry ${index} is not.`);\n }\n const { family, revalidateSeconds, reason, decisionId } = entry;\n if (\n !(SITE_SYSTEM_ESTATE_FAMILIES as readonly unknown[]).includes(family) ||\n !Number.isSafeInteger(revalidateSeconds) ||\n (revalidateSeconds as number) < 60 ||\n (revalidateSeconds as number) >= 604_800 ||\n typeof reason !== \"string\" ||\n !reason.trim() ||\n typeof decisionId !== \"string\" ||\n !decisionId.trim() ||\n families.has(family as string)\n ) {\n throw new Error(`${expected}; entry ${index} needs one of ${SITE_SYSTEM_ESTATE_FAMILIES.join(\", \")} (once each), whole seconds from 60 to 604799, a reason and a decision id.`);\n }\n families.add(family as string);\n return { family: family as SiteSystemRefreshException[\"family\"], revalidateSeconds: revalidateSeconds as number, reason, decisionId };\n });\n}\n\n/** A literal list of unique strings that each pass `valid`, or [] when absent. */\nfunction stringList(value: unknown, key: string, expected: string, valid: (item: string) => boolean): string[] {\n if (value === undefined) return [];\n if (\n !Array.isArray(value) ||\n value.some((item) => typeof item !== \"string\" || !valid(item)) ||\n new Set(value).size !== value.length\n ) {\n throw new Error(`siteSystem.${key} in ${CONFIG_FILE} must be a literal list of unique ${expected}.`);\n }\n return value as string[];\n}\n\nfunction readException(value: unknown): SiteSystemException | null {\n if (!isRecord(value)) return null;\n const { ruleId, rationale, evidence, expiresAt, reviewTrigger } = value;\n if (\n typeof ruleId !== \"string\" || !ruleId.trim() ||\n typeof rationale !== \"string\" ||\n typeof evidence !== \"string\" ||\n typeof reviewTrigger !== \"string\" ||\n (expiresAt !== undefined && typeof expiresAt !== \"string\")\n ) {\n return null;\n }\n return { ruleId, rationale, evidence, reviewTrigger, ...(expiresAt !== undefined ? { expiresAt } : {}) };\n}\n\nfunction findProjectObject(source: ts.SourceFile): ts.ObjectLiteralExpression | undefined {\n let found: ts.ObjectLiteralExpression | undefined;\n const visit = (node: ts.Node) => {\n if (found) return;\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n const name = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : null;\n const argument = node.arguments[0] ? unwrap(node.arguments[0]) : undefined;\n if (name === \"defineBrandlabProject\" && argument && ts.isObjectLiteralExpression(argument)) {\n found = argument;\n return;\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n}\n\nfunction property(object: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined {\n for (const member of object.properties) {\n if (ts.isShorthandPropertyAssignment(member) && member.name.text === name) return member.name;\n if (!ts.isPropertyAssignment(member)) continue;\n const key = member.name;\n if ((ts.isIdentifier(key) || ts.isStringLiteralLike(key)) && key.text === name) return member.initializer;\n }\n return undefined;\n}\n\nfunction unwrap(node: ts.Expression): ts.Expression {\n let current = node;\n while (\n ts.isParenthesizedExpression(current) ||\n ts.isAsExpression(current) ||\n ts.isSatisfiesExpression(current) ||\n ts.isNonNullExpression(current) ||\n ts.isTypeAssertionExpression(current)\n ) {\n current = current.expression;\n }\n return current;\n}\n\n/** The JSON value of a literal expression, or NOT_LITERAL when any part is computed. */\nfunction literal(node: ts.Expression | undefined): unknown {\n if (!node) return undefined;\n const value = unwrap(node);\n if (ts.isStringLiteralLike(value)) return value.text;\n if (ts.isNumericLiteral(value)) return Number(value.text);\n if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(value.operand)) {\n return -Number(value.operand.text);\n }\n if (value.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (value.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (value.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isArrayLiteralExpression(value)) {\n const items = value.elements.map((element) => (ts.isSpreadElement(element) ? NOT_LITERAL : literal(element)));\n return items.includes(NOT_LITERAL) ? NOT_LITERAL : items;\n }\n if (ts.isObjectLiteralExpression(value)) {\n const object: Record<string, unknown> = {};\n for (const member of value.properties) {\n if (!ts.isPropertyAssignment(member)) return NOT_LITERAL;\n const key = member.name;\n if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) return NOT_LITERAL;\n const item = literal(member.initializer);\n if (item === NOT_LITERAL) return NOT_LITERAL;\n object[key.text] = item;\n }\n return object;\n }\n return NOT_LITERAL;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* records and the ready report.
|
|
4
4
|
*/
|
|
5
5
|
export { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemCheckBlocks, type SiteSystemCheck, type SiteSystemCheckId, type SiteSystemCheckKind, type SiteSystemCheckScope, type SiteSystemFinding, type SiteSystemTier, } from "./checks.js";
|
|
6
|
-
export { parseSiteSystemProjectConfig, readSiteSystemProjectConfig, type SiteSystemException, type SiteSystemProjectConfig, type SiteSystemSettings, } from "./config.js";
|
|
6
|
+
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, type SiteSystemException, type SiteSystemHostingRecord, type SiteSystemProjectConfig, type SiteSystemSettings, } from "./config.js";
|
|
7
7
|
export { readSiteSystemJourneyReport, siteSystemCheckIdsInTitle, type SiteSystemJourneyResults, } from "./playwright-report.js";
|
|
8
8
|
export { appendSiteSystemRecord, latestSiteSystemRecords, readSiteSystemRecords, SITE_SYSTEM_RECORD_PATHS, SITE_SYSTEM_RECORDS_SCHEMA, type SiteSystemRecord, type SiteSystemRecordsDocument, } from "./records.js";
|
|
9
9
|
export { buildSiteSystemReadyReport, isExceptionExpired, renderSiteSystemReadyMarkdown, SITE_SYSTEM_READY_REPORT_PATHS, SITE_SYSTEM_READY_SCHEMA, type BuildSiteSystemReadyReportInput, type SiteSystemReadyCheck, type SiteSystemReadyException, type SiteSystemReadyInputs, type SiteSystemReadyReport, type SiteSystemReadyStatus, type SiteSystemReadyStep, type SiteSystemReadyVerdict, type SiteSystemVerifyResult, } from "./report.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* records and the ready report.
|
|
4
4
|
*/
|
|
5
5
|
export { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemCheckBlocks, } from "./checks.js";
|
|
6
|
-
export { parseSiteSystemProjectConfig, readSiteSystemProjectConfig, } from "./config.js";
|
|
6
|
+
export { parseProjectHosting, parseSiteSystemProjectConfig, readSiteSystemProjectConfig, } from "./config.js";
|
|
7
7
|
export { readSiteSystemJourneyReport, siteSystemCheckIdsInTitle, } from "./playwright-report.js";
|
|
8
8
|
export { appendSiteSystemRecord, latestSiteSystemRecords, readSiteSystemRecords, SITE_SYSTEM_RECORD_PATHS, SITE_SYSTEM_RECORDS_SCHEMA, } from "./records.js";
|
|
9
9
|
export { buildSiteSystemReadyReport, isExceptionExpired, renderSiteSystemReadyMarkdown, SITE_SYSTEM_READY_REPORT_PATHS, SITE_SYSTEM_READY_SCHEMA, } from "./report.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GAOtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/site-system/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GAOtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,GAK5B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAE1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,GAUzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,GAG7B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,GAIpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,GAKrB,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The Site System v2 ready gate: the check registry, the rule-module interface `brandlab-ready` runs, the step\n * records and the ready report.\n */\nexport {\n SITE_SYSTEM_CHECKS,\n SITE_SYSTEM_TIERS,\n siteSystemCheckBlocks,\n type SiteSystemCheck,\n type SiteSystemCheckId,\n type SiteSystemCheckKind,\n type SiteSystemCheckScope,\n type SiteSystemFinding,\n type SiteSystemTier,\n} from \"./checks.js\";\nexport {\n parseProjectHosting,\n parseSiteSystemProjectConfig,\n readSiteSystemProjectConfig,\n type SiteSystemException,\n type SiteSystemHostingRecord,\n type SiteSystemProjectConfig,\n type SiteSystemSettings,\n} from \"./config.js\";\nexport {\n readSiteSystemJourneyReport,\n siteSystemCheckIdsInTitle,\n type SiteSystemJourneyResults,\n} from \"./playwright-report.js\";\nexport {\n appendSiteSystemRecord,\n latestSiteSystemRecords,\n readSiteSystemRecords,\n SITE_SYSTEM_RECORD_PATHS,\n SITE_SYSTEM_RECORDS_SCHEMA,\n type SiteSystemRecord,\n type SiteSystemRecordsDocument,\n} from \"./records.js\";\nexport {\n buildSiteSystemReadyReport,\n isExceptionExpired,\n renderSiteSystemReadyMarkdown,\n SITE_SYSTEM_READY_REPORT_PATHS,\n SITE_SYSTEM_READY_SCHEMA,\n type BuildSiteSystemReadyReportInput,\n type SiteSystemReadyCheck,\n type SiteSystemReadyException,\n type SiteSystemReadyInputs,\n type SiteSystemReadyReport,\n type SiteSystemReadyStatus,\n type SiteSystemReadyStep,\n type SiteSystemReadyVerdict,\n type SiteSystemVerifyResult,\n} from \"./report.js\";\nexport { SITE_SYSTEM_RENDERED_RUNNERS } from \"./rule-modules.js\";\nexport {\n isSiteSystemFinding,\n runSiteSystemRenderedRunners,\n type SiteSystemRenderedInput,\n type SiteSystemRenderedRunner,\n} from \"./runners.js\";\nexport {\n builtPageFile,\n loadSiteSystemPages,\n planSiteSystemSamplePaths,\n resolveStreamedHtml,\n type SiteSystemFetch,\n type SiteSystemPage,\n type SiteSystemPageLoad,\n} from \"./samples.js\";\nexport {\n SITE_SYSTEM_HUMAN_STEPS,\n SITE_SYSTEM_LAUNCH_STEPS,\n SITE_SYSTEM_RECORDED_CHECK_IDS,\n siteSystemRecordFile,\n type SiteSystemHumanStepId,\n type SiteSystemLaunchStepId,\n type SiteSystemRecordFile,\n type SiteSystemStep,\n} from \"./steps.js\";\n"]}
|
|
@@ -4,7 +4,7 @@ type JourneyCheck = Extract<(typeof SITE_SYSTEM_CHECKS)[number], {
|
|
|
4
4
|
}>;
|
|
5
5
|
export type SiteSystemJourneyId = JourneyCheck['id'];
|
|
6
6
|
/** Every journey check, in the order the journeys register. */
|
|
7
|
-
export declare const SITE_SYSTEM_JOURNEY_IDS: readonly ["N3", "N4", "N5", "N6", "N8", "K3", "K4", "K5", "K6", "C4", "C1"];
|
|
7
|
+
export declare const SITE_SYSTEM_JOURNEY_IDS: readonly ["N3", "N4", "N5", "N6", "N8", "K3", "K4", "K5", "K6", "C4", "W2", "C1", "W9"];
|
|
8
8
|
/** Desktop widths for N6: both ends and the middle of the compact band, the desktop breakpoint and two wide screens. */
|
|
9
9
|
export declare const SITE_SYSTEM_DESKTOP_WIDTHS: readonly number[];
|
|
10
10
|
export declare const SITE_SYSTEM_PHONE_VIEWPORT: Readonly<{
|
|
@@ -27,11 +27,16 @@ export interface BrandlabSiteSystemTestOptions {
|
|
|
27
27
|
* later release to preview its journeys.
|
|
28
28
|
*/
|
|
29
29
|
release?: string;
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The sampled pages: home, the money pages (service pages with the enquiry form) and the contact page. `families`
|
|
32
|
+
* (W9, full tier from 3.13.0) names one built page per dynamic route family, such as `/services/blocked-drains` and
|
|
33
|
+
* `/locations/ryde`: its URL variants are checked.
|
|
34
|
+
*/
|
|
31
35
|
pages: {
|
|
32
36
|
home: string;
|
|
33
37
|
money?: string | readonly string[];
|
|
34
38
|
contact?: string;
|
|
39
|
+
families?: string | readonly string[];
|
|
35
40
|
};
|
|
36
41
|
/** Desktop widths for N6, each 1024 px or more (default 1024, 1100, 1280, 1440 and 1920). */
|
|
37
42
|
desktopWidths?: readonly number[];
|
|
@@ -57,6 +62,8 @@ export interface SiteSystemJourneyPlan {
|
|
|
57
62
|
home: string;
|
|
58
63
|
money: readonly string[];
|
|
59
64
|
contact: string | undefined;
|
|
65
|
+
/** One page per dynamic route family, for W9's URL variants. */
|
|
66
|
+
families: readonly string[];
|
|
60
67
|
/** Home, the money pages and the contact page, without repeats: K5, K6 and C4 run on each. */
|
|
61
68
|
sampled: readonly string[];
|
|
62
69
|
desktopWidths: readonly number[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/plan.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAA4C,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjH,KAAK,YAAY,GAAG,OAAO,CAAC,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACtF,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;AAErD,+DAA+D;AAC/D,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/plan.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAA4C,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjH,KAAK,YAAY,GAAG,OAAO,CAAC,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACtF,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;AAErD,+DAA+D;AAC/D,eAAO,MAAM,uBAAuB,yFAAkJ,CAAC;AAEvL,wHAAwH;AACxH,eAAO,MAAM,0BAA0B,mBAAgD,CAAC;AACxF,eAAO,MAAM,0BAA0B;;;EAA6C,CAAC;AACrF,eAAO,MAAM,4BAA4B;;;EAA8C,CAAC;AAExF,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,QAAe,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,aAAa,CAAC;AAElE,MAAM,WAAW,6BAA6B;IAC5C,kGAAkG;IAClG,IAAI,EAAE,cAAc,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IACrH,6FAA6F;IAC7F,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,qFAAqF;IACrF,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,oHAAoH;IACpH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oHAAoH;IACpH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sGAAsG;IACtG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,8FAA8F;IAC9F,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,qBAAqB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,GAAE,MAAwD,GAAG,mBAAmB,EAAE,CAUpJ;AASD,kGAAkG;AAClG,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,6BAA6B,GAAG,qBAAqB,CA0E5G"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { BRANDLAB_CURRENT_RUNTIME_RELEASE } from '../../current-release.js';
|
|
6
6
|
import { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemReleaseRank } from '../checks.js';
|
|
7
7
|
/** Every journey check, in the order the journeys register. */
|
|
8
|
-
export const SITE_SYSTEM_JOURNEY_IDS = Object.freeze(['N3', 'N4', 'N5', 'N6', 'N8', 'K3', 'K4', 'K5', 'K6', 'C4', 'C1']);
|
|
8
|
+
export const SITE_SYSTEM_JOURNEY_IDS = Object.freeze(['N3', 'N4', 'N5', 'N6', 'N8', 'K3', 'K4', 'K5', 'K6', 'C4', 'W2', 'C1', 'W9']);
|
|
9
9
|
/** Desktop widths for N6: both ends and the middle of the compact band, the desktop breakpoint and two wide screens. */
|
|
10
10
|
export const SITE_SYSTEM_DESKTOP_WIDTHS = Object.freeze([1024, 1100, 1280, 1440, 1920]);
|
|
11
11
|
export const SITE_SYSTEM_PHONE_VIEWPORT = Object.freeze({ width: 390, height: 844 });
|
|
@@ -58,6 +58,12 @@ export function planBrandlabSiteSystemJourneys(options) {
|
|
|
58
58
|
if (problem)
|
|
59
59
|
errors.push(problem);
|
|
60
60
|
}
|
|
61
|
+
const families = pages.families === undefined ? [] : typeof pages.families === 'string' ? [pages.families] : [...pages.families];
|
|
62
|
+
families.forEach((path, index) => {
|
|
63
|
+
const problem = pathProblem(`pages.families[${index}]`, path) ?? (/[?#]/.test(path) || path === '/' ? `pages.families[${index}] must be a page path below home with no query (got ${JSON.stringify(path)})` : null);
|
|
64
|
+
if (problem)
|
|
65
|
+
errors.push(problem);
|
|
66
|
+
});
|
|
61
67
|
const desktopWidths = input.desktopWidths?.length ? [...input.desktopWidths] : [...SITE_SYSTEM_DESKTOP_WIDTHS];
|
|
62
68
|
for (const width of desktopWidths) {
|
|
63
69
|
if (!Number.isInteger(width) || width < 1024 || width > 3840)
|
|
@@ -85,6 +91,9 @@ export function planBrandlabSiteSystemJourneys(options) {
|
|
|
85
91
|
if (journeys.includes('C1') && money.length === 0) {
|
|
86
92
|
problems.C1 = 'pages.money names no money page: C1 checks the first screen of each service page with the enquiry form';
|
|
87
93
|
}
|
|
94
|
+
if (journeys.includes('W9') && families.length === 0) {
|
|
95
|
+
problems.W9 = 'pages.families names no page: W9 checks the URL variants of one built page per dynamic route family';
|
|
96
|
+
}
|
|
88
97
|
const quoteName = input.quoteName === undefined
|
|
89
98
|
? SITE_SYSTEM_QUOTE_NAME
|
|
90
99
|
: input.quoteName instanceof RegExp
|
|
@@ -97,6 +106,7 @@ export function planBrandlabSiteSystemJourneys(options) {
|
|
|
97
106
|
home: pages.home,
|
|
98
107
|
money,
|
|
99
108
|
contact: pages.contact,
|
|
109
|
+
families: [...new Set(families)],
|
|
100
110
|
sampled: [...new Set([pages.home, ...money, ...(pages.contact ? [pages.contact] : [])])],
|
|
101
111
|
desktopWidths,
|
|
102
112
|
actionOrder: input.actionOrder ?? 'phone-first',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/site-system/journeys/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Options and planning for the site-system journeys: which journeys a tier registers, the sampled pages and the\n * defaults. Pure, so it is unit-tested without a browser.\n */\nimport { BRANDLAB_CURRENT_RUNTIME_RELEASE } from '../../current-release.js';\nimport { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemReleaseRank, type SiteSystemTier } from '../checks.js';\n\ntype JourneyCheck = Extract<(typeof SITE_SYSTEM_CHECKS)[number], { kind: 'journey' }>;\nexport type SiteSystemJourneyId = JourneyCheck['id'];\n\n/** Every journey check, in the order the journeys register. */\nexport const SITE_SYSTEM_JOURNEY_IDS = Object.freeze(['N3', 'N4', 'N5', 'N6', 'N8', 'K3', 'K4', 'K5', 'K6', 'C4', 'C1'] as const satisfies readonly SiteSystemJourneyId[]);\n\n/** Desktop widths for N6: both ends and the middle of the compact band, the desktop breakpoint and two wide screens. */\nexport const SITE_SYSTEM_DESKTOP_WIDTHS = Object.freeze([1024, 1100, 1280, 1440, 1920]);\nexport const SITE_SYSTEM_PHONE_VIEWPORT = Object.freeze({ width: 390, height: 844 });\nexport const SITE_SYSTEM_DESKTOP_VIEWPORT = Object.freeze({ width: 1440, height: 900 });\n\n/** The quote action's accessible name when the site does not record one. */\nexport const SITE_SYSTEM_QUOTE_NAME = /\\bquote\\b/i;\n\nexport type SiteSystemActionOrder = 'phone-first' | 'quote-first';\n\nexport interface BrandlabSiteSystemTestOptions {\n /** The adoption tier from brandlab.config.ts (`siteSystem.tier`). C1 registers from `enquiry`. */\n tier: SiteSystemTier;\n /**\n * The release whose journeys register (default: this conformance release). A journey registers only from the\n * release that brings it, so a later journey never turns a site's e2e run red before the gate blocks on it. Name a\n * later release to preview its journeys.\n */\n release?: string;\n /** The sampled pages: home, the money pages (service pages with the enquiry form) and the contact page. */\n pages: { home: string; money?: string | readonly string[]; contact?: string };\n /** Desktop widths for N6, each 1024 px or more (default 1024, 1100, 1280, 1440 and 1920). */\n desktopWidths?: readonly number[];\n /** The recorded order of the phone and quote actions (K6), default `phone-first`. */\n actionOrder?: SiteSystemActionOrder;\n /** The quote action's accessible name (default /\\bquote\\b/i), for a recorded label such as \"Book an inspection\". */\n quoteName?: string | RegExp;\n /** The main menu's accessible name (default \"Main\"). */\n navName?: string;\n /** The phone menu button's accessible name (default \"Menu\"). */\n menuName?: string | RegExp;\n /** How long a document may take to load, default 45 seconds; a timeout is retried once. */\n navigationTimeoutMs?: number;\n /** How long to wait for the network to settle after a load, default 10 seconds; a busy network is not a failure. */\n settleTimeoutMs?: number;\n /** The most Tab presses K5 makes on one page (default 400); a longer page is reported, not failed. */\n maxTabStops?: number;\n}\n\nexport interface SiteSystemJourneyPlan {\n tier: SiteSystemTier;\n release: string;\n journeys: readonly SiteSystemJourneyId[];\n home: string;\n money: readonly string[];\n contact: string | undefined;\n /** Home, the money pages and the contact page, without repeats: K5, K6 and C4 run on each. */\n sampled: readonly string[];\n desktopWidths: readonly number[];\n actionOrder: SiteSystemActionOrder;\n quoteName: RegExp;\n navName: string;\n menuName: string | RegExp;\n navigationTimeoutMs: number;\n settleTimeoutMs: number;\n maxTabStops: number;\n /** Gaps that fail a journey when it runs (fail closed), rather than stopping the whole spec file. */\n problems: Partial<Record<SiteSystemJourneyId, string>>;\n}\n\nexport function siteSystemJourneyTitle(id: SiteSystemJourneyId, text: string): string {\n return `[${id}] ${text}`;\n}\n\n/**\n * The journey checks a tier registers on a release: its own and the tiers below it, each from the release that brings\n * it (default: this conformance release).\n */\nexport function siteSystemJourneysFor(tier: SiteSystemTier, release: string = BRANDLAB_CURRENT_RUNTIME_RELEASE.releaseVersion): SiteSystemJourneyId[] {\n const rank = SITE_SYSTEM_TIERS.indexOf(tier);\n const releaseRank = siteSystemReleaseRank(release);\n const journeys = new Map<string, { tier: string; from: string }>(SITE_SYSTEM_CHECKS.filter((check) => check.kind === 'journey').map((check) => [check.id, check]));\n return SITE_SYSTEM_JOURNEY_IDS.filter((id) => {\n const check = journeys.get(id);\n return check !== undefined\n && SITE_SYSTEM_TIERS.indexOf(check.tier as SiteSystemTier) <= rank\n && siteSystemReleaseRank(check.from) <= releaseRank;\n });\n}\n\nfunction pathProblem(label: string, path: unknown): string | null {\n if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//') || /\\s/.test(path)) {\n return `${label} must be a site path starting with \"/\" (got ${JSON.stringify(path)})`;\n }\n return null;\n}\n\n/** Validate the options and fill the defaults. Malformed options throw, listing every problem. */\nexport function planBrandlabSiteSystemJourneys(options: BrandlabSiteSystemTestOptions): SiteSystemJourneyPlan {\n const errors: string[] = [];\n const input = (options ?? {}) as Partial<BrandlabSiteSystemTestOptions>;\n if (!SITE_SYSTEM_TIERS.includes(input.tier as SiteSystemTier)) {\n errors.push(`tier must be one of ${SITE_SYSTEM_TIERS.join(', ')} (got ${JSON.stringify(input.tier)})`);\n }\n const pages = input.pages ?? ({} as BrandlabSiteSystemTestOptions['pages']);\n const homeProblem = pathProblem('pages.home', pages.home);\n if (homeProblem) errors.push(homeProblem);\n const money = pages.money === undefined ? [] : typeof pages.money === 'string' ? [pages.money] : [...pages.money];\n money.forEach((path, index) => {\n const problem = pathProblem(`pages.money[${index}]`, path);\n if (problem) errors.push(problem);\n });\n if (pages.contact !== undefined) {\n const problem = pathProblem('pages.contact', pages.contact);\n if (problem) errors.push(problem);\n }\n const desktopWidths = input.desktopWidths?.length ? [...input.desktopWidths] : [...SITE_SYSTEM_DESKTOP_WIDTHS];\n for (const width of desktopWidths) {\n if (!Number.isInteger(width) || width < 1024 || width > 3840) errors.push(`desktopWidths must be whole pixel widths from 1024 to 3840 (got ${width})`);\n }\n if (input.actionOrder !== undefined && input.actionOrder !== 'phone-first' && input.actionOrder !== 'quote-first') {\n errors.push(`actionOrder must be phone-first or quote-first (got ${JSON.stringify(input.actionOrder)})`);\n }\n for (const key of ['navigationTimeoutMs', 'settleTimeoutMs', 'maxTabStops'] as const) {\n const value = input[key];\n if (value !== undefined && (!Number.isInteger(value) || value <= 0)) errors.push(`${key} must be a positive whole number (got ${value})`);\n }\n if (input.release !== undefined && (typeof input.release !== 'string' || !/^\\d+\\.\\d+\\.\\d+$/.test(input.release))) {\n errors.push(`release must be a version such as 3.9.0 (got ${JSON.stringify(input.release)})`);\n }\n if (input.navName !== undefined && (typeof input.navName !== 'string' || !input.navName.trim())) errors.push('navName must be a non-empty string');\n if (errors.length > 0) throw new TypeError(`defineBrandlabSiteSystemTests: ${errors.join('; ')}`);\n\n const tier = input.tier as SiteSystemTier;\n const release = input.release ?? BRANDLAB_CURRENT_RUNTIME_RELEASE.releaseVersion;\n const journeys = siteSystemJourneysFor(tier, release);\n const problems: SiteSystemJourneyPlan['problems'] = {};\n if (journeys.includes('C1') && money.length === 0) {\n problems.C1 = 'pages.money names no money page: C1 checks the first screen of each service page with the enquiry form';\n }\n const quoteName = input.quoteName === undefined\n ? SITE_SYSTEM_QUOTE_NAME\n : input.quoteName instanceof RegExp\n ? input.quoteName\n : new RegExp(`^\\\\s*${input.quoteName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*$`, 'i');\n return {\n tier,\n release,\n journeys,\n home: pages.home,\n money,\n contact: pages.contact,\n sampled: [...new Set([pages.home, ...money, ...(pages.contact ? [pages.contact] : [])])],\n desktopWidths,\n actionOrder: input.actionOrder ?? 'phone-first',\n quoteName,\n navName: input.navName ?? 'Main',\n menuName: input.menuName ?? 'Menu',\n navigationTimeoutMs: input.navigationTimeoutMs ?? 45_000,\n settleTimeoutMs: input.settleTimeoutMs ?? 10_000,\n maxTabStops: input.maxTabStops ?? 400,\n problems,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/site-system/journeys/plan.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,gCAAgC,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,qBAAqB,EAAuB,MAAM,cAAc,CAAC;AAKjH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAmD,CAAC,CAAC;AAEvL,wHAAwH;AACxH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AACrF,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAExF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,YAAY,CAAC;AA4DnD,MAAM,UAAU,sBAAsB,CAAC,EAAuB,EAAE,IAAY;IAC1E,OAAO,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAoB,EAAE,UAAkB,gCAAgC,CAAC,cAAc;IAC3H,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAyC,kBAAkB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACnK,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,KAAK,KAAK,SAAS;eACrB,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,IAAsB,CAAC,IAAI,IAAI;eAC/D,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,IAAa;IAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClG,OAAO,GAAG,KAAK,+CAA+C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;IACxF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,8BAA8B,CAAC,OAAsC;IACnF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,CAAC,OAAO,IAAI,EAAE,CAA2C,CAAC;IACxE,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAsB,CAAC,EAAE,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,uBAAuB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzG,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAK,EAA6C,CAAC;IAC5E,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,WAAW;QAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAClH,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,eAAe,KAAK,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3D,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,WAAW,CAAC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACjI,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC/B,MAAM,OAAO,GAAG,WAAW,CAAC,kBAAkB,KAAK,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,kBAAkB,KAAK,uDAAuD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpN,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,0BAA0B,CAAC,CAAC;IAC/G,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,mEAAmE,KAAK,GAAG,CAAC,CAAC;IACzJ,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,KAAK,aAAa,IAAI,KAAK,CAAC,WAAW,KAAK,aAAa,EAAE,CAAC;QAClH,MAAM,CAAC,IAAI,CAAC,uDAAuD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3G,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,EAAE,aAAa,CAAU,EAAE,CAAC;QACrF,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,yCAAyC,KAAK,GAAG,CAAC,CAAC;IAC5I,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACjH,MAAM,CAAC,IAAI,CAAC,gDAAgD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChG,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACnJ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAElG,MAAM,IAAI,GAAG,KAAK,CAAC,IAAsB,CAAC;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,gCAAgC,CAAC,cAAc,CAAC;IACjF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAsC,EAAE,CAAC;IACvD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,QAAQ,CAAC,EAAE,GAAG,wGAAwG,CAAC;IACzH,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,QAAQ,CAAC,EAAE,GAAG,qGAAqG,CAAC;IACtH,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,KAAK,SAAS;QAC7C,CAAC,CAAC,sBAAsB;QACxB,CAAC,CAAC,KAAK,CAAC,SAAS,YAAY,MAAM;YACjC,CAAC,CAAC,KAAK,CAAC,SAAS;YACjB,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7F,OAAO;QACL,IAAI;QACJ,OAAO;QACP,QAAQ;QACR,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxF,aAAa;QACb,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,aAAa;QAC/C,SAAS;QACT,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,MAAM;QAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM;QAClC,mBAAmB,EAAE,KAAK,CAAC,mBAAmB,IAAI,MAAM;QACxD,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,MAAM;QAChD,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,GAAG;QACrC,QAAQ;KACT,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Options and planning for the site-system journeys: which journeys a tier registers, the sampled pages and the\n * defaults. Pure, so it is unit-tested without a browser.\n */\nimport { BRANDLAB_CURRENT_RUNTIME_RELEASE } from '../../current-release.js';\nimport { SITE_SYSTEM_CHECKS, SITE_SYSTEM_TIERS, siteSystemReleaseRank, type SiteSystemTier } from '../checks.js';\n\ntype JourneyCheck = Extract<(typeof SITE_SYSTEM_CHECKS)[number], { kind: 'journey' }>;\nexport type SiteSystemJourneyId = JourneyCheck['id'];\n\n/** Every journey check, in the order the journeys register. */\nexport const SITE_SYSTEM_JOURNEY_IDS = Object.freeze(['N3', 'N4', 'N5', 'N6', 'N8', 'K3', 'K4', 'K5', 'K6', 'C4', 'W2', 'C1', 'W9'] as const satisfies readonly SiteSystemJourneyId[]);\n\n/** Desktop widths for N6: both ends and the middle of the compact band, the desktop breakpoint and two wide screens. */\nexport const SITE_SYSTEM_DESKTOP_WIDTHS = Object.freeze([1024, 1100, 1280, 1440, 1920]);\nexport const SITE_SYSTEM_PHONE_VIEWPORT = Object.freeze({ width: 390, height: 844 });\nexport const SITE_SYSTEM_DESKTOP_VIEWPORT = Object.freeze({ width: 1440, height: 900 });\n\n/** The quote action's accessible name when the site does not record one. */\nexport const SITE_SYSTEM_QUOTE_NAME = /\\bquote\\b/i;\n\nexport type SiteSystemActionOrder = 'phone-first' | 'quote-first';\n\nexport interface BrandlabSiteSystemTestOptions {\n /** The adoption tier from brandlab.config.ts (`siteSystem.tier`). C1 registers from `enquiry`. */\n tier: SiteSystemTier;\n /**\n * The release whose journeys register (default: this conformance release). A journey registers only from the\n * release that brings it, so a later journey never turns a site's e2e run red before the gate blocks on it. Name a\n * later release to preview its journeys.\n */\n release?: string;\n /**\n * The sampled pages: home, the money pages (service pages with the enquiry form) and the contact page. `families`\n * (W9, full tier from 3.13.0) names one built page per dynamic route family, such as `/services/blocked-drains` and\n * `/locations/ryde`: its URL variants are checked.\n */\n pages: { home: string; money?: string | readonly string[]; contact?: string; families?: string | readonly string[] };\n /** Desktop widths for N6, each 1024 px or more (default 1024, 1100, 1280, 1440 and 1920). */\n desktopWidths?: readonly number[];\n /** The recorded order of the phone and quote actions (K6), default `phone-first`. */\n actionOrder?: SiteSystemActionOrder;\n /** The quote action's accessible name (default /\\bquote\\b/i), for a recorded label such as \"Book an inspection\". */\n quoteName?: string | RegExp;\n /** The main menu's accessible name (default \"Main\"). */\n navName?: string;\n /** The phone menu button's accessible name (default \"Menu\"). */\n menuName?: string | RegExp;\n /** How long a document may take to load, default 45 seconds; a timeout is retried once. */\n navigationTimeoutMs?: number;\n /** How long to wait for the network to settle after a load, default 10 seconds; a busy network is not a failure. */\n settleTimeoutMs?: number;\n /** The most Tab presses K5 makes on one page (default 400); a longer page is reported, not failed. */\n maxTabStops?: number;\n}\n\nexport interface SiteSystemJourneyPlan {\n tier: SiteSystemTier;\n release: string;\n journeys: readonly SiteSystemJourneyId[];\n home: string;\n money: readonly string[];\n contact: string | undefined;\n /** One page per dynamic route family, for W9's URL variants. */\n families: readonly string[];\n /** Home, the money pages and the contact page, without repeats: K5, K6 and C4 run on each. */\n sampled: readonly string[];\n desktopWidths: readonly number[];\n actionOrder: SiteSystemActionOrder;\n quoteName: RegExp;\n navName: string;\n menuName: string | RegExp;\n navigationTimeoutMs: number;\n settleTimeoutMs: number;\n maxTabStops: number;\n /** Gaps that fail a journey when it runs (fail closed), rather than stopping the whole spec file. */\n problems: Partial<Record<SiteSystemJourneyId, string>>;\n}\n\nexport function siteSystemJourneyTitle(id: SiteSystemJourneyId, text: string): string {\n return `[${id}] ${text}`;\n}\n\n/**\n * The journey checks a tier registers on a release: its own and the tiers below it, each from the release that brings\n * it (default: this conformance release).\n */\nexport function siteSystemJourneysFor(tier: SiteSystemTier, release: string = BRANDLAB_CURRENT_RUNTIME_RELEASE.releaseVersion): SiteSystemJourneyId[] {\n const rank = SITE_SYSTEM_TIERS.indexOf(tier);\n const releaseRank = siteSystemReleaseRank(release);\n const journeys = new Map<string, { tier: string; from: string }>(SITE_SYSTEM_CHECKS.filter((check) => check.kind === 'journey').map((check) => [check.id, check]));\n return SITE_SYSTEM_JOURNEY_IDS.filter((id) => {\n const check = journeys.get(id);\n return check !== undefined\n && SITE_SYSTEM_TIERS.indexOf(check.tier as SiteSystemTier) <= rank\n && siteSystemReleaseRank(check.from) <= releaseRank;\n });\n}\n\nfunction pathProblem(label: string, path: unknown): string | null {\n if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//') || /\\s/.test(path)) {\n return `${label} must be a site path starting with \"/\" (got ${JSON.stringify(path)})`;\n }\n return null;\n}\n\n/** Validate the options and fill the defaults. Malformed options throw, listing every problem. */\nexport function planBrandlabSiteSystemJourneys(options: BrandlabSiteSystemTestOptions): SiteSystemJourneyPlan {\n const errors: string[] = [];\n const input = (options ?? {}) as Partial<BrandlabSiteSystemTestOptions>;\n if (!SITE_SYSTEM_TIERS.includes(input.tier as SiteSystemTier)) {\n errors.push(`tier must be one of ${SITE_SYSTEM_TIERS.join(', ')} (got ${JSON.stringify(input.tier)})`);\n }\n const pages = input.pages ?? ({} as BrandlabSiteSystemTestOptions['pages']);\n const homeProblem = pathProblem('pages.home', pages.home);\n if (homeProblem) errors.push(homeProblem);\n const money = pages.money === undefined ? [] : typeof pages.money === 'string' ? [pages.money] : [...pages.money];\n money.forEach((path, index) => {\n const problem = pathProblem(`pages.money[${index}]`, path);\n if (problem) errors.push(problem);\n });\n if (pages.contact !== undefined) {\n const problem = pathProblem('pages.contact', pages.contact);\n if (problem) errors.push(problem);\n }\n const families = pages.families === undefined ? [] : typeof pages.families === 'string' ? [pages.families] : [...pages.families];\n families.forEach((path, index) => {\n const problem = pathProblem(`pages.families[${index}]`, path) ?? (/[?#]/.test(path) || path === '/' ? `pages.families[${index}] must be a page path below home with no query (got ${JSON.stringify(path)})` : null);\n if (problem) errors.push(problem);\n });\n const desktopWidths = input.desktopWidths?.length ? [...input.desktopWidths] : [...SITE_SYSTEM_DESKTOP_WIDTHS];\n for (const width of desktopWidths) {\n if (!Number.isInteger(width) || width < 1024 || width > 3840) errors.push(`desktopWidths must be whole pixel widths from 1024 to 3840 (got ${width})`);\n }\n if (input.actionOrder !== undefined && input.actionOrder !== 'phone-first' && input.actionOrder !== 'quote-first') {\n errors.push(`actionOrder must be phone-first or quote-first (got ${JSON.stringify(input.actionOrder)})`);\n }\n for (const key of ['navigationTimeoutMs', 'settleTimeoutMs', 'maxTabStops'] as const) {\n const value = input[key];\n if (value !== undefined && (!Number.isInteger(value) || value <= 0)) errors.push(`${key} must be a positive whole number (got ${value})`);\n }\n if (input.release !== undefined && (typeof input.release !== 'string' || !/^\\d+\\.\\d+\\.\\d+$/.test(input.release))) {\n errors.push(`release must be a version such as 3.9.0 (got ${JSON.stringify(input.release)})`);\n }\n if (input.navName !== undefined && (typeof input.navName !== 'string' || !input.navName.trim())) errors.push('navName must be a non-empty string');\n if (errors.length > 0) throw new TypeError(`defineBrandlabSiteSystemTests: ${errors.join('; ')}`);\n\n const tier = input.tier as SiteSystemTier;\n const release = input.release ?? BRANDLAB_CURRENT_RUNTIME_RELEASE.releaseVersion;\n const journeys = siteSystemJourneysFor(tier, release);\n const problems: SiteSystemJourneyPlan['problems'] = {};\n if (journeys.includes('C1') && money.length === 0) {\n problems.C1 = 'pages.money names no money page: C1 checks the first screen of each service page with the enquiry form';\n }\n if (journeys.includes('W9') && families.length === 0) {\n problems.W9 = 'pages.families names no page: W9 checks the URL variants of one built page per dynamic route family';\n }\n const quoteName = input.quoteName === undefined\n ? SITE_SYSTEM_QUOTE_NAME\n : input.quoteName instanceof RegExp\n ? input.quoteName\n : new RegExp(`^\\\\s*${input.quoteName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*$`, 'i');\n return {\n tier,\n release,\n journeys,\n home: pages.home,\n money,\n contact: pages.contact,\n families: [...new Set(families)],\n sampled: [...new Set([pages.home, ...money, ...(pages.contact ? [pages.contact] : [])])],\n desktopWidths,\n actionOrder: input.actionOrder ?? 'phone-first',\n quoteName,\n navName: input.navName ?? 'Main',\n menuName: input.menuName ?? 'Menu',\n navigationTimeoutMs: input.navigationTimeoutMs ?? 45_000,\n settleTimeoutMs: input.settleTimeoutMs ?? 10_000,\n maxTabStops: input.maxTabStops ?? 400,\n problems,\n };\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measures Next's prefetches on one page at one viewport: the pages prefetched on landing, then after scrolling to
|
|
3
|
+
* the end, with their compressed and decoded bytes, and every same-site link with its role. Used by W2 and by
|
|
4
|
+
* `brandlab-prefetch-probe`. The caller opens the page (so the journey keeps its own navigation and settling).
|
|
5
|
+
*/
|
|
6
|
+
import type { Page } from '@playwright/test';
|
|
7
|
+
import { type PrefetchMeasurement } from './prefetch-policy.js';
|
|
8
|
+
export interface PrefetchMeasureOptions {
|
|
9
|
+
/** Opens the page (navigation and settling); prefetch requests are recorded from before it starts. */
|
|
10
|
+
open(page: Page, path: string): Promise<unknown>;
|
|
11
|
+
/** How long to let prefetches finish after landing and after the scroll (default 2 seconds). */
|
|
12
|
+
quietMs?: number;
|
|
13
|
+
/** The pause between scroll steps of 0.8 screens (default 300 ms). */
|
|
14
|
+
stepMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PrefetchRequestRecord {
|
|
17
|
+
readonly phase: 'landing' | 'scroll';
|
|
18
|
+
readonly page: string;
|
|
19
|
+
readonly compressed: number;
|
|
20
|
+
readonly decoded: number;
|
|
21
|
+
/** The edge cache result, when a CDN reports one (`x-vercel-cache`). */
|
|
22
|
+
readonly cache: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface PrefetchMeasureResult extends PrefetchMeasurement {
|
|
25
|
+
readonly requests: readonly PrefetchRequestRecord[];
|
|
26
|
+
}
|
|
27
|
+
export declare function measurePrefetch(page: Page, path: string, options: PrefetchMeasureOptions): Promise<PrefetchMeasureResult>;
|
|
28
|
+
//# sourceMappingURL=prefetch-measure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch-measure.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/prefetch-measure.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAoG,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAElK,MAAM,WAAW,sBAAsB;IACrC,sGAAsG;IACtG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,QAAQ,CAAC,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACrD;AAwCD,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAmD/H"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { PREFETCH_OFF_CONTAINERS, collectPrefetchLinks, prefetchedPage, prefetchPagePath, toPrefetchLinks } from './prefetch-policy.js';
|
|
2
|
+
/** How long to wait for a response's sizes and body before counting its bytes as unknown (0). */
|
|
3
|
+
const BYTES_WAIT_MS = 2_000;
|
|
4
|
+
/** Resolves to null when the promise rejects or has not settled within `ms`. */
|
|
5
|
+
async function within(promise, ms) {
|
|
6
|
+
let timer;
|
|
7
|
+
const timeout = new Promise((resolveTimeout) => { timer = setTimeout(() => resolveTimeout(null), ms); });
|
|
8
|
+
try {
|
|
9
|
+
return await Promise.race([promise.catch(() => null), timeout]);
|
|
10
|
+
}
|
|
11
|
+
finally {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function requestBytes(request) {
|
|
16
|
+
const response = await within(request.response(), BYTES_WAIT_MS);
|
|
17
|
+
if (!response)
|
|
18
|
+
return { compressed: 0, decoded: 0, cache: null };
|
|
19
|
+
// A response the page never reads (a prefetch served no-store) may never finish, so sizes() and body() would wait
|
|
20
|
+
// forever; its bytes then count as 0.
|
|
21
|
+
const [sizes, body] = await Promise.all([within(request.sizes(), BYTES_WAIT_MS), within(response.body(), BYTES_WAIT_MS)]);
|
|
22
|
+
const decoded = body?.length ?? 0;
|
|
23
|
+
return { compressed: sizes?.responseBodySize ?? decoded, decoded, cache: response.headers()['x-vercel-cache'] ?? null };
|
|
24
|
+
}
|
|
25
|
+
/** Scroll to the end of the page in steps of 0.8 screens, pausing so each step's links can come into view. */
|
|
26
|
+
async function scrollToEnd(page, stepMs) {
|
|
27
|
+
let previous = -1;
|
|
28
|
+
for (let step = 0; step < 200; step += 1) {
|
|
29
|
+
const position = await page.evaluate(() => {
|
|
30
|
+
window.scrollBy({ top: Math.floor(window.innerHeight * 0.8), behavior: 'instant' });
|
|
31
|
+
return Math.ceil(window.scrollY + window.innerHeight) >= document.documentElement.scrollHeight ? -2 : window.scrollY;
|
|
32
|
+
});
|
|
33
|
+
await page.waitForTimeout(stepMs);
|
|
34
|
+
if (position === -2 || position === previous)
|
|
35
|
+
return;
|
|
36
|
+
previous = position;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function measurePrefetch(page, path, options) {
|
|
40
|
+
const quietMs = options.quietMs ?? 2_000;
|
|
41
|
+
const stepMs = options.stepMs ?? 300;
|
|
42
|
+
let phase = 'landing';
|
|
43
|
+
let origin = '';
|
|
44
|
+
const seen = [];
|
|
45
|
+
const onRequest = (request) => {
|
|
46
|
+
if (!origin)
|
|
47
|
+
return;
|
|
48
|
+
const prefetched = prefetchedPage({ url: request.url(), method: request.method(), headers: request.headers() }, origin);
|
|
49
|
+
if (prefetched !== null)
|
|
50
|
+
seen.push({ phase, page: prefetched, request });
|
|
51
|
+
};
|
|
52
|
+
page.on('request', onRequest);
|
|
53
|
+
try {
|
|
54
|
+
// The origin is known once the document request leaves; record nothing before it.
|
|
55
|
+
let documentRequest = null;
|
|
56
|
+
const onDocument = (request) => {
|
|
57
|
+
if (!request.isNavigationRequest() || request.frame() !== page.mainFrame())
|
|
58
|
+
return;
|
|
59
|
+
documentRequest = request;
|
|
60
|
+
origin = new URL(request.url()).origin;
|
|
61
|
+
};
|
|
62
|
+
page.on('request', onDocument);
|
|
63
|
+
await options.open(page, path);
|
|
64
|
+
page.off('request', onDocument);
|
|
65
|
+
if (!origin)
|
|
66
|
+
origin = new URL(page.url()).origin;
|
|
67
|
+
await page.waitForTimeout(quietMs);
|
|
68
|
+
phase = 'scroll';
|
|
69
|
+
await scrollToEnd(page, stepMs);
|
|
70
|
+
await page.waitForLoadState('networkidle', { timeout: quietMs * 3 }).catch(() => undefined);
|
|
71
|
+
await page.waitForTimeout(quietMs);
|
|
72
|
+
const links = toPrefetchLinks(await page.evaluate(collectPrefetchLinks, { ...PREFETCH_OFF_CONTAINERS }));
|
|
73
|
+
const requests = await Promise.all(seen.map(async (record) => ({ phase: record.phase, page: record.page, ...(await requestBytes(record.request)) })));
|
|
74
|
+
const finalDocument = documentRequest;
|
|
75
|
+
const document = finalDocument ? await requestBytes(finalDocument) : null;
|
|
76
|
+
const landing = [...new Set(requests.filter((request) => request.phase === 'landing').map((request) => request.page))];
|
|
77
|
+
return {
|
|
78
|
+
page: prefetchPagePath(new URL(page.url()).pathname),
|
|
79
|
+
landing,
|
|
80
|
+
scrolled: [...new Set(requests.map((request) => request.page))],
|
|
81
|
+
links,
|
|
82
|
+
requests,
|
|
83
|
+
bytes: {
|
|
84
|
+
prefetchCompressed: requests.reduce((total, request) => total + request.compressed, 0),
|
|
85
|
+
prefetchDecoded: requests.reduce((total, request) => total + request.decoded, 0),
|
|
86
|
+
htmlCompressed: document?.compressed ?? 0,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
page.off('request', onRequest);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=prefetch-measure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch-measure.js","sourceRoot":"","sources":["../../../src/site-system/journeys/prefetch-measure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Measures Next's prefetches on one page at one viewport: the pages prefetched on landing, then after scrolling to\n * the end, with their compressed and decoded bytes, and every same-site link with its role. Used by W2 and by\n * `brandlab-prefetch-probe`. The caller opens the page (so the journey keeps its own navigation and settling).\n */\nimport type { Page, Request } from '@playwright/test';\nimport { PREFETCH_OFF_CONTAINERS, collectPrefetchLinks, prefetchedPage, prefetchPagePath, toPrefetchLinks, type PrefetchMeasurement } from './prefetch-policy.js';\n\nexport interface PrefetchMeasureOptions {\n /** Opens the page (navigation and settling); prefetch requests are recorded from before it starts. */\n open(page: Page, path: string): Promise<unknown>;\n /** How long to let prefetches finish after landing and after the scroll (default 2 seconds). */\n quietMs?: number;\n /** The pause between scroll steps of 0.8 screens (default 300 ms). */\n stepMs?: number;\n}\n\nexport interface PrefetchRequestRecord {\n readonly phase: 'landing' | 'scroll';\n readonly page: string;\n readonly compressed: number;\n readonly decoded: number;\n /** The edge cache result, when a CDN reports one (`x-vercel-cache`). */\n readonly cache: string | null;\n}\n\nexport interface PrefetchMeasureResult extends PrefetchMeasurement {\n readonly requests: readonly PrefetchRequestRecord[];\n}\n\n/** How long to wait for a response's sizes and body before counting its bytes as unknown (0). */\nconst BYTES_WAIT_MS = 2_000;\n\n/** Resolves to null when the promise rejects or has not settled within `ms`. */\nasync function within<T>(promise: Promise<T>, ms: number): Promise<T | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<null>((resolveTimeout) => { timer = setTimeout(() => resolveTimeout(null), ms); });\n try {\n return await Promise.race([promise.catch(() => null), timeout]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function requestBytes(request: Request): Promise<{ compressed: number; decoded: number; cache: string | null }> {\n const response = await within(request.response(), BYTES_WAIT_MS);\n if (!response) return { compressed: 0, decoded: 0, cache: null };\n // A response the page never reads (a prefetch served no-store) may never finish, so sizes() and body() would wait\n // forever; its bytes then count as 0.\n const [sizes, body] = await Promise.all([within(request.sizes(), BYTES_WAIT_MS), within(response.body(), BYTES_WAIT_MS)]);\n const decoded = body?.length ?? 0;\n return { compressed: sizes?.responseBodySize ?? decoded, decoded, cache: response.headers()['x-vercel-cache'] ?? null };\n}\n\n/** Scroll to the end of the page in steps of 0.8 screens, pausing so each step's links can come into view. */\nasync function scrollToEnd(page: Page, stepMs: number): Promise<void> {\n let previous = -1;\n for (let step = 0; step < 200; step += 1) {\n const position = await page.evaluate(() => {\n window.scrollBy({ top: Math.floor(window.innerHeight * 0.8), behavior: 'instant' });\n return Math.ceil(window.scrollY + window.innerHeight) >= document.documentElement.scrollHeight ? -2 : window.scrollY;\n });\n await page.waitForTimeout(stepMs);\n if (position === -2 || position === previous) return;\n previous = position;\n }\n}\n\nexport async function measurePrefetch(page: Page, path: string, options: PrefetchMeasureOptions): Promise<PrefetchMeasureResult> {\n const quietMs = options.quietMs ?? 2_000;\n const stepMs = options.stepMs ?? 300;\n let phase: 'landing' | 'scroll' = 'landing';\n let origin = '';\n const seen: { phase: 'landing' | 'scroll'; page: string; request: Request }[] = [];\n const onRequest = (request: Request) => {\n if (!origin) return;\n const prefetched = prefetchedPage({ url: request.url(), method: request.method(), headers: request.headers() }, origin);\n if (prefetched !== null) seen.push({ phase, page: prefetched, request });\n };\n page.on('request', onRequest);\n try {\n // The origin is known once the document request leaves; record nothing before it.\n let documentRequest: Request | null = null;\n const onDocument = (request: Request) => {\n if (!request.isNavigationRequest() || request.frame() !== page.mainFrame()) return;\n documentRequest = request;\n origin = new URL(request.url()).origin;\n };\n page.on('request', onDocument);\n await options.open(page, path);\n page.off('request', onDocument);\n if (!origin) origin = new URL(page.url()).origin;\n await page.waitForTimeout(quietMs);\n phase = 'scroll';\n await scrollToEnd(page, stepMs);\n await page.waitForLoadState('networkidle', { timeout: quietMs * 3 }).catch(() => undefined);\n await page.waitForTimeout(quietMs);\n const links = toPrefetchLinks(await page.evaluate(collectPrefetchLinks, { ...PREFETCH_OFF_CONTAINERS }));\n const requests: PrefetchRequestRecord[] = await Promise.all(\n seen.map(async (record) => ({ phase: record.phase, page: record.page, ...(await requestBytes(record.request)) })),\n );\n const finalDocument = documentRequest as Request | null;\n const document = finalDocument ? await requestBytes(finalDocument) : null;\n const landing = [...new Set(requests.filter((request) => request.phase === 'landing').map((request) => request.page))];\n return {\n page: prefetchPagePath(new URL(page.url()).pathname),\n landing,\n scrolled: [...new Set(requests.map((request) => request.page))],\n links,\n requests,\n bytes: {\n prefetchCompressed: requests.reduce((total, request) => total + request.compressed, 0),\n prefetchDecoded: requests.reduce((total, request) => total + request.decoded, 0),\n htmlCompressed: document?.compressed ?? 0,\n },\n };\n } finally {\n page.off('request', onRequest);\n }\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W2's rules, pure so they are unit-tested without a browser: which requests are Next prefetches, which page each
|
|
3
|
+
* one fetches, which link roles must never prefetch, and the verdict for one page at one viewport. The journey and
|
|
4
|
+
* `brandlab-prefetch-probe` share them. The roles mirror site-next's `LINK_PREFETCH_POLICY` (3.13.0).
|
|
5
|
+
*/
|
|
6
|
+
/** At most this many distinct pages prefetched on landing, and after a full scroll. */
|
|
7
|
+
export declare const PREFETCH_LANDING_LIMIT = 8;
|
|
8
|
+
export declare const PREFETCH_SCROLLED_LIMIT = 12;
|
|
9
|
+
/** Compressed prefetch bytes above this multiple of the page's own compressed HTML earn a warning (advisory). */
|
|
10
|
+
export declare const PREFETCH_BYTES_WARNING_RATIO = 2;
|
|
11
|
+
/**
|
|
12
|
+
* Containers whose links must never prefetch, by role. Shared components carry `data-bl` hooks and sections their
|
|
13
|
+
* matrix id; a site marks its own lists with `data-bl-links`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PREFETCH_OFF_CONTAINERS: Readonly<{
|
|
16
|
+
readonly footer: "[data-bl=\"footer\"], [role=\"contentinfo\"], footer:not(article footer, aside footer, main footer, nav footer, section footer, dialog footer)";
|
|
17
|
+
readonly 'header panel': "[data-bl=\"header\"] [data-slot=\"panel\"]";
|
|
18
|
+
readonly drawer: "[data-bl=\"drawer\"], dialog";
|
|
19
|
+
readonly area: "[data-bl-section=\"coverage\"], [data-bl-links=\"area\"]";
|
|
20
|
+
readonly nearby: "[data-bl-section=\"nearby-areas\"], [data-bl-links=\"nearby\"]";
|
|
21
|
+
readonly map: "[data-bl-section=\"area-map\"], [data-area-map], [data-bl-links=\"map\"]";
|
|
22
|
+
readonly finder: "[data-bl-links=\"finder\"]";
|
|
23
|
+
readonly 'other services': "[data-bl-links=\"other-services\"]";
|
|
24
|
+
}>;
|
|
25
|
+
export type PrefetchOffRole = keyof typeof PREFETCH_OFF_CONTAINERS;
|
|
26
|
+
/** One same-site link on the page after the full scroll. */
|
|
27
|
+
export interface PrefetchLink {
|
|
28
|
+
/** The page it points to, normalised as `prefetchPagePath` does. */
|
|
29
|
+
readonly path: string;
|
|
30
|
+
/** The off roles of the containers it sits in (empty for a link that may prefetch). */
|
|
31
|
+
readonly roles: readonly PrefetchOffRole[];
|
|
32
|
+
/** Whether it shows (not hidden, transparent, collapsed or zero-size). */
|
|
33
|
+
readonly rendered: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface PrefetchMeasurement {
|
|
36
|
+
/** The sampled page, normalised. */
|
|
37
|
+
readonly page: string;
|
|
38
|
+
/** Distinct pages prefetched before any scroll. */
|
|
39
|
+
readonly landing: readonly string[];
|
|
40
|
+
/** Distinct pages prefetched by the end of a full scroll (landing included). */
|
|
41
|
+
readonly scrolled: readonly string[];
|
|
42
|
+
readonly links: readonly PrefetchLink[];
|
|
43
|
+
/** Compressed and decoded bytes of every prefetch response, and the page's own compressed HTML (0 when unknown). */
|
|
44
|
+
readonly bytes: {
|
|
45
|
+
readonly prefetchCompressed: number;
|
|
46
|
+
readonly prefetchDecoded: number;
|
|
47
|
+
readonly htmlCompressed: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface PrefetchVerdict {
|
|
51
|
+
readonly failures: readonly string[];
|
|
52
|
+
readonly warnings: readonly string[];
|
|
53
|
+
/** One line for the report, whatever the result. */
|
|
54
|
+
readonly summary: string;
|
|
55
|
+
}
|
|
56
|
+
/** A page path without query, fragment, Next's `.rsc` and `.segments/` suffixes or a trailing slash. */
|
|
57
|
+
export declare function prefetchPagePath(path: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* The page a request prefetches, or null when it is not a Next prefetch of this site. A prefetch is a GET for the
|
|
60
|
+
* React Server Components payload (`rsc: 1`, `next-router-prefetch`, a segment prefetch header, an `_rsc` query or an
|
|
61
|
+
* `.rsc` path) on the site's own origin.
|
|
62
|
+
*/
|
|
63
|
+
export declare function prefetchedPage(request: {
|
|
64
|
+
url: string;
|
|
65
|
+
method: string;
|
|
66
|
+
headers: Readonly<Record<string, string>>;
|
|
67
|
+
}, origin: string): string | null;
|
|
68
|
+
/** W2's verdict for one page at one viewport. */
|
|
69
|
+
export declare function evaluatePrefetch(measurement: PrefetchMeasurement, viewport: string): PrefetchVerdict;
|
|
70
|
+
/**
|
|
71
|
+
* Every same-site link on the page with its off roles and whether it shows. Runs in the page: it uses only its
|
|
72
|
+
* argument and the DOM, plus the site-system page helpers' `isRendered` when they are installed.
|
|
73
|
+
*/
|
|
74
|
+
export declare function collectPrefetchLinks(containers: Readonly<Record<string, string>>): {
|
|
75
|
+
path: string;
|
|
76
|
+
roles: string[];
|
|
77
|
+
rendered: boolean;
|
|
78
|
+
}[];
|
|
79
|
+
/** The links as `evaluatePrefetch` reads them: paths normalised, unknown roles dropped. */
|
|
80
|
+
export declare function toPrefetchLinks(raw: readonly {
|
|
81
|
+
path: string;
|
|
82
|
+
roles: readonly string[];
|
|
83
|
+
rendered: boolean;
|
|
84
|
+
}[]): PrefetchLink[];
|
|
85
|
+
//# sourceMappingURL=prefetch-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch-policy.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/prefetch-policy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,iHAAiH;AACjH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;EASzB,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,uBAAuB,CAAC;AAEnE,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,oHAAoH;IACpH,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5H;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASrD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBjJ;AAuBD,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CA4BpG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,CAqBzI;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,YAAY,EAAE,CAO7H"}
|