@zitadel/cli 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (45) hide show
  1. package/README.md +116 -26
  2. package/SKILLS.md +102 -24
  3. package/dist/commands/apply.mjs +4 -5
  4. package/dist/commands/apply.mjs.map +1 -1
  5. package/dist/commands/doctor.mjs +214 -50
  6. package/dist/commands/doctor.mjs.map +1 -1
  7. package/dist/commands/eject.mjs +14 -7
  8. package/dist/commands/eject.mjs.map +1 -1
  9. package/dist/commands/logs.mjs +15 -7
  10. package/dist/commands/logs.mjs.map +1 -1
  11. package/dist/commands/plan.mjs +4 -5
  12. package/dist/commands/plan.mjs.map +1 -1
  13. package/dist/commands/reset.mjs +28 -12
  14. package/dist/commands/reset.mjs.map +1 -1
  15. package/dist/commands/setup.mjs +266 -106
  16. package/dist/commands/setup.mjs.map +1 -1
  17. package/dist/commands/start.mjs +180 -24
  18. package/dist/commands/start.mjs.map +1 -1
  19. package/dist/commands/status.mjs +40 -16
  20. package/dist/commands/status.mjs.map +1 -1
  21. package/dist/commands/stop.mjs +72 -11
  22. package/dist/commands/stop.mjs.map +1 -1
  23. package/dist/docker-CnGQK3ZK.mjs +432 -0
  24. package/dist/docker-CnGQK3ZK.mjs.map +1 -0
  25. package/dist/docker-guidance-ypN3IM3o.mjs +21 -0
  26. package/dist/docker-guidance-ypN3IM3o.mjs.map +1 -0
  27. package/dist/{oclif-DSPO9Sck.mjs → oclif-B7lBzh3R.mjs} +158 -63
  28. package/dist/oclif-B7lBzh3R.mjs.map +1 -0
  29. package/dist/orca-BoTFU8SI.mjs +2581 -0
  30. package/dist/orca-BoTFU8SI.mjs.map +1 -0
  31. package/dist/ports-B09RjuHx.mjs +111 -0
  32. package/dist/ports-B09RjuHx.mjs.map +1 -0
  33. package/dist/processes-Cw8TO1SY.mjs +120 -0
  34. package/dist/processes-Cw8TO1SY.mjs.map +1 -0
  35. package/dist/{project-Dwb9WVAT.mjs → project-Cd0L3PtM.mjs} +3 -3
  36. package/dist/{project-Dwb9WVAT.mjs.map → project-Cd0L3PtM.mjs.map} +1 -1
  37. package/dist/{sync-DmtTYNmq.mjs → sync-BojoQm2P.mjs} +3 -3
  38. package/dist/{sync-DmtTYNmq.mjs.map → sync-BojoQm2P.mjs.map} +1 -1
  39. package/oclif.manifest.json +49 -5
  40. package/package.json +8 -41
  41. package/dist/docker-C0aVpJqm.mjs +0 -209
  42. package/dist/docker-C0aVpJqm.mjs.map +0 -1
  43. package/dist/oclif-DSPO9Sck.mjs.map +0 -1
  44. package/dist/orca-DOxshV9n.mjs +0 -1009
  45. package/dist/orca-DOxshV9n.mjs.map +0 -1
@@ -1,14 +1,180 @@
1
- import { T as ZitadelError, r as DEFAULT_SERVER, t as BaseCommand } from "../oclif-DSPO9Sck.mjs";
2
- import { n as RENDERER_IDS, r as issuerFromPort, t as createOrca } from "../orca-DOxshV9n.mjs";
3
- import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-Dwb9WVAT.mjs";
4
- import { Flags } from "@oclif/core";
1
+ import { D as toZitadelError, E as ZitadelError, b as publicCliCommand, n as DEFAULT_SERVER, t as BaseCommand } from "../oclif-B7lBzh3R.mjs";
2
+ import { i as issuerFromPort, n as inspectScaffoldTarget, r as RENDERER_IDS, t as createOrca } from "../orca-BoTFU8SI.mjs";
3
+ import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-Cd0L3PtM.mjs";
4
+ import { t as listListeningPorts } from "../ports-B09RjuHx.mjs";
5
5
  import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
