@brandlab-digital/site-next 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.d.ts +2 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +5 -1
- package/dist/analytics.js.map +1 -1
- package/dist/contact/form-client.d.ts +1 -1
- package/dist/contact/form-client.d.ts.map +1 -1
- package/dist/contact/form-client.js +1 -1
- package/dist/contact/form-client.js.map +1 -1
- package/dist/contact/resend.d.ts +25 -3
- package/dist/contact/resend.d.ts.map +1 -1
- package/dist/contact/resend.js +54 -8
- package/dist/contact/resend.js.map +1 -1
- package/dist/contact/server.d.ts +11 -1
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +50 -2
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/testing.js +1 -1
- package/dist/contact/testing.js.map +1 -1
- package/dist/google-reviews-client.d.ts +9 -4
- package/dist/google-reviews-client.d.ts.map +1 -1
- package/dist/google-reviews-client.js +60 -13
- package/dist/google-reviews-client.js.map +1 -1
- package/dist/google-reviews-server.d.ts +11 -2
- package/dist/google-reviews-server.d.ts.map +1 -1
- package/dist/google-reviews-server.js +52 -18
- package/dist/google-reviews-server.js.map +1 -1
- package/dist/google-tag-plan.d.ts +5 -0
- package/dist/google-tag-plan.d.ts.map +1 -1
- package/dist/google-tag-plan.js +16 -1
- package/dist/google-tag-plan.js.map +1 -1
- package/dist/headers.d.ts +22 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +34 -1
- package/dist/headers.js.map +1 -1
- package/dist/lead-registry.d.ts +2 -0
- package/dist/lead-registry.d.ts.map +1 -1
- package/dist/lead-registry.js.map +1 -1
- package/dist/lead-reporting.d.ts +7 -3
- package/dist/lead-reporting.d.ts.map +1 -1
- package/dist/lead-reporting.js +5 -2
- package/dist/lead-reporting.js.map +1 -1
- package/dist/next-config.d.ts +15 -0
- package/dist/next-config.d.ts.map +1 -1
- package/dist/next-config.js +29 -4
- package/dist/next-config.js.map +1 -1
- package/dist/robots.d.ts +4 -2
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +12 -2
- package/dist/robots.js.map +1 -1
- package/dist/user-interaction.d.ts +14 -0
- package/dist/user-interaction.d.ts.map +1 -0
- package/dist/user-interaction.js +53 -0
- package/dist/user-interaction.js.map +1 -0
- package/package.json +2 -2
package/dist/headers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brandlab security/response headers: one canonical header set for every\n * client site, emitted by `defineBrandlabNextConfig`.\n *\n * The deployment decides everything environment-specific:\n * - a production build (`next build`, on Vercel or locally) enforces the\n * production Content Security Policy and sends HSTS;\n * - `next dev` sends a report-only development policy without HSTS;\n * - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and\n * `private, no-store`, so previews never reach search results or caches.\n *\n * The production X-Robots-Tag carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots.\n *\n * Declared public folders of static files (`staticAssetCache`) are served with\n * `public, max-age=31536000, immutable` by production builds; Previews and\n * `next dev` never cache them.\n */\n\nimport type {\n BrandlabDeployment,\n BrandlabDeploymentEnvironment,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n}\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\nexport const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive'\n\nexport const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\n/** Cache-Control for the declared static public folders on production builds. */\nexport const BRANDLAB_STATIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\nconst PROTECTED_HEADER_NAMES = new Set([\n 'cache-control',\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\nconst DEPLOYMENT_ENVIRONMENTS = new Set<BrandlabDeploymentEnvironment>([\n 'production',\n 'preview',\n 'development',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport interface BrandlabSecurityHeaderOptions {\n /** From resolveBrandlabDeployment(process.env). */\n deployment: BrandlabDeployment\n hsts?: BrandlabHstsExpansion\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n /** Reviewed project exception, for example `geolocation=(self)` for a location feature. */\n permissionsPolicy?: string\n /** Reviewed project headers; they cannot replace a protected header. */\n extraHeaders?: readonly BrandlabHeader[]\n}\n\nfunction assertDeployment(deployment: unknown): asserts deployment is BrandlabDeployment {\n const candidate = deployment as Partial<BrandlabDeployment> | undefined\n if (\n !candidate ||\n typeof candidate !== 'object' ||\n !DEPLOYMENT_ENVIRONMENTS.has(candidate.environment as BrandlabDeploymentEnvironment) ||\n typeof candidate.productionBuild !== 'boolean'\n ) {\n throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).')\n }\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n deployment: BrandlabDeployment\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n assertDeployment(options.deployment)\n const productionBuild = options.deployment.productionBuild\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(productionBuild ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(productionBuild ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nconst HEADER_OPTION_KEYS = new Set([\n 'deployment',\n 'hsts',\n 'contentSecurityPolicyExtensions',\n 'permissionsPolicy',\n 'extraHeaders',\n])\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(\n `Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`,\n )\n }\n assertDeployment(options.deployment)\n const { environment, productionBuild } = options.deployment\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const preview = environment === 'preview'\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n {\n key: 'X-Robots-Tag',\n value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,\n },\n ...(productionBuild\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n {\n key: productionBuild\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n },\n ...(options.extraHeaders ?? []).map((header) => ({ ...header })),\n ...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),\n ]\n}\n\nconst STATIC_ASSET_SOURCE = /^\\/((?:[A-Za-z0-9][A-Za-z0-9._-]*\\/){1,4}):([A-Za-z_][A-Za-z0-9_]*)\\*$/\nconst STATIC_ASSET_RESERVED = new Set(['api', '_next', '_vercel'])\nconst STATIC_ASSET_MAX_SOURCES = 10\nconst STATIC_ASSET_MAX_LENGTH = 100\n\nexport interface BrandlabStaticAssetFolder {\n /** The declared source, for example `/images/:path*`. */\n source: string\n /** The public folder's path segments, for example `['images']`. */\n folder: readonly string[]\n /**\n * The emitted rule's source: file names with an extension below the folder,\n * such as `/images/:path(.+\\.[A-Za-z0-9]+)`. The folder itself and\n * extensionless paths, where a page would answer, never match.\n */\n fileSource: string\n}\n\n/**\n * Validates `staticAssetCache`: at most 10 public folders, each written as Next's\n * source `/<folder>/:path*` (one to four literal segments, then one catch-all\n * parameter, at most 100 characters). `/`, `/api`, `/_next` and `/_vercel` are\n * rejected.\n */\nexport function parseBrandlabStaticAssetCache(sources: unknown): BrandlabStaticAssetFolder[] {\n if (!Array.isArray(sources)) {\n throw new Error('staticAssetCache must list public folders such as /images/:path*.')\n }\n if (sources.length > STATIC_ASSET_MAX_SOURCES) {\n throw new Error(`staticAssetCache lists at most ${STATIC_ASSET_MAX_SOURCES} public folders.`)\n }\n const seen = new Set<string>()\n return sources.map((source: unknown, index) => {\n const field = `staticAssetCache[${index}]`\n if (typeof source !== 'string' || source.length > STATIC_ASSET_MAX_LENGTH) {\n throw new Error(`${field} must be a source of at most ${STATIC_ASSET_MAX_LENGTH} characters.`)\n }\n if (STATIC_ASSET_RESERVED.has(source.split('/')[1]?.toLowerCase() ?? '')) {\n throw new Error(`${field} \"${source}\" is a Next or API path, not a public folder.`)\n }\n const match = STATIC_ASSET_SOURCE.exec(source)\n if (!match) {\n throw new Error(\n `${field} \"${source}\" must name a public folder as /<folder>/:path* (literal folder segments, then one catch-all parameter).`,\n )\n }\n const folder = match[1]!.slice(0, -1).split('/')\n const key = folder.join('/').toLowerCase()\n if (seen.has(key)) throw new Error(`${field} repeats the /${folder.join('/')} folder.`)\n seen.add(key)\n return { source, folder, fileSource: `/${folder.join('/')}/:${match[2]}(.+\\\\.[A-Za-z0-9]+)` }\n })\n}\n\nexport interface BrandlabHeaderRuleOptions extends BrandlabSecurityHeaderOptions {\n /**\n * Public folders of static files that never change in place, for example\n * `['/images/:path*', '/fonts/:path*']`: production builds serve their files with\n * `public, max-age=31536000, immutable`. A file replaced under the same name stays\n * cached in browsers for up to a year, so give a changed file a new name.\n */\n staticAssetCache?: readonly string[]\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabHeaderRuleOptions): BrandlabHeaderRule[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const { staticAssetCache, ...security } = options\n const folders = staticAssetCache === undefined ? [] : parseBrandlabStaticAssetCache(staticAssetCache)\n const rules: BrandlabHeaderRule[] = [\n {\n source: '/:path*',\n headers: buildBrandlabSecurityHeaders(security),\n },\n ]\n // A production build outside a Preview; Previews keep private, no-store everywhere.\n const { environment, productionBuild } = options.deployment\n if (productionBuild && environment !== 'preview') {\n for (const folder of folders) {\n rules.push({\n source: folder.fileSource,\n headers: [{ key: 'Cache-Control', value: BRANDLAB_STATIC_ASSET_CACHE_CONTROL }],\n })\n }\n }\n return rules\n}\n"]}
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brandlab security/response headers: one canonical header set for every\n * client site, emitted by `defineBrandlabNextConfig`.\n *\n * The deployment decides everything environment-specific:\n * - a production build (`next build`, on Vercel or locally) enforces the\n * production Content Security Policy and sends HSTS;\n * - `next dev` sends a report-only development policy without HSTS;\n * - a Vercel Preview additionally sends `noindex, nofollow, noarchive` and\n * `private, no-store`, so previews never reach search results or caches.\n *\n * The production X-Robots-Tag carries snippet-display controls only; per-page\n * indexation stays owned by Metadata.robots.\n *\n * Declared public folders of static files (`staticAssetCache`) are served with\n * `public, max-age=31536000, immutable` by production builds; Previews and\n * `next dev` never cache them.\n */\n\nimport {\n isBrandlabHostname,\n type BrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabHeader {\n key: string\n value: string\n}\n\nexport interface BrandlabHeaderRule {\n source: string\n headers: BrandlabHeader[]\n /** The rule applies only when none of these match (Next `missing`: host values are anchored regular expressions). */\n missing?: { type: 'host'; value: string }[]\n}\n\nexport const BRANDLAB_DEFAULT_PERMISSIONS_POLICY =\n 'camera=(), microphone=(), geolocation=()'\n\nexport const BRANDLAB_DEFAULT_X_ROBOTS_TAG =\n 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'\n\n/**\n * What a production deployment answers on any host but its canonical one (3.2.1): the `<project>.vercel.app` alias\n * and the deployment's own URLs serve the full site, so they tell crawlers not to index it. Alias hosts redirect.\n */\nexport const BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG = 'noindex'\n\n/** Host patterns a production build answers normally besides its canonical host: a local `next start` (CI, the harness). */\nconst LOCAL_HOST_PATTERNS = ['localhost', '127\\\\.0\\\\.0\\\\.1']\n\n/** A host as an exact pattern (Next anchors `has`/`missing` values as regular expressions). */\nfunction exactHostPattern(host: string): string {\n return host.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Production deployments answer every host but the canonical one (and a local server's) with\n * `X-Robots-Tag: noindex`: the `*.vercel.app` production alias and deployment URLs serve the full site. The rule\n * comes last, so it overrides the default X-Robots-Tag there; the canonical host keeps the default.\n */\nexport function buildNonCanonicalHostRule(canonicalHost: string): BrandlabHeaderRule {\n // Next compares a request's host lowercased and without its port: a host in any other form would match no request,\n // and every host, the canonical one included, would be told noindex.\n const host = typeof canonicalHost === 'string' ? canonicalHost.toLowerCase() : canonicalHost\n if (!isBrandlabHostname(host)) {\n throw new Error(`The canonical host must be a bare hostname (no scheme, port, path or trailing dot): \"${String(canonicalHost)}\".`)\n }\n return {\n source: '/:path*',\n missing: [exactHostPattern(host), ...LOCAL_HOST_PATTERNS].map((value) => ({ type: 'host' as const, value })),\n headers: [{ key: 'X-Robots-Tag', value: BRANDLAB_NON_CANONICAL_HOST_X_ROBOTS_TAG }],\n }\n}\n\nexport const BRANDLAB_PREVIEW_X_ROBOTS_TAG = 'noindex, nofollow, noarchive'\n\nexport const BRANDLAB_PREVIEW_CACHE_CONTROL = 'private, no-store, max-age=0'\n\nexport const BRANDLAB_HSTS_VALUE =\n 'max-age=31536000'\n\n/** Cache-Control for the declared static public folders on production builds. */\nexport const BRANDLAB_STATIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable'\n\nexport interface BrandlabHstsExpansion {\n includeSubDomains: true\n preload?: true\n /** Reviewed proof that every affected subdomain is HTTPS-ready. */\n readinessEvidence: string\n}\n\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\nconst PROTECTED_HEADER_NAMES = new Set([\n 'cache-control',\n 'content-security-policy',\n 'content-security-policy-report-only',\n 'x-frame-options',\n 'x-content-type-options',\n 'referrer-policy',\n 'permissions-policy',\n 'x-robots-tag',\n 'strict-transport-security',\n])\nconst DEPLOYMENT_ENVIRONMENTS = new Set<BrandlabDeploymentEnvironment>([\n 'production',\n 'preview',\n 'development',\n])\n\nexport interface BrandlabContentSecurityPolicyExtensions {\n scriptSrc?: readonly string[]\n connectSrc?: readonly string[]\n frameSrc?: readonly string[]\n}\n\nexport interface BrandlabSecurityHeaderOptions {\n /** From resolveBrandlabDeployment(process.env). */\n deployment: BrandlabDeployment\n hsts?: BrandlabHstsExpansion\n /** Reviewed HTTPS origins needed by an approved project integration. */\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n /** Reviewed project exception, for example `geolocation=(self)` for a location feature. */\n permissionsPolicy?: string\n /** Reviewed project headers; they cannot replace a protected header. */\n extraHeaders?: readonly BrandlabHeader[]\n}\n\nfunction assertDeployment(deployment: unknown): asserts deployment is BrandlabDeployment {\n const candidate = deployment as Partial<BrandlabDeployment> | undefined\n if (\n !candidate ||\n typeof candidate !== 'object' ||\n !DEPLOYMENT_ENVIRONMENTS.has(candidate.environment as BrandlabDeploymentEnvironment) ||\n typeof candidate.productionBuild !== 'boolean'\n ) {\n throw new Error('Header rules require the deployment from resolveBrandlabDeployment(process.env).')\n }\n}\n\nexport function buildBrandlabContentSecurityPolicy(options: {\n deployment: BrandlabDeployment\n contentSecurityPolicyExtensions?: BrandlabContentSecurityPolicyExtensions\n}) {\n assertDeployment(options.deployment)\n const productionBuild = options.deployment.productionBuild\n\n const scriptSources = [\n \"'self'\",\n \"'unsafe-inline'\",\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://www.google.com',\n 'https://www.gstatic.com',\n 'https://pagead2.googlesyndication.com',\n 'https://googleads.g.doubleclick.net',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.scriptSrc, 'scriptSrc'),\n ...(productionBuild ? [] : [\"'unsafe-eval'\", 'http://localhost:*']),\n ]\n const connectSources = [\n \"'self'\",\n 'https://www.google-analytics.com',\n 'https://*.google-analytics.com',\n 'https://www.googletagmanager.com',\n 'https://www.googleadservices.com',\n 'https://*.doubleclick.net',\n 'https://*.google.com',\n 'https://google.com',\n 'https://www.google.com.au',\n 'https://pagead2.googlesyndication.com',\n 'https://*.googletagmanager.com',\n 'https://*.analytics.google.com',\n 'https://challenges.cloudflare.com',\n ...normalizeCspExtensions(options.contentSecurityPolicyExtensions?.connectSrc, 'connectSrc'),\n ...(productionBuild ? [] : ['http://localhost:*', 'ws://localhost:*']),\n ]\n const directives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n `script-src ${scriptSources.join(' ')}`,\n \"style-src 'self' 'unsafe-inline'\",\n \"font-src 'self' data:\",\n \"img-src 'self' data: blob: https:\",\n `connect-src ${connectSources.join(' ')}`,\n `frame-src 'self' https://challenges.cloudflare.com https://www.googletagmanager.com https://www.google.com ${normalizeCspExtensions(\n options.contentSecurityPolicyExtensions?.frameSrc,\n 'frameSrc',\n ).join(' ')}`.trim(),\n \"manifest-src 'self'\",\n \"worker-src 'self' blob:\",\n ...(productionBuild ? ['upgrade-insecure-requests'] : []),\n ]\n return directives.join('; ')\n}\n\nfunction normalizeCspExtensions(\n sources: readonly string[] | undefined,\n field: keyof BrandlabContentSecurityPolicyExtensions,\n) {\n if (!sources) return []\n const normalized = sources.map((source) => {\n const value = source.trim().toLowerCase()\n if (!/^https:\\/\\/(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\\d{1,5})?$/.test(value)) {\n throw new Error(`${field} entries must be HTTPS origins without paths or CSP keywords.`)\n }\n return value\n })\n if (new Set(normalized).size !== normalized.length) {\n throw new Error(`${field} entries must be unique.`)\n }\n return normalized\n}\n\nfunction assertHeaderValue(value: string, field: string) {\n if (!value || value.length > 4_096 || /[\\u0000-\\u001F\\u007F]/.test(value)) {\n throw new Error(`${field} must be a bounded single-line header value.`)\n }\n}\n\nfunction validateExtraHeaders(headers: readonly BrandlabHeader[]) {\n const names = new Set<string>()\n headers.forEach((header, index) => {\n if (!HEADER_NAME_PATTERN.test(header.key)) {\n throw new Error(`extraHeaders[${index}].key is invalid.`)\n }\n const name = header.key.toLowerCase()\n if (PROTECTED_HEADER_NAMES.has(name) || names.has(name)) {\n throw new Error(`extraHeaders[${index}] duplicates a protected or existing header.`)\n }\n names.add(name)\n assertHeaderValue(header.value, `extraHeaders[${index}].value`)\n })\n}\n\nconst HEADER_OPTION_KEYS = new Set([\n 'deployment',\n 'hsts',\n 'contentSecurityPolicyExtensions',\n 'permissionsPolicy',\n 'extraHeaders',\n])\n\nexport function buildBrandlabSecurityHeaders(\n options: BrandlabSecurityHeaderOptions,\n): BrandlabHeader[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const unsupported = Object.keys(options).filter((key) => !HEADER_OPTION_KEYS.has(key))\n if (unsupported.length > 0) {\n throw new Error(\n `Header options contain unsupported keys: ${unsupported.join(', ')}. The deployment decides CSP, HSTS and X-Robots-Tag.`,\n )\n }\n assertDeployment(options.deployment)\n const { environment, productionBuild } = options.deployment\n const permissionsPolicy =\n options.permissionsPolicy ?? BRANDLAB_DEFAULT_PERMISSIONS_POLICY\n assertHeaderValue(permissionsPolicy, 'permissionsPolicy')\n validateExtraHeaders(options.extraHeaders ?? [])\n if (options.hsts) {\n assertHeaderValue(options.hsts.readinessEvidence, 'hsts.readinessEvidence')\n if (options.hsts.preload && !options.hsts.includeSubDomains) {\n throw new Error('HSTS preload requires includeSubDomains readiness.')\n }\n }\n const hstsValue = options.hsts\n ? `${BRANDLAB_HSTS_VALUE}; includeSubDomains${options.hsts.preload ? '; preload' : ''}`\n : BRANDLAB_HSTS_VALUE\n const preview = environment === 'preview'\n\n return [\n { key: 'X-Frame-Options', value: 'DENY' },\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n {\n key: 'Permissions-Policy',\n value: permissionsPolicy,\n },\n {\n key: 'X-Robots-Tag',\n value: preview ? BRANDLAB_PREVIEW_X_ROBOTS_TAG : BRANDLAB_DEFAULT_X_ROBOTS_TAG,\n },\n ...(productionBuild\n ? [{ key: 'Strict-Transport-Security', value: hstsValue }]\n : []),\n {\n key: productionBuild\n ? 'Content-Security-Policy'\n : 'Content-Security-Policy-Report-Only',\n value: buildBrandlabContentSecurityPolicy(options),\n },\n ...(options.extraHeaders ?? []).map((header) => ({ ...header })),\n ...(preview ? [{ key: 'Cache-Control', value: BRANDLAB_PREVIEW_CACHE_CONTROL }] : []),\n ]\n}\n\nconst STATIC_ASSET_SOURCE = /^\\/((?:[A-Za-z0-9][A-Za-z0-9._-]*\\/){1,4}):([A-Za-z_][A-Za-z0-9_]*)\\*$/\nconst STATIC_ASSET_RESERVED = new Set(['api', '_next', '_vercel'])\nconst STATIC_ASSET_MAX_SOURCES = 10\nconst STATIC_ASSET_MAX_LENGTH = 100\n\nexport interface BrandlabStaticAssetFolder {\n /** The declared source, for example `/images/:path*`. */\n source: string\n /** The public folder's path segments, for example `['images']`. */\n folder: readonly string[]\n /**\n * The emitted rule's source: file names with an extension below the folder,\n * such as `/images/:path(.+\\.[A-Za-z0-9]+)`. The folder itself and\n * extensionless paths, where a page would answer, never match.\n */\n fileSource: string\n}\n\n/**\n * Validates `staticAssetCache`: at most 10 public folders, each written as Next's\n * source `/<folder>/:path*` (one to four literal segments, then one catch-all\n * parameter, at most 100 characters). `/`, `/api`, `/_next` and `/_vercel` are\n * rejected.\n */\nexport function parseBrandlabStaticAssetCache(sources: unknown): BrandlabStaticAssetFolder[] {\n if (!Array.isArray(sources)) {\n throw new Error('staticAssetCache must list public folders such as /images/:path*.')\n }\n if (sources.length > STATIC_ASSET_MAX_SOURCES) {\n throw new Error(`staticAssetCache lists at most ${STATIC_ASSET_MAX_SOURCES} public folders.`)\n }\n const seen = new Set<string>()\n return sources.map((source: unknown, index) => {\n const field = `staticAssetCache[${index}]`\n if (typeof source !== 'string' || source.length > STATIC_ASSET_MAX_LENGTH) {\n throw new Error(`${field} must be a source of at most ${STATIC_ASSET_MAX_LENGTH} characters.`)\n }\n if (STATIC_ASSET_RESERVED.has(source.split('/')[1]?.toLowerCase() ?? '')) {\n throw new Error(`${field} \"${source}\" is a Next or API path, not a public folder.`)\n }\n const match = STATIC_ASSET_SOURCE.exec(source)\n if (!match) {\n throw new Error(\n `${field} \"${source}\" must name a public folder as /<folder>/:path* (literal folder segments, then one catch-all parameter).`,\n )\n }\n const folder = match[1]!.slice(0, -1).split('/')\n const key = folder.join('/').toLowerCase()\n if (seen.has(key)) throw new Error(`${field} repeats the /${folder.join('/')} folder.`)\n seen.add(key)\n return { source, folder, fileSource: `/${folder.join('/')}/:${match[2]}(.+\\\\.[A-Za-z0-9]+)` }\n })\n}\n\nexport interface BrandlabHeaderRuleOptions extends BrandlabSecurityHeaderOptions {\n /**\n * Public folders of static files that never change in place, for example\n * `['/images/:path*', '/fonts/:path*']`: production builds serve their files with\n * `public, max-age=31536000, immutable`. A file replaced under the same name stays\n * cached in browsers for up to a year, so give a changed file a new name.\n */\n staticAssetCache?: readonly string[]\n /**\n * The canonical host (3.2.1): a production deployment answers every other host (the `*.vercel.app` alias, the\n * deployment URLs) with `X-Robots-Tag: noindex`.\n */\n canonicalHost?: string\n}\n\nexport function buildBrandlabHeaderRules(options: BrandlabHeaderRuleOptions): BrandlabHeaderRule[] {\n if (!options || typeof options !== 'object') {\n throw new TypeError('Header options are required.')\n }\n const { staticAssetCache, canonicalHost, ...security } = options\n const folders = staticAssetCache === undefined ? [] : parseBrandlabStaticAssetCache(staticAssetCache)\n const rules: BrandlabHeaderRule[] = [\n {\n source: '/:path*',\n headers: buildBrandlabSecurityHeaders(security),\n },\n ]\n // A production build outside a Preview; Previews keep private, no-store everywhere.\n const { environment, productionBuild } = options.deployment\n if (productionBuild && environment !== 'preview') {\n for (const folder of folders) {\n rules.push({\n source: folder.fileSource,\n headers: [{ key: 'Cache-Control', value: BRANDLAB_STATIC_ASSET_CACHE_CONTROL }],\n })\n }\n }\n // Only a real production deployment: Previews already send noindex everywhere, and development has no crawlers.\n if (environment === 'production' && canonicalHost) rules.push(buildNonCanonicalHostRule(canonicalHost))\n return rules\n}\n"]}
|
package/dist/lead-registry.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { GtagTransport } from '@brandlab-digital/site-core/analytics';
|
|
|
2
2
|
export interface LeadTrackingConfig {
|
|
3
3
|
enabled: boolean;
|
|
4
4
|
enhancedConversionsEnabled?: boolean;
|
|
5
|
+
/** Disclosed Enhanced Conversions: ad_user_data is granted by default. */
|
|
6
|
+
adUserDataGrantedByDefault?: boolean;
|
|
5
7
|
measurementId?: string | null;
|
|
6
8
|
googleAdsConversionId?: string | null;
|
|
7
9
|
googleAdsConversionLabel?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lead-registry.d.ts","sourceRoot":"","sources":["../src/lead-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAA;AAG1E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAAA;IAC5C,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAAA;CAC5C;AAED,wBAAgB,cAAc,CAC5B,GAAG,GAAE,MAAM,GAAG,SAA8D,GAC3E,aAAa,GAAG,SAAS,CAG3B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAQzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,sBAKjD;AAID,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,QAErE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAExE"}
|
|
1
|
+
{"version":3,"file":"lead-registry.d.ts","sourceRoot":"","sources":["../src/lead-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAA;AAG1E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,0EAA0E;IAC1E,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAAA;IAC5C,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAAA;CAC5C;AAED,wBAAgB,cAAc,CAC5B,GAAG,GAAE,MAAM,GAAG,SAA8D,GAC3E,aAAa,GAAG,SAAS,CAG3B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAQzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,sBAKjD;AAID,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,QAErE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAExE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lead-registry.js","sourceRoot":"","sources":["../src/lead-registry.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;
|
|
1
|
+
{"version":3,"file":"lead-registry.js","sourceRoot":"","sources":["../src/lead-registry.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAoC/E,MAAM,UAAU,cAAc,CAC5B,MAA0B,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;IAE5E,MAAM,SAAS,GAAI,GAAuD,EAAE,IAAI,CAAA;IAChF,OAAO,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;AAChE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,GAAG,CAAA;IAC1C,IAAI,CAAC;QACH,OAAO,0BAA0B,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAA;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAyB;IAChD,MAAM,UAAU,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC9C,OAAO,UAAU,IAAI,+BAA+B,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,IAAI,EAAE;QAC9F,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,SAAS,CAAA;AACf,CAAC;AAED,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAuB,CAAA;AAEnD,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,IAAiB;IACpE,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,OAAO,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnF,CAAC","sourcesContent":["// Internal module (not in package exports). defineBrandlabAnalytics registers\n// each binding's declared lead reporting here; only useBrandlabContactForm and\n// the contact testing surface read it. It holds data, so pages without a\n// contact form do not bundle the lead-sending code.\nimport type { GtagTransport } from '@brandlab-digital/site-core/analytics'\nimport { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes'\n\nexport interface LeadTrackingConfig {\n enabled: boolean\n enhancedConversionsEnabled?: boolean\n /** Disclosed Enhanced Conversions: ad_user_data is granted by default. */\n adUserDataGrantedByDefault?: boolean\n measurementId?: string | null\n googleAdsConversionId?: string | null\n googleAdsConversionLabel?: string | null\n gtag?: GtagTransport\n}\n\nexport interface LeadPageParams {\n page_template?: string\n service_slug?: string\n location_slug?: string\n}\n\nexport interface LeadAdsParams {\n currency?: string\n value?: number\n}\n\nexport interface LeadContext {\n readonly pagePath: string\n readonly pageParams: LeadPageParams\n readonly formData: FormData\n}\n\nexport interface LeadBinding {\n readonly config: LeadTrackingConfig\n pageParams(pagePath: string): LeadPageParams\n adsParams(lead: LeadContext): LeadAdsParams\n}\n\nexport function getBrowserGtag(\n win: Window | undefined = typeof window === 'undefined' ? undefined : window,\n): GtagTransport | undefined {\n const candidate = (win as (Window & { gtag?: GtagTransport }) | undefined)?.gtag\n return typeof candidate === 'function' ? candidate : undefined\n}\n\nexport function safePagePath(value: string) {\n const path = value.trim().split(/[?#]/, 1)[0]\n if (!path || path.length > 500) return '/'\n try {\n return normalizeBrandlabRoutePath(path)\n } catch {\n return '/'\n }\n}\n\nexport function safeSlug(value: string | undefined) {\n const normalized = value?.trim().toLowerCase()\n return normalized && /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(normalized) && normalized.length <= 80\n ? normalized\n : undefined\n}\n\nconst bindings = new WeakMap<object, LeadBinding>()\n\nexport function registerLeadBinding(binding: object, lead: LeadBinding) {\n bindings.set(binding, Object.freeze(lead))\n}\n\nexport function leadBindingFor(binding: unknown): LeadBinding | undefined {\n return binding && typeof binding === 'object' ? bindings.get(binding) : undefined\n}\n"]}
|
package/dist/lead-reporting.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ export declare function trackDeliveredContactWithEnhancedConversions(result: Con
|
|
|
19
19
|
/** Field names with declarative roles; only email and phone roles are read. */
|
|
20
20
|
definition: BrandlabContactFieldRoles;
|
|
21
21
|
formData: FormData;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Live Consent Mode decision for ad_user_data. Absent: deny, or grant for a
|
|
24
|
+
* disclosed contract. An explicit `false` is always deny.
|
|
25
|
+
*/
|
|
26
|
+
adUserDataConsent?: boolean;
|
|
24
27
|
params?: LeadPageParams;
|
|
25
28
|
adsParams?: LeadAdsParams;
|
|
26
29
|
}): ContactFormConversionResult;
|
|
@@ -30,7 +33,8 @@ export interface AcceptedLeadInput {
|
|
|
30
33
|
/** The accepted attempt's fields. */
|
|
31
34
|
formData: FormData;
|
|
32
35
|
fieldRoles: BrandlabContactFieldRoles;
|
|
33
|
-
|
|
36
|
+
/** The site's live ad_user_data decision, when it has one (see trackDeliveredContactWithEnhancedConversions). */
|
|
37
|
+
adUserDataConsent?: boolean;
|
|
34
38
|
}
|
|
35
39
|
/** Sends the binding's declared lead for one accepted enquiry. */
|
|
36
40
|
export declare function reportAcceptedLead(lead: LeadBinding, result: AcceptedContactResult, input: AcceptedLeadInput, override?: Pick<LeadTrackingConfig, 'enabled' | 'gtag'>): ContactFormConversionResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lead-reporting.d.ts","sourceRoot":"","sources":["../src/lead-reporting.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAC9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAA;AAE3B,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAAA;AA2B9E,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,mBAAmB,GAC1B,MAAM,IAAI,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC,CAEtE;AAaD;;;;GAIG;AACH,wBAAgB,4CAA4C,CAC1D,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,UAAU,EAAE,yBAAyB,CAAA;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB
|
|
1
|
+
{"version":3,"file":"lead-reporting.d.ts","sourceRoot":"","sources":["../src/lead-reporting.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAC9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAA;AAE3B,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAAA;AA2B9E,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,mBAAmB,GAC1B,MAAM,IAAI,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC,CAEtE;AAaD;;;;GAIG;AACH,wBAAgB,4CAA4C,CAC1D,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,UAAU,EAAE,yBAAyB,CAAA;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,SAAS,CAAC,EAAE,aAAa,CAAA;CAC1B,GACA,2BAA2B,CAwB7B;AAED,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB,UAAU,EAAE,yBAAyB,CAAA;IACrC,iHAAiH;IACjH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,iBAAiB,EACxB,QAAQ,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,MAAM,CAAC,GACtD,2BAA2B,CAU7B"}
|
package/dist/lead-reporting.js
CHANGED
|
@@ -55,7 +55,10 @@ export function trackDeliveredContactWithEnhancedConversions(result, config, opt
|
|
|
55
55
|
params: sanitizeContactParams(options.params),
|
|
56
56
|
adsParams: sanitizeAdsParams(options.adsParams),
|
|
57
57
|
enhancedConversionsEnabled: config.enhancedConversionsEnabled === true,
|
|
58
|
-
|
|
58
|
+
// Absent: the contract's default. Anything given counts only as an explicit true (fail closed).
|
|
59
|
+
adUserDataConsent: options.adUserDataConsent === undefined
|
|
60
|
+
? config.adUserDataGrantedByDefault === true
|
|
61
|
+
: options.adUserDataConsent === true,
|
|
59
62
|
...((email || phone) ? { enhancedConversion: { email, phone } } : {}),
|
|
60
63
|
});
|
|
61
64
|
}
|
|
@@ -66,7 +69,7 @@ export function reportAcceptedLead(lead, result, input, override) {
|
|
|
66
69
|
pagePath: input.pagePath,
|
|
67
70
|
definition: input.fieldRoles,
|
|
68
71
|
formData: input.formData,
|
|
69
|
-
adUserDataConsent: input.adUserDataConsent,
|
|
72
|
+
...(input.adUserDataConsent !== undefined ? { adUserDataConsent: input.adUserDataConsent } : {}),
|
|
70
73
|
params,
|
|
71
74
|
adsParams: lead.adsParams({ pagePath: input.pagePath, pageParams: params, formData: input.formData }),
|
|
72
75
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lead-reporting.js","sourceRoot":"","sources":["../src/lead-reporting.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,wEAAwE;AACxE,OAAO,EACL,gCAAgC,EAChC,gCAAgC,EAChC,yBAAyB,GAE1B,MAAM,uCAAuC,CAAA;AAG9C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,QAAQ,GAKT,MAAM,oBAAoB,CAAA;AAI3B,SAAS,qBAAqB,CAAC,MAAkC;IAC/D,OAAO;QACL,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;YACjC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE;YACpD,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;YAChC,CAAC,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE;YAClD,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;YACjC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE;YACpD,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAiC;IAC1D,OAAO;QACL,GAAG,CAAC,MAAM,EAAE,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YACxD,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;YAC/B,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,MAAM,EAAE,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC;YACzF,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;YACzB,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,MAA2B;IAE3B,OAAO,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,eAAe,CAAA;AAC/D,CAAC;AAED,SAAS,iBAAiB,CACxB,UAAqC,EACrC,QAAkB,EAClB,IAAuB;IAEvB,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4CAA4C,CAC1D,MAA2B,EAC3B,MAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"lead-reporting.js","sourceRoot":"","sources":["../src/lead-reporting.ts"],"names":[],"mappings":"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","sourcesContent":["// Internal module (not in package exports): the single accepted-enquiry\n// reporting path, used only by useBrandlabContactForm and the contact testing\n// surface, with the lead reporting a binding declared in lead-registry.\nimport {\n normalizeEnhancedConversionEmail,\n normalizeEnhancedConversionPhone,\n sendContactFormConversion,\n type ContactFormConversionResult,\n} from '@brandlab-digital/site-core/analytics'\nimport type { ContactActionResult } from '@brandlab-digital/site-core/contact'\nimport type { BrandlabContactFieldRoles } from './contact/shared.js'\nimport {\n getBrowserGtag,\n safePagePath,\n safeSlug,\n type LeadAdsParams,\n type LeadBinding,\n type LeadPageParams,\n type LeadTrackingConfig,\n} from './lead-registry.js'\n\nexport type AcceptedContactResult = Extract<ContactActionResult, { ok: true }>\n\nfunction sanitizeContactParams(params: LeadPageParams | undefined) {\n return {\n ...(safeSlug(params?.page_template)\n ? { page_template: safeSlug(params?.page_template) }\n : {}),\n ...(safeSlug(params?.service_slug)\n ? { service_slug: safeSlug(params?.service_slug) }\n : {}),\n ...(safeSlug(params?.location_slug)\n ? { location_slug: safeSlug(params?.location_slug) }\n : {}),\n }\n}\n\nfunction sanitizeAdsParams(params: LeadAdsParams | undefined) {\n return {\n ...(params?.currency && /^[A-Z]{3}$/.test(params.currency)\n ? { currency: params.currency }\n : {}),\n ...(typeof params?.value === 'number' && Number.isFinite(params.value) && params.value >= 0\n ? { value: params.value }\n : {}),\n }\n}\n\nexport function isTrackableContactActionResult(\n result: ContactActionResult,\n): result is Extract<ContactActionResult, { ok: true; accepted: true }> {\n return result.ok && result.accepted && result.trackConversion\n}\n\nfunction explicitRoleValue(\n definition: BrandlabContactFieldRoles,\n formData: FormData,\n role: 'email' | 'phone',\n) {\n const field = definition.fields.find((candidate) => candidate.role === role)\n if (!field) return undefined\n const value = formData.get(field.name)\n return typeof value === 'string' ? value : undefined\n}\n\n/**\n * GA4 contact_form plus the Google Ads form conversion, only after provider\n * acceptance. Email and phone may only come from fields with explicit\n * declarative roles and never enter the GA4 parameter object.\n */\nexport function trackDeliveredContactWithEnhancedConversions(\n result: ContactActionResult,\n config: LeadTrackingConfig,\n options: {\n pagePath: string\n /** Field names with declarative roles; only email and phone roles are read. */\n definition: BrandlabContactFieldRoles\n formData: FormData\n /**\n * Live Consent Mode decision for ad_user_data. Absent: deny, or grant for a\n * disclosed contract. An explicit `false` is always deny.\n */\n adUserDataConsent?: boolean\n params?: LeadPageParams\n adsParams?: LeadAdsParams\n },\n): ContactFormConversionResult {\n if (!isTrackableContactActionResult(result)) {\n return { ga4Sent: false, enhancedConversionSent: false, adsConversionSent: false }\n }\n const email = normalizeEnhancedConversionEmail(\n explicitRoleValue(options.definition, options.formData, 'email'),\n )\n const phone = normalizeEnhancedConversionPhone(\n explicitRoleValue(options.definition, options.formData, 'phone'),\n )\n return sendContactFormConversion({\n ...config,\n gtag: config.gtag ?? getBrowserGtag(),\n pagePath: safePagePath(options.pagePath),\n submissionId: result.confirmationId,\n params: sanitizeContactParams(options.params),\n adsParams: sanitizeAdsParams(options.adsParams),\n enhancedConversionsEnabled: config.enhancedConversionsEnabled === true,\n // Absent: the contract's default. Anything given counts only as an explicit true (fail closed).\n adUserDataConsent: options.adUserDataConsent === undefined\n ? config.adUserDataGrantedByDefault === true\n : options.adUserDataConsent === true,\n ...((email || phone) ? { enhancedConversion: { email, phone } } : {}),\n })\n}\n\nexport interface AcceptedLeadInput {\n /** The form's pagePath, exactly as the hook received it. */\n pagePath: string\n /** The accepted attempt's fields. */\n formData: FormData\n fieldRoles: BrandlabContactFieldRoles\n /** The site's live ad_user_data decision, when it has one (see trackDeliveredContactWithEnhancedConversions). */\n adUserDataConsent?: boolean\n}\n\n/** Sends the binding's declared lead for one accepted enquiry. */\nexport function reportAcceptedLead(\n lead: LeadBinding,\n result: AcceptedContactResult,\n input: AcceptedLeadInput,\n override?: Pick<LeadTrackingConfig, 'enabled' | 'gtag'>,\n): ContactFormConversionResult {\n const params = lead.pageParams(input.pagePath)\n return trackDeliveredContactWithEnhancedConversions(result, { ...lead.config, ...override }, {\n pagePath: input.pagePath,\n definition: input.fieldRoles,\n formData: input.formData,\n ...(input.adUserDataConsent !== undefined ? { adUserDataConsent: input.adUserDataConsent } : {}),\n params,\n adsParams: lead.adsParams({ pagePath: input.pagePath, pageParams: params, formData: input.formData }),\n })\n}\n"]}
|
package/dist/next-config.d.ts
CHANGED
|
@@ -54,6 +54,14 @@ export interface BrandlabNextConfig {
|
|
|
54
54
|
}
|
|
55
55
|
export interface BrandlabNextConfigOptions {
|
|
56
56
|
site: BrandlabSite;
|
|
57
|
+
/**
|
|
58
|
+
* Hosts that permanently redirect every path to the canonical origin (the
|
|
59
|
+
* `www` host, a Vercel project host, a retired domain). Declared here rather
|
|
60
|
+
* than in the site identity, which client components import, so the list
|
|
61
|
+
* never reaches a browser bundle. Their rules follow any `site.aliasHosts`
|
|
62
|
+
* rules, in order, as the last redirects.
|
|
63
|
+
*/
|
|
64
|
+
aliasHosts?: readonly string[];
|
|
57
65
|
/** The src/data/redirects.ts table (defineRedirects). */
|
|
58
66
|
redirects: BrandlabRedirectTable;
|
|
59
67
|
images: BrandlabNextImagesOptions;
|
|
@@ -91,7 +99,14 @@ export declare const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING: Readonly<{
|
|
|
91
99
|
export declare function buildBrandlabRedirects(options: {
|
|
92
100
|
site: BrandlabSite;
|
|
93
101
|
redirects: BrandlabRedirectTable;
|
|
102
|
+
/** next.config's alias hosts, after the site identity's own (3.2.1). */
|
|
103
|
+
aliasHosts?: readonly string[];
|
|
94
104
|
}): BrandlabNextRedirect[];
|
|
105
|
+
/**
|
|
106
|
+
* Every alias host: the site identity's (the 3.0–3.2.0 place) then next.config's
|
|
107
|
+
* (3.2.1), validated together so a host is never declared twice.
|
|
108
|
+
*/
|
|
109
|
+
export declare function brandlabAliasHosts(site: BrandlabSite, aliasHosts: readonly string[] | undefined): readonly string[];
|
|
95
110
|
/**
|
|
96
111
|
* The whole next.config.ts after its first line
|
|
97
112
|
* `import '@brandlab-digital/site-next/image-runtime'`:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-config.d.ts","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AACnG,OAAO,
|
|
1
|
+
{"version":3,"file":"next-config.d.ts","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"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"}
|
package/dist/next-config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, posix } from 'node:path';
|
|
3
3
|
import { defineRedirects } from '@brandlab-digital/site-core/redirects';
|
|
4
|
-
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
4
|
+
import { assertBrandlabAliasHosts, resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
5
5
|
import { buildBrandlabHeaderRules, parseBrandlabStaticAssetCache, } from './headers.js';
|
|
6
6
|
/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */
|
|
7
7
|
export const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({
|
|
@@ -14,6 +14,7 @@ export const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({
|
|
|
14
14
|
const MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md'];
|
|
15
15
|
const OPTION_KEYS = new Set([
|
|
16
16
|
'site',
|
|
17
|
+
'aliasHosts',
|
|
17
18
|
'redirects',
|
|
18
19
|
'images',
|
|
19
20
|
'csp',
|
|
@@ -144,6 +145,7 @@ function assertStaticAssetFolders(folders, root) {
|
|
|
144
145
|
* rule: Vercel's edge already enforces HTTPS.
|
|
145
146
|
*/
|
|
146
147
|
export function buildBrandlabRedirects(options) {
|
|
148
|
+
const aliasHosts = brandlabAliasHosts(options.site, options.aliasHosts);
|
|
147
149
|
const table = defineRedirects(options.redirects);
|
|
148
150
|
const entries = table.flatMap((redirect) => {
|
|
149
151
|
const status = redirect.status === 301
|
|
@@ -163,7 +165,7 @@ export function buildBrandlabRedirects(options) {
|
|
|
163
165
|
return [
|
|
164
166
|
...entries,
|
|
165
167
|
{ source: '/:path+/', destination: '/:path+', permanent: true },
|
|
166
|
-
...
|
|
168
|
+
...aliasHosts.map((host) => ({
|
|
167
169
|
source: '/:path*',
|
|
168
170
|
has: [{ type: 'host', value: host }],
|
|
169
171
|
destination: `${options.site.canonicalUrl}/:path*`,
|
|
@@ -171,6 +173,20 @@ export function buildBrandlabRedirects(options) {
|
|
|
171
173
|
})),
|
|
172
174
|
];
|
|
173
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Every alias host: the site identity's (the 3.0–3.2.0 place) then next.config's
|
|
178
|
+
* (3.2.1), validated together so a host is never declared twice.
|
|
179
|
+
*/
|
|
180
|
+
export function brandlabAliasHosts(site, aliasHosts) {
|
|
181
|
+
if (aliasHosts === undefined)
|
|
182
|
+
return site.aliasHosts;
|
|
183
|
+
assertBrandlabAliasHosts(aliasHosts, site.canonicalHost ?? new URL(site.canonicalUrl).hostname, 'next.config aliasHosts');
|
|
184
|
+
const all = [...site.aliasHosts, ...aliasHosts];
|
|
185
|
+
const duplicate = all.find((host, index) => all.indexOf(host) !== index);
|
|
186
|
+
if (duplicate)
|
|
187
|
+
throw new Error(`Alias host "${duplicate}" is declared in both the site identity and next.config aliasHosts.`);
|
|
188
|
+
return all;
|
|
189
|
+
}
|
|
174
190
|
export function defineBrandlabNextConfig(options) {
|
|
175
191
|
if (!options || typeof options !== 'object') {
|
|
176
192
|
throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.');
|
|
@@ -188,13 +204,18 @@ export function defineBrandlabNextConfig(options) {
|
|
|
188
204
|
}
|
|
189
205
|
const headerRules = buildBrandlabHeaderRules({
|
|
190
206
|
deployment,
|
|
207
|
+
canonicalHost: options.site.canonicalHost ?? new URL(options.site.canonicalUrl).hostname,
|
|
191
208
|
...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),
|
|
192
209
|
...(options.hsts ? { hsts: options.hsts } : {}),
|
|
193
210
|
...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),
|
|
194
211
|
...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),
|
|
195
212
|
...(options.staticAssetCache ? { staticAssetCache: options.staticAssetCache } : {}),
|
|
196
213
|
});
|
|
197
|
-
const redirects = buildBrandlabRedirects({
|
|
214
|
+
const redirects = buildBrandlabRedirects({
|
|
215
|
+
site: options.site,
|
|
216
|
+
redirects: options.redirects,
|
|
217
|
+
...(options.aliasHosts !== undefined ? { aliasHosts: options.aliasHosts } : {}),
|
|
218
|
+
});
|
|
198
219
|
const config = {
|
|
199
220
|
poweredByHeader: false,
|
|
200
221
|
skipTrailingSlashRedirect: true,
|
|
@@ -202,7 +223,11 @@ export function defineBrandlabNextConfig(options) {
|
|
|
202
223
|
outputFileTracingIncludes: Object.fromEntries(Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]])),
|
|
203
224
|
images: buildImagesConfig(options.images),
|
|
204
225
|
...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),
|
|
205
|
-
headers: async () => headerRules.map((rule) => ({
|
|
226
|
+
headers: async () => headerRules.map((rule) => ({
|
|
227
|
+
source: rule.source,
|
|
228
|
+
...(rule.missing ? { missing: rule.missing.map((condition) => ({ ...condition })) } : {}),
|
|
229
|
+
headers: rule.headers.map((header) => ({ ...header })),
|
|
230
|
+
})),
|
|
206
231
|
redirects: async () => redirects.map((redirect) => ({
|
|
207
232
|
...redirect,
|
|
208
233
|
...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),
|
package/dist/next-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-config.js","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, statSync } from 'node:fs'\nimport { join, posix } from 'node:path'\nimport { defineRedirects, type BrandlabRedirectTable } from '@brandlab-digital/site-core/redirects'\nimport {\n resolveBrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\nimport {\n buildBrandlabHeaderRules,\n parseBrandlabStaticAssetCache,\n type BrandlabContentSecurityPolicyExtensions,\n type BrandlabHeader,\n type BrandlabHeaderRule,\n type BrandlabHstsExpansion,\n type BrandlabStaticAssetFolder,\n} from './headers.js'\n\n/** Next redirect records: 308 stays `permanent: true`, 301 stays `statusCode: 301`. */\nexport type BrandlabNextRedirect = {\n source: string\n destination: string\n has?: { type: 'host'; value: string }[]\n} & ({ permanent: true; statusCode?: never } | { statusCode: 301; permanent?: never })\n\nexport interface BrandlabNextRemotePattern {\n protocol?: 'http' | 'https'\n hostname: string\n port?: string\n pathname?: string\n search?: string\n}\n\n/** The site's own image settings; the output format is always WebP. */\nexport interface BrandlabNextImagesOptions {\n deviceSizes?: readonly number[]\n imageSizes?: readonly number[]\n qualities?: readonly number[]\n minimumCacheTTL?: number\n remotePatterns?: readonly BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextImagesConfig {\n formats: 'image/webp'[]\n deviceSizes?: number[]\n imageSizes?: number[]\n qualities?: number[]\n minimumCacheTTL?: number\n remotePatterns?: BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextConfig {\n poweredByHeader: false\n /** Trailing slashes are handled by single-hop redirect rules instead. */\n skipTrailingSlashRedirect: true\n env: { NEXT_PUBLIC_VERCEL_ENV: BrandlabDeploymentEnvironment }\n outputFileTracingIncludes: Record<string, string[]>\n images: BrandlabNextImagesConfig\n pageExtensions?: string[]\n headers: () => Promise<BrandlabHeaderRule[]>\n redirects: () => Promise<BrandlabNextRedirect[]>\n}\n\nexport interface BrandlabNextConfigOptions {\n site: BrandlabSite\n /** The src/data/redirects.ts table (defineRedirects). */\n redirects: BrandlabRedirectTable\n images: BrandlabNextImagesOptions\n /** Reviewed CSP origins for declared integrations such as Meta Pixel or map embeds. */\n csp?: BrandlabContentSecurityPolicyExtensions\n hsts?: BrandlabHstsExpansion\n /** Reviewed exception to the default Permissions-Policy that is already live. */\n permissionsPolicy?: string\n /** Reviewed response headers that are already live; protected headers cannot be replaced. */\n extraHeaders?: readonly BrandlabHeader[]\n /**\n * Public folders of static files that never change in place, written as Next\n * sources such as `['/images/:path*', '/fonts/:path*']`. Production builds serve\n * their files (names with an extension) with `public, max-age=31536000, immutable`;\n * Previews keep `private, no-store`. Each folder must exist under `public/`, and\n * no route under `src/app` or `app` may answer below it.\n */\n staticAssetCache?: readonly string[]\n /** Defaults to process.env; tests supply a fixed environment. */\n environment?: BrandlabEnvironment\n /** The project root for the staticAssetCache checks; defaults to process.cwd(). */\n projectRoot?: string\n}\n\n/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */\nexport const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({\n '/api/contact': Object.freeze([\n 'node_modules/sharp/**/*',\n 'node_modules/@img/sharp-linux-x64/**/*',\n 'node_modules/@img/sharp-libvips-linux-x64/**/*',\n ]),\n})\n\nconst MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md']\nconst OPTION_KEYS = new Set([\n 'site',\n 'redirects',\n 'images',\n 'csp',\n 'hsts',\n 'permissionsPolicy',\n 'extraHeaders',\n 'staticAssetCache',\n 'mdx',\n 'environment',\n 'projectRoot',\n])\nconst IMAGE_KEYS = new Set(['deviceSizes', 'imageSizes', 'qualities', 'minimumCacheTTL', 'remotePatterns'])\n\nfunction assertKnownKeys(value: object, allowed: ReadonlySet<string>, field: string, hint = '') {\n const unsupported = Object.keys(value).filter((key) => !allowed.has(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.${hint}`)\n }\n}\n\nfunction positiveIntegers(values: readonly number[] | undefined, field: string) {\n if (values === undefined) return undefined\n if (\n !Array.isArray(values) ||\n values.length === 0 ||\n values.some((value) => !Number.isSafeInteger(value) || value < 1)\n ) {\n throw new Error(`images.${field} must list positive integers.`)\n }\n return [...values]\n}\n\nfunction buildImagesConfig(images: BrandlabNextImagesOptions): BrandlabNextImagesConfig {\n if (!images || typeof images !== 'object' || Array.isArray(images)) {\n throw new Error('images must declare the site image settings ({} for Next defaults).')\n }\n assertKnownKeys(images, IMAGE_KEYS, 'images', ' The output format is always image/webp.')\n const deviceSizes = positiveIntegers(images.deviceSizes, 'deviceSizes')\n const imageSizes = positiveIntegers(images.imageSizes, 'imageSizes')\n const qualities = positiveIntegers(images.qualities, 'qualities')\n if (qualities?.some((quality) => quality > 100)) {\n throw new Error('images.qualities must be between 1 and 100.')\n }\n if (\n images.minimumCacheTTL !== undefined &&\n (!Number.isSafeInteger(images.minimumCacheTTL) || images.minimumCacheTTL < 0)\n ) {\n throw new Error('images.minimumCacheTTL must be a non-negative integer of seconds.')\n }\n if (images.remotePatterns !== undefined && !Array.isArray(images.remotePatterns)) {\n throw new Error('images.remotePatterns must be an array.')\n }\n return {\n formats: ['image/webp'],\n ...(deviceSizes ? { deviceSizes } : {}),\n ...(imageSizes ? { imageSizes } : {}),\n ...(qualities ? { qualities } : {}),\n ...(images.minimumCacheTTL !== undefined ? { minimumCacheTTL: images.minimumCacheTTL } : {}),\n ...(images.remotePatterns\n ? { remotePatterns: images.remotePatterns.map((pattern) => ({ ...pattern })) }\n : {}),\n }\n}\n\nconst PAGE_OR_ROUTE_FILE = /^(?:page|route)\\.[A-Za-z0-9]+$/\nconst METADATA_ROUTE_FILE = /^(?:opengraph-image|twitter-image|icon|apple-icon|sitemap|robots|manifest)(?:[\\w.-]*)\\.[A-Za-z0-9]+$/\n\nfunction isDirectory(path: string) {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n\n/**\n * The first route file under an app directory that can answer a path strictly\n * below `/<folder>/`. `depth` counts the folder segments matched so far: route\n * groups, intercepts and parallel slots add none, a dynamic segment matches any\n * one, and a catch-all reaches every path below the folder.\n */\nfunction routeBelowFolder(\n directory: string,\n folder: readonly string[],\n depth: number,\n relative: string,\n): string | null {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const name = entry.name\n if (name.startsWith('_') || name.startsWith('.') || name === 'node_modules') continue\n const path = join(directory, name)\n const nextRelative = posix.join(relative, name)\n if (entry.isFile()) {\n // Page and route files serve their own directory; metadata files serve a path below it.\n const below = depth > folder.length && PAGE_OR_ROUTE_FILE.test(name)\n const metadata = depth >= folder.length && METADATA_ROUTE_FILE.test(name)\n if (below || metadata) return nextRelative\n continue\n }\n if (!entry.isDirectory()) continue\n let next: number | null = null\n if (name.startsWith('(') || name.startsWith('@')) next = depth\n else if (/^\\[\\[?\\.\\.\\./.test(name)) next = folder.length + 1\n else if (depth >= folder.length || name.startsWith('[') || name === folder[depth]) next = depth + 1\n if (next === null) continue\n const found = routeBelowFolder(path, folder, next, nextRelative)\n if (found) return found\n }\n return null\n}\n\n/** Each declared folder exists under public/ and no app route answers below it. */\nfunction assertStaticAssetFolders(folders: readonly BrandlabStaticAssetFolder[], root: string) {\n for (const { source, folder } of folders) {\n const publicFolder = posix.join('public', ...folder)\n if (!isDirectory(join(root, publicFolder))) {\n throw new Error(`staticAssetCache \"${source}\": ${publicFolder} is not a folder of static files in ${root}.`)\n }\n for (const appDirectory of ['src/app', 'app']) {\n if (!isDirectory(join(root, appDirectory))) continue\n const route = routeBelowFolder(join(root, appDirectory), folder, 0, appDirectory)\n if (route) {\n throw new Error(\n `staticAssetCache \"${source}\": ${route} answers below /${folder.join('/')}, so immutable caching could reach a page or route.`,\n )\n }\n }\n }\n}\n\n/**\n * Converts the redirect table to Next redirects: each entry keeps its 301 or\n * 308 status (Next forwards the query string), gains a single-hop\n * trailing-slash variant, then one trailing-slash rule and one permanent\n * canonical-host rule per alias host follow. There is no x-forwarded-proto\n * rule: Vercel's edge already enforces HTTPS.\n */\nexport function buildBrandlabRedirects(options: {\n site: BrandlabSite\n redirects: BrandlabRedirectTable\n}): BrandlabNextRedirect[] {\n const table = defineRedirects(options.redirects)\n const entries = table.flatMap((redirect): BrandlabNextRedirect[] => {\n const status = redirect.status === 301\n ? { statusCode: 301 as const }\n : { permanent: true as const }\n const has = redirect.host ? { has: [{ type: 'host' as const, value: redirect.host }] } : {}\n const record = (source: string): BrandlabNextRedirect => ({\n source,\n destination: redirect.destination,\n ...status,\n ...has,\n })\n return redirect.source === '/'\n ? [record(redirect.source)]\n : [record(redirect.source), record(`${redirect.source}/`)]\n })\n return [\n ...entries,\n { source: '/:path+/', destination: '/:path+', permanent: true },\n ...options.site.aliasHosts.map((host): BrandlabNextRedirect => ({\n source: '/:path*',\n has: [{ type: 'host', value: host }],\n destination: `${options.site.canonicalUrl}/:path*`,\n permanent: true,\n })),\n ]\n}\n\n/**\n * The whole next.config.ts after its first line\n * `import '@brandlab-digital/site-next/image-runtime'`:\n * `export default defineBrandlabNextConfig({ site, redirects, images })`.\n *\n * Sets poweredByHeader: false, WebP image output, Sharp output tracing for\n * /api/contact, NEXT_PUBLIC_VERCEL_ENV for browser gates, the deployment's\n * header rules (with immutable caching for the declared static public folders on\n * production builds) and the redirect table. Validation runs when the config\n * loads, so an invalid table, header exception or static folder fails the build.\n */\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: undefined },\n): BrandlabNextConfig\nexport function defineBrandlabNextConfig<Wrapped>(\n options: BrandlabNextConfigOptions & {\n /** The @next/mdx wrapper: createMDX({ options: { remarkPlugins: ['remark-gfm'] } }). */\n mdx: (config: BrandlabNextConfig) => Wrapped\n },\n): Wrapped\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: (config: BrandlabNextConfig) => unknown },\n): unknown {\n if (!options || typeof options !== 'object') {\n throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.')\n }\n assertKnownKeys(options, OPTION_KEYS, 'next.config options')\n if (!options.site?.canonicalUrl || !Array.isArray(options.site.aliasHosts)) {\n throw new Error('site must come from defineBrandlabSite.')\n }\n if (options.mdx !== undefined && typeof options.mdx !== 'function') {\n throw new TypeError('mdx must be the @next/mdx config wrapper.')\n }\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (options.staticAssetCache !== undefined) {\n assertStaticAssetFolders(\n parseBrandlabStaticAssetCache(options.staticAssetCache),\n options.projectRoot ?? process.cwd(),\n )\n }\n const headerRules = buildBrandlabHeaderRules({\n deployment,\n ...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),\n ...(options.hsts ? { hsts: options.hsts } : {}),\n ...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),\n ...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),\n ...(options.staticAssetCache ? { staticAssetCache: options.staticAssetCache } : {}),\n })\n const redirects = buildBrandlabRedirects({ site: options.site, redirects: options.redirects })\n\n const config: BrandlabNextConfig = {\n poweredByHeader: false,\n skipTrailingSlashRedirect: true,\n env: { NEXT_PUBLIC_VERCEL_ENV: deployment.environment },\n outputFileTracingIncludes: Object.fromEntries(\n Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]]),\n ),\n images: buildImagesConfig(options.images),\n ...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),\n headers: async () =>\n headerRules.map((rule) => ({ source: rule.source, headers: rule.headers.map((header) => ({ ...header })) })),\n redirects: async () =>\n redirects.map((redirect) => ({\n ...redirect,\n ...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),\n })),\n }\n return options.mdx ? options.mdx(config) : config\n}\n"]}
|
|
1
|
+
{"version":3,"file":"next-config.js","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, statSync } from 'node:fs'\nimport { join, posix } from 'node:path'\nimport { defineRedirects, type BrandlabRedirectTable } from '@brandlab-digital/site-core/redirects'\nimport {\n assertBrandlabAliasHosts,\n resolveBrandlabDeployment,\n type BrandlabDeploymentEnvironment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\nimport {\n buildBrandlabHeaderRules,\n parseBrandlabStaticAssetCache,\n type BrandlabContentSecurityPolicyExtensions,\n type BrandlabHeader,\n type BrandlabHeaderRule,\n type BrandlabHstsExpansion,\n type BrandlabStaticAssetFolder,\n} from './headers.js'\n\n/** Next redirect records: 308 stays `permanent: true`, 301 stays `statusCode: 301`. */\nexport type BrandlabNextRedirect = {\n source: string\n destination: string\n has?: { type: 'host'; value: string }[]\n} & ({ permanent: true; statusCode?: never } | { statusCode: 301; permanent?: never })\n\nexport interface BrandlabNextRemotePattern {\n protocol?: 'http' | 'https'\n hostname: string\n port?: string\n pathname?: string\n search?: string\n}\n\n/** The site's own image settings; the output format is always WebP. */\nexport interface BrandlabNextImagesOptions {\n deviceSizes?: readonly number[]\n imageSizes?: readonly number[]\n qualities?: readonly number[]\n minimumCacheTTL?: number\n remotePatterns?: readonly BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextImagesConfig {\n formats: 'image/webp'[]\n deviceSizes?: number[]\n imageSizes?: number[]\n qualities?: number[]\n minimumCacheTTL?: number\n remotePatterns?: BrandlabNextRemotePattern[]\n}\n\nexport interface BrandlabNextConfig {\n poweredByHeader: false\n /** Trailing slashes are handled by single-hop redirect rules instead. */\n skipTrailingSlashRedirect: true\n env: { NEXT_PUBLIC_VERCEL_ENV: BrandlabDeploymentEnvironment }\n outputFileTracingIncludes: Record<string, string[]>\n images: BrandlabNextImagesConfig\n pageExtensions?: string[]\n headers: () => Promise<BrandlabHeaderRule[]>\n redirects: () => Promise<BrandlabNextRedirect[]>\n}\n\nexport interface BrandlabNextConfigOptions {\n site: BrandlabSite\n /**\n * Hosts that permanently redirect every path to the canonical origin (the\n * `www` host, a Vercel project host, a retired domain). Declared here rather\n * than in the site identity, which client components import, so the list\n * never reaches a browser bundle. Their rules follow any `site.aliasHosts`\n * rules, in order, as the last redirects.\n */\n aliasHosts?: readonly string[]\n /** The src/data/redirects.ts table (defineRedirects). */\n redirects: BrandlabRedirectTable\n images: BrandlabNextImagesOptions\n /** Reviewed CSP origins for declared integrations such as Meta Pixel or map embeds. */\n csp?: BrandlabContentSecurityPolicyExtensions\n hsts?: BrandlabHstsExpansion\n /** Reviewed exception to the default Permissions-Policy that is already live. */\n permissionsPolicy?: string\n /** Reviewed response headers that are already live; protected headers cannot be replaced. */\n extraHeaders?: readonly BrandlabHeader[]\n /**\n * Public folders of static files that never change in place, written as Next\n * sources such as `['/images/:path*', '/fonts/:path*']`. Production builds serve\n * their files (names with an extension) with `public, max-age=31536000, immutable`;\n * Previews keep `private, no-store`. Each folder must exist under `public/`, and\n * no route under `src/app` or `app` may answer below it.\n */\n staticAssetCache?: readonly string[]\n /** Defaults to process.env; tests supply a fixed environment. */\n environment?: BrandlabEnvironment\n /** The project root for the staticAssetCache checks; defaults to process.cwd(). */\n projectRoot?: string\n}\n\n/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */\nexport const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({\n '/api/contact': Object.freeze([\n 'node_modules/sharp/**/*',\n 'node_modules/@img/sharp-linux-x64/**/*',\n 'node_modules/@img/sharp-libvips-linux-x64/**/*',\n ]),\n})\n\nconst MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md']\nconst OPTION_KEYS = new Set([\n 'site',\n 'aliasHosts',\n 'redirects',\n 'images',\n 'csp',\n 'hsts',\n 'permissionsPolicy',\n 'extraHeaders',\n 'staticAssetCache',\n 'mdx',\n 'environment',\n 'projectRoot',\n])\nconst IMAGE_KEYS = new Set(['deviceSizes', 'imageSizes', 'qualities', 'minimumCacheTTL', 'remotePatterns'])\n\nfunction assertKnownKeys(value: object, allowed: ReadonlySet<string>, field: string, hint = '') {\n const unsupported = Object.keys(value).filter((key) => !allowed.has(key))\n if (unsupported.length > 0) {\n throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.${hint}`)\n }\n}\n\nfunction positiveIntegers(values: readonly number[] | undefined, field: string) {\n if (values === undefined) return undefined\n if (\n !Array.isArray(values) ||\n values.length === 0 ||\n values.some((value) => !Number.isSafeInteger(value) || value < 1)\n ) {\n throw new Error(`images.${field} must list positive integers.`)\n }\n return [...values]\n}\n\nfunction buildImagesConfig(images: BrandlabNextImagesOptions): BrandlabNextImagesConfig {\n if (!images || typeof images !== 'object' || Array.isArray(images)) {\n throw new Error('images must declare the site image settings ({} for Next defaults).')\n }\n assertKnownKeys(images, IMAGE_KEYS, 'images', ' The output format is always image/webp.')\n const deviceSizes = positiveIntegers(images.deviceSizes, 'deviceSizes')\n const imageSizes = positiveIntegers(images.imageSizes, 'imageSizes')\n const qualities = positiveIntegers(images.qualities, 'qualities')\n if (qualities?.some((quality) => quality > 100)) {\n throw new Error('images.qualities must be between 1 and 100.')\n }\n if (\n images.minimumCacheTTL !== undefined &&\n (!Number.isSafeInteger(images.minimumCacheTTL) || images.minimumCacheTTL < 0)\n ) {\n throw new Error('images.minimumCacheTTL must be a non-negative integer of seconds.')\n }\n if (images.remotePatterns !== undefined && !Array.isArray(images.remotePatterns)) {\n throw new Error('images.remotePatterns must be an array.')\n }\n return {\n formats: ['image/webp'],\n ...(deviceSizes ? { deviceSizes } : {}),\n ...(imageSizes ? { imageSizes } : {}),\n ...(qualities ? { qualities } : {}),\n ...(images.minimumCacheTTL !== undefined ? { minimumCacheTTL: images.minimumCacheTTL } : {}),\n ...(images.remotePatterns\n ? { remotePatterns: images.remotePatterns.map((pattern) => ({ ...pattern })) }\n : {}),\n }\n}\n\nconst PAGE_OR_ROUTE_FILE = /^(?:page|route)\\.[A-Za-z0-9]+$/\nconst METADATA_ROUTE_FILE = /^(?:opengraph-image|twitter-image|icon|apple-icon|sitemap|robots|manifest)(?:[\\w.-]*)\\.[A-Za-z0-9]+$/\n\nfunction isDirectory(path: string) {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n\n/**\n * The first route file under an app directory that can answer a path strictly\n * below `/<folder>/`. `depth` counts the folder segments matched so far: route\n * groups, intercepts and parallel slots add none, a dynamic segment matches any\n * one, and a catch-all reaches every path below the folder.\n */\nfunction routeBelowFolder(\n directory: string,\n folder: readonly string[],\n depth: number,\n relative: string,\n): string | null {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const name = entry.name\n if (name.startsWith('_') || name.startsWith('.') || name === 'node_modules') continue\n const path = join(directory, name)\n const nextRelative = posix.join(relative, name)\n if (entry.isFile()) {\n // Page and route files serve their own directory; metadata files serve a path below it.\n const below = depth > folder.length && PAGE_OR_ROUTE_FILE.test(name)\n const metadata = depth >= folder.length && METADATA_ROUTE_FILE.test(name)\n if (below || metadata) return nextRelative\n continue\n }\n if (!entry.isDirectory()) continue\n let next: number | null = null\n if (name.startsWith('(') || name.startsWith('@')) next = depth\n else if (/^\\[\\[?\\.\\.\\./.test(name)) next = folder.length + 1\n else if (depth >= folder.length || name.startsWith('[') || name === folder[depth]) next = depth + 1\n if (next === null) continue\n const found = routeBelowFolder(path, folder, next, nextRelative)\n if (found) return found\n }\n return null\n}\n\n/** Each declared folder exists under public/ and no app route answers below it. */\nfunction assertStaticAssetFolders(folders: readonly BrandlabStaticAssetFolder[], root: string) {\n for (const { source, folder } of folders) {\n const publicFolder = posix.join('public', ...folder)\n if (!isDirectory(join(root, publicFolder))) {\n throw new Error(`staticAssetCache \"${source}\": ${publicFolder} is not a folder of static files in ${root}.`)\n }\n for (const appDirectory of ['src/app', 'app']) {\n if (!isDirectory(join(root, appDirectory))) continue\n const route = routeBelowFolder(join(root, appDirectory), folder, 0, appDirectory)\n if (route) {\n throw new Error(\n `staticAssetCache \"${source}\": ${route} answers below /${folder.join('/')}, so immutable caching could reach a page or route.`,\n )\n }\n }\n }\n}\n\n/**\n * Converts the redirect table to Next redirects: each entry keeps its 301 or\n * 308 status (Next forwards the query string), gains a single-hop\n * trailing-slash variant, then one trailing-slash rule and one permanent\n * canonical-host rule per alias host follow. There is no x-forwarded-proto\n * rule: Vercel's edge already enforces HTTPS.\n */\nexport function buildBrandlabRedirects(options: {\n site: BrandlabSite\n redirects: BrandlabRedirectTable\n /** next.config's alias hosts, after the site identity's own (3.2.1). */\n aliasHosts?: readonly string[]\n}): BrandlabNextRedirect[] {\n const aliasHosts = brandlabAliasHosts(options.site, options.aliasHosts)\n const table = defineRedirects(options.redirects)\n const entries = table.flatMap((redirect): BrandlabNextRedirect[] => {\n const status = redirect.status === 301\n ? { statusCode: 301 as const }\n : { permanent: true as const }\n const has = redirect.host ? { has: [{ type: 'host' as const, value: redirect.host }] } : {}\n const record = (source: string): BrandlabNextRedirect => ({\n source,\n destination: redirect.destination,\n ...status,\n ...has,\n })\n return redirect.source === '/'\n ? [record(redirect.source)]\n : [record(redirect.source), record(`${redirect.source}/`)]\n })\n return [\n ...entries,\n { source: '/:path+/', destination: '/:path+', permanent: true },\n ...aliasHosts.map((host): BrandlabNextRedirect => ({\n source: '/:path*',\n has: [{ type: 'host', value: host }],\n destination: `${options.site.canonicalUrl}/:path*`,\n permanent: true,\n })),\n ]\n}\n\n/**\n * Every alias host: the site identity's (the 3.0–3.2.0 place) then next.config's\n * (3.2.1), validated together so a host is never declared twice.\n */\nexport function brandlabAliasHosts(site: BrandlabSite, aliasHosts: readonly string[] | undefined): readonly string[] {\n if (aliasHosts === undefined) return site.aliasHosts\n assertBrandlabAliasHosts(aliasHosts, site.canonicalHost ?? new URL(site.canonicalUrl).hostname, 'next.config aliasHosts')\n const all = [...site.aliasHosts, ...aliasHosts]\n const duplicate = all.find((host, index) => all.indexOf(host) !== index)\n if (duplicate) throw new Error(`Alias host \"${duplicate}\" is declared in both the site identity and next.config aliasHosts.`)\n return all\n}\n\n/**\n * The whole next.config.ts after its first line\n * `import '@brandlab-digital/site-next/image-runtime'`:\n * `export default defineBrandlabNextConfig({ site, redirects, images })`.\n *\n * Sets poweredByHeader: false, WebP image output, Sharp output tracing for\n * /api/contact, NEXT_PUBLIC_VERCEL_ENV for browser gates, the deployment's\n * header rules (with immutable caching for the declared static public folders on\n * production builds) and the redirect table. Validation runs when the config\n * loads, so an invalid table, header exception or static folder fails the build.\n */\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: undefined },\n): BrandlabNextConfig\nexport function defineBrandlabNextConfig<Wrapped>(\n options: BrandlabNextConfigOptions & {\n /** The @next/mdx wrapper: createMDX({ options: { remarkPlugins: ['remark-gfm'] } }). */\n mdx: (config: BrandlabNextConfig) => Wrapped\n },\n): Wrapped\nexport function defineBrandlabNextConfig(\n options: BrandlabNextConfigOptions & { mdx?: (config: BrandlabNextConfig) => unknown },\n): unknown {\n if (!options || typeof options !== 'object') {\n throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.')\n }\n assertKnownKeys(options, OPTION_KEYS, 'next.config options')\n if (!options.site?.canonicalUrl || !Array.isArray(options.site.aliasHosts)) {\n throw new Error('site must come from defineBrandlabSite.')\n }\n if (options.mdx !== undefined && typeof options.mdx !== 'function') {\n throw new TypeError('mdx must be the @next/mdx config wrapper.')\n }\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (options.staticAssetCache !== undefined) {\n assertStaticAssetFolders(\n parseBrandlabStaticAssetCache(options.staticAssetCache),\n options.projectRoot ?? process.cwd(),\n )\n }\n const headerRules = buildBrandlabHeaderRules({\n deployment,\n canonicalHost: options.site.canonicalHost ?? new URL(options.site.canonicalUrl).hostname,\n ...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),\n ...(options.hsts ? { hsts: options.hsts } : {}),\n ...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),\n ...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),\n ...(options.staticAssetCache ? { staticAssetCache: options.staticAssetCache } : {}),\n })\n const redirects = buildBrandlabRedirects({\n site: options.site,\n redirects: options.redirects,\n ...(options.aliasHosts !== undefined ? { aliasHosts: options.aliasHosts } : {}),\n })\n\n const config: BrandlabNextConfig = {\n poweredByHeader: false,\n skipTrailingSlashRedirect: true,\n env: { NEXT_PUBLIC_VERCEL_ENV: deployment.environment },\n outputFileTracingIncludes: Object.fromEntries(\n Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]]),\n ),\n images: buildImagesConfig(options.images),\n ...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),\n headers: async () =>\n headerRules.map((rule) => ({\n source: rule.source,\n ...(rule.missing ? { missing: rule.missing.map((condition) => ({ ...condition })) } : {}),\n headers: rule.headers.map((header) => ({ ...header })),\n })),\n redirects: async () =>\n redirects.map((redirect) => ({\n ...redirect,\n ...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),\n })),\n }\n return options.mdx ? options.mdx(config) : config\n}\n"]}
|
package/dist/robots.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface BrandlabRobotsMetadata {
|
|
|
3
3
|
rules: Array<{
|
|
4
4
|
userAgent: string;
|
|
5
5
|
allow?: string;
|
|
6
|
-
disallow?: string;
|
|
6
|
+
disallow?: string | string[];
|
|
7
7
|
}>;
|
|
8
8
|
sitemap?: string;
|
|
9
9
|
}
|
|
@@ -15,7 +15,9 @@ export interface BrandlabRobotsMetadata {
|
|
|
15
15
|
export declare function isBrandlabIndexableDeployment(deployment: BrandlabDeployment): boolean;
|
|
16
16
|
/**
|
|
17
17
|
* `src/app/robots.ts`: `export default createBrandlabRobots(site)`.
|
|
18
|
-
* The deployment is resolved when Next renders robots.txt.
|
|
18
|
+
* The deployment is resolved when Next renders robots.txt. Production allows
|
|
19
|
+
* everything but the shared API routes and names the canonical sitemap; every
|
|
20
|
+
* other deployment keeps `Disallow: /`.
|
|
19
21
|
*/
|
|
20
22
|
export declare function createBrandlabRobots(site: BrandlabSite, options?: {
|
|
21
23
|
environment?: BrandlabEnvironment;
|
package/dist/robots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAWD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,kBAAkB,WAG3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAGzB,sBAAsB,CAajD"}
|
package/dist/robots.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
2
|
+
/**
|
|
3
|
+
* The shared API routes crawlers have no use for, kept out of a production
|
|
4
|
+
* robots.txt (from 3.3.0): the contact endpoint, and the live Google reviews route
|
|
5
|
+
* with its `/summary` and `/featured` variants, where every request is a paid Places
|
|
6
|
+
* lookup. A robots.txt path is a prefix, so each covers its subpaths. Only these:
|
|
7
|
+
* not a blanket `/api/`, because a site may serve something public there.
|
|
8
|
+
*/
|
|
9
|
+
const SHARED_API_DISALLOW = ['/api/contact', '/api/google-reviews'];
|
|
2
10
|
/**
|
|
3
11
|
* Crawlers may index any production build except a Vercel Preview: the real
|
|
4
12
|
* production deployment and a local `next start`. `next dev` and every
|
|
@@ -10,7 +18,9 @@ export function isBrandlabIndexableDeployment(deployment) {
|
|
|
10
18
|
}
|
|
11
19
|
/**
|
|
12
20
|
* `src/app/robots.ts`: `export default createBrandlabRobots(site)`.
|
|
13
|
-
* The deployment is resolved when Next renders robots.txt.
|
|
21
|
+
* The deployment is resolved when Next renders robots.txt. Production allows
|
|
22
|
+
* everything but the shared API routes and names the canonical sitemap; every
|
|
23
|
+
* other deployment keeps `Disallow: /`.
|
|
14
24
|
*/
|
|
15
25
|
export function createBrandlabRobots(site, options = {}) {
|
|
16
26
|
const sitemap = `${site.canonicalUrl}/sitemap.xml`;
|
|
@@ -23,7 +33,7 @@ export function createBrandlabRobots(site, options = {}) {
|
|
|
23
33
|
// by Google and Bing; canonical-host consolidation is owned by permanent
|
|
24
34
|
// redirects and canonical URLs.
|
|
25
35
|
return {
|
|
26
|
-
rules: [{ userAgent: '*', allow: '/' }],
|
|
36
|
+
rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }],
|
|
27
37
|
sitemap,
|
|
28
38
|
};
|
|
29
39
|
};
|
package/dist/robots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAA8B;IAC1E,OAAO,UAAU,CAAC,WAAW,KAAK,YAAY;QAC5C,CAAC,UAAU,CAAC,WAAW,KAAK,aAAa,IAAI,UAAU,CAAC,eAAe,CAAC,CAAA;AAC5E,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAA;AAEnE;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAA8B;IAC1E,OAAO,UAAU,CAAC,WAAW,KAAK,YAAY;QAC5C,CAAC,UAAU,CAAC,WAAW,KAAK,aAAa,IAAI,UAAU,CAAC,eAAe,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAkB,EAClB,UAAiD,EAAE;IAEnD,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,YAAY,cAAc,CAAA;IAClD,OAAO,SAAS,MAAM;QACpB,MAAM,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA;QAChF,IAAI,CAAC,6BAA6B,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QACvD,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,gCAAgC;QAChC,OAAO;YACL,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,mBAAmB,CAAC,EAAE,CAAC;YAC3E,OAAO;SACR,CAAA;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import {\n resolveBrandlabDeployment,\n type BrandlabDeployment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabRobotsMetadata {\n rules: Array<{ userAgent: string; allow?: string; disallow?: string | string[] }>\n sitemap?: string\n}\n\n/**\n * The shared API routes crawlers have no use for, kept out of a production\n * robots.txt (from 3.3.0): the contact endpoint, and the live Google reviews route\n * with its `/summary` and `/featured` variants, where every request is a paid Places\n * lookup. A robots.txt path is a prefix, so each covers its subpaths. Only these:\n * not a blanket `/api/`, because a site may serve something public there.\n */\nconst SHARED_API_DISALLOW = ['/api/contact', '/api/google-reviews']\n\n/**\n * Crawlers may index any production build except a Vercel Preview: the real\n * production deployment and a local `next start`. `next dev` and every\n * Preview are closed. There is no environment override.\n */\nexport function isBrandlabIndexableDeployment(deployment: BrandlabDeployment) {\n return deployment.environment === 'production' ||\n (deployment.environment === 'development' && deployment.productionBuild)\n}\n\n/**\n * `src/app/robots.ts`: `export default createBrandlabRobots(site)`.\n * The deployment is resolved when Next renders robots.txt. Production allows\n * everything but the shared API routes and names the canonical sitemap; every\n * other deployment keeps `Disallow: /`.\n */\nexport function createBrandlabRobots(\n site: BrandlabSite,\n options: { environment?: BrandlabEnvironment } = {},\n) {\n const sitemap = `${site.canonicalUrl}/sitemap.xml`\n return function robots(): BrandlabRobotsMetadata {\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (!isBrandlabIndexableDeployment(deployment)) {\n return { rules: [{ userAgent: '*', disallow: '/' }] }\n }\n // No `host` directive: it is a non-standard robots.txt extension ignored\n // by Google and Bing; canonical-host consolidation is owned by permanent\n // redirects and canonical URLs.\n return {\n rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }],\n sitemap,\n }\n }\n}\n"]}
|