@zitadel/cli 0.1.0-alpha.5 → 0.1.0-alpha.9

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 (41) hide show
  1. package/README.md +15 -12
  2. package/SKILLS.md +54 -20
  3. package/dist/commands/apply.mjs +3 -3
  4. package/dist/commands/doctor.mjs +160 -26
  5. package/dist/commands/doctor.mjs.map +1 -1
  6. package/dist/commands/eject.mjs +3 -3
  7. package/dist/commands/logs.mjs +13 -5
  8. package/dist/commands/logs.mjs.map +1 -1
  9. package/dist/commands/plan.mjs +3 -3
  10. package/dist/commands/reset.mjs +24 -7
  11. package/dist/commands/reset.mjs.map +1 -1
  12. package/dist/commands/setup.mjs +18 -78
  13. package/dist/commands/setup.mjs.map +1 -1
  14. package/dist/commands/start.mjs +171 -11
  15. package/dist/commands/start.mjs.map +1 -1
  16. package/dist/commands/status.mjs +26 -7
  17. package/dist/commands/status.mjs.map +1 -1
  18. package/dist/commands/stop.mjs +69 -7
  19. package/dist/commands/stop.mjs.map +1 -1
  20. package/dist/docker-CnGQK3ZK.mjs +432 -0
  21. package/dist/docker-CnGQK3ZK.mjs.map +1 -0
  22. package/dist/docker-guidance-ypN3IM3o.mjs +21 -0
  23. package/dist/docker-guidance-ypN3IM3o.mjs.map +1 -0
  24. package/dist/{oclif-2t97lHfY.mjs → oclif-B7lBzh3R.mjs} +51 -26
  25. package/dist/oclif-B7lBzh3R.mjs.map +1 -0
  26. package/dist/{orca-CYqJP4ZJ.mjs → orca-U142Wrau.mjs} +155 -114
  27. package/dist/orca-U142Wrau.mjs.map +1 -0
  28. package/dist/ports-B09RjuHx.mjs +111 -0
  29. package/dist/ports-B09RjuHx.mjs.map +1 -0
  30. package/dist/processes-Cw8TO1SY.mjs +120 -0
  31. package/dist/processes-Cw8TO1SY.mjs.map +1 -0
  32. package/dist/{project-IzPVR0Pr.mjs → project-Cd0L3PtM.mjs} +2 -2
  33. package/dist/{project-IzPVR0Pr.mjs.map → project-Cd0L3PtM.mjs.map} +1 -1
  34. package/dist/{sync-Df9S8Pio.mjs → sync-BojoQm2P.mjs} +2 -2
  35. package/dist/{sync-Df9S8Pio.mjs.map → sync-BojoQm2P.mjs.map} +1 -1
  36. package/oclif.manifest.json +29 -1
  37. package/package.json +8 -42
  38. package/dist/docker--EAWr_WY.mjs +0 -210
  39. package/dist/docker--EAWr_WY.mjs.map +0 -1
  40. package/dist/oclif-2t97lHfY.mjs.map +0 -1
  41. package/dist/orca-CYqJP4ZJ.mjs.map +0 -1
package/README.md CHANGED
@@ -15,8 +15,8 @@ automation when reproducibility matters.
15
15
 
16
16
  ## Requirements
17
17
 
18
- - Node 20+
19
- - Docker for the managed local Zitadel runtime
18
+ - Node 24+
19
+ - Docker only when using the optional Docker runtime backend
20
20
  - A Next.js project, or an empty directory where setup can scaffold one
21
21
 
22
22
  ## Quickstart
@@ -30,11 +30,10 @@ npx @zitadel/cli@alpha setup --server local
30
30
  npm run dev
