@decocms/start 2.22.0 → 2.24.0
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/.agents/skills/deco-to-tanstack-migration/SKILL.md +12 -7
- package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +57 -47
- package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks-factories.md +186 -0
- package/.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +64 -9
- package/MIGRATION_TOOLING_PLAN.md +235 -0
- package/package.json +1 -1
- package/scripts/migrate/post-cleanup/rules.ts +239 -7
- package/scripts/migrate/post-cleanup/runner.test.ts +268 -1
- package/scripts/migrate/templates/commerce-loaders.ts +2 -1
- package/scripts/migrate/templates/routes.ts +15 -10
- package/scripts/migrate/templates/server-entry.ts +13 -55
- package/scripts/migrate/templates/vite-config.ts +0 -35
- package/scripts/migrate-post-cleanup.ts +4 -2
|
@@ -227,9 +227,11 @@ describe("rule: dead-runtime-shim", () => {
|
|
|
227
227
|
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
228
228
|
expect(r.findings).toHaveLength(1);
|
|
229
229
|
expect(r.findings[0].file).toBe("src/runtime.ts");
|
|
230
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
231
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
230
232
|
});
|
|
231
233
|
|
|
232
|
-
it("does not flag a runtime.ts that exports site-specific helpers", () => {
|
|
234
|
+
it("does not flag a runtime.ts that exports site-specific helpers (no inline proxy)", () => {
|
|
233
235
|
const fs = makeFs({
|
|
234
236
|
"/site/src/runtime.ts": "export const invoke = {};\nexport const customHelper = () => 1;\n",
|
|
235
237
|
});
|
|
@@ -237,6 +239,80 @@ describe("rule: dead-runtime-shim", () => {
|
|
|
237
239
|
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
238
240
|
expect(r.findings).toEqual([]);
|
|
239
241
|
});
|
|
242
|
+
|
|
243
|
+
it("does NOT flag the Wave 15-A canonical re-export shape", () => {
|
|
244
|
+
// The migration template now scaffolds a thin re-export from
|
|
245
|
+
// @decocms/start/sdk plus a Runtime alias. No inline proxy body.
|
|
246
|
+
const fs = makeFs({
|
|
247
|
+
"/site/src/runtime.ts":
|
|
248
|
+
'import { invoke } from "@decocms/start/sdk";\nexport { invoke };\nexport const Runtime = { invoke };\n',
|
|
249
|
+
});
|
|
250
|
+
const report = runAudit(SITE, fs);
|
|
251
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
252
|
+
expect(r.findings).toEqual([]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("flags the legacy 47-line inline createNestedInvokeProxy body (with Runtime export)", () => {
|
|
256
|
+
// The pre-Wave-15-A migration template emitted a full Proxy body
|
|
257
|
+
// alongside `Runtime = { invoke }`. The earlier rule heuristic
|
|
258
|
+
// missed this shape because `Runtime` was not in its allowlist.
|
|
259
|
+
const fs = makeFs({
|
|
260
|
+
"/site/src/runtime.ts": `
|
|
261
|
+
function createNestedInvokeProxy(path: string[] = []): any {
|
|
262
|
+
return new Proxy(
|
|
263
|
+
Object.assign(async (props: any) => {
|
|
264
|
+
const key = path.join("/");
|
|
265
|
+
const response = await fetch(\`/deco/invoke/\${key}\`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/json" },
|
|
268
|
+
body: JSON.stringify(props ?? {}),
|
|
269
|
+
});
|
|
270
|
+
return response.json();
|
|
271
|
+
}, {}),
|
|
272
|
+
{
|
|
273
|
+
get(_target: any, prop: string) {
|
|
274
|
+
if (prop === "then") return undefined;
|
|
275
|
+
return createNestedInvokeProxy([...path, prop]);
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const invoke = createNestedInvokeProxy() as any;
|
|
282
|
+
export const Runtime = { invoke };
|
|
283
|
+
`,
|
|
284
|
+
});
|
|
285
|
+
const report = runAudit(SITE, fs);
|
|
286
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
287
|
+
expect(r.findings).toHaveLength(1);
|
|
288
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
289
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
290
|
+
expect(r.findings[0].message).toContain("inline createNestedInvokeProxy body");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("flags but does NOT auto-fix when inline proxy coexists with site-specific helpers", () => {
|
|
294
|
+
// Defensive: if a site has hand-tuned the runtime file with extra
|
|
295
|
+
// exports beyond invoke/Runtime, deletion would lose data. Surface
|
|
296
|
+
// the issue but skip the destructive fix.
|
|
297
|
+
const fs = makeFs({
|
|
298
|
+
"/site/src/runtime.ts": `
|
|
299
|
+
function createNestedInvokeProxy(path: string[] = []): any {
|
|
300
|
+
return new Proxy(Object.assign(async (props: any) => {}, {}), {});
|
|
301
|
+
}
|
|
302
|
+
export const invoke = createNestedInvokeProxy();
|
|
303
|
+
export const trackPageView = () => console.log("custom tracker");
|
|
304
|
+
`,
|
|
305
|
+
});
|
|
306
|
+
const report = runAudit(SITE, fs);
|
|
307
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
308
|
+
expect(r.findings).toHaveLength(1);
|
|
309
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
310
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
311
|
+
expect(r.findings[0].message).toContain("manual review");
|
|
312
|
+
// applyFix should be a no-op when safeToAutoFix is false — verified
|
|
313
|
+
// implicitly by the runner test for --fix below; here we only
|
|
314
|
+
// assert the metadata gate.
|
|
315
|
+
});
|
|
240
316
|
});
|
|
241
317
|
|
|
242
318
|
describe("rule: site-local-with-globals", () => {
|
|
@@ -590,6 +666,7 @@ describe("runAudit — totals", () => {
|
|
|
590
666
|
[
|
|
591
667
|
"dead-lib-shims",
|
|
592
668
|
"dead-runtime-shim",
|
|
669
|
+
"local-framework-duplicate",
|
|
593
670
|
"local-widgets-types",
|
|
594
671
|
"obsolete-vite-plugins",
|
|
595
672
|
"vtex-shim-regression",
|
|
@@ -1094,3 +1171,193 @@ describe("rule: htmx-residue", () => {
|
|
|
1094
1171
|
expect(r.supportsAutoFix).toBe(false);
|
|
1095
1172
|
});
|
|
1096
1173
|
});
|
|
1174
|
+
|
|
1175
|
+
/* ------------------------------------------------------------------ */
|
|
1176
|
+
/* W15-B-1 — local-framework-duplicate rule */
|
|
1177
|
+
/* ------------------------------------------------------------------ */
|
|
1178
|
+
|
|
1179
|
+
describe("rule: local-framework-duplicate", () => {
|
|
1180
|
+
it("flags src/sdk/clx.ts when content matches the framework export (auto-fixable)", () => {
|
|
1181
|
+
const fs = makeFs({
|
|
1182
|
+
"/site/src/sdk/clx.ts":
|
|
1183
|
+
"export const clx = (...args: (string | null | undefined | false)[]) =>\n" +
|
|
1184
|
+
' args.filter(Boolean).join(" ").replace(/\\s\\s+/g, " ");\n' +
|
|
1185
|
+
"export default clx;\n",
|
|
1186
|
+
});
|
|
1187
|
+
const report = runAudit(SITE, fs);
|
|
1188
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1189
|
+
expect(r.findings).toHaveLength(1);
|
|
1190
|
+
expect(r.findings[0].file).toBe("src/sdk/clx.ts");
|
|
1191
|
+
expect(r.findings[0].message).toContain("pure dup");
|
|
1192
|
+
expect(r.findings[0].meta?.id).toBe("clx");
|
|
1193
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
1194
|
+
expect(r.findings[0].meta?.canonicalImport).toBe("@decocms/start/sdk/clx");
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it("flags src/sdk/clx.ts when site adds a clsx alias (signature still matches)", () => {
|
|
1198
|
+
const fs = makeFs({
|
|
1199
|
+
"/site/src/sdk/clx.ts":
|
|
1200
|
+
"export const clx = (...args: (string | null | undefined | false)[]) =>\n" +
|
|
1201
|
+
' args.filter(Boolean).join(" ").replace(/\\s\\s+/g, " ");\n' +
|
|
1202
|
+
"export const clsx = clx;\n" +
|
|
1203
|
+
"export default clx;\n",
|
|
1204
|
+
});
|
|
1205
|
+
const report = runAudit(SITE, fs);
|
|
1206
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1207
|
+
expect(r.findings).toHaveLength(1);
|
|
1208
|
+
expect(r.findings[0].meta?.id).toBe("clx");
|
|
1209
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
it("does NOT flag a clx.ts that has been forked (signature mismatch)", () => {
|
|
1213
|
+
const fs = makeFs({
|
|
1214
|
+
// Realistic fork: uses lodash-style cn from a different package.
|
|
1215
|
+
"/site/src/sdk/clx.ts":
|
|
1216
|
+
'import { cn } from "lodash";\nexport const clx = cn;\nexport default clx;\n',
|
|
1217
|
+
});
|
|
1218
|
+
const report = runAudit(SITE, fs);
|
|
1219
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1220
|
+
expect(r.findings).toEqual([]);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
it("flags src/sdk/useSendEvent.ts as warn-only (typing regression risk)", () => {
|
|
1224
|
+
const fs = makeFs({
|
|
1225
|
+
"/site/src/sdk/useSendEvent.ts":
|
|
1226
|
+
'import { AnalyticsEvent } from "@decocms/apps/commerce/types";\n' +
|
|
1227
|
+
"export const useSendEvent = <E extends AnalyticsEvent>(\n" +
|
|
1228
|
+
" { event, on }: { event: E; on: 'click' | 'view' | 'change' },\n" +
|
|
1229
|
+
") => ({\n" +
|
|
1230
|
+
' "data-event": encodeURIComponent(JSON.stringify(event)),\n' +
|
|
1231
|
+
' "data-event-trigger": on,\n' +
|
|
1232
|
+
"});\n",
|
|
1233
|
+
});
|
|
1234
|
+
const report = runAudit(SITE, fs);
|
|
1235
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1236
|
+
expect(r.findings).toHaveLength(1);
|
|
1237
|
+
expect(r.findings[0].file).toBe("src/sdk/useSendEvent.ts");
|
|
1238
|
+
expect(r.findings[0].message).toContain("partial overlap");
|
|
1239
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
1240
|
+
expect(r.findings[0].fix).toContain("typed AnalyticsEvent generic");
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
it("flags src/matchers/location.ts as warn-only (behaviour-superset opportunity)", () => {
|
|
1244
|
+
const fs = makeFs({
|
|
1245
|
+
"/site/src/matchers/location.ts":
|
|
1246
|
+
'import { registerMatcher } from "@decocms/start/cms";\n' +
|
|
1247
|
+
"export function registerLocationMatcher(): void {\n" +
|
|
1248
|
+
' registerMatcher("website/matchers/location.ts", (rule, ctx) => {\n' +
|
|
1249
|
+
" const cookies = ctx.cookies ?? {};\n" +
|
|
1250
|
+
" const country = cookies.__cf_geo_country ?? '';\n" +
|
|
1251
|
+
" return Boolean(country);\n" +
|
|
1252
|
+
" });\n" +
|
|
1253
|
+
"}\n",
|
|
1254
|
+
});
|
|
1255
|
+
const report = runAudit(SITE, fs);
|
|
1256
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1257
|
+
expect(r.findings).toHaveLength(1);
|
|
1258
|
+
expect(r.findings[0].file).toBe("src/matchers/location.ts");
|
|
1259
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
1260
|
+
expect(r.findings[0].fix).toContain("registerBuiltinMatchers");
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
it("emits zero findings on a clean tree (no duplicates present)", () => {
|
|
1264
|
+
const fs = makeFs({
|
|
1265
|
+
"/site/src/sections/Hello.tsx":
|
|
1266
|
+
'import { clx } from "@decocms/start/sdk/clx";\nexport default () => <div className={clx("a")}>x</div>;\n',
|
|
1267
|
+
});
|
|
1268
|
+
const report = runAudit(SITE, fs);
|
|
1269
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1270
|
+
expect(r.findings).toEqual([]);
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
it("emits warning severity for both auto-fixable AND warn-only entries (--strict gates everything)", () => {
|
|
1274
|
+
const fs = makeFs({
|
|
1275
|
+
"/site/src/sdk/clx.ts":
|
|
1276
|
+
'export const clx = (...args: any[]) => args.filter(Boolean).join(" ").replace(/\\s\\s+/g, " ");\n',
|
|
1277
|
+
"/site/src/sdk/useSendEvent.ts":
|
|
1278
|
+
'export const useSendEvent = (e: any) => ({ "data-event": encodeURIComponent(JSON.stringify(e)) });\n',
|
|
1279
|
+
});
|
|
1280
|
+
const report = runAudit(SITE, fs);
|
|
1281
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1282
|
+
for (const f of r.findings) expect(f.severity).toBe("warning");
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it("auto-fix rewrites importers using ~/sdk/clx and deletes the file", () => {
|
|
1286
|
+
const { fs, writer, store } = makeMutableFs({
|
|
1287
|
+
"/site/src/sdk/clx.ts":
|
|
1288
|
+
'export const clx = (...args: any[]) => args.filter(Boolean).join(" ").replace(/\\s\\s+/g, " ");\n',
|
|
1289
|
+
"/site/src/components/A.tsx":
|
|
1290
|
+
'import { clx } from "~/sdk/clx";\nexport default () => clx("x");\n',
|
|
1291
|
+
"/site/src/components/B.tsx":
|
|
1292
|
+
'import { clx } from "~/sdk/clx";\nimport React from "react";\nexport default () => clx("y");\n',
|
|
1293
|
+
"/site/src/components/Unrelated.tsx":
|
|
1294
|
+
'import { clx } from "@decocms/start/sdk/clx";\nexport default () => clx("z");\n',
|
|
1295
|
+
});
|
|
1296
|
+
const report = runAudit(SITE, fs, { writer });
|
|
1297
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1298
|
+
expect(r.fixes).toBeDefined();
|
|
1299
|
+
expect(r.fixes!.length).toBe(1);
|
|
1300
|
+
expect(r.fixes![0].kind).toBe("rewrite-imports+delete");
|
|
1301
|
+
expect(r.fixes![0].detail).toContain("rewrote 2 import(s)");
|
|
1302
|
+
// File deleted from the in-memory store
|
|
1303
|
+
expect(store["/site/src/sdk/clx.ts"]).toBeUndefined();
|
|
1304
|
+
// Importers rewritten
|
|
1305
|
+
expect(store["/site/src/components/A.tsx"]).toContain(
|
|
1306
|
+
'from "@decocms/start/sdk/clx"',
|
|
1307
|
+
);
|
|
1308
|
+
expect(store["/site/src/components/B.tsx"]).toContain(
|
|
1309
|
+
'from "@decocms/start/sdk/clx"',
|
|
1310
|
+
);
|
|
1311
|
+
// Already-canonical import untouched
|
|
1312
|
+
expect(store["/site/src/components/Unrelated.tsx"]).toContain(
|
|
1313
|
+
'from "@decocms/start/sdk/clx"',
|
|
1314
|
+
);
|
|
1315
|
+
expect(store["/site/src/components/Unrelated.tsx"]).not.toMatch(/~\/sdk\/clx/);
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
it("auto-fix is a no-op for warn-only entries (does NOT delete partial-overlap files)", () => {
|
|
1319
|
+
const { fs, writer, store } = makeMutableFs({
|
|
1320
|
+
"/site/src/sdk/useSendEvent.ts":
|
|
1321
|
+
'import { AnalyticsEvent } from "@decocms/apps/commerce/types";\n' +
|
|
1322
|
+
"export const useSendEvent = <E extends AnalyticsEvent>() => ({\n" +
|
|
1323
|
+
' "data-event": encodeURIComponent("x"),\n' +
|
|
1324
|
+
"});\n",
|
|
1325
|
+
"/site/src/matchers/location.ts":
|
|
1326
|
+
'import { registerMatcher } from "@decocms/start/cms";\n' +
|
|
1327
|
+
'registerMatcher("website/matchers/location.ts", () => Boolean(__cf_geo_country));\n',
|
|
1328
|
+
});
|
|
1329
|
+
const report = runAudit(SITE, fs, { writer });
|
|
1330
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1331
|
+
expect(r.findings.length).toBe(2);
|
|
1332
|
+
// Both fixes are no-ops because safeToAutoFix === false.
|
|
1333
|
+
expect(r.fixes ?? []).toEqual([]);
|
|
1334
|
+
// Files preserved.
|
|
1335
|
+
expect(store["/site/src/sdk/useSendEvent.ts"]).toBeDefined();
|
|
1336
|
+
expect(store["/site/src/matchers/location.ts"]).toBeDefined();
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
it("auto-fix runs only on auto-fixable entries when both kinds coexist", () => {
|
|
1340
|
+
const { fs, writer, store } = makeMutableFs({
|
|
1341
|
+
"/site/src/sdk/clx.ts":
|
|
1342
|
+
'export const clx = (...args: any[]) => args.filter(Boolean).join(" ").replace(/\\s\\s+/g, " ");\n',
|
|
1343
|
+
"/site/src/sdk/useSendEvent.ts":
|
|
1344
|
+
'export const useSendEvent = (e: any) => ({ "data-event": encodeURIComponent(JSON.stringify(e)) });\n',
|
|
1345
|
+
"/site/src/components/A.tsx":
|
|
1346
|
+
'import { clx } from "~/sdk/clx";\nexport default () => clx("x");\n',
|
|
1347
|
+
});
|
|
1348
|
+
const report = runAudit(SITE, fs, { writer });
|
|
1349
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1350
|
+
expect(r.findings.length).toBe(2);
|
|
1351
|
+
expect(r.fixes!.length).toBe(1); // only clx auto-fixed
|
|
1352
|
+
expect(r.fixes![0].file).toBe("src/sdk/clx.ts");
|
|
1353
|
+
expect(store["/site/src/sdk/clx.ts"]).toBeUndefined();
|
|
1354
|
+
expect(store["/site/src/sdk/useSendEvent.ts"]).toBeDefined();
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
it("supportsAutoFix is true (the rule has applyFix even though some entries are warn-only)", () => {
|
|
1358
|
+
const fs = makeFs({});
|
|
1359
|
+
const report = runAudit(SITE, fs);
|
|
1360
|
+
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
1361
|
+
expect(r.supportsAutoFix).toBe(true);
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
@@ -204,7 +204,8 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
204
204
|
lines.push(` breadcrumb: createBreadcrumbFromPath(url.pathname, url, collection.name) ?? {},`);
|
|
205
205
|
lines.push(` seo: {`);
|
|
206
206
|
lines.push(` title: collection.name,`);
|
|
207
|
-
lines.push(`
|
|
207
|
+
lines.push(` // MIGRATION TODO: replace with site-specific category description`);
|
|
208
|
+
lines.push(` description: collection.name,`);
|
|
208
209
|
lines.push(` noIndexing: false,`);
|
|
209
210
|
lines.push(` canonical: url.toString(),`);
|
|
210
211
|
lines.push(` },`);
|
|
@@ -67,8 +67,10 @@ import { DecoRootLayout } from "@decocms/start/hooks";
|
|
|
67
67
|
// @ts-ignore Vite ?url import
|
|
68
68
|
import appCss from "../styles/app.css?url";
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// MIGRATION TODO: customize description, OG image, and locale for ${siteTitle}.
|
|
71
|
+
// The migration scaffold leaves a generic default so it never falls through;
|
|
72
|
+
// CMS \`Site.seo\` overrides this once block resolution kicks in.
|
|
73
|
+
const DEFAULT_DESCRIPTION = "${siteTitle}";
|
|
72
74
|
|
|
73
75
|
export const Route = createRootRoute({
|
|
74
76
|
head: () => ({
|
|
@@ -108,11 +110,14 @@ function generateIndex(ctx: MigrationContext, siteTitle: string): string {
|
|
|
108
110
|
import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
109
111
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
110
112
|
|
|
113
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
|
|
114
|
+
// copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
|
|
115
|
+
// resolution kicks in, so leaving the migration scaffold defaults is safe
|
|
116
|
+
// but visible in pre-block-resolution states.
|
|
111
117
|
export const Route = createFileRoute("/")({
|
|
112
118
|
...cmsHomeRouteConfig({
|
|
113
|
-
defaultTitle: "${siteTitle}
|
|
114
|
-
defaultDescription:
|
|
115
|
-
"${siteTitle} - Tudo para sua casa com os melhores preços.",
|
|
119
|
+
defaultTitle: "${siteTitle}",
|
|
120
|
+
defaultDescription: "${siteTitle}",
|
|
116
121
|
siteName: "${siteTitle}",
|
|
117
122
|
}),
|
|
118
123
|
component: HomePage,
|
|
@@ -126,8 +131,7 @@ function HomePage() {
|
|
|
126
131
|
<div className="min-h-screen flex items-center justify-center">
|
|
127
132
|
<div className="text-center">
|
|
128
133
|
<h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
|
|
129
|
-
<p className="text-
|
|
130
|
-
<p className="text-sm text-base-content/40 mt-2">Nenhuma página CMS encontrada para /</p>
|
|
134
|
+
<p className="text-sm text-base-content/40 mt-2">No CMS page registered for /</p>
|
|
131
135
|
</div>
|
|
132
136
|
</div>
|
|
133
137
|
);
|
|
@@ -152,11 +156,12 @@ function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
|
|
|
152
156
|
import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
153
157
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
154
158
|
|
|
159
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
|
|
160
|
+
// (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
|
|
155
161
|
const routeConfig = cmsRouteConfig({
|
|
156
162
|
siteName: "${siteTitle}",
|
|
157
|
-
defaultTitle: "${siteTitle}
|
|
158
|
-
defaultDescription:
|
|
159
|
-
"${siteTitle} - Tudo para sua casa com os melhores preços.",
|
|
163
|
+
defaultTitle: "${siteTitle}",
|
|
164
|
+
defaultDescription: "${siteTitle}",
|
|
160
165
|
ignoreSearchParams: ["skuId"],
|
|
161
166
|
});
|
|
162
167
|
|
|
@@ -216,50 +216,20 @@ declare module "@tanstack/react-router" {
|
|
|
216
216
|
|
|
217
217
|
function generateRuntime(): string {
|
|
218
218
|
return `/**
|
|
219
|
-
* Runtime invoke proxy.
|
|
219
|
+
* Runtime invoke proxy — re-exports the framework canonical from @decocms/start/sdk.
|
|
220
220
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
221
|
+
* The implementation (typed RPC over /deco/invoke, dotted-path proxy, .ts
|
|
222
|
+
* suffix fallback) lives in @decocms/start/sdk/invoke. This file exists so
|
|
223
|
+
* existing site code can keep \`import { invoke } from "~/runtime"\` and
|
|
224
|
+
* \`Runtime.invoke\` shapes without churn.
|
|
225
225
|
*
|
|
226
|
-
*
|
|
227
|
-
* (registered loaders may have ".ts" extensions in their keys).
|
|
226
|
+
* Don't reimplement here — extend @decocms/start/sdk/invoke instead.
|
|
228
227
|
*/
|
|
229
|
-
|
|
230
|
-
return new Proxy(
|
|
231
|
-
Object.assign(async (props: any) => {
|
|
232
|
-
const key = path.join("/");
|
|
233
|
-
for (const k of [key, \`\${key}.ts\`]) {
|
|
234
|
-
const response = await fetch(\`/deco/invoke/\${k}\`, {
|
|
235
|
-
method: "POST",
|
|
236
|
-
headers: { "Content-Type": "application/json" },
|
|
237
|
-
body: JSON.stringify(props ?? {}),
|
|
238
|
-
});
|
|
239
|
-
if (response.status === 404) continue;
|
|
240
|
-
if (!response.ok) {
|
|
241
|
-
throw new Error(\`invoke(\${k}) failed: \${response.status}\`);
|
|
242
|
-
}
|
|
243
|
-
return response.json();
|
|
244
|
-
}
|
|
245
|
-
throw new Error(\`invoke(\${key}) failed: handler not found\`);
|
|
246
|
-
}, {}),
|
|
247
|
-
{
|
|
248
|
-
get(_target: any, prop: string) {
|
|
249
|
-
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
return createNestedInvokeProxy([...path, prop]);
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
}
|
|
228
|
+
import { invoke } from "@decocms/start/sdk";
|
|
257
229
|
|
|
258
|
-
export
|
|
230
|
+
export { invoke };
|
|
259
231
|
|
|
260
|
-
export const Runtime = {
|
|
261
|
-
invoke,
|
|
262
|
-
};
|
|
232
|
+
export const Runtime = { invoke };
|
|
263
233
|
`;
|
|
264
234
|
}
|
|
265
235
|
|
|
@@ -299,11 +269,8 @@ export const invoke = {} as const;
|
|
|
299
269
|
* auto-generated in invoke.gen.ts. Run \`npm run generate:invoke\` to update.
|
|
300
270
|
*/
|
|
301
271
|
import { createServerFn } from "@tanstack/react-start";
|
|
302
|
-
import {
|
|
303
|
-
|
|
304
|
-
getResponseHeaders,
|
|
305
|
-
setResponseHeader,
|
|
306
|
-
} from "@tanstack/react-start/server";
|
|
272
|
+
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
273
|
+
import { forwardResponseCookies } from "@decocms/start/sdk/cookiePassthrough";
|
|
307
274
|
import { vtexActions } from "./invoke.gen";
|
|
308
275
|
${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";\n` : ""}import {
|
|
309
276
|
extractVtexCookiesFromHeader,
|
|
@@ -314,15 +281,6 @@ ${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";
|
|
|
314
281
|
|
|
315
282
|
export type { OrderForm } from "./invoke.gen";
|
|
316
283
|
|
|
317
|
-
function mergeSetCookies(newCookies: string[]): void {
|
|
318
|
-
if (newCookies.length === 0) return;
|
|
319
|
-
const existing: string[] =
|
|
320
|
-
typeof getResponseHeaders().getSetCookie === "function"
|
|
321
|
-
? getResponseHeaders().getSetCookie()
|
|
322
|
-
: [];
|
|
323
|
-
setResponseHeader("set-cookie", [...existing, ...newCookies]);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
284
|
function getVtexCookies(): string {
|
|
327
285
|
return extractVtexCookiesFromHeader(getRequestHeader("cookie") ?? "");
|
|
328
286
|
}
|
|
@@ -336,7 +294,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
|
|
|
336
294
|
} as any);
|
|
337
295
|
if (result instanceof Response) {
|
|
338
296
|
const setCookies = result.headers.getSetCookie?.() ?? [];
|
|
339
|
-
|
|
297
|
+
forwardResponseCookies(stripCookieDomain(setCookies));
|
|
340
298
|
return result.json();
|
|
341
299
|
}
|
|
342
300
|
return result;
|
|
@@ -344,7 +302,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
|
|
|
344
302
|
` : ""}const _logout = createServerFn({ method: "POST" }).handler(
|
|
345
303
|
async (): Promise<{ success: boolean }> => {
|
|
346
304
|
const { setCookies } = await performVtexLogout(getVtexCookies());
|
|
347
|
-
|
|
305
|
+
forwardResponseCookies(setCookies);
|
|
348
306
|
return { success: true };
|
|
349
307
|
},
|
|
350
308
|
);
|
|
@@ -53,41 +53,6 @@ export default defineConfig({
|
|
|
53
53
|
}),
|
|
54
54
|
tailwindcss(),
|
|
55
55
|
decoVitePlugin(),
|
|
56
|
-
{
|
|
57
|
-
name: "site-manual-chunks",
|
|
58
|
-
config(_cfg, { command }) {
|
|
59
|
-
if (command !== "build") return;
|
|
60
|
-
return {
|
|
61
|
-
build: {
|
|
62
|
-
rollupOptions: {
|
|
63
|
-
output: {
|
|
64
|
-
manualChunks(id: string) {
|
|
65
|
-
if (id.includes("node_modules/react-dom") || id.includes("node_modules/react/"))
|
|
66
|
-
return "vendor-react";
|
|
67
|
-
if (id.includes("@tanstack/react-router") || id.includes("@tanstack/start"))
|
|
68
|
-
return "vendor-router";
|
|
69
|
-
if (id.includes("@tanstack/react-query")) return "vendor-query";
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: "deco-stub-meta-gen",
|
|
79
|
-
enforce: "pre" as const,
|
|
80
|
-
resolveId(id, importer, options) {
|
|
81
|
-
if (!options?.ssr && importer && id.includes("meta.gen")) {
|
|
82
|
-
return "\\0stub:meta-gen";
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
load(id) {
|
|
86
|
-
if (id === "\\0stub:meta-gen") {
|
|
87
|
-
return "export default {};";
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
56
|
],
|
|
92
57
|
build: {
|
|
93
58
|
sourcemap: "hidden",
|
|
@@ -77,8 +77,10 @@ function showHelp() {
|
|
|
77
77
|
--source <dir> Site directory to audit (default: .)
|
|
78
78
|
--fix Auto-apply mechanical fixes for the safe rules
|
|
79
79
|
(dead-lib-shims, dead-runtime-shim, local-widgets-types,
|
|
80
|
-
vtex-shim-regression swap subset, obsolete-vite-plugins
|
|
81
|
-
|
|
80
|
+
vtex-shim-regression swap subset, obsolete-vite-plugins,
|
|
81
|
+
local-framework-duplicate auto-fixable subset).
|
|
82
|
+
Other rules — including htmx-residue and the warn-only
|
|
83
|
+
entries of local-framework-duplicate — stay detect-only.
|
|
82
84
|
--json Emit machine-readable JSON instead of pretty text
|
|
83
85
|
--strict Exit code 2 if any warning-severity findings exist
|
|
84
86
|
--help, -h Show this help
|