@growth-labs/conformance 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/schema.js ADDED
@@ -0,0 +1,129 @@
1
+ import { z } from 'zod';
2
+ export const EVIDENCE_SCHEMA_VERSION = 'growth-labs.conformance.v1';
3
+ export const REQUIRED_NEGATIVE_FIXTURE_CODES = {
4
+ 'csp-unsafe-inline': 'security.csp.unsafe_inline',
5
+ 'headers-static-only-while-ssr-missing': 'security.headers.static_only_for_ssr',
6
+ 'undeclared-na': 'publication.na.undeclared',
7
+ 'stringified-is-accessible-for-free': 'structured_data.is_accessible_for_free.stringified',
8
+ 'video-object-date-only-upload-date': 'structured_data.video_object.upload_date.date_only',
9
+ 'missing-image-dimensions': 'images.dimensions.missing',
10
+ 'hero-under-1200px': 'images.hero.under_1200',
11
+ 'missing-max-image-preview-large': 'metadata.robots.missing_max_image_preview_large',
12
+ 'crawler-public-cache-key-collision': 'cache.crawler_public_key_collision',
13
+ 'runtime-canonical-override-not-applied': 'metadata.canonical.runtime_override_not_applied',
14
+ };
15
+ export const audienceSchema = z.enum([
16
+ 'public',
17
+ 'crawler',
18
+ 'subscriber',
19
+ 'member',
20
+ 'admin',
21
+ 'private',
22
+ ]);
23
+ export const renderModeSchema = z.enum(['ssr', 'static']);
24
+ export const checkStatusSchema = z.enum(['pass', 'fail', 'na']);
25
+ export const findingSeveritySchema = z.enum(['error', 'warning']);
26
+ export const headerValueSchema = z.union([z.string(), z.array(z.string())]);
27
+ export const headerMapSchema = z.record(headerValueSchema);
28
+ export const surfaceResponseSchema = z.object({
29
+ status: z.number().int().min(100).max(599).default(200),
30
+ url: z.string().url().optional(),
31
+ headers: headerMapSchema.default({}),
32
+ html: z.string().optional(),
33
+ body: z.string().optional(),
34
+ });
35
+ export const publicationSurfaceSchema = z.object({
36
+ name: z.string().min(1),
37
+ status: z.enum(['present', 'missing', 'na']),
38
+ url: z.string().url().optional(),
39
+ httpStatus: z.number().int().min(100).max(599).optional(),
40
+ contentType: z.string().min(1).optional(),
41
+ bodyBytes: z.number().int().nonnegative().optional(),
42
+ capabilityBoundary: z.string().min(1).optional(),
43
+ evidence: z.string().min(1).optional(),
44
+ });
45
+ export const cacheVariantSchema = z.object({
46
+ audience: audienceSchema,
47
+ cacheKey: z.string().min(1),
48
+ url: z.string().url().optional(),
49
+ contentClass: z.enum(['html', 'markdown', 'feed', 'asset']).default('html'),
50
+ vary: z.array(z.string().min(1)).default([]),
51
+ headers: headerMapSchema.default({}),
52
+ });
53
+ export const buildEvidenceSchema = z.object({
54
+ commitSha: z.string().min(7).optional(),
55
+ artifactId: z.string().min(1).optional(),
56
+ generatedAt: z.string().min(1).optional(),
57
+ packageVersions: z.record(z.string().min(1)).default({}),
58
+ staticHeaderPolicy: z.boolean().default(false),
59
+ ssrHeaderPolicy: z.boolean().default(false),
60
+ });
61
+ export const conformanceExpectedSchema = z.object({
62
+ canonicalUrl: z.string().url().optional(),
63
+ schemaTypes: z.array(z.string().min(1)).default([]),
64
+ discoverEligible: z.boolean().default(true),
65
+ });
66
+ export const conformanceFixtureSchema = z.object({
67
+ id: z.string().min(1),
68
+ site: z.string().min(1),
69
+ environment: z.string().min(1).default('production'),
70
+ url: z.string().url(),
71
+ renderMode: renderModeSchema,
72
+ audience: audienceSchema,
73
+ response: surfaceResponseSchema,
74
+ expected: conformanceExpectedSchema.default({}),
75
+ publicationSurfaces: z.array(publicationSurfaceSchema).default([]),
76
+ cacheVariants: z.array(cacheVariantSchema).default([]),
77
+ buildEvidence: buildEvidenceSchema.optional(),
78
+ expectedFailingCode: z.string().min(1).optional(),
79
+ });
80
+ export const conformanceFindingSchema = z.object({
81
+ code: z.string().min(1),
82
+ severity: findingSeveritySchema,
83
+ message: z.string().min(1),
84
+ path: z.string().min(1).optional(),
85
+ evidence: z.string().min(1).optional(),
86
+ remediation: z.string().min(1).optional(),
87
+ });
88
+ export const conformanceCheckReceiptSchema = z.object({
89
+ name: z.string().min(1),
90
+ status: checkStatusSchema,
91
+ findings: z.array(conformanceFindingSchema),
92
+ });
93
+ export const evidenceSummarySchema = z.object({
94
+ pass: z.number().int().nonnegative(),
95
+ fail: z.number().int().nonnegative(),
96
+ na: z.number().int().nonnegative(),
97
+ findings: z.number().int().nonnegative(),
98
+ });
99
+ export const siteEvidenceBundleSchema = z.object({
100
+ schemaVersion: z.literal(EVIDENCE_SCHEMA_VERSION),
101
+ generatedAt: z.string().min(1),
102
+ fixtureId: z.string().min(1),
103
+ site: z.string().min(1),
104
+ environment: z.string().min(1),
105
+ url: z.string().url(),
106
+ status: z.enum(['pass', 'fail']),
107
+ summary: evidenceSummarySchema,
108
+ checks: z.array(conformanceCheckReceiptSchema),
109
+ findings: z.array(conformanceFindingSchema),
110
+ });
111
+ export const fleetEvidenceBundleSchema = z.object({
112
+ schemaVersion: z.literal(EVIDENCE_SCHEMA_VERSION),
113
+ generatedAt: z.string().min(1),
114
+ status: z.enum(['pass', 'fail']),
115
+ summary: evidenceSummarySchema.extend({
116
+ sites: z.number().int().nonnegative(),
117
+ }),
118
+ sites: z.array(siteEvidenceBundleSchema),
119
+ });
120
+ export function parseConformanceFixture(input) {
121
+ return conformanceFixtureSchema.parse(input);
122
+ }
123
+ export function validateSiteEvidenceBundle(input) {
124
+ return siteEvidenceBundleSchema.parse(input);
125
+ }
126
+ export function validateFleetEvidenceBundle(input) {
127
+ return fleetEvidenceBundleSchema.parse(input);
128
+ }
129
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ [
2
+ {
3
+ "name": "csp-unsafe-inline",
4
+ "expectedFailingCode": "security.csp.unsafe_inline",
5
+ "patch": {
6
+ "response": {
7
+ "headers": {
8
+ "content-security-policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
9
+ }
10
+ }
11
+ }
12
+ },
13
+ {
14
+ "name": "headers-static-only-while-ssr-missing",
15
+ "expectedFailingCode": "security.headers.static_only_for_ssr",
16
+ "replacePaths": ["response.headers"],
17
+ "patch": {
18
+ "response": {
19
+ "headers": {
20
+ "cache-control": "public, max-age=0, s-maxage=300"
21
+ }
22
+ },
23
+ "buildEvidence": {
24
+ "staticHeaderPolicy": true,
25
+ "ssrHeaderPolicy": false
26
+ }
27
+ }
28
+ },
29
+ {
30
+ "name": "undeclared-na",
31
+ "expectedFailingCode": "publication.na.undeclared",
32
+ "patch": {
33
+ "publicationSurfaces": [
34
+ {
35
+ "name": "sitemap",
36
+ "status": "present",
37
+ "url": "https://fixture.example/sitemap.xml",
38
+ "httpStatus": 200,
39
+ "contentType": "application/xml",
40
+ "bodyBytes": 640
41
+ },
42
+ {
43
+ "name": "robots",
44
+ "status": "present",
45
+ "url": "https://fixture.example/robots.txt",
46
+ "httpStatus": 200,
47
+ "contentType": "text/plain",
48
+ "bodyBytes": 180
49
+ },
50
+ {
51
+ "name": "feed",
52
+ "status": "na"
53
+ }
54
+ ]
55
+ }
56
+ },
57
+ {
58
+ "name": "stringified-is-accessible-for-free",
59
+ "expectedFailingCode": "structured_data.is_accessible_for_free.stringified",
60
+ "patch": {
61
+ "response": {
62
+ "html": "<!doctype html><html><head><title>Fixture publication conformance article</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"robots\" content=\"index, follow, max-image-preview:large\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"NewsArticle\",\"headline\":\"Fixture publication conformance article\",\"isAccessibleForFree\":\"true\",\"datePublished\":\"2026-07-15T12:00:00Z\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/valid-16x9.jpg\",\"width\":1200,\"height\":675}]}</script></head><body><main><h1>Fixture publication conformance article</h1></main></body></html>"
63
+ }
64
+ }
65
+ },
66
+ {
67
+ "name": "video-object-date-only-upload-date",
68
+ "expectedFailingCode": "structured_data.video_object.upload_date.date_only",
69
+ "patch": {
70
+ "expected": {
71
+ "schemaTypes": ["VideoObject"]
72
+ },
73
+ "response": {
74
+ "html": "<!doctype html><html><head><title>Fixture publication conformance video</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"robots\" content=\"index, follow, max-image-preview:large\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"VideoObject\",\"name\":\"Fixture publication conformance video\",\"isAccessibleForFree\":true,\"uploadDate\":\"2026-07-15\",\"thumbnailUrl\":\"https://media.fixture.example/images/articles/video-poster.jpg\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/video-poster.jpg\",\"width\":1200,\"height\":675}]}</script></head><body><main><h1>Fixture publication conformance video</h1></main></body></html>"
75
+ }
76
+ }
77
+ },
78
+ {
79
+ "name": "missing-image-dimensions",
80
+ "expectedFailingCode": "images.dimensions.missing",
81
+ "patch": {
82
+ "response": {
83
+ "html": "<!doctype html><html><head><title>Fixture publication conformance article</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"robots\" content=\"index, follow, max-image-preview:large\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"NewsArticle\",\"headline\":\"Fixture publication conformance article\",\"isAccessibleForFree\":true,\"datePublished\":\"2026-07-15T12:00:00Z\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/no-dimensions.jpg\"}]}</script></head><body><main><h1>Fixture publication conformance article</h1></main></body></html>"
84
+ }
85
+ }
86
+ },
87
+ {
88
+ "name": "hero-under-1200px",
89
+ "expectedFailingCode": "images.hero.under_1200",
90
+ "patch": {
91
+ "response": {
92
+ "html": "<!doctype html><html><head><title>Fixture publication conformance article</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"robots\" content=\"index, follow, max-image-preview:large\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"NewsArticle\",\"headline\":\"Fixture publication conformance article\",\"isAccessibleForFree\":true,\"datePublished\":\"2026-07-15T12:00:00Z\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/small-hero.jpg\",\"width\":900,\"height\":506}]}</script></head><body><main><h1>Fixture publication conformance article</h1></main></body></html>"
93
+ }
94
+ }
95
+ },
96
+ {
97
+ "name": "missing-max-image-preview-large",
98
+ "expectedFailingCode": "metadata.robots.missing_max_image_preview_large",
99
+ "patch": {
100
+ "response": {
101
+ "html": "<!doctype html><html><head><title>Fixture publication conformance article</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"robots\" content=\"index, follow\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"NewsArticle\",\"headline\":\"Fixture publication conformance article\",\"isAccessibleForFree\":true,\"datePublished\":\"2026-07-15T12:00:00Z\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/valid-16x9.jpg\",\"width\":1200,\"height\":675}]}</script></head><body><main><h1>Fixture publication conformance article</h1></main></body></html>"
102
+ }
103
+ }
104
+ },
105
+ {
106
+ "name": "crawler-public-cache-key-collision",
107
+ "expectedFailingCode": "cache.crawler_public_key_collision",
108
+ "patch": {
109
+ "cacheVariants": [
110
+ {
111
+ "audience": "public",
112
+ "cacheKey": "fixture-news:/articles/valid-publication",
113
+ "url": "https://fixture.example/articles/valid-publication"
114
+ },
115
+ {
116
+ "audience": "crawler",
117
+ "cacheKey": "fixture-news:/articles/valid-publication",
118
+ "url": "https://fixture.example/articles/valid-publication"
119
+ }
120
+ ]
121
+ }
122
+ },
123
+ {
124
+ "name": "runtime-canonical-override-not-applied",
125
+ "expectedFailingCode": "metadata.canonical.runtime_override_not_applied",
126
+ "patch": {
127
+ "expected": {
128
+ "canonicalUrl": "https://fixture.example/articles/runtime-canonical"
129
+ }
130
+ }
131
+ }
132
+ ]
@@ -0,0 +1,76 @@
1
+ {
2
+ "id": "valid-public-article",
3
+ "site": "fixture-news",
4
+ "environment": "fixture",
5
+ "url": "https://fixture.example/articles/valid-publication",
6
+ "renderMode": "ssr",
7
+ "audience": "public",
8
+ "response": {
9
+ "status": 200,
10
+ "url": "https://fixture.example/articles/valid-publication",
11
+ "headers": {
12
+ "content-security-policy": "default-src 'self'; script-src 'self' 'nonce-fixture'; style-src 'self' 'nonce-fixture'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
13
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
14
+ "x-frame-options": "DENY",
15
+ "x-content-type-options": "nosniff",
16
+ "referrer-policy": "strict-origin-when-cross-origin",
17
+ "permissions-policy": "accelerometer=(), camera=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=()",
18
+ "cache-control": "public, max-age=0, s-maxage=300, stale-while-revalidate=86400"
19
+ },
20
+ "html": "<!doctype html><html><head><title>Fixture publication conformance article</title><link rel=\"canonical\" href=\"https://fixture.example/articles/valid-publication\"><meta name=\"description\" content=\"A deterministic public article fixture for the Growth Labs conformance harness.\"><meta name=\"robots\" content=\"index, follow, max-image-preview:large\"><script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"NewsArticle\",\"headline\":\"Fixture publication conformance article\",\"isAccessibleForFree\":true,\"datePublished\":\"2026-07-15T12:00:00Z\",\"image\":[{\"url\":\"https://media.fixture.example/images/articles/valid-16x9.jpg\",\"width\":1200,\"height\":675},{\"url\":\"https://media.fixture.example/images/articles/valid-4x3.jpg\",\"width\":1200,\"height\":900},{\"url\":\"https://media.fixture.example/images/articles/valid-1x1.jpg\",\"width\":1200,\"height\":1200}]}</script></head><body><main><h1>Fixture publication conformance article</h1><p>Fixture body.</p></main></body></html>"
21
+ },
22
+ "expected": {
23
+ "canonicalUrl": "https://fixture.example/articles/valid-publication",
24
+ "schemaTypes": ["NewsArticle"],
25
+ "discoverEligible": true
26
+ },
27
+ "publicationSurfaces": [
28
+ {
29
+ "name": "sitemap",
30
+ "status": "present",
31
+ "url": "https://fixture.example/sitemap.xml",
32
+ "httpStatus": 200,
33
+ "contentType": "application/xml",
34
+ "bodyBytes": 640
35
+ },
36
+ {
37
+ "name": "robots",
38
+ "status": "present",
39
+ "url": "https://fixture.example/robots.txt",
40
+ "httpStatus": 200,
41
+ "contentType": "text/plain",
42
+ "bodyBytes": 180
43
+ },
44
+ {
45
+ "name": "feed",
46
+ "status": "present",
47
+ "url": "https://fixture.example/feed.xml",
48
+ "httpStatus": 200,
49
+ "contentType": "application/rss+xml",
50
+ "bodyBytes": 940
51
+ }
52
+ ],
53
+ "cacheVariants": [
54
+ {
55
+ "audience": "public",
56
+ "cacheKey": "fixture-news:public:/articles/valid-publication",
57
+ "url": "https://fixture.example/articles/valid-publication"
58
+ },
59
+ {
60
+ "audience": "crawler",
61
+ "cacheKey": "fixture-news:crawler:/articles/valid-publication",
62
+ "url": "https://fixture.example/articles/valid-publication"
63
+ }
64
+ ],
65
+ "buildEvidence": {
66
+ "commitSha": "bd39d718f6a5b0df9a249e11638a8b430050941e",
67
+ "artifactId": "fixture-build-001",
68
+ "generatedAt": "2026-07-15T23:30:00Z",
69
+ "packageVersions": {
70
+ "@growth-labs/conformance": "0.1.0",
71
+ "@growth-labs/seo": "0.9.0"
72
+ },
73
+ "staticHeaderPolicy": true,
74
+ "ssrHeaderPolicy": true
75
+ }
76
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@growth-labs/conformance",
3
+ "version": "0.1.0",
4
+ "description": "Reusable fleet publication conformance harness for Growth Labs sites: headers, cache, metadata, structured data, images, publication surfaces, and evidence bundles.",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./policy": {
13
+ "types": "./dist/policy.d.ts",
14
+ "import": "./dist/policy.js"
15
+ },
16
+ "./schema": {
17
+ "types": "./dist/schema.d.ts",
18
+ "import": "./dist/schema.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src/**/*.ts",
24
+ "fixtures/**/*.json",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
32
+ "dependencies": {
33
+ "zod": "^3.25.0"
34
+ },
35
+ "devDependencies": {
36
+ "@cloudflare/workers-types": "^4.20260402.1",
37
+ "typescript": "^5.7.0",
38
+ "vitest": "^4.1.8"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "dev": "tsc --watch",
43
+ "check": "biome check src/ __tests__ package.json README.md CHANGELOG.md",
44
+ "test": "tsc --noEmit -p tsconfig.test.json && vitest run",
45
+ "test:fixtures": "vitest run __tests__/negative-fixtures.test.ts",
46
+ "test:watch": "vitest"
47
+ }
48
+ }
package/src/html.ts ADDED
@@ -0,0 +1,175 @@
1
+ export interface JsonLdParseError {
2
+ message: string
3
+ path: string
4
+ }
5
+
6
+ export interface JsonLdNode {
7
+ [key: string]: unknown
8
+ }
9
+
10
+ export interface ExtractedImage {
11
+ url?: string
12
+ width?: number
13
+ height?: number
14
+ path: string
15
+ }
16
+
17
+ export interface HtmlSignals {
18
+ canonicalUrl?: string
19
+ robotsContent?: string
20
+ jsonLdNodes: JsonLdNode[]
21
+ jsonLdParseErrors: JsonLdParseError[]
22
+ jsonLdImages: ExtractedImage[]
23
+ }
24
+
25
+ export function extractHtmlSignals(html = ''): HtmlSignals {
26
+ const jsonLd = extractJsonLdNodes(html)
27
+ const jsonLdImages = jsonLd.nodes.flatMap((node, index) =>
28
+ extractImagesFromJsonLdValue(node.image, `jsonLd[${index}].image`),
29
+ )
30
+
31
+ return {
32
+ canonicalUrl: extractCanonicalUrl(html),
33
+ robotsContent: extractMetaContent(html, 'robots'),
34
+ jsonLdNodes: jsonLd.nodes,
35
+ jsonLdParseErrors: jsonLd.errors,
36
+ jsonLdImages,
37
+ }
38
+ }
39
+
40
+ export function getSchemaTypes(node: JsonLdNode): string[] {
41
+ const value = node['@type']
42
+ if (typeof value === 'string') return [value]
43
+ if (Array.isArray(value)) return value.filter((item): item is string => typeof item === 'string')
44
+ return []
45
+ }
46
+
47
+ export function nodeHasSchemaType(node: JsonLdNode, expectedType: string): boolean {
48
+ return getSchemaTypes(node).includes(expectedType)
49
+ }
50
+
51
+ function extractCanonicalUrl(html: string): string | undefined {
52
+ for (const tag of findOpeningTags(html, 'link')) {
53
+ const attrs = parseAttributes(tag)
54
+ if (attrs.rel?.split(/\s+/).some((value) => value.toLowerCase() === 'canonical')) {
55
+ return attrs.href
56
+ }
57
+ }
58
+ return undefined
59
+ }
60
+
61
+ function extractMetaContent(html: string, name: string): string | undefined {
62
+ const wanted = name.toLowerCase()
63
+ for (const tag of findOpeningTags(html, 'meta')) {
64
+ const attrs = parseAttributes(tag)
65
+ const metaName = attrs.name?.toLowerCase() ?? attrs.property?.toLowerCase()
66
+ if (metaName === wanted) return attrs.content
67
+ }
68
+ return undefined
69
+ }
70
+
71
+ function extractJsonLdNodes(html: string): { nodes: JsonLdNode[]; errors: JsonLdParseError[] } {
72
+ const nodes: JsonLdNode[] = []
73
+ const errors: JsonLdParseError[] = []
74
+ const scriptPattern = /<script\b[^>]*>([\s\S]*?)<\/script>/gi
75
+ let scriptIndex = 0
76
+
77
+ for (const match of html.matchAll(scriptPattern)) {
78
+ const fullTag = match[0] ?? ''
79
+ const openTag = fullTag.slice(0, fullTag.indexOf('>') + 1)
80
+ const attrs = parseAttributes(openTag)
81
+ if ((attrs.type ?? '').toLowerCase() !== 'application/ld+json') continue
82
+
83
+ const body = match[1] ?? ''
84
+ try {
85
+ const parsed = JSON.parse(body.trim()) as unknown
86
+ collectJsonLdNodes(parsed, nodes)
87
+ } catch (error) {
88
+ const message = error instanceof Error ? error.message : String(error)
89
+ errors.push({ message, path: `script[application/ld+json][${scriptIndex}]` })
90
+ }
91
+ scriptIndex += 1
92
+ }
93
+
94
+ return { nodes, errors }
95
+ }
96
+
97
+ function collectJsonLdNodes(value: unknown, out: JsonLdNode[]): void {
98
+ if (Array.isArray(value)) {
99
+ for (const item of value) collectJsonLdNodes(item, out)
100
+ return
101
+ }
102
+
103
+ if (!isRecord(value)) return
104
+ out.push(value)
105
+
106
+ const graph = value['@graph']
107
+ if (Array.isArray(graph)) {
108
+ for (const item of graph) collectJsonLdNodes(item, out)
109
+ }
110
+ }
111
+
112
+ function extractImagesFromJsonLdValue(value: unknown, path: string): ExtractedImage[] {
113
+ if (Array.isArray(value)) {
114
+ return value.flatMap((item, index) => extractImagesFromJsonLdValue(item, `${path}[${index}]`))
115
+ }
116
+
117
+ if (typeof value === 'string') {
118
+ return [{ url: value, path }]
119
+ }
120
+
121
+ if (!isRecord(value)) return []
122
+
123
+ const url = firstString(value.url, value.contentUrl, value['@id'])
124
+ const width = toFiniteNumber(value.width)
125
+ const height = toFiniteNumber(value.height)
126
+
127
+ return [{ url, width, height, path }]
128
+ }
129
+
130
+ function findOpeningTags(html: string, tagName: string): string[] {
131
+ const pattern = new RegExp(`<${tagName}\\b[^>]*>`, 'gi')
132
+ return [...html.matchAll(pattern)].map((match) => match[0] ?? '')
133
+ }
134
+
135
+ function parseAttributes(tag: string): Record<string, string> {
136
+ const attrs: Record<string, string> = {}
137
+ const attributePattern = /([A-Za-z_:][-A-Za-z0-9_:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/g
138
+
139
+ for (const match of tag.matchAll(attributePattern)) {
140
+ const name = match[1]?.toLowerCase()
141
+ if (!name) continue
142
+ attrs[name] = decodeHtmlEntities(match[2] ?? match[3] ?? match[4] ?? '')
143
+ }
144
+
145
+ return attrs
146
+ }
147
+
148
+ function firstString(...values: unknown[]): string | undefined {
149
+ for (const value of values) {
150
+ if (typeof value === 'string' && value.trim() !== '') return value
151
+ }
152
+ return undefined
153
+ }
154
+
155
+ function toFiniteNumber(value: unknown): number | undefined {
156
+ if (typeof value === 'number' && Number.isFinite(value)) return value
157
+ if (typeof value === 'string' && value.trim() !== '') {
158
+ const parsed = Number(value)
159
+ if (Number.isFinite(parsed)) return parsed
160
+ }
161
+ return undefined
162
+ }
163
+
164
+ function decodeHtmlEntities(value: string): string {
165
+ return value
166
+ .replaceAll('&amp;', '&')
167
+ .replaceAll('&quot;', '"')
168
+ .replaceAll('&#39;', "'")
169
+ .replaceAll('&lt;', '<')
170
+ .replaceAll('&gt;', '>')
171
+ }
172
+
173
+ function isRecord(value: unknown): value is JsonLdNode {
174
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
175
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ export {
2
+ buildCacheControl,
3
+ buildContentSecurityPolicy,
4
+ buildSharedPublicationHeaders,
5
+ type CachePolicyOptions,
6
+ type HeaderPolicyOptions,
7
+ isPrivateAudience,
8
+ normalizeHeaders,
9
+ REQUIRED_SECURITY_HEADERS,
10
+ } from './policy.js'
11
+ export { type RunConformanceOptions, runFleetConformance, runSiteConformance } from './runner.js'
12
+ export {
13
+ type Audience,
14
+ audienceSchema,
15
+ type BuildEvidence,
16
+ buildEvidenceSchema,
17
+ type CacheVariant,
18
+ type ConformanceCheckReceipt,
19
+ type ConformanceFinding,
20
+ type ConformanceFixture,
21
+ type ConformanceFixtureInput,
22
+ cacheVariantSchema,
23
+ checkStatusSchema,
24
+ conformanceCheckReceiptSchema,
25
+ conformanceExpectedSchema,
26
+ conformanceFindingSchema,
27
+ conformanceFixtureSchema,
28
+ EVIDENCE_SCHEMA_VERSION,
29
+ type EvidenceSummary,
30
+ evidenceSummarySchema,
31
+ type FleetEvidenceBundle,
32
+ findingSeveritySchema,
33
+ fleetEvidenceBundleSchema,
34
+ type HeaderMap,
35
+ headerMapSchema,
36
+ headerValueSchema,
37
+ type PublicationSurfaceEvidence,
38
+ parseConformanceFixture,
39
+ publicationSurfaceSchema,
40
+ REQUIRED_NEGATIVE_FIXTURE_CODES,
41
+ type RenderMode,
42
+ type RequiredNegativeFindingCode,
43
+ type RequiredNegativeFixtureId,
44
+ renderModeSchema,
45
+ type SiteEvidenceBundle,
46
+ type SurfaceResponse,
47
+ siteEvidenceBundleSchema,
48
+ surfaceResponseSchema,
49
+ validateFleetEvidenceBundle,
50
+ validateSiteEvidenceBundle,
51
+ } from './schema.js'