@decocms/start 2.15.0 → 2.17.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/references/post-migration-cleanup.md +24 -6
- package/.cursor/rules/migration-tooling-policy.mdc +111 -0
- package/CLAUDE.md +4 -0
- package/MIGRATION_TOOLING_PLAN.md +891 -0
- package/package.json +1 -1
- package/scripts/migrate/post-cleanup/rules.ts +54 -0
- package/scripts/migrate/post-cleanup/runner.test.ts +124 -1
- package/scripts/migrate/templates/hooks.test.ts +56 -0
- package/scripts/migrate/templates/hooks.ts +39 -8
- package/scripts/migrate/templates/lib-utils.test.ts +48 -0
- package/scripts/migrate/templates/lib-utils.ts +51 -12
package/package.json
CHANGED
|
@@ -452,6 +452,60 @@ const ruleVtexShimRegression: Rule = {
|
|
|
452
452
|
}
|
|
453
453
|
return findings;
|
|
454
454
|
},
|
|
455
|
+
applyFix({ siteDir, fs }, findings, writer): FixAction[] {
|
|
456
|
+
if (findings.length === 0) return [];
|
|
457
|
+
const actions: FixAction[] = [];
|
|
458
|
+
|
|
459
|
+
// Per-file rewrite. Conservative: only swap the import path when EVERY
|
|
460
|
+
// imported symbol from the shim is a `kind: "swap"` hint pointing at
|
|
461
|
+
// the same canonical module. Mixed surfaces (some swap + some
|
|
462
|
+
// refactor, or a real impl + a stub) stay untouched — those need a
|
|
463
|
+
// human looking at call-site signatures.
|
|
464
|
+
for (const finding of findings) {
|
|
465
|
+
const stubsBySim = (finding.meta?.stubsBySim ?? {}) as Record<string, string[]>;
|
|
466
|
+
const abs = `${siteDir}/${finding.file}`;
|
|
467
|
+
if (!fs.exists(abs)) continue;
|
|
468
|
+
|
|
469
|
+
let content = fs.readText(abs);
|
|
470
|
+
let modified = false;
|
|
471
|
+
|
|
472
|
+
for (const [shim, _stubSyms] of Object.entries(stubsBySim)) {
|
|
473
|
+
const oldSpec = `~/lib/${shim}`;
|
|
474
|
+
const importedSymbols = namedRuntimeImportsFrom(content, oldSpec);
|
|
475
|
+
if (importedSymbols.length === 0) continue;
|
|
476
|
+
|
|
477
|
+
// Every imported symbol must be a swap-kind hint AND every hint
|
|
478
|
+
// must point at the same canonical module — otherwise we'd
|
|
479
|
+
// either drop a real impl or split the import across two paths,
|
|
480
|
+
// both of which are unsafe to do mechanically here.
|
|
481
|
+
const hints = importedSymbols.map((s) => STUB_FIX_HINTS[s]);
|
|
482
|
+
const allSwap = hints.every((h) => h && h.kind === "swap");
|
|
483
|
+
if (!allSwap) continue;
|
|
484
|
+
const targets = new Set(
|
|
485
|
+
hints.map((h) => (h as { kind: "swap"; canonical: string }).canonical),
|
|
486
|
+
);
|
|
487
|
+
if (targets.size !== 1) continue;
|
|
488
|
+
const target = [...targets][0];
|
|
489
|
+
|
|
490
|
+
const escaped = oldSpec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
491
|
+
const importLineRe = new RegExp(`from\\s+(['"])${escaped}\\1`, "g");
|
|
492
|
+
const next = content.replace(importLineRe, (_m, q) => `from ${q}${target}${q}`);
|
|
493
|
+
if (next !== content) {
|
|
494
|
+
content = next;
|
|
495
|
+
modified = true;
|
|
496
|
+
actions.push({
|
|
497
|
+
file: finding.file,
|
|
498
|
+
kind: "rewrite-imports",
|
|
499
|
+
detail: `${oldSpec} → ${target} (${importedSymbols.join(", ")})`,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (modified) writer.writeText(abs, content);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return actions;
|
|
508
|
+
},
|
|
455
509
|
};
|
|
456
510
|
|
|
457
511
|
/* ------------------------------------------------------------------ */
|
|
@@ -587,7 +587,12 @@ describe("runAudit — totals", () => {
|
|
|
587
587
|
.map((r) => r.rule)
|
|
588
588
|
.sort();
|
|
589
589
|
expect(supported).toEqual(
|
|
590
|
-
[
|
|
590
|
+
[
|
|
591
|
+
"dead-lib-shims",
|
|
592
|
+
"dead-runtime-shim",
|
|
593
|
+
"local-widgets-types",
|
|
594
|
+
"vtex-shim-regression",
|
|
595
|
+
].sort(),
|
|
591
596
|
);
|
|
592
597
|
});
|
|
593
598
|
});
|
|
@@ -681,3 +686,121 @@ describe("runAudit — fix mode", () => {
|
|
|
681
686
|
expect(store["/site/src/sections/A.tsx"]).toContain('"~/types/widgets-extra"');
|
|
682
687
|
});
|
|
683
688
|
});
|
|
689
|
+
|
|
690
|
+
/* ------------------------------------------------------------------ */
|
|
691
|
+
/* W12-D / W12-E — vtex-shim-regression --fix for swap-able cases */
|
|
692
|
+
/* ------------------------------------------------------------------ */
|
|
693
|
+
|
|
694
|
+
describe("runAudit — fix mode — vtex-shim-regression swap cases", () => {
|
|
695
|
+
it("rewrites `toProduct` import to canonical when it is the only stub imported", () => {
|
|
696
|
+
const { fs, writer, store } = makeMutableFs({
|
|
697
|
+
"/site/src/lib/vtex-transform.ts":
|
|
698
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
699
|
+
"/site/src/loaders/search.ts":
|
|
700
|
+
'import { toProduct } from "~/lib/vtex-transform";\nconsole.log(toProduct);\n',
|
|
701
|
+
});
|
|
702
|
+
const report = runAudit(SITE, fs, { writer });
|
|
703
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
704
|
+
expect(r.fixes).toHaveLength(1);
|
|
705
|
+
expect(r.fixes![0].file).toBe("src/loaders/search.ts");
|
|
706
|
+
expect(r.fixes![0].kind).toBe("rewrite-imports");
|
|
707
|
+
expect(r.fixes![0].detail).toContain("@decocms/apps/vtex/utils/transform");
|
|
708
|
+
expect(r.fixes![0].detail).toContain("toProduct");
|
|
709
|
+
expect(store["/site/src/loaders/search.ts"]).toContain(
|
|
710
|
+
'"@decocms/apps/vtex/utils/transform"',
|
|
711
|
+
);
|
|
712
|
+
expect(store["/site/src/loaders/search.ts"]).not.toContain('"~/lib/vtex-transform"');
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("rewrites `withSegmentCookie` import to canonical when it is the only stub imported", () => {
|
|
716
|
+
const { fs, writer, store } = makeMutableFs({
|
|
717
|
+
// Mirrors the post-#123 throwing-stub body — `throw new Error(...)`
|
|
718
|
+
// is recognised by the shim classifier as `unconditional throw`.
|
|
719
|
+
"/site/src/lib/vtex-segment.ts":
|
|
720
|
+
'export function withSegmentCookie(..._args: any[]): any { throw new Error("stub"); }\n',
|
|
721
|
+
"/site/src/loaders/x.ts":
|
|
722
|
+
'import { withSegmentCookie } from "~/lib/vtex-segment";\nconsole.log(withSegmentCookie);\n',
|
|
723
|
+
});
|
|
724
|
+
const report = runAudit(SITE, fs, { writer });
|
|
725
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
726
|
+
expect(r.fixes).toHaveLength(1);
|
|
727
|
+
expect(r.fixes![0].detail).toContain("@decocms/apps/vtex/utils/segment");
|
|
728
|
+
expect(store["/site/src/loaders/x.ts"]).toContain(
|
|
729
|
+
'"@decocms/apps/vtex/utils/segment"',
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it("does NOT rewrite mixed swap+refactor surface (getSegmentFromBag is refactor-only)", () => {
|
|
734
|
+
const { fs, writer, store } = makeMutableFs({
|
|
735
|
+
"/site/src/lib/vtex-segment.ts":
|
|
736
|
+
"export function getSegmentFromBag(_req?: any): null { return null; }\n" +
|
|
737
|
+
'export function withSegmentCookie(..._args: any[]): any { throw new Error("stub"); }\n',
|
|
738
|
+
"/site/src/loaders/x.ts":
|
|
739
|
+
'import { getSegmentFromBag, withSegmentCookie } from "~/lib/vtex-segment";\n',
|
|
740
|
+
});
|
|
741
|
+
const before = store["/site/src/loaders/x.ts"];
|
|
742
|
+
const report = runAudit(SITE, fs, { writer });
|
|
743
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
744
|
+
// Finding still emitted (audit), but no fix applied (mixed surface).
|
|
745
|
+
expect(r.findings).toHaveLength(1);
|
|
746
|
+
expect(r.fixes).toEqual([]);
|
|
747
|
+
expect(store["/site/src/loaders/x.ts"]).toBe(before);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it("does NOT rewrite when the file imports a real impl from the same shim", () => {
|
|
751
|
+
// vtex-intelligent-search exports both `getISCookiesFromBag` (stub →
|
|
752
|
+
// refactor) and `isFilterParam` (real impl, not in STUB_FIX_HINTS).
|
|
753
|
+
// Even if only one symbol is imported, the rule's classifier already
|
|
754
|
+
// skips real impls. But if a file imports BOTH, our --fix must not
|
|
755
|
+
// rewrite — the canonical doesn't expose isFilterParam.
|
|
756
|
+
const { fs, writer, store } = makeMutableFs({
|
|
757
|
+
"/site/src/lib/vtex-intelligent-search.ts":
|
|
758
|
+
"export function getISCookiesFromBag(_r?: any): Record<string,string> { return {}; }\n" +
|
|
759
|
+
"export function isFilterParam(k: string): boolean { return k.startsWith('filter.'); }\n",
|
|
760
|
+
"/site/src/loaders/x.ts":
|
|
761
|
+
'import { getISCookiesFromBag, isFilterParam } from "~/lib/vtex-intelligent-search";\n',
|
|
762
|
+
});
|
|
763
|
+
const before = store["/site/src/loaders/x.ts"];
|
|
764
|
+
const report = runAudit(SITE, fs, { writer });
|
|
765
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
766
|
+
expect(r.findings).toHaveLength(1);
|
|
767
|
+
expect(r.fixes).toEqual([]);
|
|
768
|
+
expect(store["/site/src/loaders/x.ts"]).toBe(before);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("rewrites multiple files using the same swap-able shim in one pass", () => {
|
|
772
|
+
const { fs, writer, store } = makeMutableFs({
|
|
773
|
+
"/site/src/lib/vtex-transform.ts":
|
|
774
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
775
|
+
"/site/src/loaders/A.ts":
|
|
776
|
+
'import { toProduct } from "~/lib/vtex-transform";\n',
|
|
777
|
+
"/site/src/loaders/B.ts":
|
|
778
|
+
"import { toProduct } from '~/lib/vtex-transform';\n",
|
|
779
|
+
});
|
|
780
|
+
const report = runAudit(SITE, fs, { writer });
|
|
781
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
782
|
+
expect(r.fixes!.length).toBe(2);
|
|
783
|
+
expect(store["/site/src/loaders/A.ts"]).toContain(
|
|
784
|
+
'"@decocms/apps/vtex/utils/transform"',
|
|
785
|
+
);
|
|
786
|
+
expect(store["/site/src/loaders/B.ts"]).toContain(
|
|
787
|
+
"'@decocms/apps/vtex/utils/transform'",
|
|
788
|
+
);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it("preserves the named-imports list verbatim when swapping", () => {
|
|
792
|
+
// The fix only rewrites the FROM clause, not the imported names. Keeps
|
|
793
|
+
// local aliases (`as`) intact.
|
|
794
|
+
const { fs, writer, store } = makeMutableFs({
|
|
795
|
+
"/site/src/lib/vtex-transform.ts":
|
|
796
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
797
|
+
"/site/src/loaders/x.ts":
|
|
798
|
+
'import { toProduct as toP } from "~/lib/vtex-transform";\n' +
|
|
799
|
+
"console.log(toP);\n",
|
|
800
|
+
});
|
|
801
|
+
runAudit(SITE, fs, { writer });
|
|
802
|
+
expect(store["/site/src/loaders/x.ts"]).toContain(
|
|
803
|
+
'import { toProduct as toP } from "@decocms/apps/vtex/utils/transform"',
|
|
804
|
+
);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
@@ -66,6 +66,43 @@ describe("generateHooks (vtex)", () => {
|
|
|
66
66
|
// Should be well under 20 lines (factory call + re-export + imports).
|
|
67
67
|
expect(lineCount).toBeLessThan(20);
|
|
68
68
|
});
|
|
69
|
+
|
|
70
|
+
it("useUser is the createUseUser factory shim (no signal-stub boilerplate)", () => {
|
|
71
|
+
const code = files["src/hooks/useUser.ts"];
|
|
72
|
+
expect(code).toContain(
|
|
73
|
+
'import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser"',
|
|
74
|
+
);
|
|
75
|
+
expect(code).toContain('import { invoke } from "~/server/invoke"');
|
|
76
|
+
expect(code).toContain(
|
|
77
|
+
'export type { Person } from "@decocms/apps/vtex/loaders/user"',
|
|
78
|
+
);
|
|
79
|
+
expect(code).toContain("export const { useUser, resetUser } = createUseUser");
|
|
80
|
+
// Must NOT scaffold the legacy signal stub.
|
|
81
|
+
expect(code).not.toContain('signal<User | null>(null)');
|
|
82
|
+
expect(code).not.toContain("export interface User {");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("useWishlist is the createUseWishlist factory shim", () => {
|
|
86
|
+
const code = files["src/hooks/useWishlist.ts"];
|
|
87
|
+
expect(code).toContain(
|
|
88
|
+
'import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist"',
|
|
89
|
+
);
|
|
90
|
+
expect(code).toContain('import { invoke } from "~/server/invoke"');
|
|
91
|
+
expect(code).toContain(
|
|
92
|
+
'export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist"',
|
|
93
|
+
);
|
|
94
|
+
expect(code).toContain(
|
|
95
|
+
"export const { useWishlist, resetWishlist } = createUseWishlist",
|
|
96
|
+
);
|
|
97
|
+
// Must NOT scaffold the legacy stub with TODO action bodies.
|
|
98
|
+
expect(code).not.toContain("TODO: Implement");
|
|
99
|
+
expect(code).not.toContain("getItem(_productId: string): boolean");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("useUser + useWishlist VTEX shims are each well under 10 lines", () => {
|
|
103
|
+
expect(files["src/hooks/useUser.ts"].split("\n").length).toBeLessThan(10);
|
|
104
|
+
expect(files["src/hooks/useWishlist.ts"].split("\n").length).toBeLessThan(10);
|
|
105
|
+
});
|
|
69
106
|
});
|
|
70
107
|
|
|
71
108
|
describe("generateHooks (non-vtex)", () => {
|
|
@@ -82,4 +119,23 @@ describe("generateHooks (non-vtex)", () => {
|
|
|
82
119
|
// Until a shopify factory exists, non-vtex platforms get the generic stub.
|
|
83
120
|
expect(code).toContain("Cart Hook stub");
|
|
84
121
|
});
|
|
122
|
+
|
|
123
|
+
it("non-vtex platforms keep the legacy signal-based useUser stub", () => {
|
|
124
|
+
const files = generateHooks(makeCtx("custom"));
|
|
125
|
+
const code = files["src/hooks/useUser.ts"];
|
|
126
|
+
// No factory CALL — docstring may mention it as a pointer for VTEX.
|
|
127
|
+
expect(code).not.toContain("createUseUser({");
|
|
128
|
+
expect(code).not.toContain("export const { useUser, resetUser }");
|
|
129
|
+
// Legacy stub shape (signal + User interface).
|
|
130
|
+
expect(code).toContain("signal<User | null>(null)");
|
|
131
|
+
expect(code).toContain("export interface User {");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("non-vtex platforms keep the legacy useWishlist stub", () => {
|
|
135
|
+
const files = generateHooks(makeCtx("custom"));
|
|
136
|
+
const code = files["src/hooks/useWishlist.ts"];
|
|
137
|
+
expect(code).not.toContain("createUseWishlist({");
|
|
138
|
+
expect(code).not.toContain("export const { useWishlist, resetWishlist }");
|
|
139
|
+
expect(code).toContain("TODO: Implement");
|
|
140
|
+
});
|
|
85
141
|
});
|
|
@@ -5,13 +5,14 @@ export function generateHooks(ctx: MigrationContext): Record<string, string> {
|
|
|
5
5
|
|
|
6
6
|
if (ctx.platform === "vtex") {
|
|
7
7
|
files["src/hooks/useCart.ts"] = generateVtexUseCart();
|
|
8
|
+
files["src/hooks/useUser.ts"] = generateVtexUseUser();
|
|
9
|
+
files["src/hooks/useWishlist.ts"] = generateVtexUseWishlist();
|
|
8
10
|
} else {
|
|
9
11
|
files["src/hooks/useCart.ts"] = generateGenericUseCart();
|
|
12
|
+
files["src/hooks/useUser.ts"] = generateGenericUseUser();
|
|
13
|
+
files["src/hooks/useWishlist.ts"] = generateGenericUseWishlist();
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
files["src/hooks/useUser.ts"] = generateUseUser();
|
|
13
|
-
files["src/hooks/useWishlist.ts"] = generateUseWishlist();
|
|
14
|
-
|
|
15
16
|
return files;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -66,9 +67,39 @@ export default useCart;
|
|
|
66
67
|
`;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// VTEX path — these are five-line factory shims. The heavy lifting
|
|
71
|
+
// (singleton state, listener pattern, async actions, signal-shaped
|
|
72
|
+
// accessors, legacy arg-swap conventions) lives in
|
|
73
|
+
// @decocms/apps/vtex/hooks/createUseUser and createUseWishlist.
|
|
74
|
+
function generateVtexUseUser(): string {
|
|
75
|
+
return `import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser";
|
|
76
|
+
import { invoke } from "~/server/invoke";
|
|
77
|
+
|
|
78
|
+
export type { Person } from "@decocms/apps/vtex/loaders/user";
|
|
79
|
+
|
|
80
|
+
export const { useUser, resetUser } = createUseUser({ invoke });
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function generateVtexUseWishlist(): string {
|
|
85
|
+
return `import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist";
|
|
86
|
+
import { invoke } from "~/server/invoke";
|
|
87
|
+
|
|
88
|
+
export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist";
|
|
89
|
+
|
|
90
|
+
export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Non-VTEX fallback — keeps the legacy signal-based stub shape so any
|
|
95
|
+
// generic platform port that already consumes `~/hooks/useUser` keeps
|
|
96
|
+
// type-checking. Sites must wire their own platform-specific impl.
|
|
97
|
+
function generateGenericUseUser(): string {
|
|
70
98
|
return `/**
|
|
71
99
|
* User Hook — wire to invoke.site.loaders for your platform's user API.
|
|
100
|
+
*
|
|
101
|
+
* VTEX sites get a real factory from @decocms/apps/vtex/hooks/createUseUser;
|
|
102
|
+
* see migration template hooks.ts for the canonical five-line shim.
|
|
72
103
|
*/
|
|
73
104
|
import { signal } from "~/sdk/signal";
|
|
74
105
|
|
|
@@ -90,12 +121,12 @@ export default useUser;
|
|
|
90
121
|
`;
|
|
91
122
|
}
|
|
92
123
|
|
|
93
|
-
function
|
|
124
|
+
function generateGenericUseWishlist(): string {
|
|
94
125
|
return `/**
|
|
95
126
|
* Wishlist Hook — wire to invoke.site.loaders/actions for your platform.
|
|
96
127
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
128
|
+
* VTEX sites get a real factory from @decocms/apps/vtex/hooks/createUseWishlist;
|
|
129
|
+
* see migration template hooks.ts for the canonical five-line shim.
|
|
99
130
|
*/
|
|
100
131
|
import { signal } from "~/sdk/signal";
|
|
101
132
|
|
|
@@ -105,7 +136,7 @@ export function useWishlist() {
|
|
|
105
136
|
return {
|
|
106
137
|
loading,
|
|
107
138
|
async addItem(_productId: string, _productGroupId: string) {
|
|
108
|
-
// TODO: Implement
|
|
139
|
+
// TODO: Implement for your platform
|
|
109
140
|
},
|
|
110
141
|
async removeItem(_productId: string) {
|
|
111
142
|
// TODO: Implement
|
|
@@ -89,3 +89,51 @@ describe("selectImportedLibTemplates()", () => {
|
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
|
+
|
|
93
|
+
describe("D3 — generated stubs throw at runtime", () => {
|
|
94
|
+
// Each stub MUST throw an Error whose message identifies:
|
|
95
|
+
// - the stub path so the dev sees it in their stack trace
|
|
96
|
+
// - the canonical replacement (so the fix is mechanical)
|
|
97
|
+
//
|
|
98
|
+
// See migration-tooling-policy.mdc § Decision 3.
|
|
99
|
+
it("vtex-transform.toProduct throws and points at the canonical path", () => {
|
|
100
|
+
const src = LIB_TEMPLATES["src/lib/vtex-transform.ts"];
|
|
101
|
+
expect(src).toMatch(/throw new Error/);
|
|
102
|
+
expect(src).toMatch(/@decocms\/apps\/vtex\/utils\/transform/);
|
|
103
|
+
expect(src).toMatch(/\[deco-migrate\]/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("vtex-intelligent-search.getISCookiesFromBag throws", () => {
|
|
107
|
+
const src = LIB_TEMPLATES["src/lib/vtex-intelligent-search.ts"];
|
|
108
|
+
expect(src).toMatch(/getISCookiesFromBag[\s\S]*?throw new Error/);
|
|
109
|
+
expect(src).toMatch(/\[deco-migrate\]/);
|
|
110
|
+
// The other helpers in this file (isFilterParam, toPath,
|
|
111
|
+
// withDefaultFacets, withDefaultParams) are real impls — must not
|
|
112
|
+
// throw.
|
|
113
|
+
expect(src).toMatch(/export function isFilterParam[\s\S]*?return key\.startsWith/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("vtex-segment.getSegmentFromBag and withSegmentCookie both throw", () => {
|
|
117
|
+
const src = LIB_TEMPLATES["src/lib/vtex-segment.ts"];
|
|
118
|
+
expect(src).toMatch(/getSegmentFromBag[\s\S]*?throw new Error/);
|
|
119
|
+
expect(src).toMatch(/withSegmentCookie[\s\S]*?throw new Error/);
|
|
120
|
+
expect(src).toMatch(/@decocms\/apps\/vtex\/utils\/segment/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("non-stub helpers stay implemented (negative check — no throw)", () => {
|
|
124
|
+
// These are real impls, not stubs. They must not throw.
|
|
125
|
+
const real = [
|
|
126
|
+
"src/lib/http-utils.ts",
|
|
127
|
+
"src/lib/vtex-id.ts",
|
|
128
|
+
"src/lib/graphql-utils.ts",
|
|
129
|
+
"src/lib/filter-navigate.ts",
|
|
130
|
+
"src/lib/fetch-utils.ts",
|
|
131
|
+
];
|
|
132
|
+
for (const key of real) {
|
|
133
|
+
const src = LIB_TEMPLATES[key];
|
|
134
|
+
expect(src, `${key} should not contain a generated stub throw`).not.toMatch(
|
|
135
|
+
/\[deco-migrate\][^"]*generated stub/,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -38,15 +38,41 @@ export function selectImportedLibTemplates(
|
|
|
38
38
|
return out;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Per the migration tooling policy (D3 — Throwing stubs):
|
|
42
|
+
// generated stubs MUST throw at runtime so the first call surfaces the
|
|
43
|
+
// gap loudly. Silent identity-cast `toProduct` was the bug behind
|
|
44
|
+
// baggagio-tanstack#10 (PDP product data was being dropped on the floor
|
|
45
|
+
// for weeks before anyone noticed).
|
|
46
|
+
//
|
|
47
|
+
// Each thrown message points at the canonical replacement so the fix
|
|
48
|
+
// is mechanical. `deco-post-cleanup --fix` automates the swap.
|
|
41
49
|
const LIB_VTEX_TRANSFORM = `import type { Product } from "@decocms/apps/commerce/types";
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const STUB =
|
|
52
|
+
"[deco-migrate] \`~/lib/vtex-transform.toProduct\` is a generated stub. " +
|
|
53
|
+
"Replace with: import { toProduct } from '@decocms/apps/vtex/utils/transform' " +
|
|
54
|
+
"(canonical signature: \`toProduct(product, sku, level, options)\`). " +
|
|
55
|
+
"Run \`deco-post-cleanup --fix\` or see the deco-to-tanstack-migration skill " +
|
|
56
|
+
"(post-migration-cleanup § 5).";
|
|
57
|
+
|
|
58
|
+
export function toProduct(_vtexProduct: any, ..._rest: any[]): Product {
|
|
59
|
+
throw new Error(STUB);
|
|
45
60
|
}
|
|
46
61
|
`;
|
|
47
62
|
|
|
48
|
-
const LIB_VTEX_INTELLIGENT_SEARCH =
|
|
49
|
-
|
|
63
|
+
const LIB_VTEX_INTELLIGENT_SEARCH = `// Per the migration tooling policy (D3): \`getISCookiesFromBag\` cannot
|
|
64
|
+
// be implemented on TanStack Start because the bag-based lookup
|
|
65
|
+
// mechanism does not exist. Sites must read cookies directly from the
|
|
66
|
+
// request — see the \`vtex-shim-regression\` audit rule for guidance.
|
|
67
|
+
const STUB_GET_IS_COOKIES =
|
|
68
|
+
"[deco-migrate] \`~/lib/vtex-intelligent-search.getISCookiesFromBag\` is a " +
|
|
69
|
+
"generated stub. Refactor: extract IS cookies from " +
|
|
70
|
+
"\`request.headers.get('cookie')\` directly. The bag-based lookup mechanism " +
|
|
71
|
+
"does not exist on TanStack Start. See the deco-to-tanstack-migration " +
|
|
72
|
+
"skill (post-migration-cleanup § 5).";
|
|
73
|
+
|
|
74
|
+
export function getISCookiesFromBag(_req?: any): Record<string, string> {
|
|
75
|
+
throw new Error(STUB_GET_IS_COOKIES);
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
export function isFilterParam(key: string): boolean {
|
|
@@ -85,17 +111,30 @@ export function withDefaultParams(
|
|
|
85
111
|
}
|
|
86
112
|
`;
|
|
87
113
|
|
|
88
|
-
const LIB_VTEX_SEGMENT =
|
|
89
|
-
|
|
114
|
+
const LIB_VTEX_SEGMENT = `// Per the migration tooling policy (D3): both these stubs throw at
|
|
115
|
+
// runtime to force the call site to be fixed. Silent fallbacks here
|
|
116
|
+
// mean the storefront silently fails to forward VTEX segment data
|
|
117
|
+
// (sales channel, regionId, currency, etc.) and pricing/inventory
|
|
118
|
+
// quietly diverge from what the user should see.
|
|
119
|
+
const STUB_GET_SEGMENT_FROM_BAG =
|
|
120
|
+
"[deco-migrate] \`~/lib/vtex-segment.getSegmentFromBag\` is a generated " +
|
|
121
|
+
"stub. Refactor: read cookies via \`request.headers.get('cookie')\` then " +
|
|
122
|
+
"call \`buildSegmentFromCookies()\` from '@decocms/apps/vtex/utils/segment'. " +
|
|
123
|
+
"The bag-based lookup mechanism does not exist on TanStack Start.";
|
|
124
|
+
|
|
125
|
+
const STUB_WITH_SEGMENT_COOKIE =
|
|
126
|
+
"[deco-migrate] \`~/lib/vtex-segment.withSegmentCookie\` is a generated " +
|
|
127
|
+
"stub. Replace with: import { withSegmentCookie } from " +
|
|
128
|
+
"'@decocms/apps/vtex/utils/segment' (canonical signature: " +
|
|
129
|
+
"\`withSegmentCookie(segment, headers?)\`). Run \`deco-post-cleanup --fix\` " +
|
|
130
|
+
"or see the deco-to-tanstack-migration skill.";
|
|
131
|
+
|
|
132
|
+
export function getSegmentFromBag(_req?: any): Record<string, unknown> | null {
|
|
133
|
+
throw new Error(STUB_GET_SEGMENT_FROM_BAG);
|
|
90
134
|
}
|
|
91
135
|
|
|
92
136
|
export function withSegmentCookie(..._args: any[]): any {
|
|
93
|
-
|
|
94
|
-
if (arg instanceof Headers) {
|
|
95
|
-
return arg;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return new Headers();
|
|
137
|
+
throw new Error(STUB_WITH_SEGMENT_COOKIE);
|
|
99
138
|
}
|
|
100
139
|
`;
|
|
101
140
|
|