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,9 +1,8 @@
|
|
|
1
|
-
import { dockerAvailable } from "../wp/docker.js";
|
|
2
1
|
import { addDependencyArgs, detectImportAlias, detectPackageManager, ensureGitignored, getVersion, loadEnvFiles, runCommandAsync } from "../utils.js";
|
|
3
2
|
import { PRESETS, detectPreset, getPreset } from "../presets/index.js";
|
|
4
3
|
import { fetchTemplate } from "../presets/source.js";
|
|
5
|
-
import { adaptFile, changesFor, fileEntries, patchEntries, readManifest } from "../presets/template.js";
|
|
6
|
-
import { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsNote, orCancel, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
4
|
+
import { adaptFile, changesFor, fileEntries, isOlderVersion, minCliError, patchEntries, readManifest, resolveDependencies } from "../presets/template.js";
|
|
5
|
+
import { collectConnectionFromEnv, collectConnectionInteractively, dirPath, nextStepsNote, orCancel, readEnvExample, resolveEnvKeys, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
7
6
|
import { applyLayoutPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
|
|
8
7
|
import path from "node:path";
|
|
9
8
|
import fs from "node:fs";
|
|
@@ -32,7 +31,6 @@ function detectAppDir(cwd, hasSrcDir) {
|
|
|
32
31
|
return hasSrcDir ? "src/app" : "app";
|
|
33
32
|
}
|
|
34
33
|
async function collectInteractively(ctx) {
|
|
35
|
-
const conn = await collectConnectionInteractively(ctx.preset);
|
|
36
34
|
const dir = orCancel(await p.text({
|
|
37
35
|
message: "Kizlo directory",
|
|
38
36
|
initialValue: defaultDir(ctx.hasSrcDir),
|
|
@@ -49,7 +47,7 @@ async function collectInteractively(ctx) {
|
|
|
49
47
|
})).trim();
|
|
50
48
|
}
|
|
51
49
|
return {
|
|
52
|
-
...
|
|
50
|
+
...await collectConnectionInteractively(ctx.preset),
|
|
53
51
|
dir,
|
|
54
52
|
alias
|
|
55
53
|
};
|
|
@@ -60,7 +58,7 @@ async function collectInteractively(ctx) {
|
|
|
60
58
|
* later. Never fails — `--yes` always scaffolds a fillable project.
|
|
61
59
|
*/
|
|
62
60
|
function collectFromEnv(ctx) {
|
|
63
|
-
const conn = collectConnectionFromEnv(ctx.
|
|
61
|
+
const conn = collectConnectionFromEnv(ctx.envKeys);
|
|
64
62
|
const dir = defaultDir(ctx.hasSrcDir);
|
|
65
63
|
return {
|
|
66
64
|
...conn,
|
|
@@ -71,40 +69,23 @@ function collectFromEnv(ctx) {
|
|
|
71
69
|
function readPkg(pkgPath) {
|
|
72
70
|
return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
73
71
|
}
|
|
74
|
-
function kizloDep(pkg) {
|
|
75
|
-
return pkg.dependencies?.kizlo ?? pkg.devDependencies?.kizlo;
|
|
76
|
-
}
|
|
77
|
-
/** A bare `x.y.z` from a version spec (range prefix and pre-release/build metadata dropped); undefined if none. */
|
|
78
|
-
function coerceVersion(spec) {
|
|
79
|
-
const match = /(\d+)\.(\d+)\.(\d+)/.exec(spec);
|
|
80
|
-
return match ? [
|
|
81
|
-
Number(match[1]),
|
|
82
|
-
Number(match[2]),
|
|
83
|
-
Number(match[3])
|
|
84
|
-
] : void 0;
|
|
85
|
-
}
|
|
86
|
-
/** Whether `have` is a strictly older release than `want`; false when either can't be compared. */
|
|
87
|
-
function isOlder(have, want) {
|
|
88
|
-
const a = coerceVersion(have);
|
|
89
|
-
const b = coerceVersion(want);
|
|
90
|
-
if (!a || !b) return false;
|
|
91
|
-
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i];
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
72
|
/**
|
|
95
|
-
* The template is authoritative for the
|
|
96
|
-
* (stamped at release), falling back to the running CLI's version in-repo
|
|
97
|
-
*
|
|
98
|
-
* never changed silently. Never downgrades
|
|
73
|
+
* The template is authoritative for the pinned versions: it declares `dependencies`/`devDependencies`
|
|
74
|
+
* in its manifest (stamped at release), falling back to the running CLI's version for `kizlo` in-repo
|
|
75
|
+
* before the first stamp. For each declared package the project already has at an older release, upgrade
|
|
76
|
+
* it up to the template's pin and say so — a deliberate pin is never changed silently. Never downgrades,
|
|
77
|
+
* and never adds a package the project doesn't already have (a missing `kizlo` is installed separately).
|
|
99
78
|
*/
|
|
100
|
-
async function
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
79
|
+
async function alignDependencies(cwd, pm, pkg, manifest) {
|
|
80
|
+
const { dependencies, devDependencies } = resolveDependencies(manifest);
|
|
81
|
+
for (const [name, want] of [...Object.entries(dependencies), ...Object.entries(devDependencies)]) {
|
|
82
|
+
const have = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
83
|
+
if (!have || !isOlderVersion(have, want)) continue;
|
|
84
|
+
const s = p.spinner();
|
|
85
|
+
s.start(`Upgrading ${name} from ${have} to ${want}`);
|
|
86
|
+
const ok = await runCommandAsync(addDependencyArgs(pm, `${name}@${want}`), cwd, "ignore");
|
|
87
|
+
s.stop(ok ? `Upgraded ${name} to ${want}` : `Could not upgrade ${name} automatically — install ${name}@${want} yourself`);
|
|
88
|
+
}
|
|
108
89
|
}
|
|
109
90
|
const init = defineCommand({
|
|
110
91
|
meta: {
|
|
@@ -178,76 +159,89 @@ const init = defineCommand({
|
|
|
178
159
|
process.exit(1);
|
|
179
160
|
}
|
|
180
161
|
if (yes) loadEnvFiles(cwd);
|
|
181
|
-
const setup = yes ? collectFromEnv({
|
|
182
|
-
cwd,
|
|
183
|
-
hasSrcDir,
|
|
184
|
-
preset
|
|
185
|
-
}) : await collectInteractively({
|
|
186
|
-
cwd,
|
|
187
|
-
hasSrcDir,
|
|
188
|
-
preset
|
|
189
|
-
});
|
|
190
|
-
if (args.alias !== void 0) setup.alias = String(args.alias).trim();
|
|
191
|
-
setup.alias = aliasWithSlash(setup.alias);
|
|
192
|
-
if (setup.mode === "local" && !await dockerAvailable()) {
|
|
193
|
-
p.cancel("Docker isn't available — start Docker (or install it) and re-run, or choose “Use my own WordPress”.");
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
if (!hasKizlo) {
|
|
197
|
-
const spec = `kizlo@^${getVersion()}`;
|
|
198
|
-
const s = p.spinner();
|
|
199
|
-
s.start(`Installing kizlo with ${pm}`);
|
|
200
|
-
const ok = await runCommandAsync(addDependencyArgs(pm, spec), cwd, "ignore");
|
|
201
|
-
s.stop(ok ? "Installed kizlo" : "Could not install kizlo automatically");
|
|
202
|
-
if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm, spec).join(" ")}`);
|
|
203
|
-
}
|
|
204
|
-
if (preset.apiPath && setup.baseUrl) setup.baseUrl = withApiPath(setup.baseUrl, preset.apiPath);
|
|
205
|
-
await setupLocalWordPress(cwd, setup);
|
|
206
|
-
await writeEnv(cwd, preset, setup, {
|
|
207
|
-
force,
|
|
208
|
-
yes
|
|
209
|
-
});
|
|
210
|
-
await syncRemote(setup);
|
|
211
|
-
const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
212
|
-
const serverDirRel = path.join(dirRel, "server");
|
|
213
|
-
const clientUrl = setup.siteUrl && !sameOrigin(setup.siteUrl, setup.baseUrl) ? setup.baseUrl : void 0;
|
|
214
|
-
const scaffold = buildScaffoldContext(cwd, {
|
|
215
|
-
dirRel,
|
|
216
|
-
appDir: detectAppDir(cwd, hasSrcDir),
|
|
217
|
-
alias: setup.alias,
|
|
218
|
-
clientUrl
|
|
219
|
-
});
|
|
220
|
-
const files = [{
|
|
221
|
-
label: "Kizlo config",
|
|
222
|
-
relPath: "kizlo.config.ts",
|
|
223
|
-
contents: kizloConfigTemplate(dirRel, setup.alias, setup.devPath)
|
|
224
|
-
}];
|
|
225
162
|
let manifest;
|
|
163
|
+
let fetched;
|
|
226
164
|
if (preset.template) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
for (const entry of fileEntries(changesFor(manifest, "init"))) files.push(adaptFile(fetched.dir, entry, manifest.conventions, scaffold));
|
|
232
|
-
} finally {
|
|
165
|
+
fetched = await fetchTemplate(preset.template);
|
|
166
|
+
manifest = readManifest(fetched.dir);
|
|
167
|
+
const minErr = minCliError(manifest);
|
|
168
|
+
if (minErr) {
|
|
233
169
|
fetched.cleanup();
|
|
170
|
+
p.cancel(minErr);
|
|
171
|
+
process.exit(1);
|
|
234
172
|
}
|
|
235
|
-
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
173
|
+
}
|
|
174
|
+
const envKeys = resolveEnvKeys(preset, manifest);
|
|
175
|
+
try {
|
|
176
|
+
const setup = yes ? collectFromEnv({
|
|
177
|
+
cwd,
|
|
178
|
+
hasSrcDir,
|
|
179
|
+
preset,
|
|
180
|
+
envKeys
|
|
181
|
+
}) : await collectInteractively({
|
|
182
|
+
cwd,
|
|
183
|
+
hasSrcDir,
|
|
184
|
+
preset
|
|
185
|
+
});
|
|
186
|
+
if (args.alias !== void 0) setup.alias = String(args.alias).trim();
|
|
187
|
+
setup.alias = aliasWithSlash(setup.alias);
|
|
188
|
+
const includeExamples = manifest && fetched ? yes ? true : orCancel(await p.confirm({
|
|
189
|
+
message: "Add example pages?",
|
|
190
|
+
initialValue: true
|
|
191
|
+
})) : false;
|
|
192
|
+
if (!hasKizlo) {
|
|
193
|
+
const spec = `kizlo@${manifest?.dependencies?.kizlo ?? `^${getVersion()}`}`;
|
|
194
|
+
const s = p.spinner();
|
|
195
|
+
s.start(`Installing kizlo with ${pm}`);
|
|
196
|
+
const ok = await runCommandAsync(addDependencyArgs(pm, spec), cwd, "ignore");
|
|
197
|
+
s.stop(ok ? "Installed kizlo" : "Could not install kizlo automatically");
|
|
198
|
+
if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm, spec).join(" ")}`);
|
|
199
|
+
}
|
|
200
|
+
if (preset.apiPath && setup.baseUrl) setup.baseUrl = withApiPath(setup.baseUrl, preset.apiPath);
|
|
201
|
+
await setupLocalWordPress(cwd, setup);
|
|
202
|
+
await writeEnv(cwd, envKeys, setup, {
|
|
240
203
|
force,
|
|
241
|
-
yes
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
204
|
+
yes,
|
|
205
|
+
exampleTemplate: fetched ? readEnvExample(fetched.dir) : void 0
|
|
206
|
+
});
|
|
207
|
+
await syncRemote(setup);
|
|
208
|
+
const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
209
|
+
const serverDirRel = path.join(dirRel, "server");
|
|
210
|
+
const clientUrl = setup.siteUrl && !sameOrigin(setup.siteUrl, setup.baseUrl) ? setup.baseUrl : void 0;
|
|
211
|
+
const scaffold = buildScaffoldContext(cwd, {
|
|
212
|
+
dirRel,
|
|
213
|
+
appDir: detectAppDir(cwd, hasSrcDir),
|
|
214
|
+
alias: setup.alias,
|
|
215
|
+
clientUrl
|
|
216
|
+
});
|
|
217
|
+
const files = [{
|
|
218
|
+
label: "Kizlo config",
|
|
219
|
+
relPath: "kizlo.config.ts",
|
|
220
|
+
contents: kizloConfigTemplate(dirRel, setup.alias, setup.mode === "local")
|
|
221
|
+
}];
|
|
222
|
+
if (manifest && fetched) {
|
|
223
|
+
await alignDependencies(cwd, pm, pkg, manifest);
|
|
224
|
+
for (const entry of fileEntries(changesFor(manifest, "init", { includeExamples }))) files.push(adaptFile(fetched.dir, entry, manifest.conventions, scaffold));
|
|
225
|
+
} else if (preset.scaffolds) files.push(...preset.scaffolds(scaffold));
|
|
226
|
+
const scaffolded = [];
|
|
227
|
+
for (const file of files) scaffolded.push({
|
|
228
|
+
file,
|
|
229
|
+
result: await scaffoldFile(cwd, file, {
|
|
230
|
+
force,
|
|
231
|
+
yes
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
writeGeneratedContract(cwd, serverDirRel);
|
|
235
|
+
const gitignore = ensureGitignored(cwd, ".env");
|
|
236
|
+
ensureGitignored(cwd, ".kizlo/");
|
|
237
|
+
for (const { file, result } of scaffolded) reportScaffold(file, result, yes);
|
|
238
|
+
if (manifest) applyLayoutPatches(cwd, patchEntries(changesFor(manifest, "init")), manifest.conventions, scaffold);
|
|
239
|
+
if (gitignore !== "present") p.log.success(`${gitignore === "created" ? "Created" : "Updated"} .gitignore (ignoring .env)`);
|
|
240
|
+
nextStepsNote(setup);
|
|
241
|
+
p.outro("Kizlo is ready 🎉");
|
|
242
|
+
} finally {
|
|
243
|
+
fetched?.cleanup();
|
|
244
|
+
}
|
|
251
245
|
}
|
|
252
246
|
});
|
|
253
247
|
|
|
@@ -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
|
|
23
|
-
* test
|
|
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
|
|
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
|
|
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
|
|
63
|
-
if (await isSeeded()) log.info("
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
116
|
-
else log.info("
|
|
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
|
|
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
|
|
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
|
|
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(`
|
|
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: "
|
|
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
|
-
|
|
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
|
|
122
|
-
* has
|
|
123
|
-
* project
|
|
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
|
|
129
|
+
async function usesLocalWordPress(cwd) {
|
|
126
130
|
const fileConfig = await loadConfigFile(findConfigDir(cwd));
|
|
127
|
-
return Boolean(fileConfig?.dev?.
|
|
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
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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:
|
|
158
|
-
wordpressDir: path.resolve(configDir,
|
|
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,
|
|
157
|
+
export { CONFIG_FILES, DEFAULT_DEV_DB_PORT, DEFAULT_DEV_PORT, resolveConfig, resolveDevConfig, resolveStackName, resolveTestConfig, usesLocalWordPress };
|
|
@@ -2,7 +2,7 @@ import { consola } from "consola";
|
|
|
2
2
|
|
|
3
3
|
//#region src/cli/daemon/logger.ts
|
|
4
4
|
/** Tagged logger for the daemon — every line is prefixed with `kizlo`. */
|
|
5
|
-
const log = consola
|
|
5
|
+
const log = consola;
|
|
6
6
|
log.options.formatOptions.date = false;
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CONFIG_FILES } from "./config.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { FSWatcher } from "chokidar";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/daemon/reload.ts
|
|
8
|
+
/** Env files `loadEnvFiles` reads, in the same order — the runtime values a reload must pick up. */
|
|
9
|
+
const ENV_FILES = [".env", ".env.local"];
|
|
10
|
+
/** A combined hash of the watched files' contents; a missing file counts as empty. */
|
|
11
|
+
function fingerprint(files) {
|
|
12
|
+
const hash = createHash("sha1");
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
hash.update(file);
|
|
15
|
+
hash.update("\0");
|
|
16
|
+
try {
|
|
17
|
+
hash.update(readFileSync(file));
|
|
18
|
+
} catch {}
|
|
19
|
+
hash.update("\0");
|
|
20
|
+
}
|
|
21
|
+
return hash.digest("hex");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Watch the project's `.env` files and `kizlo.config.*` and call `onReload` with the changed file's
|
|
25
|
+
* basename when their *contents* change. `kizlo dev` uses this to restart its whole session — rebooting
|
|
26
|
+
* local WordPress and regenerating the contract — so a changed port, connection, or fixture takes
|
|
27
|
+
* effect without stopping and rerunning the command by hand. Debounced so an editor's multi-write save
|
|
28
|
+
* fires once, and gated on a content hash so a no-op save (`touch`, or writing the file back unchanged)
|
|
29
|
+
* is ignored rather than triggering a pointless reboot. Returns a synchronous `stop()` that closes the
|
|
30
|
+
* watcher.
|
|
31
|
+
*
|
|
32
|
+
* `dev` rewrites `.env` itself while booting (fresh credentials, the current LAN URL); the caller
|
|
33
|
+
* pauses its reload handler around a reboot so that self-write doesn't trigger another reload. The hash
|
|
34
|
+
* is still refreshed for it here, so once the reboot settles the new content is the baseline and won't
|
|
35
|
+
* re-fire.
|
|
36
|
+
*/
|
|
37
|
+
function watchReload(configDir, onReload) {
|
|
38
|
+
const files = [...ENV_FILES, ...CONFIG_FILES].map((name) => path.join(configDir, name));
|
|
39
|
+
const watcher = new FSWatcher({
|
|
40
|
+
persistent: true,
|
|
41
|
+
ignoreInitial: true
|
|
42
|
+
});
|
|
43
|
+
let last = fingerprint(files);
|
|
44
|
+
let timer;
|
|
45
|
+
const schedule = (changed) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
timer = setTimeout(() => {
|
|
48
|
+
const next = fingerprint(files);
|
|
49
|
+
if (next === last) return;
|
|
50
|
+
last = next;
|
|
51
|
+
onReload(path.basename(changed));
|
|
52
|
+
}, 300);
|
|
53
|
+
};
|
|
54
|
+
watcher.add(files);
|
|
55
|
+
watcher.on("all", (_event, changed) => schedule(changed));
|
|
56
|
+
let stopped = false;
|
|
57
|
+
return () => {
|
|
58
|
+
if (stopped) return;
|
|
59
|
+
stopped = true;
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
watcher.close();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { watchReload };
|
package/dist/cli/daemon/watch.js
CHANGED
|
@@ -30,7 +30,6 @@ async function watch(cfg) {
|
|
|
30
30
|
});
|
|
31
31
|
const onChange = debounce(() => regenerate(cfg), 300);
|
|
32
32
|
watcher.add(path.resolve(cfg.cwd, cfg.serverDir));
|
|
33
|
-
watcher.on("ready", () => log.start("Watching for contract changes..."));
|
|
34
33
|
watcher.on("all", () => void onChange());
|
|
35
34
|
return watcher;
|
|
36
35
|
}
|
package/dist/cli/presets/base.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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"
|