@decocms/blocks-cli 7.20.3 → 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.3",
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.3",
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
@@ -262,13 +267,13 @@ vite.config.timestamp_*
262
267
 
263
268
  # Deco CMS
264
269
  .deco/metadata/*
265
- # Generated snapshots — do NOT commit. Both are 100% derived (blocks.gen.json
266
- # merges .deco/blocks/*.json; meta.gen.json is built from the TS source) and are
267
- # regenerated by \`npm run generate\` (build) and on dev cold-start. blocks.gen.json
268
- # is a single 10MB+ line, so committing it makes every content PR conflict
269
- # irresolvably. The small .gen.ts stubs and generate.digests.json stay committed.
270
+ # The decofile snapshot only. blocks.gen.json is a single 10MB+ line (merge of
271
+ # .deco/blocks/*.json), so committing it makes every content PR conflict
272
+ # irresolvably; it is regenerated by \`npm run generate\` (build) and on dev
273
+ # cold-start. meta.gen.json (the schema) stays COMMITTED — it merges cleanly and
274
+ # keeps admin/Studio working on a fresh clone without a regen. The small .gen.ts
275
+ # stubs and generate.digests.json also stay committed.
270
276
  .deco/blocks.gen.json
271
- .deco/meta.gen.json
272
277
 
273
278
  # IDE
274
279
  .vscode/
@@ -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",
@@ -354,10 +355,10 @@ export const checks: Check[] = [
354
355
  "node_modules/",
355
356
  ".tanstack/",
356
357
  "src/routeTree.gen.ts",
357
- // Generated snapshots must be ignored — committing them causes
358
+ // The decofile snapshot must be ignored — its 10MB+ single line causes
358
359
  // constant PR merge conflicts (see generateGitignore in phase-scaffold).
360
+ // meta.gen.json stays committed on purpose, so it is NOT required here.
359
361
  ".deco/blocks.gen.json",
360
- ".deco/meta.gen.json",
361
362
  ];
362
363
  const missing = required.filter((r) => !content.includes(r));
363
364
  if (missing.length > 0) {
@@ -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
  }