@decocms/start 0.22.1 → 0.22.2
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/package.json +1 -1
- package/src/cms/resolve.ts +99 -20
- package/src/matchers/builtins.ts +58 -73
- package/src/matchers/countryNames.ts +89 -0
- package/src/routes/cmsRoute.ts +8 -4
package/package.json
CHANGED
package/src/cms/resolve.ts
CHANGED
|
@@ -356,11 +356,16 @@ async function internalResolve(value: unknown, rctx: ResolveContext): Promise<un
|
|
|
356
356
|
// "resolved" short-circuit
|
|
357
357
|
if (resolveType === "resolved") return obj.data ?? null;
|
|
358
358
|
|
|
359
|
-
// Lazy section wrapper
|
|
359
|
+
// Lazy section wrapper — unwrap single inner section
|
|
360
360
|
if (resolveType === "website/sections/Rendering/Lazy.tsx") {
|
|
361
361
|
return obj.section ? internalResolve(obj.section, childCtx) : null;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// Deferred section wrapper (legacy Fresh/HTMX) — unwrap inner sections array
|
|
365
|
+
if (resolveType === "website/sections/Rendering/Deferred.tsx") {
|
|
366
|
+
return obj.sections ? internalResolve(obj.sections, childCtx) : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
364
369
|
// Request param extraction
|
|
365
370
|
if (resolveType === "website/functions/requestToParam.ts") {
|
|
366
371
|
const paramName = (obj as any).param as string;
|
|
@@ -609,7 +614,7 @@ function isRawSectionLayout(section: unknown): string | null {
|
|
|
609
614
|
|
|
610
615
|
/**
|
|
611
616
|
* Resolve the final section component key by walking block references,
|
|
612
|
-
* unwrapping Lazy wrappers, and evaluating multivariate flags.
|
|
617
|
+
* unwrapping Lazy/Deferred wrappers, and evaluating multivariate flags.
|
|
613
618
|
* Returns null if not determinable.
|
|
614
619
|
*/
|
|
615
620
|
function resolveFinalSectionKey(
|
|
@@ -625,6 +630,7 @@ function resolveFinalSectionKey(
|
|
|
625
630
|
const rt = current.__resolveType as string | undefined;
|
|
626
631
|
if (!rt) return null;
|
|
627
632
|
|
|
633
|
+
// Lazy wrapper — unwrap single inner section
|
|
628
634
|
if (rt === "website/sections/Rendering/Lazy.tsx") {
|
|
629
635
|
const inner = current.section;
|
|
630
636
|
if (!inner || typeof inner !== "object") return null;
|
|
@@ -632,6 +638,22 @@ function resolveFinalSectionKey(
|
|
|
632
638
|
continue;
|
|
633
639
|
}
|
|
634
640
|
|
|
641
|
+
// Deferred wrapper (legacy) — unwrap first inner section if deterministic
|
|
642
|
+
if (rt === "website/sections/Rendering/Deferred.tsx") {
|
|
643
|
+
const inner = current.sections;
|
|
644
|
+
if (!inner || typeof inner !== "object") return null;
|
|
645
|
+
if (Array.isArray(inner)) {
|
|
646
|
+
if (inner.length === 1 && inner[0] && typeof inner[0] === "object") {
|
|
647
|
+
current = inner[0] as Record<string, unknown>;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
// sections is an object (e.g. a flag) — follow it
|
|
653
|
+
current = inner as Record<string, unknown>;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
635
657
|
if (
|
|
636
658
|
rt === "website/flags/multivariate.ts" ||
|
|
637
659
|
rt === "website/flags/multivariate/section.ts"
|
|
@@ -670,20 +692,61 @@ function resolveFinalSectionKey(
|
|
|
670
692
|
}
|
|
671
693
|
|
|
672
694
|
/**
|
|
673
|
-
*
|
|
674
|
-
*
|
|
695
|
+
* Walk the full wrapper chain (block refs, multivariate flags, Lazy, Deferred)
|
|
696
|
+
* and return true if a deferral wrapper (Lazy.tsx or Deferred.tsx) is found
|
|
697
|
+
* at any level. This is used by shouldDeferSection to determine if the CMS
|
|
698
|
+
* editor intended this section to be deferred.
|
|
675
699
|
*/
|
|
676
|
-
function
|
|
700
|
+
function isCmsDeferralWrapped(section: unknown, matcherCtx?: MatcherContext): boolean {
|
|
677
701
|
if (!section || typeof section !== "object") return false;
|
|
678
|
-
const obj = section as Record<string, unknown>;
|
|
679
|
-
const rt = obj.__resolveType as string | undefined;
|
|
680
|
-
if (!rt) return false;
|
|
681
|
-
|
|
682
|
-
if (rt === "website/sections/Rendering/Lazy.tsx") return true;
|
|
683
702
|
|
|
684
703
|
const blocks = loadBlocks();
|
|
685
|
-
|
|
686
|
-
|
|
704
|
+
let current = section as Record<string, unknown>;
|
|
705
|
+
|
|
706
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
707
|
+
const rt = current.__resolveType as string | undefined;
|
|
708
|
+
if (!rt) return false;
|
|
709
|
+
|
|
710
|
+
if (
|
|
711
|
+
rt === "website/sections/Rendering/Lazy.tsx" ||
|
|
712
|
+
rt === "website/sections/Rendering/Deferred.tsx"
|
|
713
|
+
) {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Walk through multivariate flags to check the matched variant
|
|
718
|
+
if (
|
|
719
|
+
rt === "website/flags/multivariate.ts" ||
|
|
720
|
+
rt === "website/flags/multivariate/section.ts"
|
|
721
|
+
) {
|
|
722
|
+
const variants = current.variants as
|
|
723
|
+
| Array<{ value: unknown; rule?: unknown }>
|
|
724
|
+
| undefined;
|
|
725
|
+
if (!variants?.length) return false;
|
|
726
|
+
|
|
727
|
+
let matched: unknown = null;
|
|
728
|
+
for (const variant of variants) {
|
|
729
|
+
const rule = variant.rule as Record<string, unknown> | undefined;
|
|
730
|
+
if (evaluateMatcher(rule, matcherCtx ?? {})) {
|
|
731
|
+
matched = variant.value;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (!matched || typeof matched !== "object") return false;
|
|
736
|
+
current = matched as Record<string, unknown>;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Named block reference — follow the chain
|
|
741
|
+
const block = blocks[rt] as Record<string, unknown> | undefined;
|
|
742
|
+
if (block) {
|
|
743
|
+
const { __resolveType: _, ...overrides } = current;
|
|
744
|
+
current = { ...block, ...overrides, __resolveType: block.__resolveType as string };
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
687
750
|
|
|
688
751
|
return false;
|
|
689
752
|
}
|
|
@@ -702,19 +765,15 @@ function shouldDeferSection(
|
|
|
702
765
|
const rt = obj.__resolveType as string | undefined;
|
|
703
766
|
if (!rt) return false;
|
|
704
767
|
|
|
705
|
-
// Top-level flags (not wrapped in Lazy) — keep eager for safety
|
|
706
|
-
if (rt === "website/flags/multivariate.ts" || rt === "website/flags/multivariate/section.ts") {
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
768
|
const finalKey = resolveFinalSectionKey(section, matcherCtx);
|
|
711
769
|
if (!finalKey) return false;
|
|
712
770
|
|
|
713
771
|
if (cfg.alwaysEager.has(finalKey)) return false;
|
|
714
772
|
if (isLayoutSection(finalKey)) return false;
|
|
715
773
|
|
|
716
|
-
//
|
|
717
|
-
|
|
774
|
+
// Walk the full wrapper chain (including multivariate flags) to detect
|
|
775
|
+
// Lazy.tsx or Deferred.tsx wrappers at any nesting level.
|
|
776
|
+
if (cfg.respectCmsLazy && isCmsDeferralWrapped(section, matcherCtx)) return true;
|
|
718
777
|
|
|
719
778
|
// Fallback: threshold-based deferral for non-Lazy sections
|
|
720
779
|
if (flatIndex >= cfg.foldThreshold) return true;
|
|
@@ -725,7 +784,10 @@ function shouldDeferSection(
|
|
|
725
784
|
/**
|
|
726
785
|
* Follow the block reference chain to find the final section component
|
|
727
786
|
* and collect the CMS props WITHOUT running commerce loaders.
|
|
728
|
-
* Resolves named block references,
|
|
787
|
+
* Resolves named block references, Lazy/Deferred wrappers, and multivariate flags.
|
|
788
|
+
*
|
|
789
|
+
* For Deferred.tsx with a single inner section, the inner section is returned.
|
|
790
|
+
* For Deferred.tsx with multiple inner sections, returns null (falls back to eager).
|
|
729
791
|
*/
|
|
730
792
|
function resolveSectionShallow(
|
|
731
793
|
section: unknown,
|
|
@@ -751,6 +813,23 @@ function resolveSectionShallow(
|
|
|
751
813
|
continue;
|
|
752
814
|
}
|
|
753
815
|
|
|
816
|
+
// Deferred wrapper (legacy) — unwrap if it contains a single section
|
|
817
|
+
if (rt === "website/sections/Rendering/Deferred.tsx") {
|
|
818
|
+
const inner = current.sections;
|
|
819
|
+
if (!inner || typeof inner !== "object") return null;
|
|
820
|
+
if (Array.isArray(inner)) {
|
|
821
|
+
if (inner.length === 1 && inner[0] && typeof inner[0] === "object") {
|
|
822
|
+
current = inner[0] as Record<string, unknown>;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
// Multiple inner sections can't be represented as a single DeferredSection
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
// sections is an object (e.g. a flag) — follow it
|
|
829
|
+
current = inner as Record<string, unknown>;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
|
|
754
833
|
// Multivariate flags — evaluate matchers and continue with matched variant
|
|
755
834
|
if (
|
|
756
835
|
rt === "website/flags/multivariate.ts" ||
|
package/src/matchers/builtins.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import type { MatcherContext } from "../cms/resolve";
|
|
19
19
|
import { evaluateMatcher, registerMatcher } from "../cms/resolve";
|
|
20
|
+
import { resolveCountryCode } from "./countryNames";
|
|
20
21
|
|
|
21
22
|
// -------------------------------------------------------------------------
|
|
22
23
|
// Cookie matcher
|
|
@@ -168,106 +169,90 @@ function queryStringMatcher(rule: Record<string, unknown>, ctx: MatcherContext):
|
|
|
168
169
|
// Location matcher
|
|
169
170
|
// -------------------------------------------------------------------------
|
|
170
171
|
|
|
171
|
-
/**
|
|
172
|
-
* CF country codes -> CMS country name mapping.
|
|
173
|
-
* The CMS stores country as full names ("Brasil"), CF provides ISO codes ("BR").
|
|
174
|
-
*/
|
|
175
|
-
const COUNTRY_NAME_TO_CODE: Record<string, string> = {
|
|
176
|
-
Brasil: "BR", Brazil: "BR",
|
|
177
|
-
Argentina: "AR", Chile: "CL",
|
|
178
|
-
Colombia: "CO", Mexico: "MX", "México": "MX",
|
|
179
|
-
Peru: "PE", "Perú": "PE",
|
|
180
|
-
Uruguay: "UY", Paraguay: "PY",
|
|
181
|
-
Bolivia: "BO", Ecuador: "EC",
|
|
182
|
-
Venezuela: "VE",
|
|
183
|
-
"United States": "US", USA: "US",
|
|
184
|
-
"Estados Unidos": "US",
|
|
185
|
-
Spain: "ES", "España": "ES",
|
|
186
|
-
Portugal: "PT",
|
|
187
|
-
Canada: "CA", "Canadá": "CA",
|
|
188
|
-
Germany: "DE", Alemania: "DE", Deutschland: "DE",
|
|
189
|
-
France: "FR", Francia: "FR",
|
|
190
|
-
Italy: "IT", Italia: "IT",
|
|
191
|
-
"United Kingdom": "GB", UK: "GB",
|
|
192
|
-
Japan: "JP", "Japón": "JP",
|
|
193
|
-
China: "CN",
|
|
194
|
-
Australia: "AU",
|
|
195
|
-
"South Korea": "KR",
|
|
196
|
-
India: "IN",
|
|
197
|
-
Netherlands: "NL",
|
|
198
|
-
Switzerland: "CH",
|
|
199
|
-
Sweden: "SE",
|
|
200
|
-
Norway: "NO",
|
|
201
|
-
Denmark: "DK",
|
|
202
|
-
Finland: "FI",
|
|
203
|
-
Belgium: "BE",
|
|
204
|
-
Austria: "AT",
|
|
205
|
-
Ireland: "IE",
|
|
206
|
-
"New Zealand": "NZ",
|
|
207
|
-
"South Africa": "ZA",
|
|
208
|
-
Israel: "IL",
|
|
209
|
-
"Saudi Arabia": "SA",
|
|
210
|
-
"United Arab Emirates": "AE",
|
|
211
|
-
Turkey: "TR", "Türkiye": "TR",
|
|
212
|
-
Poland: "PL",
|
|
213
|
-
"Czech Republic": "CZ", Czechia: "CZ",
|
|
214
|
-
Romania: "RO",
|
|
215
|
-
Hungary: "HU",
|
|
216
|
-
Greece: "GR",
|
|
217
|
-
Croatia: "HR",
|
|
218
|
-
"Costa Rica": "CR",
|
|
219
|
-
Panama: "PA", "Panamá": "PA",
|
|
220
|
-
"Dominican Republic": "DO",
|
|
221
|
-
Guatemala: "GT",
|
|
222
|
-
Honduras: "HN",
|
|
223
|
-
"El Salvador": "SV",
|
|
224
|
-
Nicaragua: "NI",
|
|
225
|
-
Cuba: "CU",
|
|
226
|
-
"Puerto Rico": "PR",
|
|
227
|
-
};
|
|
228
|
-
|
|
229
172
|
interface LocationRule {
|
|
230
173
|
country?: string;
|
|
231
174
|
regionCode?: string;
|
|
232
175
|
city?: string;
|
|
233
176
|
}
|
|
234
177
|
|
|
178
|
+
interface GeoData {
|
|
179
|
+
country: string;
|
|
180
|
+
regionCode: string;
|
|
181
|
+
regionName: string;
|
|
182
|
+
city: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract geo data from the request context.
|
|
187
|
+
* Priority: request.cf (Cloudflare Workers) > geo cookies > geo headers.
|
|
188
|
+
*/
|
|
189
|
+
function getGeoData(ctx: MatcherContext): GeoData {
|
|
190
|
+
// 1. Cloudflare Workers: request.cf has authoritative geo data
|
|
191
|
+
const req = ctx.request;
|
|
192
|
+
if (req) {
|
|
193
|
+
const cf = (req as any).cf as Record<string, unknown> | undefined;
|
|
194
|
+
if (cf?.country) {
|
|
195
|
+
return {
|
|
196
|
+
country: (cf.country as string) ?? "",
|
|
197
|
+
regionCode: (cf.regionCode as string) ?? (cf.region as string) ?? "",
|
|
198
|
+
regionName: (cf.region as string) ?? "",
|
|
199
|
+
city: (cf.city as string) ?? "",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 2. Geo cookies (set by Cloudflare middleware on Fresh/Deno sites)
|
|
205
|
+
const cookies = ctx.cookies ?? {};
|
|
206
|
+
const cookieCountry = cookies.__cf_geo_country ? decodeURIComponent(cookies.__cf_geo_country) : "";
|
|
207
|
+
if (cookieCountry) {
|
|
208
|
+
return {
|
|
209
|
+
country: cookieCountry,
|
|
210
|
+
regionCode: cookies.__cf_geo_region_code ? decodeURIComponent(cookies.__cf_geo_region_code) : "",
|
|
211
|
+
regionName: cookies.__cf_geo_region ? decodeURIComponent(cookies.__cf_geo_region) : "",
|
|
212
|
+
city: cookies.__cf_geo_city ? decodeURIComponent(cookies.__cf_geo_city) : "",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 3. Fallback: standard geo headers (Vercel, etc.)
|
|
217
|
+
const headers = ctx.headers ?? {};
|
|
218
|
+
return {
|
|
219
|
+
country: headers["cf-ipcountry"] ?? headers["x-vercel-ip-country"] ?? "",
|
|
220
|
+
regionCode: headers["cf-region"] ?? headers["x-vercel-ip-country-region"] ?? "",
|
|
221
|
+
regionName: "",
|
|
222
|
+
city: "",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
235
226
|
function matchesLocationRule(
|
|
236
227
|
loc: LocationRule,
|
|
237
|
-
|
|
238
|
-
regionCode: string,
|
|
239
|
-
country: string,
|
|
240
|
-
city: string,
|
|
228
|
+
geo: GeoData,
|
|
241
229
|
): boolean {
|
|
242
230
|
if (loc.country) {
|
|
243
|
-
const code =
|
|
244
|
-
if (code.toUpperCase() !== country.toUpperCase()) return false;
|
|
231
|
+
const code = resolveCountryCode(loc.country);
|
|
232
|
+
if (code.toUpperCase() !== geo.country.toUpperCase()) return false;
|
|
245
233
|
}
|
|
246
234
|
if (loc.regionCode) {
|
|
247
235
|
// Match against both the short code ("SP") and full name ("São Paulo")
|
|
248
236
|
// so rules authored against either format continue working.
|
|
249
237
|
const ruleVal = loc.regionCode.toLowerCase();
|
|
250
|
-
if (regionCode.toLowerCase() !== ruleVal && regionName.toLowerCase() !== ruleVal) return false;
|
|
238
|
+
if (geo.regionCode.toLowerCase() !== ruleVal && geo.regionName.toLowerCase() !== ruleVal) return false;
|
|
251
239
|
}
|
|
252
|
-
if (loc.city && loc.city.toLowerCase() !== city.toLowerCase()) return false;
|
|
240
|
+
if (loc.city && loc.city.toLowerCase() !== geo.city.toLowerCase()) return false;
|
|
253
241
|
return true;
|
|
254
242
|
}
|
|
255
243
|
|
|
256
244
|
function locationMatcher(rule: Record<string, unknown>, ctx: MatcherContext): boolean {
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
const regionCode = cookies.__cf_geo_region_code ? decodeURIComponent(cookies.__cf_geo_region_code) : "";
|
|
260
|
-
const country = cookies.__cf_geo_country ? decodeURIComponent(cookies.__cf_geo_country) : "";
|
|
261
|
-
const city = cookies.__cf_geo_city ? decodeURIComponent(cookies.__cf_geo_city) : "";
|
|
245
|
+
const geo = getGeoData(ctx);
|
|
246
|
+
if (!geo.country) return !((rule.includeLocations as unknown[] | undefined)?.length);
|
|
262
247
|
|
|
263
248
|
const includeLocations = rule.includeLocations as LocationRule[] | undefined;
|
|
264
249
|
const excludeLocations = rule.excludeLocations as LocationRule[] | undefined;
|
|
265
250
|
|
|
266
|
-
if (excludeLocations?.some((loc) => matchesLocationRule(loc,
|
|
251
|
+
if (excludeLocations?.some((loc) => matchesLocationRule(loc, geo))) {
|
|
267
252
|
return false;
|
|
268
253
|
}
|
|
269
254
|
if (includeLocations?.length) {
|
|
270
|
-
return includeLocations.some((loc) => matchesLocationRule(loc,
|
|
255
|
+
return includeLocations.some((loc) => matchesLocationRule(loc, geo));
|
|
271
256
|
}
|
|
272
257
|
return true;
|
|
273
258
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS country name → ISO 3166-1 alpha-2 code mapping.
|
|
3
|
+
*
|
|
4
|
+
* The CMS stores countries as full names ("Brasil", "United States").
|
|
5
|
+
* Cloudflare and other geo providers return ISO codes ("BR", "US").
|
|
6
|
+
* Keys are case-sensitive as authored in the CMS — callers should
|
|
7
|
+
* try both the raw value and a case-normalized lookup.
|
|
8
|
+
*/
|
|
9
|
+
export const COUNTRY_NAME_TO_CODE: Record<string, string> = {
|
|
10
|
+
// Latin America
|
|
11
|
+
Brasil: "BR", Brazil: "BR",
|
|
12
|
+
Argentina: "AR",
|
|
13
|
+
Chile: "CL",
|
|
14
|
+
Colombia: "CO",
|
|
15
|
+
Mexico: "MX", "México": "MX",
|
|
16
|
+
Peru: "PE", "Perú": "PE",
|
|
17
|
+
Uruguay: "UY",
|
|
18
|
+
Paraguay: "PY",
|
|
19
|
+
Bolivia: "BO",
|
|
20
|
+
Ecuador: "EC",
|
|
21
|
+
Venezuela: "VE",
|
|
22
|
+
"Costa Rica": "CR",
|
|
23
|
+
Panama: "PA", "Panamá": "PA",
|
|
24
|
+
"Dominican Republic": "DO",
|
|
25
|
+
Guatemala: "GT",
|
|
26
|
+
Honduras: "HN",
|
|
27
|
+
"El Salvador": "SV",
|
|
28
|
+
Nicaragua: "NI",
|
|
29
|
+
Cuba: "CU",
|
|
30
|
+
"Puerto Rico": "PR",
|
|
31
|
+
|
|
32
|
+
// North America
|
|
33
|
+
"United States": "US", USA: "US", "Estados Unidos": "US",
|
|
34
|
+
Canada: "CA", "Canadá": "CA",
|
|
35
|
+
|
|
36
|
+
// Europe
|
|
37
|
+
Spain: "ES", "España": "ES",
|
|
38
|
+
Portugal: "PT",
|
|
39
|
+
Germany: "DE", Alemania: "DE", Deutschland: "DE",
|
|
40
|
+
France: "FR", Francia: "FR",
|
|
41
|
+
Italy: "IT", Italia: "IT",
|
|
42
|
+
"United Kingdom": "GB", UK: "GB",
|
|
43
|
+
Netherlands: "NL",
|
|
44
|
+
Switzerland: "CH",
|
|
45
|
+
Sweden: "SE",
|
|
46
|
+
Norway: "NO",
|
|
47
|
+
Denmark: "DK",
|
|
48
|
+
Finland: "FI",
|
|
49
|
+
Belgium: "BE",
|
|
50
|
+
Austria: "AT",
|
|
51
|
+
Ireland: "IE",
|
|
52
|
+
Turkey: "TR", "Türkiye": "TR",
|
|
53
|
+
Poland: "PL",
|
|
54
|
+
"Czech Republic": "CZ", Czechia: "CZ",
|
|
55
|
+
Romania: "RO",
|
|
56
|
+
Hungary: "HU",
|
|
57
|
+
Greece: "GR",
|
|
58
|
+
Croatia: "HR",
|
|
59
|
+
|
|
60
|
+
// Asia & Oceania
|
|
61
|
+
Japan: "JP", "Japón": "JP",
|
|
62
|
+
China: "CN",
|
|
63
|
+
"South Korea": "KR",
|
|
64
|
+
India: "IN",
|
|
65
|
+
Australia: "AU",
|
|
66
|
+
"New Zealand": "NZ",
|
|
67
|
+
|
|
68
|
+
// Middle East & Africa
|
|
69
|
+
Israel: "IL",
|
|
70
|
+
"Saudi Arabia": "SA",
|
|
71
|
+
"United Arab Emirates": "AE",
|
|
72
|
+
"South Africa": "ZA",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a country name (as stored in CMS) to its ISO code.
|
|
77
|
+
* Tries exact match first, then case-insensitive lookup.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveCountryCode(name: string): string {
|
|
80
|
+
if (COUNTRY_NAME_TO_CODE[name]) return COUNTRY_NAME_TO_CODE[name];
|
|
81
|
+
|
|
82
|
+
const lower = name.toLowerCase();
|
|
83
|
+
for (const [key, code] of Object.entries(COUNTRY_NAME_TO_CODE)) {
|
|
84
|
+
if (key.toLowerCase() === lower) return code;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Assume it's already an ISO code
|
|
88
|
+
return name.toUpperCase();
|
|
89
|
+
}
|
package/src/routes/cmsRoute.ts
CHANGED
|
@@ -64,17 +64,19 @@ async function loadCmsPageInternal(fullPath: string) {
|
|
|
64
64
|
? new URL(fullPath, serverUrl.origin).toString()
|
|
65
65
|
: serverUrl.toString();
|
|
66
66
|
|
|
67
|
+
const originRequest = getRequest();
|
|
67
68
|
const matcherCtx: MatcherContext = {
|
|
68
69
|
userAgent: getRequestHeader("user-agent") ?? "",
|
|
69
70
|
url: urlWithSearch,
|
|
70
71
|
path: basePath,
|
|
71
72
|
cookies: getCookies(),
|
|
73
|
+
request: originRequest,
|
|
72
74
|
};
|
|
73
75
|
const page = await resolveDecoPage(basePath, matcherCtx);
|
|
74
76
|
if (!page) return null;
|
|
75
77
|
|
|
76
78
|
const request = new Request(urlWithSearch, {
|
|
77
|
-
headers:
|
|
79
|
+
headers: originRequest.headers,
|
|
78
80
|
});
|
|
79
81
|
const enrichedSections = await runSectionLoaders(page.resolvedSections, request);
|
|
80
82
|
|
|
@@ -113,16 +115,16 @@ export const loadCmsPage = createServerFn({ method: "GET" })
|
|
|
113
115
|
* Avoids passing data through the server function for the homepage.
|
|
114
116
|
*/
|
|
115
117
|
export const loadCmsHomePage = createServerFn({ method: "GET" }).handler(async () => {
|
|
118
|
+
const request = getRequest();
|
|
116
119
|
const matcherCtx: MatcherContext = {
|
|
117
120
|
userAgent: getRequestHeader("user-agent") ?? "",
|
|
118
121
|
url: getRequestUrl().toString(),
|
|
119
122
|
path: "/",
|
|
120
123
|
cookies: getCookies(),
|
|
124
|
+
request,
|
|
121
125
|
};
|
|
122
126
|
const page = await resolveDecoPage("/", matcherCtx);
|
|
123
127
|
if (!page) return null;
|
|
124
|
-
|
|
125
|
-
const request = getRequest();
|
|
126
128
|
const enrichedSections = await runSectionLoaders(page.resolvedSections, request);
|
|
127
129
|
|
|
128
130
|
const eagerKeys = enrichedSections.map((s) => s.component);
|
|
@@ -147,19 +149,21 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
|
|
|
147
149
|
.handler(async (ctx) => {
|
|
148
150
|
const { component, rawProps, pagePath, pageUrl } = ctx.data;
|
|
149
151
|
|
|
152
|
+
const originRequest = getRequest();
|
|
150
153
|
const serverUrl = getRequestUrl().toString();
|
|
151
154
|
const matcherCtx: MatcherContext = {
|
|
152
155
|
userAgent: getRequestHeader("user-agent") ?? "",
|
|
153
156
|
url: pageUrl || serverUrl,
|
|
154
157
|
path: pagePath,
|
|
155
158
|
cookies: getCookies(),
|
|
159
|
+
request: originRequest,
|
|
156
160
|
};
|
|
157
161
|
|
|
158
162
|
const section = await resolveDeferredSection(component, rawProps, pagePath, matcherCtx);
|
|
159
163
|
if (!section) return null;
|
|
160
164
|
|
|
161
165
|
const request = new Request(pageUrl || serverUrl, {
|
|
162
|
-
headers:
|
|
166
|
+
headers: originRequest.headers,
|
|
163
167
|
});
|
|
164
168
|
const enriched = await runSingleSectionLoader(section, request);
|
|
165
169
|
return normalizeUrlsInObject(enriched);
|