@decocms/blocks-cli 7.5.0 → 7.5.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.
Files changed (37) hide show
  1. package/package.json +3 -3
  2. package/scripts/generate-invoke.ts +10 -0
  3. package/scripts/generate-schema.test.ts +4 -4
  4. package/scripts/generate-sections.test.ts +6 -6
  5. package/scripts/migrate/analyzers/loader-inventory.ts +1 -1
  6. package/scripts/migrate/phase-analyze.ts +3 -3
  7. package/scripts/migrate/phase-cleanup.ts +29 -23
  8. package/scripts/migrate/phase-report.ts +7 -7
  9. package/scripts/migrate/phase-scaffold.ts +4 -4
  10. package/scripts/migrate/phase-transform.ts +1 -1
  11. package/scripts/migrate/phase-verify.ts +6 -5
  12. package/scripts/migrate/post-cleanup/rules.ts +55 -44
  13. package/scripts/migrate/post-cleanup/runner.test.ts +58 -34
  14. package/scripts/migrate/templates/cache-config.ts +4 -4
  15. package/scripts/migrate/templates/commerce-loaders.ts +10 -10
  16. package/scripts/migrate/templates/cursor-rules.test.ts +6 -0
  17. package/scripts/migrate/templates/cursor-rules.ts +12 -11
  18. package/scripts/migrate/templates/hooks.test.ts +7 -7
  19. package/scripts/migrate/templates/hooks.ts +10 -10
  20. package/scripts/migrate/templates/lib-utils.test.ts +2 -2
  21. package/scripts/migrate/templates/lib-utils.ts +4 -4
  22. package/scripts/migrate/templates/no-legacy-packages.test.ts +147 -0
  23. package/scripts/migrate/templates/package-json.ts +24 -10
  24. package/scripts/migrate/templates/routes.ts +8 -10
  25. package/scripts/migrate/templates/sdk-gen.ts +1 -1
  26. package/scripts/migrate/templates/section-loaders.ts +7 -7
  27. package/scripts/migrate/templates/server-entry.ts +37 -37
  28. package/scripts/migrate/templates/setup.ts +6 -6
  29. package/scripts/migrate/templates/types-gen.ts +2 -2
  30. package/scripts/migrate/templates/ui-components.ts +2 -2
  31. package/scripts/migrate/templates/vite-config.ts +12 -3
  32. package/scripts/migrate/transforms/fresh-apis.ts +5 -5
  33. package/scripts/migrate/transforms/imports.ts +49 -43
  34. package/scripts/migrate/transforms/section-conventions.ts +1 -1
  35. package/scripts/migrate/types.ts +1 -1
  36. package/scripts/migrate-post-cleanup.ts +8 -8
  37. package/scripts/migrate.ts +11 -11
@@ -37,15 +37,15 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
37
37
  lines.push(` */`);
38
38
 
