@decocms/start 1.2.5 → 1.2.7
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 +1 -1
- package/scripts/deco-migrate-cli.ts +444 -0
- package/scripts/migrate/analyzers/island-classifier.ts +73 -0
- package/scripts/migrate/analyzers/loader-inventory.ts +63 -0
- package/scripts/migrate/analyzers/section-metadata.ts +91 -0
- package/scripts/migrate/analyzers/theme-extractor.ts +122 -0
- package/scripts/migrate/phase-analyze.ts +147 -17
- package/scripts/migrate/phase-cleanup.ts +124 -2
- package/scripts/migrate/phase-report.ts +44 -16
- package/scripts/migrate/phase-scaffold.ts +38 -132
- package/scripts/migrate/phase-transform.ts +28 -3
- package/scripts/migrate/phase-verify.ts +127 -5
- package/scripts/migrate/templates/app-css.ts +204 -0
- package/scripts/migrate/templates/cache-config.ts +26 -0
- package/scripts/migrate/templates/commerce-loaders.ts +124 -0
- package/scripts/migrate/templates/hooks.ts +358 -0
- package/scripts/migrate/templates/package-json.ts +29 -6
- package/scripts/migrate/templates/routes.ts +41 -136
- package/scripts/migrate/templates/sdk-gen.ts +59 -0
- package/scripts/migrate/templates/section-loaders.ts +108 -0
- package/scripts/migrate/templates/server-entry.ts +174 -67
- package/scripts/migrate/templates/setup.ts +64 -55
- package/scripts/migrate/templates/types-gen.ts +119 -0
- package/scripts/migrate/templates/ui-components.ts +113 -0
- package/scripts/migrate/templates/vite-config.ts +18 -1
- package/scripts/migrate/templates/wrangler.ts +4 -1
- package/scripts/migrate/transforms/dead-code.ts +23 -2
- package/scripts/migrate/transforms/imports.ts +40 -10
- package/scripts/migrate/transforms/jsx.ts +9 -0
- package/scripts/migrate/transforms/section-conventions.ts +83 -0
- package/scripts/migrate/types.ts +74 -0
- package/src/cms/resolve.ts +10 -0
- package/src/routes/cmsRoute.ts +13 -0
|
@@ -27,40 +27,19 @@ export default createStartHandler(defaultStreamHandler);
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function generateWorkerEntry(ctx: MigrationContext): string {
|
|
30
|
+
const isVtex = ctx.platform === "vtex";
|
|
31
|
+
|
|
32
|
+
if (isVtex) {
|
|
33
|
+
return generateVtexWorkerEntry(ctx);
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
const isCommerce = ctx.platform !== "custom";
|
|
31
|
-
const
|
|
32
|
-
? `\n// Uncomment to enable checkout/API proxy for ${ctx.platform}:
|
|
33
|
-
// import { shouldProxyTo${ctx.platform === "vtex" ? "Vtex" : capitalize(ctx.platform)}, proxyTo${ctx.platform === "vtex" ? "Vtex" : capitalize(ctx.platform)} } from "@decocms/apps/${ctx.platform}/utils/proxy";\n`
|
|
34
|
-
: "";
|
|
35
|
-
|
|
36
|
-
const proxyOption = isCommerce
|
|
37
|
-
? `
|
|
38
|
-
// Uncomment to enable checkout/API proxy for ${ctx.platform}:
|
|
39
|
-
// proxyHandler: (request, url) => {
|
|
40
|
-
// if (shouldProxyTo${ctx.platform === "vtex" ? "Vtex" : capitalize(ctx.platform)}(url.pathname)) {
|
|
41
|
-
// return proxyTo${ctx.platform === "vtex" ? "Vtex" : capitalize(ctx.platform)}(request);
|
|
42
|
-
// }
|
|
43
|
-
// return null;
|
|
44
|
-
// },`
|
|
45
|
-
: "";
|
|
46
|
-
|
|
47
|
-
const segmentOption = ctx.platform === "vtex"
|
|
48
|
-
? `
|
|
49
|
-
// Uncomment for per-sales-channel/region cache segmentation:
|
|
50
|
-
// buildSegment: (request) => {
|
|
51
|
-
// const ua = request.headers.get("user-agent") ?? "";
|
|
52
|
-
// return {
|
|
53
|
-
// device: /mobile|android|iphone/i.test(ua) ? "mobile" : "desktop",
|
|
54
|
-
// // loggedIn: true bypasses cache automatically
|
|
55
|
-
// };
|
|
56
|
-
// },`
|
|
57
|
-
: "";
|
|
37
|
+
const platformLabel = isCommerce ? ctx.platform : null;
|
|
58
38
|
|
|
59
39
|
return `/**
|
|
60
40
|
* Cloudflare Worker entry point.
|
|
61
41
|
*
|
|
62
42
|
* Wraps TanStack Start with admin protocol handlers and edge caching.
|
|
63
|
-
* For commerce sites, uncomment proxyHandler and buildSegment options.
|
|
64
43
|
*/
|
|
65
44
|
import "./setup";
|
|
66
45
|
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
|
|
@@ -72,7 +51,10 @@ import {
|
|
|
72
51
|
handleRender,
|
|
73
52
|
corsHeaders,
|
|
74
53
|
} from "@decocms/start/admin";
|
|
75
|
-
${
|
|
54
|
+
${isCommerce ? `
|
|
55
|
+
// TODO: Uncomment and wire proxy for ${platformLabel}
|
|
56
|
+
// import { shouldProxyTo${capitalize(platformLabel!)}, proxyTo${capitalize(platformLabel!)} } from "@decocms/apps/${platformLabel}/utils/proxy";
|
|
57
|
+
` : ""}
|
|
76
58
|
const serverEntry = createServerEntry({ fetch: handler.fetch });
|
|
77
59
|
|
|
78
60
|
export default createDecoWorkerEntry(serverEntry, {
|
|
@@ -82,52 +64,142 @@ export default createDecoWorkerEntry(serverEntry, {
|
|
|
82
64
|
handleDecofileReload,
|
|
83
65
|
handleRender,
|
|
84
66
|
corsHeaders,
|
|
85
|
-
}
|
|
67
|
+
},
|
|
86
68
|
});
|
|
87
69
|
`;
|
|
88
70
|
}
|
|
89
71
|
|
|
90
|
-
function
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
function generateVtexWorkerEntry(ctx: MigrationContext): string {
|
|
73
|
+
return `/**
|
|
74
|
+
* Cloudflare Worker entry point — VTEX storefront.
|
|
75
|
+
*
|
|
76
|
+
* Handles admin protocol, VTEX checkout proxy, CSP,
|
|
77
|
+
* segment building, and edge caching.
|
|
78
|
+
*
|
|
79
|
+
* MANUAL REVIEW: Add site-specific CSP domains (analytics, CDN, tag managers).
|
|
80
|
+
*/
|
|
94
81
|
import "./setup";
|
|
82
|
+
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
|
|
83
|
+
import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
|
|
84
|
+
import { detectDevice } from "@decocms/start/sdk/useDevice";
|
|
85
|
+
import {
|
|
86
|
+
handleMeta,
|
|
87
|
+
handleDecofileRead,
|
|
88
|
+
handleDecofileReload,
|
|
89
|
+
handleRender,
|
|
90
|
+
corsHeaders,
|
|
91
|
+
} from "@decocms/start/admin";
|
|
92
|
+
import { extractVtexContext } from "@decocms/apps/vtex/middleware";
|
|
93
|
+
import {
|
|
94
|
+
shouldProxyToVtex,
|
|
95
|
+
createVtexCheckoutProxy,
|
|
96
|
+
} from "@decocms/apps/vtex/utils/proxy";
|
|
97
|
+
import { getVtexConfig } from "@decocms/apps/vtex";
|
|
95
98
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
const serverEntry = createServerEntry({ fetch: handler.fetch });
|
|
100
|
+
|
|
101
|
+
const CSP_DIRECTIVES = [
|
|
102
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval' *.vtex.com.br *.vteximg.com.br *.vtexassets.com",
|
|
103
|
+
"img-src 'self' data: blob: *.vteximg.com.br *.vtexassets.com *.vtexcommercestable.com.br",
|
|
104
|
+
"connect-src 'self' *.vtex.com.br *.vtexcommercestable.com.br *.vtexassets.com",
|
|
105
|
+
"frame-src 'self' *.vtex.com.br",
|
|
106
|
+
"style-src 'self' 'unsafe-inline' fonts.googleapis.com",
|
|
107
|
+
"font-src 'self' fonts.gstatic.com data:",
|
|
108
|
+
// TODO: Add site-specific domains (analytics, CDN, tag managers)
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const { account } = getVtexConfig();
|
|
112
|
+
|
|
113
|
+
const vtexProxy = createVtexCheckoutProxy({
|
|
114
|
+
account,
|
|
115
|
+
checkoutOrigin: \`\${account}.vtexcommercestable.com.br\`,
|
|
116
|
+
// TODO: Set your secure checkout origin if different from default
|
|
117
|
+
// checkoutOrigin: "secure.yourdomain.com.br",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const decoWorker = createDecoWorkerEntry(serverEntry, {
|
|
121
|
+
admin: {
|
|
122
|
+
handleMeta,
|
|
123
|
+
handleDecofileRead,
|
|
124
|
+
handleDecofileReload,
|
|
125
|
+
handleRender,
|
|
126
|
+
corsHeaders,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
csp: CSP_DIRECTIVES,
|
|
130
|
+
|
|
131
|
+
buildSegment: (request) => {
|
|
132
|
+
const vtx = extractVtexContext(request);
|
|
133
|
+
const device = detectDevice(request.headers.get("user-agent") ?? "");
|
|
107
134
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
return {
|
|
136
|
+
device,
|
|
137
|
+
...(vtx.isLoggedIn ? { loggedIn: true } : {}),
|
|
138
|
+
...(vtx.salesChannel !== "1" ? { salesChannel: vtx.salesChannel } : {}),
|
|
139
|
+
...(vtx.regionId ? { regionId: vtx.regionId } : {}),
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
proxyHandler: async (request, url) => {
|
|
144
|
+
const { pathname } = url;
|
|
145
|
+
|
|
146
|
+
// CMS-managed routes — don't proxy
|
|
147
|
+
if (pathname === "/login" || pathname === "/logout") return null;
|
|
148
|
+
|
|
149
|
+
// VTEX checkout and API proxy
|
|
150
|
+
if (shouldProxyToVtex(pathname)) {
|
|
151
|
+
return vtexProxy(request, url);
|
|
116
152
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export default decoWorker;
|
|
159
|
+
|
|
160
|
+
// ─── A/B Testing + Redirects (uncomment when ready) ─────────────────
|
|
161
|
+
// import { withABTesting } from "@decocms/start/sdk/abTesting";
|
|
162
|
+
// import { loadBlocks } from "@decocms/start/cms";
|
|
163
|
+
// import { loadRedirects, matchRedirect } from "@decocms/start/sdk/redirects";
|
|
164
|
+
//
|
|
165
|
+
// const cmsRedirects = loadRedirects(loadBlocks());
|
|
166
|
+
//
|
|
167
|
+
// export default withABTesting(decoWorker, {
|
|
168
|
+
// kvBinding: "AB_TESTING",
|
|
169
|
+
// preHandler: (request) => {
|
|
170
|
+
// const url = new URL(request.url);
|
|
171
|
+
// const redirect = matchRedirect(url.pathname, cmsRedirects);
|
|
172
|
+
// if (redirect) {
|
|
173
|
+
// return new Response(null, {
|
|
174
|
+
// status: redirect.type === "temporary" ? 307 : 301,
|
|
175
|
+
// headers: { Location: redirect.to },
|
|
176
|
+
// });
|
|
177
|
+
// }
|
|
178
|
+
// return null;
|
|
179
|
+
// },
|
|
180
|
+
// shouldBypassAB: (_request, url) => shouldProxyToVtex(url.pathname),
|
|
181
|
+
// });
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function generateRouter(): string {
|
|
186
|
+
return `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
187
|
+
import { createDecoRouter } from "@decocms/start/sdk/router";
|
|
188
|
+
import { routeTree } from "./routeTree.gen";
|
|
189
|
+
import "./setup";
|
|
190
|
+
|
|
191
|
+
const queryClient = new QueryClient({
|
|
192
|
+
defaultOptions: { queries: { staleTime: 30_000 } },
|
|
193
|
+
});
|
|
121
194
|
|
|
122
195
|
export function getRouter() {
|
|
123
|
-
|
|
196
|
+
return createDecoRouter({
|
|
124
197
|
routeTree,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
198
|
+
context: { queryClient },
|
|
199
|
+
Wrap: ({ children }) => (
|
|
200
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
201
|
+
),
|
|
129
202
|
});
|
|
130
|
-
return router;
|
|
131
203
|
}
|
|
132
204
|
|
|
133
205
|
declare module "@tanstack/react-router" {
|
|
@@ -140,15 +212,50 @@ declare module "@tanstack/react-router" {
|
|
|
140
212
|
|
|
141
213
|
function generateRuntime(): string {
|
|
142
214
|
return `/**
|
|
143
|
-
* Runtime invoke proxy
|
|
215
|
+
* Runtime invoke proxy.
|
|
144
216
|
*
|
|
217
|
+
* Turns nested property access into a typed RPC call to /deco/invoke.
|
|
218
|
+
* Converts dot-notation paths to slash-separated keys:
|
|
145
219
|
* invoke.vtex.loaders.productList(props)
|
|
146
220
|
* → POST /deco/invoke/vtex/loaders/productList
|
|
221
|
+
*
|
|
222
|
+
* The .ts suffix variant is also tried if the primary key isn't found
|
|
223
|
+
* (registered loaders may have ".ts" extensions in their keys).
|
|
147
224
|
*/
|
|
148
|
-
|
|
225
|
+
function createNestedInvokeProxy(path: string[] = []): any {
|
|
226
|
+
return new Proxy(
|
|
227
|
+
Object.assign(async (props: any) => {
|
|
228
|
+
const key = path.join("/");
|
|
229
|
+
for (const k of [key, \`\${key}.ts\`]) {
|
|
230
|
+
const response = await fetch(\`/deco/invoke/\${k}\`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: { "Content-Type": "application/json" },
|
|
233
|
+
body: JSON.stringify(props ?? {}),
|
|
234
|
+
});
|
|
235
|
+
if (response.status === 404) continue;
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
throw new Error(\`invoke(\${k}) failed: \${response.status}\`);
|
|
238
|
+
}
|
|
239
|
+
return response.json();
|
|
240
|
+
}
|
|
241
|
+
throw new Error(\`invoke(\${key}) failed: handler not found\`);
|
|
242
|
+
}, {}),
|
|
243
|
+
{
|
|
244
|
+
get(_target: any, prop: string) {
|
|
245
|
+
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
return createNestedInvokeProxy([...path, prop]);
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const invoke = createNestedInvokeProxy() as any;
|
|
149
255
|
|
|
150
|
-
export const
|
|
151
|
-
|
|
256
|
+
export const Runtime = {
|
|
257
|
+
invoke,
|
|
258
|
+
};
|
|
152
259
|
`;
|
|
153
260
|
}
|
|
154
261
|
|
|
@@ -1,69 +1,78 @@
|
|
|
1
1
|
import type { MigrationContext } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
export function generateSetup(ctx: MigrationContext): string {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
for (const f of ctx.files) {
|
|
7
|
-
if (f.category !== "section" || f.action === "delete") continue;
|
|
8
|
-
const name = f.path.replace(/^sections\//, "").replace(/\.tsx$/, "");
|
|
9
|
-
const lower = name.toLowerCase();
|
|
10
|
-
if (lower.includes("header") || lower.includes("footer") || lower.includes("theme")) {
|
|
11
|
-
layoutSections.push(`site/sections/${name}.tsx`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
// Also check islands that became sections
|
|
15
|
-
for (const f of ctx.files) {
|
|
16
|
-
if (f.category !== "island") continue;
|
|
17
|
-
const name = f.path.replace(/^islands\//, "").replace(/\.tsx$/, "");
|
|
18
|
-
const lower = name.toLowerCase();
|
|
19
|
-
if (lower.includes("header") || lower.includes("footer") || lower.includes("theme")) {
|
|
20
|
-
layoutSections.push(`site/sections/${name}.tsx`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
4
|
+
const isVtex = ctx.platform === "vtex";
|
|
5
|
+
const siteName = ctx.siteName;
|
|
23
6
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
]);\n`
|
|
29
|
-
: "";
|
|
30
|
-
|
|
31
|
-
const layoutImport = layoutSections.length > 0
|
|
32
|
-
? "\n registerLayoutSections," : "";
|
|
7
|
+
const productionOrigins = [
|
|
8
|
+
`"https://www.${siteName}.com.br"`,
|
|
9
|
+
`"https://${siteName}.com.br"`,
|
|
10
|
+
];
|
|
33
11
|
|
|
34
12
|
return `/**
|
|
35
|
-
* Site setup —
|
|
13
|
+
* Site setup — orchestrator that wires framework, commerce, and sections.
|
|
14
|
+
*
|
|
15
|
+
* Actual logic lives in focused modules:
|
|
16
|
+
* setup/commerce-loaders.ts — COMMERCE_LOADERS map (data fetchers)
|
|
17
|
+
* setup/section-loaders.ts — registerSectionLoaders (per-section prop enrichment)
|
|
36
18
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
19
|
+
* Section metadata (eager, sync, layout, cache, LoadingFallback) is declared
|
|
20
|
+
* in each section file and auto-extracted by generate-sections.ts.
|
|
39
21
|
*/
|
|
40
|
-
|
|
22
|
+
|
|
23
|
+
import "./cache-config";
|
|
24
|
+
|
|
41
25
|
import {
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
registerCommerceLoaders,
|
|
27
|
+
applySectionConventions,
|
|
44
28
|
} from "@decocms/start/cms";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
29
|
+
import { createSiteSetup } from "@decocms/start/setup";
|
|
30
|
+
import { setInvokeLoaders } from "@decocms/start/admin";${isVtex ? `
|
|
31
|
+
import { createInstrumentedFetch } from "@decocms/start/sdk/instrumentedFetch";
|
|
32
|
+
import { initVtexFromBlocks, setVtexFetch } from "@decocms/apps/vtex";` : ""}
|
|
33
|
+
import { blocks as generatedBlocks } from "./server/cms/blocks.gen";
|
|
34
|
+
import { sectionMeta, syncComponents, loadingFallbacks } from "./server/cms/sections.gen";
|
|
35
|
+
import { PreviewProviders } from "@decocms/start/hooks";
|
|
36
|
+
// @ts-ignore Vite ?url import
|
|
37
|
+
import appCss from "./styles/app.css?url";
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (typeof document === "undefined") {
|
|
51
|
-
setBlocks(generatedBlocks);
|
|
52
|
-
// Auto-configure apps from CMS blocks — registers invoke handlers,
|
|
53
|
-
// app state, and middleware (cookie forwarding, etc.)
|
|
54
|
-
autoconfigApps(generatedBlocks);
|
|
55
|
-
}
|
|
39
|
+
import { COMMERCE_LOADERS } from "./setup/commerce-loaders";
|
|
40
|
+
import "./setup/section-loaders";
|
|
56
41
|
|
|
57
|
-
// --
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
${
|
|
66
|
-
|
|
67
|
-
|
|
42
|
+
// -- Framework setup --
|
|
43
|
+
createSiteSetup({
|
|
44
|
+
sections: import.meta.glob("./sections/**/*.tsx") as Record<string, () => Promise<any>>,
|
|
45
|
+
blocks: generatedBlocks,
|
|
46
|
+
meta: () => import("./server/admin/meta.gen.json").then((m) => m.default),
|
|
47
|
+
css: appCss,
|
|
48
|
+
fonts: [],
|
|
49
|
+
productionOrigins: [
|
|
50
|
+
${productionOrigins.join(",\n ")},
|
|
51
|
+
],
|
|
52
|
+
previewWrapper: PreviewProviders,${isVtex ? `
|
|
53
|
+
initPlatform: (blocks) => initVtexFromBlocks(blocks),` : ""}
|
|
54
|
+
onResolveError: (error, resolveType, context) => {
|
|
55
|
+
console.error(\`[CMS-DEBUG] \${context} "\${resolveType}" failed:\`, error);
|
|
56
|
+
},
|
|
57
|
+
onDanglingReference: (resolveType) => {
|
|
58
|
+
console.warn(\`[CMS-DEBUG] Dangling reference: \${resolveType}\`);
|
|
59
|
+
return null;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
${isVtex ? `
|
|
63
|
+
// -- VTEX wiring --
|
|
64
|
+
setVtexFetch(createInstrumentedFetch("vtex"));
|
|
65
|
+
` : ""}
|
|
66
|
+
// -- Convention-driven section registration --
|
|
67
|
+
applySectionConventions({
|
|
68
|
+
meta: sectionMeta,
|
|
69
|
+
syncComponents,
|
|
70
|
+
loadingFallbacks,
|
|
71
|
+
sectionGlob: import.meta.glob("./sections/**/*.tsx") as Record<string, () => Promise<any>>,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// -- Commerce + invoke --
|
|
75
|
+
registerCommerceLoaders(COMMERCE_LOADERS);
|
|
76
|
+
setInvokeLoaders(() => COMMERCE_LOADERS);
|
|
68
77
|
`;
|
|
69
78
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { MigrationContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export function generateTypeFiles(ctx: MigrationContext): Record<string, string> {
|
|
4
|
+
const files: Record<string, string> = {};
|
|
5
|
+
|
|
6
|
+
files["src/types/widgets.ts"] = `export type ImageWidget = string;
|
|
7
|
+
export type HTMLWidget = string;
|
|
8
|
+
export type VideoWidget = string;
|
|
9
|
+
export type TextWidget = string;
|
|
10
|
+
export type RichText = string;
|
|
11
|
+
export type Secret = string;
|
|
12
|
+
export type Color = string;
|
|
13
|
+
export type ButtonWidget = string;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
files["src/types/deco.ts"] = `export type SectionProps<T extends (...args: any[]) => any> = Awaited<
|
|
17
|
+
ReturnType<T>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
export type Resolved<T = any> = T;
|
|
21
|
+
|
|
22
|
+
export type Section = any;
|
|
23
|
+
|
|
24
|
+
export type Block = any;
|
|
25
|
+
|
|
26
|
+
export type LoadingFallbackProps = {
|
|
27
|
+
height?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function asResolved<T>(value: T): T {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isDeferred(value: unknown): boolean {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const context = {
|
|
39
|
+
isDeploy: false,
|
|
40
|
+
platform: "tanstack-start" as const,
|
|
41
|
+
site: "${ctx.siteName}",
|
|
42
|
+
siteId: 0,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function redirect(_url: string, _status?: number): never {
|
|
46
|
+
throw new Error("redirect is not supported in TanStack Start -- use router navigation instead");
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
files["src/types/commerce-app.ts"] = `export type AppContext = {
|
|
51
|
+
device: "mobile" | "desktop" | "tablet";
|
|
52
|
+
};
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
// Compat shim for apps/website/loaders/extension.ts
|
|
56
|
+
files["src/types/website.ts"] = `export type ExtensionOf<T = any> = T;
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
if (ctx.platform === "vtex") {
|
|
60
|
+
files["src/types/vtex-app.ts"] = `export interface VtexConfig {
|
|
61
|
+
account: string;
|
|
62
|
+
publicUrl?: string;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
files["src/types/vtex-loaders.ts"] = `import type { Product, ProductListingPage } from "@decocms/apps/commerce/types";
|
|
67
|
+
|
|
68
|
+
export interface ProductListProps {
|
|
69
|
+
page: ProductListingPage | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ProductDetailsProps {
|
|
73
|
+
page: {
|
|
74
|
+
product: Product;
|
|
75
|
+
seo?: { title?: string; description?: string; canonical?: string };
|
|
76
|
+
} | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SearchProps {
|
|
80
|
+
query?: string;
|
|
81
|
+
page?: number;
|
|
82
|
+
sort?: string;
|
|
83
|
+
filters?: Record<string, string>;
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
files["src/types/vtex-actions.ts"] = `export interface UserMutation {
|
|
88
|
+
firstName?: string;
|
|
89
|
+
lastName?: string;
|
|
90
|
+
email?: string;
|
|
91
|
+
document?: string;
|
|
92
|
+
phone?: string;
|
|
93
|
+
gender?: string;
|
|
94
|
+
birthDate?: string;
|
|
95
|
+
corporateName?: string;
|
|
96
|
+
corporateDocument?: string;
|
|
97
|
+
stateRegistration?: string;
|
|
98
|
+
isCorporate?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface AddressMutation {
|
|
102
|
+
addressName?: string;
|
|
103
|
+
addressType?: string;
|
|
104
|
+
postalCode?: string;
|
|
105
|
+
street?: string;
|
|
106
|
+
number?: string;
|
|
107
|
+
complement?: string;
|
|
108
|
+
neighborhood?: string;
|
|
109
|
+
city?: string;
|
|
110
|
+
state?: string;
|
|
111
|
+
country?: string;
|
|
112
|
+
receiverName?: string;
|
|
113
|
+
reference?: string;
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return files;
|
|
119
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { MigrationContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export function generateUiComponents(_ctx: MigrationContext): Record<string, string> {
|
|
4
|
+
const files: Record<string, string> = {};
|
|
5
|
+
|
|
6
|
+
files["src/components/ui/Image.tsx"] = `export {
|
|
7
|
+
Image as default,
|
|
8
|
+
Image,
|
|
9
|
+
getOptimizedMediaUrl,
|
|
10
|
+
getSrcSet,
|
|
11
|
+
registerImageCdnDomain,
|
|
12
|
+
getImageCdnDomain,
|
|
13
|
+
FACTORS,
|
|
14
|
+
type ImageProps,
|
|
15
|
+
type FitOptions,
|
|
16
|
+
} from "@decocms/apps/commerce/components/Image";
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
files["src/components/ui/Picture.tsx"] = `import {
|
|
20
|
+
Image,
|
|
21
|
+
getSrcSet,
|
|
22
|
+
type FitOptions,
|
|
23
|
+
type ImageProps,
|
|
24
|
+
} from "@decocms/apps/commerce/components/Image";
|
|
25
|
+
|
|
26
|
+
export interface PictureSourceProps {
|
|
27
|
+
src: string;
|
|
28
|
+
width: number;
|
|
29
|
+
height?: number;
|
|
30
|
+
media: string;
|
|
31
|
+
fit?: FitOptions;
|
|
32
|
+
sizes?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PictureProps extends Omit<ImageProps, "sizes"> {
|
|
36
|
+
sources: PictureSourceProps[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Picture({
|
|
40
|
+
sources,
|
|
41
|
+
src,
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
fit = "cover",
|
|
45
|
+
preload,
|
|
46
|
+
...rest
|
|
47
|
+
}: PictureProps) {
|
|
48
|
+
return (
|
|
49
|
+
<picture>
|
|
50
|
+
{sources.map((source, i) => {
|
|
51
|
+
const srcSet = getSrcSet(source.src, source.width, source.height, source.fit ?? fit);
|
|
52
|
+
return (
|
|
53
|
+
<source
|
|
54
|
+
key={i}
|
|
55
|
+
srcSet={srcSet}
|
|
56
|
+
media={source.media}
|
|
57
|
+
width={source.width}
|
|
58
|
+
height={source.height}
|
|
59
|
+
sizes={source.sizes ?? \`\${source.width}px\`}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
})}
|
|
63
|
+
<Image src={src} width={width} height={height} fit={fit} preload={preload} {...rest} />
|
|
64
|
+
</picture>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
files["src/components/ui/Video.tsx"] = `interface Props {
|
|
70
|
+
src: string;
|
|
71
|
+
width?: number;
|
|
72
|
+
height?: number;
|
|
73
|
+
autoPlay?: boolean;
|
|
74
|
+
muted?: boolean;
|
|
75
|
+
loop?: boolean;
|
|
76
|
+
playsInline?: boolean;
|
|
77
|
+
controls?: boolean;
|
|
78
|
+
className?: string;
|
|
79
|
+
loading?: "lazy" | "eager";
|
|
80
|
+
poster?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default function Video({
|
|
84
|
+
src,
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
autoPlay = true,
|
|
88
|
+
muted = true,
|
|
89
|
+
loop = true,
|
|
90
|
+
playsInline = true,
|
|
91
|
+
controls = false,
|
|
92
|
+
className,
|
|
93
|
+
poster,
|
|
94
|
+
}: Props) {
|
|
95
|
+
return (
|
|
96
|
+
<video
|
|
97
|
+
src={src}
|
|
98
|
+
width={width}
|
|
99
|
+
height={height}
|
|
100
|
+
autoPlay={autoPlay}
|
|
101
|
+
muted={muted}
|
|
102
|
+
loop={loop}
|
|
103
|
+
playsInline={playsInline}
|
|
104
|
+
controls={controls}
|
|
105
|
+
className={className}
|
|
106
|
+
poster={poster}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import type { MigrationContext } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
export function generateViteConfig(ctx: MigrationContext): string {
|
|
4
|
+
const isVtex = ctx.platform === "vtex";
|
|
5
|
+
|
|
6
|
+
const vtexProxy = isVtex ? `
|
|
7
|
+
// VTEX API proxy for local development
|
|
8
|
+
proxy: {
|
|
9
|
+
"/api/": {
|
|
10
|
+
target: "https://\${process.env.VTEX_ACCOUNT || "${ctx.siteName}"}.vtexcommercestable.com.br",
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
secure: true,
|
|
13
|
+
},
|
|
14
|
+
"/checkout/": {
|
|
15
|
+
target: "https://\${process.env.VTEX_ACCOUNT || "${ctx.siteName}"}.vtexcommercestable.com.br",
|
|
16
|
+
changeOrigin: true,
|
|
17
|
+
secure: true,
|
|
18
|
+
},
|
|
19
|
+
},` : "";
|
|
20
|
+
|
|
4
21
|
return `import { cloudflare } from "@cloudflare/vite-plugin";
|
|
5
22
|
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
|
|
6
23
|
import { decoVitePlugin } from "@decocms/start/vite";
|
|
@@ -13,7 +30,7 @@ const srcDir = path.resolve(__dirname, "src");
|
|
|
13
30
|
|
|
14
31
|
export default defineConfig({
|
|
15
32
|
server: {
|
|
16
|
-
allowedHosts: [".decocdn.com"]
|
|
33
|
+
allowedHosts: [".decocdn.com"],${vtexProxy}
|
|
17
34
|
},
|
|
18
35
|
plugins: [
|
|
19
36
|
cloudflare({ viteEnvironment: { name: "ssr" } }),
|