kizlo 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli/commands/_setup.js +132 -79
- package/dist/cli/commands/_wiring.js +5 -4
- package/dist/cli/commands/create.js +35 -28
- package/dist/cli/commands/dev.js +172 -87
- package/dist/cli/commands/init.js +97 -103
- package/dist/cli/commands/test.js +34 -34
- package/dist/cli/daemon/config.js +18 -24
- package/dist/cli/daemon/logger.js +1 -1
- package/dist/cli/daemon/reload.js +66 -0
- package/dist/cli/daemon/watch.js +0 -1
- package/dist/cli/presets/base.js +4 -2
- package/dist/cli/presets/nextjs.js +0 -1
- package/dist/cli/presets/template.js +104 -18
- package/dist/cli/utils.js +41 -19
- package/dist/cli/wp/compose/override.js +3 -3
- package/dist/cli/wp/compose/override.test.ts +2 -2
- package/dist/cli/wp/compose/override.ts +3 -3
- package/dist/cli/wp/constants.d.ts +10 -4
- package/dist/cli/wp/constants.js +10 -4
- package/dist/cli/wp/dev.js +13 -58
- package/dist/cli/wp/docker.d.ts +16 -4
- package/dist/cli/wp/docker.js +20 -7
- package/dist/cli/wp/settings.js +9 -3
- package/dist/cli/wp/setup.js +1 -1
- package/dist/cli/wp/stack.js +4 -4
- package/dist/comment/schema.js +2 -2
- package/dist/config.d.ts +20 -23
- package/dist/context.d.ts +9 -0
- package/dist/context.js +20 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/integrations/nextjs/kizlo.server.js +1 -1
- package/dist/integrations/nextjs/utils.js +2 -2
- package/dist/kizlo.d.ts +16 -16
- package/dist/kizlo.js +19 -15
- package/dist/package.js +1 -1
- package/dist/page/schema.js +2 -2
- package/dist/post/schema.js +2 -2
- package/dist/router.d.ts +9 -9
- package/dist/shared/schema.d.ts +1 -2
- package/dist/shared/schema.js +2 -2
- package/dist/test/server.js +1 -1
- package/dist/test.d.ts +3 -3
- package/dist/test.js +3 -3
- package/dist/wordpress/service.d.ts +4 -0
- package/dist/wordpress/service.js +5 -0
- package/package.json +2 -2
- package/dist/cli/wp/byo.js +0 -113
|
@@ -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";
|
|
@@ -7,17 +8,25 @@ import fs from "node:fs";
|
|
|
7
8
|
/**
|
|
8
9
|
* The template's `template.json`. It is the single declaration of what Kizlo lays onto a project and
|
|
9
10
|
* how — the same engine drives both `create` (onto a fresh app the framework CLI just bootstrapped)
|
|
10
|
-
* and `init` (onto the user's existing project). Changes are grouped into three sections
|
|
11
|
-
*
|
|
11
|
+
* and `init` (onto the user's existing project). Changes are grouped into three sections by the one
|
|
12
|
+
* question that decides whether a command may safely apply them — *where is this safe to land?* — and
|
|
13
|
+
* no command ever reads another command's section:
|
|
12
14
|
*
|
|
13
|
-
* - `base` —
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* - `create` —
|
|
17
|
-
* the root layout (written whole,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* - `base` — safe on any project: additive, Kizlo-owned paths that never collide with the user's own
|
|
16
|
+
* files (the server entry, client, routes, and additive demo pages like a new blog route). Applied
|
|
17
|
+
* by both commands.
|
|
18
|
+
* - `create` — safe only on a freshly bootstrapped app `create` may freely overwrite: the files the
|
|
19
|
+
* framework CLI already scaffolded, like the root layout (written whole, SEO-wired) and the homepage
|
|
20
|
+
* showcase. Applied only by `create`, never `init` — overwriting these on a real project would
|
|
21
|
+
* clobber the user's work.
|
|
22
|
+
* - `init` — the non-destructive counterpart for the user's existing project: the root-layout SEO
|
|
23
|
+
* wiring lands here as a `patch` that merges into the layout the user already owns.
|
|
24
|
+
*
|
|
25
|
+
* Cutting across all three is the orthogonal `example` flag: opt-in demo content, applied only when the
|
|
26
|
+
* user answers yes to "Add example pages?". A cross-cutting example (e.g. a blog) files each of its
|
|
27
|
+
* pieces in the section that matches its safety — the additive new route in `base`, the homepage
|
|
28
|
+
* showcase that overwrites an app-owned file in `create` — so `init` picks up only the additive half and
|
|
29
|
+
* never touches a file the user owns, while `create` gets the whole demo. See {@link changesFor}.
|
|
21
30
|
*
|
|
22
31
|
* Each section is a list of changes, every one a member of a small closed set — deliberately no
|
|
23
32
|
* "patch an arbitrary source file" kind, so a dangerous strategy is unrepresentable:
|
|
@@ -58,9 +67,36 @@ const bootstrapSchema = z.object({
|
|
|
58
67
|
flags: z.array(z.string()).default([])
|
|
59
68
|
});
|
|
60
69
|
const changeSchema = z.discriminatedUnion("kind", [fileSchema, patchSchema]);
|
|
70
|
+
/**
|
|
71
|
+
* The `.env` key names the scaffold writes and the pinned runtime later reads — the one part of the
|
|
72
|
+
* wiring contract that can drift when the scaffold-time CLI and the pinned runtime disagree. Declared
|
|
73
|
+
* here as data so the scaffold writes whatever names the pinned runtime expects. Semantic slots only;
|
|
74
|
+
* the CLI still fills each value (generate a secret, provision Docker, prompt for a remote URL).
|
|
75
|
+
*/
|
|
76
|
+
const envSchema = z.object({
|
|
77
|
+
baseUrl: z.string(),
|
|
78
|
+
remote: z.object({
|
|
79
|
+
siteSecret: z.string(),
|
|
80
|
+
wpUrl: z.string(),
|
|
81
|
+
wpUsername: z.string(),
|
|
82
|
+
wpPassword: z.string()
|
|
83
|
+
}),
|
|
84
|
+
local: z.object({
|
|
85
|
+
connect: z.string(),
|
|
86
|
+
siteSecret: z.string(),
|
|
87
|
+
wpUrl: z.string(),
|
|
88
|
+
wpUsername: z.string(),
|
|
89
|
+
wpPassword: z.string()
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
/** A package name → version-range map, package.json's own vocabulary. */
|
|
93
|
+
const depsSchema = z.record(z.string(), z.string());
|
|
61
94
|
const manifestSchema = z.object({
|
|
62
95
|
framework: z.string(),
|
|
63
|
-
|
|
96
|
+
env: envSchema.optional(),
|
|
97
|
+
dependencies: depsSchema.optional(),
|
|
98
|
+
devDependencies: depsSchema.optional(),
|
|
99
|
+
minCli: z.string().optional(),
|
|
64
100
|
bootstrap: bootstrapSchema.optional(),
|
|
65
101
|
conventions: conventionsSchema,
|
|
66
102
|
base: z.array(changeSchema).default([]),
|
|
@@ -88,12 +124,62 @@ function readManifest(templateDir) {
|
|
|
88
124
|
return manifestSchema.parse(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
89
125
|
}
|
|
90
126
|
/**
|
|
91
|
-
* The
|
|
92
|
-
*
|
|
93
|
-
*
|
|
127
|
+
* The dependencies a scaffolded project should pin, from the manifest. `kizlo` is always present —
|
|
128
|
+
* every project needs it — so it falls back to the running CLI's version in-repo before the first
|
|
129
|
+
* stamped release; any other declared package is taken verbatim (a template only lists packages it has
|
|
130
|
+
* a version for). Framework packages release in lockstep with `kizlo`, so a stamped manifest carries
|
|
131
|
+
* them all at the same released version.
|
|
132
|
+
*/
|
|
133
|
+
function resolveDependencies(manifest) {
|
|
134
|
+
return {
|
|
135
|
+
dependencies: {
|
|
136
|
+
kizlo: `^${getVersion()}`,
|
|
137
|
+
...manifest.dependencies
|
|
138
|
+
},
|
|
139
|
+
devDependencies: { ...manifest.devDependencies }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** A bare `x.y.z` from a version spec (range prefix and pre-release/build metadata dropped); undefined if none. */
|
|
143
|
+
function coerceVersion(spec) {
|
|
144
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(spec);
|
|
145
|
+
return match ? [
|
|
146
|
+
Number(match[1]),
|
|
147
|
+
Number(match[2]),
|
|
148
|
+
Number(match[3])
|
|
149
|
+
] : void 0;
|
|
150
|
+
}
|
|
151
|
+
/** Whether `have` is a strictly older release than `want`; false when either can't be compared. */
|
|
152
|
+
function isOlderVersion(have, want) {
|
|
153
|
+
const a = coerceVersion(have);
|
|
154
|
+
const b = coerceVersion(want);
|
|
155
|
+
if (!a || !b) return false;
|
|
156
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i];
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The loud-failure backstop: an error message when the running CLI is too old to apply this manifest,
|
|
161
|
+
* or `undefined` when it's fine. Dormant by design — a manifest with no `minCli` never fails, so the
|
|
162
|
+
* check is inert until a structural change bumps the floor. The first CLI-incompatible manifest change
|
|
163
|
+
* sets `minCli`; from then on older CLIs (that already ship this check) stop with a clear message
|
|
164
|
+
* instead of silently under-wiring the project.
|
|
165
|
+
*/
|
|
166
|
+
function minCliError(manifest) {
|
|
167
|
+
if (!manifest.minCli) return void 0;
|
|
168
|
+
const running = getVersion();
|
|
169
|
+
if (!isOlderVersion(running, manifest.minCli)) return void 0;
|
|
170
|
+
return `This template needs the kizlo CLI >= ${manifest.minCli} (you have ${running}). Run \`npx kizlo@latest\` and try again.`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The changes a command applies: the shared `base` set plus the command's own section (`base` first, so
|
|
174
|
+
* a later section can override it). Section placement is the *safety* axis — `base` is additive and safe
|
|
175
|
+
* on any project, `create`/`init` carry the fresh-app-vs-existing-project halves — while the `example`
|
|
176
|
+
* flag is the orthogonal *opt-in* axis. Example changes are dropped unless `includeExamples`, so the same
|
|
177
|
+
* call serves both "core wiring only" and "plus the demo pages", and each command only ever sees its own
|
|
178
|
+
* section: init's opt-in examples are the additive ones in `base`, never create's homepage overwrite.
|
|
94
179
|
*/
|
|
95
|
-
function changesFor(manifest, command) {
|
|
96
|
-
|
|
180
|
+
function changesFor(manifest, command, opts = {}) {
|
|
181
|
+
const changes = [...manifest.base, ...manifest[command]];
|
|
182
|
+
return opts.includeExamples ? changes : changes.filter((change) => !isExample(change));
|
|
97
183
|
}
|
|
98
184
|
/** The whole-file changes in a resolved change list. */
|
|
99
185
|
function fileEntries(changes) {
|
|
@@ -103,7 +189,7 @@ function fileEntries(changes) {
|
|
|
103
189
|
function patchEntries(changes) {
|
|
104
190
|
return changes.filter((change) => change.kind === "patch");
|
|
105
191
|
}
|
|
106
|
-
/** Whether a change is opt-in demo content —
|
|
192
|
+
/** Whether a change is opt-in demo content — applied only when example pages are requested. */
|
|
107
193
|
function isExample(change) {
|
|
108
194
|
return change.kind === "file" && change.example === true;
|
|
109
195
|
}
|
|
@@ -193,4 +279,4 @@ function rendersHtml(file) {
|
|
|
193
279
|
}
|
|
194
280
|
|
|
195
281
|
//#endregion
|
|
196
|
-
export { adaptFile, changesFor, fileEntries, findRootLayout,
|
|
282
|
+
export { adaptFile, changesFor, fileEntries, findRootLayout, 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
|
|
234
|
-
*
|
|
235
|
-
* keys are actually being appended get a header, so
|
|
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(
|
|
259
|
+
function envGroups(envKeys) {
|
|
238
260
|
return [
|
|
239
261
|
{
|
|
240
|
-
comment: "Kizlo
|
|
241
|
-
keys: [
|
|
262
|
+
comment: "Kizlo Connect (local | remote)",
|
|
263
|
+
keys: [envKeys.local.connect]
|
|
242
264
|
},
|
|
243
265
|
{
|
|
244
|
-
comment: "Kizlo
|
|
245
|
-
keys: [
|
|
266
|
+
comment: "Kizlo API URL",
|
|
267
|
+
keys: [envKeys.baseUrl]
|
|
246
268
|
},
|
|
247
269
|
{
|
|
248
|
-
comment: "Kizlo
|
|
270
|
+
comment: "Kizlo Remote",
|
|
249
271
|
keys: [
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
279
|
+
comment: "Kizlo Local",
|
|
258
280
|
keys: [
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
7
|
-
const OVERRIDE_REL = ".kizlo/
|
|
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/
|
|
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", "
|
|
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", "
|
|
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
|
|
6
|
-
const OVERRIDE_REL = ".kizlo/
|
|
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/
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
21
|
-
declare const CREDENTIALS_REL = ".kizlo/test
|
|
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 };
|
package/dist/cli/wp/constants.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
31
|
-
const CREDENTIALS_REL = ".kizlo/test
|
|
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 };
|
package/dist/cli/wp/dev.js
CHANGED
|
@@ -3,9 +3,8 @@ import { DEFAULT_PLUGINS, TEST_ADMIN } from "./constants.js";
|
|
|
3
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
|
-
import { prepareByo } from "./byo.js";
|
|
7
6
|
import { join } from "node:path";
|
|
8
|
-
import { existsSync
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
9
8
|
import { networkInterfaces } from "node:os";
|
|
10
9
|
import { randomBytes } from "node:crypto";
|
|
11
10
|
|
|
@@ -19,7 +18,7 @@ function lanAddress() {
|
|
|
19
18
|
for (const ifaces of Object.values(networkInterfaces())) for (const iface of ifaces ?? []) if (iface.family === "IPv4" && !iface.internal) return iface.address;
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
* The URL
|
|
21
|
+
* The URL local WordPress is provisioned at. We prefer the router-assigned LAN address over `localhost`
|
|
23
22
|
* so it's reachable from off the host (the app in a container or on another device). Serving WordPress
|
|
24
23
|
* under the request host is already handled by the container's `WORDPRESS_CONFIG_EXTRA` (it derives
|
|
25
24
|
* `WP_HOME`/`WP_SITEURL` from `HTTP_HOST` per request), so this is only the address we write into `.env`
|
|
@@ -29,7 +28,7 @@ function devUrl(port) {
|
|
|
29
28
|
return `http://${lanAddress() ?? "localhost"}:${port}`;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
|
-
* Seed `dev.fixtures` into
|
|
31
|
+
* Seed `dev.fixtures` into freshly installed local WordPress, reusing the test seeding
|
|
33
32
|
* primitives: seed the default subscriber so `ctx.userId` exists, then run each `seed`
|
|
34
33
|
* over REST. Plugins are already active (`bootstrapDev` ensures them before seeding).
|
|
35
34
|
* The application password is minted only to drive seeding here — it's never printed
|
|
@@ -61,22 +60,18 @@ async function seedDevFixtures(cfg, url) {
|
|
|
61
60
|
return seeded;
|
|
62
61
|
}
|
|
63
62
|
/**
|
|
64
|
-
* Boot
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* stack is left untouched (idempotent reruns).
|
|
63
|
+
* Boot local WordPress via docker + wp-cli. A fresh install is provisioned with a default
|
|
64
|
+
* `wp core install`, then sets permalinks and ensures the `dev.fixtures` plugins (installed
|
|
65
|
+
* sources + bind-mounted locals). An already-provisioned install is left untouched
|
|
66
|
+
* (idempotent reruns).
|
|
69
67
|
*
|
|
70
|
-
* Credentials are an output, not a stored file: a
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* concern.
|
|
68
|
+
* Credentials are an output, not a stored file: a fresh install mints a random admin
|
|
69
|
+
* password and returns it once (to log into wp-admin). No application password is minted —
|
|
70
|
+
* that's a test concern.
|
|
74
71
|
*/
|
|
75
72
|
async function bootstrapDev(cfg) {
|
|
76
73
|
const url = devUrl(cfg.port);
|
|
77
|
-
|
|
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);
|
|
74
|
+
if (!existsSync(join(cfg.wordpressDir, "wp-includes", "version.php"))) await composePull(["wordpress", "wp-cli"]).catch(() => void 0);
|
|
80
75
|
await composeUp();
|
|
81
76
|
const installed = (await compose([
|
|
82
77
|
"exec",
|
|
@@ -87,46 +82,7 @@ async function bootstrapDev(cfg) {
|
|
|
87
82
|
"is-installed"
|
|
88
83
|
])).code === 0;
|
|
89
84
|
let password;
|
|
90
|
-
|
|
91
|
-
if (!installed && byo) {
|
|
92
|
-
imported = true;
|
|
93
|
-
if (byo.prefix !== "wp_") await wpCli([
|
|
94
|
-
"config",
|
|
95
|
-
"set",
|
|
96
|
-
"table_prefix",
|
|
97
|
-
byo.prefix,
|
|
98
|
-
"--type=variable"
|
|
99
|
-
]);
|
|
100
|
-
const imp = await compose([
|
|
101
|
-
"exec",
|
|
102
|
-
"-T",
|
|
103
|
-
"mysql",
|
|
104
|
-
"mysql",
|
|
105
|
-
"-uwordpress",
|
|
106
|
-
"-pwppass",
|
|
107
|
-
"wordpress"
|
|
108
|
-
], { inputFile: byo.sqlPath });
|
|
109
|
-
if (imp.code !== 0) throw new Error(`importing dev.byo database failed:\n${imp.stderr || imp.stdout}`);
|
|
110
|
-
rmSync(byo.sqlPath, { force: true });
|
|
111
|
-
const oldUrl = await wpCli([
|
|
112
|
-
"option",
|
|
113
|
-
"get",
|
|
114
|
-
"siteurl"
|
|
115
|
-
]).catch(() => "");
|
|
116
|
-
if (oldUrl && oldUrl !== url) await wpCli([
|
|
117
|
-
"search-replace",
|
|
118
|
-
oldUrl,
|
|
119
|
-
url,
|
|
120
|
-
"--all-tables",
|
|
121
|
-
"--skip-columns=guid",
|
|
122
|
-
"--report-changed-only"
|
|
123
|
-
]);
|
|
124
|
-
await wpCli([
|
|
125
|
-
"rewrite",
|
|
126
|
-
"flush",
|
|
127
|
-
"--hard"
|
|
128
|
-
]);
|
|
129
|
-
} else if (!installed) {
|
|
85
|
+
if (!installed) {
|
|
130
86
|
password = generatePassword();
|
|
131
87
|
await wpCli([
|
|
132
88
|
"core",
|
|
@@ -146,13 +102,12 @@ async function bootstrapDev(cfg) {
|
|
|
146
102
|
]);
|
|
147
103
|
}
|
|
148
104
|
await ensurePlugins([...DEFAULT_PLUGINS, ...cfg.fixtures.flatMap((fixture) => fixture.plugins ?? [])]);
|
|
149
|
-
const seeded = !installed &&
|
|
105
|
+
const seeded = !installed && cfg.fixtures.length ? await seedDevFixtures(cfg, url) : 0;
|
|
150
106
|
const appPassword = password ? await createAdminAppPassword("kizlo-dev") : void 0;
|
|
151
107
|
return {
|
|
152
108
|
url,
|
|
153
109
|
username: TEST_ADMIN.username,
|
|
154
110
|
dbPort: cfg.dbPort,
|
|
155
|
-
imported,
|
|
156
111
|
seeded,
|
|
157
112
|
appPassword,
|
|
158
113
|
secrets: password ? { password } : void 0
|
package/dist/cli/wp/docker.d.ts
CHANGED
|
@@ -41,10 +41,22 @@ interface RunResult {
|
|
|
41
41
|
stderr: string;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* Docker's readiness in three states, so callers can tell "not installed" from "installed but not
|
|
45
|
+
* running" and say the right thing:
|
|
46
|
+
* - `missing` — the `docker` binary isn't on PATH (`docker --version` errors).
|
|
47
|
+
* - `stopped` — the client is installed but the daemon is unreachable (`docker version` fails).
|
|
48
|
+
* - `running` — the daemon answered, so a stack can boot.
|
|
47
49
|
*/
|
|
50
|
+
type DockerStatus = "missing" | "stopped" | "running";
|
|
51
|
+
/**
|
|
52
|
+
* Probe Docker in two steps: `docker --version` only touches the client (is it installed?), then
|
|
53
|
+
* `docker version` contacts the server (is the daemon up?). Used before any command that needs local
|
|
54
|
+
* WordPress, so the caller can distinguish an install problem from a "start Docker" problem.
|
|
55
|
+
*/
|
|
56
|
+
declare function dockerStatus(): Promise<DockerStatus>;
|
|
57
|
+
/** The one-line fix to print when Docker isn't ready — install it, or start the daemon. */
|
|
58
|
+
declare function dockerHint(status: "missing" | "stopped"): string;
|
|
59
|
+
/** Whether the Docker daemon is reachable — the early check before a command that needs a stack. */
|
|
48
60
|
declare function dockerAvailable(): Promise<boolean>;
|
|
49
61
|
/** Build a stack-bound docker helper and mark it active for the bare helpers below. */
|
|
50
62
|
declare function createStack(stack: Stack): DockerStack;
|
|
@@ -65,4 +77,4 @@ declare const composeStop: (opts?: {
|
|
|
65
77
|
/** Run a wp-cli command against the active stack's warm `wp-cli` container. */
|
|
66
78
|
declare const wpCli: (args: string[]) => Promise<string>;
|
|
67
79
|
//#endregion
|
|
68
|
-
export { DockerStack, RunResult, Stack, compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli };
|
|
80
|
+
export { DockerStack, DockerStatus, RunResult, Stack, compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, dockerHint, dockerStatus, wpCli };
|
package/dist/cli/wp/docker.js
CHANGED
|
@@ -33,17 +33,30 @@ function run(cmd, args, env, opts) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* the
|
|
36
|
+
* Probe Docker in two steps: `docker --version` only touches the client (is it installed?), then
|
|
37
|
+
* `docker version` contacts the server (is the daemon up?). Used before any command that needs local
|
|
38
|
+
* WordPress, so the caller can distinguish an install problem from a "start Docker" problem.
|
|
39
39
|
*/
|
|
40
|
-
async function
|
|
40
|
+
async function dockerStatus() {
|
|
41
|
+
try {
|
|
42
|
+
if ((await run("docker", ["--version"], process.env)).code !== 0) return "missing";
|
|
43
|
+
} catch {
|
|
44
|
+
return "missing";
|
|
45
|
+
}
|
|
41
46
|
try {
|
|
42
|
-
return (await run("docker", ["version"], process.env)).code === 0;
|
|
47
|
+
return (await run("docker", ["version"], process.env)).code === 0 ? "running" : "stopped";
|
|
43
48
|
} catch {
|
|
44
|
-
return
|
|
49
|
+
return "stopped";
|
|
45
50
|
}
|
|
46
51
|
}
|
|
52
|
+
/** The one-line fix to print when Docker isn't ready — install it, or start the daemon. */
|
|
53
|
+
function dockerHint(status) {
|
|
54
|
+
return status === "missing" ? "Docker isn't installed. Install it from https://docs.docker.com/get-docker/, then re-run." : "Docker is installed but not running. Start Docker (Docker Desktop, or `sudo systemctl start docker`) and re-run.";
|
|
55
|
+
}
|
|
56
|
+
/** Whether the Docker daemon is reachable — the early check before a command that needs a stack. */
|
|
57
|
+
async function dockerAvailable() {
|
|
58
|
+
return await dockerStatus() === "running";
|
|
59
|
+
}
|
|
47
60
|
function bind(stack) {
|
|
48
61
|
const base = [
|
|
49
62
|
"compose",
|
|
@@ -131,4 +144,4 @@ const composeStop = (opts) => activeStack().composeStop(opts);
|
|
|
131
144
|
const wpCli = (args) => activeStack().wpCli(args);
|
|
132
145
|
|
|
133
146
|
//#endregion
|
|
134
|
-
export { compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, wpCli };
|
|
147
|
+
export { compose, composeDown, composePull, composeStop, composeUp, createStack, dockerAvailable, dockerHint, dockerStatus, wpCli };
|
package/dist/cli/wp/settings.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WP_KIZLO_BASE } from "../../wordpress/constants.js";
|
|
2
2
|
import { WordPressService } from "../../wordpress/service.js";
|
|
3
|
+
import { PLUGIN_VERSION_HEADER } from "@kizlo/shared";
|
|
3
4
|
|
|
4
5
|
//#region src/cli/wp/settings.ts
|
|
5
6
|
/** Loopback hosts that, from inside the dev Docker stack, must be rewritten to reach the host. */
|
|
@@ -18,7 +19,7 @@ function originOf(url) {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* Rewrite a loopback host to `host.docker.internal` so the dockerized WordPress
|
|
22
|
+
* Rewrite a loopback host to `host.docker.internal` so the dockerized local WordPress can reach a
|
|
22
23
|
* Kizlo server on the host (the container's own `localhost` is not the host). Non-loopback hosts (real
|
|
23
24
|
* deployments) and unparseable URLs are returned unchanged.
|
|
24
25
|
*/
|
|
@@ -52,11 +53,16 @@ async function syncSiteSettings(credentials, settings) {
|
|
|
52
53
|
base: WP_KIZLO_BASE,
|
|
53
54
|
body
|
|
54
55
|
});
|
|
56
|
+
const pluginVersion = response.headers.get(PLUGIN_VERSION_HEADER);
|
|
55
57
|
if (response.error) return {
|
|
56
58
|
ok: false,
|
|
57
|
-
error: describeError(response.status, response.error)
|
|
59
|
+
error: describeError(response.status, response.error),
|
|
60
|
+
pluginVersion
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
pluginVersion
|
|
58
65
|
};
|
|
59
|
-
return { ok: true };
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* A short, log-friendly summary of a failed request. A transport failure has no HTTP status (`0`), so
|
package/dist/cli/wp/setup.js
CHANGED
|
@@ -8,7 +8,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
8
8
|
/**
|
|
9
9
|
* Phase 2 — after bootstrap, build a `SeedContext` and run each `Fixture.seed`
|
|
10
10
|
* over REST, collecting namespaced fixtures, then write the credentials artifact
|
|
11
|
-
* at `<config-dir>/.kizlo/test
|
|
11
|
+
* at `<config-dir>/.kizlo/test.json`. Returns a teardown that runs
|
|
12
12
|
* each fixture's `cleanup`.
|
|
13
13
|
*/
|
|
14
14
|
async function runSeeds(config) {
|
package/dist/cli/wp/stack.js
CHANGED
|
@@ -10,7 +10,7 @@ function localMounts(fixtures) {
|
|
|
10
10
|
/**
|
|
11
11
|
* The test stack: the base compose file under a `<name>-test` project, plus a generated
|
|
12
12
|
* mount override when any fixture carries a local (`{ path }`) plugin — so the same
|
|
13
|
-
* fixtures bind-mount their live plugin source here exactly as in
|
|
13
|
+
* fixtures bind-mount their live plugin source here exactly as in local WordPress.
|
|
14
14
|
*/
|
|
15
15
|
function testStack(cfg) {
|
|
16
16
|
const mounts = localMounts(cfg.fixtures);
|
|
@@ -36,9 +36,9 @@ function detectHostUser() {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
* `<name>-dev` project. Building
|
|
41
|
-
*
|
|
39
|
+
* Local WordPress: the base compose file plus the generated mount override, under a
|
|
40
|
+
* `<name>-dev` project. Building it (re)writes the override so it always matches the
|
|
41
|
+
* install folder (`.kizlo/local`) and the `dev.fixtures` local plugins.
|
|
42
42
|
*/
|
|
43
43
|
function devStack(cfg) {
|
|
44
44
|
const override = writeDevOverride(cfg.configDir, {
|
package/dist/comment/schema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ListMetadata, ListOrder
|
|
2
|
-
import { NumberLike, arrayable } from "@kizlo/shared";
|
|
1
|
+
import { ListMetadata, ListOrder } from "../shared/schema.js";
|
|
2
|
+
import { Media, NumberLike, arrayable } from "@kizlo/shared";
|
|
3
3
|
import z from "zod/v4";
|
|
4
4
|
|
|
5
5
|
//#region src/comment/schema.ts
|