39
39
  if (ctx.platform === "vtex") {
40
- lines.push(`import { getVtexConfig } from "@decocms/apps/vtex";`);
41
- lines.push(`import { createVtexCommerceLoaders, createCachedPDPLoader } from "@decocms/apps/vtex/commerceLoaders";`);
40
+ lines.push(`import { getVtexConfig } from "@decocms/apps-vtex";`);
41
+ lines.push(`import { createVtexCommerceLoaders, createCachedPDPLoader } from "@decocms/apps-vtex/commerceLoaders";`);
42
42
  if (hasAutocomplete) {
43
- lines.push(`import { autocompleteSearch } from "@decocms/apps/vtex/loaders/autocomplete";`);
43
+ lines.push(`import { autocompleteSearch } from "@decocms/apps-vtex/loaders/autocomplete";`);
44
44
  }
45
- lines.push(`import { getAddressByPostalCode } from "@decocms/apps/vtex/loaders/address";`);
46
- lines.push(`import { createAddressFromRequest, updateAddressFromRequest, deleteAddressFromRequest } from "@decocms/apps/vtex/actions/address";`);
47
- lines.push(`import { updateProfileFromRequest, newsletterProfileFromRequest, deletePaymentFromRequest, getPasswordLastUpdate } from "@decocms/apps/vtex/actions/profile";`);
48
- lines.push(`import { createCachedLoader } from "@decocms/start/sdk/cachedLoader";`);
45
+ lines.push(`import { getAddressByPostalCode } from "@decocms/apps-vtex/loaders/address";`);
46
+ lines.push(`import { createAddressFromRequest, updateAddressFromRequest, deleteAddressFromRequest } from "@decocms/apps-vtex/actions/address";`);
47
+ lines.push(`import { updateProfileFromRequest, newsletterProfileFromRequest, deletePaymentFromRequest, getPasswordLastUpdate } from "@decocms/apps-vtex/actions/profile";`);
48
+ lines.push(`import { createCachedLoader } from "@decocms/blocks/sdk/cachedLoader";`);
49
49
 
50
50
  if (hasVtexAuth) {
51
51
  lines.push(`import vtexAuthLoader from "../loaders/vtex-auth-loader";`);
@@ -92,7 +92,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
92
92
 
93
93
  // Autocomplete aliases
94
94
  if (hasAutocomplete) {
95
- lines.push(` // Autocomplete search — from @decocms/apps`);
95
+ lines.push(` // Autocomplete search — from @decocms/apps-vtex`);
96
96
  lines.push(` "site/loaders/search/intelligenseSearch.ts": cachedAutocomplete,`);
97
97
  lines.push(` "site/loaders/search/intelligenseSearch": cachedAutocomplete,`);
98
98
  lines.push(``);
@@ -109,7 +109,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
109
109
  }
110
110
 
111
111
  // VTEX address CRUD
112
- lines.push(` // VTEX address CRUD — request-aware wrappers from @decocms/apps`);
112
+ lines.push(` // VTEX address CRUD — request-aware wrappers from @decocms/apps-vtex`);
113
113
  lines.push(` "vtex/actions/address/createAddress": createAddressFromRequest as any,`);
114
114
  lines.push(` "vtex/actions/address/updateAddress": updateAddressFromRequest as any,`);
115
115
  lines.push(` "vtex/actions/address/deleteAddress": deleteAddressFromRequest as any,`);
@@ -215,7 +215,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
215
215
 
216
216
  lines.push(``);
217
217
  // Profile actions
218
- lines.push(` // Profile actions — request-aware wrappers from @decocms/apps`);
218
+ lines.push(` // Profile actions — request-aware wrappers from @decocms/apps-vtex`);
219
219
  lines.push(` "vtex/actions/profile/updateProfile": updateProfileFromRequest as any,`);
220
220
  lines.push(` "vtex/actions/profile/updateProfile.ts": updateProfileFromRequest as any,`);
221
221
  lines.push(` "vtex/actions/profile/newsletterProfile": newsletterProfileFromRequest as any,`);
@@ -43,6 +43,12 @@ describe("generateMigrationPolicyPointerRule", () => {
43
43
  expect(body.length).toBeLessThan(3000);
44
44
  });
45
45
 
46
+ it("does not emit the pre-split @decocms/start or @decocms/apps package names", () => {
47
+ expect(body).not.toContain("@decocms/start");
48
+ expect(body).not.toContain("@decocms/apps\`");
49
+ expect(body).toContain("@decocms/blocks-cli");
50
+ });
51
+
46
52
  it("is deterministic — same site name, same output", () => {
47
53
  const a = generateMigrationPolicyPointerRule("foo");
48
54
  const b = generateMigrationPolicyPointerRule("foo");
@@ -26,10 +26,11 @@ alwaysApply: true
26
26
 
27
27
  # Migration Tooling Policy — Pointer
28
28
 
29
- > This site (\`${siteName}\`) was generated by the \`@decocms/start\`
30
- > migration script. The canonical policy that governs how the migration
31
- > tooling, framework (\`@decocms/start\`), and commerce layer
32
- > (\`@decocms/apps\`) evolve lives **upstream**, not in this repo.
29
+ > This site (\`${siteName}\`) was generated by the \`@decocms/blocks-cli\`
30
+ > migration script (\`deco-migrate\`). The canonical policy that governs how
31
+ > the migration tooling, framework (\`@decocms/blocks\` + \`@decocms/tanstack\`),
32
+ > and commerce layer (\`@decocms/apps-*\`) evolve lives **upstream**, not in
33
+ > this repo.
33
34
 
34
35
  ## Where to read
35
36
 
@@ -38,24 +39,24 @@ alwaysApply: true
38
39
  - **Plan (living tracker, decisions + waves):**
39
40
  https://github.com/decocms/deco-start/blob/main/MIGRATION_TOOLING_PLAN.md
40
41
  - **Migration skill (phase playbook):**
41
- \`@decocms/start/.agents/skills/deco-to-tanstack-migration\`
42
+ https://github.com/decocms/deco-start/blob/main/.agents/skills/deco-to-tanstack-migration/SKILL.md
42
43
 
43
44
  ## What you need to know in this site
44
45
 
45
46
  | ID | Decision | What it means here |
46
47
  |----|----------|--------------------|
47
- | **D1** | Force convergence — no fork runtime support | Site customisations live in \`src/apps/local/\` or open a PR to \`@decocms/apps\`. Don't wrap framework/commerce code in soft adapters. |
48
+ | **D1** | Force convergence — no fork runtime support | Site customisations live in \`src/apps/local/\` or open a PR to \`@decocms/apps-*\`. Don't wrap framework/commerce code in soft adapters. |
48
49
  | **D2** | Rewrite HTMX on migration | If you find HTMX residue, rewrite to React. Don't bring back \`hx-*\` runtime. |
49
- | **D3** | Generated stubs throw at runtime | If a \`~/lib/vtex-*\` import comes from a stub that returns \`null\` / \`{}\` / identity-cast, replace it. \`npx -p @decocms/start deco-post-cleanup --fix\` does the safe swaps automatically. |
50
- | **D4** | Site-local apps by default, promote at 3+ sites | Don't try to upstream a pattern that has only shipped here. Build it twice in different sites first, then PR it to \`@decocms/apps\`. |
50
+ | **D3** | Generated stubs throw at runtime | If a \`~/lib/vtex-*\` import comes from a stub that returns \`null\` / \`{}\` / identity-cast, replace it. \`npx -p @decocms/blocks-cli deco-post-cleanup --fix\` does the safe swaps automatically. |
51
+ | **D4** | Site-local apps by default, promote at 3+ sites | Don't try to upstream a pattern that has only shipped here. Build it twice in different sites first, then PR it to \`@decocms/apps-*\`. |
51
52
  | **D5** | Failed migrations: \`rm -rf\` and re-run | No restart-mode magic. If the migration goes sideways, blow away the working tree and run again. |
52
53
 
53
54
  ## How to find issues this rule wants you to fix
54
55
 
55
56
  \`\`\`bash
56
- npx -p @decocms/start deco-post-cleanup # audit only
57
- npx -p @decocms/start deco-post-cleanup --fix # auto-fix the safe rules
58
- npx -p @decocms/start deco-post-cleanup --strict # exit 1 on any finding (CI)
57
+ npx -p @decocms/blocks-cli deco-post-cleanup # audit only
58
+ npx -p @decocms/blocks-cli deco-post-cleanup --fix # auto-fix the safe rules
59
+ npx -p @decocms/blocks-cli deco-post-cleanup --strict # exit 1 on any finding (CI)
59
60
  \`\`\`
60
61
 
61
62
  ## Process
@@ -44,12 +44,12 @@ describe("generateHooks (vtex)", () => {
44
44
  const code = files["src/hooks/useCart.ts"];
45
45
  // Imports from the framework factory.
46
46
  expect(code).toContain(
47
- 'import { createUseCart } from "@decocms/apps/vtex/hooks/createUseCart"',
47
+ 'import { createUseCart } from "@decocms/apps-vtex/hooks/createUseCart"',
48
48
  );
49
49
  expect(code).toContain('import { invoke } from "~/server/invoke"');
50
- // Re-exports types from @decocms/apps directly.
50
+ // Re-exports types from @decocms/apps-vtex directly.
51
51
  expect(code).toContain(
52
- 'export type { OrderForm, OrderFormItem } from "@decocms/apps/vtex/types"',
52
+ 'export type { OrderForm, OrderFormItem } from "@decocms/apps-vtex/types"',
53
53
  );
54
54
  // Calls the factory with invoke and destructures the public API.
55
55
  expect(code).toContain(
@@ -70,11 +70,11 @@ describe("generateHooks (vtex)", () => {
70
70
  it("useUser is the createUseUser factory shim (no signal-stub boilerplate)", () => {
71
71
  const code = files["src/hooks/useUser.ts"];
72
72
  expect(code).toContain(
73
- 'import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser"',
73
+ 'import { createUseUser } from "@decocms/apps-vtex/hooks/createUseUser"',
74
74
  );
75
75
  expect(code).toContain('import { invoke } from "~/server/invoke"');
76
76
  expect(code).toContain(
77
- 'export type { Person } from "@decocms/apps/vtex/loaders/user"',
77
+ 'export type { Person } from "@decocms/apps-vtex/loaders/user"',
78
78
  );
79
79
  expect(code).toContain("export const { useUser, resetUser } = createUseUser");
80
80
  // Must NOT scaffold the legacy signal stub.
@@ -85,11 +85,11 @@ describe("generateHooks (vtex)", () => {
85
85
  it("useWishlist is the createUseWishlist factory shim", () => {
86
86
  const code = files["src/hooks/useWishlist.ts"];
87
87
  expect(code).toContain(
88
- 'import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist"',
88
+ 'import { createUseWishlist } from "@decocms/apps-vtex/hooks/createUseWishlist"',
89
89
  );
90
90
  expect(code).toContain('import { invoke } from "~/server/invoke"');
91
91
  expect(code).toContain(
92
- 'export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist"',
92
+ 'export type { WishlistItem } from "@decocms/apps-vtex/loaders/wishlist"',
93
93
  );
94
94
  expect(code).toContain(
95
95
  "export const { useWishlist, resetWishlist } = createUseWishlist",
@@ -19,11 +19,11 @@ export function generateHooks(ctx: MigrationContext): Record<string, string> {
19
19
  function generateVtexUseCart(): string {
20
20
  // The legacy invoke-based useCart hook is now a 5-line factory call —
21
21
  // the heavy lifting (singleton state, listener pattern, async actions,
22
- // analytics helpers) lives in @decocms/apps/vtex/hooks/createUseCart.
23
- return `import { createUseCart } from "@decocms/apps/vtex/hooks/createUseCart";
22
+ // analytics helpers) lives in @decocms/apps-vtex/hooks/createUseCart.
23
+ return `import { createUseCart } from "@decocms/apps-vtex/hooks/createUseCart";
24
24
  import { invoke } from "~/server/invoke";
25
25
 
26
- export type { OrderForm, OrderFormItem } from "@decocms/apps/vtex/types";
26
+ export type { OrderForm, OrderFormItem } from "@decocms/apps-vtex/types";
27
27
 
28
28
  export const { useCart, resetCart, itemToAnalyticsItem } = createUseCart({
29
29
  invoke,
@@ -70,22 +70,22 @@ export default useCart;
70
70
  // VTEX path — these are five-line factory shims. The heavy lifting
71
71
  // (singleton state, listener pattern, async actions, signal-shaped
72
72
  // accessors, legacy arg-swap conventions) lives in
73
- // @decocms/apps/vtex/hooks/createUseUser and createUseWishlist.
73
+ // @decocms/apps-vtex/hooks/createUseUser and createUseWishlist.
74
74
  function generateVtexUseUser(): string {
75
- return `import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser";
75
+ return `import { createUseUser } from "@decocms/apps-vtex/hooks/createUseUser";
76
76
  import { invoke } from "~/server/invoke";
77
77
 
78
- export type { Person } from "@decocms/apps/vtex/loaders/user";
78
+ export type { Person } from "@decocms/apps-vtex/loaders/user";
79
79
 
80
80
  export const { useUser, resetUser } = createUseUser({ invoke });
81
81
  `;
82
82
  }
83
83
 
84
84
  function generateVtexUseWishlist(): string {
85
- return `import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist";
85
+ return `import { createUseWishlist } from "@decocms/apps-vtex/hooks/createUseWishlist";
86
86
  import { invoke } from "~/server/invoke";
87
87
 
88
- export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist";
88
+ export type { WishlistItem } from "@decocms/apps-vtex/loaders/wishlist";
89
89
 
90
90
  export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
91
91
  `;
@@ -98,7 +98,7 @@ function generateGenericUseUser(): string {
98
98
  return `/**
99
99
  * User Hook — wire to invoke.site.loaders for your platform's user API.
100
100
  *
101
- * VTEX sites get a real factory from @decocms/apps/vtex/hooks/createUseUser;
101
+ * VTEX sites get a real factory from @decocms/apps-vtex/hooks/createUseUser;
102
102
  * see migration template hooks.ts for the canonical five-line shim.
103
103
  */
104
104
  import { signal } from "~/sdk/signal";
@@ -125,7 +125,7 @@ function generateGenericUseWishlist(): string {
125
125
  return `/**
126
126
  * Wishlist Hook — wire to invoke.site.loaders/actions for your platform.
127
127
  *
128
- * VTEX sites get a real factory from @decocms/apps/vtex/hooks/createUseWishlist;
128
+ * VTEX sites get a real factory from @decocms/apps-vtex/hooks/createUseWishlist;
129
129
  * see migration template hooks.ts for the canonical five-line shim.
130
130
  */
131
131
  import { signal } from "~/sdk/signal";
@@ -99,7 +99,7 @@ describe("D3 — generated stubs throw at runtime", () => {
99
99
  it("vtex-transform.toProduct throws and points at the canonical path", () => {
100
100
  const src = LIB_TEMPLATES["src/lib/vtex-transform.ts"];
101
101
  expect(src).toMatch(/throw new Error/);
102
- expect(src).toMatch(/@decocms\/apps\/vtex\/utils\/transform/);
102
+ expect(src).toMatch(/@decocms\/apps-vtex\/utils\/transform/);
103
103
  expect(src).toMatch(/\[deco-migrate\]/);
104
104
  });
105
105
 
@@ -117,7 +117,7 @@ describe("D3 — generated stubs throw at runtime", () => {
117
117
  const src = LIB_TEMPLATES["src/lib/vtex-segment.ts"];
118
118
  expect(src).toMatch(/getSegmentFromBag[\s\S]*?throw new Error/);
119
119
  expect(src).toMatch(/withSegmentCookie[\s\S]*?throw new Error/);
120
- expect(src).toMatch(/@decocms\/apps\/vtex\/utils\/segment/);
120
+ expect(src).toMatch(/@decocms\/apps-vtex\/utils\/segment/);
121
121
  });
122
122
 
123
123
  it("non-stub helpers stay implemented (negative check — no throw)", () => {
@@ -46,11 +46,11 @@ export function selectImportedLibTemplates(
46
46
  //
47
47
  // Each thrown message points at the canonical replacement so the fix
48
48
  // is mechanical. `deco-post-cleanup --fix` automates the swap.
49
- const LIB_VTEX_TRANSFORM = `import type { Product } from "@decocms/apps/commerce/types";
49
+ const LIB_VTEX_TRANSFORM = `import type { Product } from "@decocms/apps-commerce/types";
50
50
 
51
51
  const STUB =
52
52
  "[deco-migrate] \`~/lib/vtex-transform.toProduct\` is a generated stub. " +
53
- "Replace with: import { toProduct } from '@decocms/apps/vtex/utils/transform' " +
53
+ "Replace with: import { toProduct } from '@decocms/apps-vtex/utils/transform' " +
54
54
  "(canonical signature: \`toProduct(product, sku, level, options)\`). " +
55
55
  "Run \`deco-post-cleanup --fix\` or see the deco-to-tanstack-migration skill " +
56
56
  "(post-migration-cleanup § 5).";
@@ -119,13 +119,13 @@ const LIB_VTEX_SEGMENT = `// Per the migration tooling policy (D3): both these s
119
119
  const STUB_GET_SEGMENT_FROM_BAG =
120
120
  "[deco-migrate] \`~/lib/vtex-segment.getSegmentFromBag\` is a generated " +
121
121
  "stub. Refactor: read cookies via \`request.headers.get('cookie')\` then " +
122
- "call \`buildSegmentFromCookies()\` from '@decocms/apps/vtex/utils/segment'. " +
122
+ "call \`buildSegmentFromCookies()\` from '@decocms/apps-vtex/utils/segment'. " +
123
123
  "The bag-based lookup mechanism does not exist on TanStack Start.";
124
124
 
125
125
  const STUB_WITH_SEGMENT_COOKIE =
126
126
  "[deco-migrate] \`~/lib/vtex-segment.withSegmentCookie\` is a generated " +
127
127
  "stub. Replace with: import { withSegmentCookie } from " +
128
- "'@decocms/apps/vtex/utils/segment' (canonical signature: " +
128
+ "'@decocms/apps-vtex/utils/segment' (canonical signature: " +
129
129
  "\`withSegmentCookie(segment, headers?)\`). Run \`deco-post-cleanup --fix\` " +
130
130
  "or see the deco-to-tanstack-migration skill.";
131
131
 
@@ -0,0 +1,147 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createContext } from "../types";
3
+ import type { MigrationContext } from "../types";
4
+ import { generateRoutes } from "./routes";
5
+ import { generateServerEntry } from "./server-entry";
6
+ import { generateSetup } from "./setup";
7
+ import { generateViteConfig } from "./vite-config";
8
+ import { generateCommerceLoaders } from "./commerce-loaders";
9
+ import { generateSectionLoaders } from "./section-loaders";
10
+ import { generateHooks } from "./hooks";
11
+ import { generateUiComponents } from "./ui-components";
12
+ import { generateTypeFiles } from "./types-gen";
13
+ import { generateCacheConfig } from "./cache-config";
14
+ import { generateSdkFiles } from "./sdk-gen";
15
+ import { generatePackageJson } from "./package-json";
16
+ import { generateMigrationPolicyPointerRule } from "./cursor-rules";
17
+
18
+ /**
19
+ * Fleet-wide regression guard: none of the scaffolder's templates may emit
20
+ * the pre-7.x-split `@decocms/start` package, or a bare `@decocms/apps/`
21
+ * monolith subpath import — neither exists for 7.x consumers, so any
22
+ * occurrence here means a freshly scaffolded site ships broken code.
23
+ *
24
+ * `@decocms/apps-<platform>` (the current split packages) are fine and
25
+ * intentionally NOT flagged by this check.
26
+ */
27
+ function assertNoLegacyPackageNames(label: string, output: string) {
28
+ expect(output, `${label} must not emit "@decocms/start"`).not.toContain(
29
+ "@decocms/start",
30
+ );
31
+ expect(
32
+ output,
33
+ `${label} must not emit the "@decocms/apps/" monolith subpath`,
34
+ ).not.toMatch(/@decocms\/apps\//);
35
+ }
36
+
37
+ function makeCtx(platform: MigrationContext["platform"]): MigrationContext {
38
+ const ctx = createContext("/tmp/no-legacy-packages-fixture-site");
39
+ ctx.siteName = "acme-storefront";
40
+ ctx.platform = platform;
41
+ ctx.vtexAccount = platform === "vtex" ? "acme" : null;
42
+ return ctx;
43
+ }
44
+
45
+ describe("scaffolder templates never emit @decocms/start or @decocms/apps/*", () => {
46
+ for (const platform of ["vtex", "custom"] as const) {
47
+ describe(`platform: ${platform}`, () => {
48
+ const ctx = makeCtx(platform);
49
+
50
+ it("routes.ts", () => {
51
+ const files = generateRoutes(ctx);
52
+ for (const [path, content] of Object.entries(files)) {
53
+ assertNoLegacyPackageNames(`routes.ts (${path})`, content);
54
+ }
55
+ });
56
+
57
+ it("server-entry.ts", () => {
58
+ const files = generateServerEntry(ctx);
59
+ for (const [path, content] of Object.entries(files)) {
60
+ assertNoLegacyPackageNames(`server-entry.ts (${path})`, content);
61
+ }
62
+ });
63
+
64
+ it("setup.ts", () => {
65
+ assertNoLegacyPackageNames("setup.ts", generateSetup(ctx));
66
+ });
67
+
68
+ it("vite-config.ts", () => {
69
+ assertNoLegacyPackageNames("vite-config.ts", generateViteConfig(ctx));
70
+ });
71
+
72
+ it("commerce-loaders.ts", () => {
73
+ assertNoLegacyPackageNames(
74
+ "commerce-loaders.ts",
75
+ generateCommerceLoaders(ctx),
76
+ );
77
+ });
78
+
79
+ it("section-loaders.ts", () => {
80
+ assertNoLegacyPackageNames(
81
+ "section-loaders.ts",
82
+ generateSectionLoaders(ctx),
83
+ );
84
+ });
85
+
86
+ it("hooks.ts", () => {
87
+ const files = generateHooks(ctx);
88
+ for (const [path, content] of Object.entries(files)) {
89
+ assertNoLegacyPackageNames(`hooks.ts (${path})`, content);
90
+ }
91
+ });
92
+
93
+ it("types-gen.ts", () => {
94
+ const files = generateTypeFiles(ctx);
95
+ for (const [path, content] of Object.entries(files)) {
96
+ assertNoLegacyPackageNames(`types-gen.ts (${path})`, content);
97
+ }
98
+ });
99
+
100
+ it("cache-config.ts", () => {
101
+ assertNoLegacyPackageNames(
102
+ "cache-config.ts",
103
+ generateCacheConfig(ctx),
104
+ );
105
+ });
106
+
107
+ it("sdk-gen.ts", () => {
108
+ const files = generateSdkFiles(ctx);
109
+ for (const [path, content] of Object.entries(files)) {
110
+ assertNoLegacyPackageNames(`sdk-gen.ts (${path})`, content);
111
+ }
112
+ });
113
+ });
114
+ }
115
+
116
+ // Platform-agnostic templates
117
+ it("ui-components.ts", () => {
118
+ const ctx = makeCtx("custom");
119
+ const files = generateUiComponents(ctx);
120
+ for (const [path, content] of Object.entries(files)) {
121
+ assertNoLegacyPackageNames(`ui-components.ts (${path})`, content);
122
+ }
123
+ });
124
+
125
+ it("cursor-rules.ts", () => {
126
+ assertNoLegacyPackageNames(
127
+ "cursor-rules.ts",
128
+ generateMigrationPolicyPointerRule("acme-storefront"),
129
+ );
130
+ });
131
+
132
+ // package-json.ts shells out to `npm view` to resolve the latest published
133
+ // version, with a hardcoded fallback if that fails (offline/CI-sandboxed).
134
+ // Either way the emitted dependency *names* must never be the retired
135
+ // @decocms/start / @decocms/apps monolith.
136
+ it("package-json.ts", () => {
137
+ const ctx = makeCtx("vtex");
138
+ const pkg = generatePackageJson(ctx);
139
+ assertNoLegacyPackageNames("package-json.ts", pkg);
140
+ const parsed = JSON.parse(pkg);
141
+ expect(parsed.dependencies).not.toHaveProperty("@decocms/start");
142
+ expect(parsed.dependencies).not.toHaveProperty("@decocms/apps");
143
+ expect(parsed.dependencies).toHaveProperty("@decocms/blocks");
144
+ expect(parsed.dependencies).toHaveProperty("@decocms/tanstack");
145
+ expect(parsed.dependencies).toHaveProperty("@decocms/apps-vtex");
146
+ }, 20000);
147
+ });
@@ -95,9 +95,19 @@ export function generatePackageJson(ctx: MigrationContext): string {
95
95
 
96
96
  const siteDeps = extractedDeps;
97
97
 
98
- // Fetch latest versions from npm registry
99
- const startVersion = getLatestVersion("@decocms/start", "0.34.0");
100
- const appsVersion = getLatestVersion("@decocms/apps", "0.27.0");
98
+ // @decocms/blocks, @decocms/blocks-admin, @decocms/blocks-cli,
99
+ // @decocms/tanstack, and the @decocms/apps-* platform packages are
100
+ // published in lockstep from the same monorepo release, so a single
101
+ // version lookup covers all of them (mirrors how consumer sites bump
102
+ // "@decocms/blocks/blocks-admin/nextjs ranges" together).
103
+ const frameworkVersion = getLatestVersion("@decocms/blocks", "7.5.1");
104
+
105
+ const platformPackage: Partial<Record<typeof ctx.platform, string>> = {
106
+ vtex: "@decocms/apps-vtex",
107
+ shopify: "@decocms/apps-shopify",
108
+ magento: "@decocms/apps-magento",
109
+ };
110
+ const platformDep = platformPackage[ctx.platform];
101
111
 
102
112
  const pkg = {
103
113
  name: ctx.siteName,
@@ -109,14 +119,14 @@ export function generatePackageJson(ctx: MigrationContext): string {
109
119
  "dev:clean":
110
120
  "rm -rf node_modules/.vite .wrangler/state .tanstack && vite dev",
111
121
  "generate:blocks":
112
- "tsx node_modules/@decocms/start/scripts/generate-blocks.ts",
122
+ "tsx node_modules/@decocms/blocks-cli/scripts/generate-blocks.ts",
113
123
  "generate:routes": "tsr generate",
114
- "generate:schema": `tsx node_modules/@decocms/start/scripts/generate-schema.ts --site ${ctx.siteName}`,
124
+ "generate:schema": `tsx node_modules/@decocms/blocks-cli/scripts/generate-schema.ts --site ${ctx.siteName}`,
115
125
  "generate:invoke":
116
- "tsx node_modules/@decocms/start/scripts/generate-invoke.ts",
126
+ "tsx node_modules/@decocms/blocks-cli/scripts/generate-invoke.ts",
117
127
  "generate:sections":
118
- "tsx node_modules/@decocms/start/scripts/generate-sections.ts",
119
- "generate:loaders": `tsx node_modules/@decocms/start/scripts/generate-loaders.ts --exclude vtex/loaders,vtex/actions,loaders/vtex-auth-loader,loaders/reviews/productReviews,loaders/product/buyTogether,loaders/search/productListPageCollection,loaders/search/intelligenseSearch,loaders/Layouts/ProductCard`,
128
+ "tsx node_modules/@decocms/blocks-cli/scripts/generate-sections.ts",
129
+ "generate:loaders": `tsx node_modules/@decocms/blocks-cli/scripts/generate-loaders.ts --exclude vtex/loaders,vtex/actions,loaders/vtex-auth-loader,loaders/reviews/productReviews,loaders/product/buyTogether,loaders/search/productListPageCollection,loaders/search/intelligenseSearch,loaders/Layouts/ProductCard`,
120
130
  build:
121
131
  "npm run generate:blocks && npm run generate:sections && npm run generate:loaders && npm run generate:schema && npm run generate:invoke && tsr generate && vite build",
122
132
  preview: "vite preview",
@@ -141,8 +151,11 @@ export function generatePackageJson(ctx: MigrationContext): string {
141
151
  license: "MIT",
142
152
  packageManager: `bun@${CANONICAL_BUN_VERSION}`,
143
153
  dependencies: {
144
- "@decocms/apps": `^${appsVersion}`,
145
- "@decocms/start": `^${startVersion}`,
154
+ "@decocms/blocks": `^${frameworkVersion}`,
155
+ "@decocms/blocks-admin": `^${frameworkVersion}`,
156
+ "@decocms/tanstack": `^${frameworkVersion}`,
157
+ "@decocms/apps-commerce": `^${frameworkVersion}`,
158
+ ...(platformDep ? { [platformDep]: `^${frameworkVersion}` } : {}),
146
159
  "@tanstack/react-query": "5.90.21",
147
160
  "@tanstack/react-router": "1.166.7",
148
161
  "@tanstack/react-start": "1.166.8",
@@ -155,6 +168,7 @@ export function generatePackageJson(ctx: MigrationContext): string {
155
168
  },
156
169
  devDependencies: {
157
170
  "@cloudflare/vite-plugin": "^1.27.0",
171
+ "@decocms/blocks-cli": `^${frameworkVersion}`,
158
172
  "@tailwindcss/vite": "^4.2.1",
159
173
  "@tanstack/router-cli": "1.166.7",
160
174
  "@types/react": "^19.2.14",
@@ -63,7 +63,7 @@ function generateRoot(ctx: MigrationContext, siteTitle: string, vtexAccount: str
63
63
  }
64
64
 
65
65
  return `import { createRootRoute } from "@tanstack/react-router";
66
- import { DecoRootLayout } from "@decocms/start/hooks";
66
+ import { DecoRootLayout } from "@decocms/tanstack";
67
67
  // @ts-ignore Vite ?url import
68
68
  import appCss from "../styles/app.css?url";
69
69
 
@@ -107,8 +107,7 @@ function RootLayout() {
107
107
 
108
108
  function generateIndex(ctx: MigrationContext, siteTitle: string): string {
109
109
  return `import { createFileRoute } from "@tanstack/react-router";
110
- import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
111
- import { DecoPageRenderer } from "@decocms/start/hooks";
110
+ import { cmsHomeRouteConfig, DecoPageRenderer, loadDeferredSection } from "@decocms/tanstack";
112
111
 
113
112
  // MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
114
113
  // copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
@@ -144,7 +143,7 @@ function HomePage() {
144
143
  deferredPromises={data.deferredPromises}
145
144
  pagePath={data.pagePath}
146
145
  pageUrl={data.pageUrl}
147
- loadDeferredSectionFn={deferredSectionLoader}
146
+ loadDeferredSectionFn={loadDeferredSection}
148
147
  />
149
148
  );
150
149
  }
@@ -153,8 +152,7 @@ function HomePage() {
153
152
 
154
153
  function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
155
154
  return `import { createFileRoute } from "@tanstack/react-router";
156
- import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
157
- import { DecoPageRenderer } from "@decocms/start/hooks";
155
+ import { cmsRouteConfig, DecoPageRenderer, loadDeferredSection } from "@decocms/tanstack";
158
156
 
159
157
  // MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
160
158
  // (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
@@ -182,7 +180,7 @@ function CmsPage() {
182
180
  deferredPromises={data.deferredPromises}
183
181
  pagePath={data.pagePath}
184
182
  pageUrl={data.pageUrl}
185
- loadDeferredSectionFn={deferredSectionLoader}
183
+ loadDeferredSectionFn={loadDeferredSection}
186
184
  />
187
185
  );
188
186
  }
@@ -214,7 +212,7 @@ function NotFoundPage() {
214
212
 
215
213
  function generateDecoMeta(): string {
216
214
  return `import { createFileRoute } from "@tanstack/react-router";
217
- import { decoMetaRoute } from "@decocms/start/routes";
215
+ import { decoMetaRoute } from "@decocms/tanstack";
218
216
 
219
217
  export const Route = createFileRoute("/deco/meta")(decoMetaRoute);
220
218
  `;
@@ -222,7 +220,7 @@ export const Route = createFileRoute("/deco/meta")(decoMetaRoute);
222
220
 
223
221
  function generateDecoInvoke(): string {
224
222
  return `import { createFileRoute } from "@tanstack/react-router";
225
- import { decoInvokeRoute } from "@decocms/start/routes";
223
+ import { decoInvokeRoute } from "@decocms/tanstack";
226
224
 
227
225
  export const Route = createFileRoute("/deco/invoke/$")(decoInvokeRoute);
228
226
  `;
@@ -230,7 +228,7 @@ export const Route = createFileRoute("/deco/invoke/$")(decoInvokeRoute);
230
228
 
231
229
  function generateDecoRender(): string {
232
230
  return `import { createFileRoute } from "@tanstack/react-router";
233
- import { decoRenderRoute } from "@decocms/start/routes";
231
+ import { decoRenderRoute } from "@decocms/tanstack";
234
232
 
235
233
  export const Route = createFileRoute("/deco/render")(decoRenderRoute);
236
234
  `;
@@ -12,7 +12,7 @@ export function generateSdkFiles(ctx: MigrationContext): Record<string, string>
12
12
  function generateDeviceServer(): string {
13
13
  return `import { createServerFn } from "@tanstack/react-start";
14
14
  import { getRequestHeader } from "@tanstack/react-start/server";
15
- import { detectDevice } from "@decocms/start/sdk/useDevice";
15
+ import { detectDevice } from "@decocms/blocks/sdk/useDevice";
16
16
 
17
17
  export const getDeviceFromServer = createServerFn({ method: "GET" }).handler(
18
18
  async () => {
@@ -71,22 +71,22 @@ export function generateSectionLoaders(ctx: MigrationContext): string {
71
71
  lines.push(` withSearchParam,`);
72
72
  lines.push(` withSectionLoader,`);
73
73
  lines.push(` compose,`);
74
- lines.push(`} from "@decocms/start/cms";`);
74
+ lines.push(`} from "@decocms/blocks/cms";`);
75
75
 
76
76
  if (hasSearchResult) {
77
- lines.push(`import { detectDevice } from "@decocms/start/sdk/useDevice";`);
77
+ lines.push(`import { detectDevice } from "@decocms/blocks/sdk/useDevice";`);
78
78
  }
79
79
 
80
80
  if (isVtex) {
81
- lines.push(`import { getVtexConfig } from "@decocms/apps/vtex";`);
81
+ lines.push(`import { getVtexConfig } from "@decocms/apps-vtex";`);
82
82
  if (hasWishlistSection && hasWishlistLoaders) {
83
- lines.push(`import { getUser } from "@decocms/apps/vtex/loaders/user";`);
84
- lines.push(`import { getVtexCookies } from "@decocms/apps/vtex/utils/cookies";`);
83
+ lines.push(`import { getUser } from "@decocms/apps-vtex/loaders/user";`);
84
+ lines.push(`import { getVtexCookies } from "@decocms/apps-vtex/utils/cookies";`);
85
85
  }
86
86
  }
87
87
 
88
88
  if (hasAccountSections) {
89
- lines.push(`import { vtexAccountLoaders } from "@decocms/apps/vtex/utils/accountLoaders";`);
89
+ lines.push(`import { vtexAccountLoaders } from "@decocms/apps-vtex/utils/accountLoaders";`);
90
90
  }
91
91
 
92
92
  if (hasProductReviewsLoader && (hasProductReviews || hasSearchResult)) {
@@ -338,7 +338,7 @@ export function generateSectionLoaders(ctx: MigrationContext): string {
338
338
  // ---------- Account sections ----------
339
339
  if (isVtex && hasAccountSections) {
340
340
  entries.push(``);
341
- entries.push(` // Account sections — via @decocms/apps factory`);
341
+ entries.push(` // Account sections — via @decocms/apps-vtex factory`);
342
342
 
343
343
  for (const meta of ctx.sectionMetas) {
344
344
  if (!meta.isAccountSection) continue;