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.
Files changed (34) hide show
  1. package/dist/cli/commands/_setup.js +91 -55
  2. package/dist/cli/commands/_tasks.js +152 -0
  3. package/dist/cli/commands/_wiring.js +11 -10
  4. package/dist/cli/commands/create.js +270 -87
  5. package/dist/cli/commands/init.js +122 -61
  6. package/dist/cli/daemon/config.js +1 -1
  7. package/dist/cli/presets/source.js +13 -8
  8. package/dist/cli/presets/template.js +255 -50
  9. package/dist/cli/utils.js +149 -80
  10. package/dist/integrations/tanstack-start/api.d.ts +15 -0
  11. package/dist/integrations/tanstack-start/api.js +21 -0
  12. package/dist/integrations/tanstack-start/kizlo.client.d.ts +20 -0
  13. package/dist/integrations/tanstack-start/kizlo.client.js +18 -0
  14. package/dist/integrations/tanstack-start/kizlo.server.d.ts +14 -0
  15. package/dist/integrations/tanstack-start/kizlo.server.js +15 -0
  16. package/dist/integrations/tanstack-start/manifest.d.ts +9 -0
  17. package/dist/integrations/tanstack-start/manifest.js +11 -0
  18. package/dist/integrations/tanstack-start/metadata.d.ts +49 -0
  19. package/dist/integrations/tanstack-start/metadata.js +112 -0
  20. package/dist/integrations/tanstack-start/robots.d.ts +8 -0
  21. package/dist/integrations/tanstack-start/robots.js +11 -0
  22. package/dist/integrations/tanstack-start/root-head.d.ts +15 -0
  23. package/dist/integrations/tanstack-start/root-head.js +54 -0
  24. package/dist/integrations/tanstack-start/sitemap.d.ts +16 -0
  25. package/dist/integrations/tanstack-start/sitemap.js +20 -0
  26. package/dist/integrations/tanstack-start/utils.d.ts +20 -0
  27. package/dist/integrations/tanstack-start/utils.js +17 -0
  28. package/dist/package.js +1 -1
  29. package/dist/router.d.ts +5 -5
  30. package/dist/tanstack-start/server.d.ts +11 -0
  31. package/dist/tanstack-start/server.js +12 -0
  32. package/dist/tanstack-start.d.ts +4 -0
  33. package/dist/tanstack-start.js +5 -0
  34. package/package.json +22 -1
@@ -1,8 +1,9 @@
1
- import { approveBuildsCommand, ensureGitignored, frameworkCreateArgs, initGitRepository, installArgs, isCommandAvailable, isGitRepository, runCommandCaptured, runCommandCapturedAsync } from "../utils.js";
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, setupLocalWordPress, syncRemote, validate, withApiPath, writeEnv } from "./_setup.js";
5
- import { applyProjectPatches, buildScaffoldContext, kizloConfigTemplate, reportScaffold, scaffoldFile, writeGeneratedContract } from "./_wiring.js";
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
- * A project name may be a bare folder (`my-app`) or a path to scaffold somewhere other than the current
28
- * dir — relative (`apps/my-app`, `../my-app`) or absolute (`/srv/my-app`). Only the last segment is the
29
- * app folder, so that's what's validated as a name; the parent dirs (which may not exist yet) are created
30
- * by the framework's own CLI. `.`/`..` as the final segment, or a name with no real segment, is rejected.
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
- * The argv `create` runs to bootstrap a fresh base app with the framework's own official CLI, built
67
- * from the template manifest's `bootstrap` (initializer + flags) — the template is the single source
68
- * of truth for the framework. The CLI owns only the package-manager mechanics: {@link frameworkCreateArgs}
69
- * assembles the `<pm> create …` invocation, and the `{{pm}}` token in the flags is substituted with the
70
- * chosen manager (e.g. `--use-{{pm}}` → `--use-pnpm`).
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.bootstrap) return void 0;
74
- const flags = manifest.bootstrap.flags.map((flag) => flag.replaceAll("{{pm}}", pm));
75
- return frameworkCreateArgs(pm, manifest.bootstrap.initializer, name, flags);
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 { kizloDir, appDir, alias } = manifest.conventions;
186
+ const { alias, kizloPath } = manifest.config;
91
187
  const scaffold = buildScaffoldContext(dir, {
92
- dirRel: kizloDir,
93
- appDir,
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(kizloDir, alias, opts.localDev)
103
- }, ...fileEntries(changes).map((entry) => adaptFile(templateDir, entry, manifest.conventions, scaffold))];
104
- for (const file of files) reportScaffold(file, await scaffoldFile(dir, file, {
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
- }), false);
108
- writeGeneratedContract(dir, path.join(kizloDir, "server"));
203
+ });
204
+ writeGeneratedContract(dir, path.join(kizloPath, "server"));
109
205
  ensureGitignored(dir, ".env");
110
206
  ensureGitignored(dir, ".kizlo/");
111
- applyProjectPatches(dir, patchEntries(changes), manifest.conventions, scaffold);
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: "What is your project named?",
181
- placeholder: "my-app",
182
- validate: projectName
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
- p.cancel(invalid);
187
- process.exit(1);
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 dir = path.resolve(cwd, name);
190
- if (fs.existsSync(dir)) return cancelFrom(`${name} already exists — pick a different name or remove it.`);
191
- const pm = await selectPackageManager("Package manager");
192
- const conn = await collectConnectionInteractively(manifest.apiPath, { baseUrl: `http://localhost:${await pickAppPort()}` });
193
- if (manifest.apiPath && conn.baseUrl) conn.baseUrl = withApiPath(conn.baseUrl, manifest.apiPath);
194
- const includeExamples = orCancel(await p.confirm({
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 bootstrap = bootstrapArgs(manifest, pm, name);
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
- const is = p.spinner();
225
- is.start(`Installing dependencies with ${pm}`);
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
- const gs = p.spinner();
244
- gs.start("Initializing git repository");
245
- const ok = initGitRepository(dir);
246
- gs.stop(ok ? "Initialized git repository" : "Could not initialize git repository");
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");