6
+ import { Flags } from "@oclif/core";
7
+ import { createZitadelClient } from "@zitadel/api/client";
6
8
  import { consola as consola$1 } from "consola";
7
- import { readFile, stat } from "node:fs/promises";
8
9
  import { basename, join } from "node:path";
9
- import { execFile } from "node:child_process";
10
- import { createZitadelClient } from "@zitadel/api/client";
10
+ import { readFile, stat } from "node:fs/promises";
11
+ import { spawn } from "node:child_process";
11
12
  import pc from "picocolors";
13
+ //#region src/lib/package-manager.ts
14
+ async function detectPackageManager(cwd) {
15
+ const declared = await packageManagerFromManifest(cwd);
16
+ if (declared) return declared;
17
+ if (await exists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18
+ if (await exists(join(cwd, "yarn.lock"))) return "yarn";
19
+ if (await exists(join(cwd, "bun.lock"))) return "bun";
20
+ if (await exists(join(cwd, "bun.lockb"))) return "bun";
21
+ if (await exists(join(cwd, "package-lock.json"))) return "npm";
22
+ return "npm";
23
+ }
24
+ function installCommandFor(packageManager) {
25
+ return command(packageManager, ["install"]);
26
+ }
27
+ function devCommandFor(packageManager) {
28
+ switch (packageManager) {
29
+ case "npm": return command("npm", ["run", "dev"]);
30
+ case "pnpm": return command("pnpm", ["dev"]);
31
+ case "yarn": return command("yarn", ["dev"]);
32
+ case "bun": return command("bun", ["run", "dev"]);
33
+ }
34
+ }
35
+ async function runPackageCommand(packageCommand, options) {
36
+ await new Promise((resolve, reject) => {
37
+ const child = spawn(packageCommand.command, packageCommand.args, {
38
+ cwd: options.cwd,
39
+ env: options.env ?? process.env,
40
+ stdio: options.redirectStdoutToStderr ? [
41
+ "ignore",
42
+ "pipe",
43
+ "pipe"
44
+ ] : "inherit"
45
+ });
46
+ if (options.redirectStdoutToStderr) {
47
+ child.stdout?.on("data", (chunk) => process.stderr.write(chunk));
48
+ child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
49
+ }
50
+ child.on("error", reject);
51
+ child.on("close", (code, signal) => {
52
+ if (code === 0) {
53
+ resolve();
54
+ return;
55
+ }
56
+ const detail = signal ? `signal ${signal}` : `exit ${String(code ?? 1)}`;
57
+ const error = /* @__PURE__ */ new Error(`${packageCommand.display} failed with ${detail}`);
58
+ Object.assign(error, {
59
+ code: code ?? 1,
60
+ signal
61
+ });
62
+ reject(error);
63
+ });
64
+ });
65
+ }
66
+ function command(packageManager, args) {
67
+ return {
68
+ command: packageManager,
69
+ args,
70
+ display: [packageManager, ...args].join(" ")
71
+ };
72
+ }
73
+ async function packageManagerFromManifest(cwd) {
74
+ try {
75
+ const raw = await readFile(join(cwd, "package.json"), "utf8");
76
+ const parsed = JSON.parse(raw);
77
+ return typeof parsed.packageManager === "string" ? packageManagerFromString(parsed.packageManager) : void 0;
78
+ } catch {
79
+ return;
80
+ }
81
+ }
82
+ function packageManagerFromString(value) {
83
+ const name = value.split("@")[0];
84
+ return name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" ? name : void 0;
85
+ }
86
+ async function exists(path) {
87
+ try {
88
+ await stat(path);
89
+ return true;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+ //#endregion
95
+ //#region src/commands/setup/install.ts
96
+ async function installDependenciesForSetup(input) {
97
+ const packageManager = await detectPackageManager(input.cwd);
98
+ const installCommand = installCommandFor(packageManager);
99
+ const devCommand = devCommandFor(packageManager);
100
+ if (!(input.scaffoldedFramework || input.depsAdded.length > 0)) return outcome({
101
+ install: {
102
+ status: "not-needed",
103
+ package_manager: packageManager,
104
+ command: installCommand.display,
105
+ reason: "no-dependency-changes"
106
+ },
107
+ devCommand: devCommand.display,
108
+ issuer: input.issuer,
109
+ includeInstallCommand: false
110
+ });
111
+ if (input.dryRun || input.skipInstall) return outcome({
112
+ install: {
113
+ status: "skipped",
114
+ package_manager: packageManager,
115
+ command: installCommand.display,
116
+ reason: input.dryRun ? "dry-run" : "skip-install"
117
+ },
118
+ devCommand: devCommand.display,
119
+ issuer: input.issuer,
120
+ includeInstallCommand: true
121
+ });
122
+ consola$1.start(`Installing dependencies with ${installCommand.display}`);
123
+ try {
124
+ await (input.run ?? runPackageCommand)(installCommand, {
125
+ cwd: input.cwd,
126
+ env: input.env,
127
+ redirectStdoutToStderr: input.json
128
+ });
129
+ } catch (error) {
130
+ throw installFailed(error, installCommand, devCommand, input.cwd);
131
+ }
132
+ consola$1.success("Installed dependencies");
133
+ return outcome({
134
+ install: {
135
+ status: "completed",
136
+ package_manager: packageManager,
137
+ command: installCommand.display
138
+ },
139
+ devCommand: devCommand.display,
140
+ issuer: input.issuer,
141
+ includeInstallCommand: false
142
+ });
143
+ }
144
+ function outcome(input) {
145
+ const startAction = `Start your project: ${input.devCommand} (then open ${input.issuer})`;
146
+ const verifyAction = "Verify auth in the browser: register a user, log out, log in again with the same user, and confirm /profile shows Signed in.";
147
+ return {
148
+ install: input.install,
149
+ devCommand: input.devCommand,
150
+ nextActions: input.includeInstallCommand ? [
151
+ `Install dependencies: ${input.install.command}`,
152
+ startAction,
153
+ verifyAction
154
+ ] : [startAction, verifyAction],
155
+ nextCommands: input.includeInstallCommand ? [input.install.command, input.devCommand] : [input.devCommand]
156
+ };
157
+ }
158
+ function installFailed(error, installCommand, devCommand, cwd) {
159
+ return new ZitadelError("E_VALIDATION", `Dependency install failed: ${installCommand.display}`, {
160
+ hint: `Run ${installCommand.display} in ${cwd}, then start the app with ${devCommand.display}.`,
161
+ nextCommands: [installCommand.display],
162
+ details: {
163
+ command: installCommand.display,
164
+ cwd,
165
+ original: errorShape(error)
166
+ }
167
+ });
168
+ }
169
+ function errorShape(error) {
170
+ if (error instanceof Error) return {
171
+ name: error.name,
172
+ message: error.message,
173
+ code: error.code
174
+ };
175
+ return { message: String(error) };
176
+ }
177
+ //#endregion
12
178
  //#region src/commands/setup/prompts/cancel.ts
13
179
  /**
14
180
  * Converts a clack cancellation (Ctrl-C) into a thrown `E_VALIDATION` rather
@@ -28,7 +194,8 @@ function bail(value) {
28
194
  * becomes the issuer URL (`http://localhost:<port>`) via `issuerFromPort`.
29
195
  */
30
196
  var DevPortPrompt = class {
31
- async ask(answers, _ctx) {
197
+ async ask(answers, ctx) {
198
+ if (ctx.devPortFromFlag) return answers;
32
199
  const value = await text({
33
200
  message: "Dev server port",
34
201
  placeholder: String(answers.devPort),
@@ -64,79 +231,6 @@ var FrameworkConfirmPrompt = class {
64
231
  }
65
232
  };
66
233
  //#endregion
67
- //#region src/lib/prober/ports.ts
68
- /**
69
- * Enumerate TCP ports currently in LISTEN state on the loopback interface
70
- * (`127.0.0.1`, `::1`, or the wildcard `*`). Spawns `lsof -iTCP -sTCP:LISTEN
71
- * -P -n -F n` and parses its machine-readable output. Returns the unique,
72
- * numerically-sorted list of ports.
73
- *
74
- * Never throws. Returns `[]` whenever lsof is unavailable (e.g. Windows,
75
- * unusual PATH), the spawn errors, exits non-zero, or the call exceeds
76
- * `timeoutMs` (default 1000ms). The caller treats an empty list the same as
77
- * "no listeners worth probing."
78
- */
79
- async function listListeningPorts(opts) {
80
- const timeoutMs = opts?.timeoutMs ?? 1e3;
81
- try {
82
- return parseLsofPorts(await runLsof(timeoutMs));
83
- } catch {
84
- return [];
85
- }
86
- }
87
- /**
88
- * Spawn `lsof` with the canned argv and resolve to its stdout. Hand-rolled
89
- * rather than `util.promisify(execFile)` because the latter resolves with a
90
- * `{stdout, stderr}` object via its custom-promisify symbol — a heavier shape
91
- * to mock and worse to read at the call site, where we only ever want stdout.
92
- */
93
- function runLsof(timeoutMs) {
94
- return new Promise((resolve, reject) => {
95
- execFile("lsof", [
96
- "-iTCP",
97
- "-sTCP:LISTEN",
98
- "-P",
99
- "-n",
100
- "-F",
101
- "n"
102
- ], {
103
- timeout: timeoutMs,
104
- encoding: "utf8"
105
- }, (err, stdout) => {
106
- if (err) {
107
- reject(err);
108
- return;
109
- }
110
- resolve(stdout);
111
- });
112
- });
113
- }
114
- /**
115
- * Parse the `n` records emitted by `lsof -F n`. Each record is a single line
116
- * `n<address>` where `<address>` ends in `:<port>` (e.g. `n*:8080`,
117
- * `n127.0.0.1:3000`, `n[::1]:5050`). Only loopback/wildcard hosts are kept;
118
- * external interface bindings are ignored.
119
- */
120
- function parseLsofPorts(stdout) {
121
- const ports = /* @__PURE__ */ new Set();
122
- for (const line of stdout.split(/\r?\n/)) {
123
- if (!line.startsWith("n")) continue;
124
- const address = line.slice(1);
125
- const colon = address.lastIndexOf(":");
126
- if (colon < 0) continue;
127
- const host = address.slice(0, colon);
128
- const portStr = address.slice(colon + 1);
129
- if (!isLoopback(host)) continue;
130
- const port = Number.parseInt(portStr, 10);
131
- if (Number.isFinite(port) && port > 0 && port < 65536) ports.add(port);
132
- }
133
- return [...ports].sort((a, b) => a - b);
134
- }
135
- /** Recognised loopback host strings as emitted by `lsof -F n`. */
136
- function isLoopback(host) {
137
- return host === "*" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
138
- }
139
- //#endregion
140
234
  //#region src/lib/prober/http.ts
141
235
  /**
142
236
  * Fetch `url` with a per-call timeout and pass the resulting `Response` to
@@ -346,9 +440,8 @@ const url = (s) => pc.cyan(s);
346
440
  const id = (s) => pc.yellow(s);
347
441
  /**
348
442
  * Reads the project root to identify the framework version, TS presence,
349
- * and which package manager the user runs. Returns the worst-case
350
- * (`"unknown"` PM, no version) on any error so summary rendering can
351
- * always proceed.
443
+ * and which package manager the user runs. Returns safe defaults (npm
444
+ * fallback, no version) on any error so summary rendering can always proceed.
352
445
  */
353
446
  async function detectProjectFacts(cwd, frameworkId) {
354
447
  const facts = {
@@ -371,16 +464,9 @@ function formatFrameworkLine(facts) {
371
464
  const pretty = prettyFramework(facts.framework);
372
465
  segments.push(facts.frameworkVersion ? `${pretty} ${facts.frameworkVersion}` : pretty);
373
466
  if (facts.typescript) segments.push("TypeScript");
374
- if (facts.packageManager !== "unknown") segments.push(facts.packageManager);
467
+ segments.push(facts.packageManager);
375
468
  return segments.join(` ${pc.dim("·")} `);
376
469
  }
377
- async function detectPackageManager(cwd) {
378
- if (await fileExists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
379
- if (await fileExists(join(cwd, "yarn.lock"))) return "yarn";
380
- if (await fileExists(join(cwd, "bun.lockb"))) return "bun";
381
- if (await fileExists(join(cwd, "package-lock.json"))) return "npm";
382
- return "unknown";
383
- }
384
470
  async function fileExists(p) {
385
471
  try {
386
472
  await stat(p);
@@ -435,7 +521,7 @@ const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) =>
435
521
  */
436
522
  var Setup = class Setup extends BaseCommand {
437
523
  static description = "Create a Zitadel project and scaffold local auth.";
438
- static examples = ["<%= config.bin %> setup --framework next"];
524
+ static examples = ["<%= config.bin %> setup --framework next", "<%= config.bin %> setup --framework react --dev-port 3000"];
439
525
  static flags = {
440
526
  framework: Flags.string({
441
527
  description: "Framework to target.",
@@ -444,11 +530,17 @@ var Setup = class Setup extends BaseCommand {
444
530
  renderer: Flags.string({
445
531
  description: "Renderer (default: react).",
446
532
  options: [...RENDERER_IDS]
447
- })
533
+ }),
534
+ "dev-port": Flags.integer({ description: "Dev-server port; also the issuer origin registered with Zitadel. Defaults to the detected port. Use distinct ports to run several scaffolded apps side by side." }),
535
+ "skip-install": Flags.boolean({ description: "Do not install dependencies after setup updates package.json." })
448
536
  };
449
537
  async run() {
450
538
  const { flags } = await this.parse(Setup);
451
- await this.toMeta(flags);
539
+ try {
540
+ await this.toMeta(flags);
541
+ } catch (error) {
542
+ throw localSetupHint(error, flags.framework, this.config.version);
543
+ }
452
544
  const { cwd, nonInteractive, dryRun, force } = this.meta;
453
545
  if (await hasZitadelConfig(cwd)) return this.emit({
454
546
  status: "skipped",
@@ -463,13 +555,24 @@ var Setup = class Setup extends BaseCommand {
463
555
  framework = await orca.detect(cwd, flags.framework);
464
556
  consola$1.success(`Detected ${framework.id}${framework.devPort ? ` (dev port ${framework.devPort})` : ""}`);
465
557
  } catch (error) {
466
- if (error instanceof ZitadelError && error.code === "E_FRAMEWORK_NOT_DETECTED" && await orca.isEmpty(cwd)) {
467
- consola$1.info("Empty directory scaffolding a fresh project");
558
+ if (error instanceof ZitadelError && error.code === "E_FRAMEWORK_NOT_DETECTED") {
559
+ const target = await inspectScaffoldTarget(cwd);
560
+ if (!target.scaffoldable) throw frameworkDetectionWithScaffoldTarget(error, cwd, target);
561
+ consola$1.info("Fresh app directory — scaffolding a fresh project");
468
562
  framework = await orca.scaffold(cwd, await resolveScaffoldFramework(flags.framework, nonInteractive, orca));
469
563
  scaffoldedFramework = true;
470
564
  consola$1.success(`Scaffolded ${framework.id} skeleton`);
471
565
  } else throw error;
472
566
  }
567
+ if (flags["dev-port"] !== void 0) {
568
+ const devPort = flags["dev-port"];
569
+ if (!Number.isInteger(devPort) || devPort < 1 || devPort > 65535) throw new ZitadelError("E_VALIDATION", `--dev-port must be an integer in 1..65535, got ${devPort}`);
570
+ framework = {
571
+ ...framework,
572
+ devPort,
573
+ url: issuerFromPort(devPort)
574
+ };
575
+ }
473
576
  let answers = {
474
577
  server: this.meta.source,
475
578
  devPort: framework.devPort
@@ -478,15 +581,21 @@ var Setup = class Setup extends BaseCommand {
478
581
  intro("Zitadel setup");
479
582
  const promptCtx = {
480
583
  framework,
481
- serverFlag: this.meta.serverFlag
584
+ serverFlag: this.meta.serverFlag,
585
+ devPortFromFlag: flags["dev-port"] !== void 0
482
586
  };
483
587
  for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
484
588
  outro("Configuration captured");
485
589
  }
486
590
  const issuer = issuerFromPort(answers.devPort);
591
+ framework = {
592
+ ...framework,
593
+ devPort: answers.devPort,
594
+ url: issuer
595
+ };
487
596
  consola$1.start(`Creating project on ${answers.server}${dryRun ? " (dry run)" : ""}`);
488
597
  const unauthClient = createZitadelClient({ baseUrl: answers.server });
489
- const project = dryRun ? dryRunProject() : await unauthClient.createProject({ previewOrigins: [] });
598
+ const project = dryRun ? dryRunProject(issuer) : await createProjectWithLocalHint(unauthClient, answers.server, this.meta.cliVersion, issuer, framework.id);
490
599
  consola$1.success(`Created project ${project.id}`);
491
600
  const ctx = {
492
601
  framework,
@@ -494,7 +603,8 @@ var Setup = class Setup extends BaseCommand {
494
603
  project,
495
604
  issuer,
496
605
  server: answers.server,
497
- cliVersion: this.meta.cliVersion
606
+ cliVersion: this.meta.cliVersion,
607
+ scaffoldedFramework
498
608
  };
499
609
  consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
500
610
  const result = await orca.patcherFor(framework.id).patch(ctx, {
@@ -508,6 +618,16 @@ var Setup = class Setup extends BaseCommand {
508
618
  }
509
619
  for (const file of result.filesSkipped) consola$1.info(`Left ${relativeDisplay(cwd, file)} unchanged (already matches target)`);
510
620
  consola$1.success(`Patched ${result.filesWritten.length} file${result.filesWritten.length === 1 ? "" : "s"}` + (result.filesSkipped.length > 0 ? ` (${result.filesSkipped.length} unchanged)` : ""));
621
+ const installOutcome = await installDependenciesForSetup({
622
+ cwd,
623
+ depsAdded: result.depsAdded,
624
+ dryRun,
625
+ env: this.meta.env,
626
+ issuer,
627
+ json: this.jsonEnabled(),
628
+ scaffoldedFramework,
629
+ skipInstall: Boolean(flags["skip-install"])
630
+ });
511
631
  const writtenRel = result.filesWritten.map((file) => relativeDisplay(cwd, file));
512
632
  if (!this.jsonEnabled()) {
513
633
  const sections = buildSummary({
@@ -523,7 +643,7 @@ var Setup = class Setup extends BaseCommand {
523
643
  message: [
524
644
  renderSummary(sections),
525
645
  "",
526
- `Open your app on ${url(`${issuer}/login`)} and register your first user.`
646
+ installOutcome.nextActions.join("\n")
527
647
  ].join("\n"),
528
648
  style: {
529
649
  padding: 1,
@@ -545,12 +665,23 @@ var Setup = class Setup extends BaseCommand {
545
665
  server: answers.server,
546
666
  files_written: result.filesWritten.map((file) => relativeDisplay(cwd, file)),
547
667
  files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),
548
- next_actions: [`Start your project: npm install && npm run dev (then open ${issuer}/login)`],
549
- next_commands: ["npm install", "npm run dev"]
668
+ install: installOutcome.install,
669
+ next_actions: installOutcome.nextActions,
670
+ next_commands: installOutcome.nextCommands
550
671
  }
551
672
  });
552
673
  }
553
674
  };
675
+ function frameworkDetectionWithScaffoldTarget(error, cwd, target) {
676
+ return new ZitadelError(error.code, "Could not detect a supported app framework, and this directory is not a fresh scaffold target", {
677
+ hint: `${target.reason ?? "Directory is not empty."} Run setup from an empty directory to scaffold a new app, or run setup from an existing supported app project.`,
678
+ details: {
679
+ cwd,
680
+ entries: target.entries,
681
+ reason: target.reason
682
+ }
683
+ });
684
+ }
554
685
  /**
555
686
  * Resolves which framework to scaffold into an empty directory: the explicit
556
687
  * `--framework`, else PickFrameworkPrompt, else a hard error in non-interactive
@@ -558,19 +689,44 @@ var Setup = class Setup extends BaseCommand {
558
689
  */
559
690
  async function resolveScaffoldFramework(framework, nonInteractive, orca) {
560
691
  if (framework) return framework;
561
- if (nonInteractive) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", "Empty directory — pass --framework", { hint: "Example: --framework next" });
692
+ if (nonInteractive) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", "Empty directory — pass --framework", { hint: "Run without --json/--non-interactive to choose from a prompt, or pass --framework for scripted setup." });
562
693
  return new PickFrameworkPrompt().ask(orca.availableFrameworks());
563
694
  }
564
695
  /** A deterministic stand-in project for `--dry-run`, so no remote call is made. */
565
- function dryRunProject() {
696
+ function dryRunProject(issuer) {
566
697
  return {
567
698
  id: "dry-run-0000",
568
699
  projectSecret: "sk_proj_dry_run_full",
569
700
  previewSecret: "sk_proj_dry_run_preview",
570
- previewOrigins: [],
701
+ previewOrigins: [issuer],
571
702
  createdAt: "2026-04-21T14:03:11.000Z"
572
703
  };
573
704
  }
705
+ async function createProjectWithLocalHint(client, server, cliVersion, issuer, framework) {
706
+ try {
707
+ return await client.createProject({ previewOrigins: [issuer] });
708
+ } catch (error) {
709
+ const normalized = toZitadelError(error);
710
+ throw new ZitadelError(normalized.code, normalized.message, {
711
+ hint: `${normalized.hint ? `${normalized.hint} ` : ""}If you meant to use a local Zitadel server, start it first and retry setup with --framework ${framework} --server local.`,
712
+ nextCommands: [publicCliCommand("start", cliVersion), publicCliCommand(`setup --framework ${framework} --server local`, cliVersion)],
713
+ details: {
714
+ server,
715
+ original: normalized.details
716
+ }
717
+ });
718
+ }
719
+ }
720
+ function localSetupHint(error, framework, cliVersion) {
721
+ const normalized = toZitadelError(error);
722
+ if (normalized.code !== "E_LOCAL_SERVER_NOT_RUNNING") return error;
723
+ const setupCommand = framework ? `setup --framework ${framework} --server local` : "setup --server local";
724
+ return new ZitadelError(normalized.code, normalized.message, {
725
+ hint: `${normalized.hint ? `${normalized.hint} ` : ""}Start local Zitadel first, then rerun setup. After setup succeeds, follow its next_commands to start the app and verify registration, logout, and login in the browser.`,
726
+ nextCommands: [publicCliCommand("start", cliVersion), publicCliCommand(setupCommand, cliVersion)],
727
+ details: normalized.details
728
+ });
729
+ }
574
730
  /** Renders an absolute path relative to `cwd` for human-readable output. */
575
731
  function relativeDisplay(cwd, path) {
576
732
  return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;
@@ -622,10 +778,12 @@ const SENTENCE_BY_PATH = {
622
778
  ".env.example": { subject: "the .env example template" },
623
779
  ".env.local": { subject: "the local development environment variables" },
624
780
  ".zitadel/state.json": { subject: "the empty sync state file" },
781
+ "app/page.tsx": { subject: "the auth home page" },
625
782
  "app/login/page.tsx": { subject: "the login page" },
626
783
  "app/register/page.tsx": { subject: "the registration page" },
627
784
  "app/profile/page.tsx": { subject: "the profile page" },
628
785
  "middleware.ts": { subject: "the Next.js middleware" },
786
+ "proxy.ts": { subject: "the Next.js proxy" },
629
787
  "custom-elements.d.ts": { subject: "the web-component type declarations" },
630
788
  "package.json": { subject: "package.json with the SDK dependency" }
631
789
  };
@@ -649,9 +807,11 @@ function buildSummary(opts) {
649
807
  secondary: path(fileNameOf(packageJsonHit))
650
808
  });
651
809
  for (const [label, suffix] of [
810
+ ["Home page", "app/page.tsx"],
652
811
  ["Login page", "app/login/page.tsx"],
653
812
  ["Register page", "app/register/page.tsx"],
654
813
  ["Profile page", "app/profile/page.tsx"],
814
+ ["Request proxy", "proxy.ts"],
655
815
  ["Middleware", "middleware.ts"],
656
816
  ["Env vars", ".env.local"]
657
817
  ]) {