@decocms/blocks-cli 7.5.1 → 7.5.3
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 +2 -2
- package/scripts/generate-invoke.ts +10 -0
- package/scripts/generate-schema.ts +46 -15
- package/scripts/generate-sections.test.ts +67 -0
- package/scripts/generate-sections.ts +16 -2
- package/scripts/lib/codegenExclusions.test.ts +16 -6
- package/scripts/lib/codegenExclusions.ts +13 -1
- package/scripts/migrate/analyzers/loader-inventory.ts +1 -1
- package/scripts/migrate/phase-analyze.ts +3 -3
- package/scripts/migrate/phase-cleanup.test.ts +70 -1
- package/scripts/migrate/phase-cleanup.ts +47 -26
- package/scripts/migrate/phase-report.ts +7 -7
- package/scripts/migrate/phase-scaffold.ts +4 -4
- package/scripts/migrate/phase-transform.ts +1 -1
- package/scripts/migrate/phase-verify.ts +6 -5
- package/scripts/migrate/post-cleanup/rules.ts +55 -44
- package/scripts/migrate/post-cleanup/runner.test.ts +58 -34
- package/scripts/migrate/templates/cache-config.ts +4 -4
- package/scripts/migrate/templates/commerce-loaders.ts +10 -10
- package/scripts/migrate/templates/cursor-rules.test.ts +6 -0
- package/scripts/migrate/templates/cursor-rules.ts +12 -11
- package/scripts/migrate/templates/hooks.test.ts +7 -7
- package/scripts/migrate/templates/hooks.ts +10 -10
- package/scripts/migrate/templates/lib-utils.test.ts +2 -2
- package/scripts/migrate/templates/lib-utils.ts +4 -4
- package/scripts/migrate/templates/no-legacy-packages.test.ts +147 -0
- package/scripts/migrate/templates/package-json.ts +30 -10
- package/scripts/migrate/templates/routes.ts +8 -10
- package/scripts/migrate/templates/sdk-gen.ts +1 -1
- package/scripts/migrate/templates/section-loaders.ts +7 -7
- package/scripts/migrate/templates/server-entry.ts +37 -37
- package/scripts/migrate/templates/setup.ts +6 -6
- package/scripts/migrate/templates/types-gen.ts +2 -2
- package/scripts/migrate/templates/ui-components.ts +2 -2
- package/scripts/migrate/templates/vite-config.ts +12 -3
- package/scripts/migrate/transforms/fresh-apis.ts +5 -5
- package/scripts/migrate/transforms/imports.test.ts +138 -0
- package/scripts/migrate/transforms/imports.ts +65 -43
- package/scripts/migrate/transforms/jsx.ts +8 -0
- package/scripts/migrate/transforms/section-conventions.ts +1 -1
- package/scripts/migrate/types.ts +1 -1
- package/scripts/migrate-post-cleanup.ts +8 -8
- package/scripts/migrate.ts +11 -11
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { transformImports } from "./imports";
|
|
3
|
+
|
|
4
|
+
describe("transformImports — shopify hooks", () => {
|
|
5
|
+
it("rewrites the three known Shopify hooks to the site-local scaffolded files, mirroring VTEX", () => {
|
|
6
|
+
const src =
|
|
7
|
+
`import { useUser } from "apps/shopify/hooks/useUser.ts";\n` +
|
|
8
|
+
`import { useCart } from "apps/shopify/hooks/useCart.ts";\n` +
|
|
9
|
+
`import { useWishlist } from "apps/shopify/hooks/useWishlist.ts";\n`;
|
|
10
|
+
|
|
11
|
+
const r = transformImports(src);
|
|
12
|
+
|
|
13
|
+
expect(r.changed).toBe(true);
|
|
14
|
+
expect(r.content).toContain(`import { useUser } from "~/hooks/useUser";`);
|
|
15
|
+
expect(r.content).toContain(`import { useCart } from "~/hooks/useCart";`);
|
|
16
|
+
expect(r.content).toContain(
|
|
17
|
+
`import { useWishlist } from "~/hooks/useWishlist";`,
|
|
18
|
+
);
|
|
19
|
+
// Must never point at the nonexistent @decocms/apps-shopify/hooks/* target.
|
|
20
|
+
expect(r.content).not.toContain("@decocms/apps-shopify/hooks");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("does not rewrite an unknown shopify hook name to a nonexistent package export", () => {
|
|
24
|
+
// No shopify hook other than useCart/useUser/useWishlist has ever existed
|
|
25
|
+
// (verified against full git history), so there is no generic fallback
|
|
26
|
+
// rule anymore. Unhandled "apps/*" imports fall through to the general
|
|
27
|
+
// apps/ catch-all, which removes the import line entirely — surfacing as
|
|
28
|
+
// an obvious break at the usage site rather than an unresolvable module.
|
|
29
|
+
const src = `import { useSomethingElse } from "apps/shopify/hooks/useSomethingElse.ts";\n`;
|
|
30
|
+
|
|
31
|
+
const r = transformImports(src);
|
|
32
|
+
|
|
33
|
+
expect(r.content).not.toContain("@decocms/apps-shopify/hooks");
|
|
34
|
+
expect(r.content).not.toContain("apps/shopify/hooks");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("still rewrites shopify utils/actions/loaders to the real @decocms/apps-shopify package", () => {
|
|
38
|
+
const src =
|
|
39
|
+
`import { formatMoney } from "apps/shopify/utils/formatMoney.ts";\n` +
|
|
40
|
+
`import { addItems } from "apps/shopify/actions/cart/addItems.ts";\n` +
|
|
41
|
+
`import ProductList from "apps/shopify/loaders/ProductList.ts";\n`;
|
|
42
|
+
|
|
43
|
+
const r = transformImports(src);
|
|
44
|
+
|
|
45
|
+
expect(r.content).toContain(
|
|
46
|
+
`import { formatMoney } from "@decocms/apps-shopify/utils/formatMoney";`,
|
|
47
|
+
);
|
|
48
|
+
expect(r.content).toContain(
|
|
49
|
+
`import { addItems } from "@decocms/apps-shopify/actions/cart/addItems";`,
|
|
50
|
+
);
|
|
51
|
+
expect(r.content).toContain(
|
|
52
|
+
`import ProductList from "@decocms/apps-shopify/loaders/ProductList";`,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("transformImports — splitDecoHooksImports", () => {
|
|
58
|
+
it("splits a mixed useDevice + non-useDevice named import from the legacy @deco/deco/hooks specifier", () => {
|
|
59
|
+
const src = `import { useDevice, useScript, useSection } from "@deco/deco/hooks";\n`;
|
|
60
|
+
|
|
61
|
+
const r = transformImports(src);
|
|
62
|
+
|
|
63
|
+
expect(r.changed).toBe(true);
|
|
64
|
+
expect(r.content).toContain(
|
|
65
|
+
`import { useDevice } from "@decocms/blocks/sdk/useDevice";`,
|
|
66
|
+
);
|
|
67
|
+
expect(r.content).toContain(
|
|
68
|
+
`import { useScript, useSection } from "@decocms/blocks/sdk/useScript";`,
|
|
69
|
+
);
|
|
70
|
+
expect(r.notes).toContain(
|
|
71
|
+
"Split useDevice into separate import from @decocms/blocks/sdk/useDevice",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("emits only the useDevice import when useDevice is the sole named import", () => {
|
|
76
|
+
const src = `import { useDevice } from "@deco/deco/hooks";\n`;
|
|
77
|
+
|
|
78
|
+
const r = transformImports(src);
|
|
79
|
+
|
|
80
|
+
expect(r.content).toBe(
|
|
81
|
+
`import { useDevice } from "@decocms/blocks/sdk/useDevice";\n`,
|
|
82
|
+
);
|
|
83
|
+
expect(r.content).not.toContain("@decocms/blocks/sdk/useScript");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("leaves a non-useDevice-only import pointed at useScript, unsplit", () => {
|
|
87
|
+
const src = `import { useScript, useSection } from "@deco/deco/hooks";\n`;
|
|
88
|
+
|
|
89
|
+
const r = transformImports(src);
|
|
90
|
+
|
|
91
|
+
expect(r.content).toBe(
|
|
92
|
+
`import { useScript, useSection } from "@decocms/blocks/sdk/useScript";\n`,
|
|
93
|
+
);
|
|
94
|
+
expect(r.content).not.toContain("@decocms/blocks/sdk/useDevice");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("handles a `type` import with mixed useDevice/non-useDevice specifiers", () => {
|
|
98
|
+
const src = `import type { useDevice, useScript } from "@deco/deco/hooks";\n`;
|
|
99
|
+
|
|
100
|
+
const r = transformImports(src);
|
|
101
|
+
|
|
102
|
+
// The base IMPORT_RULES rewrite fires first (regardless of `type`), then
|
|
103
|
+
// splitDecoHooksImports re-splits the resulting line — confirming the
|
|
104
|
+
// regex-chain order (rewriteSpecifier → splitDecoHooksImports) still
|
|
105
|
+
// matches `import type { ... }` shapes, not just bare `import { ... }`.
|
|
106
|
+
expect(r.content).toContain(
|
|
107
|
+
`import { useDevice } from "@decocms/blocks/sdk/useDevice";`,
|
|
108
|
+
);
|
|
109
|
+
expect(r.content).toContain(
|
|
110
|
+
`import { useScript } from "@decocms/blocks/sdk/useScript";`,
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("preserves aliased specifiers (useDevice as useDeviceHook) on the split line", () => {
|
|
115
|
+
const src = `import { useDevice as useDeviceHook, useScript } from "@deco/deco/hooks";\n`;
|
|
116
|
+
|
|
117
|
+
const r = transformImports(src);
|
|
118
|
+
|
|
119
|
+
expect(r.content).toContain(
|
|
120
|
+
`import { useDevice as useDeviceHook } from "@decocms/blocks/sdk/useDevice";`,
|
|
121
|
+
);
|
|
122
|
+
expect(r.content).toContain(
|
|
123
|
+
`import { useScript } from "@decocms/blocks/sdk/useScript";`,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("does not touch @decocms/blocks/sdk/useScript imports that were never routed through @deco/deco/hooks", () => {
|
|
128
|
+
// Guards the order-of-operations: splitDecoHooksImports must only ever
|
|
129
|
+
// fire as a post-process of the IMPORT_RULES rewrite, not independently
|
|
130
|
+
// match any pre-existing import already targeting useScript.
|
|
131
|
+
const src = `import { useScript } from "@decocms/blocks/sdk/useScript";\n`;
|
|
132
|
+
|
|
133
|
+
const r = transformImports(src);
|
|
134
|
+
|
|
135
|
+
expect(r.changed).toBe(false);
|
|
136
|
+
expect(r.content).toBe(src);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -20,7 +20,7 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
20
20
|
[/^"@preact\/signals"$/, `"~/sdk/signal"`],
|
|
21
21
|
|
|
22
22
|
// Deco framework — hooks need splitting (useDevice, useScript, useSection)
|
|
23
|
-
[/^"@deco\/deco\/hooks"$/, `"@decocms/
|
|
23
|
+
[/^"@deco\/deco\/hooks"$/, `"@decocms/blocks/sdk/useScript"`],
|
|
24
24
|
[/^"@deco\/deco\/blocks"$/, `"~/types/deco"`],
|
|
25
25
|
[/^"@deco\/deco\/o11y"$/, null], // logger — use console.log/warn/error instead
|
|
26
26
|
[/^"@deco\/deco\/web"$/, null], // runtime.ts is rewritten
|
|
@@ -32,66 +32,82 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
32
32
|
// Widget aliases (ImageWidget, HTMLWidget, ...) are framework-owned —
|
|
33
33
|
// every site has the same type set, and the schema generator detects
|
|
34
34
|
// them via type-text matching, not module identity. Re-export from
|
|
35
|
-
// @decocms/
|
|
35
|
+
// @decocms/blocks/types/widgets so we don't keep a duplicated 8-line
|
|
36
36
|
// file in every site.
|
|
37
|
-
[/^"apps\/admin\/widgets\.ts"$/, `"@decocms/
|
|
37
|
+
[/^"apps\/admin\/widgets\.ts"$/, `"@decocms/blocks/types/widgets"`],
|
|
38
38
|
[/^"apps\/website\/components\/Image\.tsx"$/, `"~/components/ui/Image"`],
|
|
39
39
|
[/^"apps\/website\/components\/Picture\.tsx"$/, `"~/components/ui/Picture"`],
|
|
40
40
|
[/^"apps\/website\/components\/Video\.tsx"$/, `"~/components/ui/Video"`],
|
|
41
41
|
[/^"apps\/website\/components\/Theme\.tsx"$/, `"~/components/ui/Theme"`],
|
|
42
42
|
[/^"apps\/website\/components\/_seo\/[^"]+?"$/, null], // SEO preview — framework-only, remove
|
|
43
43
|
[/^"apps\/website\/components\/([^"]+?)(?:\.tsx?)?"$/, `"~/components/ui/$1"`],
|
|
44
|
-
[/^"apps\/commerce\/types\.ts"$/, `"@decocms/apps
|
|
44
|
+
[/^"apps\/commerce\/types\.ts"$/, `"@decocms/apps-commerce/types"`],
|
|
45
45
|
[/^"apps\/commerce\/mod\.ts"$/, `"~/types/commerce-app"`],
|
|
46
|
-
[/^"apps\/commerce\/types"$/, `"@decocms/apps
|
|
46
|
+
[/^"apps\/commerce\/types"$/, `"@decocms/apps-commerce/types"`],
|
|
47
47
|
|
|
48
|
-
// Apps — VTEX hooks: useUser/useCart/useWishlist → local hooks (react-query based @decocms/apps hooks crash Workers SSR)
|
|
48
|
+
// Apps — VTEX hooks: useUser/useCart/useWishlist → local hooks (react-query based @decocms/apps-vtex hooks crash Workers SSR)
|
|
49
49
|
[/^"apps\/vtex\/hooks\/useUser(?:\.ts)?"$/, `"~/hooks/useUser"`],
|
|
50
50
|
[/^"apps\/vtex\/hooks\/useCart(?:\.ts)?"$/, `"~/hooks/useCart"`],
|
|
51
51
|
[/^"apps\/vtex\/hooks\/useWishlist(?:\.ts)?"$/, `"~/hooks/useWishlist"`],
|
|
52
|
-
[/^"apps\/vtex\/hooks\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
53
|
-
// Specific VTEX utils that moved to different paths in @decocms/apps
|
|
52
|
+
[/^"apps\/vtex\/hooks\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-vtex/hooks/$1"`],
|
|
53
|
+
// Specific VTEX utils that moved to different paths in @decocms/apps-vtex
|
|
54
54
|
// fetchVTEX (generic fetchSafe + QS sanitization) lives at vtex/utils/fetch in apps-start.
|
|
55
|
-
[/^"apps\/vtex\/utils\/fetchVTEX(?:\.ts)?"$/, `"@decocms/apps
|
|
56
|
-
[/^"apps\/vtex\/utils\/client(?:\.ts)?"$/, `"@decocms/apps
|
|
57
|
-
[/^"apps\/vtex\/utils\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
58
|
-
[/^"apps\/vtex\/actions\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
55
|
+
[/^"apps\/vtex\/utils\/fetchVTEX(?:\.ts)?"$/, `"@decocms/apps-vtex/utils/fetch"`],
|
|
56
|
+
[/^"apps\/vtex\/utils\/client(?:\.ts)?"$/, `"@decocms/apps-vtex/client"`],
|
|
57
|
+
[/^"apps\/vtex\/utils\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-vtex/utils/$1"`],
|
|
58
|
+
[/^"apps\/vtex\/actions\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-vtex/actions/$1"`],
|
|
59
59
|
// Tier B loader path rewrites (apps-start has no `intelligentSearch/`, `legacy/<file>`, or `paths/` subdirs).
|
|
60
60
|
// Intelligent Search loaders moved to inline-loaders/.
|
|
61
61
|
[
|
|
62
62
|
/^"apps\/vtex\/loaders\/intelligentSearch\/productList(?:\.ts)?"$/,
|
|
63
|
-
`"@decocms/apps
|
|
63
|
+
`"@decocms/apps-vtex/inline-loaders/productList"`,
|
|
64
64
|
],
|
|
65
65
|
[
|
|
66
66
|
/^"apps\/vtex\/loaders\/intelligentSearch\/productListingPage(?:\.ts)?"$/,
|
|
67
|
-
`"@decocms/apps
|
|
67
|
+
`"@decocms/apps-vtex/inline-loaders/productListingPage"`,
|
|
68
68
|
],
|
|
69
69
|
[
|
|
70
70
|
/^"apps\/vtex\/loaders\/intelligentSearch\/productDetailsPage(?:\.ts)?"$/,
|
|
71
|
-
`"@decocms/apps
|
|
71
|
+
`"@decocms/apps-vtex/inline-loaders/productDetailsPage"`,
|
|
72
72
|
],
|
|
73
73
|
[
|
|
74
74
|
/^"apps\/vtex\/loaders\/intelligentSearch\/suggestions(?:\.ts)?"$/,
|
|
75
|
-
`"@decocms/apps
|
|
75
|
+
`"@decocms/apps-vtex/inline-loaders/suggestions"`,
|
|
76
76
|
],
|
|
77
77
|
// Legacy product loaders are consolidated into a single file (named exports).
|
|
78
78
|
[
|
|
79
79
|
/^"apps\/vtex\/loaders\/legacy\/(?:productList|productListingPage|productDetailsPage|search|category)(?:\.ts)?"$/,
|
|
80
|
-
`"@decocms/apps
|
|
80
|
+
`"@decocms/apps-vtex/loaders/legacy"`,
|
|
81
81
|
],
|
|
82
82
|
// Path-default loaders (sitemap seeds) don't exist in TanStack Start — paths resolve at request time.
|
|
83
83
|
[/^"apps\/vtex\/loaders\/paths\/(?:[^"]+)(?:\.ts)?"$/, null],
|
|
84
|
-
[/^"apps\/vtex\/loaders\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
85
|
-
[/^"apps\/vtex\/types(?:\.ts)?"$/, `"@decocms/apps
|
|
84
|
+
[/^"apps\/vtex\/loaders\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-vtex/loaders/$1"`],
|
|
85
|
+
[/^"apps\/vtex\/types(?:\.ts)?"$/, `"@decocms/apps-vtex/types"`],
|
|
86
86
|
[/^"apps\/vtex\/mod(?:\.ts)?"$/, `"~/types/vtex-app"`],
|
|
87
87
|
// Apps — Shopify (hooks, utils, actions, loaders)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
// Shopify hooks were never a real package export — not in the pre-split
|
|
89
|
+
// @decocms/apps monolith, not in @decocms/apps-shopify today (it ships
|
|
90
|
+
// no src/hooks/ dir and no ./hooks/* export; verified via `git log
|
|
91
|
+
// --diff-filter=A -- '**/shopify/hooks/**'` returning nothing across all
|
|
92
|
+
// history). The legacy migration reference
|
|
93
|
+
// (.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md)
|
|
94
|
+
// confirms Shopify's useCart/useUser/useWishlist were always meant to be
|
|
95
|
+
// site-local no-op stubs, and templates/hooks.ts's generateHooks() still
|
|
96
|
+
// scaffolds them at src/hooks/use{Cart,User,Wishlist}.ts for every
|
|
97
|
+
// non-VTEX platform (shopify included). Mirror the VTEX rule shape below:
|
|
98
|
+
// route the three known hook names to the scaffolded local files, same as
|
|
99
|
+
// "apps/vtex/hooks/useUser" → "~/hooks/useUser" above. Do NOT reintroduce
|
|
100
|
+
// a generic "apps/shopify/hooks/$1" → "@decocms/apps-shopify/hooks/$1"
|
|
101
|
+
// fallback — that target has never existed.
|
|
102
|
+
[/^"apps\/shopify\/hooks\/useUser(?:\.ts)?"$/, `"~/hooks/useUser"`],
|
|
103
|
+
[/^"apps\/shopify\/hooks\/useCart(?:\.ts)?"$/, `"~/hooks/useCart"`],
|
|
104
|
+
[/^"apps\/shopify\/hooks\/useWishlist(?:\.ts)?"$/, `"~/hooks/useWishlist"`],
|
|
105
|
+
[/^"apps\/shopify\/utils\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-shopify/utils/$1"`],
|
|
106
|
+
[/^"apps\/shopify\/actions\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-shopify/actions/$1"`],
|
|
107
|
+
[/^"apps\/shopify\/loaders\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-shopify/loaders/$1"`],
|
|
92
108
|
// Apps — commerce (types, SDK, utils)
|
|
93
|
-
[/^"apps\/commerce\/sdk\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
94
|
-
[/^"apps\/commerce\/utils\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps
|
|
109
|
+
[/^"apps\/commerce\/sdk\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-commerce/sdk/$1"`],
|
|
110
|
+
[/^"apps\/commerce\/utils\/([^"]+?)(?:\.ts)?"$/, `"@decocms/apps-commerce/utils/$1"`],
|
|
95
111
|
|
|
96
112
|
// Apps — shared utils (STALE, fetchSafe, createHttpClient, etc.)
|
|
97
113
|
[/^"apps\/utils\/fetch(?:\.ts)?"$/, `"~/lib/fetch-utils"`],
|
|
@@ -116,7 +132,7 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
116
132
|
[/^"@std\/crypto"$/, null], // Use globalThis.crypto instead
|
|
117
133
|
|
|
118
134
|
// site/sdk/* → framework equivalents (before the catch-all site/ → ~/ rule)
|
|
119
|
-
[/^"site\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/
|
|
135
|
+
[/^"site\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/blocks/sdk/clx"`],
|
|
120
136
|
[/^"site\/sdk\/useId(?:\.tsx?)?.*"$/, `"react"`],
|
|
121
137
|
// useOffer and useVariantPossiblities kept as site files (~/sdk/)
|
|
122
138
|
[/^"site\/sdk\/usePlatform(?:\.tsx?)?.*"$/, null],
|
|
@@ -127,10 +143,10 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
127
143
|
[/^"~\/account\.json"$/, `"~/constants/account"`],
|
|
128
144
|
|
|
129
145
|
// $store/ → ~/ (common Deno import map alias for project root)
|
|
130
|
-
[/^"\$store\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/
|
|
146
|
+
[/^"\$store\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/blocks/sdk/clx"`],
|
|
131
147
|
[/^"\$store\/sdk\/useId(?:\.tsx?)?.*"$/, `"react"`],
|
|
132
148
|
// useOffer and useVariantPossiblities kept as site files (~/sdk/)
|
|
133
|
-
[/^"\$store\/sdk\/format(?:\.tsx?)?.*"$/, `"@decocms/apps
|
|
149
|
+
[/^"\$store\/sdk\/format(?:\.tsx?)?.*"$/, `"@decocms/apps-commerce/sdk/formatPrice"`],
|
|
134
150
|
[/^"\$store\/sdk\/usePlatform(?:\.tsx?)?.*"$/, null],
|
|
135
151
|
// islands → components (must be before $store catch-all)
|
|
136
152
|
[/^"\$store\/islands\/ui\/([^"]+?)(?:\.tsx?)?"$/, `"~/components/ui/$1"`],
|
|
@@ -141,10 +157,10 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
141
157
|
[/^"\$home\/(.+)"$/, `"~/$1"`],
|
|
142
158
|
|
|
143
159
|
// site/ → ~/
|
|
144
|
-
[/^"site\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/
|
|
160
|
+
[/^"site\/sdk\/clx(?:\.tsx?)?.*"$/, `"@decocms/blocks/sdk/clx"`],
|
|
145
161
|
[/^"site\/sdk\/useId(?:\.tsx?)?.*"$/, `"react"`],
|
|
146
162
|
// useOffer and useVariantPossiblities kept as site files (~/sdk/)
|
|
147
|
-
[/^"site\/sdk\/format(?:\.tsx?)?.*"$/, `"@decocms/apps
|
|
163
|
+
[/^"site\/sdk\/format(?:\.tsx?)?.*"$/, `"@decocms/apps-commerce/sdk/formatPrice"`],
|
|
148
164
|
[/^"site\/sdk\/usePlatform(?:\.tsx?)?.*"$/, null],
|
|
149
165
|
// islands → components (must be before site/ catch-all)
|
|
150
166
|
[/^"site\/islands\/ui\/([^"]+?)(?:\.tsx?)?"$/, `"~/components/ui/$1"`],
|
|
@@ -155,10 +171,16 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
155
171
|
[/^"~\/islands\/ui\/([^"]+?)(?:\.tsx?)?"$/, `"~/components/ui/$1"`],
|
|
156
172
|
[/^"~\/islands\/([^"]+?)(?:\.tsx?)?"$/, `"~/components/$1"`],
|
|
157
173
|
|
|
158
|
-
// @decocms/apps hooks → local hooks (react-query hooks crash Workers SSR at module eval)
|
|
174
|
+
// @decocms/apps-vtex hooks → local hooks (react-query hooks crash Workers SSR at module eval)
|
|
175
|
+
// Pre-7.x monolith path — kept for sites whose Deno import map already
|
|
176
|
+
// aliased directly to the npm specifier instead of the "apps/vtex/..." form.
|
|
159
177
|
[/^"@decocms\/apps\/vtex\/hooks\/useUser"$/, `"~/hooks/useUser"`],
|
|
160
178
|
[/^"@decocms\/apps\/vtex\/hooks\/useCart"$/, `"~/hooks/useCart"`],
|
|
161
179
|
[/^"@decocms\/apps\/vtex\/hooks\/useWishlist"$/, `"~/hooks/useWishlist"`],
|
|
180
|
+
// Post-7.x split package path — same rationale, current package name.
|
|
181
|
+
[/^"@decocms\/apps-vtex\/hooks\/useUser"$/, `"~/hooks/useUser"`],
|
|
182
|
+
[/^"@decocms\/apps-vtex\/hooks\/useCart"$/, `"~/hooks/useCart"`],
|
|
183
|
+
[/^"@decocms\/apps-vtex\/hooks\/useWishlist"$/, `"~/hooks/useWishlist"`],
|
|
162
184
|
];
|
|
163
185
|
|
|
164
186
|
/**
|
|
@@ -167,14 +189,14 @@ const IMPORT_RULES: Array<[RegExp, string | null]> = [
|
|
|
167
189
|
* The key is the ending of the import path, the value is the replacement specifier.
|
|
168
190
|
*/
|
|
169
191
|
const RELATIVE_SDK_REWRITES: Array<[RegExp, string]> = [
|
|
170
|
-
// sdk/clx → @decocms/
|
|
171
|
-
[/(?:\.\.\/)*sdk\/clx(?:\.tsx?)?$/, "@decocms/
|
|
192
|
+
// sdk/clx → @decocms/blocks/sdk/clx (framework utility)
|
|
193
|
+
[/(?:\.\.\/)*sdk\/clx(?:\.tsx?)?$/, "@decocms/blocks/sdk/clx"],
|
|
172
194
|
// sdk/useId → react (useId is built-in in React 19)
|
|
173
195
|
[/(?:\.\.\/)*sdk\/useId(?:\.tsx?)?$/, "react"],
|
|
174
196
|
// sdk/useOffer — kept as-is (sites customize offer logic)
|
|
175
197
|
// sdk/useVariantPossiblities — kept as-is (sites customize variant logic)
|
|
176
|
-
// sdk/format → @decocms/apps
|
|
177
|
-
[/(?:\.\.\/)*sdk\/format(?:\.tsx?)?$/, "@decocms/apps
|
|
198
|
+
// sdk/format → @decocms/apps-commerce/sdk/formatPrice
|
|
199
|
+
[/(?:\.\.\/)*sdk\/format(?:\.tsx?)?$/, "@decocms/apps-commerce/sdk/formatPrice"],
|
|
178
200
|
// sdk/usePlatform → remove entirely
|
|
179
201
|
[/(?:\.\.\/)*sdk\/usePlatform(?:\.tsx?)?$/, ""],
|
|
180
202
|
// static/adminIcons → deleted (icon loaders need rewriting)
|
|
@@ -234,16 +256,16 @@ export function transformImports(
|
|
|
234
256
|
/**
|
|
235
257
|
* Post-process: split @deco/deco/hooks imports.
|
|
236
258
|
* In the old stack, @deco/deco/hooks exported useDevice, useScript, useSection, etc.
|
|
237
|
-
* In @decocms/
|
|
259
|
+
* In @decocms/blocks, useDevice is at @decocms/blocks/sdk/useDevice.
|
|
238
260
|
* After import rewriting, we need to split lines like:
|
|
239
|
-
* import { useDevice, useScript } from "@decocms/
|
|
261
|
+
* import { useDevice, useScript } from "@decocms/blocks/sdk/useScript"
|
|
240
262
|
* into:
|
|
241
|
-
* import { useDevice } from "@decocms/
|
|
242
|
-
* import { useScript } from "@decocms/
|
|
263
|
+
* import { useDevice } from "@decocms/blocks/sdk/useDevice"
|
|
264
|
+
* import { useScript } from "@decocms/blocks/sdk/useScript"
|
|
243
265
|
*/
|
|
244
266
|
function splitDecoHooksImports(code: string): string {
|
|
245
267
|
return code.replace(
|
|
246
|
-
/^(import\s+(?:type\s+)?\{)([^}]*\buseDevice\b[^}]*)(\}\s+from\s+["']@decocms\/
|
|
268
|
+
/^(import\s+(?:type\s+)?\{)([^}]*\buseDevice\b[^}]*)(\}\s+from\s+["']@decocms\/blocks\/sdk\/useScript["'];?)$/gm,
|
|
247
269
|
(_match, _prefix, importList, _suffix) => {
|
|
248
270
|
const items = importList.split(",").map((s: string) => s.trim()).filter(Boolean);
|
|
249
271
|
const deviceItems = items.filter((s: string) => s.includes("useDevice"));
|
|
@@ -251,10 +273,10 @@ export function transformImports(
|
|
|
251
273
|
|
|
252
274
|
const lines: string[] = [];
|
|
253
275
|
if (deviceItems.length > 0) {
|
|
254
|
-
lines.push(`import { ${deviceItems.join(", ")} } from "@decocms/
|
|
276
|
+
lines.push(`import { ${deviceItems.join(", ")} } from "@decocms/blocks/sdk/useDevice";`);
|
|
255
277
|
}
|
|
256
278
|
if (otherItems.length > 0) {
|
|
257
|
-
lines.push(`import { ${otherItems.join(", ")} } from "@decocms/
|
|
279
|
+
lines.push(`import { ${otherItems.join(", ")} } from "@decocms/blocks/sdk/useScript";`);
|
|
258
280
|
}
|
|
259
281
|
return lines.join("\n");
|
|
260
282
|
},
|
|
@@ -356,7 +378,7 @@ export function transformImports(
|
|
|
356
378
|
if (afterSplit !== result) {
|
|
357
379
|
result = afterSplit;
|
|
358
380
|
changed = true;
|
|
359
|
-
notes.push("Split useDevice into separate import from @decocms/
|
|
381
|
+
notes.push("Split useDevice into separate import from @decocms/blocks/sdk/useDevice");
|
|
360
382
|
}
|
|
361
383
|
|
|
362
384
|
// Rewrite dynamic imports: route through rewriteSpecifier so sdk-specific
|
|
@@ -257,6 +257,14 @@ export function transformJsx(content: string): TransformResult {
|
|
|
257
257
|
// In TanStack Start, nested sections have Component as a string key, not a function.
|
|
258
258
|
// SectionRenderer from @decocms/start/hooks handles the lazy registry lookup.
|
|
259
259
|
//
|
|
260
|
+
// HANDOFF MARKER: "@decocms/start/hooks" below is intentionally the
|
|
261
|
+
// pre-split package path, not the real current one — it's a same-run
|
|
262
|
+
// handoff to phase-cleanup.ts's upgradeSectionRenderer(), which runs
|
|
263
|
+
// later in the same migration and rewrites this exact import specifier
|
|
264
|
+
// to `import { RenderSection } from "@decocms/blocks/hooks"`. If this
|
|
265
|
+
// transform's run order relative to the cleanup phase ever changes, that
|
|
266
|
+
// rewrite target must move with it.
|
|
267
|
+
//
|
|
260
268
|
// Gate on ANY variant of the .Component/.props pattern (simple, extra props, or multi-line).
|
|
261
269
|
const sectionPatternGate = /\.\s*Component[\s\n]+\{\.\.\.(\w+)\.props\}/;
|
|
262
270
|
if (sectionPatternGate.test(result)) {
|
|
@@ -8,7 +8,7 @@ import type { SectionMeta, TransformResult } from "../types";
|
|
|
8
8
|
* Adds section convention exports (sync, eager, layout, cache)
|
|
9
9
|
* to section files based on metadata extracted during analysis.
|
|
10
10
|
*
|
|
11
|
-
* These exports are read by generate-sections.ts in @decocms/
|
|
11
|
+
* These exports are read by generate-sections.ts in @decocms/blocks-cli
|
|
12
12
|
* to build the sections.gen.ts registry.
|
|
13
13
|
*
|
|
14
14
|
* The set of section *names* that get hints applied is configurable
|
package/scripts/migrate/types.ts
CHANGED
|
@@ -122,7 +122,7 @@ export interface LoaderInfo {
|
|
|
122
122
|
hasCache: boolean;
|
|
123
123
|
/** Has export const cacheKey */
|
|
124
124
|
hasCacheKey: boolean;
|
|
125
|
-
/** Maps to a known @decocms/apps equivalent */
|
|
125
|
+
/** Maps to a known @decocms/apps-* equivalent */
|
|
126
126
|
appsEquivalent: string | null;
|
|
127
127
|
/** Is a custom loader that needs dynamic import in commerce-loaders */
|
|
128
128
|
isCustom: boolean;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* but turns it into something CI can actually run.
|
|
9
9
|
*
|
|
10
10
|
* Usage (from a migrated site directory):
|
|
11
|
-
* npx -p @decocms/
|
|
12
|
-
* npx -p @decocms/
|
|
11
|
+
* npx -p @decocms/blocks-cli deco-post-cleanup
|
|
12
|
+
* npx -p @decocms/blocks-cli deco-post-cleanup --json
|
|
13
13
|
*
|
|
14
14
|
* Options:
|
|
15
15
|
* --source <dir> Site directory to audit (default: current directory)
|
|
@@ -65,13 +65,13 @@ function parseArgs(args: string[]): CliOpts {
|
|
|
65
65
|
|
|
66
66
|
function showHelp() {
|
|
67
67
|
console.log(`
|
|
68
|
-
@decocms/
|
|
68
|
+
@decocms/blocks-cli — Post-Migration Cleanup Audit
|
|
69
69
|
|
|
70
70
|
Scans a migrated site for dead code and obsolete boilerplate that the
|
|
71
71
|
framework now owns. Read-only — prints findings, does not modify files.
|
|
72
72
|
|
|
73
73
|
Usage:
|
|
74
|
-
npx -p @decocms/
|
|
74
|
+
npx -p @decocms/blocks-cli deco-post-cleanup [options]
|
|
75
75
|
|
|
76
76
|
Options:
|
|
77
77
|
--source <dir> Site directory to audit (default: .)
|
|
@@ -88,10 +88,10 @@ function showHelp() {
|
|
|
88
88
|
--help, -h Show this help
|
|
89
89
|
|
|
90
90
|
Examples:
|
|
91
|
-
npx -p @decocms/
|
|
92
|
-
npx -p @decocms/
|
|
93
|
-
npx -p @decocms/
|
|
94
|
-
npx -p @decocms/
|
|
91
|
+
npx -p @decocms/blocks-cli deco-post-cleanup
|
|
92
|
+
npx -p @decocms/blocks-cli deco-post-cleanup --source ./my-site --json
|
|
93
|
+
npx -p @decocms/blocks-cli deco-post-cleanup --fix
|
|
94
|
+
npx -p @decocms/blocks-cli deco-post-cleanup --fix --strict # fail CI if anything left
|
|
95
95
|
|
|
96
96
|
See: .agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md
|
|
97
97
|
`);
|
package/scripts/migrate.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Migration Script: Fresh/Deno/Preact → TanStack Start/React/Cloudflare Workers
|
|
5
5
|
*
|
|
6
6
|
* Converts a Deco storefront from the old Fresh/Deno stack to the new TanStack Start stack.
|
|
7
|
-
*
|
|
7
|
+
* Ships as part of @decocms/blocks-cli — run from a site's root directory.
|
|
8
8
|
*
|
|
9
9
|
* Usage (from your Fresh site directory):
|
|
10
|
-
* npx -p @decocms/
|
|
10
|
+
* npx -p @decocms/blocks-cli deco-migrate [options]
|
|
11
11
|
*
|
|
12
12
|
* Options:
|
|
13
13
|
* --source <dir> Source directory (default: current directory)
|
|
@@ -102,10 +102,10 @@ function parseArgs(args: string[]): {
|
|
|
102
102
|
|
|
103
103
|
function showHelp() {
|
|
104
104
|
console.log(`
|
|
105
|
-
@decocms/
|
|
105
|
+
@decocms/blocks-cli — Migration Script: Fresh/Deno → TanStack Start
|
|
106
106
|
|
|
107
107
|
Usage:
|
|
108
|
-
npx -p @decocms/
|
|
108
|
+
npx -p @decocms/blocks-cli deco-migrate [options]
|
|
109
109
|
|
|
110
110
|
Options:
|
|
111
111
|
--source <dir> Source directory (default: .)
|
|
@@ -119,10 +119,10 @@ function showHelp() {
|
|
|
119
119
|
--help, -h Show this help message
|
|
120
120
|
|
|
121
121
|
Examples:
|
|
122
|
-
npx -p @decocms/
|
|
123
|
-
npx -p @decocms/
|
|
124
|
-
npx -p @decocms/
|
|
125
|
-
npx -p @decocms/
|
|
122
|
+
npx -p @decocms/blocks-cli deco-migrate --dry-run --verbose
|
|
123
|
+
npx -p @decocms/blocks-cli deco-migrate --source ./my-site
|
|
124
|
+
npx -p @decocms/blocks-cli deco-migrate --strict --with-build
|
|
125
|
+
npx -p @decocms/blocks-cli deco-migrate
|
|
126
126
|
`);
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -136,7 +136,7 @@ async function main() {
|
|
|
136
136
|
|
|
137
137
|
const sourceDir = path.resolve(opts.source);
|
|
138
138
|
|
|
139
|
-
banner("@decocms/
|
|
139
|
+
banner("@decocms/blocks-cli — Migrate: Fresh/Deno → TanStack Start");
|
|
140
140
|
stat("Source", sourceDir);
|
|
141
141
|
stat("Mode", opts.dryRun ? yellow("DRY RUN") : green("EXECUTE"));
|
|
142
142
|
stat("Verbose", opts.verbose ? "yes" : "no");
|
|
@@ -254,7 +254,7 @@ function bootstrap(ctx: { sourceDir: string }) {
|
|
|
254
254
|
// matching `packageManager` field that pins the version.
|
|
255
255
|
const pm = "bun";
|
|
256
256
|
if (!run(`${pm} install`, "Install dependencies", true)) return;
|
|
257
|
-
run("bunx tsx node_modules/@decocms/
|
|
257
|
+
run("bunx tsx node_modules/@decocms/blocks-cli/scripts/generate-blocks.ts", "Generate CMS blocks");
|
|
258
258
|
// generate-invoke emits src/server/invoke.gen.ts with top-level
|
|
259
259
|
// createServerFn declarations + the forwardResponseCookies bridge that
|
|
260
260
|
// propagates VTEX Set-Cookie headers (orderFormId, segment, sc…) to the
|
|
@@ -265,7 +265,7 @@ function bootstrap(ctx: { sourceDir: string }) {
|
|
|
265
265
|
// running the generator gives every freshly-migrated site the canonical
|
|
266
266
|
// RPC path so VTEX hooks (useCart, useUser, useWishlist) work end-to-end.
|
|
267
267
|
run(
|
|
268
|
-
"bunx tsx node_modules/@decocms/
|
|
268
|
+
"bunx tsx node_modules/@decocms/blocks-cli/scripts/generate-invoke.ts",
|
|
269
269
|
"Generate VTEX invoke server functions",
|
|
270
270
|
);
|
|
271
271
|
run("bunx tsr generate", "Generate TanStack routes");
|