kizlo 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { createStack } from "../wp/docker.js";
1
+ import { createStack, dockerHint, dockerStatus } from "../wp/docker.js";
2
2
  import { isSeeded, recordedPort } from "../wp/utils.js";
3
3
  import { log } from "../daemon/logger.js";
4
4
  import { isFree } from "../wp/ports.js";
@@ -19,8 +19,8 @@ async function resolve(cwd) {
19
19
  };
20
20
  }
21
21
  /**
22
- * The host port to bring the test stack up on. Unlike the dev stack (rebuilt every run), the
23
- * test stack is reused and its tests connect via the URL recorded in the credentials artifact —
22
+ * The host port to bring the test WordPress up on. Unlike local dev WordPress (rebuilt every run), the
23
+ * test WordPress is reused and its tests connect via the URL recorded in the credentials artifact —
24
24
  * so the port can't just be re-chosen each time:
25
25
  *
26
26
  * - A **running** stack keeps its live published port (fast reruns stay on the same URL).
@@ -40,7 +40,7 @@ async function resolveTestPort(cfg, stack, fresh) {
40
40
  const recorded = recordedPort();
41
41
  if (recorded !== void 0) {
42
42
  if (await isFree(recorded)) return recorded;
43
- log.error(`The test stack's port ${recorded} (from a previous run) is now held by something else.\nFree that port, or run \`kizlo test reset\` to rebuild on a fresh one.`);
43
+ log.error(`The test WordPress port ${recorded} (from a previous run) is now held by something else.\nFree that port, or run \`kizlo test reset\` to rebuild on a fresh one.`);
44
44
  process.exit(1);
45
45
  }
46
46
  }
@@ -50,7 +50,7 @@ async function resolveTestPort(cfg, stack, fresh) {
50
50
  });
51
51
  }
52
52
  /**
53
- * Boot the test stack on a resolved host port and seed it once (idempotent — skips seeding when
53
+ * Boot the test WordPress on a resolved host port and seed it once (idempotent — skips seeding when
54
54
  * already seeded). Returns the port-bound stack and the port, so callers report the real URL.
55
55
  */
56
56
  async function bringUp(cfg, stack, fresh = false) {
@@ -59,8 +59,8 @@ async function bringUp(cfg, stack, fresh = false) {
59
59
  ...cfg,
60
60
  port
61
61
  }));
