kizlo 0.11.0 → 0.12.1

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.
Files changed (50) hide show
  1. package/dist/astro/server.d.ts +9 -0
  2. package/dist/astro/server.js +10 -0
  3. package/dist/astro.d.ts +2 -0
  4. package/dist/astro.js +3 -0
  5. package/dist/cli/commands/_setup.js +10 -10
  6. package/dist/cli/commands/_wiring.js +13 -11
  7. package/dist/cli/commands/create.js +101 -71
  8. package/dist/cli/commands/generate.js +4 -0
  9. package/dist/cli/commands/init.js +110 -60
  10. package/dist/cli/daemon/config.js +23 -9
  11. package/dist/cli/daemon/generate.js +2 -3
  12. package/dist/cli/daemon/jiti.js +101 -0
  13. package/dist/cli/daemon/watch.js +7 -3
  14. package/dist/cli/presets/patch.js +38 -0
  15. package/dist/cli/presets/source.js +122 -24
  16. package/dist/cli/presets/template.js +47 -35
  17. package/dist/cli/wp/stack.js +2 -2
  18. package/dist/config.d.ts +1 -1
  19. package/dist/context.js +9 -3
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +2 -2
  22. package/dist/integrations/astro/kizlo.client.d.ts +15 -0
  23. package/dist/integrations/astro/kizlo.client.js +13 -0
  24. package/dist/integrations/astro/kizlo.server.d.ts +10 -0
  25. package/dist/integrations/astro/kizlo.server.js +12 -0
  26. package/dist/integrations/astro/manifest.d.ts +9 -0
  27. package/dist/integrations/astro/manifest.js +11 -0
  28. package/dist/integrations/astro/metadata.d.ts +64 -0
  29. package/dist/integrations/astro/metadata.js +71 -0
  30. package/dist/integrations/astro/robots.d.ts +8 -0
  31. package/dist/integrations/astro/robots.js +11 -0
  32. package/dist/integrations/astro/sitemap.d.ts +16 -0
  33. package/dist/integrations/astro/sitemap.js +20 -0
  34. package/dist/integrations/astro/utils.d.ts +19 -0
  35. package/dist/integrations/astro/utils.js +17 -0
  36. package/dist/integrations/nextjs/metadata.d.ts +8 -1
  37. package/dist/integrations/nextjs/metadata.js +13 -1
  38. package/dist/kizlo.d.ts +8 -1
  39. package/dist/kizlo.js +16 -13
  40. package/dist/nextjs/server.d.ts +3 -2
  41. package/dist/nextjs/server.js +3 -2
  42. package/dist/package.js +1 -1
  43. package/dist/router.d.ts +5 -5
  44. package/dist/seo/utils.d.ts +8 -2
  45. package/dist/seo/utils.js +9 -1
  46. package/dist/shared/constants.js +9 -1
  47. package/package.json +18 -2
  48. package/dist/cli/presets/base.js +0 -40
  49. package/dist/cli/presets/index.js +0 -16
  50. package/dist/cli/presets/nextjs.js +0 -16
