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.
- package/dist/cli/commands/_setup.js +121 -77
- package/dist/cli/commands/_wiring.js +5 -4
- package/dist/cli/commands/create.js +32 -25
- package/dist/cli/commands/dev.js +63 -71
- package/dist/cli/commands/init.js +93 -103
- package/dist/cli/commands/test.js +34 -34
- package/dist/cli/daemon/config.js +18 -24
- package/dist/cli/presets/base.js +4 -2
- package/dist/cli/presets/nextjs.js +0 -1
- package/dist/cli/presets/template.js +76 -2
- 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 +1 -1
- package/dist/cli/wp/setup.js +1 -1
- package/dist/cli/wp/stack.js +4 -4
- package/dist/config.d.ts +20 -23
- package/dist/index.d.ts +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/router.d.ts +9 -9
- package/dist/test/server.js +1 -1
- package/dist/test.d.ts +3 -3
- package/dist/test.js +3 -3
- package/package.json +1 -1
- package/dist/cli/wp/byo.js +0 -113
package/dist/cli/commands/dev.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createStack } from "../wp/docker.js";
|
|
2
|
-
import { ensureGitignored, envGroups, groupDefault, mergeEnv, pickStackPort, withSpinner } from "../utils.js";
|
|
3
|
-
import {
|
|
1
|
+
import { createStack, dockerHint, dockerStatus } from "../wp/docker.js";
|
|
2
|
+
import { DEFAULT_ENV_KEYS, ensureGitignored, envGroups, groupDefault, mergeEnv, pickStackPort, withSpinner } from "../utils.js";
|
|
3
|
+
import { resolveDevConfig, usesLocalWordPress } from "../daemon/config.js";
|
|
4
4
|
import { bootstrapDev } from "../wp/dev.js";
|
|
5
5
|
import { reapOrphans, registerSession, removeProjectContainers, spawnWatchdog, unregisterSession } from "../wp/session.js";
|
|
6
6
|
import { syncSiteSettings } from "../wp/settings.js";
|
|
@@ -21,15 +21,13 @@ async function resolve$1(cwd) {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* print a plain note explaining there's nothing to do and exit cleanly. Returns true when a
|
|
28
|
-
* local stack is configured and the caller may proceed.
|
|
24
|
+
* The lifecycle subcommands (`stop`/`reset`) only act on local WordPress. A project pointing at its
|
|
25
|
+
* own WordPress (no `dev.local`) has nothing to manage, so print a plain note and exit cleanly. Returns
|
|
26
|
+
* true when local WordPress is configured and the caller may proceed.
|
|
29
27
|
*/
|
|
30
|
-
async function
|
|
31
|
-
if (await
|
|
32
|
-
note("
|
|
28
|
+
async function requireLocalWordPress(cwd) {
|
|
29
|
+
if (await usesLocalWordPress(cwd)) return true;
|
|
30
|
+
note("This project doesn't run local WordPress — set `dev.local: true` in kizlo.config.ts to add it.");
|
|
33
31
|
return false;
|
|
34
32
|
}
|
|
35
33
|
/** A dim, untagged one-liner (no consola timestamp) for the clean dev output. */
|
|
@@ -39,7 +37,7 @@ function note(text) {
|
|
|
39
37
|
}
|
|
40
38
|
/**
|
|
41
39
|
* Print the connection summary as an aligned, Next.js-style block. Shows the wp-admin URL on
|
|
42
|
-
* the loopback ("Local") and — when
|
|
40
|
+
* the loopback ("Local") and — when local WordPress is installed on a LAN address rather than
|
|
43
41
|
* localhost — on the network, so it can be opened from other devices. On a fresh install, also
|
|
44
42
|
* show the one-time wp-admin password (it's never stored, so this is the only chance to see it).
|
|
45
43
|
*/
|
|
@@ -52,57 +50,56 @@ function printSummary(info) {
|
|
|
52
50
|
const width = Math.max(...rows.map(([label]) => label.length)) + 1;
|
|
53
51
|
const lines = ["", ...rows.map(([label, value]) => ` ${dim}- ${`${label}:`.padEnd(width)}${reset} ${cyan}${value}${reset}`)];
|
|
54
52
|
if (info.secrets) lines.push("", ` ${dim}admin login:${reset} ${info.username} / ${info.secrets.password}`, ` ${dim}save the password now — it's shown only once${reset}`);
|
|
55
|
-
else if (info.imported) lines.push("", ` ${dim}log in with your imported site's own credentials${reset}`);
|
|
56
53
|
if (info.seeded > 0) lines.push(` ${dim}seeded ${info.seeded} fixture${info.seeded === 1 ? "" : "s"}${reset}`);
|
|
57
54
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
58
55
|
}
|
|
59
56
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* touched by `kizlo dev`, so pointing them at a real site survives booting
|
|
57
|
+
* Local-WordPress connection keys a fresh install must rewrite (the rest of `.env` is untouched). The
|
|
58
|
+
* remote `KIZLO_WP_*` / `KIZLO_WP_SECRET` keys live under their own names and are never
|
|
59
|
+
* touched by `kizlo dev`, so pointing them at a real site survives booting local WordPress.
|
|
63
60
|
*/
|
|
64
61
|
const WP_ENV_KEYS = [
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
62
|
+
"KIZLO_LOCAL_WP_URL",
|
|
63
|
+
"KIZLO_LOCAL_WP_USERNAME",
|
|
64
|
+
"KIZLO_LOCAL_WP_APP_PASSWORD"
|
|
68
65
|
];
|
|
69
66
|
/** Read a key's value from raw `.env` content (first match, trimmed); undefined when absent or empty. */
|
|
70
67
|
function readEnvValue(content, key) {
|
|
71
68
|
return content.match(new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`, "m"))?.[1]?.trim() || void 0;
|
|
72
69
|
}
|
|
73
70
|
/**
|
|
74
|
-
* The Kizlo backend URL from `.env` — the one key ending in `
|
|
75
|
-
* or a framework-prefixed `
|
|
71
|
+
* The Kizlo backend URL from `.env` — the one key ending in `KIZLO_API_URL` (`KIZLO_API_URL`,
|
|
72
|
+
* or a framework-prefixed `NEXT_PUBLIC_KIZLO_API_URL`). It's the handler mount the plugin delivers
|
|
76
73
|
* webhook events to; its origin is the site's canonical URL. User-managed (init writes it), so dev
|
|
77
74
|
* only reads it. Undefined before `kizlo init` has populated `.env`.
|
|
78
75
|
*/
|
|
79
76
|
function readBaseUrl(content) {
|
|
80
|
-
return content.match(/^\s*[A-Z_]*
|
|
77
|
+
return content.match(/^\s*[A-Z_]*KIZLO_API_URL\s*=\s*(.*)$/m)?.[1]?.trim() || void 0;
|
|
81
78
|
}
|
|
82
79
|
/**
|
|
83
|
-
* Rewrite the
|
|
80
|
+
* Rewrite the local WordPress connection in `.env` after a fresh install. A reset (or first boot)
|
|
84
81
|
* wipes the database, so the previous application password — and possibly the port — no longer
|
|
85
82
|
* match; without this the app keeps the stale credentials and REST auth fails. Only the three
|
|
86
83
|
* connection keys are overwritten; everything else in `.env` is preserved, and the file is created
|
|
87
84
|
* if absent. Gated by the caller on a minted `appPassword`, so a warm resume never churns it.
|
|
88
85
|
*
|
|
89
|
-
* `
|
|
90
|
-
* value the user already set is preserved — booting
|
|
91
|
-
* resolved
|
|
92
|
-
* returned so the caller can sync them into the plugin.
|
|
86
|
+
* `KIZLO_CONNECT=local` and `KIZLO_LOCAL_WP_SECRET` are written too but kept out of the overwrite set,
|
|
87
|
+
* so a value the user already set is preserved — booting local WordPress only fills them when absent.
|
|
88
|
+
* The resolved local secret (the existing one, or a freshly minted one) and the Kizlo server base URL
|
|
89
|
+
* are returned so the caller can sync them into the plugin.
|
|
93
90
|
*/
|
|
94
91
|
function updateWpEnv(cfg, info) {
|
|
95
92
|
if (!info.appPassword) return void 0;
|
|
96
93
|
const envPath = join(cfg.configDir, ".env");
|
|
97
94
|
const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
98
|
-
const siteSecret = readEnvValue(existing, "
|
|
95
|
+
const siteSecret = readEnvValue(existing, "KIZLO_LOCAL_WP_SECRET") ?? randomBytes(32).toString("hex");
|
|
99
96
|
const { content } = mergeEnv(existing, {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}, new Set(WP_ENV_KEYS), envGroups(
|
|
97
|
+
KIZLO_LOCAL_WP_URL: info.url,
|
|
98
|
+
KIZLO_LOCAL_WP_USERNAME: info.username,
|
|
99
|
+
KIZLO_LOCAL_WP_APP_PASSWORD: info.appPassword,
|
|
100
|
+
KIZLO_LOCAL_WP_SECRET: siteSecret,
|
|
101
|
+
KIZLO_CONNECT: "local"
|
|
102
|
+
}, new Set(WP_ENV_KEYS), envGroups(DEFAULT_ENV_KEYS));
|
|
106
103
|
writeFileSync(envPath, content);
|
|
107
104
|
return {
|
|
108
105
|
siteSecret,
|
|
@@ -110,19 +107,19 @@ function updateWpEnv(cfg, info) {
|
|
|
110
107
|
};
|
|
111
108
|
}
|
|
112
109
|
/**
|
|
113
|
-
* Re-point `
|
|
110
|
+
* Re-point `KIZLO_LOCAL_WP_URL` at the current URL on a warm resume, so a LAN address that
|
|
114
111
|
* changed since the last session (a new DHCP lease, a different network) doesn't leave the app talking
|
|
115
112
|
* to a dead IP. Only this one key is rewritten — the credentials and secret from the fresh install are
|
|
116
113
|
* preserved — and it's a no-op when `.env` is absent (nothing provisioned yet) or already current.
|
|
117
114
|
* The full fresh-install write goes through {@link updateWpEnv} instead; this covers the resume path it
|
|
118
115
|
* skips. Returns true when it changed the file. The app still needs a restart to read the new value.
|
|
119
116
|
*/
|
|
120
|
-
function
|
|
117
|
+
function syncLocalUrl(cfg, url) {
|
|
121
118
|
const envPath = join(cfg.configDir, ".env");
|
|
122
119
|
if (!existsSync(envPath)) return false;
|
|
123
120
|
const existing = readFileSync(envPath, "utf8");
|
|
124
|
-
if (readEnvValue(existing, "
|
|
125
|
-
const { content } = mergeEnv(existing, {
|
|
121
|
+
if (readEnvValue(existing, "KIZLO_LOCAL_WP_URL") === url) return false;
|
|
122
|
+
const { content } = mergeEnv(existing, { KIZLO_LOCAL_WP_URL: url }, new Set(["KIZLO_LOCAL_WP_URL"]), envGroups(DEFAULT_ENV_KEYS));
|
|
126
123
|
writeFileSync(envPath, content);
|
|
127
124
|
return true;
|
|
128
125
|
}
|
|
@@ -134,7 +131,7 @@ function syncDevUrl(cfg, url) {
|
|
|
134
131
|
* `next dev` child server — nothing stops them automatically when we die. A dying process
|
|
135
132
|
* also can't be trusted to finish async work (a closed terminal breaks its stdout
|
|
136
133
|
* mid-write), so this process does no docker work on the way out. It only needs to
|
|
137
|
-
* *exit*; the watchdog, watching our PID from its own session, stops
|
|
134
|
+
* *exit*; the watchdog, watching our PID from its own session, stops it the moment
|
|
138
135
|
* we're gone — by Ctrl+C, SIGTERM, a closed tab, SIGKILL, or a crash alike.
|
|
139
136
|
*
|
|
140
137
|
* We trigger that exit two ways, because neither alone is enough: signal handlers (for
|
|
@@ -150,7 +147,7 @@ function armForegroundTeardown(cfg) {
|
|
|
150
147
|
exiting = true;
|
|
151
148
|
unregisterSession(cfg.project);
|
|
152
149
|
try {
|
|
153
|
-
note(`Stopping
|
|
150
|
+
note(`Stopping local WordPress (${reason})…`);
|
|
154
151
|
} catch {}
|
|
155
152
|
process.exit(0);
|
|
156
153
|
};
|
|
@@ -169,7 +166,7 @@ function armForegroundTeardown(cfg) {
|
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
168
|
/**
|
|
172
|
-
* Boot
|
|
169
|
+
* Boot local WordPress and run in the foreground until exit — the path behind bare
|
|
173
170
|
* `kizlo dev`. Arms teardown first (so a mid-startup cancel still
|
|
174
171
|
* stops partial containers), shows a timed spinner, prints the summary + stop hint, then
|
|
175
172
|
* parks. A referenced timer holds the event loop open — an unresolved promise alone would
|
|
@@ -196,7 +193,7 @@ async function startForeground(cfg) {
|
|
|
196
193
|
if (dbPort !== cfg.dbPort) note(`Database port ${cfg.dbPort} is in use — using ${dbPort} instead.`);
|
|
197
194
|
armForegroundTeardown(ready);
|
|
198
195
|
const start = Date.now();
|
|
199
|
-
const creds = await withSpinner("Starting WordPress
|
|
196
|
+
const creds = await withSpinner("Starting local WordPress", () => bootstrapDev(ready));
|
|
200
197
|
if (creds.appPassword) {
|
|
201
198
|
const env = updateWpEnv(ready, creds);
|
|
202
199
|
note("Updated .env with the new WordPress credentials");
|
|
@@ -212,12 +209,12 @@ async function startForeground(cfg) {
|
|
|
212
209
|
});
|
|
213
210
|
if (!sync.ok) note(`Could not sync the site settings to WordPress (${sync.error}) — make sure the kizlo plugin is active, then set them from the Kizlo settings.`);
|
|
214
211
|
}
|
|
215
|
-
} else if (
|
|
212
|
+
} else if (syncLocalUrl(ready, creds.url)) note("Updated .env WordPress URL to the current network address — restart your app to pick it up");
|
|
216
213
|
printSummary(creds);
|
|
217
214
|
const { green, dim, bold, reset } = palette();
|
|
218
215
|
const ms = Date.now() - start;
|
|
219
216
|
const took = ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
220
|
-
process.stdout.write(`\n ${dim}Press ${reset}${bold}Ctrl+C${reset}${dim} (or close the terminal) to stop
|
|
217
|
+
process.stdout.write(`\n ${dim}Press ${reset}${bold}Ctrl+C${reset}${dim} (or close the terminal) to stop local WordPress${reset}\n`);
|
|
221
218
|
process.stdout.write(`\n ${green}✓${reset} Ready in ${took}\n\n`);
|
|
222
219
|
const stopWatcher = await startWatcher(ready.configDir);
|
|
223
220
|
if (stopWatcher) process.on("exit", stopWatcher);
|
|
@@ -225,8 +222,8 @@ async function startForeground(cfg) {
|
|
|
225
222
|
await new Promise(() => {});
|
|
226
223
|
}
|
|
227
224
|
/**
|
|
228
|
-
* Run the contract watcher in the foreground until exit — the path bare `kizlo dev` takes when
|
|
229
|
-
*
|
|
225
|
+
* Run the contract watcher in the foreground until exit — the path bare `kizlo dev` takes when local
|
|
226
|
+
* WordPress isn't configured (a project pointing at its own WordPress). Generates once, then watches
|
|
230
227
|
* the server files and regenerates on save. Returns immediately when another watcher already holds
|
|
231
228
|
* the lock; otherwise the persistent watcher keeps the process alive on its own.
|
|
232
229
|
*/
|
|
@@ -244,44 +241,39 @@ async function watchOnly(cwd) {
|
|
|
244
241
|
});
|
|
245
242
|
}
|
|
246
243
|
/**
|
|
247
|
-
* Start (or resume)
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
244
|
+
* Start (or resume) local WordPress. Backs bare `kizlo dev`. Always runs in the foreground and stops
|
|
245
|
+
* it on exit, so it never outlives the session — the subcommands (`stop`/`reset`) manage everything
|
|
246
|
+
* else. Without local WordPress configured there's nothing to boot, so it runs the contract watcher
|
|
247
|
+
* alone.
|
|
251
248
|
*/
|
|
252
249
|
async function bringUp() {
|
|
253
250
|
const cwd = process.cwd();
|
|
254
|
-
if (!await
|
|
251
|
+
if (!await usesLocalWordPress(cwd)) {
|
|
252
|
+
note("This project doesn't run local WordPress — running the contract watcher only. Set `dev.local` in kizlo.config.ts to add it.");
|
|
255
253
|
await watchOnly(cwd);
|
|
256
254
|
return;
|
|
257
255
|
}
|
|
256
|
+
const status = await dockerStatus();
|
|
257
|
+
if (status !== "running") {
|
|
258
|
+
note(dockerHint(status));
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
258
261
|
const reaped = await reapOrphans();
|
|
259
|
-
if (reaped.length) note(`Stopped ${reaped.length} orphaned
|
|
262
|
+
if (reaped.length) note(`Stopped ${reaped.length} orphaned local WordPress${reaped.length === 1 ? "" : "s"}: ${reaped.join(", ")}`);
|
|
260
263
|
const cfg = await resolveDevConfig(cwd);
|
|
261
|
-
|
|
264
|
+
ensureGitignored(cfg.configDir, ".kizlo/");
|
|
262
265
|
await startForeground(cfg);
|
|
263
266
|
}
|
|
264
267
|
const subCommands = {
|
|
265
268
|
stop: defineCommand({
|
|
266
269
|
meta: {
|
|
267
270
|
name: "stop",
|
|
268
|
-
description: "Stop
|
|
269
|
-
},
|
|
270
|
-
async run() {
|
|
271
|
-
if (!await requireDevStack(process.cwd())) return;
|
|
272
|
-
const { stack } = await resolve$1(process.cwd());
|
|
273
|
-
await withSpinner("Stopping WordPress dev stack", () => stack.composeStop(), "Stack stopped (volumes kept)");
|
|
274
|
-
}
|
|
275
|
-
}),
|
|
276
|
-
down: defineCommand({
|
|
277
|
-
meta: {
|
|
278
|
-
name: "down",
|
|
279
|
-
description: "Stop and remove the dev stack containers (keeps the database and files)"
|
|
271
|
+
description: "Stop local WordPress, keeping the database volume"
|
|
280
272
|
},
|
|
281
273
|
async run() {
|
|
282
|
-
if (!await
|
|
274
|
+
if (!await requireLocalWordPress(process.cwd())) return;
|
|
283
275
|
const { stack } = await resolve$1(process.cwd());
|
|
284
|
-
await withSpinner("
|
|
276
|
+
await withSpinner("Stopping local WordPress", () => stack.composeStop(), "Local WordPress stopped (volumes kept)");
|
|
285
277
|
}
|
|
286
278
|
}),
|
|
287
279
|
reset: defineCommand({
|
|
@@ -290,21 +282,21 @@ const subCommands = {
|
|
|
290
282
|
description: "Wipe the database and the install folder so the next `kizlo dev` rebuilds fresh"
|
|
291
283
|
},
|
|
292
284
|
async run() {
|
|
293
|
-
if (!await
|
|
285
|
+
if (!await requireLocalWordPress(process.cwd())) return;
|
|
294
286
|
const { cfg, stack } = await resolve$1(process.cwd());
|
|
295
|
-
await withSpinner("Wiping WordPress
|
|
287
|
+
await withSpinner("Wiping local WordPress", () => stack.composeDown({ volumes: true }), "Local WordPress reset successfully");
|
|
296
288
|
rmSync(cfg.wordpressDir, {
|
|
297
289
|
recursive: true,
|
|
298
290
|
force: true
|
|
299
291
|
});
|
|
300
|
-
note("Run `kizlo dev` to rebuild
|
|
292
|
+
note("Run `kizlo dev` to rebuild fresh.");
|
|
301
293
|
}
|
|
302
294
|
})
|
|
303
295
|
};
|
|
304
296
|
const dev = defineCommand({
|
|
305
297
|
meta: {
|
|
306
298
|
name: "dev",
|
|
307
|
-
description: "Run
|
|
299
|
+
description: "Run local WordPress and the contract watcher (stop | reset)"
|
|
308
300
|
},
|
|
309
301
|
subCommands,
|
|
310
302
|
run: groupDefault(Object.keys(subCommands), bringUp)
|
|
@@ -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,85 @@ 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
|
+
if (!hasKizlo) {
|
|
189
|
+
const spec = `kizlo@${manifest?.dependencies?.kizlo ?? `^${getVersion()}`}`;
|
|
190
|
+
const s = p.spinner();
|
|
191
|
+
s.start(`Installing kizlo with ${pm}`);
|
|
192
|
+
const ok = await runCommandAsync(addDependencyArgs(pm, spec), cwd, "ignore");
|
|
193
|
+
s.stop(ok ? "Installed kizlo" : "Could not install kizlo automatically");
|
|
194
|
+
if (!ok) p.log.warn(`Install it yourself: ${addDependencyArgs(pm, spec).join(" ")}`);
|
|
195
|
+
}
|
|
196
|
+
if (preset.apiPath && setup.baseUrl) setup.baseUrl = withApiPath(setup.baseUrl, preset.apiPath);
|
|
197
|
+
await setupLocalWordPress(cwd, setup);
|
|
198
|
+
await writeEnv(cwd, envKeys, setup, {
|
|
240
199
|
force,
|
|
241
|
-
yes
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
200
|
+
yes,
|
|
201
|
+
exampleTemplate: fetched ? readEnvExample(fetched.dir) : void 0
|
|
202
|
+
});
|
|
203
|
+
await syncRemote(setup);
|
|
204
|
+
const dirRel = setup.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
205
|
+
const serverDirRel = path.join(dirRel, "server");
|
|
206
|
+
const clientUrl = setup.siteUrl && !sameOrigin(setup.siteUrl, setup.baseUrl) ? setup.baseUrl : void 0;
|
|
207
|
+
const scaffold = buildScaffoldContext(cwd, {
|
|
208
|
+
dirRel,
|
|
209
|
+
appDir: detectAppDir(cwd, hasSrcDir),
|
|
210
|
+
alias: setup.alias,
|
|
211
|
+
clientUrl
|
|
212
|
+
});
|
|
213
|
+
const files = [{
|
|
214
|
+
label: "Kizlo config",
|
|
215
|
+
relPath: "kizlo.config.ts",
|
|
216
|
+
contents: kizloConfigTemplate(dirRel, setup.alias, setup.mode === "local")
|
|
217
|
+
}];
|
|
218
|
+
if (manifest && fetched) {
|
|
219
|
+
await alignDependencies(cwd, pm, pkg, manifest);
|
|
220
|
+
for (const entry of fileEntries(changesFor(manifest, "init"))) files.push(adaptFile(fetched.dir, entry, manifest.conventions, scaffold));
|
|
221
|
+
} else if (preset.scaffolds) files.push(...preset.scaffolds(scaffold));
|
|
222
|
+
const scaffolded = [];
|
|
223
|
+
for (const file of files) scaffolded.push({
|
|
224
|
+
file,
|
|
225
|
+
result: await scaffoldFile(cwd, file, {
|
|
226
|
+
force,
|
|
227
|
+
yes
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
writeGeneratedContract(cwd, serverDirRel);
|
|
231
|
+
const gitignore = ensureGitignored(cwd, ".env");
|
|
232
|
+
ensureGitignored(cwd, ".kizlo/");
|
|
233
|
+
for (const { file, result } of scaffolded) reportScaffold(file, result, yes);
|
|
234
|
+
if (manifest) applyLayoutPatches(cwd, patchEntries(changesFor(manifest, "init")), manifest.conventions, scaffold);
|
|
235
|
+
if (gitignore !== "present") p.log.success(`${gitignore === "created" ? "Created" : "Updated"} .gitignore (ignoring .env)`);
|
|
236
|
+
nextStepsNote(setup);
|
|
237
|
+
p.outro("Kizlo is ready 🎉");
|
|
238
|
+
} finally {
|
|
239
|
+
fetched?.cleanup();
|
|
240
|
+
}
|
|
251
241
|
}
|
|
252
242
|
});
|
|
253
243
|
|