62
- await withSpinner("Starting WordPress test stack", () => bound.composeUp(), "WordPress test stack ready");
63
- if (await isSeeded()) log.info("Stack already seeded — skipping seed.");
62
+ await withSpinner("Starting test WordPress", () => bound.composeUp(), "Test WordPress ready");
63
+ if (await isSeeded()) log.info("Test WordPress already seeded — skipping seed.");
64
64
  else await withSpinner("Seeding WordPress", () => runSeeds({
65
65
  port,
66
66
  fixtures: cfg.fixtures
@@ -90,30 +90,45 @@ function spawnTest(command, pm) {
90
90
  child.on("close", (code) => resolvePromise(code ?? 0));
91
91
  });
92
92
  }
93
+ /** Log which test command is about to run, then spawn it and resolve with its exit code. */
94
+ function runProjectTests(cfg) {
95
+ log.info(cfg.command ? `Running: ${cfg.command}` : `Running: ${testCommand(cfg.packageManager).join(" ")}`);
96
+ return spawnTest(cfg.command, cfg.packageManager);
97
+ }
93
98
  const runArgs = {
94
99
  teardown: {
95
100
  type: "boolean",
96
- description: "Tear down the test stack after tests finish (default: leave it running for fast reruns)"
101
+ description: "Tear down the test WordPress after tests finish (default: leave it running for fast reruns)"
97
102
  },
98
103
  reset: {
99
104
  type: "boolean",
100
105
  description: "Wipe the database and reseed before running"
101
106
  }
102
107
  };
103
- /** Boot (+ seed) the stack, run the project's test script, then leave it up (or tear down). */
108
+ /**
109
+ * Run the project's tests. When `test.local` is set, boot (+ seed) local WordPress first, run the
110
+ * suite (JS then the plugin's PHPUnit) against it, then leave it up (or tear down). Without
111
+ * `test.local`, there's no Docker WordPress to manage — just run the project's own test script.
112
+ */
104
113
  async function runSuite({ args }) {
105
- const { cfg, stack: probe } = await resolve(process.cwd());
114
+ const cfg = await resolveTestConfig(process.cwd());
115
+ if (!cfg.local) process.exit(await runProjectTests(cfg));
116
+ const status = await dockerStatus();
117
+ if (status !== "running") {
118
+ log.error(dockerHint(status));
119
+ process.exit(1);
120
+ }
121
+ const probe = createStack(testStack(cfg));
106
122
  if (args.reset) await withSpinner("Wiping WordPress database", () => probe.composeDown({ volumes: true }), "Database wiped");
107
123
  const { stack } = await bringUp(cfg, probe, Boolean(args.reset));
108
- log.info(cfg.command ? `Running: ${cfg.command}` : `Running: ${testCommand(cfg.packageManager).join(" ")}`);
109
124
  let code = 1;
110
125
  try {
111
- const jsCode = await spawnTest(cfg.command, cfg.packageManager);
126
+ const jsCode = await runProjectTests(cfg);
112
127
  const phpCode = await runPluginPhpunit(cfg);
113
128
  code = jsCode !== 0 ? jsCode : phpCode;
114
129
  } finally {
115
- if (args.teardown) await withSpinner("Tearing down WordPress test stack", () => stack.composeDown(), "WordPress test stack stopped");
116
- else log.info("Stack left running for fast reruns — `kizlo test stop` to stop it, or rerun with --teardown.");
130
+ if (args.teardown) await withSpinner("Tearing down test WordPress", () => stack.composeDown(), "Test WordPress stopped");
131
+ else log.info("Test WordPress left running for fast reruns — `kizlo test stop` to stop it, or rerun with --teardown.");
117
132
  }
118
133
  process.exit(code);
119
134
  }
@@ -121,45 +136,30 @@ const subCommands = {
121
136
  stop: defineCommand({
122
137
  meta: {
123
138
  name: "stop",
124
- description: "Stop the WordPress test stack, keeping the database volume"
139
+ description: "Stop the test WordPress, keeping the database volume"
125
140
  },
126
141
  async run() {
127
142
  const { stack } = await resolve(process.cwd());
128
- await withSpinner("Stopping WordPress test stack", () => stack.composeStop(), "Stack stopped (volumes kept)");
129
- }
130
- }),
131
- down: defineCommand({
132
- meta: {
133
- name: "down",
134
- description: "Stop and remove the test stack containers"
135
- },
136
- args: { volumes: {
137
- type: "boolean",
138
- alias: "v",
139
- description: "Also drop the database volume"
140
- } },
141
- async run({ args }) {
142
- const { stack } = await resolve(process.cwd());
143
- await withSpinner("Removing WordPress test stack", () => stack.composeDown({ volumes: args.volumes }), args.volumes ? "Stack removed (volumes dropped)" : "Stack removed (volumes kept)");
143
+ await withSpinner("Stopping test WordPress", () => stack.composeStop(), "Test WordPress stopped (volumes kept)");
144
144
  }
145
145
  }),
146
146
  reset: defineCommand({
147
147
  meta: {
148
148
  name: "reset",
149
- description: "Wipe the database and reseed a fresh WordPress test stack"
149
+ description: "Wipe the database and reseed fresh test WordPress"
150
150
  },
151
151
  async run() {
152
152
  const { cfg, stack } = await resolve(process.cwd());
153
153
  await withSpinner("Wiping WordPress database", () => stack.composeDown({ volumes: true }), "Database wiped");
154
154
  const { port } = await bringUp(cfg, stack, true);
155
- log.success(`Stack ready on http://localhost:${port} — credentials at ${cfg.credentialsPath}`);
155
+ log.success(`Test WordPress ready on http://localhost:${port} — credentials at ${cfg.credentialsPath}`);
156
156
  }
157
157
  })
158
158
  };
159
159
  const test = defineCommand({
160
160
  meta: {
161
161
  name: "test",
162
- description: "Manage the WordPress test stack and run tests (stop | down | reset)"
162
+ description: "Run tests against local WordPress (stop | reset)"
163
163
  },
164
164
  args: runArgs,
165
165
  subCommands,
@@ -1,3 +1,4 @@
1
+ import { LOCAL_DIR_REL } from "../wp/constants.js";
1
2
  import { credentialsPath, findConfigDir } from "../wp/utils.js";
2
3
  import { log } from "./logger.js";
3
4
  import { detectPackageManager } from "../utils.js";
@@ -14,13 +15,13 @@ const configSchema = z.object({
14
15
  alias: z.string().optional(),
15
16
  name: z.string().optional(),
16
17
  dev: z.object({
17
- path: z.string().optional(),
18
+ local: z.boolean().optional(),
18
19
  port: z.number().int().positive().optional(),
19
20
  dbPort: z.number().int().positive().optional(),
20
- byo: z.string().optional(),
21
21
  fixtures: z.array(fixtureSchema).optional()
22
22
  }).optional(),
23
23
  test: z.object({
24
+ local: z.boolean().optional(),
24
25
  port: z.number().int().positive().optional(),
25
26
  fixtures: z.array(fixtureSchema).optional(),
26
27
  packageManager: z.enum([
@@ -108,6 +109,7 @@ async function resolveTestConfig(cwd) {
108
109
  const test = fileConfig?.test ?? {};
109
110
  return {
110
111
  configDir,
112
+ local: Boolean(test.local),
111
113
  project: `${resolveStackName(configDir, fileConfig?.name)}-test`,
112
114
  command: test.command,
113
115
  port: test.port ?? DEFAULT_TEST_PORT,
@@ -118,33 +120,26 @@ async function resolveTestConfig(cwd) {
118
120
  };
119
121
  }
120
122
  /**
121
- * Whether a local WordPress dev stack is configured (`dev.path` is set). When false, `kizlo dev`
122
- * has no stack to boot and runs the contract watcher alone — the path a bring-your-own-WordPress
123
- * project takes. Loads `kizlo.config.*` from the config dir; a missing config counts as no stack.
123
+ * Whether this project runs local WordPress under `kizlo dev` — `dev.local` is `true` in
124
+ * `kizlo.config.*`. When false, `kizlo dev` has nothing to boot and runs the contract watcher alone,
125
+ * the path a project pointing at its own WordPress takes. The flag is written by `create`/`init` when
126
+ * local WordPress is chosen, and lives next to the rest of the `dev` config, so it's committed and
127
+ * survives `kizlo dev reset`.
124
128
  */
125
- async function hasDevStack(cwd) {
129
+ async function usesLocalWordPress(cwd) {
126
130
  const fileConfig = await loadConfigFile(findConfigDir(cwd));
127
- return Boolean(fileConfig?.dev?.path);
131
+ return Boolean(fileConfig?.dev?.local);
128
132
  }
129
133
  /**
130
- * Resolve the `dev` block from `kizlo.config.*` into concrete values for the `dev`
131
- * command, applying defaults (port 8080). `dev.path` is required — the whole
132
- * WordPress install lives there, so we make the user choose a real folder rather
133
- * than hide it under a default. Callers gate on {@link hasDevStack} first, since a
134
- * project without a local stack runs the watcher alone rather than reaching here.
134
+ * Resolve the `dev` block from `kizlo.config.*` into concrete values for the `dev` command, applying
135
+ * defaults (port 8080). The install folder is fixed at `.kizlo/local` — no longer a config choice — so
136
+ * there's nothing required here. Callers gate on {@link usesLocalWordPress} first, since a project
137
+ * without local WordPress runs the watcher alone rather than reaching here.
135
138
  */
136
139
  async function resolveDevConfig(cwd) {
137
140
  const configDir = findConfigDir(cwd);
138
141
  const fileConfig = await loadConfigFile(configDir);
139
142
  const dev = fileConfig?.dev ?? {};
140
- if (!dev.path) {
141
- log.error("`dev.path` is required in kizlo.config — it's the folder your local WordPress lives in.\nPick a real, visible folder (it persists your site between runs), e.g. dev: { path: \"wordpress\" }.");
142
- process.exit(1);
143
- }
144
- if (dev.byo && dev.fixtures?.length) {
145
- log.error("`dev.byo` and `dev.fixtures` are mutually exclusive — byo imports an existing site, so it brings its own data.");
146
- process.exit(1);
147
- }
148
143
  return {
149
144
  configDir,
150
145
  project: `${resolveStackName(configDir, fileConfig?.name)}-dev`,
@@ -152,12 +147,11 @@ async function resolveDevConfig(cwd) {
152
147
  portExplicit: dev.port !== void 0,
153
148
  dbPort: dev.dbPort ?? DEFAULT_DEV_DB_PORT,
154
149
  dbPortExplicit: dev.dbPort !== void 0,
155
- byo: dev.byo ? path.resolve(configDir, dev.byo) : void 0,
156
150
  fixtures: dev.fixtures ?? [],
157
- wordpressPath: dev.path,
158
- wordpressDir: path.resolve(configDir, dev.path)
151
+ wordpressPath: LOCAL_DIR_REL,
152
+ wordpressDir: path.resolve(configDir, LOCAL_DIR_REL)
159
153
  };
160
154
  }
161
155
 
162
156
  //#endregion
163
- export { DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, hasDevStack, resolveConfig, resolveDevConfig, resolveStackName, resolveTestConfig };
157
+ export { DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveConfig, resolveDevConfig, resolveStackName, resolveTestConfig, usesLocalWordPress };
@@ -1,9 +1,11 @@
1
+ import { DEFAULT_ENV_KEYS } from "../utils.js";
2
+
1
3
  //#region src/cli/presets/base.ts
2
4
  const base = {
3
5
  id: "base",
4
6
  label: "Generic (no framework detected)",
5
7
  detect: () => 1,
6
- baseUrlEnvKey: "KIZLO_BACKEND_URL",
8
+ envKeys: DEFAULT_ENV_KEYS,
7
9
  scaffolds(ctx) {
8
10
  return [{
9
11
  label: "Kizlo server instance",
@@ -24,7 +26,7 @@ function clientEntry(ctx) {
24
26
  import { contract } from "./${ctx.serverDirName}/generated"
25
27
 
26
28
  // Your Kizlo backend runs on a different origin than this app. Swap in your bundler's
27
- // public env var (e.g. import.meta.env.VITE_KIZLO_BACKEND_URL) to set it per environment.
29
+ // public env var (e.g. import.meta.env.VITE_KIZLO_API_URL) to set it per environment.
28
30
  export const client = createKizloClient(contract, { url: "${ctx.clientUrl}" })
29
31
  `;
30
32
  return `import { createKizloClient } from "kizlo"
@@ -8,7 +8,6 @@ const nextjs = {
8
8
  ...ctx.pkg.devDependencies
9
9
  }.next ? 100 : 0;
10
10
  },
11
- baseUrlEnvKey: "NEXT_PUBLIC_KIZLO_BACKEND_URL",
12
11
  apiPath: "/api/kizlo",
13
12
  template: "nextjs"
14
13
  };
@@ -1,3 +1,4 @@
1
+ import { getVersion } from "../utils.js";
1
2
  import { resolvePatchTargetPath } from "./patch.js";
2
3
  import z from "zod/v4";
3
4
  import path from "node:path";
@@ -58,9 +59,36 @@ const bootstrapSchema = z.object({
58
59
  flags: z.array(z.string()).default([])
59
60
  });
60
61
  const changeSchema = z.discriminatedUnion("kind", [fileSchema, patchSchema]);
62
+ /**
63
+ * The `.env` key names the scaffold writes and the pinned runtime later reads — the one part of the
64
+ * wiring contract that can drift when the scaffold-time CLI and the pinned runtime disagree. Declared
65
+ * here as data so the scaffold writes whatever names the pinned runtime expects. Semantic slots only;
66
+ * the CLI still fills each value (generate a secret, provision Docker, prompt for a remote URL).
67
+ */
68
+ const envSchema = z.object({
69
+ baseUrl: z.string(),
70
+ remote: z.object({
71
+ siteSecret: z.string(),
72
+ wpUrl: z.string(),
73
+ wpUsername: z.string(),
74
+ wpPassword: z.string()
75
+ }),
76
+ local: z.object({
77
+ connect: z.string(),
78
+ siteSecret: z.string(),
79
+ wpUrl: z.string(),
80
+ wpUsername: z.string(),
81
+ wpPassword: z.string()
82
+ })
83
+ });
84
+ /** A package name → version-range map, package.json's own vocabulary. */
85
+ const depsSchema = z.record(z.string(), z.string());
61
86
  const manifestSchema = z.object({
62
87
  framework: z.string(),
63
- kizloVersion: z.string().optional(),
88
+ env: envSchema.optional(),
89
+ dependencies: depsSchema.optional(),
90
+ devDependencies: depsSchema.optional(),
91
+ minCli: z.string().optional(),
64
92
  bootstrap: bootstrapSchema.optional(),
65
93
  conventions: conventionsSchema,
66
94
  base: z.array(changeSchema).default([]),
@@ -88,6 +116,52 @@ function readManifest(templateDir) {
88
116
  return manifestSchema.parse(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
89
117
  }
90
118
  /**
119
+ * The dependencies a scaffolded project should pin, from the manifest. `kizlo` is always present —
120
+ * every project needs it — so it falls back to the running CLI's version in-repo before the first
121
+ * stamped release; any other declared package is taken verbatim (a template only lists packages it has
122
+ * a version for). Framework packages release in lockstep with `kizlo`, so a stamped manifest carries
123
+ * them all at the same released version.
124
+ */
125
+ function resolveDependencies(manifest) {
126
+ return {
127
+ dependencies: {
128
+ kizlo: `^${getVersion()}`,
129
+ ...manifest.dependencies
130
+ },
131
+ devDependencies: { ...manifest.devDependencies }
132
+ };
133
+ }
134
+ /** A bare `x.y.z` from a version spec (range prefix and pre-release/build metadata dropped); undefined if none. */
135
+ function coerceVersion(spec) {
136
+ const match = /(\d+)\.(\d+)\.(\d+)/.exec(spec);
137
+ return match ? [
138
+ Number(match[1]),
139
+ Number(match[2]),
140
+ Number(match[3])
141
+ ] : void 0;
142
+ }
143
+ /** Whether `have` is a strictly older release than `want`; false when either can't be compared. */
144
+ function isOlderVersion(have, want) {
145
+ const a = coerceVersion(have);
146
+ const b = coerceVersion(want);
147
+ if (!a || !b) return false;
148
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i];
149
+ return false;
150
+ }
151
+ /**
152
+ * The loud-failure backstop: an error message when the running CLI is too old to apply this manifest,
153
+ * or `undefined` when it's fine. Dormant by design — a manifest with no `minCli` never fails, so the
154
+ * check is inert until a structural change bumps the floor. The first CLI-incompatible manifest change
155
+ * sets `minCli`; from then on older CLIs (that already ship this check) stop with a clear message
156
+ * instead of silently under-wiring the project.
157
+ */
158
+ function minCliError(manifest) {
159
+ if (!manifest.minCli) return void 0;
160
+ const running = getVersion();
161
+ if (!isOlderVersion(running, manifest.minCli)) return void 0;
162
+ return `This template needs the kizlo CLI >= ${manifest.minCli} (you have ${running}). Run \`npx kizlo@latest\` and try again.`;
163
+ }
164
+ /**
91
165
  * The changes a command applies: the shared `base` set plus the command's own section. `create` gets
92
166
  * `base + create` (plumbing plus the demo pages); `init` gets `base + init` (plumbing plus any
93
167
  * init-only surgical patches). `base` is applied first so a later section can override it.
@@ -193,4 +267,4 @@ function rendersHtml(file) {
193
267
  }
194
268
 
195
269
  //#endregion
196
- export { adaptFile, changesFor, fileEntries, findRootLayout, isExample, patchEntries, readManifest, resolvePatch };
270
+ export { adaptFile, changesFor, fileEntries, findRootLayout, isExample, isOlderVersion, minCliError, patchEntries, readManifest, resolveDependencies, resolvePatch };
package/dist/cli/utils.js CHANGED
@@ -229,37 +229,59 @@ function envKeysPresent(contents, keys) {
229
229
  return keys.filter((key) => new RegExp(`^\\s*${key}\\s*=`, "m").test(contents));
230
230
  }
231
231
  /**
232
+ * The built-in key names, used by the generic `base` preset (no template, no separately-pinned
233
+ * runtime) and as the fallback when a template's manifest declares no `env` section (in-repo before the
234
+ * first stamped release). Real templates carry their own `env`, so this is the standard `KIZLO_*` set.
235
+ */
236
+ const DEFAULT_ENV_KEYS = {
237
+ baseUrl: "KIZLO_API_URL",
238
+ remote: {
239
+ siteSecret: "KIZLO_WP_SECRET",
240
+ wpUrl: "KIZLO_WP_URL",
241
+ wpUsername: "KIZLO_WP_USERNAME",
242
+ wpPassword: "KIZLO_WP_APP_PASSWORD"
243
+ },
244
+ local: {
245
+ connect: "KIZLO_CONNECT",
246
+ siteSecret: "KIZLO_LOCAL_WP_SECRET",
247
+ wpUrl: "KIZLO_LOCAL_WP_URL",
248
+ wpUsername: "KIZLO_LOCAL_WP_USERNAME",
249
+ wpPassword: "KIZLO_LOCAL_WP_APP_PASSWORD"
250
+ }
251
+ };
252
+ /**
232
253
  * The grouped `.env` layout shared by `init` and `dev`, so both write the same sectioned file:
233
- * a target switch, the backend URL, then the production and dev connection blocks. `baseUrlEnvKey`
234
- * varies per preset (e.g. `NEXT_PUBLIC_KIZLO_BACKEND_URL`), so it's threaded in. Only sections whose
235
- * keys are actually being appended get a header, so a remote/prod run skips the dev block and vice versa.
254
+ * a connect switch, the API URL, then the remote and local connection blocks. The key names come from
255
+ * the resolved {@link EnvKeys} (the template's `env` contract), so the sections stay labelled correctly
256
+ * whatever a template calls them. Only sections whose keys are actually being appended get a header, so
257
+ * a remote run skips the local block and vice versa.
236
258
  */
237
- function envGroups(baseUrlEnvKey) {
259
+ function envGroups(envKeys) {
238
260
  return [
239
261
  {
240
- comment: "Kizlo Target (dev | prod)",
241
- keys: ["KIZLO_TARGET"]
262
+ comment: "Kizlo Connect (local | remote)",
263
+ keys: [envKeys.local.connect]
242
264
  },
243
265
  {
244
- comment: "Kizlo Backend URL",
245
- keys: [baseUrlEnvKey]
266
+ comment: "Kizlo API URL",
267
+ keys: [envKeys.baseUrl]
246
268
  },
247
269
  {
248
- comment: "Kizlo Production Env",
270
+ comment: "Kizlo Remote",
249
271
  keys: [
250
- "KIZLO_SITE_SECRET",
251
- "KIZLO_WORDPRESS_URL",
252
- "KIZLO_WORDPRESS_USERNAME",
253
- "KIZLO_WORDPRESS_APPLICATION_PASSWORD"
272
+ envKeys.remote.siteSecret,
273
+ envKeys.remote.wpUrl,
274
+ envKeys.remote.wpUsername,
275
+ envKeys.remote.wpPassword
254
276
  ]
255
277
  },
256
278
  {
257
- comment: "Kizlo Development Env",
279
+ comment: "Kizlo Local",
258
280
  keys: [
259
- "KIZLO_DEV_SITE_SECRET",
260
- "KIZLO_DEV_WORDPRESS_URL",
261
- "KIZLO_DEV_WORDPRESS_USERNAME",
262
- "KIZLO_DEV_WORDPRESS_APPLICATION_PASSWORD"
281
+ envKeys.local.siteSecret,
282
+ envKeys.local.wpUrl,
283
+ envKeys.local.wpUsername,
284
+ envKeys.local.wpPassword
263
285
  ]
264
286
  }
265
287
  ];
@@ -354,4 +376,4 @@ function groupDefault(subCommandNames, run) {
354
376
  }
355
377
 
356
378
  //#endregion
357
- export { addDependencyArgs, availablePackageManagers, detectImportAlias, detectInvokingPackageManager, detectPackageManager, ensureGitignored, envGroups, envKeysPresent, frameworkCreateArgs, getVersion, groupDefault, installArgs, loadEnvFiles, mergeEnv, pickStackPort, resolveModuleImport, runCommandAsync, runCommandCaptured, withSpinner, writeFileIfAbsent };
379
+ export { DEFAULT_ENV_KEYS, addDependencyArgs, availablePackageManagers, detectImportAlias, detectInvokingPackageManager, detectPackageManager, ensureGitignored, envGroups, envKeysPresent, frameworkCreateArgs, getVersion, groupDefault, installArgs, loadEnvFiles, mergeEnv, pickStackPort, resolveModuleImport, runCommandAsync, runCommandCaptured, withSpinner, writeFileIfAbsent };
@@ -3,10 +3,10 @@ import { basename, dirname, isAbsolute, resolve } from "node:path";
3
3
  import { mkdirSync, writeFileSync } from "node:fs";
4
4
 
5
5
  //#region src/cli/wp/compose/override.ts
6
- /** Generated dev override location, relative to the config root. */
7
- const OVERRIDE_REL = ".kizlo/docker-compose.dev.override.yml";
6
+ /** Generated local WordPress override location, relative to the config root. */
7
+ const OVERRIDE_REL = ".kizlo/compose.local.yml";
8
8
  /** Generated test override location (plugin mounts only), relative to the config root. */
9
- const TEST_OVERRIDE_REL = ".kizlo/docker-compose.test.override.yml";
9
+ const TEST_OVERRIDE_REL = ".kizlo/compose.test.yml";
10
10
  const WP_ROOT = "/var/www/html";
11
11
  const PLUGINS_DIR = `${WP_ROOT}/wp-content/plugins`;
12
12
  /** Where the dev OPcache config is dropped so the container's PHP picks it up. */
@@ -20,7 +20,7 @@ describe("writeDevOverride", () => {
20
20
 
21
21
  test("bind-mounts the whole install on both services and creates the host dir", () => {
22
22
  const file = writeDevOverride(dir, { wordpressDir, mounts: [] })
23
- expect(file).toBe(path.join(dir, ".kizlo", "docker-compose.dev.override.yml"))
23
+ expect(file).toBe(path.join(dir, ".kizlo", "compose.local.yml"))
24
24
  expect(fs.existsSync(wordpressDir)).toBe(true)
25
25
 
26
26
  const body = fs.readFileSync(file, "utf8")
@@ -89,7 +89,7 @@ describe("writeTestOverride", () => {
89
89
 
90
90
  test("mounts local plugin dirs by basename on both services", () => {
91
91
  const file = writeTestOverride(dir, ["plugins/kizlo-woocommerce"])
92
- expect(file).toBe(path.join(dir, ".kizlo", "docker-compose.test.override.yml"))
92
+ expect(file).toBe(path.join(dir, ".kizlo", "compose.test.yml"))
93
93
 
94
94
  const body = fs.readFileSync(file, "utf8")
95
95
  const pluginBind = `- "${path.join(dir, "plugins/kizlo-woocommerce")}:/var/www/html/wp-content/plugins/kizlo-woocommerce"`
@@ -2,10 +2,10 @@ import { mkdirSync, writeFileSync } from "node:fs"
2
2
  import { basename, dirname, isAbsolute, resolve } from "node:path"
3
3
  import { OPCACHE_INI, REMAP_ENTRYPOINT } from "../constants"
4
4
 
5
- /** Generated dev override location, relative to the config root. */
6
- const OVERRIDE_REL = ".kizlo/docker-compose.dev.override.yml"
5
+ /** Generated local WordPress override location, relative to the config root. */
6
+ const OVERRIDE_REL = ".kizlo/compose.local.yml"
7
7
  /** Generated test override location (plugin mounts only), relative to the config root. */
8
- const TEST_OVERRIDE_REL = ".kizlo/docker-compose.test.override.yml"
8
+ const TEST_OVERRIDE_REL = ".kizlo/compose.test.yml"
9
9
 
10
10
  const WP_ROOT = "/var/www/html"
11
11
  const PLUGINS_DIR = `${WP_ROOT}/wp-content/plugins`
@@ -12,13 +12,19 @@ declare function githubRelease(repo: string, tag: string): string;
12
12
  declare function kizloRelease(slug: string): string;
13
13
  /** The docker-compose file shipped alongside this module (in both `src/` and `dist/`). */
14
14
  declare const COMPOSE_FILE: string;
15
- /** Dev PHP OPcache config, shipped alongside this module and bind-mounted into the dev stack. */
15
+ /** PHP OPcache config, shipped alongside this module and bind-mounted into the local WordPress. */
16
16
  declare const OPCACHE_INI: string;
17
17
  /** Linux-only entrypoint that retags www-data to the host user (see the script for why). */
18
18
  declare const REMAP_ENTRYPOINT: string;
19
19
  declare const CONFIG_FILES: string[];
20
- /** Credentials artifact location, relative to the config root. */
21
- declare const CREDENTIALS_REL = ".kizlo/test-credentials.json";
20
+ /** Test connection artifact location, relative to the config root. */
21
+ declare const CREDENTIALS_REL = ".kizlo/test.json";
22
+ /**
23
+ * Fixed folder the local WordPress install lives in, relative to the config root. The whole install
24
+ * (core, themes, uploads, plugins) is bind-mounted here; `kizlo dev reset` wipes it. It sits under
25
+ * `.kizlo/` — the single gitignored working dir — so there's no folder to choose or configure.
26
+ */
27
+ declare const LOCAL_DIR_REL = ".kizlo/local";
22
28
  /**
23
29
  * DB-side marker written as the final bootstrap step and checked by `isSeeded`.
24
30
  * It records that this database holds a completed bootstrap — independent of any
@@ -32,4 +38,4 @@ declare const DEFAULT_PLUGINS: PluginSource[];
32
38
  declare const TEST_ADMIN: Omit<TestAdminUser, "id" | "applicationPassword">;
33
39
  declare const TEST_USER: Omit<TestUser, "id">;
34
40
  //#endregion
35
- export { 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 };
41
+ export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease };
@@ -18,7 +18,7 @@ function kizloRelease(slug) {
18
18
  const HERE = dirname(fileURLToPath(import.meta.url));
19
19
  /** The docker-compose file shipped alongside this module (in both `src/` and `dist/`). */
20
20
  const COMPOSE_FILE = resolve(HERE, "compose/docker-compose.yml");
21
- /** Dev PHP OPcache config, shipped alongside this module and bind-mounted into the dev stack. */
21
+ /** PHP OPcache config, shipped alongside this module and bind-mounted into the local WordPress. */
22
22
  const OPCACHE_INI = resolve(HERE, "compose/opcache.ini");
23
23
  /** Linux-only entrypoint that retags www-data to the host user (see the script for why). */
24
24
  const REMAP_ENTRYPOINT = resolve(HERE, "compose/remap-entrypoint.sh");
@@ -27,8 +27,14 @@ const CONFIG_FILES = [
27
27
  "kizlo.config.js",
28
28
  "kizlo.config.mjs"
29
29
  ];
30
- /** Credentials artifact location, relative to the config root. */
31
- const CREDENTIALS_REL = ".kizlo/test-credentials.json";
30
+ /** Test connection artifact location, relative to the config root. */
31
+ const CREDENTIALS_REL = ".kizlo/test.json";
32
+ /**
33
+ * Fixed folder the local WordPress install lives in, relative to the config root. The whole install
34
+ * (core, themes, uploads, plugins) is bind-mounted here; `kizlo dev reset` wipes it. It sits under
35
+ * `.kizlo/` — the single gitignored working dir — so there's no folder to choose or configure.
36
+ */
37
+ const LOCAL_DIR_REL = ".kizlo/local";
32
38
  /**
33
39
  * DB-side marker written as the final bootstrap step and checked by `isSeeded`.
34
40
  * It records that this database holds a completed bootstrap — independent of any
@@ -59,4 +65,4 @@ const TEST_USER = {
59
65
  };
60
66
 
61
67
  //#endregion
62
- export { 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 };
68
+ export { COMPOSE_FILE, CONFIG_FILES, CREDENTIALS_REL, DEFAULT_PLUGINS, DEFAULT_PORT, LOCAL_DIR_REL, OPCACHE_INI, REMAP_ENTRYPOINT, SEED_MARKER_OPTION, SEED_VERSION, TEST_ADMIN, TEST_USER, githubRelease, kizloRelease };