@@ -0,0 +1,9 @@
1
+ import { WEB_MANIFEST_ROUTE } from "../brand/manifest.js";
2
+ import { renderJsonLd } from "../seo/utils.js";
3
+ import { KizloServerOptions, createKizlo } from "../integrations/astro/kizlo.server.js";
4
+ import { AstroRoute, AstroRouteContext, getServerBaseUrl } from "../integrations/astro/utils.js";
5
+ import { createManifestRoute } from "../integrations/astro/manifest.js";
6
+ import { PageHead, RootHead, ThemeColor, resolvePageHead, resolveRootHead } from "../integrations/astro/metadata.js";
7
+ import { createRobotsRoute } from "../integrations/astro/robots.js";
8
+ import { createSitemapRedirectRoute, createSitemapRoute } from "../integrations/astro/sitemap.js";
9
+ export { AstroRoute, AstroRouteContext, KizloServerOptions, PageHead, RootHead, ThemeColor, WEB_MANIFEST_ROUTE, createKizlo, createManifestRoute, createRobotsRoute, createSitemapRedirectRoute, createSitemapRoute, getServerBaseUrl, renderJsonLd, resolvePageHead, resolveRootHead };
@@ -0,0 +1,10 @@
1
+ import { WEB_MANIFEST_ROUTE } from "../brand/manifest.js";
2
+ import { renderJsonLd } from "../seo/utils.js";
3
+ import { getServerBaseUrl } from "../integrations/astro/utils.js";
4
+ import { createKizlo } from "../integrations/astro/kizlo.server.js";
5
+ import { createManifestRoute } from "../integrations/astro/manifest.js";
6
+ import { resolvePageHead, resolveRootHead } from "../integrations/astro/metadata.js";
7
+ import { createRobotsRoute } from "../integrations/astro/robots.js";
8
+ import { createSitemapRedirectRoute, createSitemapRoute } from "../integrations/astro/sitemap.js";
9
+
10
+ export { WEB_MANIFEST_ROUTE, createKizlo, createManifestRoute, createRobotsRoute, createSitemapRedirectRoute, createSitemapRoute, getServerBaseUrl, renderJsonLd, resolvePageHead, resolveRootHead };
@@ -0,0 +1,2 @@
1
+ import { KizloClientOptions, createKizloClient } from "./integrations/astro/kizlo.client.js";
2
+ export { KizloClientOptions, createKizloClient };
package/dist/astro.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createKizloClient } from "./integrations/astro/kizlo.client.js";
2
+
3
+ export { createKizloClient };
@@ -1,7 +1,7 @@
1
1
  import { LOCAL_DIR_REL } from "../wp/constants.js";
2
2
  import { composeStop, createStack, dockerHint, dockerStatus } from "../wp/docker.js";
3
3
  import { DEFAULT_ENV_KEYS, ensureGitignored, envGroups, envKeysPresent, mergeEnv, pickStackPort, writeFileIfAbsent } from "../utils.js";
4
- import { DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveStackName } from "../daemon/config.js";
4
+ import { DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveStackName, stackProject } from "../daemon/config.js";
5
5
  import { createAdminAppPassword } from "../wp/bootstrap.js";
6
6
  import { bootstrapDev } from "../wp/dev.js";
7
7
  import { removeProjectContainers } from "../wp/session.js";
@@ -17,13 +17,13 @@ import getPort, { portNumbers } from "get-port";
17
17
 
18
18
  //#region src/cli/commands/_setup.ts
19
19
  /**
20
- * The resolved `.env` key names the scaffold writes: the template's declared `env` when it has one,
21
- * else the preset's built-in `envKeys` (the generic `base` preset), else the {@link DEFAULT_ENV_KEYS}
22
- * fallback for an in-repo template before the first stamped release. Threaded into `managedEnv`,
23
- * `writeEnv`, and `collectConnectionFromEnv` so the scaffold writes the names the pinned runtime reads.
20
+ * The resolved `.env` key names the scaffold writes: the template's declared `env` when it has one, else
21
+ * the {@link DEFAULT_ENV_KEYS} fallback — for an in-repo template before the first stamped release, and
22
+ * for the base no-template fallback (which has no manifest). Threaded into `managedEnv`, `writeEnv`, and
23
+ * `collectConnectionFromEnv` so the scaffold writes the names the pinned runtime reads.
24
24
  */
