@decocms/blocks-cli 7.3.1 → 7.5.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/package.json +2 -2
- package/scripts/generate-blocks.ts +16 -2
- package/scripts/generate-invoke.ts +10 -0
- package/scripts/generate-loaders.ts +13 -2
- package/scripts/generate-schema.test.ts +121 -2
- package/scripts/generate-schema.ts +29 -4
- package/scripts/generate-sections.test.ts +261 -0
- package/scripts/generate-sections.ts +44 -3
- package/scripts/lib/codegenExclusions.test.ts +22 -0
- package/scripts/lib/codegenExclusions.ts +11 -0
- package/scripts/lib/legacyArtifact.ts +20 -0
- package/scripts/migrate/templates/commerce-loaders.ts +1 -1
- package/scripts/migrate/templates/knip-config.ts +0 -1
- package/scripts/migrate/templates/setup.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks-cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"lint:unused": "knip"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@decocms/blocks": "7.
|
|
37
|
+
"@decocms/blocks": "7.5.0",
|
|
38
38
|
"ts-morph": "^27.0.0",
|
|
39
39
|
"tsx": "^4.19.0"
|
|
40
40
|
},
|
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Env / CLI:
|
|
16
16
|
* --blocks-dir override input (default: .deco/blocks)
|
|
17
|
-
* --out-file override output (default:
|
|
17
|
+
* --out-file override output (default: .deco/blocks.gen.ts, with a sibling
|
|
18
|
+
* .deco/blocks.gen.json — the .json path is always derived from
|
|
19
|
+
* --out-file by swapping its extension, so passing --out-file
|
|
20
|
+
* moves both artifacts together)
|
|
21
|
+
*
|
|
22
|
+
* If no `--out-file` is passed and the OLD default (src/server/cms/blocks.gen.ts)
|
|
23
|
+
* still exists on disk, a one-line legacy warning is printed to stderr and the
|
|
24
|
+
* NEW default is written anyway — see lib/legacyArtifact.ts.
|
|
18
25
|
*
|
|
19
26
|
* Programmatic:
|
|
20
27
|
* import { generateBlocks } from "@decocms/blocks-cli/generate-blocks";
|
|
@@ -33,6 +40,7 @@ import {
|
|
|
33
40
|
mergeCandidates,
|
|
34
41
|
singleDecodeBlockName,
|
|
35
42
|
} from "./lib/blocks-dedupe";
|
|
43
|
+
import { warnLegacyArtifact } from "./lib/legacyArtifact";
|
|
36
44
|
|
|
37
45
|
const TS_STUB = [
|
|
38
46
|
"// Auto-generated — thin wrapper around blocks.gen.json.",
|
|
@@ -291,7 +299,13 @@ if (isMainModule()) {
|
|
|
291
299
|
};
|
|
292
300
|
|
|
293
301
|
const blocksDir = path.resolve(process.cwd(), arg("blocks-dir", ".deco/blocks"));
|
|
294
|
-
const
|
|
302
|
+
const OUT_FILE_EXPLICIT = args.includes("--out-file");
|
|
303
|
+
const NEW_DEFAULT_OUT_FILE = ".deco/blocks.gen.ts";
|
|
304
|
+
const OLD_DEFAULT_OUT_FILE = "src/server/cms/blocks.gen.ts";
|
|
305
|
+
const outFile = path.resolve(process.cwd(), arg("out-file", NEW_DEFAULT_OUT_FILE));
|
|
306
|
+
if (!OUT_FILE_EXPLICIT && fs.existsSync(path.resolve(process.cwd(), OLD_DEFAULT_OUT_FILE))) {
|
|
307
|
+
warnLegacyArtifact(OLD_DEFAULT_OUT_FILE, NEW_DEFAULT_OUT_FILE);
|
|
308
|
+
}
|
|
295
309
|
|
|
296
310
|
generateBlocks({ blocksDir, outFile }).catch((err) => {
|
|
297
311
|
console.error(err);
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* This script generates an equivalent file where each server function is a
|
|
12
12
|
* top-level const, which the compiler can correctly transform into RPC stubs.
|
|
13
13
|
*
|
|
14
|
+
* Unlike the other four generators (generate-blocks, generate-loaders,
|
|
15
|
+
* generate-sections, generate-schema), this one's default output stays under
|
|
16
|
+
* `src/`, not `.deco/`. The other four emit inert framework artifacts (data
|
|
17
|
+
* snapshots / metadata) that the framework loads at runtime — those belong
|
|
18
|
+
* next to the rest of the framework's own generated state. This file emits
|
|
19
|
+
* actual app server-function code (`createServerFn().handler()` calls) that
|
|
20
|
+
* TanStack Start's own build-time compiler transforms into RPC stubs; it's
|
|
21
|
+
* compiled and shipped as part of the site's application code, so it lives
|
|
22
|
+
* in `src/` alongside the code it's compiled with, not in `.deco/`.
|
|
23
|
+
*
|
|
14
24
|
* Usage (from site root):
|
|
15
25
|
* npx tsx node_modules/@decocms/blocks-cli/scripts/generate-invoke.ts
|
|
16
26
|
*
|
|
@@ -34,13 +34,18 @@
|
|
|
34
34
|
* CLI:
|
|
35
35
|
* --loaders-dir override loaders input (default: src/loaders)
|
|
36
36
|
* --actions-dir override actions input (default: src/actions)
|
|
37
|
-
* --out-file override output (default:
|
|
37
|
+
* --out-file override output (default: .deco/loaders.gen.ts)
|
|
38
38
|
* --exclude comma-separated list of loader keys to skip (they have custom wiring)
|
|
39
39
|
* --prune-by-decofile only emit entries whose key appears as `__resolveType` in any JSON
|
|
40
40
|
* --decofile-dir @deprecated alias for --prune-by-decofile
|
|
41
|
+
*
|
|
42
|
+
* If no `--out-file` is passed and the OLD default (src/server/cms/loaders.gen.ts)
|
|
43
|
+
* still exists on disk, a one-line legacy warning is printed to stderr and the
|
|
44
|
+
* NEW default is written anyway — see lib/legacyArtifact.ts.
|
|
41
45
|
*/
|
|
42
46
|
import fs from "node:fs";
|
|
43
47
|
import path from "node:path";
|
|
48
|
+
import { warnLegacyArtifact } from "./lib/legacyArtifact";
|
|
44
49
|
|
|
45
50
|
const args = process.argv.slice(2);
|
|
46
51
|
function arg(name: string, fallback: string): string {
|
|
@@ -50,7 +55,13 @@ function arg(name: string, fallback: string): string {
|
|
|
50
55
|
|
|
51
56
|
const loadersDir = path.resolve(process.cwd(), arg("loaders-dir", "src/loaders"));
|
|
52
57
|
const actionsDir = path.resolve(process.cwd(), arg("actions-dir", "src/actions"));
|
|
53
|
-
const
|
|
58
|
+
const OUT_FILE_EXPLICIT = args.includes("--out-file");
|
|
59
|
+
const NEW_DEFAULT_OUT_FILE = ".deco/loaders.gen.ts";
|
|
60
|
+
const OLD_DEFAULT_OUT_FILE = "src/server/cms/loaders.gen.ts";
|
|
61
|
+
const outFile = path.resolve(process.cwd(), arg("out-file", NEW_DEFAULT_OUT_FILE));
|
|
62
|
+
if (!OUT_FILE_EXPLICIT && fs.existsSync(path.resolve(process.cwd(), OLD_DEFAULT_OUT_FILE))) {
|
|
63
|
+
warnLegacyArtifact(OLD_DEFAULT_OUT_FILE, NEW_DEFAULT_OUT_FILE);
|
|
64
|
+
}
|
|
54
65
|
const excludeRaw = arg("exclude", "");
|
|
55
66
|
const excludeSet = new Set(excludeRaw.split(",").map((s) => s.trim()).filter(Boolean));
|
|
56
67
|
|
|
@@ -1,6 +1,33 @@
|
|
|
1
|
+
import * as cp from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
1
5
|
import { Project } from "ts-morph";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import {
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
WIDGET_TYPE_FORMATS,
|
|
9
|
+
applyWidgetFormat,
|
|
10
|
+
definitionIdForPath,
|
|
11
|
+
typeToJsonSchema,
|
|
12
|
+
} from "./generate-schema";
|
|
13
|
+
|
|
14
|
+
describe("definitionIdForPath", () => {
|
|
15
|
+
it("is repo-relative, never absolute", () => {
|
|
16
|
+
const id = definitionIdForPath(
|
|
17
|
+
"/Users/anyone/code/mysite/src/sections/Hero.tsx",
|
|
18
|
+
"/Users/anyone/code/mysite",
|
|
19
|
+
);
|
|
20
|
+
expect(Buffer.from(id, "base64").toString()).toBe("src/sections/Hero.tsx");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("normalizes file:// prefixes from ts-morph", () => {
|
|
24
|
+
const id = definitionIdForPath(
|
|
25
|
+
"file:///Users/anyone/code/mysite/src/sections/Hero.tsx",
|
|
26
|
+
"/Users/anyone/code/mysite",
|
|
27
|
+
);
|
|
28
|
+
expect(Buffer.from(id, "base64").toString()).toBe("src/sections/Hero.tsx");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
4
31
|
|
|
5
32
|
describe("applyWidgetFormat", () => {
|
|
6
33
|
it("recovers an unresolved widget alias (empty schema) as string + format", () => {
|
|
@@ -72,3 +99,95 @@ describe("typeToJsonSchema with an unresolvable widget alias import", () => {
|
|
|
72
99
|
});
|
|
73
100
|
});
|
|
74
101
|
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Default output path (.deco/) + legacy warning — subprocess, mirrors the
|
|
105
|
+
// pattern in generate-sections.test.ts. generate-schema.ts IS guarded by
|
|
106
|
+
// isMainModule(), but it's still driven as a subprocess here so the CLI's
|
|
107
|
+
// argv-parsed OUT_REL/legacy-check top-level code runs against a real cwd.
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
const SCRIPT = path.resolve(__dirname, "generate-schema.ts");
|
|
111
|
+
|
|
112
|
+
function runGenerator(
|
|
113
|
+
args: string[],
|
|
114
|
+
opts: { cwd?: string } = {},
|
|
115
|
+
): { stdout: string; stderr: string; code: number } {
|
|
116
|
+
const r = cp.spawnSync("npx", ["tsx", SCRIPT, ...args], { encoding: "utf8", cwd: opts.cwd });
|
|
117
|
+
return { stdout: r.stdout || "", stderr: r.stderr || "", code: r.status ?? 0 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
describe("generate-schema default output path (.deco/)", () => {
|
|
121
|
+
let tmpDir: string;
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "generate-schema-defaults-"));
|
|
125
|
+
fs.writeFileSync(
|
|
126
|
+
path.join(tmpDir, "tsconfig.json"),
|
|
127
|
+
JSON.stringify({
|
|
128
|
+
compilerOptions: {
|
|
129
|
+
target: "ES2020",
|
|
130
|
+
module: "ESNext",
|
|
131
|
+
moduleResolution: "Bundler",
|
|
132
|
+
jsx: "react-jsx",
|
|
133
|
+
skipLibCheck: true,
|
|
134
|
+
strict: true,
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
const sectionsDir = path.join(tmpDir, "src", "sections");
|
|
139
|
+
fs.mkdirSync(sectionsDir, { recursive: true });
|
|
140
|
+
fs.writeFileSync(
|
|
141
|
+
path.join(sectionsDir, "Hero.tsx"),
|
|
142
|
+
[
|
|
143
|
+
"export interface Props {",
|
|
144
|
+
" title: string;",
|
|
145
|
+
"}",
|
|
146
|
+
"export default function Hero(props: Props) { return null; }",
|
|
147
|
+
].join("\n"),
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
afterEach(() => {
|
|
152
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("writes to .deco/meta.gen.json when no --out flag is passed", () => {
|
|
156
|
+
const { code } = runGenerator(["--skip-apps"], { cwd: tmpDir });
|
|
157
|
+
expect(code).toBe(0);
|
|
158
|
+
|
|
159
|
+
const newDefault = path.join(tmpDir, ".deco", "meta.gen.json");
|
|
160
|
+
expect(fs.existsSync(newDefault)).toBe(true);
|
|
161
|
+
const meta = JSON.parse(fs.readFileSync(newDefault, "utf-8"));
|
|
162
|
+
expect(meta.manifest.blocks.sections).toHaveProperty("site/sections/Hero.tsx");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("warns once to stderr naming both paths when the OLD default file exists and no --out is passed, but still writes the NEW default", () => {
|
|
166
|
+
const oldDefaultDir = path.join(tmpDir, "src", "server", "admin");
|
|
167
|
+
fs.mkdirSync(oldDefaultDir, { recursive: true });
|
|
168
|
+
fs.writeFileSync(path.join(oldDefaultDir, "meta.gen.json"), "{}");
|
|
169
|
+
|
|
170
|
+
const { code, stderr } = runGenerator(["--skip-apps"], { cwd: tmpDir });
|
|
171
|
+
expect(code).toBe(0);
|
|
172
|
+
|
|
173
|
+
expect(stderr).toContain("src/server/admin/meta.gen.json");
|
|
174
|
+
expect(stderr).toContain(".deco/meta.gen.json");
|
|
175
|
+
expect(stderr).toContain("Move the file and update its importers");
|
|
176
|
+
|
|
177
|
+
const newDefault = path.join(tmpDir, ".deco", "meta.gen.json");
|
|
178
|
+
expect(fs.existsSync(newDefault)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("does not warn when an explicit --out is passed, even if the OLD default file exists", () => {
|
|
182
|
+
const oldDefaultDir = path.join(tmpDir, "src", "server", "admin");
|
|
183
|
+
fs.mkdirSync(oldDefaultDir, { recursive: true });
|
|
184
|
+
fs.writeFileSync(path.join(oldDefaultDir, "meta.gen.json"), "{}");
|
|
185
|
+
|
|
186
|
+
const explicitOut = path.join(tmpDir, "custom", "meta.gen.json");
|
|
187
|
+
const { code, stderr } = runGenerator(["--skip-apps", "--out", explicitOut], { cwd: tmpDir });
|
|
188
|
+
expect(code).toBe(0);
|
|
189
|
+
|
|
190
|
+
expect(stderr).not.toContain("Generator default output moved");
|
|
191
|
+
expect(fs.existsSync(explicitOut)).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -20,8 +20,12 @@ import { fileURLToPath } from "node:url";
|
|
|
20
20
|
* --loaders Loaders directory (default: "src/loaders")
|
|
21
21
|
* --apps Apps directory (default: "src/apps")
|
|
22
22
|
* --skip-apps Skip app schema generation
|
|
23
|
-
* --out Output file (default: "
|
|
23
|
+
* --out Output file (default: ".deco/meta.gen.json")
|
|
24
24
|
* --platform Platform name (default: "cloudflare")
|
|
25
|
+
*
|
|
26
|
+
* If no `--out` is passed and the OLD default (src/server/admin/meta.gen.json)
|
|
27
|
+
* still exists on disk, a one-line legacy warning is printed to stderr and the
|
|
28
|
+
* NEW default is written anyway — see lib/legacyArtifact.ts.
|
|
25
29
|
*/
|
|
26
30
|
import {
|
|
27
31
|
type Symbol as MorphSymbol,
|
|
@@ -31,6 +35,8 @@ import {
|
|
|
31
35
|
SyntaxKind,
|
|
32
36
|
type Type,
|
|
33
37
|
} from "ts-morph";
|
|
38
|
+
import { isExcludedCodegenFile } from "./lib/codegenExclusions";
|
|
39
|
+
import { warnLegacyArtifact } from "./lib/legacyArtifact";
|
|
34
40
|
|
|
35
41
|
// ---------------------------------------------------------------------------
|
|
36
42
|
// CLI arg parsing
|
|
@@ -48,8 +54,14 @@ const SECTIONS_REL = arg("sections", "src/sections");
|
|
|
48
54
|
const LOADERS_REL = arg("loaders", "src/loaders");
|
|
49
55
|
const APPS_REL = arg("apps", "src/apps");
|
|
50
56
|
const SKIP_APPS = argv.includes("--skip-apps");
|
|
51
|
-
const
|
|
57
|
+
const OUT_FILE_EXPLICIT = argv.includes("--out");
|
|
58
|
+
const NEW_DEFAULT_OUT_REL = ".deco/meta.gen.json";
|
|
59
|
+
const OLD_DEFAULT_OUT_REL = "src/server/admin/meta.gen.json";
|
|
60
|
+
const OUT_REL = arg("out", NEW_DEFAULT_OUT_REL);
|
|
52
61
|
const PLATFORM = arg("platform", "cloudflare");
|
|
62
|
+
if (!OUT_FILE_EXPLICIT && fs.existsSync(path.resolve(process.cwd(), OLD_DEFAULT_OUT_REL))) {
|
|
63
|
+
warnLegacyArtifact(OLD_DEFAULT_OUT_REL, NEW_DEFAULT_OUT_REL);
|
|
64
|
+
}
|
|
53
65
|
|
|
54
66
|
// ---------------------------------------------------------------------------
|
|
55
67
|
// Interfaces
|
|
@@ -72,6 +84,18 @@ function toBase64(str: string): string {
|
|
|
72
84
|
return Buffer.from(str).toString("base64");
|
|
73
85
|
}
|
|
74
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Definition IDs must be stable across machines: the raw ts-morph path is
|
|
89
|
+
* absolute (`file:///Users/<user>/...`), which made meta.gen.json differ
|
|
90
|
+
* per machine and destabilized the /live/_meta ETag. IDs are opaque to the
|
|
91
|
+
* admin — only internal $ref consistency matters — so relativize to root.
|
|
92
|
+
*/
|
|
93
|
+
export function definitionIdForPath(filePath: string, rootDir: string): string {
|
|
94
|
+
const cleaned = filePath.replace(/^file:\/+/, "/");
|
|
95
|
+
const rel = path.relative(rootDir, cleaned).replaceAll("\\", "/");
|
|
96
|
+
return toBase64(rel.startsWith("..") ? cleaned : rel);
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
/**
|
|
76
100
|
* Map JSDoc tags to JSON Schema 7 keywords.
|
|
77
101
|
* Supports all 20+ tags from deco-cx/deco.
|
|
@@ -734,6 +758,7 @@ function findTsxFiles(dir: string): string[] {
|
|
|
734
758
|
const results: string[] = [];
|
|
735
759
|
if (!fs.existsSync(dir)) return results;
|
|
736
760
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
761
|
+
if (isExcludedCodegenFile(entry.name)) continue;
|
|
737
762
|
const full = path.join(dir, entry.name);
|
|
738
763
|
if (entry.isDirectory()) results.push(...findTsxFiles(full));
|
|
739
764
|
else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) results.push(full);
|
|
@@ -1141,7 +1166,7 @@ function generateMeta(): MetaResponse {
|
|
|
1141
1166
|
|
|
1142
1167
|
const propCount = Object.keys(propsSchema.properties || {}).length;
|
|
1143
1168
|
|
|
1144
|
-
const propsDefKey =
|
|
1169
|
+
const propsDefKey = definitionIdForPath(filePath, root) + "@Props";
|
|
1145
1170
|
definitions[propsDefKey] = propsSchema;
|
|
1146
1171
|
|
|
1147
1172
|
const sectionDefKey = toBase64(blockKey);
|
|
@@ -1232,7 +1257,7 @@ function generateMeta(): MetaResponse {
|
|
|
1232
1257
|
|
|
1233
1258
|
const propCount = Object.keys(propsSchema.properties || {}).length;
|
|
1234
1259
|
|
|
1235
|
-
const propsDefKey =
|
|
1260
|
+
const propsDefKey = definitionIdForPath(filePath, root) + "@Props";
|
|
1236
1261
|
definitions[propsDefKey] = propsSchema;
|
|
1237
1262
|
|
|
1238
1263
|
const appDefKey = toBase64(blockKey);
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test for `generate-sections.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Drives the script as a child process against a tmp sections-dir fixture,
|
|
5
|
+
* mirroring the pattern used by migrate-to-cf-observability.test.ts. The
|
|
6
|
+
* script has no `isMainModule()` guard (unlike generate-schema.ts) — it runs
|
|
7
|
+
* its filesystem walk and write on import — so it's exercised as a subprocess
|
|
8
|
+
* rather than imported directly.
|
|
9
|
+
*
|
|
10
|
+
* Verifies the operationally important behavior: a co-located test/spec/
|
|
11
|
+
* stories/gen file sitting next to a real section must never be walked into
|
|
12
|
+
* sectionMeta (the fila incident this generator's `walkDir` reproduced:
|
|
13
|
+
* `sections.test.ts` became a bogus section in a site's generated output).
|
|
14
|
+
*/
|
|
15
|
+
import * as cp from "node:child_process";
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as os from "node:os";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
21
|
+
|
|
22
|
+
const SCRIPT = path.resolve(__dirname, "generate-sections.ts");
|
|
23
|
+
|
|
24
|
+
function runGenerator(
|
|
25
|
+
args: string[],
|
|
26
|
+
opts: { cwd?: string } = {},
|
|
27
|
+
): { stdout: string; stderr: string; code: number } {
|
|
28
|
+
const r = cp.spawnSync("npx", ["tsx", SCRIPT, ...args], { encoding: "utf8", cwd: opts.cwd });
|
|
29
|
+
return { stdout: r.stdout || "", stderr: r.stderr || "", code: r.status ?? 0 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("generate-sections walkDir exclusions", () => {
|
|
33
|
+
let tmpDir: string;
|
|
34
|
+
let sectionsDir: string;
|
|
35
|
+
let outFile: string;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "generate-sections-"));
|
|
39
|
+
sectionsDir = path.join(tmpDir, "sections");
|
|
40
|
+
outFile = path.join(tmpDir, "out", "sections.gen.ts");
|
|
41
|
+
fs.mkdirSync(sectionsDir, { recursive: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("excludes co-located test/spec/stories/gen files from the generated sectionMeta", () => {
|
|
49
|
+
fs.writeFileSync(
|
|
50
|
+
path.join(sectionsDir, "Hero.tsx"),
|
|
51
|
+
`export const eager = true;\nexport default function Hero() { return null; }\n`,
|
|
52
|
+
);
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
path.join(sectionsDir, "Hero.test.tsx"),
|
|
55
|
+
`export const eager = true;\nexport default function HeroTest() { return null; }\n`,
|
|
56
|
+
);
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(sectionsDir, "Hero.stories.tsx"),
|
|
59
|
+
`export const eager = true;\nexport default function HeroStories() { return null; }\n`,
|
|
60
|
+
);
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
path.join(sectionsDir, "sections.gen.ts"),
|
|
63
|
+
`export const eager = true;\n`,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const { code } = runGenerator(["--sections-dir", sectionsDir, "--out-file", outFile]);
|
|
67
|
+
expect(code).toBe(0);
|
|
68
|
+
|
|
69
|
+
const generated = fs.readFileSync(outFile, "utf-8");
|
|
70
|
+
expect(generated).toContain("site/sections/Hero.tsx");
|
|
71
|
+
expect(generated).not.toContain("Hero.test.tsx");
|
|
72
|
+
expect(generated).not.toContain("Hero.stories.tsx");
|
|
73
|
+
expect(generated).not.toContain("sections.gen.ts");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("generate-sections default output path (.deco/)", () => {
|
|
78
|
+
let tmpDir: string;
|
|
79
|
+
let sectionsDir: string;
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "generate-sections-defaults-"));
|
|
83
|
+
sectionsDir = path.join(tmpDir, "src", "sections");
|
|
84
|
+
fs.mkdirSync(sectionsDir, { recursive: true });
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
path.join(sectionsDir, "Hero.tsx"),
|
|
87
|
+
"export const eager = true;\nexport default function Hero() { return null; }\n",
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("writes to .deco/sections.gen.ts when no --out-file flag is passed", () => {
|
|
96
|
+
const { code, stderr } = runGenerator([], { cwd: tmpDir });
|
|
97
|
+
expect(code).toBe(0);
|
|
98
|
+
|
|
99
|
+
const newDefault = path.join(tmpDir, ".deco", "sections.gen.ts");
|
|
100
|
+
expect(fs.existsSync(newDefault)).toBe(true);
|
|
101
|
+
expect(fs.readFileSync(newDefault, "utf-8")).toContain("site/sections/Hero.tsx");
|
|
102
|
+
// No legacy file present, so no warning is expected.
|
|
103
|
+
expect(stderr).not.toContain("Generator default output moved");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("warns once to stderr naming both paths when the OLD default file exists and no --out-file is passed, but still writes the NEW default", () => {
|
|
107
|
+
const oldDefaultDir = path.join(tmpDir, "src", "server", "cms");
|
|
108
|
+
fs.mkdirSync(oldDefaultDir, { recursive: true });
|
|
109
|
+
fs.writeFileSync(path.join(oldDefaultDir, "sections.gen.ts"), "// stale\n");
|
|
110
|
+
|
|
111
|
+
const { code, stderr } = runGenerator([], { cwd: tmpDir });
|
|
112
|
+
expect(code).toBe(0);
|
|
113
|
+
|
|
114
|
+
expect(stderr).toContain("src/server/cms/sections.gen.ts");
|
|
115
|
+
expect(stderr).toContain(".deco/sections.gen.ts");
|
|
116
|
+
expect(stderr).toContain("Move the file and update its importers");
|
|
117
|
+
|
|
118
|
+
const newDefault = path.join(tmpDir, ".deco", "sections.gen.ts");
|
|
119
|
+
expect(fs.existsSync(newDefault)).toBe(true);
|
|
120
|
+
expect(fs.readFileSync(newDefault, "utf-8")).toContain("site/sections/Hero.tsx");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("does not warn when an explicit --out-file is passed, even if the OLD default file exists", () => {
|
|
124
|
+
const oldDefaultDir = path.join(tmpDir, "src", "server", "cms");
|
|
125
|
+
fs.mkdirSync(oldDefaultDir, { recursive: true });
|
|
126
|
+
fs.writeFileSync(path.join(oldDefaultDir, "sections.gen.ts"), "// stale\n");
|
|
127
|
+
|
|
128
|
+
const explicitOut = path.join(tmpDir, "custom", "sections.gen.ts");
|
|
129
|
+
const { code, stderr } = runGenerator(["--out-file", explicitOut], { cwd: tmpDir });
|
|
130
|
+
expect(code).toBe(0);
|
|
131
|
+
|
|
132
|
+
expect(stderr).not.toContain("Generator default output moved");
|
|
133
|
+
expect(fs.existsSync(explicitOut)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("generate-sections --registry", () => {
|
|
138
|
+
let tmpDir: string;
|
|
139
|
+
let sectionsDir: string;
|
|
140
|
+
let outFile: string;
|
|
141
|
+
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "generate-sections-registry-"));
|
|
144
|
+
sectionsDir = path.join(tmpDir, "sections");
|
|
145
|
+
outFile = path.join(tmpDir, "out", "sections.gen.ts");
|
|
146
|
+
fs.mkdirSync(path.join(sectionsDir, "Nested"), { recursive: true });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
afterEach(() => {
|
|
150
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
function expectedImportPath(filePath: string): string {
|
|
154
|
+
let rel = path.relative(path.dirname(outFile), filePath).replace(/\\/g, "/");
|
|
155
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
156
|
+
return rel.replace(/\.tsx?$/, "");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
it("emits sectionImports keyed glob-style with relative dynamic imports, built from all scanned section files (not just convention-carrying ones)", () => {
|
|
160
|
+
const heroPath = path.join(sectionsDir, "Hero.tsx");
|
|
161
|
+
const promoPath = path.join(sectionsDir, "Nested", "Promo.tsx");
|
|
162
|
+
fs.writeFileSync(
|
|
163
|
+
heroPath,
|
|
164
|
+
"export const sync = true\nexport default function Hero() { return null }\n",
|
|
165
|
+
);
|
|
166
|
+
// No convention exports — regression guard: without --registry this file
|
|
167
|
+
// never makes it into `entries`, so the registry must be built from the
|
|
168
|
+
// raw `sectionFiles` walk, not from `entries`.
|
|
169
|
+
fs.writeFileSync(
|
|
170
|
+
promoPath,
|
|
171
|
+
"export default function Promo() { return null }\n",
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const { code } = runGenerator([
|
|
175
|
+
"--sections-dir", sectionsDir,
|
|
176
|
+
"--out-file", outFile,
|
|
177
|
+
"--registry",
|
|
178
|
+
]);
|
|
179
|
+
expect(code).toBe(0);
|
|
180
|
+
|
|
181
|
+
const generated = fs.readFileSync(outFile, "utf-8");
|
|
182
|
+
expect(generated).toContain("export const sectionImports");
|
|
183
|
+
expect(generated).toContain(
|
|
184
|
+
`"./sections/Hero.tsx": () => import("${expectedImportPath(heroPath)}")`,
|
|
185
|
+
);
|
|
186
|
+
expect(generated).toContain(
|
|
187
|
+
`"./sections/Nested/Promo.tsx": () => import("${expectedImportPath(promoPath)}")`,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("does not emit sectionImports without the --registry flag", () => {
|
|
192
|
+
fs.writeFileSync(
|
|
193
|
+
path.join(sectionsDir, "Hero.tsx"),
|
|
194
|
+
"export const sync = true\nexport default function Hero() { return null }\n",
|
|
195
|
+
);
|
|
196
|
+
fs.writeFileSync(
|
|
197
|
+
path.join(sectionsDir, "Nested", "Promo.tsx"),
|
|
198
|
+
"export default function Promo() { return null }\n",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const { code } = runGenerator(["--sections-dir", sectionsDir, "--out-file", outFile]);
|
|
202
|
+
expect(code).toBe(0);
|
|
203
|
+
|
|
204
|
+
const generated = fs.readFileSync(outFile, "utf-8");
|
|
205
|
+
expect(generated).not.toContain("sectionImports");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("emits a doc comment that does not self-terminate early, and the resulting file is importable (regression: a literal `**/` inside the emitted /** */ comment used to close it prematurely, leaving prose as bare statements and making every --registry output invalid TypeScript)", () => {
|
|
209
|
+
const heroPath = path.join(sectionsDir, "Hero.tsx");
|
|
210
|
+
fs.writeFileSync(
|
|
211
|
+
heroPath,
|
|
212
|
+
"export const sync = true\nexport default function Hero() { return null }\n",
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const { code } = runGenerator([
|
|
216
|
+
"--sections-dir", sectionsDir,
|
|
217
|
+
"--out-file", outFile,
|
|
218
|
+
"--registry",
|
|
219
|
+
]);
|
|
220
|
+
expect(code).toBe(0);
|
|
221
|
+
|
|
222
|
+
const generated = fs.readFileSync(outFile, "utf-8");
|
|
223
|
+
|
|
224
|
+
// Pin the regression directly: the doc comment's body (everything
|
|
225
|
+
// between the opening `/**` and its own closing `*/`) must contain
|
|
226
|
+
// exactly one `*/` — the intended closing marker itself, at the very
|
|
227
|
+
// end. A premature `*/` embedded in the prose (e.g. from a literal
|
|
228
|
+
// `**/*.tsx` glob pattern) would close the block comment early and
|
|
229
|
+
// leave the rest of the doc text as bare top-level statements.
|
|
230
|
+
const docCommentStart = generated.indexOf("/**\n * Lazy section registry");
|
|
231
|
+
const exportStart = generated.indexOf("export const sectionImports");
|
|
232
|
+
expect(docCommentStart).toBeGreaterThan(-1);
|
|
233
|
+
expect(exportStart).toBeGreaterThan(docCommentStart);
|
|
234
|
+
const docComment = generated.slice(docCommentStart + "/**".length, exportStart);
|
|
235
|
+
const closeMarkerCount = (docComment.match(/\*\//g) ?? []).length;
|
|
236
|
+
expect(closeMarkerCount).toBe(1);
|
|
237
|
+
expect(docComment.trimEnd().endsWith("*/")).toBe(true);
|
|
238
|
+
|
|
239
|
+
// Strongest available check: actually import the generated file through
|
|
240
|
+
// tsx (esbuild) and confirm it parses/executes as valid TS/ESM and
|
|
241
|
+
// exports `sectionImports`. A premature `*/` would leave trailing prose
|
|
242
|
+
// as bare top-level statements, which fails to parse. `tsx -e` evaluates
|
|
243
|
+
// its argument as CommonJS (named exports of a dynamic `import()` come
|
|
244
|
+
// back CJS-interop-wrapped), so write a real `.mjs` file instead and run
|
|
245
|
+
// that — mirrors the check used to hand-verify this fix.
|
|
246
|
+
const checkerFile = path.join(tmpDir, "check-import.mjs");
|
|
247
|
+
fs.writeFileSync(
|
|
248
|
+
checkerFile,
|
|
249
|
+
[
|
|
250
|
+
`import { pathToFileURL } from "node:url";`,
|
|
251
|
+
`const m = await import(${JSON.stringify(pathToFileURL(outFile).href)});`,
|
|
252
|
+
`if (typeof m.sectionImports !== "object" || m.sectionImports === null) {`,
|
|
253
|
+
` throw new Error("sectionImports missing or not an object");`,
|
|
254
|
+
`}`,
|
|
255
|
+
].join("\n"),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const importResult = cp.spawnSync("npx", ["tsx", checkerFile], { encoding: "utf8" });
|
|
259
|
+
expect(importResult.status, importResult.stderr).toBe(0);
|
|
260
|
+
}, 30_000);
|
|
261
|
+
});
|
|
@@ -16,10 +16,22 @@
|
|
|
16
16
|
*
|
|
17
17
|
* CLI:
|
|
18
18
|
* --sections-dir override input (default: src/sections)
|
|
19
|
-
* --out-file override output (default:
|
|
19
|
+
* --out-file override output (default: .deco/sections.gen.ts)
|
|
20
|
+
* --registry also emit `sectionImports` — a lazy section-import map
|
|
21
|
+
* keyed glob-style (`./sections/...`), the Next.js/webpack
|
|
22
|
+
* equivalent of Vite's `import.meta.glob("./sections/**\/*.tsx")`.
|
|
23
|
+
* Built from every scanned section file, not just the ones
|
|
24
|
+
* carrying convention exports. Off by default so existing
|
|
25
|
+
* Vite sites regenerating sections.gen.ts in CI see zero diff.
|
|
26
|
+
*
|
|
27
|
+
* If no `--out-file` is passed and the OLD default (src/server/cms/sections.gen.ts)
|
|
28
|
+
* still exists on disk, a one-line legacy warning is printed to stderr and the
|
|
29
|
+
* NEW default is written anyway — see lib/legacyArtifact.ts.
|
|
20
30
|
*/
|
|
21
31
|
import fs from "node:fs";
|
|
22
32
|
import path from "node:path";
|
|
33
|
+
import { isExcludedCodegenFile } from "./lib/codegenExclusions";
|
|
34
|
+
import { warnLegacyArtifact } from "./lib/legacyArtifact";
|
|
23
35
|
|
|
24
36
|
const args = process.argv.slice(2);
|
|
25
37
|
function arg(name: string, fallback: string): string {
|
|
@@ -28,7 +40,14 @@ function arg(name: string, fallback: string): string {
|
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
const sectionsDir = path.resolve(process.cwd(), arg("sections-dir", "src/sections"));
|
|
31
|
-
const
|
|
43
|
+
const OUT_FILE_EXPLICIT = args.includes("--out-file");
|
|
44
|
+
const NEW_DEFAULT_OUT_FILE = ".deco/sections.gen.ts";
|
|
45
|
+
const OLD_DEFAULT_OUT_FILE = "src/server/cms/sections.gen.ts";
|
|
46
|
+
const outFile = path.resolve(process.cwd(), arg("out-file", NEW_DEFAULT_OUT_FILE));
|
|
47
|
+
if (!OUT_FILE_EXPLICIT && fs.existsSync(path.resolve(process.cwd(), OLD_DEFAULT_OUT_FILE))) {
|
|
48
|
+
warnLegacyArtifact(OLD_DEFAULT_OUT_FILE, NEW_DEFAULT_OUT_FILE);
|
|
49
|
+
}
|
|
50
|
+
const EMIT_REGISTRY = args.includes("--registry");
|
|
32
51
|
|
|
33
52
|
interface SectionMeta {
|
|
34
53
|
eager?: boolean;
|
|
@@ -92,7 +111,10 @@ function walkDir(dir: string, base: string = dir): string[] {
|
|
|
92
111
|
const fullPath = path.join(dir, entry.name);
|
|
93
112
|
if (entry.isDirectory()) {
|
|
94
113
|
results.push(...walkDir(fullPath, base));
|
|
95
|
-
} else if (
|
|
114
|
+
} else if (
|
|
115
|
+
(entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) &&
|
|
116
|
+
!isExcludedCodegenFile(entry.name)
|
|
117
|
+
) {
|
|
96
118
|
results.push(fullPath);
|
|
97
119
|
}
|
|
98
120
|
}
|
|
@@ -223,6 +245,25 @@ if (allFallbacks.length > 0) {
|
|
|
223
245
|
}
|
|
224
246
|
lines.push("");
|
|
225
247
|
|
|
248
|
+
// Lazy section-import registry — opt-in via --registry, built from every
|
|
249
|
+
// scanned section file (not just convention-carrying `entries`).
|
|
250
|
+
if (EMIT_REGISTRY) {
|
|
251
|
+
lines.push("");
|
|
252
|
+
lines.push("/**");
|
|
253
|
+
lines.push(" * Lazy section registry — the Next.js/webpack equivalent of Vite's");
|
|
254
|
+
lines.push(" * `import.meta.glob` scan over every file under ./sections, recursively.");
|
|
255
|
+
lines.push(" * Keys use the glob-style `./sections/...` form so this map drops");
|
|
256
|
+
lines.push(" * straight into `createSiteSetup({ sections })` / `createNextSetup({ sections })`.");
|
|
257
|
+
lines.push(" */");
|
|
258
|
+
lines.push("export const sectionImports: Record<string, () => Promise<any>> = {");
|
|
259
|
+
for (const filePath of sectionFiles) {
|
|
260
|
+
const rel = path.relative(sectionsDir, filePath).replace(/\\/g, "/");
|
|
261
|
+
const importPath = relativeImportPath(outFile, filePath);
|
|
262
|
+
lines.push(` "./sections/${rel}": () => import("${importPath}"),`);
|
|
263
|
+
}
|
|
264
|
+
lines.push("};");
|
|
265
|
+
}
|
|
266
|
+
|
|
226
267
|
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
227
268
|
fs.writeFileSync(outFile, lines.join("\n"));
|
|
228
269
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isExcludedCodegenFile } from "./codegenExclusions";
|
|
3
|
+
|
|
4
|
+
describe("isExcludedCodegenFile", () => {
|
|
5
|
+
it.each([
|
|
6
|
+
"Hero.test.tsx",
|
|
7
|
+
"Hero.test.ts",
|
|
8
|
+
"Hero.spec.tsx",
|
|
9
|
+
"Hero.stories.tsx",
|
|
10
|
+
"sections.gen.ts",
|
|
11
|
+
"meta.gen.json",
|
|
12
|
+
])("excludes %s", (name) => {
|
|
13
|
+
expect(isExcludedCodegenFile(name)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it.each(["Hero.tsx", "Product/SearchResult.tsx", "testimonials.tsx", "generic.ts"])(
|
|
17
|
+
"keeps %s",
|
|
18
|
+
(name) => {
|
|
19
|
+
expect(isExcludedCodegenFile(name)).toBe(false);
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files the codegen scanners must never treat as section/loader sources.
|
|
3
|
+
* `generate-schema.ts` once emitted a site's co-located test file as a
|
|
4
|
+
* section block (it scans every .ts/.tsx under the sections dir), so both
|
|
5
|
+
* generators route their directory walks through this predicate.
|
|
6
|
+
*/
|
|
7
|
+
const EXCLUDED_SUFFIX_RE = /\.(test|spec|stories|gen)\.(ts|tsx|js|jsx|json)$/;
|
|
8
|
+
|
|
9
|
+
export function isExcludedCodegenFile(fileName: string): boolean {
|
|
10
|
+
return EXCLUDED_SUFFIX_RE.test(fileName);
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator default output paths flipped from `src/server/{cms,admin}/` to
|
|
3
|
+
* `.deco/` (framework artifacts live in the framework's folder, not mixed
|
|
4
|
+
* into app source). Sites that never pass an explicit `--out`/`--out-file`
|
|
5
|
+
* flag pick up the new default silently — except when the OLD default file
|
|
6
|
+
* is still sitting on disk, which almost always means something (an
|
|
7
|
+
* importer, a `.gitignore` entry, a stale CI cache check) still points at
|
|
8
|
+
* it. In that case we warn once, to stderr, and then write to the NEW
|
|
9
|
+
* default anyway: the artifact is regenerated code, so there's no reason to
|
|
10
|
+
* block the run — the warning is just a nudge to go clean up the stale file
|
|
11
|
+
* and its importers.
|
|
12
|
+
*
|
|
13
|
+
* An explicit flag means the caller made a deliberate choice about where
|
|
14
|
+
* output goes; it gets no warning and no guard.
|
|
15
|
+
*/
|
|
16
|
+
export function warnLegacyArtifact(oldPath: string, newPath: string): void {
|
|
17
|
+
console.warn(
|
|
18
|
+
`[deco] Generator default output moved: ${oldPath} -> ${newPath}. Move the file and update its importers.`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -64,7 +64,7 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Always import siteLoaders — the generate:loaders script creates this file
|
|
67
|
-
lines.push(`import { siteLoaders } from "
|
|
67
|
+
lines.push(`import { siteLoaders } from "../../.deco/loaders.gen";`);
|
|
68
68
|
lines.push(``);
|
|
69
69
|
|
|
70
70
|
lines.push(`const DOMAIN_RE = /;\\s*domain=[^;]*/gi;`);
|
|
@@ -99,8 +99,8 @@ import { setInvokeLoaders } from "@decocms/start/admin";${isVtex ? `
|
|
|
99
99
|
import { createInstrumentedFetch } from "@decocms/start/sdk/instrumentedFetch";
|
|
100
100
|
import { initVtexFromBlocks, setVtexFetch } from "@decocms/apps/vtex";` : ""}${hasLocationMatcher ? `
|
|
101
101
|
import { registerLocationMatcher } from "./matchers/location";` : ""}
|
|
102
|
-
import { blocks as generatedBlocks } from "
|
|
103
|
-
import { sectionMeta, syncComponents, loadingFallbacks } from "
|
|
102
|
+
import { blocks as generatedBlocks } from "../.deco/blocks.gen";
|
|
103
|
+
import { sectionMeta, syncComponents, loadingFallbacks } from "../.deco/sections.gen";
|
|
104
104
|
import { PreviewProviders } from "@decocms/start/hooks";
|
|
105
105
|
// @ts-ignore Vite ?url import
|
|
106
106
|
import appCss from "./styles/app.css?url";
|
|
@@ -112,7 +112,7 @@ import "./setup/section-loaders";
|
|
|
112
112
|
createSiteSetup({
|
|
113
113
|
sections: import.meta.glob("./sections/**/*.tsx") as Record<string, () => Promise<any>>,
|
|
114
114
|
blocks: generatedBlocks,
|
|
115
|
-
meta: () => import("
|
|
115
|
+
meta: () => import("../.deco/meta.gen.json").then((m) => m.default),
|
|
116
116
|
css: appCss,
|
|
117
117
|
fonts: [${fontEntries}],
|
|
118
118
|
productionOrigins: [
|