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