31
31
  ```
32
32
 
33
- `start` runs a Docker-backed local Zitadel server and stores runtime data
34
- under `.zitadel/local/`. Alpha CLI versions use the matching
35
- `ghcr.io/zitadel/nextgen:<cli-version>` image by default; local/dev builds fall
36
- back to `ghcr.io/zitadel/nextgen:latest`. Override with `--image` or
37
- `ZITADEL_LOCAL_IMAGE` for advanced debugging.
33
+ `start` runs the `@zitadel/server` npm binary by default and stores runtime data
34
+ under `.zitadel/local/`. Remote-server setup can use `--server <url>` without
35
+ starting a local runtime. Use `--runtime docker`, `--image`, or
36
+ `ZITADEL_LOCAL_IMAGE` for advanced Docker backend debugging.
38
37
  `setup --server local` creates a project on that local server, asks which
39
38
  framework to scaffold when the directory is fresh, writes the Next.js app into
40
39
  the current directory, scaffolds `app/login`, `app/register`, `app/profile`, and
@@ -63,12 +62,12 @@ passkey can sign in with either credential.
63
62
 
64
63
  ## Other commands
65
64
 
66
- - `zitadel doctor` — verify the local Docker runtime and generated project files
67
- - `zitadel status` — summarise the local Docker runtime and project
65
+ - `zitadel doctor` — verify the local runtime and generated project files
66
+ - `zitadel status` — summarise the local runtime and project
68
67
  - `zitadel plan` — validate config and preview sync changes without mutation
69
68
  - `zitadel apply` — validate and upload repo config to Zitadel
70
69
  - `zitadel eject` — remove what setup wrote (alias: `zitadel uninstall`)
71
- - `zitadel start|stop|logs|reset` — manage the local Docker runtime
70
+ - `zitadel start|stop|logs|reset` — manage the local runtime
72
71
 
73
72
  ## Reference
74
73
 
@@ -188,7 +187,7 @@ Verify local runtime and project state.
188
187
  ```
189
188
  USAGE
190
189
  $ zitadel doctor [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug] [--fix]
191
- [--image <value>] [--port <value>]
190
+ [--image <value>] [--port <value>] [--runtime binary|docker]
192
191
 
193
192
  FLAGS
194
193
  -c, --cwd=<value> Project directory to operate on.
@@ -200,6 +199,8 @@ FLAGS
200
199
  --fix Re-apply missing managed files.
201
200
  --image=<value> Container image to check.
202
201
  --port=<value> [default: 8080] Local HTTP port.
202
+ --runtime=<option> Local runtime backend.
203
+ <options: binary|docker>
203
204
  --verbose Verbose logging.
204
205
 
205
206
  GLOBAL FLAGS
