@decocms/blocks-cli 7.20.4 → 7.20.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks-cli",
3
- "version": "7.20.4",
3
+ "version": "7.20.5",
4
4
  "type": "module",
5
5
  "description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "lint:unused": "knip"
31
31
  },
32
32
  "dependencies": {
33
- "@decocms/blocks": "7.20.4",
33
+ "@decocms/blocks": "7.20.5",
34
34
  "ts-morph": "^27.0.0",
35
35
  "tsx": "^4.22.5"
36
36
  },
@@ -14,6 +14,7 @@ import { generateAppCss } from "./templates/app-css";
14
14
  import { generateTypeFiles } from "./templates/types-gen";
15
15
  import { generateUiComponents } from "./templates/ui-components";
16
16
  import { generateHooks } from "./templates/hooks";
17
+ import { generateCommerceInit } from "./templates/commerce-init";
17
18
  import { generateCommerceLoaders } from "./templates/commerce-loaders";
18
19
  import { generateSectionLoaders } from "./templates/section-loaders";
19
20
  import { generateCacheConfig } from "./templates/cache-config";
@@ -110,6 +111,10 @@ export function scaffold(ctx: MigrationContext): void {
110
111
  writeFile(ctx, "src/setup.ts", generateSetup(ctx));
111
112
  writeFile(ctx, "src/cache-config.ts", generateCacheConfig(ctx));
112
113
  writeFile(ctx, "src/setup/commerce-loaders.ts", generateCommerceLoaders(ctx));
114
+ // Server-only registration of COMMERCE_LOADERS + invoke — imported by
115
+ // worker-entry.ts, not setup.ts, so the loader/action graph stays out of the
116
+ // client bundle.
117
+ writeFile(ctx, "src/setup/commerce-init.ts", generateCommerceInit(ctx));
113
118
  writeFile(ctx, "src/setup/section-loaders.ts", generateSectionLoaders(ctx));
114
119
 
115
120
  // Theme extraction + Styles
@@ -32,6 +32,7 @@ const REQUIRED_FILES = [
32
32
  "src/setup.ts",
33
33
  "src/cache-config.ts",
34
34
  "src/setup/commerce-loaders.ts",
35
+ "src/setup/commerce-init.ts",
35
36
  "src/setup/section-loaders.ts",
36
37
  "src/styles/app.css",
37
38
  "src/apps/site.ts",
@@ -417,6 +418,7 @@ export const checks: Check[] = [
417
418
  "src/setup.ts",
418
419
  "src/cache-config.ts",
419
420
  "src/setup/commerce-loaders.ts",
421
+ "src/setup/commerce-init.ts",
420
422
  "src/setup/section-loaders.ts",
421
423
  ];
422
424
  const missing = setupFiles.filter(
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { MigrationContext } from "../types";
3
+ import { createContext } from "../types";
4
+ import { generateCommerceInit } from "./commerce-init";
5
+ import { generateServerEntry } from "./server-entry";
6
+ import { generateSetup } from "./setup";
7
+
8
+ function makeCtx(platform: MigrationContext["platform"]): MigrationContext {
9
+ const ctx = createContext("/tmp/commerce-init-template-fixture-site");
10
+ ctx.siteName = "acme-storefront";
11
+ ctx.platform = platform;
12
+ ctx.vtexAccount = platform === "vtex" ? "acme" : null;
13
+ return ctx;
14
+ }
15
+
16
+ /**
17
+ * Regression guard for the site loader/action bundle-leak boundary.
18
+ *
19
+ * COMMERCE_LOADERS (and every site loader/action module it imports) must be
20
+ * registered in a server-only module imported by the worker entry, NEVER by
21
+ * setup.ts — which router.tsx imports into the client bundle. If the
22
+ * registration leaks back into setup.ts, the whole loader/action graph (and any
23
+ * credential hardcoded in it) ships to the browser assets again.
24
+ */
25
+ describe("server-only commerce/invoke registration boundary", () => {
26
+ for (const platform of ["vtex", "custom"] as const) {
27
+ describe(`platform: ${platform}`, () => {
28
+ const ctx = makeCtx(platform);
29
+ const commerceInit = generateCommerceInit(ctx);
30
+ const setup = generateSetup(ctx);
31
+ const serverFiles = generateServerEntry(ctx);
32
+
33
+ it("commerce-init registers COMMERCE_LOADERS + invoke server-side", () => {
34
+ expect(commerceInit).toContain(`import { COMMERCE_LOADERS } from "./commerce-loaders"`);
35
+ expect(commerceInit).toContain("registerCommerceLoaders(COMMERCE_LOADERS)");
36
+ expect(commerceInit).toContain("setInvokeLoaders(() => COMMERCE_LOADERS)");
37
+ });
38
+
39
+ it("setup.ts (client-imported) does NOT import or register COMMERCE_LOADERS", () => {
40
+ // The doc comment may reference COMMERCE_LOADERS; what must be absent is
41
+ // the actual import + the server-only registration calls.
42
+ expect(setup).not.toContain('from "./setup/commerce-loaders"');
43
+ expect(setup).not.toContain("registerCommerceLoaders(");
44
+ expect(setup).not.toContain("setInvokeLoaders(");
45
+ });
46
+
47
+ it("worker-entry imports commerce-init, router.tsx does not", () => {
48
+ expect(serverFiles["src/worker-entry.ts"]).toContain('import "./setup/commerce-init"');
49
+ expect(serverFiles["src/router.tsx"]).not.toContain("commerce-init");
50
+ // router.tsx still imports the client-safe setup for section registration.
51
+ expect(serverFiles["src/router.tsx"]).toContain('import "./setup"');
52
+ });
53
+ });
54
+ }
55
+ });
@@ -0,0 +1,42 @@
1
+ import type { MigrationContext } from "../types";
2
+
3
+ /**
4
+ * Server-only commerce + invoke registration.
5
+ *
6
+ * `commerce-loaders.ts` statically imports COMMERCE_LOADERS, which pulls in the
7
+ * site's loader/action modules and the platform commerce loaders. If that graph
8
+ * is reachable from the CLIENT entry (router.tsx -> setup.ts), Vite bundles all
9
+ * of it — and any credential hardcoded in a site loader/action — into the
10
+ * browser assets. So the registration lives here instead and is imported ONLY by
11
+ * the worker entry (server), never by router.tsx.
12
+ *
13
+ * This is safe because both consumers run server-side:
14
+ * - CMS commerce-loader resolution (loadCmsPage server fn), via
15
+ * registerCommerceLoaders.
16
+ * - the /deco/invoke handler, via setInvokeLoaders(() => COMMERCE_LOADERS)
17
+ * -> getRegisteredLoaders() inside handleInvoke.
18
+ *
19
+ * Client components reach loaders/actions exclusively through the HTTP `invoke`
20
+ * proxy (@decocms/blocks/sdk/invoke), which imports no modules.
21
+ */
22
+ export function generateCommerceInit(_ctx: MigrationContext): string {
23
+ return `/**
24
+ * Server-only commerce + invoke registration.
25
+ *
26
+ * Imported ONLY by the worker entry (src/worker-entry.ts), never by
27
+ * src/router.tsx. This keeps COMMERCE_LOADERS — and every site loader/action
28
+ * module it imports — out of the client bundle, so no server-side credential
29
+ * can leak into the browser assets. Both consumers run server-side:
30
+ * - CMS commerce-loader resolution (registerCommerceLoaders)
31
+ * - the /deco/invoke handler (setInvokeLoaders)
32
+ * The client reaches loaders/actions only via the HTTP invoke proxy.
33
+ */
34
+ import { registerCommerceLoaders } from "@decocms/blocks/cms";
35
+ import { setInvokeLoaders } from "@decocms/blocks-admin";
36
+
37
+ import { COMMERCE_LOADERS } from "./commerce-loaders";
38
+
39
+ registerCommerceLoaders(COMMERCE_LOADERS);
40
+ setInvokeLoaders(() => COMMERCE_LOADERS);
41
+ `;
42
+ }
@@ -5,6 +5,7 @@ import { generateRoutes } from "./routes";
5
5
  import { generateServerEntry } from "./server-entry";
6
6
  import { generateSetup } from "./setup";
7
7
  import { generateViteConfig } from "./vite-config";
8
+ import { generateCommerceInit } from "./commerce-init";
8
9
  import { generateCommerceLoaders } from "./commerce-loaders";
9
10
  import { generateSectionLoaders } from "./section-loaders";
10
11
  import { generateHooks } from "./hooks";
@@ -76,6 +77,10 @@ describe("scaffolder templates never emit @decocms/start or @decocms/apps/*", ()
76
77
  );
77
78
  });
78
79
 
80
+ it("commerce-init.ts", () => {
81
+ assertNoLegacyPackageNames("commerce-init.ts", generateCommerceInit(ctx));
82
+ });
83
+
79
84
  it("section-loaders.ts", () => {
80
85
  assertNoLegacyPackageNames(
81
86
  "section-loaders.ts",
@@ -65,6 +65,10 @@ function generateWorkerEntry(ctx: MigrationContext): string {
65
65
  * npx -p @decocms/blocks-cli deco-cf-observability --write
66
66
  */
67
67
  import "./setup";
68
+ // Server-only: registers COMMERCE_LOADERS + invoke. Kept out of ./setup (which
69
+ // router.tsx imports) so the loader/action module graph never enters the client
70
+ // bundle. See setup/commerce-init.ts.
71
+ import "./setup/commerce-init";
68
72
  import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
69
73
  import { createDecoWorkerEntry } from "@decocms/tanstack";
70
74
  import { instrumentWorker } from "@decocms/blocks/sdk/observability";
@@ -109,6 +113,10 @@ function generateVtexWorkerEntry(ctx: MigrationContext): string {
109
113
  const vtexAccount = ctx.vtexAccount || ctx.siteName;
110
114
 
111
115
  return `import "./setup";
116
+ // Server-only: registers COMMERCE_LOADERS + invoke. Kept out of ./setup (which
117
+ // router.tsx imports) so the loader/action module graph never enters the client
118
+ // bundle. See setup/commerce-init.ts.
119
+ import "./setup/commerce-init";
112
120
  import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
113
121
  import { createDecoWorkerEntry } from "@decocms/tanstack";
114
122
  import { instrumentWorker } from "@decocms/blocks/sdk/observability";
@@ -82,20 +82,23 @@ export function generateSetup(ctx: MigrationContext): string {
82
82
  *
83
83
  * Actual logic lives in focused modules:
84
84
  * setup/commerce-loaders.ts — COMMERCE_LOADERS map (VTEX + site data fetchers)
85
+ * setup/commerce-init.ts — server-only registration of COMMERCE_LOADERS +
86
+ * invoke. Imported by worker-entry.ts, NOT here,
87
+ * so the loader/action module graph (and any
88
+ * credential in it) never enters the client bundle.
85
89
  * setup/section-loaders.ts — registerSectionLoaders (per-section prop enrichment)
86
90
  *
91
+ * This file IS imported by router.tsx (client) for section registration/hydration,
92
+ * so it must stay free of server-only imports (COMMERCE_LOADERS, invoke, secrets).
93
+ *
87
94
  * Section metadata (eager, sync, layout, cache, LoadingFallback) is declared
88
95
  * in each section file and auto-extracted by generate-sections.ts.
89
96
  */
90
97
 
91
98
  import "./cache-config";
92
99
 
93
- import {
94
- registerCommerceLoaders,
95
- applySectionConventions,
96
- } from "@decocms/blocks/cms";
97
- import { createSiteSetup } from "@decocms/blocks/setup";
98
- import { setInvokeLoaders } from "@decocms/blocks-admin";${isVtex ? `
100
+ import { applySectionConventions } from "@decocms/blocks/cms";
101
+ import { createSiteSetup } from "@decocms/blocks/setup";${isVtex ? `
99
102
  import { createInstrumentedFetch } from "@decocms/blocks/sdk/instrumentedFetch";
100
103
  import { initVtexFromBlocks, setVtexFetch } from "@decocms/apps-vtex";` : ""}${hasLocationMatcher ? `
101
104
  import { registerLocationMatcher } from "./matchers/location";` : ""}
@@ -105,7 +108,6 @@ import { PreviewProviders } from "@decocms/tanstack";
105
108
  // @ts-ignore Vite ?url import
106
109
  import appCss from "./styles/app.css?url";
107
110
 
108
- import { COMMERCE_LOADERS } from "./setup/commerce-loaders";
109
111
  import "./setup/section-loaders";
110
112
 
111
113
  // -- Framework setup --
@@ -142,7 +144,8 @@ applySectionConventions({
142
144
  });
143
145
 
144
146
  // -- Commerce + invoke --
145
- registerCommerceLoaders(COMMERCE_LOADERS);
146
- setInvokeLoaders(() => COMMERCE_LOADERS);
147
+ // Registration lives in setup/commerce-init.ts, imported ONLY by
148
+ // src/worker-entry.ts (server). Importing it here would drag COMMERCE_LOADERS —
149
+ // and every site loader/action module it references — into the client bundle.
147
150
  `;
148
151
  }