@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.
@@ -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(` description: "O melhor site de compras online para sua casa: compre itens de cozinha, móveis para sala e escritório, acessórios de tecnologia e mais. Clique já!",`);
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
- const DEFAULT_DESCRIPTION =
71
- "${siteTitle} - Tudo para sua casa com os melhores preços.";
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} - Tudo para sua casa",
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-lg text-base-content/60">Tudo para sua casa</p>
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} - Tudo para sua casa",
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
- * Turns nested property access into a typed RPC call to /deco/invoke.
222
- * Converts dot-notation paths to slash-separated keys:
223
- * invoke.vtex.loaders.productList(props)
224
- * POST /deco/invoke/vtex/loaders/productList
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
- * The .ts suffix variant is also tried if the primary key isn't found
227
- * (registered loaders may have ".ts" extensions in their keys).
226
+ * Don't reimplement here extend @decocms/start/sdk/invoke instead.
228
227
  */
229
- function createNestedInvokeProxy(path: string[] = []): any {
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 const invoke = createNestedInvokeProxy() as any;
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
- getRequestHeader,
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
- mergeSetCookies(stripCookieDomain(setCookies));
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
- mergeSetCookies(setCookies);
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
- Other rules — including htmx-residue — stay detect-only.
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