kizlo 0.8.0 → 0.8.2
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/dist/cli/commands/create.js +37 -30
- package/dist/cli/commands/init.js +76 -28
- package/dist/cli/presets/nextjs.js +1 -5
- package/dist/cli/presets/patch.js +27 -39
- package/dist/cli/presets/source.js +50 -0
- package/dist/cli/presets/template.js +139 -19
- package/dist/cli/wp/dev.js +1 -1
- package/dist/package.js +1 -1
- package/dist/router.d.ts +5 -5
- package/package.json +1 -1
- package/dist/cli/presets/generated/templates.js +0 -59
|
@@ -2,56 +2,64 @@ import { dockerAvailable } from "../wp/docker.js";
|
|
|
2
2
|
import { detectPackageManager, getVersion } from "../utils.js";
|
|
3
3
|
import { printBanner } from "../banner.js";
|
|
4
4
|
import { getPreset } from "../presets/index.js";
|
|
5
|
+
import { fetchTemplate } from "../presets/source.js";
|
|
6
|
+
import { readManifest } from "../presets/template.js";
|
|
5
7
|
import { collectConnectionInteractively, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
6
8
|
import z from "zod/v4";
|
|
7
9
|
import path from "node:path";
|
|
8
10
|
import fs from "node:fs";
|
|
9
11
|
import { defineCommand } from "citty";
|
|
10
12
|
import * as p from "@clack/prompts";
|
|
11
|
-
import { downloadTemplate } from "giget";
|
|
12
13
|
|
|
13
14
|
//#region src/cli/commands/create.ts
|
|
14
|
-
/** GitHub repo the full templates are fetched from, matching the release-tag scheme. */
|
|
15
|
-
const TEMPLATE_REPO = "github:kizlo-io/kizlo";
|
|
16
15
|
/**
|
|
17
16
|
* The templates `create` can scaffold from. Each id is both the `templates/<id>` folder and the
|
|
18
|
-
* preset that drives its WordPress setup.
|
|
17
|
+
* preset that drives its WordPress setup.
|
|
19
18
|
*/
|
|
20
19
|
const TEMPLATES = ["nextjs"];
|
|
21
20
|
function isTemplate(value) {
|
|
22
21
|
return TEMPLATES.includes(value);
|
|
23
22
|
}
|
|
24
23
|
const projectName = validate(z.string().trim().min(1, "Required").refine((value) => !value.startsWith(".") && !value.includes("/") && !value.includes("\\"), "Enter a folder name (letters, numbers, dashes)"));
|
|
24
|
+
/** Directory entries never copied into a scaffolded project — the internal manifest and build junk. */
|
|
25
|
+
const SKIP_COPY = new Set([
|
|
26
|
+
"template.json",
|
|
27
|
+
"node_modules",
|
|
28
|
+
".next",
|
|
29
|
+
".turbo",
|
|
30
|
+
".git"
|
|
31
|
+
]);
|
|
25
32
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const localDir = process.env.KIZLO_TEMPLATE_LOCAL_DIR;
|
|
33
|
-
if (localDir) return { local: path.resolve(localDir, template) };
|
|
34
|
-
return { remote: `${TEMPLATE_REPO}/templates/${template}#${process.env.KIZLO_TEMPLATE_REF ?? `kizlo-v${getVersion()}`}` };
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Copy the template into `dir`, then make it a standalone project: rewrite the `workspace:*` Kizlo
|
|
38
|
-
* dependency to this CLI's version and name the package after the project folder. The template's
|
|
39
|
-
* `kizlo.template.json` is an internal build input, so it's removed once the copy lands.
|
|
33
|
+
* Copy the template into `dir`, then make it a standalone project: name the package after the
|
|
34
|
+
* project folder and resolve the monorepo-only `workspace:*` Kizlo dependency to the version the
|
|
35
|
+
* template declares (`kizloVersion` in its manifest, stamped at release), falling back to the running
|
|
36
|
+
* CLI's version. The version the project runs is the template's to decide, not a moving `latest` tag.
|
|
37
|
+
* The template's `template.json` is an internal input, so it's left out of the copy — as is
|
|
38
|
+
* local build junk (`node_modules`, `.next`, …) that only exists on the `KIZLO_TEMPLATE_LOCAL_DIR` path.
|
|
40
39
|
*/
|
|
41
40
|
async function scaffoldTemplate(template, dir, name) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
const { dir: src, cleanup } = await fetchTemplate(template);
|
|
42
|
+
let kizloVersion;
|
|
43
|
+
try {
|
|
44
|
+
kizloVersion = readManifest(src).kizloVersion ?? `^${getVersion()}`;
|
|
45
|
+
fs.cpSync(src, dir, {
|
|
46
|
+
recursive: true,
|
|
47
|
+
filter: (from) => !SKIP_COPY.has(path.basename(from))
|
|
48
|
+
});
|
|
49
|
+
} finally {
|
|
50
|
+
cleanup();
|
|
51
|
+
}
|
|
51
52
|
const pkgPath = path.join(dir, "package.json");
|
|
53
|
+
if (!fs.existsSync(pkgPath)) {
|
|
54
|
+
fs.rmSync(dir, {
|
|
55
|
+
recursive: true,
|
|
56
|
+
force: true
|
|
57
|
+
});
|
|
58
|
+
throw new Error(`Template "${template}" wasn't found. Check the template name and your network connection, then try again.`);
|
|
59
|
+
}
|
|
52
60
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
53
61
|
pkg.name = name;
|
|
54
|
-
if (pkg.dependencies?.kizlo === "workspace:*") pkg.dependencies.kizlo =
|
|
62
|
+
if (pkg.dependencies?.kizlo === "workspace:*") pkg.dependencies.kizlo = kizloVersion;
|
|
55
63
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, " ")}\n`);
|
|
56
64
|
}
|
|
57
65
|
const create = defineCommand({
|
|
@@ -83,8 +91,7 @@ const create = defineCommand({
|
|
|
83
91
|
process.exit(1);
|
|
84
92
|
}
|
|
85
93
|
template = requested;
|
|
86
|
-
} else
|
|
87
|
-
else template = orCancel(await p.select({
|
|
94
|
+
} else template = orCancel(await p.select({
|
|
88
95
|
message: "Template",
|
|
89
96
|
options: TEMPLATES.map((id) => ({
|
|
90
97
|
value: id,
|
|
@@ -2,9 +2,11 @@ import { dockerAvailable } from "../wp/docker.js";
|
|
|
2
2
|
import { addDependencyArgs, detectImportAlias, detectPackageManager, ensureGitignored, getVersion, loadEnvFiles, resolveModuleImport, runCommand, writeFileIfAbsent } from "../utils.js";
|
|
3
3
|
import { printBanner } from "../banner.js";
|
|
4
4
|
import { PRESETS, detectPreset, getPreset } from "../presets/index.js";
|
|
5
|
+
import { fetchTemplate } from "../presets/source.js";
|
|
6
|
+
import { applyPatchToSource, patchChanged, renderPatchCode } from "../presets/patch.js";
|
|
7
|
+
import { adaptOwnFile, findRootLayout, ownEntries, patchEntries, readManifest, resolvePatch } from "../presets/template.js";
|
|
5
8
|
import { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
6
9
|
import { CONTRACT_BARREL } from "../daemon/generate.js";
|
|
7
|
-
import { applyPatchToSource, patchChanged, resolvePatch, resolvePatchTargetPath, templatePatches } from "../presets/patch.js";
|
|
8
10
|
import path from "node:path";
|
|
9
11
|
import fs from "node:fs";
|
|
10
12
|
import { defineCommand } from "citty";
|
|
@@ -108,6 +110,41 @@ function collectFromEnv(ctx) {
|
|
|
108
110
|
function readPkg(pkgPath) {
|
|
109
111
|
return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
110
112
|
}
|
|
113
|
+
function kizloDep(pkg) {
|
|
114
|
+
return pkg.dependencies?.kizlo ?? pkg.devDependencies?.kizlo;
|
|
115
|
+
}
|
|
116
|
+
/** A bare `x.y.z` from a version spec (range prefix and pre-release/build metadata dropped); undefined if none. */
|
|
117
|
+
function coerceVersion(spec) {
|
|
118
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(spec);
|
|
119
|
+
return match ? [
|
|
120
|
+
Number(match[1]),
|
|
121
|
+
Number(match[2]),
|
|
122
|
+
Number(match[3])
|
|
123
|
+
] : void 0;
|
|
124
|
+
}
|
|
125
|
+
/** Whether `have` is a strictly older release than `want`; false when either can't be compared. */
|
|
126
|
+
function isOlder(have, want) {
|
|
127
|
+
const a = coerceVersion(have);
|
|
128
|
+
const b = coerceVersion(want);
|
|
129
|
+
if (!a || !b) return false;
|
|
130
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i];
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The template is authoritative for the kizlo version: it declares `kizloVersion` in its manifest
|
|
135
|
+
* (stamped at release), falling back to the running CLI's version in-repo before the first stamp.
|
|
136
|
+
* When the project's kizlo is older, upgrade the project up to it and say so — a deliberate pin is
|
|
137
|
+
* never changed silently. Never downgrades.
|
|
138
|
+
*/
|
|
139
|
+
function alignKizloVersion(cwd, pm, pkg, manifest) {
|
|
140
|
+
const want = manifest.kizloVersion ?? `^${getVersion()}`;
|
|
141
|
+
const have = kizloDep(pkg);
|
|
142
|
+
if (!have || !isOlder(have, want)) return;
|
|
143
|
+
const s = p.spinner();
|
|
144
|
+
s.start(`Upgrading kizlo from ${have} to ${want}`);
|
|
145
|
+
const ok = runCommand(addDependencyArgs(pm, `kizlo@${want}`), cwd, "ignore");
|
|
146
|
+
s.stop(ok ? `Upgraded kizlo to ${want}` : `Could not upgrade kizlo automatically — install kizlo@${want} yourself`);
|
|
147
|
+
}
|
|
111
148
|
const init = defineCommand({
|
|
112
149
|
meta: {
|
|
113
150
|
name: "init",
|
|
@@ -168,6 +205,10 @@ const init = defineCommand({
|
|
|
168
205
|
preset = detectPreset(initCtx);
|
|
169
206
|
if (preset.id !== "base") p.log.success(`${preset.label} detected`);
|
|
170
207
|
}
|
|
208
|
+
if (preset.template === "nextjs" && !fs.existsSync(path.join(cwd, "app")) && !fs.existsSync(path.join(cwd, "src/app"))) {
|
|
209
|
+
p.cancel("Kizlo needs the Next.js App Router — no `app` or `src/app` directory found. The Pages Router isn't supported.");
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
171
212
|
if (yes) loadEnvFiles(cwd);
|
|
172
213
|
const setup = yes ? collectFromEnv({
|
|
173
214
|
cwd,
|
|
@@ -185,11 +226,12 @@ const init = defineCommand({
|
|
|
185
226
|
process.exit(1);
|
|
186
227
|
}
|
|
187
228
|
if (!hasKizlo) {
|
|
229
|
+
const spec = `kizlo@^${getVersion()}`;
|
|
188
230
|
const s = p.spinner();
|
|
189
231
|
s.start(`Installing kizlo with ${pm}`);
|
|
190
|
-
const ok = runCommand(addDependencyArgs(pm,
|
|
232
|
+
const ok = runCommand(addDependencyArgs(pm, spec), cwd, "ignore");
|
|
191
233
|
s.stop(ok ? "Installed kizlo" : "Could not install kizlo automatically");
|
|
192
|
-
if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm,
|
|
234
|
+
if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm, spec).join(" ")}`);
|
|
193
235
|
}
|
|
194
236
|
if (preset.apiPath && setup.baseUrl) setup.baseUrl = withApiPath(setup.baseUrl, preset.apiPath);
|
|
195
237
|
await setupLocalWordPress(cwd, setup);
|
|
@@ -214,7 +256,18 @@ const init = defineCommand({
|
|
|
214
256
|
label: "Kizlo config",
|
|
215
257
|
relPath: "kizlo.config.ts",
|
|
216
258
|
contents: kizloConfigTemplate(dirRel, setup.alias, setup.devPath)
|
|
217
|
-
}
|
|
259
|
+
}];
|
|
260
|
+
let manifest;
|
|
261
|
+
if (preset.template) {
|
|
262
|
+
const fetched = await fetchTemplate(preset.template);
|
|
263
|
+
try {
|
|
264
|
+
manifest = readManifest(fetched.dir);
|
|
265
|
+
alignKizloVersion(cwd, pm, pkg, manifest);
|
|
266
|
+
for (const entry of ownEntries(manifest)) files.push(adaptOwnFile(fetched.dir, entry, manifest.tokens, scaffold));
|
|
267
|
+
} finally {
|
|
268
|
+
fetched.cleanup();
|
|
269
|
+
}
|
|
270
|
+
} else if (preset.scaffolds) files.push(...preset.scaffolds(scaffold));
|
|
218
271
|
const scaffolded = [];
|
|
219
272
|
for (const file of files) scaffolded.push({
|
|
220
273
|
file,
|
|
@@ -228,42 +281,37 @@ const init = defineCommand({
|
|
|
228
281
|
writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
|
|
229
282
|
const gitignore = ensureGitignored(cwd, ".env");
|
|
230
283
|
for (const { file, result } of scaffolded) reportScaffold(file, result, yes);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
284
|
+
const manualSteps = [];
|
|
285
|
+
for (const entry of manifest ? patchEntries(manifest) : []) {
|
|
286
|
+
const resolved = resolvePatch(entry, manifest.tokens, scaffold);
|
|
287
|
+
const target = findRootLayout(cwd, scaffold.appDir, resolved.relPath);
|
|
288
|
+
if (!target) {
|
|
289
|
+
manualSteps.push(resolved);
|
|
290
|
+
p.log.info(`Couldn't find your ${resolved.label} to wire Kizlo into — see the code to add below`);
|
|
236
291
|
continue;
|
|
237
292
|
}
|
|
238
|
-
const relTarget = path.relative(cwd,
|
|
239
|
-
const src = fs.readFileSync(
|
|
240
|
-
let
|
|
241
|
-
let changes;
|
|
293
|
+
const relTarget = path.relative(cwd, target);
|
|
294
|
+
const src = fs.readFileSync(target, "utf8");
|
|
295
|
+
let applied;
|
|
242
296
|
try {
|
|
243
|
-
|
|
244
|
-
} catch
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (!force && !yes && changes.keptExports.length) {
|
|
249
|
-
p.log.warn(`${resolved.label} (${relTarget}) already defines ${changes.keptExports.join(", ")}`);
|
|
250
|
-
if (orCancel(await p.confirm({
|
|
251
|
-
message: "Replace with Kizlo's version?",
|
|
252
|
-
initialValue: true
|
|
253
|
-
}))) ({text, changes} = applyPatchToSource(src, resolved, { replaceConflicts: true }));
|
|
297
|
+
applied = applyPatchToSource(src, resolved);
|
|
298
|
+
} catch {
|
|
299
|
+
manualSteps.push(resolved);
|
|
300
|
+
p.log.warn(`Couldn't parse your ${resolved.label} (${relTarget}) — see the code to add below`);
|
|
301
|
+
continue;
|
|
254
302
|
}
|
|
303
|
+
const { text, changes } = applied;
|
|
255
304
|
if (patchChanged(changes)) {
|
|
256
|
-
fs.writeFileSync(
|
|
305
|
+
fs.writeFileSync(target, text);
|
|
257
306
|
const parts = [
|
|
258
307
|
...changes.replacedExports.length ? [`replaced ${changes.replacedExports.join(", ")}`] : [],
|
|
259
308
|
...changes.addedExports.length ? [`added ${changes.addedExports.join(", ")}`] : [],
|
|
260
309
|
...changes.addedImports.length ? ["imports"] : []
|
|
261
310
|
];
|
|
262
311
|
p.log.success(`Wired Kizlo into your ${resolved.label} (${relTarget}) — ${parts.join(", ")}`);
|
|
263
|
-
}
|
|
264
|
-
if (changes.keptExports.length) p.log.info(`Kept your existing ${changes.keptExports.join(", ")} in ${resolved.label} — pass --force to replace`);
|
|
265
|
-
else if (!patchChanged(changes)) p.log.info(`Your ${resolved.label} is already wired (${relTarget})`);
|
|
312
|
+
} else p.log.info(`Your ${resolved.label} is already wired (${relTarget})`);
|
|
266
313
|
}
|
|
314
|
+
for (const resolved of manualSteps) p.note(renderPatchCode(resolved), `Add these to your ${resolved.label} (the layout that renders <html>)`);
|
|
267
315
|
if (gitignore !== "present") p.log.success(`${gitignore === "created" ? "Created" : "Updated"} .gitignore (ignoring .env)`);
|
|
268
316
|
nextStepsNote(setup.mode);
|
|
269
317
|
p.outro("Kizlo is ready 🎉");
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { materializeTemplate } from "./template.js";
|
|
2
|
-
|
|
3
1
|
//#region src/cli/presets/nextjs.ts
|
|
4
2
|
const nextjs = {
|
|
5
3
|
id: "nextjs",
|
|
@@ -12,9 +10,7 @@ const nextjs = {
|
|
|
12
10
|
},
|
|
13
11
|
baseUrlEnvKey: "NEXT_PUBLIC_KIZLO_BACKEND_URL",
|
|
14
12
|
apiPath: "/api/kizlo",
|
|
15
|
-
|
|
16
|
-
return materializeTemplate("nextjs", ctx);
|
|
17
|
-
}
|
|
13
|
+
template: "nextjs"
|
|
18
14
|
};
|
|
19
15
|
|
|
20
16
|
//#endregion
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TEMPLATE_PATCHES } from "./generated/templates.js";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import { builders, detectCodeFormat, generateCode, parseModule } from "magicast";
|
|
@@ -28,26 +27,7 @@ function resolvePatchTargetPath(cwd, relPath) {
|
|
|
28
27
|
if (fs.existsSync(candidate)) return candidate;
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
/**
|
|
32
|
-
function templatePatches(framework) {
|
|
33
|
-
return TEMPLATE_PATCHES[framework] ?? [];
|
|
34
|
-
}
|
|
35
|
-
/** Substitute the path/import tokens for a patch against the project's real directories. */
|
|
36
|
-
function resolvePatch(patch, ctx) {
|
|
37
|
-
const relPath = patch.tokenizedFile.replaceAll("{{kizloDir}}", ctx.kizloDir).replaceAll("{{appDir}}", ctx.appDir);
|
|
38
|
-
const fromDir = path.posix.dirname(relPath);
|
|
39
|
-
const imports = patch.imports.map((imp) => ({
|
|
40
|
-
module: imp.module.replaceAll("{{serverImport}}", ctx.serverImport(fromDir)),
|
|
41
|
-
names: imp.names
|
|
42
|
-
}));
|
|
43
|
-
return {
|
|
44
|
-
label: patch.label,
|
|
45
|
-
relPath,
|
|
46
|
-
imports,
|
|
47
|
-
exports: patch.exports
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Whether a patch actually modified the file (vs. a no-op / kept outcome). */
|
|
30
|
+
/** Whether a patch actually modified the file (vs. a no-op / already-wired outcome). */
|
|
51
31
|
function patchChanged(changes) {
|
|
52
32
|
return Boolean(changes.addedImports.length || changes.addedExports.length || changes.replacedExports.length);
|
|
53
33
|
}
|
|
@@ -69,15 +49,18 @@ function sameExpr(a, b) {
|
|
|
69
49
|
}
|
|
70
50
|
/**
|
|
71
51
|
* Merge a resolved patch into `source` by parsing it with magicast (a real TS/JS/JSX parser via Babel,
|
|
72
|
-
* so declarations are bounded exactly — never by a text heuristic).
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
52
|
+
* so declarations are bounded exactly — never by a text heuristic). Missing imports are added; each
|
|
53
|
+
* export is upserted — set with `builders`, which replaces it if present and adds it if not — and a
|
|
54
|
+
* static Next counterpart (`metadata` for `generateMetadata`) is dropped as part of a replace. The
|
|
55
|
+
* upsert is deliberate replace-always: we cannot distinguish a user's own `generateMetadata` from a
|
|
56
|
+
* stale copy of ours, and those values come from WordPress (edited in wp-admin, not the file), so the
|
|
57
|
+
* export body isn't meant to be hand-customized. Idempotent: an export already equal to ours is left
|
|
58
|
+
* untouched, so re-running never churns the file.
|
|
76
59
|
*
|
|
77
|
-
* Throws if the target cannot be parsed or regenerated. Callers must NOT swallow that — a file we
|
|
78
|
-
* parse is one we must not guess at, so wiring
|
|
60
|
+
* Throws if the target cannot be parsed or regenerated. Callers must NOT swallow that — a file we
|
|
61
|
+
* can't parse is one we must not guess at, so wiring falls back to a printed instruction instead.
|
|
79
62
|
*/
|
|
80
|
-
function applyPatchToSource(source, patch
|
|
63
|
+
function applyPatchToSource(source, patch) {
|
|
81
64
|
const format = detectCodeFormat(source);
|
|
82
65
|
const mod = parseModule(source, {
|
|
83
66
|
sourceFileName: "layout.tsx",
|
|
@@ -86,8 +69,7 @@ function applyPatchToSource(source, patch, opts) {
|
|
|
86
69
|
const changes = {
|
|
87
70
|
addedImports: [],
|
|
88
71
|
addedExports: [],
|
|
89
|
-
replacedExports: []
|
|
90
|
-
keptExports: []
|
|
72
|
+
replacedExports: []
|
|
91
73
|
};
|
|
92
74
|
for (const imp of patch.imports) for (const name of imp.names) {
|
|
93
75
|
if (mod.imports.$items.some((item) => item.from === imp.module && item.imported === name)) continue;
|
|
@@ -101,20 +83,12 @@ function applyPatchToSource(source, patch, opts) {
|
|
|
101
83
|
const exportNames = Object.keys(mod.exports);
|
|
102
84
|
if (exportNames.includes(exp.name)) {
|
|
103
85
|
if (sameExpr(generateCode(mod.exports[exp.name]).code, exp.value)) continue;
|
|
104
|
-
if (!opts.replaceConflicts) {
|
|
105
|
-
changes.keptExports.push(exp.name);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
86
|
mod.exports[exp.name] = builders.raw(exp.value);
|
|
109
87
|
changes.replacedExports.push(exp.name);
|
|
110
88
|
continue;
|
|
111
89
|
}
|
|
112
90
|
const counterpart = staticCounterpart(exp.name);
|
|
113
91
|
if (counterpart && exportNames.includes(counterpart)) {
|
|
114
|
-
if (!opts.replaceConflicts) {
|
|
115
|
-
changes.keptExports.push(exp.name);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
92
|
delete mod.exports[counterpart];
|
|
119
93
|
mod.exports[exp.name] = builders.raw(exp.value);
|
|
120
94
|
changes.replacedExports.push(exp.name);
|
|
@@ -128,6 +102,20 @@ function applyPatchToSource(source, patch, opts) {
|
|
|
128
102
|
changes
|
|
129
103
|
};
|
|
130
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Render a resolved patch as source the user can paste in, for the printed fallback when the target
|
|
107
|
+
* can't be resolved or parsed. The same declared payload the confident apply upserts, so what we tell
|
|
108
|
+
* the user to add never drifts from what we would have written.
|
|
109
|
+
*/
|
|
110
|
+
function renderPatchCode(patch) {
|
|
111
|
+
const imports = patch.imports.map((imp) => `import { ${imp.names.join(", ")} } from "${imp.module}"`);
|
|
112
|
+
const exports = patch.exports.map((exp) => `export const ${exp.name} = ${exp.value}`);
|
|
113
|
+
return `${[
|
|
114
|
+
...imports,
|
|
115
|
+
"",
|
|
116
|
+
...exports
|
|
117
|
+
].join("\n")}\n`;
|
|
118
|
+
}
|
|
131
119
|
|
|
132
120
|
//#endregion
|
|
133
|
-
export { applyPatchToSource, patchChanged,
|
|
121
|
+
export { applyPatchToSource, patchChanged, renderPatchCode, resolvePatchTargetPath };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { downloadTemplate } from "giget";
|
|
5
|
+
|
|
6
|
+
//#region src/cli/presets/source.ts
|
|
7
|
+
/** GitHub repo the templates are fetched from. */
|
|
8
|
+
const TEMPLATE_REPO = "github:kizlo-io/kizlo";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve where a template is fetched from. Defaults to the templates on the repo's default branch:
|
|
11
|
+
* the template is decoupled from the CLI version — it declares its own `kizlo` dependency — so there
|
|
12
|
+
* is no tag to match and no release to cut. `KIZLO_TEMPLATE_LOCAL_DIR` points at a local `templates/`
|
|
13
|
+
* directory so development (and `create`/`init` tests) never touch the network.
|
|
14
|
+
*/
|
|
15
|
+
function templateSource(template) {
|
|
16
|
+
const localDir = process.env.KIZLO_TEMPLATE_LOCAL_DIR;
|
|
17
|
+
if (localDir) return { local: path.resolve(localDir, template) };
|
|
18
|
+
return { remote: `${TEMPLATE_REPO}/templates/${template}` };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the template as a readable directory both `create` and `init` can copy from or read. With
|
|
22
|
+
* `KIZLO_TEMPLATE_LOCAL_DIR` set it's the local folder (returned as-is, cleanup is a no-op). Otherwise
|
|
23
|
+
* it's downloaded once into a fresh temp directory the caller cleans up. The single fetch path shared
|
|
24
|
+
* by both commands, so they can never source different bytes.
|
|
25
|
+
*/
|
|
26
|
+
async function fetchTemplate(template) {
|
|
27
|
+
const source = templateSource(template);
|
|
28
|
+
if (source.local) {
|
|
29
|
+
if (!fs.existsSync(source.local)) throw new Error(`Local template not found: ${source.local}`);
|
|
30
|
+
return {
|
|
31
|
+
dir: source.local,
|
|
32
|
+
cleanup: () => {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kizlo-template-"));
|
|
36
|
+
await downloadTemplate(source.remote, {
|
|
37
|
+
dir,
|
|
38
|
+
forceClean: true
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
dir,
|
|
42
|
+
cleanup: () => fs.rmSync(dir, {
|
|
43
|
+
recursive: true,
|
|
44
|
+
force: true
|
|
45
|
+
})
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { fetchTemplate };
|
|
@@ -1,8 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolvePatchTargetPath } from "./patch.js";
|
|
2
|
+
import z from "zod/v4";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
3
5
|
|
|
4
6
|
//#region src/cli/presets/template.ts
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The template's `template.json`. It is the single declaration of which files are Kizlo wiring
|
|
9
|
+
* and how each is applied. Every entry is one of a small closed set — deliberately no "patch an
|
|
10
|
+
* arbitrary source file" kind, so a dangerous strategy is unrepresentable:
|
|
11
|
+
*
|
|
12
|
+
* - `own` — a whole file Kizlo writes. Its path is authoritative; `create` copies it and `init`
|
|
13
|
+
* adapts + writes it through the overwrite policy.
|
|
14
|
+
* - `patch` — a partial injection into a file the user owns (the root layout's SEO exports). `init`
|
|
15
|
+
* tries a confident apply, otherwise prints the payload with placement instructions. `create` never
|
|
16
|
+
* patches — its own template files already carry the wiring.
|
|
17
|
+
*/
|
|
18
|
+
const tokensSchema = z.object({
|
|
19
|
+
kizloDir: z.string(),
|
|
20
|
+
appDir: z.string(),
|
|
21
|
+
alias: z.string()
|
|
22
|
+
});
|
|
23
|
+
const ownSchema = z.object({
|
|
24
|
+
kind: z.literal("own"),
|
|
25
|
+
role: z.string(),
|
|
26
|
+
path: z.string()
|
|
27
|
+
});
|
|
28
|
+
const patchSchema = z.object({
|
|
29
|
+
kind: z.literal("patch"),
|
|
30
|
+
role: z.string(),
|
|
31
|
+
label: z.string(),
|
|
32
|
+
path: z.string(),
|
|
33
|
+
imports: z.array(z.object({
|
|
34
|
+
module: z.string(),
|
|
35
|
+
names: z.array(z.string())
|
|
36
|
+
})),
|
|
37
|
+
exports: z.array(z.object({
|
|
38
|
+
name: z.string(),
|
|
39
|
+
value: z.string()
|
|
40
|
+
}))
|
|
41
|
+
});
|
|
42
|
+
const manifestSchema = z.object({
|
|
43
|
+
framework: z.string(),
|
|
44
|
+
kizloVersion: z.string().optional(),
|
|
45
|
+
tokens: tokensSchema,
|
|
46
|
+
files: z.array(z.discriminatedUnion("kind", [ownSchema, patchSchema]))
|
|
47
|
+
});
|
|
48
|
+
/** Role → the human label init uses in prompts and logs. */
|
|
6
49
|
const ROLE_LABELS = {
|
|
7
50
|
"server-entry": "Kizlo server instance",
|
|
8
51
|
client: "Browser client",
|
|
@@ -12,25 +55,102 @@ const ROLE_LABELS = {
|
|
|
12
55
|
"sitemap-redirect": "sitemap.xml redirect route",
|
|
13
56
|
manifest: "web manifest route"
|
|
14
57
|
};
|
|
58
|
+
/** Read and validate the template's manifest from a fetched template directory. */
|
|
59
|
+
function readManifest(templateDir) {
|
|
60
|
+
const manifestPath = path.join(templateDir, "template.json");
|
|
61
|
+
if (!fs.existsSync(manifestPath)) throw new Error(`Template manifest not found at ${manifestPath}`);
|
|
62
|
+
return manifestSchema.parse(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
63
|
+
}
|
|
64
|
+
function ownEntries(manifest) {
|
|
65
|
+
return manifest.files.filter((file) => file.kind === "own");
|
|
66
|
+
}
|
|
67
|
+
function patchEntries(manifest) {
|
|
68
|
+
return manifest.files.filter((file) => file.kind === "patch");
|
|
69
|
+
}
|
|
70
|
+
/** The specifier the template's files use to reach the server entry, e.g. `@/lib/kizlo/server`. */
|
|
71
|
+
function serverImportSpecifier(tokens) {
|
|
72
|
+
const withoutSrc = tokens.kizloDir.replace(/^src\//, "");
|
|
73
|
+
return `${tokens.alias.replace(/\/+$/, "")}/${withoutSrc}/server`;
|
|
74
|
+
}
|
|
15
75
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* with the project's real directories, and `{{serverImport}}` with the import specifier resolved for
|
|
19
|
-
* each file's location (tsconfig alias or relative). The single source of these bodies is the
|
|
20
|
-
* template folder, so init and `create` can never drift.
|
|
76
|
+
* Adapt a template path prefix (`src/app/...`, `src/lib/kizlo/...`) to the project's real directories.
|
|
77
|
+
* Only the leading token dir is swapped; the rest of the path is preserved verbatim.
|
|
21
78
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
79
|
+
function adaptTemplatePath(relPath, tokens, ctx) {
|
|
80
|
+
const normalized = relPath.split(path.sep).join("/");
|
|
81
|
+
if (normalized === tokens.appDir || normalized.startsWith(`${tokens.appDir}/`)) return `${ctx.appDir}${normalized.slice(tokens.appDir.length)}`;
|
|
82
|
+
if (normalized === tokens.kizloDir || normalized.startsWith(`${tokens.kizloDir}/`)) return `${ctx.kizloDir}${normalized.slice(tokens.kizloDir.length)}`;
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Turn an `own` manifest entry into the {@link ScaffoldFile} init writes: read the template's real
|
|
87
|
+
* file, rewrite its path prefix to the project's directories, and swap the server-entry import
|
|
88
|
+
* specifier for the one resolved at the file's new location (tsconfig alias or relative). The swap is
|
|
89
|
+
* surgical — it replaces the exact specifier the template uses, not a loose directory substring — so
|
|
90
|
+
* an unrelated occurrence can't be corrupted. This is the job the old build-time extractor did; it
|
|
91
|
+
* moves to runtime and stays as precise.
|
|
92
|
+
*/
|
|
93
|
+
function adaptOwnFile(templateDir, entry, tokens, ctx) {
|
|
94
|
+
const abs = path.join(templateDir, entry.path);
|
|
95
|
+
if (!fs.existsSync(abs)) throw new Error(`Template file "${entry.path}" is listed in the manifest but does not exist in the template.`);
|
|
96
|
+
const body = fs.readFileSync(abs, "utf8");
|
|
97
|
+
const relPath = adaptTemplatePath(entry.path, tokens, ctx);
|
|
98
|
+
const fromDir = path.posix.dirname(relPath);
|
|
99
|
+
const contents = body.split(serverImportSpecifier(tokens)).join(ctx.serverImport(fromDir));
|
|
100
|
+
return {
|
|
101
|
+
label: ROLE_LABELS[entry.role] ?? entry.role,
|
|
102
|
+
relPath,
|
|
103
|
+
contents
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Substitute the path/import tokens for a patch against the project's real directories. */
|
|
107
|
+
function resolvePatch(entry, tokens, ctx) {
|
|
108
|
+
const relPath = adaptTemplatePath(entry.path, tokens, ctx);
|
|
109
|
+
const fromDir = path.posix.dirname(relPath);
|
|
110
|
+
const imports = entry.imports.map((imp) => ({
|
|
111
|
+
module: imp.module.replaceAll("{{serverImport}}", ctx.serverImport(fromDir)),
|
|
112
|
+
names: imp.names
|
|
113
|
+
}));
|
|
114
|
+
return {
|
|
115
|
+
label: entry.label,
|
|
116
|
+
relPath,
|
|
117
|
+
imports,
|
|
118
|
+
exports: entry.exports
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const LAYOUT_FILE = /^layout\.(tsx|jsx|ts|js|mjs|cjs)$/;
|
|
122
|
+
/** A root layout is the one that renders `<html>` — its identity, not its path. */
|
|
123
|
+
const RENDERS_HTML = /<html[\s/>]/;
|
|
124
|
+
/** Every file under `dir`, recursively (files only). Missing dir yields nothing. */
|
|
125
|
+
function walk(dir) {
|
|
126
|
+
if (!fs.existsSync(dir)) return [];
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
129
|
+
const full = path.join(dir, entry.name);
|
|
130
|
+
if (entry.isDirectory()) out.push(...walk(full));
|
|
131
|
+
else out.push(full);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the root layout by identity, not path. Try the manifest hint first; if it exists and
|
|
137
|
+
* renders `<html>`, use it. Otherwise scan the App Router directory for a layout that renders `<html>`
|
|
138
|
+
* and use it only when there is exactly one. On zero, many, or an unreadable file, return undefined so
|
|
139
|
+
* the caller prints the wiring for the user to place. Never guesses.
|
|
140
|
+
*/
|
|
141
|
+
function findRootLayout(cwd, appDir, hintRelPath) {
|
|
142
|
+
const hint = resolvePatchTargetPath(cwd, hintRelPath);
|
|
143
|
+
if (hint && rendersHtml(hint)) return hint;
|
|
144
|
+
const matches = walk(path.join(cwd, appDir)).filter((file) => LAYOUT_FILE.test(path.basename(file)) && rendersHtml(file));
|
|
145
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
146
|
+
}
|
|
147
|
+
function rendersHtml(file) {
|
|
148
|
+
try {
|
|
149
|
+
return RENDERS_HTML.test(fs.readFileSync(file, "utf8"));
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
33
153
|
}
|
|
34
154
|
|
|
35
155
|
//#endregion
|
|
36
|
-
export {
|
|
156
|
+
export { adaptOwnFile, findRootLayout, ownEntries, patchEntries, readManifest, resolvePatch };
|
package/dist/cli/wp/dev.js
CHANGED
|
@@ -6,8 +6,8 @@ import { createAdminAppPassword, seedUsers } from "./bootstrap.js";
|
|
|
6
6
|
import { prepareByo } from "./byo.js";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { existsSync, rmSync } from "node:fs";
|
|
9
|
-
import { randomBytes } from "node:crypto";
|
|
10
9
|
import { networkInterfaces } from "node:os";
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
11
|
|
|
12
12
|
//#region src/cli/wp/dev.ts
|
|
13
13
|
/** A strong random admin password (144 bits, URL/JSON/CLI-safe characters). */
|
package/dist/package.js
CHANGED
package/dist/router.d.ts
CHANGED
|
@@ -179,7 +179,7 @@ declare const ROUTER_MAP: {
|
|
|
179
179
|
include?: string | number | (string | number)[] | undefined;
|
|
180
180
|
offset?: string | number | undefined;
|
|
181
181
|
order?: "asc" | "desc" | undefined;
|
|
182
|
-
orderby?: "
|
|
182
|
+
orderby?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | undefined;
|
|
183
183
|
searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
|
|
184
184
|
slug?: string | string[] | undefined;
|
|
185
185
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -507,7 +507,7 @@ declare const ROUTER_MAP: {
|
|
|
507
507
|
include?: string | number | (string | number)[] | undefined;
|
|
508
508
|
offset?: string | number | undefined;
|
|
509
509
|
order?: "asc" | "desc" | undefined;
|
|
510
|
-
orderby?: "
|
|
510
|
+
orderby?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | "menu_order" | undefined;
|
|
511
511
|
searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
|
|
512
512
|
slug?: string | string[] | undefined;
|
|
513
513
|
parent?: string | number | (string | number)[] | undefined;
|
|
@@ -1186,7 +1186,7 @@ declare const ROUTER_MAP: {
|
|
|
1186
1186
|
include?: string | number | (string | number)[] | undefined;
|
|
1187
1187
|
offset?: string | number | undefined;
|
|
1188
1188
|
order?: "asc" | "desc" | undefined;
|
|
1189
|
-
orderby?: "
|
|
1189
|
+
orderby?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
|
|
1190
1190
|
searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
|
|
1191
1191
|
slug?: string | string[] | undefined;
|
|
1192
1192
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -1265,7 +1265,7 @@ declare const ROUTER_MAP: {
|
|
|
1265
1265
|
include?: string | number | (string | number)[] | undefined;
|
|
1266
1266
|
offset?: string | number | undefined;
|
|
1267
1267
|
order?: "asc" | "desc" | undefined;
|
|
1268
|
-
orderby?: "
|
|
1268
|
+
orderby?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
|
|
1269
1269
|
searchColumns?: ("post_title" | "post_content" | "post_excerpt")[] | undefined;
|
|
1270
1270
|
slug?: string | string[] | undefined;
|
|
1271
1271
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -1414,7 +1414,7 @@ declare const ROUTER_MAP: {
|
|
|
1414
1414
|
include?: string | number | (string | number)[] | undefined;
|
|
1415
1415
|
offset?: string | number | undefined;
|
|
1416
1416
|
order?: "asc" | "desc" | undefined;
|
|
1417
|
-
orderby?: "
|
|
1417
|
+
orderby?: "id" | "date" | "parent" | "post" | "include" | undefined;
|
|
1418
1418
|
parent?: string | number | (string | number)[] | undefined;
|
|
1419
1419
|
parentExclude?: string | number | (string | number)[] | undefined;
|
|
1420
1420
|
post?: string | number | (string | number)[] | undefined;
|
package/package.json
CHANGED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
//#region src/cli/presets/generated/templates.ts
|
|
2
|
-
const TEMPLATE_SUBSETS = { "nextjs": [
|
|
3
|
-
{
|
|
4
|
-
"role": "server-entry",
|
|
5
|
-
"tokenizedPath": "{{kizloDir}}/server/index.ts",
|
|
6
|
-
"body": "import { createKizlo } from \"kizlo/nextjs/server\"\n\nexport const { router, client, context, handler } = createKizlo()\n"
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
"role": "client",
|
|
10
|
-
"tokenizedPath": "{{kizloDir}}/client.ts",
|
|
11
|
-
"body": "import { createKizloClient } from \"kizlo/nextjs\"\nimport { contract } from \"./server/generated\"\n\nexport const client = createKizloClient(contract)\n"
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"role": "api-route",
|
|
15
|
-
"tokenizedPath": "{{appDir}}/api/kizlo/[[...rest]]/route.ts",
|
|
16
|
-
"body": "import { handler } from \"{{serverImport}}\"\n\nexport { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE, handler as OPTIONS }\n"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"role": "robots",
|
|
20
|
-
"tokenizedPath": "{{appDir}}/robots.txt/route.ts",
|
|
21
|
-
"body": "import { createRobotsRoute } from \"kizlo/nextjs/server\"\nimport { client } from \"{{serverImport}}\"\n\n// Run on the edge: cheaper and faster than Node for a response this small.\nexport const runtime = \"edge\"\n\n// Vercel treats special metadata files like robots.txt and sitemap.xml as completely static\n// files at build time, bypassing normal Incremental Static Regeneration (ISR) and on-demand\n// revalidatePath rules.\n//\n// While your revalidation logic executes perfectly in a local Node.js server, Vercel uploads\n// these routes directly to its Edge CDN as immutable assets. Subsequent on-demand revalidation\n// triggers will successfully clear the Data Cache but fail to purge the CDN-level Edge cache\n// for that file.\n//\n// force-dynamic opts out of that static treatment so CMS edits show up; the WordPress call is\n// cached, so requests stay cheap.\nexport const dynamic = \"force-dynamic\"\n\nexport const GET = createRobotsRoute(client)\n"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"role": "sitemap",
|
|
25
|
-
"tokenizedPath": "{{appDir}}/sitemaps/[sitemap]/route.ts",
|
|
26
|
-
"body": "import { createSitemapRoute } from \"kizlo/nextjs/server\"\nimport { client } from \"{{serverImport}}\"\n\n// Run on the edge: cheaper and faster than Node for a response this small.\nexport const runtime = \"edge\"\n\n// Like robots.txt, Vercel bakes sitemap routes into immutable Edge CDN assets at build time,\n// bypassing ISR and on-demand revalidatePath. The index gets frozen with whatever WordPress\n// returned at build (often nothing, since the self-referential backend isn't reachable yet),\n// and revalidation can't purge that CDN copy.\n//\n// force-dynamic opts out of that static treatment. createSitemapRoute caches the WordPress calls\n// per sitemap and refreshes them on content changes (via revalidateTag), so requests stay cheap.\nexport const dynamic = \"force-dynamic\"\n\nexport const GET = createSitemapRoute(client)\n"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"role": "sitemap-redirect",
|
|
30
|
-
"tokenizedPath": "{{appDir}}/sitemap.xml/route.ts",
|
|
31
|
-
"body": "import { createSitemapRedirectRoute } from \"kizlo/nextjs/server\"\n\n// Many crawlers ignore robots.txt and probe the well-known /sitemap.xml directly. This route\n// permanently redirects (308) to the generated index at /sitemaps/index.xml. The response is a\n// fixed constant with no WordPress call or request input, so Next statically generates it at\n// build time (no runtime/edge function invoked per request).\nexport const GET = createSitemapRedirectRoute()\n"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"role": "manifest",
|
|
35
|
-
"tokenizedPath": "{{appDir}}/site.webmanifest/route.ts",
|
|
36
|
-
"body": "import { createManifestRoute } from \"kizlo/nextjs/server\"\nimport { client } from \"{{serverImport}}\"\n\n// Run on the edge: cheaper and faster than Node for a response this small.\nexport const runtime = \"edge\"\n\n// Like robots.txt, Vercel bakes this route into an immutable Edge CDN asset at build time,\n// bypassing ISR and on-demand revalidatePath. force-dynamic opts out of that static treatment\n// so brand-settings edits show up; createManifestRoute caches the WordPress call, so requests\n// stay cheap.\nexport const dynamic = \"force-dynamic\"\n\nexport const GET = createManifestRoute(client)\n"
|
|
37
|
-
}
|
|
38
|
-
] };
|
|
39
|
-
const TEMPLATE_PATCHES = { "nextjs": [{
|
|
40
|
-
"label": "root layout",
|
|
41
|
-
"tokenizedFile": "{{appDir}}/layout.tsx",
|
|
42
|
-
"imports": [{
|
|
43
|
-
"module": "kizlo/nextjs/server",
|
|
44
|
-
"names": ["createRootMetadata", "createRootViewport"]
|
|
45
|
-
}, {
|
|
46
|
-
"module": "{{serverImport}}",
|
|
47
|
-
"names": ["client"]
|
|
48
|
-
}],
|
|
49
|
-
"exports": [{
|
|
50
|
-
"name": "generateMetadata",
|
|
51
|
-
"value": "createRootMetadata(client)"
|
|
52
|
-
}, {
|
|
53
|
-
"name": "generateViewport",
|
|
54
|
-
"value": "createRootViewport(client)"
|
|
55
|
-
}]
|
|
56
|
-
}] };
|
|
57
|
-
|
|
58
|
-
//#endregion
|
|
59
|
-
export { TEMPLATE_PATCHES, TEMPLATE_SUBSETS };
|