kizlo 0.13.0 → 0.14.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 +91 -55
- package/dist/cli/commands/_tasks.js +152 -0
- package/dist/cli/commands/_wiring.js +11 -10
- package/dist/cli/commands/create.js +270 -87
- package/dist/cli/commands/init.js +122 -61
- package/dist/cli/daemon/config.js +1 -1
- package/dist/cli/presets/source.js +13 -8
- package/dist/cli/presets/template.js +255 -50
- package/dist/cli/utils.js +149 -80
- package/dist/integrations/tanstack-start/api.d.ts +15 -0
- package/dist/integrations/tanstack-start/api.js +21 -0
- package/dist/integrations/tanstack-start/kizlo.client.d.ts +20 -0
- package/dist/integrations/tanstack-start/kizlo.client.js +18 -0
- package/dist/integrations/tanstack-start/kizlo.server.d.ts +14 -0
- package/dist/integrations/tanstack-start/kizlo.server.js +15 -0
- package/dist/integrations/tanstack-start/manifest.d.ts +9 -0
- package/dist/integrations/tanstack-start/manifest.js +11 -0
- package/dist/integrations/tanstack-start/metadata.d.ts +49 -0
- package/dist/integrations/tanstack-start/metadata.js +112 -0
- package/dist/integrations/tanstack-start/robots.d.ts +8 -0
- package/dist/integrations/tanstack-start/robots.js +11 -0
- package/dist/integrations/tanstack-start/root-head.d.ts +15 -0
- package/dist/integrations/tanstack-start/root-head.js +54 -0
- package/dist/integrations/tanstack-start/sitemap.d.ts +16 -0
- package/dist/integrations/tanstack-start/sitemap.js +20 -0
- package/dist/integrations/tanstack-start/utils.d.ts +20 -0
- package/dist/integrations/tanstack-start/utils.js +17 -0
- package/dist/package.js +1 -1
- package/dist/router.d.ts +5 -5
- package/dist/tanstack-start/server.d.ts +11 -0
- package/dist/tanstack-start/server.js +12 -0
- package/dist/tanstack-start.d.ts +4 -0
- package/dist/tanstack-start.js +5 -0
- package/package.json +22 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { approveBuildsCommand, ensureGitignored,
|
|
1
|
+
import { approveBuildsCommand, cleanupWorkspaceArtifacts, detectInvokingPackageManager, detectPackageManager, ensureGitignored, findWorkspaceRoot, initGitRepository, installArgs, isCommandAvailable, isGitRepository, isWorkspaceMember, runCommandCapturedAsync, tokenizeCommand } from "../utils.js";
|
|
2
2
|
import { fetchRegistry, listTemplates, resolveRegistry } from "../presets/source.js";
|
|
3
3
|
import { adaptFile, changesFor, fileEntries, minCliError, patchEntries, readManifest, resolveDependencies } from "../presets/template.js";
|
|
4
|
-
import { collectConnectionInteractively, nextStepsLines, orCancel, pickAppPort, readEnvExample, resolveEnvKeys, selectPackageManager,
|
|
5
|
-
import {
|
|
4
|
+
import { collectConnectionFromEnv, collectConnectionInteractively, collectTemplatePrompts, nextStepsLines, orCancel, pickAppPort, provisionLocalWordPress, readEnvExample, resolveEnvKeys, selectPackageManager, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
|
|
5
|
+
import { FINAL_CONFIRMATION_TEXT, StepError, confirmProceed, runChecklist, summaryNote } from "./_tasks.js";
|
|
6
|
+
import { applyProjectPatches, buildScaffoldContext, kizloConfigTemplate, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
|
|
6
7
|
import z from "zod/v4";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import fs from "node:fs";
|
|
@@ -24,12 +25,94 @@ function normalizeProjectName(value) {
|
|
|
24
25
|
return value.trim().replace(/^\.\/+/, "").replace(/[/\\]+$/, "");
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
* dir
|
|
29
|
-
*
|
|
30
|
-
|
|
28
|
+
* How a chosen name is shown back to the user. A bare folder is displayed as `./my-app` so it's obvious it
|
|
29
|
+
* lands in the current dir; a name that's already a path (relative `apps/my-app`, `../my-app`, or an
|
|
30
|
+
* absolute path) is shown as-is. Display only — the stored name stays normalized (no leading `./`).
|
|
31
|
+
*/
|
|
32
|
+
function displayProjectName(value) {
|
|
33
|
+
return /[/\\]/.test(value) ? value : `./${value}`;
|
|
34
|
+
}
|
|
35
|
+
const NAME_ADJECTIVES = [
|
|
36
|
+
"amber",
|
|
37
|
+
"brave",
|
|
38
|
+
"calm",
|
|
39
|
+
"clever",
|
|
40
|
+
"cosmic",
|
|
41
|
+
"eager",
|
|
42
|
+
"fancy",
|
|
43
|
+
"gentle",
|
|
44
|
+
"happy",
|
|
45
|
+
"jolly",
|
|
46
|
+
"keen",
|
|
47
|
+
"lively",
|
|
48
|
+
"lucky",
|
|
49
|
+
"mellow",
|
|
50
|
+
"nimble",
|
|
51
|
+
"plucky",
|
|
52
|
+
"quiet",
|
|
53
|
+
"rapid",
|
|
54
|
+
"shiny",
|
|
55
|
+
"smooth",
|
|
56
|
+
"sunny",
|
|
57
|
+
"swift",
|
|
58
|
+
"witty",
|
|
59
|
+
"zesty"
|
|
60
|
+
];
|
|
61
|
+
const NAME_NOUNS = [
|
|
62
|
+
"otter",
|
|
63
|
+
"falcon",
|
|
64
|
+
"maple",
|
|
65
|
+
"comet",
|
|
66
|
+
"harbor",
|
|
67
|
+
"pixel",
|
|
68
|
+
"willow",
|
|
69
|
+
"ember",
|
|
70
|
+
"meadow",
|
|
71
|
+
"cactus",
|
|
72
|
+
"lotus",
|
|
73
|
+
"badger",
|
|
74
|
+
"cobra",
|
|
75
|
+
"gecko",
|
|
76
|
+
"heron",
|
|
77
|
+
"koala",
|
|
78
|
+
"lemur",
|
|
79
|
+
"marble",
|
|
80
|
+
"nebula",
|
|
81
|
+
"onyx",
|
|
82
|
+
"quartz",
|
|
83
|
+
"raven",
|
|
84
|
+
"tulip",
|
|
85
|
+
"wren"
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* A throwaway two-word name (`brave-otter`) used as the name prompt's placeholder and default. We
|
|
89
|
+
* generate it from small curated word lists rather than pulling in a name-generator dependency: it's
|
|
90
|
+
* only a placeholder, and every combination is a valid folder name that satisfies `projectNameSchema`.
|
|
91
|
+
* The default is deliberately not the current directory's name — an empty submit should scaffold into a
|
|
92
|
+
* fresh subdirectory, never the current dir (entering `.` is the explicit opt-in for that).
|
|
93
|
+
*/
|
|
94
|
+
function randomProjectName() {
|
|
95
|
+
const pick = (list) => list[Math.floor(Math.random() * list.length)];
|
|
96
|
+
return `${pick(NAME_ADJECTIVES)}-${pick(NAME_NOUNS)}`;
|
|
97
|
+
}
|
|
98
|
+
const SCAFFOLD_IGNORED_ENTRIES = new Set([".git", ".DS_Store"]);
|
|
99
|
+
/**
|
|
100
|
+
* Whether `dir` is empty enough to scaffold into with `.` — nothing but the benign entries above. Used to
|
|
101
|
+
* reject scaffolding over a non-empty current directory before the framework CLI runs (it would refuse
|
|
102
|
+
* anyway, but this gives a clear Kizlo-level message instead of the CLI's).
|
|
103
|
+
*/
|
|
104
|
+
function isDirScaffoldable(dir) {
|
|
105
|
+
return fs.readdirSync(dir).every((entry) => SCAFFOLD_IGNORED_ENTRIES.has(entry));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A project name may be a bare folder (`my-app`), a path to scaffold somewhere other than the current
|
|
109
|
+
* dir — relative (`apps/my-app`, `../my-app`) or absolute (`/srv/my-app`) — or a lone `.` to scaffold
|
|
110
|
+
* into the current directory itself. Only the last segment is the app folder, so that's what's validated
|
|
111
|
+
* as a name; the parent dirs (which may not exist yet) are created by the framework's own CLI. `.`/`..`
|
|
112
|
+
* as the final segment of a longer path, or a name with no real segment, is rejected.
|
|
31
113
|
*/
|
|
32
114
|
const projectNameSchema = z.string().trim().min(1, "Required").refine((value) => {
|
|
115
|
+
if (value === ".") return true;
|
|
33
116
|
const base = folderSegment(value);
|
|
34
117
|
return base.length > 0 && base !== "." && base !== ".." && /^[A-Za-z0-9._-]+$/.test(base);
|
|
35
118
|
}, "The app folder (last path segment) can only contain letters, numbers, dots, and dashes");
|
|
@@ -63,16 +146,29 @@ function sortDependencies(deps) {
|
|
|
63
146
|
return Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
64
147
|
}
|
|
65
148
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
149
|
+
* Each manager's command to download-and-run a package (npm's `create` mangles a name into `create-*`, so
|
|
150
|
+
* a plain CLI like `@tanstack/cli` must go through exec instead). Substituted for the `{{dlx}}` token.
|
|
151
|
+
*/
|
|
152
|
+
const DLX_COMMAND = {
|
|
153
|
+
pnpm: "pnpm dlx",
|
|
154
|
+
yarn: "yarn dlx",
|
|
155
|
+
npm: "npx",
|
|
156
|
+
bun: "bunx"
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* The argv `create` runs to bootstrap a fresh base app with the framework's own official CLI, built from
|
|
160
|
+
* the template manifest's `bootstrap` command — the template is the single source of truth for the whole
|
|
161
|
+
* invocation. The CLI imposes no shape: it substitutes the `{{dlx}}` (the manager's exec command),
|
|
162
|
+
* `{{pm}}` (chosen manager), and `{{name}}` (project name) tokens, plus a `{{token}}` for every declared
|
|
163
|
+
* prompt (from `promptArgs`), then tokenizes the result into argv. A prompt fragment may be empty (drops
|
|
164
|
+
* out when tokenized) or several flags. `undefined` when the manifest declares no bootstrap (an
|
|
165
|
+
* `init`-only template).
|
|
71
166
|
*/
|
|
72
|
-
function bootstrapArgs(manifest, pm, name) {
|
|
73
|
-
if (!manifest.
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
function bootstrapArgs(manifest, pm, name, promptArgs = {}) {
|
|
168
|
+
if (!manifest.create) return void 0;
|
|
169
|
+
let command = manifest.create.command.replaceAll("{{dlx}}", DLX_COMMAND[pm]).replaceAll("{{pm}}", pm).replaceAll("{{name}}", name);
|
|
170
|
+
for (const [token, fragment] of Object.entries(promptArgs)) command = command.replaceAll(`{{${token}}}`, fragment);
|
|
171
|
+
return tokenizeCommand(command);
|
|
76
172
|
}
|
|
77
173
|
/**
|
|
78
174
|
* Layer Kizlo's wiring onto a freshly bootstrapped app in `dir`, from an already-fetched template
|
|
@@ -87,10 +183,10 @@ function bootstrapArgs(manifest, pm, name) {
|
|
|
87
183
|
* files are written with `force`, silently replacing the framework defaults.
|
|
88
184
|
*/
|
|
89
185
|
async function applyManifestWiring(dir, templateDir, manifest, opts) {
|
|
90
|
-
const {
|
|
186
|
+
const { alias, kizloPath } = manifest.config;
|
|
91
187
|
const scaffold = buildScaffoldContext(dir, {
|
|
92
|
-
dirRel:
|
|
93
|
-
|
|
188
|
+
dirRel: kizloPath,
|
|
189
|
+
hasSrcDir: fs.existsSync(path.join(dir, "src")),
|
|
94
190
|
alias,
|
|
95
191
|
clientUrl: void 0
|
|
96
192
|
});
|
|
@@ -99,16 +195,16 @@ async function applyManifestWiring(dir, templateDir, manifest, opts) {
|
|
|
99
195
|
const files = [{
|
|
100
196
|
label: "Kizlo config",
|
|
101
197
|
relPath: "kizlo.config.ts",
|
|
102
|
-
contents: kizloConfigTemplate(
|
|
103
|
-
}, ...fileEntries(changes).map((entry) => adaptFile(templateDir, entry, manifest.
|
|
104
|
-
for (const file of files)
|
|
198
|
+
contents: kizloConfigTemplate(kizloPath, alias, opts.localDev)
|
|
199
|
+
}, ...fileEntries(changes).map((entry) => adaptFile(templateDir, entry, manifest.config, scaffold))];
|
|
200
|
+
for (const file of files) await scaffoldFile(dir, file, {
|
|
105
201
|
force: true,
|
|
106
202
|
yes: false
|
|
107
|
-
})
|
|
108
|
-
writeGeneratedContract(dir, path.join(
|
|
203
|
+
});
|
|
204
|
+
writeGeneratedContract(dir, path.join(kizloPath, "server"));
|
|
109
205
|
ensureGitignored(dir, ".env");
|
|
110
206
|
ensureGitignored(dir, ".kizlo/");
|
|
111
|
-
applyProjectPatches(dir, patchEntries(changes), manifest.
|
|
207
|
+
applyProjectPatches(dir, patchEntries(changes), manifest.config, scaffold);
|
|
112
208
|
}
|
|
113
209
|
const create = defineCommand({
|
|
114
210
|
meta: {
|
|
@@ -126,12 +222,19 @@ const create = defineCommand({
|
|
|
126
222
|
required: false,
|
|
127
223
|
description: "Project folder to create — a bare name or a path (e.g. apps/my-app, ../my-app, /srv/my-app)"
|
|
128
224
|
},
|
|
225
|
+
yes: {
|
|
226
|
+
type: "boolean",
|
|
227
|
+
alias: "y",
|
|
228
|
+
description: "Skip prompts and scaffold with defaults (non-interactive); requires the template and name",
|
|
229
|
+
default: false
|
|
230
|
+
},
|
|
129
231
|
source: {
|
|
130
232
|
type: "string",
|
|
131
233
|
description: "Where to scaffold from — a local dir or giget source, either a registry of templates or a single template (default: Kizlo's GitHub templates)"
|
|
132
234
|
}
|
|
133
235
|
},
|
|
134
236
|
async run({ args }) {
|
|
237
|
+
const yes = Boolean(args.yes);
|
|
135
238
|
const cwd = process.cwd();
|
|
136
239
|
p.intro(`Let's create a new Kizlo application`);
|
|
137
240
|
const registry = resolveRegistry(args.source ? String(args.source) : void 0);
|
|
@@ -153,12 +256,14 @@ const create = defineCommand({
|
|
|
153
256
|
p.cancel(message);
|
|
154
257
|
process.exit(1);
|
|
155
258
|
};
|
|
259
|
+
const fail = cancelFrom;
|
|
156
260
|
const requested = args.template;
|
|
157
261
|
let selected;
|
|
158
262
|
if (requested) {
|
|
159
263
|
selected = templates.find((entry) => entry.id === requested);
|
|
160
264
|
if (!selected) return cancelFrom(`Unknown template "${requested}". Available: ${templates.map((entry) => entry.id).join(", ")}`);
|
|
161
|
-
} else {
|
|
265
|
+
} else if (yes) return cancelFrom(`--yes needs a template: kizlo create <template> <name> --yes (available: ${templates.map((e) => e.id).join(", ")})`);
|
|
266
|
+
else {
|
|
162
267
|
const id = orCancel(await p.select({
|
|
163
268
|
message: "Template",
|
|
164
269
|
options: templates.map((entry) => ({
|
|
@@ -172,83 +277,161 @@ const create = defineCommand({
|
|
|
172
277
|
const template = selected.id;
|
|
173
278
|
const templateDir = selected.dir;
|
|
174
279
|
const label = selected.label;
|
|
175
|
-
const fail = cancelFrom;
|
|
176
280
|
const manifest = readManifest(templateDir);
|
|
177
281
|
const minErr = minCliError(manifest);
|
|
178
282
|
if (minErr) return fail(minErr);
|
|
283
|
+
if (yes && !args.name) return cancelFrom("--yes needs a project name: kizlo create <template> <name> --yes");
|
|
284
|
+
const defaultName = randomProjectName();
|
|
179
285
|
const name = normalizeProjectName(args.name ? String(args.name) : orCancel(await p.text({
|
|
180
|
-
message:
|
|
181
|
-
placeholder:
|
|
182
|
-
|
|
286
|
+
message: `What is your project named? (enter for "${displayProjectName(defaultName)}", or "." for the current directory)`,
|
|
287
|
+
placeholder: displayProjectName(defaultName),
|
|
288
|
+
defaultValue: displayProjectName(defaultName),
|
|
289
|
+
validate: (value) => value ? projectName(value) : void 0
|
|
183
290
|
})));
|
|
184
291
|
const invalid = projectName(name);
|
|
185
|
-
if (invalid)
|
|
186
|
-
|
|
187
|
-
|
|
292
|
+
if (invalid) return cancelFrom(invalid);
|
|
293
|
+
const inCurrentDir = name === ".";
|
|
294
|
+
const displayName = inCurrentDir ? "current directory" : displayProjectName(name);
|
|
295
|
+
const dir = inCurrentDir ? cwd : path.resolve(cwd, name);
|
|
296
|
+
if (!inCurrentDir && fs.existsSync(dir)) return cancelFrom(`${name} already exists — pick a different name or remove it.`);
|
|
297
|
+
if (inCurrentDir && !isDirScaffoldable(cwd)) {
|
|
298
|
+
if (yes) return cancelFrom("The current directory isn't empty — scaffold into an empty directory, or pass a name instead of `.`.");
|
|
299
|
+
p.log.warn("The current directory isn't empty — scaffolding here may overwrite or conflict with existing files.");
|
|
300
|
+
if (!orCancel(await p.confirm({
|
|
301
|
+
message: "Scaffold into it anyway?",
|
|
302
|
+
initialValue: false
|
|
303
|
+
}))) {
|
|
304
|
+
fetchedRegistry.cleanup();
|
|
305
|
+
p.cancel("Setup cancelled.");
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
188
308
|
}
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const
|
|
309
|
+
const parentDir = path.dirname(dir);
|
|
310
|
+
const workspace = findWorkspaceRoot(parentDir);
|
|
311
|
+
const detectedPm = await detectPackageManager(workspace?.root ?? parentDir);
|
|
312
|
+
const pm = detectedPm ?? (yes ? detectInvokingPackageManager() ?? "npm" : await selectPackageManager("Package manager"));
|
|
313
|
+
const templatePrompts = await collectTemplatePrompts(manifest, { yes });
|
|
314
|
+
const baseUrl = `http://localhost:${await pickAppPort()}`;
|
|
315
|
+
const conn = yes ? collectConnectionFromEnv(resolveEnvKeys(manifest)) : await collectConnectionInteractively(manifest.config.apiPath, { baseUrl });
|
|
316
|
+
if (yes && !conn.baseUrl) conn.baseUrl = baseUrl;
|
|
317
|
+
if (manifest.config.apiPath && conn.baseUrl) conn.baseUrl = withApiPath(conn.baseUrl, manifest.config.apiPath);
|
|
318
|
+
const includeExamples = yes ? true : orCancel(await p.confirm({
|
|
195
319
|
message: "Add examples?",
|
|
196
320
|
initialValue: true
|
|
197
321
|
}));
|
|
198
|
-
const
|
|
199
|
-
if (!bootstrap) return fail(`Template "${template}" can't be scaffolded — its manifest declares no framework bootstrap.`);
|
|
200
|
-
const s = p.spinner();
|
|
201
|
-
s.start(`Creating ${name} with the ${label} CLI`);
|
|
202
|
-
const scaffold = runCommandCaptured(bootstrap, cwd);
|
|
203
|
-
s.stop(scaffold.ok ? `Created ${name} with the ${label} CLI` : `${label} setup failed`);
|
|
204
|
-
if (!scaffold.ok) {
|
|
205
|
-
if (scaffold.output) p.log.error(scaffold.output);
|
|
206
|
-
fail(`${label} setup failed — see the output above and try again.`);
|
|
207
|
-
}
|
|
208
|
-
const exampleTemplate = readEnvExample(templateDir);
|
|
209
|
-
try {
|
|
210
|
-
await applyManifestWiring(dir, templateDir, manifest, {
|
|
211
|
-
includeExamples,
|
|
212
|
-
localDev: conn.mode === "local"
|
|
213
|
-
});
|
|
214
|
-
} catch (error) {
|
|
215
|
-
fail(error instanceof Error ? error.message : String(error));
|
|
216
|
-
}
|
|
217
|
-
fetchedRegistry.cleanup();
|
|
218
|
-
let depsInstalled = false;
|
|
219
|
-
let approveBuilds;
|
|
220
|
-
if (orCancel(await p.confirm({
|
|
322
|
+
const installDeps = yes ? true : orCancel(await p.confirm({
|
|
221
323
|
message: "Install dependencies now?",
|
|
222
324
|
initialValue: true
|
|
223
|
-
}))
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const install = await runCommandCapturedAsync(installArgs(pm), dir);
|
|
227
|
-
depsInstalled = install.ok;
|
|
228
|
-
if (depsInstalled) approveBuilds = approveBuildsCommand(pm, install.output);
|
|
229
|
-
is.stop(depsInstalled ? "Installed dependencies" : "Could not install dependencies");
|
|
230
|
-
if (!depsInstalled) p.log.warn(`Install them yourself: cd ${name} && ${installArgs(pm).join(" ")}`);
|
|
231
|
-
}
|
|
232
|
-
await setupLocalWordPress(dir, conn);
|
|
233
|
-
await writeEnv(dir, resolveEnvKeys(manifest), conn, {
|
|
234
|
-
force: true,
|
|
235
|
-
yes: false,
|
|
236
|
-
exampleTemplate
|
|
237
|
-
});
|
|
238
|
-
await syncRemote(conn);
|
|
239
|
-
if (isCommandAvailable("git") && !isGitRepository(dir) && orCancel(await p.confirm({
|
|
325
|
+
}));
|
|
326
|
+
const canGit = isCommandAvailable("git") && !workspace && !isGitRepository(cwd);
|
|
327
|
+
const initGit = canGit && (yes || orCancel(await p.confirm({
|
|
240
328
|
message: "Initialize a git repository?",
|
|
241
329
|
initialValue: true
|
|
242
|
-
})))
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
330
|
+
})));
|
|
331
|
+
const bootstrap = bootstrapArgs(manifest, pm, name, templatePrompts.args);
|
|
332
|
+
if (!bootstrap) return fail(`Template "${template}" can't be scaffolded — its manifest declares no framework bootstrap.`);
|
|
333
|
+
summaryNote([
|
|
334
|
+
["Template", label],
|
|
335
|
+
...templatePrompts.rows,
|
|
336
|
+
[inCurrentDir || /[/\\]/.test(name) ? "Location" : "Project", displayName],
|
|
337
|
+
["Package manager", `${pm}${detectedPm ? " (detected)" : ""}`],
|
|
338
|
+
["API URL", conn.baseUrl],
|
|
339
|
+
["WordPress", conn.mode === "local" ? "Local" : "Remote"],
|
|
340
|
+
["Examples", includeExamples ? "Yes" : "No"],
|
|
341
|
+
["Install dependencies", installDeps ? "Yes" : "No"],
|
|
342
|
+
...canGit ? [["Git repository", initGit ? "Yes" : "No"]] : []
|
|
343
|
+
]);
|
|
344
|
+
if (!yes && !await confirmProceed(FINAL_CONFIRMATION_TEXT)) {
|
|
345
|
+
fetchedRegistry.cleanup();
|
|
346
|
+
p.cancel("Setup cancelled.");
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
const exampleTemplate = readEnvExample(templateDir);
|
|
350
|
+
const warnings = [];
|
|
351
|
+
const result = { depsInstalled: false };
|
|
352
|
+
const aborted = await runChecklist([
|
|
353
|
+
{
|
|
354
|
+
title: inCurrentDir ? `Scaffolding into the current directory with the ${label} CLI` : `Creating ${name} with the ${label} CLI`,
|
|
355
|
+
run: async () => {
|
|
356
|
+
const scaffold = await runCommandCapturedAsync(bootstrap, cwd);
|
|
357
|
+
if (!scaffold.ok) throw new StepError(`${label} setup failed`, { detail: scaffold.output || void 0 });
|
|
358
|
+
return inCurrentDir ? `Scaffolded into the current directory with the ${label} CLI` : `Created ${name} with the ${label} CLI`;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
title: "Wiring Kizlo into the project",
|
|
363
|
+
run: async () => {
|
|
364
|
+
await applyManifestWiring(dir, templateDir, manifest, {
|
|
365
|
+
includeExamples,
|
|
366
|
+
localDev: conn.mode === "local"
|
|
367
|
+
});
|
|
368
|
+
return "Wired Kizlo into the project";
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
title: "Setting up local WordPress (first run downloads images, this can take a while)",
|
|
373
|
+
enabled: conn.mode === "local",
|
|
374
|
+
run: async () => {
|
|
375
|
+
try {
|
|
376
|
+
const warning = await provisionLocalWordPress(dir, conn);
|
|
377
|
+
if (warning) warnings.push(warning);
|
|
378
|
+
return "Local WordPress ready";
|
|
379
|
+
} catch (error) {
|
|
380
|
+
throw new StepError("Local WordPress setup failed", { detail: error instanceof Error ? error.message : String(error) });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
title: "Writing environment files",
|
|
386
|
+
run: () => writeEnv(dir, resolveEnvKeys(manifest), conn, {
|
|
387
|
+
overwrite: true,
|
|
388
|
+
exampleTemplate
|
|
389
|
+
})
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
title: "Syncing settings to WordPress",
|
|
393
|
+
enabled: conn.mode === "remote" && Boolean(conn.wpUrl && conn.wpUsername && conn.wpPassword),
|
|
394
|
+
run: async () => {
|
|
395
|
+
warnings.push(...await syncRemote(conn));
|
|
396
|
+
return "Synced settings to WordPress";
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
title: "Initializing git repository",
|
|
401
|
+
enabled: initGit,
|
|
402
|
+
run: () => {
|
|
403
|
+
if (!initGitRepository(dir)) throw new StepError("Could not initialize a git repository", { fatal: false });
|
|
404
|
+
return "Initialized git repository";
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
title: `Installing dependencies with ${pm}`,
|
|
409
|
+
enabled: installDeps,
|
|
410
|
+
run: async () => {
|
|
411
|
+
const member = workspace ? isWorkspaceMember(workspace, dir) : false;
|
|
412
|
+
if (member && workspace) cleanupWorkspaceArtifacts(dir);
|
|
413
|
+
const installCwd = member && workspace ? workspace.root : dir;
|
|
414
|
+
const install = await runCommandCapturedAsync(installArgs(pm), installCwd);
|
|
415
|
+
result.depsInstalled = install.ok;
|
|
416
|
+
if (!install.ok) throw new StepError("Could not install dependencies", {
|
|
417
|
+
detail: install.output || void 0,
|
|
418
|
+
fatal: false
|
|
419
|
+
});
|
|
420
|
+
result.approveBuilds = approveBuildsCommand(pm, install.output);
|
|
421
|
+
return "Installed dependencies";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
]);
|
|
425
|
+
fetchedRegistry.cleanup();
|
|
426
|
+
if (aborted) {
|
|
427
|
+
p.cancel("Setup failed — see the errors above.");
|
|
428
|
+
process.exit(1);
|
|
247
429
|
}
|
|
430
|
+
for (const warning of warnings) p.log.warn(warning);
|
|
248
431
|
p.note([
|
|
249
|
-
`cd ${name}
|
|
250
|
-
...depsInstalled ? [] : [`${pm} install`],
|
|
251
|
-
...approveBuilds ? [approveBuilds] : [],
|
|
432
|
+
...inCurrentDir ? [] : [`cd ${name}`],
|
|
433
|
+
...result.depsInstalled ? [] : [`${pm} install`],
|
|
434
|
+
...result.approveBuilds ? [result.approveBuilds] : [],
|
|
252
435
|
``,
|
|
253
436
|
...nextStepsLines(conn)
|
|
254
437
|
].join("\n"), "Next steps");
|