@decocms/blocks-cli 7.5.1 → 7.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/generate-invoke.ts +10 -0
- package/scripts/generate-schema.ts +46 -15
- package/scripts/generate-sections.test.ts +67 -0
- package/scripts/generate-sections.ts +16 -2
- package/scripts/lib/codegenExclusions.test.ts +16 -6
- package/scripts/lib/codegenExclusions.ts +13 -1
- package/scripts/migrate/analyzers/loader-inventory.ts +1 -1
- package/scripts/migrate/phase-analyze.ts +3 -3
- package/scripts/migrate/phase-cleanup.test.ts +70 -1
- package/scripts/migrate/phase-cleanup.ts +47 -26
- package/scripts/migrate/phase-report.ts +7 -7
- package/scripts/migrate/phase-scaffold.ts +4 -4
- package/scripts/migrate/phase-transform.ts +1 -1
- package/scripts/migrate/phase-verify.ts +6 -5
- package/scripts/migrate/post-cleanup/rules.ts +55 -44
- package/scripts/migrate/post-cleanup/runner.test.ts +58 -34
- package/scripts/migrate/templates/cache-config.ts +4 -4
- package/scripts/migrate/templates/commerce-loaders.ts +10 -10
- package/scripts/migrate/templates/cursor-rules.test.ts +6 -0
- package/scripts/migrate/templates/cursor-rules.ts +12 -11
- package/scripts/migrate/templates/hooks.test.ts +7 -7
- package/scripts/migrate/templates/hooks.ts +10 -10
- package/scripts/migrate/templates/lib-utils.test.ts +2 -2
- package/scripts/migrate/templates/lib-utils.ts +4 -4
- package/scripts/migrate/templates/no-legacy-packages.test.ts +147 -0
- package/scripts/migrate/templates/package-json.ts +30 -10
- package/scripts/migrate/templates/routes.ts +8 -10
- package/scripts/migrate/templates/sdk-gen.ts +1 -1
- package/scripts/migrate/templates/section-loaders.ts +7 -7
- package/scripts/migrate/templates/server-entry.ts +37 -37
- package/scripts/migrate/templates/setup.ts +6 -6
- package/scripts/migrate/templates/types-gen.ts +2 -2
- package/scripts/migrate/templates/ui-components.ts +2 -2
- package/scripts/migrate/templates/vite-config.ts +12 -3
- package/scripts/migrate/transforms/fresh-apis.ts +5 -5
- package/scripts/migrate/transforms/imports.test.ts +138 -0
- package/scripts/migrate/transforms/imports.ts +65 -43
- package/scripts/migrate/transforms/jsx.ts +8 -0
- package/scripts/migrate/transforms/section-conventions.ts +1 -1
- package/scripts/migrate/types.ts +1 -1
- package/scripts/migrate-post-cleanup.ts +8 -8
- package/scripts/migrate.ts +11 -11
|
@@ -242,10 +242,10 @@ describe("rule: dead-runtime-shim", () => {
|
|
|
242
242
|
|
|
243
243
|
it("does NOT flag the Wave 15-A canonical re-export shape", () => {
|
|
244
244
|
// The migration template now scaffolds a thin re-export from
|
|
245
|
-
// @decocms/
|
|
245
|
+
// @decocms/blocks/sdk/invoke plus a Runtime alias. No inline proxy body.
|
|
246
246
|
const fs = makeFs({
|
|
247
247
|
"/site/src/runtime.ts":
|
|
248
|
-
'import { invoke } from "@decocms/
|
|
248
|
+
'import { invoke } from "@decocms/blocks/sdk/invoke";\nexport { invoke };\nexport const Runtime = { invoke };\n',
|
|
249
249
|
});
|
|
250
250
|
const report = runAudit(SITE, fs);
|
|
251
251
|
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
@@ -330,7 +330,7 @@ describe("rule: site-local-with-globals", () => {
|
|
|
330
330
|
it("does not flag a re-export from the framework", () => {
|
|
331
331
|
const fs = makeFs({
|
|
332
332
|
"/site/src/server/routes/withSiteGlobals.ts":
|
|
333
|
-
'export { withSiteGlobals } from "@decocms/
|
|
333
|
+
'export { withSiteGlobals } from "@decocms/tanstack";\n',
|
|
334
334
|
});
|
|
335
335
|
const report = runAudit(SITE, fs);
|
|
336
336
|
const r = report.rules.find((r) => r.rule === "site-local-with-globals")!;
|
|
@@ -492,12 +492,12 @@ describe("rule: vtex-shim-regression — per-symbol fix hints", () => {
|
|
|
492
492
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
493
493
|
expect(r.findings).toHaveLength(1);
|
|
494
494
|
const f = r.findings[0];
|
|
495
|
-
expect(f.fix).toContain("toProduct → @decocms/apps
|
|
495
|
+
expect(f.fix).toContain("toProduct → @decocms/apps-vtex/utils/transform");
|
|
496
496
|
expect(f.fix).toContain("1:1 import swap");
|
|
497
497
|
expect(f.meta?.fixHints).toEqual({
|
|
498
498
|
toProduct: {
|
|
499
499
|
kind: "swap",
|
|
500
|
-
canonical: "@decocms/apps
|
|
500
|
+
canonical: "@decocms/apps-vtex/utils/transform",
|
|
501
501
|
note: expect.stringContaining("canonical signature"),
|
|
502
502
|
},
|
|
503
503
|
});
|
|
@@ -537,7 +537,7 @@ describe("rule: vtex-shim-regression — per-symbol fix hints", () => {
|
|
|
537
537
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
538
538
|
const f = r.findings[0];
|
|
539
539
|
expect(f.fix).toContain("getSegmentFromBag → call-site refactor");
|
|
540
|
-
expect(f.fix).toContain("toProduct → @decocms/apps
|
|
540
|
+
expect(f.fix).toContain("toProduct → @decocms/apps-vtex/utils/transform");
|
|
541
541
|
// Joined with " | " for visual separation.
|
|
542
542
|
expect(f.fix).toContain(" | ");
|
|
543
543
|
expect(Object.keys(f.meta?.fixHints as object)).toEqual(
|
|
@@ -555,7 +555,7 @@ describe("rule: vtex-shim-regression — per-symbol fix hints", () => {
|
|
|
555
555
|
const report = runAudit(SITE, fs);
|
|
556
556
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
557
557
|
const f = r.findings[0];
|
|
558
|
-
expect(f.fix).toContain("unknownStub → repoint to '@decocms/apps
|
|
558
|
+
expect(f.fix).toContain("unknownStub → repoint to '@decocms/apps-vtex/...");
|
|
559
559
|
// No fixHints in meta when no symbols match the table.
|
|
560
560
|
expect(f.meta?.fixHints).toBeUndefined();
|
|
561
561
|
});
|
|
@@ -573,7 +573,7 @@ describe("rule: vtex-shim-regression — per-symbol fix hints", () => {
|
|
|
573
573
|
const report = runAudit(SITE, fs);
|
|
574
574
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
575
575
|
const f = r.findings[0];
|
|
576
|
-
expect(f.fix).toContain("toProduct → @decocms/apps
|
|
576
|
+
expect(f.fix).toContain("toProduct → @decocms/apps-vtex/utils/transform");
|
|
577
577
|
expect(f.fix).toContain("unknownStub → repoint");
|
|
578
578
|
// Only the known symbol shows up in fixHints.
|
|
579
579
|
expect(f.meta?.fixHints).toEqual({
|
|
@@ -665,6 +665,30 @@ export default createDecoWorkerEntry(serverEntry, {
|
|
|
665
665
|
expect(r.findings).toEqual([]);
|
|
666
666
|
});
|
|
667
667
|
|
|
668
|
+
it("recognizes the current @decocms/apps-vtex package name (post-7.x split)", () => {
|
|
669
|
+
// Sites migrated with the current scaffolder emit `@decocms/apps-vtex`,
|
|
670
|
+
// not the retired `@decocms/apps/vtex` monolith path. Both the isVtex
|
|
671
|
+
// signal and the proxy-import detection must recognize the new form.
|
|
672
|
+
const currentWorkerEntry = `
|
|
673
|
+
import { createDecoWorkerEntry } from "@decocms/tanstack";
|
|
674
|
+
import { shouldProxyToVtex, createVtexCheckoutProxy } from "@decocms/apps-vtex/utils/proxy";
|
|
675
|
+
const proxy = createVtexCheckoutProxy({ account: "x", checkoutOrigin: "x.vtexcommercestable.com.br" });
|
|
676
|
+
export default createDecoWorkerEntry(serverEntry, {
|
|
677
|
+
proxyHandler: async (req, url) => {
|
|
678
|
+
if (!shouldProxyToVtex(url.pathname)) return null;
|
|
679
|
+
return proxy(req, url);
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
`;
|
|
683
|
+
const fs = makeFs({
|
|
684
|
+
"/site/src/commerceLoaders.ts": 'import {} from "@decocms/apps-vtex/mod";',
|
|
685
|
+
"/site/src/worker-entry.ts": currentWorkerEntry,
|
|
686
|
+
});
|
|
687
|
+
const report = runAudit(SITE, fs);
|
|
688
|
+
const r = report.rules.find((r) => r.rule === "vtex-proxy-handler-missing")!;
|
|
689
|
+
expect(r.findings).toEqual([]);
|
|
690
|
+
});
|
|
691
|
+
|
|
668
692
|
it("flags VTEX site missing src/worker-entry.ts entirely", () => {
|
|
669
693
|
const fs = makeFs({
|
|
670
694
|
"/site/src/commerceLoaders.ts": "import {} from \"@decocms/apps/vtex/mod\";",
|
|
@@ -687,7 +711,7 @@ export default createDecoWorkerEntry(serverEntry, { admin: {} });
|
|
|
687
711
|
const report = runAudit(SITE, fs);
|
|
688
712
|
const r = report.rules.find((r) => r.rule === "vtex-proxy-handler-missing")!;
|
|
689
713
|
expect(r.findings).toHaveLength(1);
|
|
690
|
-
expect(r.findings[0].message).toContain("no `@decocms/apps
|
|
714
|
+
expect(r.findings[0].message).toContain("no `@decocms/apps-vtex/utils/proxy` import");
|
|
691
715
|
});
|
|
692
716
|
|
|
693
717
|
it("does not flag VTEX site using object-shorthand proxyHandler wiring", () => {
|
|
@@ -821,8 +845,8 @@ describe("runAudit — fix mode", () => {
|
|
|
821
845
|
expect(r.fixes).toHaveLength(1);
|
|
822
846
|
expect(r.fixes![0].detail).toMatch(/rewrote 2 import/);
|
|
823
847
|
expect("/site/src/runtime.ts" in store).toBe(false);
|
|
824
|
-
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/
|
|
825
|
-
expect(store["/site/src/sections/B.tsx"]).toContain("'@decocms/
|
|
848
|
+
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/blocks/sdk/invoke"');
|
|
849
|
+
expect(store["/site/src/sections/B.tsx"]).toContain("'@decocms/blocks/sdk/invoke'");
|
|
826
850
|
expect(store["/site/src/sections/C.tsx"]).toContain('"~/something-else"');
|
|
827
851
|
expect(log.filter((e) => e.kind === "delete")).toHaveLength(1);
|
|
828
852
|
expect(log.filter((e) => e.kind === "write")).toHaveLength(2);
|
|
@@ -841,8 +865,8 @@ describe("runAudit — fix mode", () => {
|
|
|
841
865
|
expect(r.fixes).toHaveLength(1);
|
|
842
866
|
expect(r.fixes![0].detail).toMatch(/rewrote 2 import/);
|
|
843
867
|
expect("/site/src/types/widgets.ts" in store).toBe(false);
|
|
844
|
-
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/
|
|
845
|
-
expect(store["/site/src/sections/B.tsx"]).toContain("'@decocms/
|
|
868
|
+
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/blocks/types/widgets"');
|
|
869
|
+
expect(store["/site/src/sections/B.tsx"]).toContain("'@decocms/blocks/types/widgets'");
|
|
846
870
|
});
|
|
847
871
|
|
|
848
872
|
it("fix mode is a no-op for rules without applyFix (e.g. framework-todos)", () => {
|
|
@@ -865,7 +889,7 @@ describe("runAudit — fix mode", () => {
|
|
|
865
889
|
'import type { ImageWidget } from "~/types/widgets";\nimport thing from "~/types/widgets-extra";\n',
|
|
866
890
|
});
|
|
867
891
|
runAudit(SITE, fs, { writer });
|
|
868
|
-
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/
|
|
892
|
+
expect(store["/site/src/sections/A.tsx"]).toContain('"@decocms/blocks/types/widgets"');
|
|
869
893
|
expect(store["/site/src/sections/A.tsx"]).toContain('"~/types/widgets-extra"');
|
|
870
894
|
});
|
|
871
895
|
});
|
|
@@ -887,10 +911,10 @@ describe("runAudit — fix mode — vtex-shim-regression swap cases", () => {
|
|
|
887
911
|
expect(r.fixes).toHaveLength(1);
|
|
888
912
|
expect(r.fixes![0].file).toBe("src/loaders/search.ts");
|
|
889
913
|
expect(r.fixes![0].kind).toBe("rewrite-imports");
|
|
890
|
-
expect(r.fixes![0].detail).toContain("@decocms/apps
|
|
914
|
+
expect(r.fixes![0].detail).toContain("@decocms/apps-vtex/utils/transform");
|
|
891
915
|
expect(r.fixes![0].detail).toContain("toProduct");
|
|
892
916
|
expect(store["/site/src/loaders/search.ts"]).toContain(
|
|
893
|
-
'"@decocms/apps
|
|
917
|
+
'"@decocms/apps-vtex/utils/transform"',
|
|
894
918
|
);
|
|
895
919
|
expect(store["/site/src/loaders/search.ts"]).not.toContain('"~/lib/vtex-transform"');
|
|
896
920
|
});
|
|
@@ -907,9 +931,9 @@ describe("runAudit — fix mode — vtex-shim-regression swap cases", () => {
|
|
|
907
931
|
const report = runAudit(SITE, fs, { writer });
|
|
908
932
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
909
933
|
expect(r.fixes).toHaveLength(1);
|
|
910
|
-
expect(r.fixes![0].detail).toContain("@decocms/apps
|
|
934
|
+
expect(r.fixes![0].detail).toContain("@decocms/apps-vtex/utils/segment");
|
|
911
935
|
expect(store["/site/src/loaders/x.ts"]).toContain(
|
|
912
|
-
'"@decocms/apps
|
|
936
|
+
'"@decocms/apps-vtex/utils/segment"',
|
|
913
937
|
);
|
|
914
938
|
});
|
|
915
939
|
|
|
@@ -964,10 +988,10 @@ describe("runAudit — fix mode — vtex-shim-regression swap cases", () => {
|
|
|
964
988
|
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
965
989
|
expect(r.fixes!.length).toBe(2);
|
|
966
990
|
expect(store["/site/src/loaders/A.ts"]).toContain(
|
|
967
|
-
'"@decocms/apps
|
|
991
|
+
'"@decocms/apps-vtex/utils/transform"',
|
|
968
992
|
);
|
|
969
993
|
expect(store["/site/src/loaders/B.ts"]).toContain(
|
|
970
|
-
"'@decocms/apps
|
|
994
|
+
"'@decocms/apps-vtex/utils/transform'",
|
|
971
995
|
);
|
|
972
996
|
});
|
|
973
997
|
|
|
@@ -983,7 +1007,7 @@ describe("runAudit — fix mode — vtex-shim-regression swap cases", () => {
|
|
|
983
1007
|
});
|
|
984
1008
|
runAudit(SITE, fs, { writer });
|
|
985
1009
|
expect(store["/site/src/loaders/x.ts"]).toContain(
|
|
986
|
-
'import { toProduct as toP } from "@decocms/apps
|
|
1010
|
+
'import { toProduct as toP } from "@decocms/apps-vtex/utils/transform"',
|
|
987
1011
|
);
|
|
988
1012
|
});
|
|
989
1013
|
});
|
|
@@ -1296,7 +1320,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1296
1320
|
expect(r.findings[0].message).toContain("pure dup");
|
|
1297
1321
|
expect(r.findings[0].meta?.id).toBe("clx");
|
|
1298
1322
|
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
1299
|
-
expect(r.findings[0].meta?.canonicalImport).toBe("@decocms/
|
|
1323
|
+
expect(r.findings[0].meta?.canonicalImport).toBe("@decocms/blocks/sdk/clx");
|
|
1300
1324
|
});
|
|
1301
1325
|
|
|
1302
1326
|
it("flags src/sdk/clx.ts when site adds a clsx alias (signature still matches)", () => {
|
|
@@ -1328,7 +1352,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1328
1352
|
it("flags src/sdk/useSendEvent.ts as warn-only (typing regression risk)", () => {
|
|
1329
1353
|
const fs = makeFs({
|
|
1330
1354
|
"/site/src/sdk/useSendEvent.ts":
|
|
1331
|
-
'import { AnalyticsEvent } from "@decocms/apps
|
|
1355
|
+
'import { AnalyticsEvent } from "@decocms/apps-commerce/types";\n' +
|
|
1332
1356
|
"export const useSendEvent = <E extends AnalyticsEvent>(\n" +
|
|
1333
1357
|
" { event, on }: { event: E; on: 'click' | 'view' | 'change' },\n" +
|
|
1334
1358
|
") => ({\n" +
|
|
@@ -1367,7 +1391,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1367
1391
|
expect(r.findings[0].meta?.id).toBe("url-relative");
|
|
1368
1392
|
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
1369
1393
|
expect(r.findings[0].meta?.canonicalImport).toBe(
|
|
1370
|
-
"@decocms/apps
|
|
1394
|
+
"@decocms/apps-commerce/sdk/url",
|
|
1371
1395
|
);
|
|
1372
1396
|
expect(r.findings[0].fix).toContain("stripSearchParams");
|
|
1373
1397
|
});
|
|
@@ -1393,7 +1417,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1393
1417
|
expect(r.findings[0].meta?.id).toBe("use-suggestions");
|
|
1394
1418
|
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
1395
1419
|
expect(r.findings[0].meta?.canonicalImport).toBe(
|
|
1396
|
-
"@decocms/
|
|
1420
|
+
"@decocms/blocks/sdk/useSuggestions",
|
|
1397
1421
|
);
|
|
1398
1422
|
expect(r.findings[0].fix).toContain("createUseSuggestions");
|
|
1399
1423
|
});
|
|
@@ -1404,7 +1428,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1404
1428
|
// strings. The rule's signature must NOT fire on the shim.
|
|
1405
1429
|
const fs = makeFs({
|
|
1406
1430
|
"/site/src/sdk/useSuggestions.ts":
|
|
1407
|
-
'import { createUseSuggestions } from "@decocms/
|
|
1431
|
+
'import { createUseSuggestions } from "@decocms/blocks/sdk/useSuggestions";\n' +
|
|
1408
1432
|
'import * as Sentry from "@sentry/react";\n' +
|
|
1409
1433
|
"export const { useSuggestions } = createUseSuggestions({\n" +
|
|
1410
1434
|
" onError: (err) => Sentry.captureException(err),\n" +
|
|
@@ -1437,7 +1461,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1437
1461
|
it("flags src/matchers/location.ts as warn-only (behaviour-superset opportunity)", () => {
|
|
1438
1462
|
const fs = makeFs({
|
|
1439
1463
|
"/site/src/matchers/location.ts":
|
|
1440
|
-
'import { registerMatcher } from "@decocms/
|
|
1464
|
+
'import { registerMatcher } from "@decocms/blocks/cms";\n' +
|
|
1441
1465
|
"export function registerLocationMatcher(): void {\n" +
|
|
1442
1466
|
' registerMatcher("website/matchers/location.ts", (rule, ctx) => {\n' +
|
|
1443
1467
|
" const cookies = ctx.cookies ?? {};\n" +
|
|
@@ -1457,7 +1481,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1457
1481
|
it("emits zero findings on a clean tree (no duplicates present)", () => {
|
|
1458
1482
|
const fs = makeFs({
|
|
1459
1483
|
"/site/src/sections/Hello.tsx":
|
|
1460
|
-
'import { clx } from "@decocms/
|
|
1484
|
+
'import { clx } from "@decocms/blocks/sdk/clx";\nexport default () => <div className={clx("a")}>x</div>;\n',
|
|
1461
1485
|
});
|
|
1462
1486
|
const report = runAudit(SITE, fs);
|
|
1463
1487
|
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
@@ -1485,7 +1509,7 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1485
1509
|
"/site/src/components/B.tsx":
|
|
1486
1510
|
'import { clx } from "~/sdk/clx";\nimport React from "react";\nexport default () => clx("y");\n',
|
|
1487
1511
|
"/site/src/components/Unrelated.tsx":
|
|
1488
|
-
'import { clx } from "@decocms/
|
|
1512
|
+
'import { clx } from "@decocms/blocks/sdk/clx";\nexport default () => clx("z");\n',
|
|
1489
1513
|
});
|
|
1490
1514
|
const report = runAudit(SITE, fs, { writer });
|
|
1491
1515
|
const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
|
|
@@ -1497,14 +1521,14 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1497
1521
|
expect(store["/site/src/sdk/clx.ts"]).toBeUndefined();
|
|
1498
1522
|
// Importers rewritten
|
|
1499
1523
|
expect(store["/site/src/components/A.tsx"]).toContain(
|
|
1500
|
-
'from "@decocms/
|
|
1524
|
+
'from "@decocms/blocks/sdk/clx"',
|
|
1501
1525
|
);
|
|
1502
1526
|
expect(store["/site/src/components/B.tsx"]).toContain(
|
|
1503
|
-
'from "@decocms/
|
|
1527
|
+
'from "@decocms/blocks/sdk/clx"',
|
|
1504
1528
|
);
|
|
1505
1529
|
// Already-canonical import untouched
|
|
1506
1530
|
expect(store["/site/src/components/Unrelated.tsx"]).toContain(
|
|
1507
|
-
'from "@decocms/
|
|
1531
|
+
'from "@decocms/blocks/sdk/clx"',
|
|
1508
1532
|
);
|
|
1509
1533
|
expect(store["/site/src/components/Unrelated.tsx"]).not.toMatch(/~\/sdk\/clx/);
|
|
1510
1534
|
});
|
|
@@ -1512,12 +1536,12 @@ describe("rule: local-framework-duplicate", () => {
|
|
|
1512
1536
|
it("auto-fix is a no-op for warn-only entries (does NOT delete partial-overlap files)", () => {
|
|
1513
1537
|
const { fs, writer, store } = makeMutableFs({
|
|
1514
1538
|
"/site/src/sdk/useSendEvent.ts":
|
|
1515
|
-
'import { AnalyticsEvent } from "@decocms/apps
|
|
1539
|
+
'import { AnalyticsEvent } from "@decocms/apps-commerce/types";\n' +
|
|
1516
1540
|
"export const useSendEvent = <E extends AnalyticsEvent>() => ({\n" +
|
|
1517
1541
|
' "data-event": encodeURIComponent("x"),\n' +
|
|
1518
1542
|
"});\n",
|
|
1519
1543
|
"/site/src/matchers/location.ts":
|
|
1520
|
-
'import { registerMatcher } from "@decocms/
|
|
1544
|
+
'import { registerMatcher } from "@decocms/blocks/cms";\n' +
|
|
1521
1545
|
'registerMatcher("website/matchers/location.ts", () => Boolean(__cf_geo_country));\n',
|
|
1522
1546
|
});
|
|
1523
1547
|
const report = runAudit(SITE, fs, { writer });
|
|
@@ -4,18 +4,18 @@ export function generateCacheConfig(ctx: MigrationContext): string {
|
|
|
4
4
|
if (ctx.platform !== "vtex") {
|
|
5
5
|
return `/**
|
|
6
6
|
* Cache configuration — customize edge cache profiles per route type.
|
|
7
|
-
* See @decocms/
|
|
7
|
+
* See @decocms/blocks/sdk/cacheHeaders for available profiles.
|
|
8
8
|
*/
|
|
9
|
-
// import { setCacheProfile } from "@decocms/
|
|
9
|
+
// import { setCacheProfile } from "@decocms/blocks/sdk/cacheHeaders";
|
|
10
10
|
// setCacheProfile("product", { sMaxAge: 300, staleWhileRevalidate: 600 });
|
|
11
11
|
`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
return `/**
|
|
15
15
|
* Cache configuration for VTEX storefront.
|
|
16
|
-
* Overrides default cache profiles from @decocms/
|
|
16
|
+
* Overrides default cache profiles from @decocms/blocks/sdk/cacheHeaders.
|
|
17
17
|
*/
|
|
18
|
-
// import { setCacheProfile } from "@decocms/
|
|
18
|
+
// import { setCacheProfile } from "@decocms/blocks/sdk/cacheHeaders";
|
|
19
19
|
|
|
20
20
|
// Uncomment and adjust as needed:
|
|
21
21
|
// setCacheProfile("product", { sMaxAge: 300, staleWhileRevalidate: 600 });
|
|
@@ -37,15 +37,15 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
37
37
|
lines.push(` */`);
|
|
38
38
|
|
|
39
39
|
if (ctx.platform === "vtex") {
|
|
40
|
-
lines.push(`import { getVtexConfig } from "@decocms/apps
|
|
41
|
-
lines.push(`import { createVtexCommerceLoaders, createCachedPDPLoader } from "@decocms/apps
|
|
40
|
+
lines.push(`import { getVtexConfig } from "@decocms/apps-vtex";`);
|
|
41
|
+
lines.push(`import { createVtexCommerceLoaders, createCachedPDPLoader } from "@decocms/apps-vtex/commerceLoaders";`);
|
|
42
42
|
if (hasAutocomplete) {
|
|
43
|
-
lines.push(`import { autocompleteSearch } from "@decocms/apps
|
|
43
|
+
lines.push(`import { autocompleteSearch } from "@decocms/apps-vtex/loaders/autocomplete";`);
|
|
44
44
|
}
|
|
45
|
-
lines.push(`import { getAddressByPostalCode } from "@decocms/apps
|
|
46
|
-
lines.push(`import { createAddressFromRequest, updateAddressFromRequest, deleteAddressFromRequest } from "@decocms/apps
|
|
47
|
-
lines.push(`import { updateProfileFromRequest, newsletterProfileFromRequest, deletePaymentFromRequest, getPasswordLastUpdate } from "@decocms/apps
|
|
48
|
-
lines.push(`import { createCachedLoader } from "@decocms/
|
|
45
|
+
lines.push(`import { getAddressByPostalCode } from "@decocms/apps-vtex/loaders/address";`);
|
|
46
|
+
lines.push(`import { createAddressFromRequest, updateAddressFromRequest, deleteAddressFromRequest } from "@decocms/apps-vtex/actions/address";`);
|
|
47
|
+
lines.push(`import { updateProfileFromRequest, newsletterProfileFromRequest, deletePaymentFromRequest, getPasswordLastUpdate } from "@decocms/apps-vtex/actions/profile";`);
|
|
48
|
+
lines.push(`import { createCachedLoader } from "@decocms/blocks/sdk/cachedLoader";`);
|
|
49
49
|
|
|
50
50
|
if (hasVtexAuth) {
|
|
51
51
|
lines.push(`import vtexAuthLoader from "../loaders/vtex-auth-loader";`);
|
|
@@ -92,7 +92,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
92
92
|
|
|
93
93
|
// Autocomplete aliases
|
|
94
94
|
if (hasAutocomplete) {
|
|
95
|
-
lines.push(` // Autocomplete search — from @decocms/apps`);
|
|
95
|
+
lines.push(` // Autocomplete search — from @decocms/apps-vtex`);
|
|
96
96
|
lines.push(` "site/loaders/search/intelligenseSearch.ts": cachedAutocomplete,`);
|
|
97
97
|
lines.push(` "site/loaders/search/intelligenseSearch": cachedAutocomplete,`);
|
|
98
98
|
lines.push(``);
|
|
@@ -109,7 +109,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// VTEX address CRUD
|
|
112
|
-
lines.push(` // VTEX address CRUD — request-aware wrappers from @decocms/apps`);
|
|
112
|
+
lines.push(` // VTEX address CRUD — request-aware wrappers from @decocms/apps-vtex`);
|
|
113
113
|
lines.push(` "vtex/actions/address/createAddress": createAddressFromRequest as any,`);
|
|
114
114
|
lines.push(` "vtex/actions/address/updateAddress": updateAddressFromRequest as any,`);
|
|
115
115
|
lines.push(` "vtex/actions/address/deleteAddress": deleteAddressFromRequest as any,`);
|
|
@@ -215,7 +215,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
215
215
|
|
|
216
216
|
lines.push(``);
|
|
217
217
|
// Profile actions
|
|
218
|
-
lines.push(` // Profile actions — request-aware wrappers from @decocms/apps`);
|
|
218
|
+
lines.push(` // Profile actions — request-aware wrappers from @decocms/apps-vtex`);
|
|
219
219
|
lines.push(` "vtex/actions/profile/updateProfile": updateProfileFromRequest as any,`);
|
|
220
220
|
lines.push(` "vtex/actions/profile/updateProfile.ts": updateProfileFromRequest as any,`);
|
|
221
221
|
lines.push(` "vtex/actions/profile/newsletterProfile": newsletterProfileFromRequest as any,`);
|
|
@@ -43,6 +43,12 @@ describe("generateMigrationPolicyPointerRule", () => {
|
|
|
43
43
|
expect(body.length).toBeLessThan(3000);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it("does not emit the pre-split @decocms/start or @decocms/apps package names", () => {
|
|
47
|
+
expect(body).not.toContain("@decocms/start");
|
|
48
|
+
expect(body).not.toContain("@decocms/apps\`");
|
|
49
|
+
expect(body).toContain("@decocms/blocks-cli");
|
|
50
|
+
});
|
|
51
|
+
|
|
46
52
|
it("is deterministic — same site name, same output", () => {
|
|
47
53
|
const a = generateMigrationPolicyPointerRule("foo");
|
|
48
54
|
const b = generateMigrationPolicyPointerRule("foo");
|
|
@@ -26,10 +26,11 @@ alwaysApply: true
|
|
|
26
26
|
|
|
27
27
|
# Migration Tooling Policy — Pointer
|
|
28
28
|
|
|
29
|
-
> This site (\`${siteName}\`) was generated by the \`@decocms/
|
|
30
|
-
> migration script. The canonical policy that governs how
|
|
31
|
-
> tooling, framework (\`@decocms/
|
|
32
|
-
> (\`@decocms/apps
|
|
29
|
+
> This site (\`${siteName}\`) was generated by the \`@decocms/blocks-cli\`
|
|
30
|
+
> migration script (\`deco-migrate\`). The canonical policy that governs how
|
|
31
|
+
> the migration tooling, framework (\`@decocms/blocks\` + \`@decocms/tanstack\`),
|
|
32
|
+
> and commerce layer (\`@decocms/apps-*\`) evolve lives **upstream**, not in
|
|
33
|
+
> this repo.
|
|
33
34
|
|
|
34
35
|
## Where to read
|
|
35
36
|
|
|
@@ -38,24 +39,24 @@ alwaysApply: true
|
|
|
38
39
|
- **Plan (living tracker, decisions + waves):**
|
|
39
40
|
https://github.com/decocms/deco-start/blob/main/MIGRATION_TOOLING_PLAN.md
|
|
40
41
|
- **Migration skill (phase playbook):**
|
|
41
|
-
|
|
42
|
+
https://github.com/decocms/deco-start/blob/main/.agents/skills/deco-to-tanstack-migration/SKILL.md
|
|
42
43
|
|
|
43
44
|
## What you need to know in this site
|
|
44
45
|
|
|
45
46
|
| ID | Decision | What it means here |
|
|
46
47
|
|----|----------|--------------------|
|
|
47
|
-
| **D1** | Force convergence — no fork runtime support | Site customisations live in \`src/apps/local/\` or open a PR to \`@decocms/apps
|
|
48
|
+
| **D1** | Force convergence — no fork runtime support | Site customisations live in \`src/apps/local/\` or open a PR to \`@decocms/apps-*\`. Don't wrap framework/commerce code in soft adapters. |
|
|
48
49
|
| **D2** | Rewrite HTMX on migration | If you find HTMX residue, rewrite to React. Don't bring back \`hx-*\` runtime. |
|
|
49
|
-
| **D3** | Generated stubs throw at runtime | If a \`~/lib/vtex-*\` import comes from a stub that returns \`null\` / \`{}\` / identity-cast, replace it. \`npx -p @decocms/
|
|
50
|
-
| **D4** | Site-local apps by default, promote at 3+ sites | Don't try to upstream a pattern that has only shipped here. Build it twice in different sites first, then PR it to \`@decocms/apps
|
|
50
|
+
| **D3** | Generated stubs throw at runtime | If a \`~/lib/vtex-*\` import comes from a stub that returns \`null\` / \`{}\` / identity-cast, replace it. \`npx -p @decocms/blocks-cli deco-post-cleanup --fix\` does the safe swaps automatically. |
|
|
51
|
+
| **D4** | Site-local apps by default, promote at 3+ sites | Don't try to upstream a pattern that has only shipped here. Build it twice in different sites first, then PR it to \`@decocms/apps-*\`. |
|
|
51
52
|
| **D5** | Failed migrations: \`rm -rf\` and re-run | No restart-mode magic. If the migration goes sideways, blow away the working tree and run again. |
|
|
52
53
|
|
|
53
54
|
## How to find issues this rule wants you to fix
|
|
54
55
|
|
|
55
56
|
\`\`\`bash
|
|
56
|
-
npx -p @decocms/
|
|
57
|
-
npx -p @decocms/
|
|
58
|
-
npx -p @decocms/
|
|
57
|
+
npx -p @decocms/blocks-cli deco-post-cleanup # audit only
|
|
58
|
+
npx -p @decocms/blocks-cli deco-post-cleanup --fix # auto-fix the safe rules
|
|
59
|
+
npx -p @decocms/blocks-cli deco-post-cleanup --strict # exit 1 on any finding (CI)
|
|
59
60
|
\`\`\`
|
|
60
61
|
|
|
61
62
|
## Process
|
|
@@ -44,12 +44,12 @@ describe("generateHooks (vtex)", () => {
|
|
|
44
44
|
const code = files["src/hooks/useCart.ts"];
|
|
45
45
|
// Imports from the framework factory.
|
|
46
46
|
expect(code).toContain(
|
|
47
|
-
'import { createUseCart } from "@decocms/apps
|
|
47
|
+
'import { createUseCart } from "@decocms/apps-vtex/hooks/createUseCart"',
|
|
48
48
|
);
|
|
49
49
|
expect(code).toContain('import { invoke } from "~/server/invoke"');
|
|
50
|
-
// Re-exports types from @decocms/apps directly.
|
|
50
|
+
// Re-exports types from @decocms/apps-vtex directly.
|
|
51
51
|
expect(code).toContain(
|
|
52
|
-
'export type { OrderForm, OrderFormItem } from "@decocms/apps
|
|
52
|
+
'export type { OrderForm, OrderFormItem } from "@decocms/apps-vtex/types"',
|
|
53
53
|
);
|
|
54
54
|
// Calls the factory with invoke and destructures the public API.
|
|
55
55
|
expect(code).toContain(
|
|
@@ -70,11 +70,11 @@ describe("generateHooks (vtex)", () => {
|
|
|
70
70
|
it("useUser is the createUseUser factory shim (no signal-stub boilerplate)", () => {
|
|
71
71
|
const code = files["src/hooks/useUser.ts"];
|
|
72
72
|
expect(code).toContain(
|
|
73
|
-
'import { createUseUser } from "@decocms/apps
|
|
73
|
+
'import { createUseUser } from "@decocms/apps-vtex/hooks/createUseUser"',
|
|
74
74
|
);
|
|
75
75
|
expect(code).toContain('import { invoke } from "~/server/invoke"');
|
|
76
76
|
expect(code).toContain(
|
|
77
|
-
'export type { Person } from "@decocms/apps
|
|
77
|
+
'export type { Person } from "@decocms/apps-vtex/loaders/user"',
|
|
78
78
|
);
|
|
79
79
|
expect(code).toContain("export const { useUser, resetUser } = createUseUser");
|
|
80
80
|
// Must NOT scaffold the legacy signal stub.
|
|
@@ -85,11 +85,11 @@ describe("generateHooks (vtex)", () => {
|
|
|
85
85
|
it("useWishlist is the createUseWishlist factory shim", () => {
|
|
86
86
|
const code = files["src/hooks/useWishlist.ts"];
|
|
87
87
|
expect(code).toContain(
|
|
88
|
-
'import { createUseWishlist } from "@decocms/apps
|
|
88
|
+
'import { createUseWishlist } from "@decocms/apps-vtex/hooks/createUseWishlist"',
|
|
89
89
|
);
|
|
90
90
|
expect(code).toContain('import { invoke } from "~/server/invoke"');
|
|
91
91
|
expect(code).toContain(
|
|
92
|
-
'export type { WishlistItem } from "@decocms/apps
|
|
92
|
+
'export type { WishlistItem } from "@decocms/apps-vtex/loaders/wishlist"',
|
|
93
93
|
);
|
|
94
94
|
expect(code).toContain(
|
|
95
95
|
"export const { useWishlist, resetWishlist } = createUseWishlist",
|
|
@@ -19,11 +19,11 @@ export function generateHooks(ctx: MigrationContext): Record<string, string> {
|
|
|
19
19
|
function generateVtexUseCart(): string {
|
|
20
20
|
// The legacy invoke-based useCart hook is now a 5-line factory call —
|
|
21
21
|
// the heavy lifting (singleton state, listener pattern, async actions,
|
|
22
|
-
// analytics helpers) lives in @decocms/apps
|
|
23
|
-
return `import { createUseCart } from "@decocms/apps
|
|
22
|
+
// analytics helpers) lives in @decocms/apps-vtex/hooks/createUseCart.
|
|
23
|
+
return `import { createUseCart } from "@decocms/apps-vtex/hooks/createUseCart";
|
|
24
24
|
import { invoke } from "~/server/invoke";
|
|
25
25
|
|
|
26
|
-
export type { OrderForm, OrderFormItem } from "@decocms/apps
|
|
26
|
+
export type { OrderForm, OrderFormItem } from "@decocms/apps-vtex/types";
|
|
27
27
|
|
|
28
28
|
export const { useCart, resetCart, itemToAnalyticsItem } = createUseCart({
|
|
29
29
|
invoke,
|
|
@@ -70,22 +70,22 @@ export default useCart;
|
|
|
70
70
|
// VTEX path — these are five-line factory shims. The heavy lifting
|
|
71
71
|
// (singleton state, listener pattern, async actions, signal-shaped
|
|
72
72
|
// accessors, legacy arg-swap conventions) lives in
|
|
73
|
-
// @decocms/apps
|
|
73
|
+
// @decocms/apps-vtex/hooks/createUseUser and createUseWishlist.
|
|
74
74
|
function generateVtexUseUser(): string {
|
|
75
|
-
return `import { createUseUser } from "@decocms/apps
|
|
75
|
+
return `import { createUseUser } from "@decocms/apps-vtex/hooks/createUseUser";
|
|
76
76
|
import { invoke } from "~/server/invoke";
|
|
77
77
|
|
|
78
|
-
export type { Person } from "@decocms/apps
|
|
78
|
+
export type { Person } from "@decocms/apps-vtex/loaders/user";
|
|
79
79
|
|
|
80
80
|
export const { useUser, resetUser } = createUseUser({ invoke });
|
|
81
81
|
`;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function generateVtexUseWishlist(): string {
|
|
85
|
-
return `import { createUseWishlist } from "@decocms/apps
|
|
85
|
+
return `import { createUseWishlist } from "@decocms/apps-vtex/hooks/createUseWishlist";
|
|
86
86
|
import { invoke } from "~/server/invoke";
|
|
87
87
|
|
|
88
|
-
export type { WishlistItem } from "@decocms/apps
|
|
88
|
+
export type { WishlistItem } from "@decocms/apps-vtex/loaders/wishlist";
|
|
89
89
|
|
|
90
90
|
export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
|
|
91
91
|
`;
|
|
@@ -98,7 +98,7 @@ function generateGenericUseUser(): string {
|
|
|
98
98
|
return `/**
|
|
99
99
|
* User Hook — wire to invoke.site.loaders for your platform's user API.
|
|
100
100
|
*
|
|
101
|
-
* VTEX sites get a real factory from @decocms/apps
|
|
101
|
+
* VTEX sites get a real factory from @decocms/apps-vtex/hooks/createUseUser;
|
|
102
102
|
* see migration template hooks.ts for the canonical five-line shim.
|
|
103
103
|
*/
|
|
104
104
|
import { signal } from "~/sdk/signal";
|
|
@@ -125,7 +125,7 @@ function generateGenericUseWishlist(): string {
|
|
|
125
125
|
return `/**
|
|
126
126
|
* Wishlist Hook — wire to invoke.site.loaders/actions for your platform.
|
|
127
127
|
*
|
|
128
|
-
* VTEX sites get a real factory from @decocms/apps
|
|
128
|
+
* VTEX sites get a real factory from @decocms/apps-vtex/hooks/createUseWishlist;
|
|
129
129
|
* see migration template hooks.ts for the canonical five-line shim.
|
|
130
130
|
*/
|
|
131
131
|
import { signal } from "~/sdk/signal";
|
|
@@ -99,7 +99,7 @@ describe("D3 — generated stubs throw at runtime", () => {
|
|
|
99
99
|
it("vtex-transform.toProduct throws and points at the canonical path", () => {
|
|
100
100
|
const src = LIB_TEMPLATES["src/lib/vtex-transform.ts"];
|
|
101
101
|
expect(src).toMatch(/throw new Error/);
|
|
102
|
-
expect(src).toMatch(/@decocms\/apps
|
|
102
|
+
expect(src).toMatch(/@decocms\/apps-vtex\/utils\/transform/);
|
|
103
103
|
expect(src).toMatch(/\[deco-migrate\]/);
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -117,7 +117,7 @@ describe("D3 — generated stubs throw at runtime", () => {
|
|
|
117
117
|
const src = LIB_TEMPLATES["src/lib/vtex-segment.ts"];
|
|
118
118
|
expect(src).toMatch(/getSegmentFromBag[\s\S]*?throw new Error/);
|
|
119
119
|
expect(src).toMatch(/withSegmentCookie[\s\S]*?throw new Error/);
|
|
120
|
-
expect(src).toMatch(/@decocms\/apps
|
|
120
|
+
expect(src).toMatch(/@decocms\/apps-vtex\/utils\/segment/);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
it("non-stub helpers stay implemented (negative check — no throw)", () => {
|
|
@@ -46,11 +46,11 @@ export function selectImportedLibTemplates(
|
|
|
46
46
|
//
|
|
47
47
|
// Each thrown message points at the canonical replacement so the fix
|
|
48
48
|
// is mechanical. `deco-post-cleanup --fix` automates the swap.
|
|
49
|
-
const LIB_VTEX_TRANSFORM = `import type { Product } from "@decocms/apps
|
|
49
|
+
const LIB_VTEX_TRANSFORM = `import type { Product } from "@decocms/apps-commerce/types";
|
|
50
50
|
|
|
51
51
|
const STUB =
|
|
52
52
|
"[deco-migrate] \`~/lib/vtex-transform.toProduct\` is a generated stub. " +
|
|
53
|
-
"Replace with: import { toProduct } from '@decocms/apps
|
|
53
|
+
"Replace with: import { toProduct } from '@decocms/apps-vtex/utils/transform' " +
|
|
54
54
|
"(canonical signature: \`toProduct(product, sku, level, options)\`). " +
|
|
55
55
|
"Run \`deco-post-cleanup --fix\` or see the deco-to-tanstack-migration skill " +
|
|
56
56
|
"(post-migration-cleanup § 5).";
|
|
@@ -119,13 +119,13 @@ const LIB_VTEX_SEGMENT = `// Per the migration tooling policy (D3): both these s
|
|
|
119
119
|
const STUB_GET_SEGMENT_FROM_BAG =
|
|
120
120
|
"[deco-migrate] \`~/lib/vtex-segment.getSegmentFromBag\` is a generated " +
|
|
121
121
|
"stub. Refactor: read cookies via \`request.headers.get('cookie')\` then " +
|
|
122
|
-
"call \`buildSegmentFromCookies()\` from '@decocms/apps
|
|
122
|
+
"call \`buildSegmentFromCookies()\` from '@decocms/apps-vtex/utils/segment'. " +
|
|
123
123
|
"The bag-based lookup mechanism does not exist on TanStack Start.";
|
|
124
124
|
|
|
125
125
|
const STUB_WITH_SEGMENT_COOKIE =
|
|
126
126
|
"[deco-migrate] \`~/lib/vtex-segment.withSegmentCookie\` is a generated " +
|
|
127
127
|
"stub. Replace with: import { withSegmentCookie } from " +
|
|
128
|
-
"'@decocms/apps
|
|
128
|
+
"'@decocms/apps-vtex/utils/segment' (canonical signature: " +
|
|
129
129
|
"\`withSegmentCookie(segment, headers?)\`). Run \`deco-post-cleanup --fix\` " +
|
|
130
130
|
"or see the deco-to-tanstack-migration skill.";
|
|
131
131
|
|