25
- function resolveEnvKeys(preset, manifest) {
26
- return manifest?.env ?? preset.envKeys ?? DEFAULT_ENV_KEYS;
25
+ function resolveEnvKeys(manifest) {
26
+ return manifest?.env ?? DEFAULT_ENV_KEYS;
27
27
  }
28
28
  /** Remote connection keys — what a real deploy needs, and what `.env.example` always lists. */
29
29
  function remoteKeys(envKeys) {
@@ -135,11 +135,11 @@ function pickAppPort() {
135
135
  * `create` scaffolds a brand-new app that has no public URL yet, so it defaults to the local dev
136
136
  * origin and lets the user fill in production later.
137
137
  */
138
- async function collectConnectionInteractively(preset, opts = {}) {
138
+ async function collectConnectionInteractively(apiPath, opts = {}) {
139
139
  let siteUrl;
140
140
  let baseUrl;
141
141
  if (opts.baseUrl !== void 0) baseUrl = opts.baseUrl;
142
- else if (preset.apiPath) baseUrl = orCancel(await p.text({
142
+ else if (apiPath) baseUrl = orCancel(await p.text({
143
143
  message: "Public app URL",
144
144
  placeholder: "https://your-app.com",
145
145
  validate: validate(urlString)
@@ -238,7 +238,7 @@ function collectConnectionFromEnv(envKeys) {
238
238
  function devConfigFor(cwd) {
239
239
  return {
240
240
  configDir: cwd,
241
- project: `${resolveStackName(cwd)}-dev`,
241
+ project: stackProject(resolveStackName(cwd), "dev"),
242
242
  port: DEFAULT_DEV_PORT,
243
243
  portExplicit: false,
244
244
  dbPort: DEFAULT_DEV_DB_PORT,
@@ -1,8 +1,8 @@
1
1
  import { resolveModuleImport, writeFileIfAbsent } from "../utils.js";
2
- import { applyPatchToSource, patchChanged, renderPatchCode } from "../presets/patch.js";
3
- import { findRootLayout, resolvePatch } from "../presets/template.js";
2
+ import { resolvePatch } from "../presets/template.js";
4
3
  import { orCancel } from "./_setup.js";
5
4
  import { CONTRACT_BARREL } from "../daemon/generate.js";
5
+ import { applyPatchToSource, patchChanged, renderPatchCode, resolvePatchTargetPath } from "../presets/patch.js";
6
6
  import path from "node:path";
7
7
  import fs from "node:fs";
8
8
  import * as p from "@clack/prompts";
@@ -73,17 +73,19 @@ function writeGeneratedContract(cwd, serverDirRel) {
73
73
  writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
74
74
  }
75
75
  /**
76
- * Merge Kizlo wiring into files the project already owns (the root layout's SEO exports). Never
77
- * aborts: the target is resolved by identity (the layout that renders `<html>`), and on any doubt —
78
- * not found, not parseable — the resolved payload is printed at the end with placement instructions
79
- * rather than written to a guessed-at file. A confident apply is an idempotent upsert: it replaces
80
- * our exports if present, adds them if not.
76
+ * Apply every patch a template declares, merging Kizlo wiring into files the project already owns
77
+ * (today: the root layout and home page SEO exports — but the loop is role-agnostic, so a new patch
78
+ * needs no change here). Never aborts: the target is the exact path the template declares (extension-
79
+ * probed so a JS project's `.js`/`.jsx` is found), and on any doubt — the file isn't at that path, or
80
+ * won't parse — the resolved payload is printed at the end with placement instructions rather than
81
+ * written to a guessed-at file. We never scan the tree to find a stand-in. A confident apply is an
82
+ * idempotent upsert: it replaces our exports if present, adds them if not.
81
83
  */
82
- function applyLayoutPatches(cwd, patches, conventions, scaffold) {
84
+ function applyProjectPatches(cwd, patches, conventions, scaffold) {
83
85
  const manualSteps = [];
84
86
  for (const entry of patches) {
85
87
  const resolved = resolvePatch(entry, conventions, scaffold);
86
- const target = findRootLayout(cwd, scaffold.appDir, resolved.relPath);
88
+ const target = resolvePatchTargetPath(cwd, resolved.relPath);
87
89
  if (!target) {
88
90
  manualSteps.push(resolved);
89
91
  p.log.info(`Couldn't find your ${resolved.label} to wire Kizlo into — see the code to add below`);
@@ -105,8 +107,8 @@ function applyLayoutPatches(cwd, patches, conventions, scaffold) {
105
107
  p.log.success(`Wired Kizlo into your ${resolved.label} (${relTarget})`);
106
108
  } else p.log.info(`Your ${resolved.label} is already wired (${relTarget})`);
107
109
  }
108
- for (const resolved of manualSteps) p.note(renderPatchCode(resolved), `Add these to your ${resolved.label} (the layout that renders <html>)`);
110
+ for (const resolved of manualSteps) p.note(renderPatchCode(resolved), `Add these to your ${resolved.label} (${resolved.relPath})`);
109
111
  }
110
112
 
111
113
  //#endregion
112
- export { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract };
114
+ export { applyProjectPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract };
@@ -1,9 +1,8 @@
1
1
  import { availablePackageManagers, detectInvokingPackageManager, ensureGitignored, frameworkCreateArgs, installArgs, runCommandAsync, runCommandCaptured } from "../utils.js";
2
- import { getPreset } from "../presets/index.js";
3
- import { fetchTemplate } from "../presets/source.js";
2
+ import { fetchRegistry, listTemplates, resolveRegistry } from "../presets/source.js";
4
3
  import { adaptFile, changesFor, fileEntries, minCliError, patchEntries, readManifest, resolveDependencies } from "../presets/template.js";
5
4
  import { collectConnectionInteractively, nextStepsLines, orCancel, pickAppPort, readEnvExample, resolveEnvKeys, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
6
- import { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
5
+ import { applyProjectPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
7
6
  import z from "zod/v4";
8
7
  import path from "node:path";
9
8
  import fs from "node:fs";
@@ -11,15 +10,30 @@ import { defineCommand } from "citty";
11
10
  import * as p from "@clack/prompts";
12
11
 
13
12
  //#region src/cli/commands/create.ts
13
+ /** The final path segment of a project name — the app folder itself; everything before it is where it lands. */
14
+ function folderSegment(value) {
15
+ const segments = value.replace(/\\/g, "/").split("/").filter(Boolean);
16
+ return segments[segments.length - 1] ?? "";
17
+ }
14
18
  /**
15
- * The templates `create` can scaffold from. Each id is both the `templates/<id>` folder and the
16
- * preset that drives its WordPress setup and framework bootstrap.
19
+ * Tidy the entered name/path for both the target dir and the display: trim, drop a leading `./`, and drop a
20
+ * trailing separator, so `cd`, the spinner, and the warning lines read as `templates/my-app`, not
21
+ * `./templates/my-app/`. Preserves `../` and absolute paths (only a leading `./` is redundant).
17
22
  */
18
- const TEMPLATES = ["nextjs"];
19
- function isTemplate(value) {
20
- return TEMPLATES.includes(value);
23
+ function normalizeProjectName(value) {
24
+ return value.trim().replace(/^\.\/+/, "").replace(/[/\\]+$/, "");
21
25
  }
22
- const projectName = validate(z.string().trim().min(1, "Required").refine((value) => !value.startsWith(".") && !value.includes("/") && !value.includes("\\"), "Enter a folder name (letters, numbers, dashes)"));
26
+ /**
27
+ * A project name may be a bare folder (`my-app`) or a path to scaffold somewhere other than the current
28
+ * dir — relative (`apps/my-app`, `../my-app`) or absolute (`/srv/my-app`). Only the last segment is the
29
+ * app folder, so that's what's validated as a name; the parent dirs (which may not exist yet) are created
30
+ * by the framework's own CLI. `.`/`..` as the final segment, or a name with no real segment, is rejected.
31
+ */
32
+ const projectNameSchema = z.string().trim().min(1, "Required").refine((value) => {
33
+ const base = folderSegment(value);
34
+ return base.length > 0 && base !== "." && base !== ".." && /^[A-Za-z0-9._-]+$/.test(base);
35
+ }, "The app folder (last path segment) can only contain letters, numbers, dots, and dashes");
36
+ const projectName = validate(projectNameSchema);
23
37
  /** Package managers `create` can wire the getting-started steps for, in display order. */
24
38
  const PACKAGE_MANAGERS = [
25
39
  "pnpm",
@@ -37,17 +51,25 @@ function recordDependencies(dir, manifest) {
37
51
  const pkgPath = path.join(dir, "package.json");
38
52
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
39
53
  const { dependencies, devDependencies } = resolveDependencies(manifest);
40
- pkg.dependencies = {
54
+ pkg.dependencies = sortDependencies({
41
55
  ...pkg.dependencies,
42
56
  ...dependencies
43
- };
44
- if (Object.keys(devDependencies).length) pkg.devDependencies = {
57
+ });
58
+ if (Object.keys(devDependencies).length) pkg.devDependencies = sortDependencies({
45
59
  ...pkg.devDependencies,
46
60
  ...devDependencies
47
- };
61
+ });
48
62
  fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, " ")}\n`);
49
63
  }
50
64
  /**
65
+ * Alphabetize a dependency map by package name. The framework CLI writes its deps sorted, but merging
66
+ * Kizlo's pins on top appends them out of order — sherif flags unsorted dependency keys, so re-sort the
67
+ * whole map before writing.
68
+ */
69
+ function sortDependencies(deps) {
70
+ return Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
71
+ }
72
+ /**
51
73
  * The argv `create` runs to bootstrap a fresh base app with the framework's own official CLI, built
52
74
  * from the template manifest's `bootstrap` (initializer + flags) — the template is the single source
53
75
  * of truth for the framework. The CLI owns only the package-manager mechanics: {@link frameworkCreateArgs}
@@ -66,7 +88,7 @@ function bootstrapArgs(manifest, pm, name) {
66
88
  * `kizlo` dependency, write `kizlo.config.ts`, scaffold the manifest's files (core wiring from `base`
67
89
  * and `create` always, the `example`-flagged demo pages only when `includeExamples`),
68
90
  * seed the generated contract, and ignore `.env` and the `.kizlo/` working dir (which holds the local WordPress install). On a fresh app Kizlo owns the layout, so `create`
69
- * writes it whole (already SEO-wired) rather than patching — `applyLayoutPatches` runs over `create`'s
91
+ * writes it whole (already SEO-wired) rather than patching — `applyProjectPatches` runs over `create`'s
70
92
  * patches, which are none today. The target directory layout comes straight from the manifest's
71
93
  * conventions, which the bootstrap flags are chosen to match, so files land where they expect. Fresh
72
94
  * files are written with `force`, silently replacing the framework defaults.
@@ -93,7 +115,7 @@ async function applyManifestWiring(dir, templateDir, manifest, opts) {
93
115
  writeGeneratedContract(dir, path.join(kizloDir, "server"));
94
116
  ensureGitignored(dir, ".env");
95
117
  ensureGitignored(dir, ".kizlo/");
96
- applyLayoutPatches(dir, patchEntries(changes), manifest.conventions, scaffold);
118
+ applyProjectPatches(dir, patchEntries(changes), manifest.conventions, scaffold);
97
119
  }
98
120
  const create = defineCommand({
99
121
  meta: {
@@ -104,54 +126,75 @@ const create = defineCommand({
104
126
  template: {
105
127
  type: "positional",
106
128
  required: false,
107
- description: `Template to scaffold from (${TEMPLATES.join(", ")})`
129
+ description: "Template to scaffold from (discovered from the registry)"
108
130
  },
109
131
  name: {
110
132
  type: "positional",
111
133
  required: false,
112
- description: "Project folder to create"
134
+ description: "Project folder to create — a bare name or a path (e.g. apps/my-app, ../my-app, /srv/my-app)"
135
+ },
136
+ source: {
137
+ type: "string",
138
+ description: "Where to scaffold from — a local dir or giget source, either a registry of templates or a single template (default: Kizlo's GitHub templates)"
113
139
  }
114
140
  },
115
141
  async run({ args }) {
116
142
  const cwd = process.cwd();
117
143
  p.intro(`Let's create a new Kizlo application`);
144
+ const registry = resolveRegistry(args.source ? String(args.source) : void 0);
145
+ let fetchedRegistry;
146
+ try {
147
+ fetchedRegistry = await fetchRegistry(registry);
148
+ } catch (error) {
149
+ p.cancel(error instanceof Error ? error.message : String(error));
150
+ process.exit(1);
151
+ }
152
+ const templates = listTemplates(fetchedRegistry.dir);
153
+ if (templates.length === 0) {
154
+ fetchedRegistry.cleanup();
155
+ p.cancel(`No templates found in the registry (${registry.source}). Each template needs a template.json.`);
156
+ process.exit(1);
157
+ }
158
+ const cancelFrom = (message) => {
159
+ fetchedRegistry.cleanup();
160
+ p.cancel(message);
161
+ process.exit(1);
162
+ };
118
163
  const requested = args.template;
119
- let template;
164
+ let selected;
120
165
  if (requested) {
121
- if (!isTemplate(requested)) {
122
- p.cancel(`Unknown template "${requested}". Available: ${TEMPLATES.join(", ")}`);
123
- process.exit(1);
124
- }
125
- template = requested;
126
- } else template = orCancel(await p.select({
127
- message: "Template",
128
- options: TEMPLATES.map((id) => ({
129
- value: id,
130
- label: id
131
- }))
132
- }));
133
- const name = args.name ? String(args.name).trim() : orCancel(await p.text({
134
- message: "Project name",
166
+ selected = templates.find((entry) => entry.id === requested);
167
+ if (!selected) return cancelFrom(`Unknown template "${requested}". Available: ${templates.map((entry) => entry.id).join(", ")}`);
168
+ } else {
169
+ const id = orCancel(await p.select({
170
+ message: "Template",
171
+ options: templates.map((entry) => ({
172
+ value: entry.id,
173
+ label: entry.label
174
+ }))
175
+ }));
176
+ selected = templates.find((entry) => entry.id === id);
177
+ }
178
+ if (!selected) return cancelFrom("No template selected.");
179
+ const template = selected.id;
180
+ const templateDir = selected.dir;
181
+ const label = selected.label;
182
+ const fail = cancelFrom;
183
+ const manifest = readManifest(templateDir);
184
+ const minErr = minCliError(manifest);
185
+ if (minErr) return fail(minErr);
186
+ const name = normalizeProjectName(args.name ? String(args.name) : orCancel(await p.text({
187
+ message: "What is your project named?",
135
188
  placeholder: "my-app",
136
189
  validate: projectName
137
- }));
138
- if (!args.name) {
139
- const check = projectName(name);
140
- if (check) {
141
- p.cancel(check);
142
- process.exit(1);
143
- }
144
- }
145
- const dir = path.resolve(cwd, name);
146
- if (fs.existsSync(dir)) {
147
- p.cancel(`${name} already exists — pick a different name or remove it.`);
148
- process.exit(1);
149
- }
150
- const preset = getPreset(template);
151
- if (!preset) {
152
- p.cancel(`No preset for template "${template}".`);
190
+ })));
191
+ const invalid = projectName(name);
192
+ if (invalid) {
193
+ p.cancel(invalid);
153
194
  process.exit(1);
154
195
  }
196
+ const dir = path.resolve(cwd, name);
197
+ if (fs.existsSync(dir)) return cancelFrom(`${name} already exists — pick a different name or remove it.`);
155
198
  const installed = availablePackageManagers(PACKAGE_MANAGERS);
156
199
  const invokingPm = detectInvokingPackageManager();
157
200
  const defaultPm = invokingPm && installed.includes(invokingPm) ? invokingPm : installed[0];
@@ -163,45 +206,32 @@ const create = defineCommand({
163
206
  })),
164
207
  initialValue: defaultPm
165
208
  }));
166
- const conn = await collectConnectionInteractively(preset, { baseUrl: `http://localhost:${await pickAppPort()}` });
167
- if (preset.apiPath && conn.baseUrl) conn.baseUrl = withApiPath(conn.baseUrl, preset.apiPath);
209
+ const conn = await collectConnectionInteractively(manifest.apiPath, { baseUrl: `http://localhost:${await pickAppPort()}` });
210
+ if (manifest.apiPath && conn.baseUrl) conn.baseUrl = withApiPath(conn.baseUrl, manifest.apiPath);
168
211
  const includeExamples = orCancel(await p.confirm({
169
212
  message: "Add examples?",
170
213
  initialValue: true
171
214
  }));
172
- const fetched = await fetchTemplate(template);
173
- const fail = (message) => {
174
- fetched.cleanup();
175
- p.cancel(message);
176
- process.exit(1);
177
- };
178
- const manifest = readManifest(fetched.dir);
179
- const minErr = minCliError(manifest);
180
- if (minErr) fail(minErr);
181
215
  const bootstrap = bootstrapArgs(manifest, pm, name);
182
- if (!bootstrap) {
183
- fetched.cleanup();
184
- p.cancel(`Template "${template}" can't be scaffolded — its manifest declares no framework bootstrap.`);
185
- process.exit(1);
186
- }
216
+ if (!bootstrap) return fail(`Template "${template}" can't be scaffolded — its manifest declares no framework bootstrap.`);
187
217
  const s = p.spinner();
188
- s.start(`Creating ${name} with the ${preset.label} CLI`);
218
+ s.start(`Creating ${name} with the ${label} CLI`);
189
219
  const scaffold = runCommandCaptured(bootstrap, cwd);
190
- s.stop(scaffold.ok ? `Created ${name} with the ${preset.label} CLI` : `${preset.label} setup failed`);
220
+ s.stop(scaffold.ok ? `Created ${name} with the ${label} CLI` : `${label} setup failed`);
191
221
  if (!scaffold.ok) {
192
222
  if (scaffold.output) p.log.error(scaffold.output);
193
- fail(`${preset.label} setup failed — see the output above and try again.`);
223
+ fail(`${label} setup failed — see the output above and try again.`);
194
224
  }
195
- const exampleTemplate = readEnvExample(fetched.dir);
225
+ const exampleTemplate = readEnvExample(templateDir);
196
226
  try {
197
- await applyManifestWiring(dir, fetched.dir, manifest, {
227
+ await applyManifestWiring(dir, templateDir, manifest, {
198
228
  includeExamples,
199
229
  localDev: conn.mode === "local"
200
230
  });
201
231
  } catch (error) {
202
232
  fail(error instanceof Error ? error.message : String(error));
203
233
  }
204
- fetched.cleanup();
234
+ fetchedRegistry.cleanup();
205
235
  let depsInstalled = false;
206
236
  if (orCancel(await p.confirm({
207
237
  message: "Install dependencies now?",
@@ -214,7 +244,7 @@ const create = defineCommand({
214
244
  if (!depsInstalled) p.log.warn(`Install them yourself: cd ${name} && ${installArgs(pm).join(" ")}`);
215
245
  }
216
246
  await setupLocalWordPress(dir, conn);
217
- await writeEnv(dir, resolveEnvKeys(preset, manifest), conn, {
247
+ await writeEnv(dir, resolveEnvKeys(manifest), conn, {
218
248
  force: true,
219
249
  yes: false,
220
250
  exampleTemplate
@@ -15,6 +15,10 @@ const generate = defineCommand({
15
15
  } },
16
16
  async run({ args }) {
17
17
  const cfg = await resolveConfig(process.cwd(), { dir: args.dir });
18
+ if (!cfg) {
19
+ log.info("No Kizlo server directory configured, nothing to generate. Set `dir` in kizlo.config.ts or pass --dir.");
20
+ return;
21
+ }
18
22
  let ok;
19
23
  try {
20
24
  ok = await generateOnce(cfg);