kizlo 0.8.2 → 0.8.4

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.
@@ -321,11 +321,11 @@ async function syncRemote(conn) {
321
321
  });
322
322
  if (!sync.ok) p.log.warn(`Could not sync the site settings to WordPress (${sync.error}) — make sure the kizlo plugin is active, then set them from the Kizlo settings.`);
323
323
  }
324
- /** The shared "Next steps" note, branched on whether a local dev stack was set up. */
325
- function nextStepsNote(mode, prefix = "") {
326
- p.note([
324
+ /** The "Next steps" lines, branched on whether a local dev stack was set up. */
325
+ function nextStepsLines(mode, prefix = "") {
326
+ return [
327
327
  ...mode === "local" ? [
328
- `Start your local WordPress dev stack (also watches your extensions):`,
328
+ `Start your local WordPress dev stack:`,
329
329
  ` ${prefix}npx kizlo dev`,
330
330
  ``
331
331
  ] : [
@@ -335,8 +335,12 @@ function nextStepsNote(mode, prefix = "") {
335
335
  ],
336
336
  `Generate the contract once for production builds:`,
337
337
  ` ${prefix}npx kizlo generate`
338
- ].join("\n"), "Next steps");
338
+ ];
339
+ }
340
+ /** The shared "Next steps" note, branched on whether a local dev stack was set up. */
341
+ function nextStepsNote(mode, prefix = "") {
342
+ p.note(nextStepsLines(mode, prefix).join("\n"), "Next steps");
339
343
  }
340
344
 
341
345
  //#endregion
342
- export { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv };
346
+ export { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsLines, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv };
@@ -0,0 +1,116 @@
1
+ import { resolveModuleImport, writeFileIfAbsent } from "../utils.js";
2
+ import { applyPatchToSource, patchChanged, renderPatchCode } from "../presets/patch.js";
3
+ import { findRootLayout, patchEntries, resolvePatch } from "../presets/template.js";
4
+ import { orCancel } from "./_setup.js";
5
+ import { CONTRACT_BARREL } from "../daemon/generate.js";
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+ import * as p from "@clack/prompts";
9
+
10
+ //#region src/cli/commands/_wiring.ts
11
+ /** The kizlo.config.ts a scaffolded project gets: the Kizlo directory, optional import alias, and —
12
+ * for a local dev setup — the WordPress install folder so `kizlo dev` knows where it lives. */
13
+ function kizloConfigTemplate(dir, alias, devPath) {
14
+ return `import { defineConfig } from "kizlo/config"
15
+
16
+ export default defineConfig({
17
+ dir: "${dir}",${alias ? `\n\talias: "${alias}",` : ""}${devPath ? `\n\tdev: { path: "${devPath}" },` : ""}
18
+ })
19
+ `;
20
+ }
21
+ /**
22
+ * Build the {@link ScaffoldContext} that adapts a template's tokenized paths and imports to a real
23
+ * project. `dirRel` is the Kizlo home directory, `appDir` the App Router directory, and `alias` the
24
+ * import-alias prefix (empty for relative imports). The server-entry import is resolved per calling
25
+ * file so each scaffolded file references the server through the right specifier.
26
+ */
27
+ function buildScaffoldContext(cwd, { dirRel, appDir, alias, clientUrl }) {
28
+ const serverDirRel = path.join(dirRel, "server");
29
+ return {
30
+ kizloDir: dirRel,
31
+ serverDirName: path.basename(serverDirRel),
32
+ serverEntryPath: path.join(serverDirRel, "index.ts"),
33
+ clientPath: path.join(dirRel, "client.ts"),
34
+ appDir,
35
+ serverImport: (fromDir) => resolveModuleImport(cwd, serverDirRel, fromDir, alias),
36
+ clientUrl
37
+ };
38
+ }
39
+ /**
40
+ * The single overwrite policy for every scaffolded file: create it when absent, overwrite on
41
+ * `--force`, and otherwise ask before clobbering an existing file (keeping it when the user
42
+ * declines or when running non-interactively with `--yes`). Every scaffolded file routes through
43
+ * here rather than deciding for itself, so new files inherit the same behavior for free.
44
+ */
45
+ async function scaffoldFile(cwd, file, opts) {
46
+ const absPath = path.join(cwd, file.relPath);
47
+ const existed = fs.existsSync(absPath);
48
+ if (existed) {
49
+ let overwrite = opts.force;
50
+ if (!opts.force && !opts.yes) {
51
+ p.log.warn(`${file.label} already exists at ${file.relPath}`);
52
+ overwrite = orCancel(await p.confirm({
53
+ message: "Overwrite it?",
54
+ initialValue: true
55
+ }));
56
+ }
57
+ if (!overwrite) return "kept";
58
+ }
59
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
60
+ fs.writeFileSync(absPath, file.contents);
61
+ return existed ? "overwritten" : "created";
62
+ }
63
+ /** Report a {@link scaffoldFile} outcome in the setup commands' usual voice. */
64
+ function reportScaffold(file, result, yes) {
65
+ if (result === "kept") p.log.info(`Kept existing ${file.label} (${file.relPath})${yes ? " — pass --force to overwrite" : ""}`);
66
+ else p.log.success(`${result === "overwritten" ? "Overwrote" : "Created"} ${file.label} (${file.relPath})`);
67
+ }
68
+ /** Seed the generated-contract directory so imports resolve before the first `kizlo watch`/`generate`. */
69
+ function writeGeneratedContract(cwd, serverDirRel) {
70
+ const generatedDirRel = path.join(serverDirRel, "generated");
71
+ writeFileIfAbsent(path.join(cwd, generatedDirRel, "contract.json"), "{}\n");
72
+ writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
73
+ }
74
+ /**
75
+ * Merge Kizlo wiring into files the project already owns (the root layout's SEO exports). Never
76
+ * aborts: the target is resolved by identity (the layout that renders `<html>`), and on any doubt —
77
+ * not found, not parseable — the resolved payload is printed at the end with placement instructions
78
+ * rather than written to a guessed-at file. A confident apply is an idempotent upsert: it replaces
79
+ * our exports if present, adds them if not.
80
+ */
81
+ function applyLayoutPatches(cwd, manifest, scaffold) {
82
+ const manualSteps = [];
83
+ for (const entry of patchEntries(manifest)) {
84
+ const resolved = resolvePatch(entry, manifest.tokens, scaffold);
85
+ const target = findRootLayout(cwd, scaffold.appDir, resolved.relPath);
86
+ if (!target) {
87
+ manualSteps.push(resolved);
88
+ p.log.info(`Couldn't find your ${resolved.label} to wire Kizlo into — see the code to add below`);
89
+ continue;
90
+ }
91
+ const relTarget = path.relative(cwd, target);
92
+ const src = fs.readFileSync(target, "utf8");
93
+ let applied;
94
+ try {
95
+ applied = applyPatchToSource(src, resolved);
96
+ } catch {
97
+ manualSteps.push(resolved);
98
+ p.log.warn(`Couldn't parse your ${resolved.label} (${relTarget}) — see the code to add below`);
99
+ continue;
100
+ }
101
+ const { text, changes } = applied;
102
+ if (patchChanged(changes)) {
103
+ fs.writeFileSync(target, text);
104
+ const parts = [
105
+ ...changes.replacedExports.length ? [`replaced ${changes.replacedExports.join(", ")}`] : [],
106
+ ...changes.addedExports.length ? [`added ${changes.addedExports.join(", ")}`] : [],
107
+ ...changes.addedImports.length ? ["imports"] : []
108
+ ];
109
+ p.log.success(`Wired Kizlo into your ${resolved.label} (${relTarget}) — ${parts.join(", ")}`);
110
+ } else p.log.info(`Your ${resolved.label} is already wired (${relTarget})`);
111
+ }
112
+ for (const resolved of manualSteps) p.note(renderPatchCode(resolved), `Add these to your ${resolved.label} (the layout that renders <html>)`);
113
+ }
114
+
115
+ //#endregion
116
+ export { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract };
@@ -1,10 +1,11 @@
1
1
  import { dockerAvailable } from "../wp/docker.js";
2
- import { detectPackageManager, getVersion } from "../utils.js";
2
+ import { availablePackageManagers, detectInvokingPackageManager, ensureGitignored, frameworkCreateArgs, getVersion, installArgs, runCommandAsync, runCommandCaptured } from "../utils.js";
3
3
  import { printBanner } from "../banner.js";
4
4
  import { getPreset } from "../presets/index.js";
5
5
  import { fetchTemplate } from "../presets/source.js";
6
- import { readManifest } from "../presets/template.js";
7
- import { collectConnectionInteractively, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
6
+ import { adaptOwnFile, ownEntries, readManifest } from "../presets/template.js";
7
+ import { collectConnectionInteractively, nextStepsLines, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
8
+ import { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
8
9
  import z from "zod/v4";
9
10
  import path from "node:path";
10
11
  import fs from "node:fs";
@@ -14,54 +15,77 @@ import * as p from "@clack/prompts";
14
15
  //#region src/cli/commands/create.ts
15
16
  /**
16
17
  * The templates `create` can scaffold from. Each id is both the `templates/<id>` folder and the
17
- * preset that drives its WordPress setup.
18
+ * preset that drives its WordPress setup and framework bootstrap.
18
19
  */
19
20
  const TEMPLATES = ["nextjs"];
20
21
  function isTemplate(value) {
21
22
  return TEMPLATES.includes(value);
22
23
  }
23
24
  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
+ /** Package managers `create` can wire the getting-started steps for, in display order. */
26
+ const PACKAGE_MANAGERS = [
27
+ "pnpm",
28
+ "npm",
29
+ "yarn",
30
+ "bun"
31
+ ];
32
32
  /**
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.
33
+ * Pin the `kizlo` dependency in the freshly bootstrapped `package.json`. The template is
34
+ * authoritative for the version (`kizloVersion` in its manifest, stamped at release), falling back to
35
+ * the running CLI's version in-repo before the first stamp. The dependency is only recorded, not
36
+ * installed — the getting-started note tells the user to run install.
39
37
  */
40
- async function scaffoldTemplate(template, dir, name) {
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
- }
38
+ function recordKizloDependency(dir, manifest) {
52
39
  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
- }
60
40
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
61
- pkg.name = name;
62
- if (pkg.dependencies?.kizlo === "workspace:*") pkg.dependencies.kizlo = kizloVersion;
41
+ pkg.dependencies ??= {};
42
+ pkg.dependencies.kizlo = manifest.kizloVersion ?? `^${getVersion()}`;
63
43
  fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, " ")}\n`);
64
44
  }
45
+ /**
46
+ * The argv `create` runs to bootstrap a fresh base app with the framework's own official CLI, built
47
+ * from the template manifest's `bootstrap` (initializer + flags) — the template is the single source
48
+ * of truth for the framework. The CLI owns only the package-manager mechanics: {@link frameworkCreateArgs}
49
+ * assembles the `<pm> create …` invocation, and the `{{pm}}` token in the flags is substituted with the
50
+ * chosen manager (e.g. `--use-{{pm}}` → `--use-pnpm`).
51
+ */
52
+ function bootstrapArgs(manifest, pm, name) {
53
+ if (!manifest.bootstrap) return void 0;
54
+ const flags = manifest.bootstrap.flags.map((flag) => flag.replaceAll("{{pm}}", pm));
55
+ return frameworkCreateArgs(pm, manifest.bootstrap.initializer, name, flags);
56
+ }
57
+ /**
58
+ * Layer Kizlo's wiring onto a freshly bootstrapped app in `dir`, from an already-fetched template
59
+ * directory + manifest. The framework CLI has produced the base project (package.json, config,
60
+ * tsconfig, the root layout); this drives the manifest — the same engine `init` uses — to record the
61
+ * `kizlo` dependency, write `kizlo.config.ts`, scaffold the manifest's own files (wiring plus the demo
62
+ * starter pages), seed the generated contract, ignore `.env`, and patch the root layout's SEO exports.
63
+ * The target directory layout comes straight from the manifest tokens, which the bootstrap flags are
64
+ * chosen to match, so files land where the manifest expects. Fresh files are written with `force`,
65
+ * silently replacing the framework defaults.
66
+ */
67
+ async function applyManifestWiring(dir, templateDir, manifest, opts) {
68
+ const { kizloDir, appDir, alias } = manifest.tokens;
69
+ const scaffold = buildScaffoldContext(dir, {
70
+ dirRel: kizloDir,
71
+ appDir,
72
+ alias,
73
+ clientUrl: void 0
74
+ });
75
+ recordKizloDependency(dir, manifest);
76
+ const files = [{
77
+ label: "Kizlo config",
78
+ relPath: "kizlo.config.ts",
79
+ contents: kizloConfigTemplate(kizloDir, alias, opts.devPath)
80
+ }, ...ownEntries(manifest, { includeStarter: opts.includeStarter }).map((entry) => adaptOwnFile(templateDir, entry, manifest.tokens, scaffold))];
81
+ for (const file of files) reportScaffold(file, await scaffoldFile(dir, file, {
82
+ force: true,
83
+ yes: false
84
+ }), false);
85
+ writeGeneratedContract(dir, path.join(kizloDir, "server"));
86
+ ensureGitignored(dir, ".env");
87
+ applyLayoutPatches(dir, manifest, scaffold);
88
+ }
65
89
  const create = defineCommand({
66
90
  meta: {
67
91
  name: "create",
@@ -120,21 +144,67 @@ const create = defineCommand({
120
144
  p.cancel(`No preset for template "${template}".`);
121
145
  process.exit(1);
122
146
  }
147
+ const installed = availablePackageManagers(PACKAGE_MANAGERS);
148
+ const invokingPm = detectInvokingPackageManager();
149
+ const defaultPm = invokingPm && installed.includes(invokingPm) ? invokingPm : installed[0];
150
+ const pm = orCancel(await p.select({
151
+ message: "Package manager",
152
+ options: installed.map((id) => ({
153
+ value: id,
154
+ label: id
155
+ })),
156
+ initialValue: defaultPm
157
+ }));
158
+ const includeStarter = orCancel(await p.confirm({
159
+ message: "Add example pages?",
160
+ initialValue: true
161
+ }));
123
162
  const conn = await collectConnectionInteractively(preset);
124
163
  if (preset.apiPath && conn.baseUrl) conn.baseUrl = withApiPath(conn.baseUrl, preset.apiPath);
125
164
  if (conn.mode === "local" && !await dockerAvailable()) {
126
165
  p.cancel("Docker isn't available — start Docker (or install it) and re-run, or choose “Use my own WordPress”.");
127
166
  process.exit(1);
128
167
  }
168
+ const fetched = await fetchTemplate(template);
169
+ const fail = (message) => {
170
+ fetched.cleanup();
171
+ p.cancel(message);
172
+ process.exit(1);
173
+ };
174
+ const manifest = readManifest(fetched.dir);
175
+ const bootstrap = bootstrapArgs(manifest, pm, name);
176
+ if (!bootstrap) {
177
+ fetched.cleanup();
178
+ p.cancel(`Template "${template}" can't be scaffolded — its manifest declares no framework bootstrap.`);
179
+ process.exit(1);
180
+ }
129
181
  const s = p.spinner();
130
- s.start(`Creating ${name} from the ${template} template`);
182
+ s.start(`Creating ${name} with the ${preset.label} CLI`);
183
+ const scaffold = runCommandCaptured(bootstrap, cwd);
184
+ s.stop(scaffold.ok ? `Created ${name} with the ${preset.label} CLI` : `${preset.label} setup failed`);
185
+ if (!scaffold.ok) {
186
+ if (scaffold.output) p.log.error(scaffold.output);
187
+ fail(`${preset.label} setup failed — see the output above and try again.`);
188
+ }
131
189
  try {
132
- await scaffoldTemplate(template, dir, name);
133
- s.stop(`Created ${name}`);
190
+ await applyManifestWiring(dir, fetched.dir, manifest, {
191
+ devPath: conn.devPath,
192
+ includeStarter
193
+ });
134
194
  } catch (error) {
135
- s.stop("Could not create the project");
136
- p.cancel(error instanceof Error ? error.message : String(error));
137
- process.exit(1);
195
+ fail(error instanceof Error ? error.message : String(error));
196
+ }
197
+ fetched.cleanup();
198
+ let depsInstalled = false;
199
+ if (orCancel(await p.confirm({
200
+ message: "Install dependencies now?",
201
+ initialValue: true
202
+ }))) {
203
+ const is = p.spinner();
204
+ is.start(`Installing dependencies with ${pm}`);
205
+ depsInstalled = await runCommandAsync(installArgs(pm), dir, "ignore");
206
+ is.stop(depsInstalled ? "Installed dependencies" : "Could not install dependencies");
207
+ if (!depsInstalled) p.log.warn(`Install them yourself: cd ${name} && ${installArgs(pm).join(" ")}`);
138
208
  }
139
209
  await setupLocalWordPress(dir, conn);
140
210
  await writeEnv(dir, preset, conn, {
@@ -142,9 +212,12 @@ const create = defineCommand({
142
212
  yes: false
143
213
  });
144
214
  await syncRemote(conn);
145
- const pm = detectPackageManager(dir);
146
- p.note([`cd ${name}`, `${pm} install`].join("\n"), "Get started");
147
- nextStepsNote(conn.mode);
215
+ p.note([
216
+ `cd ${name}`,
217
+ ...depsInstalled ? [] : [`${pm} install`],
218
+ ``,
219
+ ...nextStepsLines(conn.mode)
220
+ ].join("\n"), "Next steps");
148
221
  p.outro("Kizlo is ready 🎉");
149
222
  }
150
223
  });
@@ -1,12 +1,11 @@
1
1
  import { dockerAvailable } from "../wp/docker.js";
2
- import { addDependencyArgs, detectImportAlias, detectPackageManager, ensureGitignored, getVersion, loadEnvFiles, resolveModuleImport, runCommand, writeFileIfAbsent } from "../utils.js";
2
+ import { addDependencyArgs, detectImportAlias, detectPackageManager, ensureGitignored, getVersion, loadEnvFiles, runCommandAsync } from "../utils.js";
3
3
  import { printBanner } from "../banner.js";
4
4
  import { PRESETS, detectPreset, getPreset } from "../presets/index.js";
5
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";
6
+ import { adaptOwnFile, ownEntries, readManifest } from "../presets/template.js";
8
7
  import { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
9
- import { CONTRACT_BARREL } from "../daemon/generate.js";
8
+ import { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
10
9
  import path from "node:path";
11
10
  import fs from "node:fs";
12
11
  import { defineCommand } from "citty";
@@ -20,14 +19,6 @@ function defaultDir(hasSrcDir) {
20
19
  function aliasWithSlash(alias) {
21
20
  return alias ? `${alias.replace(/\/+$/, "")}/` : "";
22
21
  }
23
- function kizloConfigTemplate(dir, alias, devPath) {
24
- return `import { defineConfig } from "kizlo/config"
25
-
26
- export default defineConfig({
27
- dir: "${dir}",${alias ? `\n\talias: "${alias}",` : ""}${devPath ? `\n\tdev: { path: "${devPath}" },` : ""}
28
- })
29
- `;
30
- }
31
22
  /** Whether two URLs share an origin (scheme + host + port); false when either can't be parsed. */
32
23
  function sameOrigin(a, b) {
33
24
  try {
@@ -41,35 +32,6 @@ function detectAppDir(cwd, hasSrcDir) {
41
32
  if (fs.existsSync(path.join(cwd, "app"))) return "app";
42
33
  return hasSrcDir ? "src/app" : "app";
43
34
  }
44
- /**
45
- * The single overwrite policy for every scaffolded file: create it when absent, overwrite on
46
- * `--force`, and otherwise ask before clobbering an existing file (keeping it when the user
47
- * declines or when running non-interactively with `--yes`). Every scaffolded file routes through
48
- * here rather than deciding for itself, so new files inherit the same behavior for free.
49
- */
50
- async function scaffoldFile(cwd, file, opts) {
51
- const absPath = path.join(cwd, file.relPath);
52
- const existed = fs.existsSync(absPath);
53
- if (existed) {
54
- let overwrite = opts.force;
55
- if (!opts.force && !opts.yes) {
56
- p.log.warn(`${file.label} already exists at ${file.relPath}`);
57
- overwrite = orCancel(await p.confirm({
58
- message: "Overwrite it?",
59
- initialValue: true
60
- }));
61
- }
62
- if (!overwrite) return "kept";
63
- }
64
- fs.mkdirSync(path.dirname(absPath), { recursive: true });
65
- fs.writeFileSync(absPath, file.contents);
66
- return existed ? "overwritten" : "created";
67
- }
68
- /** Report a {@link scaffoldFile} outcome in init's usual voice. */
69
- function reportScaffold(file, result, yes) {
70
- if (result === "kept") p.log.info(`Kept existing ${file.label} (${file.relPath})${yes ? " — pass --force to overwrite" : ""}`);
71
- else p.log.success(`${result === "overwritten" ? "Overwrote" : "Created"} ${file.label} (${file.relPath})`);
72
- }
73
35
  async function collectInteractively(ctx) {
74
36
  const conn = await collectConnectionInteractively(ctx.preset);
75
37
  const dir = orCancel(await p.text({
@@ -136,13 +98,13 @@ function isOlder(have, want) {
136
98
  * When the project's kizlo is older, upgrade the project up to it and say so — a deliberate pin is
137
99
  * never changed silently. Never downgrades.
138
100
  */
139
- function alignKizloVersion(cwd, pm, pkg, manifest) {
101
+ async function alignKizloVersion(cwd, pm, pkg, manifest) {
140
102
  const want = manifest.kizloVersion ?? `^${getVersion()}`;
141
103
  const have = kizloDep(pkg);
142
104
  if (!have || !isOlder(have, want)) return;
143
105
  const s = p.spinner();
144
106
  s.start(`Upgrading kizlo from ${have} to ${want}`);
145
- const ok = runCommand(addDependencyArgs(pm, `kizlo@${want}`), cwd, "ignore");
107
+ const ok = await runCommandAsync(addDependencyArgs(pm, `kizlo@${want}`), cwd, "ignore");
146
108
  s.stop(ok ? `Upgraded kizlo to ${want}` : `Could not upgrade kizlo automatically — install kizlo@${want} yourself`);
147
109
  }
148
110
  const init = defineCommand({
@@ -221,6 +183,10 @@ const init = defineCommand({
221
183
  });
222
184
  if (args.alias !== void 0) setup.alias = String(args.alias).trim();
223
185
  setup.alias = aliasWithSlash(setup.alias);
186
+ const includeStarter = preset.template ? yes || orCancel(await p.confirm({
187
+ message: "Add example pages?",
188
+ initialValue: true
189
+ })) : false;
224
190
  if (setup.mode === "local" && !await dockerAvailable()) {
225
191
  p.cancel("Docker isn't available — start Docker (or install it) and re-run, or choose “Use my own WordPress”.");
226
192
  process.exit(1);
@@ -229,7 +195,7 @@ const init = defineCommand({
229
195
  const spec = `kizlo@^${getVersion()}`;
230
196
  const s = p.spinner();
231
197
  s.start(`Installing kizlo with ${pm}`);
232
- const ok = runCommand(addDependencyArgs(pm, spec), cwd, "ignore");
198
+ const ok = await runCommandAsync(addDependencyArgs(pm, spec), cwd, "ignore");
233
199
  s.stop(ok ? "Installed kizlo" : "Could not install kizlo automatically");
234
200
  if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm, spec).join(" ")}`);
235
201
  }
@@ -243,15 +209,12 @@ const init = defineCommand({
243
209
  const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
244
210
  const serverDirRel = path.join(dirRel, "server");
245
211
  const clientUrl = setup.siteUrl && !sameOrigin(setup.siteUrl, setup.baseUrl) ? setup.baseUrl : void 0;
246
- const scaffold = {
247
- kizloDir: dirRel,
248
- serverDirName: path.basename(serverDirRel),
249
- serverEntryPath: path.join(serverDirRel, "index.ts"),
250
- clientPath: path.join(dirRel, "client.ts"),
212
+ const scaffold = buildScaffoldContext(cwd, {
213
+ dirRel,
251
214
  appDir: detectAppDir(cwd, hasSrcDir),
252
- serverImport: (fromDir) => resolveModuleImport(cwd, serverDirRel, fromDir, setup.alias),
215
+ alias: setup.alias,
253
216
  clientUrl
254
- };
217
+ });
255
218
  const files = [{
256
219
  label: "Kizlo config",
257
220
  relPath: "kizlo.config.ts",
@@ -262,8 +225,8 @@ const init = defineCommand({
262
225
  const fetched = await fetchTemplate(preset.template);
263
226
  try {
264
227
  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));
228
+ await alignKizloVersion(cwd, pm, pkg, manifest);
229
+ for (const entry of ownEntries(manifest, { includeStarter })) files.push(adaptOwnFile(fetched.dir, entry, manifest.tokens, scaffold));
267
230
  } finally {
268
231
  fetched.cleanup();
269
232
  }
@@ -276,42 +239,10 @@ const init = defineCommand({
276
239
  yes
277
240
  })
278
241
  });
279
- const generatedDirRel = path.join(serverDirRel, "generated");
280
- writeFileIfAbsent(path.join(cwd, generatedDirRel, "contract.json"), "{}\n");
281
- writeFileIfAbsent(path.join(cwd, generatedDirRel, "index.ts"), CONTRACT_BARREL);
242
+ writeGeneratedContract(cwd, serverDirRel);
282
243
  const gitignore = ensureGitignored(cwd, ".env");
283
244
  for (const { file, result } of scaffolded) reportScaffold(file, result, yes);
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`);
291
- continue;
292
- }
293
- const relTarget = path.relative(cwd, target);
294
- const src = fs.readFileSync(target, "utf8");
295
- let applied;
296
- try {
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;
302
- }
303
- const { text, changes } = applied;
304
- if (patchChanged(changes)) {
305
- fs.writeFileSync(target, text);
306
- const parts = [
307
- ...changes.replacedExports.length ? [`replaced ${changes.replacedExports.join(", ")}`] : [],
308
- ...changes.addedExports.length ? [`added ${changes.addedExports.join(", ")}`] : [],
309
- ...changes.addedImports.length ? ["imports"] : []
310
- ];
311
- p.log.success(`Wired Kizlo into your ${resolved.label} (${relTarget}) — ${parts.join(", ")}`);
312
- } else p.log.info(`Your ${resolved.label} is already wired (${relTarget})`);
313
- }
314
- for (const resolved of manualSteps) p.note(renderPatchCode(resolved), `Add these to your ${resolved.label} (the layout that renders <html>)`);
245
+ if (manifest) applyLayoutPatches(cwd, manifest, scaffold);
315
246
  if (gitignore !== "present") p.log.success(`${gitignore === "created" ? "Created" : "Updated"} .gitignore (ignoring .env)`);
316
247
  nextStepsNote(setup.mode);
317
248
  p.outro("Kizlo is ready 🎉");
@@ -5,15 +5,17 @@ import fs from "node:fs";
5
5
 
6
6
  //#region src/cli/presets/template.ts
7
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:
8
+ * The template's `template.json`. It is the single declaration of which files Kizlo lays onto a
9
+ * project and how each is applied — the same engine drives both `create` (onto a fresh app the
10
+ * framework CLI just bootstrapped) and `init` (onto the user's existing project). Every entry is one
11
+ * of a small closed set — deliberately no "patch an arbitrary source file" kind, so a dangerous
12
+ * strategy is unrepresentable:
11
13
  *
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.
14
+ * - `own` — a whole file Kizlo writes, adapted to the project's directories and written through the
15
+ * overwrite policy. `wiring`-scope files (the default) are laid down by both commands; `starter`
16
+ * demo files are laid down only by `create` (see {@link ownEntries}).
17
+ * - `patch` — a partial injection into a file the framework owns (the root layout's SEO exports).
18
+ * Both commands try a confident apply, otherwise print the payload with placement instructions.
17
19
  */
18
20
  const tokensSchema = z.object({
19
21
  kizloDir: z.string(),
@@ -23,7 +25,8 @@ const tokensSchema = z.object({
23
25
  const ownSchema = z.object({
24
26
  kind: z.literal("own"),
25
27
  role: z.string(),
26
- path: z.string()
28
+ path: z.string(),
29
+ scope: z.enum(["wiring", "starter"]).optional()
27
30
  });
28
31
  const patchSchema = z.object({
29
32
  kind: z.literal("patch"),
@@ -39,9 +42,14 @@ const patchSchema = z.object({
39
42
  value: z.string()
40
43
  }))
41
44
  });
45
+ const bootstrapSchema = z.object({
46
+ initializer: z.string(),
47
+ flags: z.array(z.string()).default([])
48
+ });
42
49
  const manifestSchema = z.object({
43
50
  framework: z.string(),
44
51
  kizloVersion: z.string().optional(),
52
+ bootstrap: bootstrapSchema.optional(),
45
53
  tokens: tokensSchema,
46
54
  files: z.array(z.discriminatedUnion("kind", [ownSchema, patchSchema]))
47
55
  });
@@ -53,7 +61,10 @@ const ROLE_LABELS = {
53
61
  robots: "robots.txt route",
54
62
  sitemap: "sitemap route",
55
63
  "sitemap-redirect": "sitemap.xml redirect route",
56
- manifest: "web manifest route"
64
+ manifest: "web manifest route",
65
+ "home-page": "home page",
66
+ "blog-post": "blog post page",
67
+ styles: "global styles"
57
68
  };
58
69
  /** Read and validate the template's manifest from a fetched template directory. */
59
70
  function readManifest(templateDir) {
@@ -61,8 +72,13 @@ function readManifest(templateDir) {
61
72
  if (!fs.existsSync(manifestPath)) throw new Error(`Template manifest not found at ${manifestPath}`);
62
73
  return manifestSchema.parse(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
63
74
  }
64
- function ownEntries(manifest) {
65
- return manifest.files.filter((file) => file.kind === "own");
75
+ /**
76
+ * The `own` files to scaffold. By default only `wiring` files (the Kizlo plumbing) — this is what
77
+ * `init` lays onto an existing project, so it never touches the user's own pages. With
78
+ * `includeStarter`, demo `starter` files are added too, which is what `create` lays onto a fresh app.
79
+ */
80
+ function ownEntries(manifest, opts = {}) {
81
+ return manifest.files.filter((file) => file.kind === "own" && (opts.includeStarter || file.scope !== "starter"));
66
82
  }
67
83
  function patchEntries(manifest) {
68
84
  return manifest.files.filter((file) => file.kind === "patch");
package/dist/cli/utils.js CHANGED
@@ -2,7 +2,7 @@ import { log } from "./daemon/logger.js";
2
2
  import { PortInUseError, resolveHostPort } from "./wp/ports.js";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { spawnSync } from "node:child_process";
5
+ import { spawn, spawnSync } from "node:child_process";
6
6
  import fs from "node:fs";
7
7
  import * as p from "@clack/prompts";
8
8
 
@@ -107,6 +107,31 @@ function detectPackageManager(cwd) {
107
107
  if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) return "bun";
108
108
  return "npm";
109
109
  }
110
+ /**
111
+ * The package manager that invoked the current process, read from `npm_config_user_agent`
112
+ * (e.g. `pnpm/9.0.0 npm/? node/v20`). Used to pre-select the most likely choice when a fresh
113
+ * project has no lockfile to detect from. Returns undefined when the agent is absent or unknown.
114
+ */
115
+ function detectInvokingPackageManager() {
116
+ const name = process.env.npm_config_user_agent?.split("/")[0];
117
+ return name === "pnpm" || name === "yarn" || name === "bun" || name === "npm" ? name : void 0;
118
+ }
119
+ /**
120
+ * Whether `command` resolves to a runnable executable, probed with `--version`. Used to hide or
121
+ * disable package-manager choices the user can't actually run. A non-zero exit or a spawn error
122
+ * (the binary isn't on PATH) both count as unavailable.
123
+ */
124
+ function isCommandAvailable(command) {
125
+ const result = spawnSync(command, ["--version"], {
126
+ stdio: "ignore",
127
+ shell: process.platform === "win32"
128
+ });
129
+ return !result.error && result.status === 0;
130
+ }
131
+ /** The package managers currently installed on the host, in the given display order. */
132
+ function availablePackageManagers(order) {
133
+ return order.filter((pm) => isCommandAvailable(pm));
134
+ }
110
135
  function addDependencyArgs(pm, pkg) {
111
136
  if (pm === "npm") return [
112
137
  "npm",
@@ -119,14 +144,80 @@ function addDependencyArgs(pm, pkg) {
119
144
  pkg
120
145
  ];
121
146
  }
122
- function runCommand(args, cwd, stdio = "inherit") {
147
+ /** The argv to install a project's dependencies with `pm` — every supported manager spells it `<pm> install`. */
148
+ function installArgs(pm) {
149
+ return [pm, "install"];
150
+ }
151
+ /**
152
+ * The argv to run a framework's `create-*` initializer through a package manager, e.g.
153
+ * `pnpm create next-app@latest my-app --ts …`. The mechanics differ per manager: npm forwards
154
+ * flags to the initializer only after a `--` separator, and `yarn create` resolves the latest
155
+ * version itself so the `@latest` tag is dropped. `flags` are the initializer's own options.
156
+ */
157
+ function frameworkCreateArgs(pm, initializer, name, flags) {
158
+ switch (pm) {
159
+ case "npm": return [
160
+ "npm",
161
+ "create",
162
+ initializer,
163
+ name,
164
+ "--",
165
+ ...flags
166
+ ];
167
+ case "yarn": return [
168
+ "yarn",
169
+ "create",
170
+ initializer.replace(/@latest$/, ""),
171
+ name,
172
+ ...flags
173
+ ];
174
+ default: return [
175
+ pm,
176
+ "create",
177
+ initializer,
178
+ name,
179
+ ...flags
180
+ ];
181
+ }
182
+ }
183
+ /**
184
+ * Async sibling of {@link runCommand}: runs a command without blocking the event loop, so a clack
185
+ * spinner can keep animating while it works (a synchronous `spawnSync` would freeze it on the first
186
+ * frame). Resolves whether it exited cleanly; a spawn error (binary off PATH) resolves false.
187
+ */
188
+ function runCommandAsync(args, cwd, stdio = "inherit") {
123
189
  const [command, ...rest] = args;
124
- if (!command) return false;
125
- return spawnSync(command, rest, {
190
+ if (!command) return Promise.resolve(false);
191
+ return new Promise((resolve$1) => {
192
+ const child = spawn(command, rest, {
193
+ cwd,
194
+ stdio,
195
+ shell: process.platform === "win32"
196
+ });
197
+ child.on("error", () => resolve$1(false));
198
+ child.on("close", (code) => resolve$1(code === 0));
199
+ });
200
+ }
201
+ /**
202
+ * Run a command capturing its combined output instead of streaming it, for a step that should stay
203
+ * quiet on success but whose logs are worth surfacing when it fails (e.g. the framework's scaffolder
204
+ * behind a spinner). Returns whether it succeeded and the trimmed stdout+stderr.
205
+ */
206
+ function runCommandCaptured(args, cwd) {
207
+ const [command, ...rest] = args;
208
+ if (!command) return {
209
+ ok: false,
210
+ output: ""
211
+ };
212
+ const result = spawnSync(command, rest, {
126
213
  cwd,
127
- stdio,
214
+ encoding: "utf8",
128
215
  shell: process.platform === "win32"
129
- }).status === 0;
216
+ });
217
+ return {
218
+ ok: result.status === 0,
219
+ output: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim()
220
+ };
130
221
  }
131
222
  function writeFileIfAbsent(filePath, contents) {
132
223
  if (fs.existsSync(filePath)) return false;
@@ -263,4 +354,4 @@ function groupDefault(subCommandNames, run) {
263
354
  }
264
355
 
265
356
  //#endregion
266
- export { addDependencyArgs, detectImportAlias, detectPackageManager, ensureGitignored, envGroups, envKeysPresent, getVersion, groupDefault, loadEnvFiles, mergeEnv, pickStackPort, resolveModuleImport, runCommand, withSpinner, writeFileIfAbsent };
357
+ export { addDependencyArgs, availablePackageManagers, detectImportAlias, detectInvokingPackageManager, detectPackageManager, ensureGitignored, envGroups, envKeysPresent, frameworkCreateArgs, getVersion, groupDefault, installArgs, loadEnvFiles, mergeEnv, pickStackPort, resolveModuleImport, runCommandAsync, runCommandCaptured, withSpinner, writeFileIfAbsent };
@@ -1,6 +1,6 @@
1
1
  import { WordPressService } from "../../wordpress/service.js";
2
2
  import { DEFAULT_PLUGINS, TEST_ADMIN } from "./constants.js";
3
- import { compose, composeUp, wpCli } from "./docker.js";
3
+ import { compose, composePull, composeUp, wpCli } from "./docker.js";
4
4
  import { ensurePlugins } from "./utils.js";
5
5
  import { createAdminAppPassword, seedUsers } from "./bootstrap.js";
6
6
  import { prepareByo } from "./byo.js";
@@ -74,7 +74,9 @@ async function seedDevFixtures(cfg, url) {
74
74
  */
75
75
  async function bootstrapDev(cfg) {
76
76
  const url = devUrl(cfg.port);
77
- const byo = !existsSync(join(cfg.wordpressDir, "wp-includes", "version.php")) && cfg.byo ? await prepareByo(cfg.byo, cfg.wordpressDir, cfg.configDir) : void 0;
77
+ const fresh = !existsSync(join(cfg.wordpressDir, "wp-includes", "version.php"));
78
+ const byo = fresh && cfg.byo ? await prepareByo(cfg.byo, cfg.wordpressDir, cfg.configDir) : void 0;
79
+ if (fresh && !byo) await composePull(["wordpress", "wp-cli"]).catch(() => void 0);
78
80
  await composeUp();
79
81
  const installed = (await compose([
80
82
  "exec",
@@ -22,6 +22,8 @@ interface Stack {
22
22
  /** Stack-bound docker helpers. */
23
23
  interface DockerStack {
24
24
  compose(args: string[], opts?: RunInput): Promise<RunResult>;
25
+ /** Pull the latest images for the named services (all when omitted) — refreshes a cached tag like `wordpress:latest`. */
26
+ composePull(services?: string[]): Promise<RunResult>;
25
27
  composeUp(): Promise<void>;
26
28
  composeStop(opts?: {
27
29
  detached?: boolean;
@@ -48,6 +50,8 @@ declare function dockerAvailable(): Promise<boolean>;
48
50
  declare function createStack(stack: Stack): DockerStack;
49
51
  /** `docker compose <args>` against the active stack. */
50
52
  declare const compose: DockerStack["compose"];
53
+ /** Pull the latest images for the active stack's named services (all when omitted). */
54
+ declare const composePull: (services?: string[]) => Promise<RunResult>;
51
55
  /** Bring the active stack's services up detached and wait for health checks. */
52
56
  declare const composeUp: () => Promise<void>;
53
57
  /** Tear the active stack down, optionally removing volumes. */
@@ -61,4 +65,4 @@ declare const composeStop: (opts?: {
61
65
  /** Run a wp-cli command against the active stack's warm `wp-cli` container. */
62
66
  declare const wpCli: (args: string[]) => Promise<string>;
63
67
  //#endregion
64
- export { DockerStack, RunResult, Stack, compose, composeDown, composeStop, composeUp, createStack, dockerAvailable, wpCli };
68
+ export { DockerStack, RunResult, Stack, compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli };
@@ -56,6 +56,7 @@ function bind(stack) {
56
56
  WP_PORT: String(stack.port)
57
57
  };
58
58
  const compose$1 = (args, opts) => run("docker", [...base, ...args], env, opts);
59
+ const composePull$1 = (services = []) => compose$1(["pull", ...services]);
59
60
  const composeUp$1 = async () => {
60
61
  const res = await compose$1([
61
62
  "up",
@@ -93,6 +94,7 @@ function bind(stack) {
93
94
  };
94
95
  return {
95
96
  compose: compose$1,
97
+ composePull: composePull$1,
96
98
  composeUp: composeUp$1,
97
99
  composeStop: composeStop$1,
98
100
  composeDown: composeDown$1,
@@ -117,6 +119,8 @@ function activeStack() {
117
119
  }
118
120
  /** `docker compose <args>` against the active stack. */
119
121
  const compose = (args, opts) => activeStack().compose(args, opts);
122
+ /** Pull the latest images for the active stack's named services (all when omitted). */
123
+ const composePull = (services) => activeStack().composePull(services);
120
124
  /** Bring the active stack's services up detached and wait for health checks. */
121
125
  const composeUp = () => activeStack().composeUp();
122
126
  /** Tear the active stack down, optionally removing volumes. */
@@ -127,4 +131,4 @@ const composeStop = (opts) => activeStack().composeStop(opts);
127
131
  const wpCli = (args) => activeStack().wpCli(args);
128
132
 
129
133
  //#endregion
130
- export { compose, composeDown, composeStop, composeUp, createStack, dockerAvailable, wpCli };
134
+ export { compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.8.2";
2
+ var version = "0.8.4";
3
3
 
4
4
  //#endregion
5
5
  export { version };
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?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | undefined;
182
+ orderby?: "date" | "id" | "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?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "modified" | "relevance" | "include_slugs" | "menu_order" | undefined;
510
+ orderby?: "date" | "id" | "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?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
1189
+ orderby?: "date" | "id" | "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?: "id" | "date" | "slug" | "parent" | "title" | "author" | "include" | "relevance" | "include_slugs" | "menu_order" | undefined;
1268
+ orderby?: "date" | "id" | "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?: "id" | "date" | "parent" | "post" | "include" | undefined;
1417
+ orderby?: "date" | "id" | "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/dist/test.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DevPluginSource, Fixture, FixtureCleanupFn, FixtureSeedFn, JsonValue, LocalPlugin, PluginSource, SeedContext, TestAdminUser, TestCredentials, TestUser, defineFixture, isLocalPlugin } from "./cli/wp/types.js";
2
2
  import { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease } from "./cli/wp/constants.js";
3
- import { DockerStack, RunResult, Stack, compose, composeDown, composeStop, composeUp, createStack, dockerAvailable, wpCli } from "./cli/wp/docker.js";
3
+ import { DockerStack, RunResult, Stack, compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli } from "./cli/wp/docker.js";
4
4
  import { activateLocalPlugin, credentialsPath, ensurePlugin, ensurePlugins, findConfigDir, getTestCredentials, isSeeded, recordedPort, resolvePluginSource } from "./cli/wp/utils.js";
5
5
  import { TestAuthAdapterUser, toTestUser } from "./test/users.js";
6
6
  import { testAuthAdapter } from "./test/auth.js";
@@ -10,4 +10,4 @@ import { KizloClientTestInstance, getKizloClientTestInstance } from "./test/clie
10
10
  import { menuFixture } from "./menu/fixture.js";
11
11
  import { postFixture } from "./post/fixture.js";
12
12
  import { coreFixtures } from "./test/fixtures.js";
13
- export { CAPTCHA_TEST_TOKEN_PASS, COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DevPluginSource, DockerStack, Fixture, FixtureCleanupFn, FixtureSeedFn, JsonValue, KizloClientTestInstance, KizloTestInstance, LocalPlugin, OPCACHE_INI, PluginSource, REMAP_ENTRYPOINT, RunResult, SEED_MARKER_OPTION, SEED_VERSION, SeedContext, Stack, TEST_ADMIN, TEST_USER, TestAdminUser, TestAuthAdapterUser, TestCredentials, TestUser, activateLocalPlugin, captchaMock, compose, composeDown, composeStop, composeUp, coreFixtures, createStack, credentialsPath, defineFixture, dockerAvailable, ensurePlugin, ensurePlugins, findConfigDir, getKizloClientTestInstance, getKizloTestInstance, getTestCredentials, githubRelease, isLocalPlugin, isSeeded, kizloRelease, menuFixture, postFixture, recordedPort, resolvePluginSource, testAuthAdapter, toTestUser, wpCli };
13
+ export { CAPTCHA_TEST_TOKEN_PASS, COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, DevPluginSource, DockerStack, Fixture, FixtureCleanupFn, FixtureSeedFn, JsonValue, KizloClientTestInstance, KizloTestInstance, LocalPlugin, OPCACHE_INI, PluginSource, REMAP_ENTRYPOINT, RunResult, SEED_MARKER_OPTION, SEED_VERSION, SeedContext, Stack, TEST_ADMIN, TEST_USER, TestAdminUser, TestAuthAdapterUser, TestCredentials, TestUser, activateLocalPlugin, captchaMock, compose, composeDown, composePull, composeStop, composeUp, coreFixtures, createStack, credentialsPath, defineFixture, dockerAvailable, ensurePlugin, ensurePlugins, findConfigDir, getKizloClientTestInstance, getKizloTestInstance, getTestCredentials, githubRelease, isLocalPlugin, isSeeded, kizloRelease, menuFixture, postFixture, recordedPort, resolvePluginSource, testAuthAdapter, toTestUser, wpCli };
package/dist/test.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineFixture, isLocalPlugin } from "./cli/wp/types.js";
2
2
  import { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease } from "./cli/wp/constants.js";
3
- import { compose, composeDown, composeStop, composeUp, createStack, dockerAvailable, wpCli } from "./cli/wp/docker.js";
3
+ import { compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli } from "./cli/wp/docker.js";
4
4
  import { activateLocalPlugin, credentialsPath, ensurePlugin, ensurePlugins, findConfigDir, getTestCredentials, isSeeded, recordedPort, resolvePluginSource } from "./cli/wp/utils.js";
5
5
  import { testAuthAdapter } from "./test/auth.js";
6
6
  import { CAPTCHA_TEST_TOKEN_PASS, captchaMock } from "./test/captcha.js";
@@ -11,4 +11,4 @@ import { coreFixtures } from "./test/fixtures.js";
11
11
  import { toTestUser } from "./test/users.js";
12
12
  import { getKizloTestInstance } from "./test/server.js";
13
13
 
14
- export { CAPTCHA_TEST_TOKEN_PASS, COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, activateLocalPlugin, captchaMock, compose, composeDown, composeStop, composeUp, coreFixtures, createStack, credentialsPath, defineFixture, dockerAvailable, ensurePlugin, ensurePlugins, findConfigDir, getKizloClientTestInstance, getKizloTestInstance, getTestCredentials, githubRelease, isLocalPlugin, isSeeded, kizloRelease, menuFixture, postFixture, recordedPort, resolvePluginSource, testAuthAdapter, toTestUser, wpCli };
14
+ export { CAPTCHA_TEST_TOKEN_PASS, COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, activateLocalPlugin, captchaMock, compose, composeDown, composePull, composeStop, composeUp, coreFixtures, createStack, credentialsPath, defineFixture, dockerAvailable, ensurePlugin, ensurePlugins, findConfigDir, getKizloClientTestInstance, getKizloTestInstance, getTestCredentials, githubRelease, isLocalPlugin, isSeeded, kizloRelease, menuFixture, postFixture, recordedPort, resolvePluginSource, testAuthAdapter, toTestUser, wpCli };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kizlo",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "type": "module",
5
5
  "description": "A headless WordPress framework for TypeScript.",
6
6
  "keywords": [