@@ -394,7 +395,7 @@ Start a local Zitadel server.
394
395
  ```
395
396
  USAGE
396
397
  $ zitadel start [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug] [--image
397
- <value>] [--port <value>]
398
+ <value>] [--port <value>] [--runtime binary|docker]
398
399
 
399
400
  FLAGS
400
401
  -c, --cwd=<value> Project directory to operate on.
@@ -405,6 +406,8 @@ FLAGS
405
406
  --dry-run Preview without mutating files or the platform.
406
407
  --image=<value> Container image to run.
407
408
  --port=<value> [default: 8080] Local HTTP port.
409
+ --runtime=<option> Local runtime backend.
410
+ <options: binary|docker>
408
411
  --verbose Verbose logging.
409
412
 
410
413
  GLOBAL FLAGS
package/SKILLS.md CHANGED
@@ -42,8 +42,11 @@ Each invocation prints one JSON object:
42
42
  - On failure: `code` (e.g. `E_VALIDATION`, `E_NETWORK`, `E_CONFLICT`) and
43
43
  `message`.
44
44
  - `next_commands`: the suggested follow-ups. Prefer these over free-text hints.
45
- - `E_LOCAL_SERVER_NOT_RUNNING`: start the local Docker runtime with
45
+ - `E_LOCAL_SERVER_NOT_RUNNING`: start the local runtime with
46
46
  `npx @zitadel/cli@alpha start`, then retry with `--server local`.
47
+ - `E_PORT_IN_USE`: the requested local runtime port already has a listener.
48
+ Stop that process, run `npx @zitadel/cli@alpha stop --all` for host-wide
49
+ CLI-managed local runtimes, or choose another `start --port`.
47
50
 
48
51
  Exit codes mirror the error class (3 = validation, 4 = network, 5 = conflict,
49
52
  1 = auth, 2 = not-implemented). An unknown command is handled by the CLI's help
@@ -65,25 +68,26 @@ layer, not the envelope.
65
68
  - `plan` — validate config and preview the sync diff without mutating anything.
66
69
  - `apply` — validate and upload repo config to the platform.
67
70
  - `doctor` — verify generated app files and local state once `zitadel.json`
68
- exists. Local Docker runtime prerequisites are advisory warnings unless an
69
- existing managed runtime is unhealthy; `--fix` re-applies missing managed
70
- files.
71
- - `status` — summarize the local Docker runtime and project state.
71
+ exists. The default local runtime is the `@zitadel/server` npm binary;
72
+ Docker checks apply only when using `--runtime docker` or `--image`.
73
+ `--fix` re-applies missing managed files.
74
+ - `status` — summarize the local runtime and project state.
72
75
  - `eject` (alias `uninstall`) — remove managed files and local Zitadel state;
73
76
  requires `--force` when non-interactive.
74
- - `start` — start the managed local Zitadel container and persist runtime
75
- metadata under `.zitadel/local/runtime.json`.
76
- - `stop` — stop/remove the managed container while preserving
77
- `.zitadel/local/nextgen-data`.
78
- - `logs` — print managed container logs; `--follow` streams in human mode.
79
- - `reset` — stop/remove the managed container and delete local runtime data;
77
+ - `start` — start the managed local Zitadel server and persist runtime metadata
78
+ under `.zitadel/local/runtime.json`. Use `--runtime docker` or `--image` for
79
+ the Docker backend.
80
+ - `stop` — stop the managed runtime while preserving
81
+ `.zitadel/local/nextgen-data`. Use `stop --all` to sweep all discovered
82
+ host-wide CLI-managed local runtime processes, including healthy runtimes
83
+ from other local projects; it does not kill arbitrary `/healthz` listeners.
84
+ - `logs` — print managed runtime logs; `--follow` streams in human mode.
85
+ - `reset` — stop/remove the managed runtime and delete local runtime data;
80
86
  requires `--force` when non-interactive.
81
87
 
82
- Alpha releases are lockstep trains. `npx @zitadel/cli@alpha start` runs the
83
- latest tested alpha server image, and
84
- `npx @zitadel/cli@0.1.0-alpha.N start` runs
85
- `ghcr.io/zitadel/nextgen:0.1.0-alpha.N`. `zitadel start --image <ref>` remains
86
- the explicit image override for debugging.
88
+ Alpha releases are fixed product package trains. `npx @zitadel/cli@alpha start`
89
+ uses the matching `@zitadel/server` package by default. `zitadel start --runtime
90
+ docker --image <ref>` remains the explicit image override for debugging.
87
91
 
88
92
  ## Golden path
89
93
 
@@ -107,10 +111,40 @@ npx @zitadel/cli@0.1.0-alpha.N setup --framework next --server local --non-inter
107
111
  After `setup`, follow `data.next_commands` to start the app. Prove the generated
108
112
  auth flow in a visible browser by registering a unique user, logging out, logging
109
113
  back in with the same email/password, and ending on the signed-in profile page.
110
- Do not treat a rendered login or registration form as completion. If browser DOM
111
- snapshots are sparse, target the component hooks exposed by the generated UI:
112
- `zitadel-field-email-input`, `zitadel-field-password-input`, and
113
- `zitadel-action-submit-button`.
114
+ Do not treat a rendered login or registration form as completion.
115
+
116
+ ### Driving the login UI
117
+
118
+ `<zitadel-login>` and `<zitadel-logout>` are Lit elements with open shadow
119
+ roots. The stable automation hooks live inside nested shadow roots, so a flat
120
+ `document.querySelector('[data-testid="zitadel-field-email-input"]')` will not
121
+ find them. Browser drivers with shadow-DOM-aware locators, such as Playwright,
122
+ can target the hooks directly. Generic DOM-eval drivers should pierce shadow
123
+ roots recursively:
124
+
125
+ ```js
126
+ function deepQuery(sel, root = document) {
127
+ const hit = root.querySelector(sel);
128
+ if (hit) return hit;
129
+ for (const el of root.querySelectorAll("*")) {
130
+ if (el.shadowRoot) {
131
+ const result = deepQuery(sel, el.shadowRoot);
132
+ if (result) return result;
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ ```
138
+
139
+ Use `zitadel-field-email-input`, `zitadel-field-password-input`, and
140
+ `zitadel-action-submit-button` for sign-in and registration. For sign-out, open
141
+ the user menu button if needed, then pierce to `.signout-btn`; Playwright-style
142
+ locators may use `zitadel-logout .signout-btn`. The canonical component hook
143
+ list lives in `packages/components/README.md`.
144
+
145
+ The checked-in automated regression path is `moon run workspace:journey`, which
146
+ exercises fresh-app setup plus registration, logout, and login across the
147
+ supported frameworks.
114
148
 
115
149
  Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
116
150
  then re-run `plan` and `apply`. Managed files carry a marker comment; `eject`
@@ -1,6 +1,6 @@
1
- import { t as BaseCommand } from "../oclif-2t97lHfY.mjs";
2
- import { o as readZitadelSecret } from "../project-IzPVR0Pr.mjs";
3
- import { a as makeSyncers, i as runSyncLoop, n as summarizePlan, o as environmentSchema, r as buildSyncPlan, t as renderPlan } from "../sync-Df9S8Pio.mjs";
1
+ import { t as BaseCommand } from "../oclif-B7lBzh3R.mjs";
2
+ import { o as readZitadelSecret } from "../project-Cd0L3PtM.mjs";
3
+ import { a as makeSyncers, i as runSyncLoop, n as summarizePlan, o as environmentSchema, r as buildSyncPlan, t as renderPlan } from "../sync-BojoQm2P.mjs";
4
4
  import { Flags } from "@oclif/core";
5
5
  import { createZitadelClient } from "@zitadel/api/client";
6
6
  import { consola as consola$1 } from "consola";
@@ -1,7 +1,10 @@
1
- import { D as ZitadelError, a as DEFAULT_LOCAL_SERVER_PORT, c as checkLocalServerHealth, f as isPortAvailable, h as readRuntimeMetadata, l as defaultLocalServerImageForCliVersion, m as localServerUrl, s as assertLocalStateWritable, t as BaseCommand, w as isObject, x as publicCliCommand } from "../oclif-2t97lHfY.mjs";
2
- import { r as issuerFromPort, t as createOrca } from "../orca-CYqJP4ZJ.mjs";
3
- import { a as readZitadelConfig, i as readRendererId, o as readZitadelSecret, r as readDevelopmentIssuer, t as hasZitadelConfig } from "../project-IzPVR0Pr.mjs";
4
- import { o as imageAvailable, r as dockerAvailable } from "../docker--EAWr_WY.mjs";
1
+ import { C as isObject, E as ZitadelError, a as DEFAULT_LOCAL_SERVER_PORT, b as publicCliCommand, c as checkLocalServerHealth, l as defaultLocalServerImageForCliVersion, m as readRuntimeMetadata, p as localServerUrl, s as assertLocalStateWritable, t as BaseCommand } from "../oclif-B7lBzh3R.mjs";
2
+ import { i as issuerFromPort, t as createOrca } from "../orca-U142Wrau.mjs";
3
+ import { a as readZitadelConfig, i as readRendererId, o as readZitadelSecret, r as readDevelopmentIssuer, t as hasZitadelConfig } from "../project-Cd0L3PtM.mjs";
4
+ import { n as listenersForPort } from "../ports-B09RjuHx.mjs";
5
+ import { d as assertServerPackageAvailable, o as imageAvailable, r as dockerAvailable } from "../docker-CnGQK3ZK.mjs";
6
+ import { n as dockerUnavailableMessage, t as dockerRuntimeGuidance } from "../docker-guidance-ypN3IM3o.mjs";
7
+ import { t as discoverManagedRuntimeProcesses } from "../processes-Cw8TO1SY.mjs";
5
8
  import { Flags } from "@oclif/core";
6
9
  import consola from "consola";
7
10
  import { join } from "node:path";
@@ -247,6 +250,7 @@ const SANITY_CHECKS = [
247
250
  //#endregion
248
251
  //#region src/commands/doctor/index.ts
249
252
  const LOCAL_RUNTIME_CHECK_NAMES = new Set([
253
+ "server-binary",
250
254
  "docker-cli",
251
255
  "image",
252
256
  "state-dir",
@@ -274,6 +278,10 @@ var Doctor = class Doctor extends BaseCommand {
274
278
  port: Flags.integer({
275
279
  description: "Local HTTP port.",
276
280
  default: DEFAULT_LOCAL_SERVER_PORT
281
+ }),
282
+ runtime: Flags.string({
283
+ description: "Local runtime backend.",
284
+ options: ["binary", "docker"]
277
285
  })
278
286
  };
279
287
  async run() {
@@ -284,8 +292,15 @@ var Doctor = class Doctor extends BaseCommand {
284
292
  source: localServerUrl(port)
285
293
  });
286
294
  const { cwd, dryRun } = this.meta;
295
+ const existingRuntime = await readRuntimeMetadata(cwd);
296
+ const runtimeBackend = resolveRuntimeBackend({
297
+ runtime: flags.runtime,
298
+ image: flags.image,
299
+ envImage: this.meta.env.ZITADEL_LOCAL_IMAGE,
300
+ existingRuntime
301
+ });
287
302
  const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? defaultLocalServerImageForCliVersion(this.meta.cliVersion);
288
- const runtimeChecks = await runLocalRuntimeChecks(cwd, image, port);
303
+ const runtimeChecks = await runLocalRuntimeChecks(cwd, runtimeBackend, image, port);
289
304
  const hasConfig = await hasZitadelConfig(cwd);
290
305
  const ctx = {
291
306
  cwd,
@@ -308,17 +323,23 @@ var Doctor = class Doctor extends BaseCommand {
308
323
  const checks = [...runtimeChecks, ...projectChecks];
309
324
  const failed = checks.filter((check) => check.status === "fail");
310
325
  const warnings = checks.filter((check) => check.status === "warn");
326
+ const warningAdvice = advisoryForWarnings(warnings, this.meta.cliVersion);
311
327
  const data = {
312
328
  title: failed.length > 0 ? "Zitadel doctor found issues." : warnings.length > 0 ? "Zitadel doctor passed with warnings." : "Zitadel doctor passed.",
313
329
  ok: failed.length === 0,
314
- image,
330
+ runtime: runtimeBackend,
331
+ ...runtimeBackend === "docker" ? { image } : {},
315
332
  port,
316
333
  project: { lifecycle: hasConfig ? "configured" : "not-configured" },
317
- checks
334
+ checks,
335
+ ...warningAdvice ? {
336
+ next_actions: warningAdvice.nextActions,
337
+ next_commands: warningAdvice.nextCommands
338
+ } : {}
318
339
  };
319
340
  if (failed.length > 0) {
320
341
  const advice = failureAdvice(failed, image, port, this.meta.cliVersion);
321
- throw new ZitadelError("E_VALIDATION", "Zitadel doctor found issues", {
342
+ throw new ZitadelError(failed.some((check) => check.name === "port") ? "E_PORT_IN_USE" : "E_VALIDATION", "Zitadel doctor found issues", {
322
343
  hint: advice.hint,
323
344
  nextCommands: advice.nextCommands,
324
345
  details: data
@@ -333,10 +354,17 @@ var Doctor = class Doctor extends BaseCommand {
333
354
  };
334
355
  function failureAdvice(failed, image, port, cliVersion) {
335
356
  const failedNames = new Set(failed.map((check) => check.name));
336
- if (failedNames.has("docker-cli")) return {
337
- hint: "Docker is required for `zitadel start`, but the Docker daemon is not reachable. Install or start Docker, then rerun `zitadel doctor`.",
338
- nextCommands: ["docker version", publicCliCommand("doctor", cliVersion)]
357
+ if (failedNames.has("server-binary")) return {
358
+ hint: "The Zitadel server npm package is not available. Reinstall the CLI package, then retry.",
359
+ nextCommands: [publicCliCommand("doctor", cliVersion)]
339
360
  };
361
+ if (failedNames.has("docker-cli")) {
362
+ const advice = dockerRuntimeGuidance("doctor", cliVersion);
363
+ return {
364
+ hint: advice.hint,
365
+ nextCommands: advice.nextCommands
366
+ };
367
+ }
340
368
  if (failedNames.has("image")) return {
341
369
  hint: "The local Zitadel image is not available. Check Docker registry access, build it locally, or pass --image / ZITADEL_LOCAL_IMAGE.",
342
370
  nextCommands: [`docker pull ${image}`, publicCliCommand("doctor", cliVersion)]
@@ -365,8 +393,39 @@ function failureAdvice(failed, image, port, cliVersion) {
365
393
  nextCommands: [publicCliCommand("doctor", cliVersion)]
366
394
  };
367
395
  }
368
- async function runLocalRuntimeChecks(cwd, image, port) {
396
+ function advisoryForWarnings(warnings, cliVersion) {
397
+ const nextActions = [];
398
+ const nextCommands = [];
399
+ if (warnings.some((check) => check.name === "docker-cli")) {
400
+ const advice = dockerRuntimeGuidance("doctor", cliVersion);
401
+ nextActions.push(...advice.nextActions);
402
+ nextCommands.push(...advice.nextCommands);
403
+ }
404
+ if (hasManagedRuntimeProcesses(warnings.find((check) => check.name === "managed-runtime-processes"))) {
405
+ nextActions.push("Review other host-wide CLI-managed local Zitadel runtimes before starting a new one.");
406
+ nextCommands.push(publicCliCommand("stop --all", cliVersion));
407
+ }
408
+ if (nextActions.length === 0 && nextCommands.length === 0) return;
409
+ return {
410
+ nextActions: unique(nextActions),
411
+ nextCommands: unique(nextCommands)
412
+ };
413
+ }
414
+ async function runLocalRuntimeChecks(cwd, runtimeBackend, image, port) {
369
415
  const runtime = await readRuntimeMetadata(cwd);
416
+ const managedRuntimeCheck = await checkManagedRuntimeProcesses(runtime);
417
+ if (runtimeBackend === "binary") return [
418
+ await check("server-binary", "Server npm package is available", async () => {
419
+ return `@zitadel/server ${await assertServerPackageAvailable()} is available`;
420
+ }),
421
+ await check("state-dir", "Local state directory is writable", async () => {
422
+ const probe = await assertLocalStateWritable(cwd);
423
+ return probe.checkedPath === probe.targetPath ? `${probe.targetPath} is writable` : `${probe.targetPath} can be created (${probe.checkedPath} is writable)`;
424
+ }, "warn"),
425
+ await check("port", `Port ${String(port)} is available`, () => checkPortAvailability(runtime, port)),
426
+ await checkRuntime(runtime, runtimeBackend),
427
+ managedRuntimeCheck
428
+ ];
370
429
  const docker = await check("docker-cli", "Docker is reachable", async () => {
371
430
  let result;
372
431
  try {
@@ -394,18 +453,87 @@ async function runLocalRuntimeChecks(cwd, image, port) {
394
453
  const probe = await assertLocalStateWritable(cwd);
395
454
  return probe.checkedPath === probe.targetPath ? `${probe.targetPath} is writable` : `${probe.targetPath} can be created (${probe.checkedPath} is writable)`;
396
455
  }, "warn"),
397
- await check("port", `Port ${String(port)} is available`, async () => {
398
- if (runtime && await checkLocalServerHealth(runtime.server_url)) return `${runtime.server_url} is already healthy`;
399
- if (!await isPortAvailable(port)) throw new Error(`Port ${String(port)} is already in use`);
400
- return `Port ${String(port)} is available`;
401
- }, "warn"),
402
- await check("runtime", "Existing local runtime is healthy", async () => {
403
- if (!runtime) return "No existing runtime metadata";
404
- if (!await checkLocalServerHealth(runtime.server_url)) throw new Error(`${runtime.server_url} did not respond to /healthz`);
405
- return `${runtime.server_url} is healthy`;
406
- })
456
+ await check("port", `Port ${String(port)} is available`, () => checkPortAvailability(runtime, port)),
457
+ await checkRuntime(runtime, runtimeBackend),
458
+ managedRuntimeCheck
407
459
  ];
408
460
  }
461
+ async function checkPortAvailability(runtime, port) {
462
+ if (runtime?.port === port && await checkLocalServerHealth(runtime.server_url)) return `${runtime.server_url} is already healthy`;
463
+ const listeners = await listenersForPort(port);
464
+ if (listeners.length > 0) throw new PortInUseCheckError(port, localServerUrl(port), listeners);
465
+ return `Port ${String(port)} is available`;
466
+ }
467
+ var PortInUseCheckError = class extends Error {
468
+ constructor(port, serverUrl, listeners) {
469
+ super(`Port ${String(port)} is already in use by ${formatListeners(listeners)}`);
470
+ this.port = port;
471
+ this.serverUrl = serverUrl;
472
+ this.listeners = listeners;
473
+ }
474
+ };
475
+ async function checkManagedRuntimeProcesses(runtime) {
476
+ const discovery = await discoverManagedRuntimeProcesses();
477
+ if (!discovery.supported) return {
478
+ name: "managed-runtime-processes",
479
+ status: "warn",
480
+ message: "Managed local runtime process discovery is unavailable.",
481
+ details: {
482
+ supported: false,
483
+ error: discovery.error
484
+ }
485
+ };
486
+ const processes = additionalManagedRuntimeProcesses(discovery.processes, runtime);
487
+ if (processes.length === 0) return {
488
+ name: "managed-runtime-processes",
489
+ status: "pass",
490
+ message: "No additional host-wide managed local runtime processes found.",
491
+ details: {
492
+ supported: true,
493
+ scope: "host",
494
+ processes: []
495
+ }
496
+ };
497
+ return {
498
+ name: "managed-runtime-processes",
499
+ status: "warn",
500
+ message: `${String(processes.length)} other host-wide managed local runtime process${processes.length === 1 ? "" : "es"} found.`,
501
+ details: {
502
+ supported: true,
503
+ scope: "host",
504
+ processes
505
+ }
506
+ };
507
+ }
508
+ function additionalManagedRuntimeProcesses(processes, runtime) {
509
+ if (runtime?.backend !== "binary") return processes;
510
+ return processes.filter((processInfo) => processInfo.pid !== runtime.pid && processInfo.ppid !== runtime.pid);
511
+ }
512
+ async function checkRuntime(runtime, runtimeBackend) {
513
+ return check("runtime", "Existing local runtime is healthy", async () => {
514
+ if (!runtime) return "No existing runtime metadata";
515
+ if (runtime.backend !== runtimeBackend) throw new Error(`Existing local runtime uses ${runtime.backend}; run start --runtime ${runtimeBackend} to switch backends.`);
516
+ if (!await checkLocalServerHealth(runtime.server_url)) throw new Error(`${runtime.server_url} did not respond to /healthz`);
517
+ return `${runtime.server_url} is healthy`;
518
+ });
519
+ }
520
+ function formatListeners(listeners) {
521
+ return listeners.map((listener) => [listener.command ?? "unknown", listener.pid ? `pid ${String(listener.pid)}` : void 0].filter(Boolean).join(" ")).join(", ");
522
+ }
523
+ function hasManagedRuntimeProcesses(check) {
524
+ if (!check || check.status !== "warn") return false;
525
+ const details = check.details;
526
+ return typeof details === "object" && details !== null && "supported" in details && details.supported === true && Array.isArray(details.processes) && (details.processes?.length ?? 0) > 0;
527
+ }
528
+ function unique(values) {
529
+ return [...new Set(values)];
530
+ }
531
+ function resolveRuntimeBackend(input) {
532
+ if (input.runtime === "binary" || input.runtime === "docker") return input.runtime;
533
+ if (input.existingRuntime) return input.existingRuntime.backend;
534
+ if (input.image || input.envImage) return "docker";
535
+ return "binary";
536
+ }
409
537
  async function check(name, fallback, run, failureStatus = "fail") {
410
538
  try {
411
539
  return {
@@ -414,6 +542,16 @@ async function check(name, fallback, run, failureStatus = "fail") {
414
542
  message: await run()
415
543
  };
416
544
  } catch (error) {
545
+ if (error instanceof PortInUseCheckError) return {
546
+ name,
547
+ status: failureStatus,
548
+ message: error.message,
549
+ details: {
550
+ port: error.port,
551
+ server_url: error.serverUrl,
552
+ listeners: error.listeners
553
+ }
554
+ };
417
555
  return {
418
556
  name,
419
557
  status: failureStatus,
@@ -421,10 +559,6 @@ async function check(name, fallback, run, failureStatus = "fail") {
421
559
  };
422
560
  }
423
561
  }
424
- function dockerUnavailableMessage(error) {
425
- const detail = error instanceof Error ? error.message : String(error);
426
- return `Docker is not reachable${detail.trim() ? ` (${detail.trim()})` : ""}; \`zitadel start\` needs Docker, but cloud setup can continue.`;
427
- }
428
562
  function imageUnavailableMessage(image, error) {
429
563
  const detail = error instanceof Error ? error.message : String(error);
430
564
  return `Image ${image} is not available to Docker${detail.trim() ? ` (${detail.trim()})` : ""}; \`zitadel start\` may need a pull or a different image.`;