@decocms/blocks 7.11.0 → 7.11.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 +4 -1
- package/src/cms/loader.test.ts +18 -0
- package/src/cms/loader.ts +16 -0
- package/src/cms/resolve.test.ts +41 -0
- package/src/cms/resolve.ts +15 -2
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks",
|
|
3
|
-
"version": "7.11.
|
|
3
|
+
"version": "7.11.2",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=24"
|
|
7
|
+
},
|
|
5
8
|
"description": "Deco framework-agnostic core: CMS block resolution, section registry, matchers, portable SDK utilities",
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
package/src/cms/loader.test.ts
CHANGED
|
@@ -101,6 +101,24 @@ describe("matchPath", () => {
|
|
|
101
101
|
expect(() => matchPath("/[invalid", "/anything")).not.toThrow();
|
|
102
102
|
expect(matchPath("/[invalid", "/anything")).toBeNull();
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
// Node <= 22 has no URLPattern global. The malformed-pattern try/catch
|
|
106
|
+
// above must NOT absorb that ReferenceError — a missing API has to fail
|
|
107
|
+
// loudly at first match, not degrade into every CMS page silently
|
|
108
|
+
// returning null (which renders as sitewide 404s).
|
|
109
|
+
it("throws a descriptive error when the runtime lacks URLPattern", () => {
|
|
110
|
+
const g = globalThis as { URLPattern?: unknown };
|
|
111
|
+
const saved = g.URLPattern;
|
|
112
|
+
// biome-ignore lint/performance/noDelete: restoring exact global state
|
|
113
|
+
delete g.URLPattern;
|
|
114
|
+
try {
|
|
115
|
+
expect(() => matchPath("/foo/:slug", "/foo/bar")).toThrow(
|
|
116
|
+
/URLPattern.*Node\.js >= 24/s,
|
|
117
|
+
);
|
|
118
|
+
} finally {
|
|
119
|
+
if (saved !== undefined) g.URLPattern = saved;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
104
122
|
});
|
|
105
123
|
});
|
|
106
124
|
|
package/src/cms/loader.ts
CHANGED
|
@@ -223,11 +223,27 @@ declare const URLPattern: {
|
|
|
223
223
|
*
|
|
224
224
|
* Malformed patterns return `null` instead of throwing — bad CMS data must
|
|
225
225
|
* never take down the worker.
|
|
226
|
+
*
|
|
227
|
+
* A MISSING `URLPattern` API, however, throws loudly and immediately: the
|
|
228
|
+
* try/catch below exists to absorb bad *patterns*, and letting it also
|
|
229
|
+
* swallow a `ReferenceError` from an old runtime turns "wrong Node version"
|
|
230
|
+
* into "every CMS page silently 404s" — the worst possible failure mode.
|
|
231
|
+
* `URLPattern` is native in browsers, workerd, Deno, and Node >= 24 (this
|
|
232
|
+
* package's `engines` floor). Node 22 and older lack it.
|
|
226
233
|
*/
|
|
227
234
|
export function matchPath(
|
|
228
235
|
pattern: string,
|
|
229
236
|
urlPath: string,
|
|
230
237
|
): Record<string, string> | null {
|
|
238
|
+
if (typeof URLPattern === "undefined") {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"@decocms/blocks: this runtime has no URLPattern Web API, so CMS page " +
|
|
241
|
+
"paths cannot be matched. URLPattern is native in browsers, " +
|
|
242
|
+
"Cloudflare workerd, Deno, and Node.js >= 24 — you are most likely " +
|
|
243
|
+
"running Node <= 22. Upgrade the runtime to Node 24+ (see this " +
|
|
244
|
+
'package\'s "engines" field).',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
231
247
|
let result: MatchPatternResult | null;
|
|
232
248
|
try {
|
|
233
249
|
result = new URLPattern({ pathname: pattern }).exec({ pathname: urlPath });
|
package/src/cms/resolve.test.ts
CHANGED
|
@@ -306,6 +306,47 @@ describe("commerce loader auto-injects URL search params as props", () => {
|
|
|
306
306
|
});
|
|
307
307
|
});
|
|
308
308
|
|
|
309
|
+
describe("commerce loader resolves legacy .ts-suffixed resolveType", () => {
|
|
310
|
+
beforeEach(() => clearCommerceLoaders());
|
|
311
|
+
afterEach(() => clearCommerceLoaders());
|
|
312
|
+
|
|
313
|
+
it("matches a manifest key (no extension) against a decofile .ts resolveType", async () => {
|
|
314
|
+
const calls: Array<Record<string, unknown>> = [];
|
|
315
|
+
// Split-package manifests register WITHOUT the file extension.
|
|
316
|
+
registerCommerceLoader("shopify/loaders/ProductDetailsPage", async (props) => {
|
|
317
|
+
calls.push({ ...props });
|
|
318
|
+
return null;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Legacy (Fresh/Deno) decofile references it WITH the .ts extension.
|
|
322
|
+
await resolveValue(
|
|
323
|
+
{ __resolveType: "shopify/loaders/ProductDetailsPage.ts", slug: "oversize-t-shirt-123" },
|
|
324
|
+
undefined,
|
|
325
|
+
{ url: "https://store.com/products/oversize-t-shirt-123", path: "/products/oversize-t-shirt-123" },
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(calls).toHaveLength(1);
|
|
329
|
+
expect(calls[0]).toMatchObject({ slug: "oversize-t-shirt-123" });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("prefers an exact .ts registration over the stripped fallback", async () => {
|
|
333
|
+
const hits: string[] = [];
|
|
334
|
+
registerCommerceLoader("shopify/loaders/X", async () => {
|
|
335
|
+
hits.push("plain");
|
|
336
|
+
return null;
|
|
337
|
+
});
|
|
338
|
+
registerCommerceLoader("shopify/loaders/X.ts", async () => {
|
|
339
|
+
hits.push("dotts");
|
|
340
|
+
return null;
|
|
341
|
+
});
|
|
342
|
+
await resolveValue({ __resolveType: "shopify/loaders/X.ts" }, undefined, {
|
|
343
|
+
url: "https://s.com/",
|
|
344
|
+
path: "/",
|
|
345
|
+
});
|
|
346
|
+
expect(hits).toEqual(["dotts"]);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
309
350
|
// ---------------------------------------------------------------------------
|
|
310
351
|
// Async rendering: the admin (CMS Lazy ⚡ toggle) is the source of truth
|
|
311
352
|
// ---------------------------------------------------------------------------
|
package/src/cms/resolve.ts
CHANGED
|
@@ -495,6 +495,19 @@ export function clearCommerceLoaders(): void {
|
|
|
495
495
|
for (const key of Object.keys(commerceLoaders)) delete commerceLoaders[key];
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Look up a commerce loader tolerant of a legacy `.ts`/`.tsx` resolveType
|
|
500
|
+
* suffix. Fresh/Deno decofiles reference app loaders with the file extension
|
|
501
|
+
* (`shopify/loaders/ProductDetailsPage.ts`), but the split-package app
|
|
502
|
+
* manifests register them WITHOUT it (`shopify/loaders/ProductDetailsPage`).
|
|
503
|
+
* Exact key wins (so an explicitly-registered `.ts` alias — e.g. a site's
|
|
504
|
+
* wrapped loader — still takes precedence); otherwise fall back to the
|
|
505
|
+
* extension-stripped key so existing content keeps resolving after a migration.
|
|
506
|
+
*/
|
|
507
|
+
export function getCommerceLoader(resolveType: string): CommerceLoader | undefined {
|
|
508
|
+
return commerceLoaders[resolveType] ?? commerceLoaders[resolveType.replace(/\.tsx?$/, "")];
|
|
509
|
+
}
|
|
510
|
+
|
|
498
511
|
// ---------------------------------------------------------------------------
|
|
499
512
|
// Custom matchers
|
|
500
513
|
// ---------------------------------------------------------------------------
|
|
@@ -832,7 +845,7 @@ async function internalResolve(value: unknown, rctx: ResolveContext): Promise<un
|
|
|
832
845
|
}
|
|
833
846
|
|
|
834
847
|
// Commerce loaders
|
|
835
|
-
const commerceLoader =
|
|
848
|
+
const commerceLoader = getCommerceLoader(resolveType);
|
|
836
849
|
if (commerceLoader) {
|
|
837
850
|
const { __resolveType: _, ...loaderProps } = obj;
|
|
838
851
|
const resolvedProps = await resolveProps(loaderProps, childCtx);
|
|
@@ -1186,7 +1199,7 @@ function resolvesToCommerceLoader(value: unknown, depth = 0): boolean {
|
|
|
1186
1199
|
const rt = obj.__resolveType as string | undefined;
|
|
1187
1200
|
if (!rt) return false;
|
|
1188
1201
|
|
|
1189
|
-
if (
|
|
1202
|
+
if (getCommerceLoader(rt)) return true;
|
|
1190
1203
|
|
|
1191
1204
|
if (rt === WELL_KNOWN_TYPES.LAZY) return resolvesToCommerceLoader(obj.section, depth + 1);
|
|
1192
1205
|
if (rt === WELL_KNOWN_TYPES.DEFERRED) {
|