@brandlab-digital/site-next 2.0.0 → 3.0.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 +111 -52
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +281 -153
- package/dist/analytics.js.map +1 -1
- package/dist/blog.d.ts +4 -2
- package/dist/blog.d.ts.map +1 -1
- package/dist/blog.js +7 -7
- package/dist/blog.js.map +1 -1
- package/dist/contact/client-entry.d.ts +2 -0
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js +1 -0
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/client.d.ts +12 -22
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +28 -21
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/form-client.d.ts +148 -0
- package/dist/contact/form-client.d.ts.map +1 -0
- package/dist/contact/form-client.js +234 -0
- package/dist/contact/form-client.js.map +1 -0
- package/dist/contact/index.d.ts +1 -1
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +1 -1
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/route-handler.d.ts +32 -0
- package/dist/contact/route-handler.d.ts.map +1 -0
- package/dist/contact/route-handler.js +44 -0
- package/dist/contact/route-handler.js.map +1 -0
- package/dist/contact/server-entry.d.ts +1 -0
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -0
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +5 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +13 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +4 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +29 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +27 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/google-reviews-client.d.ts +12 -1
- package/dist/google-reviews-client.d.ts.map +1 -1
- package/dist/google-reviews-client.js +38 -41
- package/dist/google-reviews-client.js.map +1 -1
- package/dist/google-reviews-ui.d.ts +4 -2
- package/dist/google-reviews-ui.d.ts.map +1 -1
- package/dist/google-reviews-ui.js +10 -9
- package/dist/google-reviews-ui.js.map +1 -1
- package/dist/google-tag-plan.d.ts +1 -4
- package/dist/google-tag-plan.d.ts.map +1 -1
- package/dist/google-tag-plan.js +1 -14
- package/dist/google-tag-plan.js.map +1 -1
- package/dist/google-tag.d.ts +5 -4
- package/dist/google-tag.d.ts.map +1 -1
- package/dist/google-tag.js +5 -4
- package/dist/google-tag.js.map +1 -1
- package/dist/headers.d.ts +21 -28
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +62 -51
- package/dist/headers.js.map +1 -1
- package/dist/instrumentation-client.d.ts +21 -1
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +62 -7
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/lead-registry.d.ts +34 -0
- package/dist/lead-registry.d.ts.map +1 -0
- package/dist/lead-registry.js +30 -0
- package/dist/lead-registry.js.map +1 -0
- package/dist/lead-reporting.d.ts +37 -0
- package/dist/lead-reporting.d.ts.map +1 -0
- package/dist/lead-reporting.js +74 -0
- package/dist/lead-reporting.js.map +1 -0
- package/dist/metadata.d.ts +10 -11
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +17 -19
- package/dist/metadata.js.map +1 -1
- package/dist/next-config.d.ts +102 -0
- package/dist/next-config.d.ts.map +1 -0
- package/dist/next-config.js +140 -0
- package/dist/next-config.js.map +1 -0
- package/dist/robots.d.ts +14 -6
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +26 -17
- package/dist/robots.js.map +1 -1
- package/dist/sitemaps.d.ts +34 -0
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +66 -1
- package/dist/sitemaps.js.map +1 -1
- package/dist/testing/server-only.d.ts +8 -0
- package/dist/testing/server-only.d.ts.map +1 -0
- package/dist/testing/server-only.js +8 -0
- package/dist/testing/server-only.js.map +1 -0
- package/dist/ui-client.d.ts +1 -12
- package/dist/ui-client.d.ts.map +1 -1
- package/dist/ui-client.js +0 -17
- package/dist/ui-client.js.map +1 -1
- package/dist/ui.d.ts +22 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +67 -6
- package/dist/ui.js.map +1 -1
- package/package.json +16 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-registry.js","sourceRoot":"","sources":["../src/lead-registry.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAkC/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 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"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ContactFormConversionResult } from '@brandlab-digital/site-core/analytics';
|
|
2
|
+
import type { ContactActionResult } from '@brandlab-digital/site-core/contact';
|
|
3
|
+
import type { BrandlabContactFieldRoles } from './contact/shared.js';
|
|
4
|
+
import { type LeadAdsParams, type LeadBinding, type LeadPageParams, type LeadTrackingConfig } from './lead-registry.js';
|
|
5
|
+
export type AcceptedContactResult = Extract<ContactActionResult, {
|
|
6
|
+
ok: true;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function isTrackableContactActionResult(result: ContactActionResult): result is Extract<ContactActionResult, {
|
|
9
|
+
ok: true;
|
|
10
|
+
accepted: true;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* GA4 contact_form plus the Google Ads form conversion, only after provider
|
|
14
|
+
* acceptance. Email and phone may only come from fields with explicit
|
|
15
|
+
* declarative roles and never enter the GA4 parameter object.
|
|
16
|
+
*/
|
|
17
|
+
export declare function trackDeliveredContactWithEnhancedConversions(result: ContactActionResult, config: LeadTrackingConfig, options: {
|
|
18
|
+
pagePath: string;
|
|
19
|
+
/** Field names with declarative roles; only email and phone roles are read. */
|
|
20
|
+
definition: BrandlabContactFieldRoles;
|
|
21
|
+
formData: FormData;
|
|
22
|
+
/** Live Consent Mode decision for ad_user_data; absence is deny. */
|
|
23
|
+
adUserDataConsent: boolean;
|
|
24
|
+
params?: LeadPageParams;
|
|
25
|
+
adsParams?: LeadAdsParams;
|
|
26
|
+
}): ContactFormConversionResult;
|
|
27
|
+
export interface AcceptedLeadInput {
|
|
28
|
+
/** The form's pagePath, exactly as the hook received it. */
|
|
29
|
+
pagePath: string;
|
|
30
|
+
/** The accepted attempt's fields. */
|
|
31
|
+
formData: FormData;
|
|
32
|
+
fieldRoles: BrandlabContactFieldRoles;
|
|
33
|
+
adUserDataConsent: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Sends the binding's declared lead for one accepted enquiry. */
|
|
36
|
+
export declare function reportAcceptedLead(lead: LeadBinding, result: AcceptedContactResult, input: AcceptedLeadInput, override?: Pick<LeadTrackingConfig, 'enabled' | 'gtag'>): ContactFormConversionResult;
|
|
37
|
+
//# sourceMappingURL=lead-reporting.d.ts.map
|
|
@@ -0,0 +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,oEAAoE;IACpE,iBAAiB,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,SAAS,CAAC,EAAE,aAAa,CAAA;CAC1B,GACA,2BAA2B,CAqB7B;AAED,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB,UAAU,EAAE,yBAAyB,CAAA;IACrC,iBAAiB,EAAE,OAAO,CAAA;CAC3B;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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Internal module (not in package exports): the single accepted-enquiry
|
|
2
|
+
// reporting path, used only by useBrandlabContactForm and the contact testing
|
|
3
|
+
// surface, with the lead reporting a binding declared in lead-registry.
|
|
4
|
+
import { normalizeEnhancedConversionEmail, normalizeEnhancedConversionPhone, sendContactFormConversion, } from '@brandlab-digital/site-core/analytics';
|
|
5
|
+
import { getBrowserGtag, safePagePath, safeSlug, } from './lead-registry.js';
|
|
6
|
+
function sanitizeContactParams(params) {
|
|
7
|
+
return {
|
|
8
|
+
...(safeSlug(params?.page_template)
|
|
9
|
+
? { page_template: safeSlug(params?.page_template) }
|
|
10
|
+
: {}),
|
|
11
|
+
...(safeSlug(params?.service_slug)
|
|
12
|
+
? { service_slug: safeSlug(params?.service_slug) }
|
|
13
|
+
: {}),
|
|
14
|
+
...(safeSlug(params?.location_slug)
|
|
15
|
+
? { location_slug: safeSlug(params?.location_slug) }
|
|
16
|
+
: {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function sanitizeAdsParams(params) {
|
|
20
|
+
return {
|
|
21
|
+
...(params?.currency && /^[A-Z]{3}$/.test(params.currency)
|
|
22
|
+
? { currency: params.currency }
|
|
23
|
+
: {}),
|
|
24
|
+
...(typeof params?.value === 'number' && Number.isFinite(params.value) && params.value >= 0
|
|
25
|
+
? { value: params.value }
|
|
26
|
+
: {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function isTrackableContactActionResult(result) {
|
|
30
|
+
return result.ok && result.accepted && result.trackConversion;
|
|
31
|
+
}
|
|
32
|
+
function explicitRoleValue(definition, formData, role) {
|
|
33
|
+
const field = definition.fields.find((candidate) => candidate.role === role);
|
|
34
|
+
if (!field)
|
|
35
|
+
return undefined;
|
|
36
|
+
const value = formData.get(field.name);
|
|
37
|
+
return typeof value === 'string' ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* GA4 contact_form plus the Google Ads form conversion, only after provider
|
|
41
|
+
* acceptance. Email and phone may only come from fields with explicit
|
|
42
|
+
* declarative roles and never enter the GA4 parameter object.
|
|
43
|
+
*/
|
|
44
|
+
export function trackDeliveredContactWithEnhancedConversions(result, config, options) {
|
|
45
|
+
if (!isTrackableContactActionResult(result)) {
|
|
46
|
+
return { ga4Sent: false, enhancedConversionSent: false, adsConversionSent: false };
|
|
47
|
+
}
|
|
48
|
+
const email = normalizeEnhancedConversionEmail(explicitRoleValue(options.definition, options.formData, 'email'));
|
|
49
|
+
const phone = normalizeEnhancedConversionPhone(explicitRoleValue(options.definition, options.formData, 'phone'));
|
|
50
|
+
return sendContactFormConversion({
|
|
51
|
+
...config,
|
|
52
|
+
gtag: config.gtag ?? getBrowserGtag(),
|
|
53
|
+
pagePath: safePagePath(options.pagePath),
|
|
54
|
+
submissionId: result.confirmationId,
|
|
55
|
+
params: sanitizeContactParams(options.params),
|
|
56
|
+
adsParams: sanitizeAdsParams(options.adsParams),
|
|
57
|
+
enhancedConversionsEnabled: config.enhancedConversionsEnabled === true,
|
|
58
|
+
adUserDataConsent: options.adUserDataConsent === true,
|
|
59
|
+
...((email || phone) ? { enhancedConversion: { email, phone } } : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Sends the binding's declared lead for one accepted enquiry. */
|
|
63
|
+
export function reportAcceptedLead(lead, result, input, override) {
|
|
64
|
+
const params = lead.pageParams(input.pagePath);
|
|
65
|
+
return trackDeliveredContactWithEnhancedConversions(result, { ...lead.config, ...override }, {
|
|
66
|
+
pagePath: input.pagePath,
|
|
67
|
+
definition: input.fieldRoles,
|
|
68
|
+
formData: input.formData,
|
|
69
|
+
adUserDataConsent: input.adUserDataConsent,
|
|
70
|
+
params,
|
|
71
|
+
adsParams: lead.adsParams({ pagePath: input.pagePath, pageParams: params, formData: input.formData }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=lead-reporting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 /** Live Consent Mode decision for ad_user_data; absence is deny. */\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 adUserDataConsent: 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 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 adUserDataConsent: input.adUserDataConsent,\n params,\n adsParams: lead.adsParams({ pagePath: input.pagePath, pageParams: params, formData: input.formData }),\n })\n}\n"]}
|
package/dist/metadata.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { type BrandlabSeoMetadata
|
|
1
|
+
import { type BrandlabSeoMetadata } from '@brandlab-digital/site-core/metadata';
|
|
2
2
|
import { type BrandlabRouteCatalog } from '@brandlab-digital/site-core/routes';
|
|
3
3
|
import { type BrandlabSeoPage } from '@brandlab-digital/site-core/seo-page';
|
|
4
|
+
import type { BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
4
5
|
export type NextCompatibleMetadata = Omit<BrandlabSeoMetadata, 'canonicalPath' | 'canonicalUrl' | 'resolvedTitle'>;
|
|
5
|
-
export type BrandlabMetadataDefaults = Pick<BuildSeoMetadataInput, 'brandName' | 'siteUrl' | 'locale' | 'titleTemplate' | 'defaultSocialImage'>;
|
|
6
|
-
export type BrandlabPageMetadataInput = Omit<BuildSeoMetadataInput, keyof BrandlabMetadataDefaults>;
|
|
7
|
-
export type BrandlabStrictMetadataDefaults = Pick<BuildBrandlabMetadataInput, 'brandName' | 'siteUrl' | 'locale' | 'titleTemplate' | 'defaultSocialImage'>;
|
|
8
|
-
export type BrandlabStrictPageMetadataInput = Omit<BuildBrandlabMetadataInput, keyof BrandlabStrictMetadataDefaults>;
|
|
9
6
|
export declare function toNextMetadata(metadata: BrandlabSeoMetadata): NextCompatibleMetadata;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The project's one page-metadata binding (`src/lib/seo/metadata.ts`):
|
|
9
|
+
* `export const pageMetadata = createRouteMetadataBuilder(site, routes)`.
|
|
10
|
+
* Canonical path and robots come from the route catalogue; the title mode is
|
|
11
|
+
* explicit per page. Keywords keep their form: a comma-separated string renders
|
|
12
|
+
* exactly as given, a list renders joined with commas, as Next renders them.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRouteMetadataBuilder(site: BrandlabSite, routes: BrandlabRouteCatalog): (input: BrandlabSeoPage) => NextCompatibleMetadata;
|
|
16
15
|
//# sourceMappingURL=metadata.d.ts.map
|
package/dist/metadata.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,sCAAsC,CAAA;AAC1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEpE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,mBAAmB,EACnB,eAAe,GAAG,cAAc,GAAG,eAAe,CACnD,CAAA;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,mBAAmB,GAAG,sBAAsB,CAIpF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,oBAAoB,IAEpB,OAAO,eAAe,KAAG,sBAAsB,CA+BxD"}
|
package/dist/metadata.js
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
|
-
import { buildBrandlabMetadata,
|
|
1
|
+
import { buildBrandlabMetadata, } from '@brandlab-digital/site-core/metadata';
|
|
2
2
|
import { getRouteById, resolveBrandlabRouteIndexing, } from '@brandlab-digital/site-core/routes';
|
|
3
3
|
import { defineSeoPage } from '@brandlab-digital/site-core/seo-page';
|
|
4
4
|
export function toNextMetadata(metadata) {
|
|
5
5
|
const { canonicalPath: _canonicalPath, canonicalUrl: _canonicalUrl, resolvedTitle: _resolvedTitle, ...next } = metadata;
|
|
6
6
|
return next;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
export function createBrandlabNextMetadataBuilder(defaults) {
|
|
18
|
-
return (page) => buildBrandlabNextMetadata({ ...defaults, ...page });
|
|
19
|
-
}
|
|
20
|
-
/** Builds page metadata from the same route identity used by breadcrumbs and sitemaps. */
|
|
21
|
-
export function createRouteMetadataBuilder(defaults, routes) {
|
|
8
|
+
/**
|
|
9
|
+
* The project's one page-metadata binding (`src/lib/seo/metadata.ts`):
|
|
10
|
+
* `export const pageMetadata = createRouteMetadataBuilder(site, routes)`.
|
|
11
|
+
* Canonical path and robots come from the route catalogue; the title mode is
|
|
12
|
+
* explicit per page. Keywords keep their form: a comma-separated string renders
|
|
13
|
+
* exactly as given, a list renders joined with commas, as Next renders them.
|
|
14
|
+
*/
|
|
15
|
+
export function createRouteMetadataBuilder(site, routes) {
|
|
22
16
|
return (input) => {
|
|
23
17
|
const page = defineSeoPage(input);
|
|
24
18
|
const route = getRouteById(routes, page.routeId);
|
|
25
19
|
if (!route)
|
|
26
20
|
throw new Error(`Unknown metadata route ID "${page.routeId}".`);
|
|
27
|
-
return
|
|
28
|
-
|
|
21
|
+
return toNextMetadata(buildBrandlabMetadata({
|
|
22
|
+
brandName: site.name,
|
|
23
|
+
siteUrl: site.canonicalUrl,
|
|
24
|
+
locale: site.locale,
|
|
25
|
+
titleTemplate: site.titleTemplate,
|
|
26
|
+
defaultSocialImage: site.defaultSocialImage,
|
|
29
27
|
title: page.title,
|
|
30
28
|
description: page.description,
|
|
31
29
|
pathname: route.canonicalPath ?? route.path,
|
|
32
30
|
indexing: resolveBrandlabRouteIndexing(route),
|
|
33
31
|
...(page.titleMode ? { titleMode: page.titleMode } : {}),
|
|
34
|
-
...(page.keywords ? { keywords:
|
|
32
|
+
...(page.keywords !== undefined ? { keywords: page.keywords } : {}),
|
|
35
33
|
...(page.socialImage ? { socialImage: page.socialImage } : {}),
|
|
36
34
|
...(page.article
|
|
37
35
|
? {
|
|
@@ -44,7 +42,7 @@ export function createRouteMetadataBuilder(defaults, routes) {
|
|
|
44
42
|
},
|
|
45
43
|
}
|
|
46
44
|
: {}),
|
|
47
|
-
});
|
|
45
|
+
}));
|
|
48
46
|
};
|
|
49
47
|
}
|
|
50
48
|
//# sourceMappingURL=metadata.js.map
|
package/dist/metadata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAEtB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,YAAY,EACZ,4BAA4B,GAE7B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAwB,MAAM,sCAAsC,CAAA;AAQ1F,MAAM,UAAU,cAAc,CAAC,QAA6B;IAC1D,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAC1G,QAAQ,CAAA;IACV,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAAkB,EAClB,MAA4B;IAE5B,OAAO,CAAC,KAAsB,EAA0B,EAAE;QACxD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QACjC,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QAChD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA;QAE3E,OAAO,cAAc,CAAC,qBAAqB,CAAC;YAC1C,SAAS,EAAE,IAAI,CAAC,IAAI;YACpB,OAAO,EAAE,IAAI,CAAC,YAAY;YAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,IAAI;YAC3C,QAAQ,EAAE,4BAA4B,CAAC,KAAK,CAAC;YAC7C,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC;oBACE,OAAO,EAAE;wBACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;wBACzC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;4BAC3B,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;4BAC7C,CAAC,CAAC,EAAE,CAAC;wBACP,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;qBACnC;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC,CAAA;IACL,CAAC,CAAA;AACH,CAAC","sourcesContent":["import {\n buildBrandlabMetadata,\n type BrandlabSeoMetadata,\n} from '@brandlab-digital/site-core/metadata'\nimport {\n getRouteById,\n resolveBrandlabRouteIndexing,\n type BrandlabRouteCatalog,\n} from '@brandlab-digital/site-core/routes'\nimport { defineSeoPage, type BrandlabSeoPage } from '@brandlab-digital/site-core/seo-page'\nimport type { BrandlabSite } from '@brandlab-digital/site-core/site'\n\nexport type NextCompatibleMetadata = Omit<\n BrandlabSeoMetadata,\n 'canonicalPath' | 'canonicalUrl' | 'resolvedTitle'\n>\n\nexport function toNextMetadata(metadata: BrandlabSeoMetadata): NextCompatibleMetadata {\n const { canonicalPath: _canonicalPath, canonicalUrl: _canonicalUrl, resolvedTitle: _resolvedTitle, ...next } =\n metadata\n return next\n}\n\n/**\n * The project's one page-metadata binding (`src/lib/seo/metadata.ts`):\n * `export const pageMetadata = createRouteMetadataBuilder(site, routes)`.\n * Canonical path and robots come from the route catalogue; the title mode is\n * explicit per page. Keywords keep their form: a comma-separated string renders\n * exactly as given, a list renders joined with commas, as Next renders them.\n */\nexport function createRouteMetadataBuilder(\n site: BrandlabSite,\n routes: BrandlabRouteCatalog,\n) {\n return (input: BrandlabSeoPage): NextCompatibleMetadata => {\n const page = defineSeoPage(input)\n const route = getRouteById(routes, page.routeId)\n if (!route) throw new Error(`Unknown metadata route ID \"${page.routeId}\".`)\n\n return toNextMetadata(buildBrandlabMetadata({\n brandName: site.name,\n siteUrl: site.canonicalUrl,\n locale: site.locale,\n titleTemplate: site.titleTemplate,\n defaultSocialImage: site.defaultSocialImage,\n title: page.title,\n description: page.description,\n pathname: route.canonicalPath ?? route.path,\n indexing: resolveBrandlabRouteIndexing(route),\n ...(page.titleMode ? { titleMode: page.titleMode } : {}),\n ...(page.keywords !== undefined ? { keywords: page.keywords } : {}),\n ...(page.socialImage ? { socialImage: page.socialImage } : {}),\n ...(page.article\n ? {\n article: {\n publishedTime: page.article.publishedTime,\n ...(page.article.modifiedTime\n ? { modifiedTime: page.article.modifiedTime }\n : {}),\n authors: [...page.article.authors],\n },\n }\n : {}),\n }))\n }\n}\n"]}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type BrandlabRedirectTable } from '@brandlab-digital/site-core/redirects';
|
|
2
|
+
import { type BrandlabDeploymentEnvironment, type BrandlabEnvironment, type BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
3
|
+
import { type BrandlabContentSecurityPolicyExtensions, type BrandlabHeader, type BrandlabHeaderRule, type BrandlabHstsExpansion } from './headers.js';
|
|
4
|
+
/** Next redirect records: 308 stays `permanent: true`, 301 stays `statusCode: 301`. */
|
|
5
|
+
export type BrandlabNextRedirect = {
|
|
6
|
+
source: string;
|
|
7
|
+
destination: string;
|
|
8
|
+
has?: {
|
|
9
|
+
type: 'host';
|
|
10
|
+
value: string;
|
|
11
|
+
}[];
|
|
12
|
+
} & ({
|
|
13
|
+
permanent: true;
|
|
14
|
+
statusCode?: never;
|
|
15
|
+
} | {
|
|
16
|
+
statusCode: 301;
|
|
17
|
+
permanent?: never;
|
|
18
|
+
});
|
|
19
|
+
export interface BrandlabNextRemotePattern {
|
|
20
|
+
protocol?: 'http' | 'https';
|
|
21
|
+
hostname: string;
|
|
22
|
+
port?: string;
|
|
23
|
+
pathname?: string;
|
|
24
|
+
search?: string;
|
|
25
|
+
}
|
|
26
|
+
/** The site's own image settings; the output format is always WebP. */
|
|
27
|
+
export interface BrandlabNextImagesOptions {
|
|
28
|
+
deviceSizes?: readonly number[];
|
|
29
|
+
imageSizes?: readonly number[];
|
|
30
|
+
qualities?: readonly number[];
|
|
31
|
+
minimumCacheTTL?: number;
|
|
32
|
+
remotePatterns?: readonly BrandlabNextRemotePattern[];
|
|
33
|
+
}
|
|
34
|
+
export interface BrandlabNextImagesConfig {
|
|
35
|
+
formats: 'image/webp'[];
|
|
36
|
+
deviceSizes?: number[];
|
|
37
|
+
imageSizes?: number[];
|
|
38
|
+
qualities?: number[];
|
|
39
|
+
minimumCacheTTL?: number;
|
|
40
|
+
remotePatterns?: BrandlabNextRemotePattern[];
|
|
41
|
+
}
|
|
42
|
+
export interface BrandlabNextConfig {
|
|
43
|
+
poweredByHeader: false;
|
|
44
|
+
/** Trailing slashes are handled by single-hop redirect rules instead. */
|
|
45
|
+
skipTrailingSlashRedirect: true;
|
|
46
|
+
env: {
|
|
47
|
+
NEXT_PUBLIC_VERCEL_ENV: BrandlabDeploymentEnvironment;
|
|
48
|
+
};
|
|
49
|
+
outputFileTracingIncludes: Record<string, string[]>;
|
|
50
|
+
images: BrandlabNextImagesConfig;
|
|
51
|
+
pageExtensions?: string[];
|
|
52
|
+
headers: () => Promise<BrandlabHeaderRule[]>;
|
|
53
|
+
redirects: () => Promise<BrandlabNextRedirect[]>;
|
|
54
|
+
}
|
|
55
|
+
export interface BrandlabNextConfigOptions {
|
|
56
|
+
site: BrandlabSite;
|
|
57
|
+
/** The src/data/redirects.ts table (defineRedirects). */
|
|
58
|
+
redirects: BrandlabRedirectTable;
|
|
59
|
+
images: BrandlabNextImagesOptions;
|
|
60
|
+
/** Reviewed CSP origins for declared integrations such as Meta Pixel or map embeds. */
|
|
61
|
+
csp?: BrandlabContentSecurityPolicyExtensions;
|
|
62
|
+
hsts?: BrandlabHstsExpansion;
|
|
63
|
+
/** Reviewed exception to the default Permissions-Policy that is already live. */
|
|
64
|
+
permissionsPolicy?: string;
|
|
65
|
+
/** Reviewed response headers that are already live; protected headers cannot be replaced. */
|
|
66
|
+
extraHeaders?: readonly BrandlabHeader[];
|
|
67
|
+
/** Defaults to process.env; tests supply a fixed environment. */
|
|
68
|
+
environment?: BrandlabEnvironment;
|
|
69
|
+
}
|
|
70
|
+
/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */
|
|
71
|
+
export declare const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING: Readonly<{
|
|
72
|
+
'/api/contact': readonly string[];
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Converts the redirect table to Next redirects: each entry keeps its 301 or
|
|
76
|
+
* 308 status (Next forwards the query string), gains a single-hop
|
|
77
|
+
* trailing-slash variant, then one trailing-slash rule and one permanent
|
|
78
|
+
* canonical-host rule per alias host follow. There is no x-forwarded-proto
|
|
79
|
+
* rule: Vercel's edge already enforces HTTPS.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildBrandlabRedirects(options: {
|
|
82
|
+
site: BrandlabSite;
|
|
83
|
+
redirects: BrandlabRedirectTable;
|
|
84
|
+
}): BrandlabNextRedirect[];
|
|
85
|
+
/**
|
|
86
|
+
* The whole next.config.ts after its first line
|
|
87
|
+
* `import '@brandlab-digital/site-next/image-runtime'`:
|
|
88
|
+
* `export default defineBrandlabNextConfig({ site, redirects, images })`.
|
|
89
|
+
*
|
|
90
|
+
* Sets poweredByHeader: false, WebP image output, Sharp output tracing for
|
|
91
|
+
* /api/contact, NEXT_PUBLIC_VERCEL_ENV for browser gates, the deployment's
|
|
92
|
+
* header rules and the redirect table. Validation runs when the config loads,
|
|
93
|
+
* so an invalid table or header exception fails the build.
|
|
94
|
+
*/
|
|
95
|
+
export declare function defineBrandlabNextConfig(options: BrandlabNextConfigOptions & {
|
|
96
|
+
mdx?: undefined;
|
|
97
|
+
}): BrandlabNextConfig;
|
|
98
|
+
export declare function defineBrandlabNextConfig<Wrapped>(options: BrandlabNextConfigOptions & {
|
|
99
|
+
/** The @next/mdx wrapper: createMDX({ options: { remarkPlugins: ['remark-gfm'] } }). */
|
|
100
|
+
mdx: (config: BrandlabNextConfig) => Wrapped;
|
|
101
|
+
}): Wrapped;
|
|
102
|
+
//# sourceMappingURL=next-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-config.d.ts","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineRedirects } from '@brandlab-digital/site-core/redirects';
|
|
2
|
+
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
3
|
+
import { buildBrandlabHeaderRules, } from './headers.js';
|
|
4
|
+
/** Sharp's Linux binaries must travel with the contact route's image sanitiser. */
|
|
5
|
+
export const BRANDLAB_CONTACT_OUTPUT_FILE_TRACING = Object.freeze({
|
|
6
|
+
'/api/contact': Object.freeze([
|
|
7
|
+
'node_modules/sharp/**/*',
|
|
8
|
+
'node_modules/@img/sharp-linux-x64/**/*',
|
|
9
|
+
'node_modules/@img/sharp-libvips-linux-x64/**/*',
|
|
10
|
+
]),
|
|
11
|
+
});
|
|
12
|
+
const MDX_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md'];
|
|
13
|
+
const OPTION_KEYS = new Set([
|
|
14
|
+
'site',
|
|
15
|
+
'redirects',
|
|
16
|
+
'images',
|
|
17
|
+
'csp',
|
|
18
|
+
'hsts',
|
|
19
|
+
'permissionsPolicy',
|
|
20
|
+
'extraHeaders',
|
|
21
|
+
'mdx',
|
|
22
|
+
'environment',
|
|
23
|
+
]);
|
|
24
|
+
const IMAGE_KEYS = new Set(['deviceSizes', 'imageSizes', 'qualities', 'minimumCacheTTL', 'remotePatterns']);
|
|
25
|
+
function assertKnownKeys(value, allowed, field, hint = '') {
|
|
26
|
+
const unsupported = Object.keys(value).filter((key) => !allowed.has(key));
|
|
27
|
+
if (unsupported.length > 0) {
|
|
28
|
+
throw new Error(`${field} contains unsupported keys: ${unsupported.join(', ')}.${hint}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function positiveIntegers(values, field) {
|
|
32
|
+
if (values === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (!Array.isArray(values) ||
|
|
35
|
+
values.length === 0 ||
|
|
36
|
+
values.some((value) => !Number.isSafeInteger(value) || value < 1)) {
|
|
37
|
+
throw new Error(`images.${field} must list positive integers.`);
|
|
38
|
+
}
|
|
39
|
+
return [...values];
|
|
40
|
+
}
|
|
41
|
+
function buildImagesConfig(images) {
|
|
42
|
+
if (!images || typeof images !== 'object' || Array.isArray(images)) {
|
|
43
|
+
throw new Error('images must declare the site image settings ({} for Next defaults).');
|
|
44
|
+
}
|
|
45
|
+
assertKnownKeys(images, IMAGE_KEYS, 'images', ' The output format is always image/webp.');
|
|
46
|
+
const deviceSizes = positiveIntegers(images.deviceSizes, 'deviceSizes');
|
|
47
|
+
const imageSizes = positiveIntegers(images.imageSizes, 'imageSizes');
|
|
48
|
+
const qualities = positiveIntegers(images.qualities, 'qualities');
|
|
49
|
+
if (qualities?.some((quality) => quality > 100)) {
|
|
50
|
+
throw new Error('images.qualities must be between 1 and 100.');
|
|
51
|
+
}
|
|
52
|
+
if (images.minimumCacheTTL !== undefined &&
|
|
53
|
+
(!Number.isSafeInteger(images.minimumCacheTTL) || images.minimumCacheTTL < 0)) {
|
|
54
|
+
throw new Error('images.minimumCacheTTL must be a non-negative integer of seconds.');
|
|
55
|
+
}
|
|
56
|
+
if (images.remotePatterns !== undefined && !Array.isArray(images.remotePatterns)) {
|
|
57
|
+
throw new Error('images.remotePatterns must be an array.');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
formats: ['image/webp'],
|
|
61
|
+
...(deviceSizes ? { deviceSizes } : {}),
|
|
62
|
+
...(imageSizes ? { imageSizes } : {}),
|
|
63
|
+
...(qualities ? { qualities } : {}),
|
|
64
|
+
...(images.minimumCacheTTL !== undefined ? { minimumCacheTTL: images.minimumCacheTTL } : {}),
|
|
65
|
+
...(images.remotePatterns
|
|
66
|
+
? { remotePatterns: images.remotePatterns.map((pattern) => ({ ...pattern })) }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Converts the redirect table to Next redirects: each entry keeps its 301 or
|
|
72
|
+
* 308 status (Next forwards the query string), gains a single-hop
|
|
73
|
+
* trailing-slash variant, then one trailing-slash rule and one permanent
|
|
74
|
+
* canonical-host rule per alias host follow. There is no x-forwarded-proto
|
|
75
|
+
* rule: Vercel's edge already enforces HTTPS.
|
|
76
|
+
*/
|
|
77
|
+
export function buildBrandlabRedirects(options) {
|
|
78
|
+
const table = defineRedirects(options.redirects);
|
|
79
|
+
const entries = table.flatMap((redirect) => {
|
|
80
|
+
const status = redirect.status === 301
|
|
81
|
+
? { statusCode: 301 }
|
|
82
|
+
: { permanent: true };
|
|
83
|
+
const has = redirect.host ? { has: [{ type: 'host', value: redirect.host }] } : {};
|
|
84
|
+
const record = (source) => ({
|
|
85
|
+
source,
|
|
86
|
+
destination: redirect.destination,
|
|
87
|
+
...status,
|
|
88
|
+
...has,
|
|
89
|
+
});
|
|
90
|
+
return redirect.source === '/'
|
|
91
|
+
? [record(redirect.source)]
|
|
92
|
+
: [record(redirect.source), record(`${redirect.source}/`)];
|
|
93
|
+
});
|
|
94
|
+
return [
|
|
95
|
+
...entries,
|
|
96
|
+
{ source: '/:path+/', destination: '/:path+', permanent: true },
|
|
97
|
+
...options.site.aliasHosts.map((host) => ({
|
|
98
|
+
source: '/:path*',
|
|
99
|
+
has: [{ type: 'host', value: host }],
|
|
100
|
+
destination: `${options.site.canonicalUrl}/:path*`,
|
|
101
|
+
permanent: true,
|
|
102
|
+
})),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
export function defineBrandlabNextConfig(options) {
|
|
106
|
+
if (!options || typeof options !== 'object') {
|
|
107
|
+
throw new TypeError('defineBrandlabNextConfig requires the site, redirects and images.');
|
|
108
|
+
}
|
|
109
|
+
assertKnownKeys(options, OPTION_KEYS, 'next.config options');
|
|
110
|
+
if (!options.site?.canonicalUrl || !Array.isArray(options.site.aliasHosts)) {
|
|
111
|
+
throw new Error('site must come from defineBrandlabSite.');
|
|
112
|
+
}
|
|
113
|
+
if (options.mdx !== undefined && typeof options.mdx !== 'function') {
|
|
114
|
+
throw new TypeError('mdx must be the @next/mdx config wrapper.');
|
|
115
|
+
}
|
|
116
|
+
const deployment = resolveBrandlabDeployment(options.environment ?? process.env);
|
|
117
|
+
const headerRules = buildBrandlabHeaderRules({
|
|
118
|
+
deployment,
|
|
119
|
+
...(options.csp ? { contentSecurityPolicyExtensions: options.csp } : {}),
|
|
120
|
+
...(options.hsts ? { hsts: options.hsts } : {}),
|
|
121
|
+
...(options.permissionsPolicy ? { permissionsPolicy: options.permissionsPolicy } : {}),
|
|
122
|
+
...(options.extraHeaders ? { extraHeaders: options.extraHeaders } : {}),
|
|
123
|
+
});
|
|
124
|
+
const redirects = buildBrandlabRedirects({ site: options.site, redirects: options.redirects });
|
|
125
|
+
const config = {
|
|
126
|
+
poweredByHeader: false,
|
|
127
|
+
skipTrailingSlashRedirect: true,
|
|
128
|
+
env: { NEXT_PUBLIC_VERCEL_ENV: deployment.environment },
|
|
129
|
+
outputFileTracingIncludes: Object.fromEntries(Object.entries(BRANDLAB_CONTACT_OUTPUT_FILE_TRACING).map(([route, globs]) => [route, [...globs]])),
|
|
130
|
+
images: buildImagesConfig(options.images),
|
|
131
|
+
...(options.mdx ? { pageExtensions: [...MDX_PAGE_EXTENSIONS] } : {}),
|
|
132
|
+
headers: async () => headerRules.map((rule) => ({ source: rule.source, headers: rule.headers.map((header) => ({ ...header })) })),
|
|
133
|
+
redirects: async () => redirects.map((redirect) => ({
|
|
134
|
+
...redirect,
|
|
135
|
+
...(redirect.has ? { has: redirect.has.map((condition) => ({ ...condition })) } : {}),
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
return options.mdx ? options.mdx(config) : config;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=next-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-config.js","sourceRoot":"","sources":["../src/next-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { 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 type BrandlabContentSecurityPolicyExtensions,\n type BrandlabHeader,\n type BrandlabHeaderRule,\n type BrandlabHstsExpansion,\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 /** Defaults to process.env; tests supply a fixed environment. */\n environment?: BrandlabEnvironment\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 'mdx',\n 'environment',\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\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 and the redirect table. Validation runs when the config loads,\n * so an invalid table or header exception 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 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 })\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"]}
|
package/dist/robots.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type BrandlabDeployment, type BrandlabEnvironment, type BrandlabSite } from '@brandlab-digital/site-core/site';
|
|
1
2
|
export interface BrandlabRobotsMetadata {
|
|
2
3
|
rules: Array<{
|
|
3
4
|
userAgent: string;
|
|
@@ -6,10 +7,17 @@ export interface BrandlabRobotsMetadata {
|
|
|
6
7
|
}>;
|
|
7
8
|
sitemap?: string;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Crawlers may index any production build except a Vercel Preview: the real
|
|
12
|
+
* production deployment and a local `next start`. `next dev` and every
|
|
13
|
+
* Preview are closed. There is no environment override.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isBrandlabIndexableDeployment(deployment: BrandlabDeployment): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* `src/app/robots.ts`: `export default createBrandlabRobots(site)`.
|
|
18
|
+
* The deployment is resolved when Next renders robots.txt.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createBrandlabRobots(site: BrandlabSite, options?: {
|
|
21
|
+
environment?: BrandlabEnvironment;
|
|
22
|
+
}): () => BrandlabRobotsMetadata;
|
|
15
23
|
//# sourceMappingURL=robots.d.ts.map
|
package/dist/robots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"
|
|
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;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,kBAAkB,WAG3E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAGzB,sBAAsB,CAajD"}
|