@decocms/blocks-cli 7.34.2 → 7.34.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-loaders.test.ts +4 -2
- package/scripts/generate-sections.test.ts +23 -2
- package/scripts/generate-sections.ts +3 -1
- package/scripts/lib/legacyArtifact.ts +35 -5
- package/scripts/migrate/templates/routes.test.ts +31 -0
- package/scripts/migrate/templates/routes.ts +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks-cli",
|
|
3
|
-
"version": "7.34.
|
|
3
|
+
"version": "7.34.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lint:unused": "knip"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@decocms/blocks": "7.34.
|
|
34
|
+
"@decocms/blocks": "7.34.3",
|
|
35
35
|
"ts-morph": "^27.0.0",
|
|
36
36
|
"tsx": "^4.22.5"
|
|
37
37
|
},
|
|
@@ -95,10 +95,12 @@ describe("generate-loaders legacy artifact sync", () => {
|
|
|
95
95
|
// New path must exist.
|
|
96
96
|
expect(fs.existsSync(path.join(tmpDir, ".deco", "loaders.gen.ts"))).toBe(true);
|
|
97
97
|
|
|
98
|
-
// Old file must be synced (not stale placeholder
|
|
98
|
+
// Old file must be synced as a re-export shim (not the stale placeholder,
|
|
99
|
+
// and not a verbatim copy — a byte copy would carry `.deco/`-relative
|
|
100
|
+
// import paths that break at the deeper legacy location).
|
|
99
101
|
const oldContent = fs.readFileSync(path.join(oldDir, "loaders.gen.ts"), "utf-8");
|
|
100
102
|
expect(oldContent).not.toBe("// stale\n");
|
|
101
|
-
expect(oldContent).toContain("
|
|
103
|
+
expect(oldContent).toContain('export * from "../../../.deco/loaders.gen"');
|
|
102
104
|
}, 30_000);
|
|
103
105
|
|
|
104
106
|
it("does not warn and does not sync when an explicit --out-file is passed", () => {
|
|
@@ -72,6 +72,22 @@ describe("generate-sections walkDir exclusions", () => {
|
|
|
72
72
|
expect(generated).not.toContain("Hero.stories.tsx");
|
|
73
73
|
expect(generated).not.toContain("sections.gen.ts");
|
|
74
74
|
}, 30_000);
|
|
75
|
+
|
|
76
|
+
it("extracts `export const deferred = true` into sectionMeta", () => {
|
|
77
|
+
fs.writeFileSync(
|
|
78
|
+
path.join(sectionsDir, "HeavyPLP.tsx"),
|
|
79
|
+
"export const deferred = true;\nexport default function HeavyPLP() { return null; }\n",
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { code } = runGenerator(["--sections-dir", sectionsDir, "--out-file", outFile]);
|
|
83
|
+
expect(code).toBe(0);
|
|
84
|
+
|
|
85
|
+
const generated = fs.readFileSync(outFile, "utf-8");
|
|
86
|
+
// The per-section deferral flag must survive the regex scan → sectionMeta,
|
|
87
|
+
// or applySectionConventions can never register it as always-defer.
|
|
88
|
+
expect(generated).toMatch(/"site\/sections\/HeavyPLP\.tsx":\s*\{[^}]*deferred:\s*true/);
|
|
89
|
+
expect(generated).toContain("deferred?: boolean;");
|
|
90
|
+
}, 30_000);
|
|
75
91
|
});
|
|
76
92
|
|
|
77
93
|
describe("generate-sections default output path (.deco/)", () => {
|
|
@@ -119,9 +135,14 @@ describe("generate-sections default output path (.deco/)", () => {
|
|
|
119
135
|
expect(fs.existsSync(newDefault)).toBe(true);
|
|
120
136
|
expect(fs.readFileSync(newDefault, "utf-8")).toContain("site/sections/Hero.tsx");
|
|
121
137
|
|
|
122
|
-
// Old file
|
|
138
|
+
// Old file is kept in sync as a re-export shim (NOT a verbatim copy — a
|
|
139
|
+
// byte copy would carry `.deco/`-relative import paths that break at the
|
|
140
|
+
// deeper legacy location). It re-exports the new file so mid-migration
|
|
141
|
+
// imports still resolve at any depth.
|
|
123
142
|
const oldDefault = path.join(tmpDir, "src", "server", "cms", "sections.gen.ts");
|
|
124
|
-
|
|
143
|
+
const oldContent = fs.readFileSync(oldDefault, "utf-8");
|
|
144
|
+
expect(oldContent).toContain('export * from "../../../.deco/sections.gen"');
|
|
145
|
+
expect(oldContent).not.toContain("site/sections/Hero.tsx");
|
|
125
146
|
}, 30_000);
|
|
126
147
|
|
|
127
148
|
it("does not warn when an explicit --out-file is passed, even if the OLD default file exists", () => {
|
|
@@ -59,6 +59,7 @@ const EMIT_REGISTRY = args.includes("--registry");
|
|
|
59
59
|
interface SectionMeta {
|
|
60
60
|
eager?: boolean;
|
|
61
61
|
neverDefer?: boolean;
|
|
62
|
+
deferred?: boolean;
|
|
62
63
|
cache?: string;
|
|
63
64
|
layout?: boolean;
|
|
64
65
|
sync?: boolean;
|
|
@@ -67,7 +68,7 @@ interface SectionMeta {
|
|
|
67
68
|
hasLoadingFallback?: boolean;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
const EXPORT_CONST_RE = /export\s+const\s+(eager|neverDefer|cache|layout|sync|clientOnly|seo)\s*=\s*(.+?)(?:;|\n)/g;
|
|
71
|
+
const EXPORT_CONST_RE = /export\s+const\s+(eager|neverDefer|deferred|cache|layout|sync|clientOnly|seo)\s*=\s*(.+?)(?:;|\n)/g;
|
|
71
72
|
// Detects `export function LoadingFallback(...)`, `export const LoadingFallback = ...`, etc.
|
|
72
73
|
const LOADING_FALLBACK_INLINE_RE = /export\s+(?:function|const|let|var)\s+LoadingFallback\b/;
|
|
73
74
|
// Detects re-exports like:
|
|
@@ -209,6 +210,7 @@ lines.push("");
|
|
|
209
210
|
lines.push("export interface SectionMetaEntry {");
|
|
210
211
|
lines.push(" eager?: boolean;");
|
|
211
212
|
lines.push(" neverDefer?: boolean;");
|
|
213
|
+
lines.push(" deferred?: boolean;");
|
|
212
214
|
lines.push(" cache?: string;");
|
|
213
215
|
lines.push(" layout?: boolean;");
|
|
214
216
|
lines.push(" sync?: boolean;");
|
|
@@ -23,12 +23,42 @@ export function warnLegacyArtifact(oldPath: string, newPath: string): void {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* sites mid-migration whose imports still point at the old location
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Keeps the legacy `oldPath` in sync with the freshly-generated `newPath` so
|
|
27
|
+
* sites mid-migration whose imports still point at the old location keep
|
|
28
|
+
* working without a full migration first. Call this after generation
|
|
29
|
+
* completes, only when the old file was detected.
|
|
30
|
+
*
|
|
31
|
+
* For `.ts`/`.tsx` artifacts a verbatim copy would be broken: the generated
|
|
32
|
+
* file's import specifiers are relative to `newPath`'s directory (`.deco/`,
|
|
33
|
+
* one level deep), but `oldPath` lives deeper (`src/server/cms/`), so every
|
|
34
|
+
* `../src/...` specifier would resolve to a nonexistent path and crash the
|
|
35
|
+
* dev server on boot. Instead we write a re-export shim pointing at `newPath`
|
|
36
|
+
* — one specifier, correct at any depth, and self-documenting the deprecation.
|
|
37
|
+
* The generated `.gen.ts` files only have named exports, so `export *` forwards
|
|
38
|
+
* everything (including type exports). Non-TS artifacts (e.g. `.json` schema)
|
|
39
|
+
* have no import paths, so a byte copy is still correct.
|
|
30
40
|
*/
|
|
31
41
|
export function syncLegacyArtifact(oldPath: string, newPath: string): void {
|
|
32
42
|
fs.mkdirSync(path.dirname(oldPath), { recursive: true });
|
|
33
|
-
|
|
43
|
+
|
|
44
|
+
if (!/\.tsx?$/.test(oldPath)) {
|
|
45
|
+
fs.copyFileSync(newPath, oldPath);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const newRel = path.relative(process.cwd(), newPath).replace(/\\/g, "/");
|
|
50
|
+
let specifier = path
|
|
51
|
+
.relative(path.dirname(oldPath), newPath)
|
|
52
|
+
.replace(/\\/g, "/")
|
|
53
|
+
.replace(/\.tsx?$/, "");
|
|
54
|
+
if (!specifier.startsWith(".")) specifier = `./${specifier}`;
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
oldPath,
|
|
58
|
+
`// AUTO-GENERATED — DO NOT EDIT.\n` +
|
|
59
|
+
`// Deprecated path: the generator now writes to "${newRel}".\n` +
|
|
60
|
+
`// This file only re-exports it so mid-migration imports keep working.\n` +
|
|
61
|
+
`// Update your imports to the new path, then delete this file.\n` +
|
|
62
|
+
`export * from "${specifier}";\n`,
|
|
63
|
+
);
|
|
34
64
|
}
|
|
@@ -57,3 +57,34 @@ describe("scaffolded deco admin routes use HMR-safe factories", () => {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Regression guard: the page renderers must wire `loadDeferredSectionFn` to the
|
|
63
|
+
* `deferredSectionLoader` shim, never the raw `loadDeferredSection` server fn.
|
|
64
|
+
* `loadDeferredSection` is a createServerFn with an inputValidator, so it must
|
|
65
|
+
* be called as `fn({ data })`; DeferredSectionWrapper calls `loadFn(bareArgs)`,
|
|
66
|
+
* so passing the raw fn leaves `ctx.data` undefined and the deferred section
|
|
67
|
+
* renders blank. The shim wraps the `{ data }` envelope.
|
|
68
|
+
*/
|
|
69
|
+
describe("scaffolded page routes wire the deferred-loader shim, not the raw server fn", () => {
|
|
70
|
+
for (const platform of ["vtex", "custom"] as const) {
|
|
71
|
+
describe(`platform: ${platform}`, () => {
|
|
72
|
+
const files = generateRoutes(makeCtx(platform));
|
|
73
|
+
|
|
74
|
+
for (const file of ["src/routes/index.tsx", "src/routes/$.tsx"] as const) {
|
|
75
|
+
it(`${file} passes deferredSectionLoader from the sdk subpath`, () => {
|
|
76
|
+
const content = files[file];
|
|
77
|
+
expect(content, `${file} must be emitted`).toBeTypeOf("string");
|
|
78
|
+
|
|
79
|
+
expect(content).toContain(
|
|
80
|
+
'import { deferredSectionLoader } from "@decocms/tanstack/sdk/deferredSectionLoader"',
|
|
81
|
+
);
|
|
82
|
+
expect(content).toContain("loadDeferredSectionFn={deferredSectionLoader}");
|
|
83
|
+
|
|
84
|
+
// The broken form: the raw server fn passed directly.
|
|
85
|
+
expect(content).not.toContain("loadDeferredSectionFn={loadDeferredSection}");
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
@@ -115,7 +115,8 @@ function RootLayout() {
|
|
|
115
115
|
|
|
116
116
|
function generateIndex(ctx: MigrationContext, siteTitle: string): string {
|
|
117
117
|
return `import { createFileRoute } from "@tanstack/react-router";
|
|
118
|
-
import { cmsHomeRouteConfig, DecoPageRenderer
|
|
118
|
+
import { cmsHomeRouteConfig, DecoPageRenderer } from "@decocms/tanstack";
|
|
119
|
+
import { deferredSectionLoader } from "@decocms/tanstack/sdk/deferredSectionLoader";
|
|
119
120
|
|
|
120
121
|
// MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
|
|
121
122
|
// copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
|
|
@@ -151,7 +152,7 @@ function HomePage() {
|
|
|
151
152
|
deferredPromises={data.deferredPromises}
|
|
152
153
|
pagePath={data.pagePath}
|
|
153
154
|
pageUrl={data.pageUrl}
|
|
154
|
-
loadDeferredSectionFn={
|
|
155
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
155
156
|
/>
|
|
156
157
|
);
|
|
157
158
|
}
|
|
@@ -160,7 +161,8 @@ function HomePage() {
|
|
|
160
161
|
|
|
161
162
|
function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
|
|
162
163
|
return `import { createFileRoute } from "@tanstack/react-router";
|
|
163
|
-
import { cmsRouteConfig, DecoPageRenderer
|
|
164
|
+
import { cmsRouteConfig, DecoPageRenderer } from "@decocms/tanstack";
|
|
165
|
+
import { deferredSectionLoader } from "@decocms/tanstack/sdk/deferredSectionLoader";
|
|
164
166
|
|
|
165
167
|
// MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
|
|
166
168
|
// (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
|
|
@@ -188,7 +190,7 @@ function CmsPage() {
|
|
|
188
190
|
deferredPromises={data.deferredPromises}
|
|
189
191
|
pagePath={data.pagePath}
|
|
190
192
|
pageUrl={data.pageUrl}
|
|
191
|
-
loadDeferredSectionFn={
|
|
193
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
192
194
|
/>
|
|
193
195
|
);
|
|
194
196
|
}
|