@zitadel/cli 0.1.0-alpha.9 → 0.2.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 (49) hide show
  1. package/README.md +4043 -229
  2. package/bin/run.js +8 -0
  3. package/dist/index.mjs +24173 -0
  4. package/dist/index.mjs.map +1 -0
  5. package/dist/lib/oclif/help.mjs +117 -0
  6. package/dist/lib/oclif/help.mjs.map +1 -0
  7. package/dist/skills-hint-CBoak9HL.mjs +4348 -0
  8. package/dist/skills-hint-CBoak9HL.mjs.map +1 -0
  9. package/oclif.manifest.json +8635 -253
  10. package/package.json +106 -15
  11. package/skills/zitadel-cli/SKILL.md +538 -0
  12. package/skills/zitadel-cli/references/driving-login-ui.md +109 -0
  13. package/SKILLS.md +0 -157
  14. package/dist/commands/apply.mjs +0 -62
  15. package/dist/commands/apply.mjs.map +0 -1
  16. package/dist/commands/doctor.mjs +0 -569
  17. package/dist/commands/doctor.mjs.map +0 -1
  18. package/dist/commands/eject.mjs +0 -145
  19. package/dist/commands/eject.mjs.map +0 -1
  20. package/dist/commands/logs.mjs +0 -58
  21. package/dist/commands/logs.mjs.map +0 -1
  22. package/dist/commands/plan.mjs +0 -52
  23. package/dist/commands/plan.mjs.map +0 -1
  24. package/dist/commands/reset.mjs +0 -79
  25. package/dist/commands/reset.mjs.map +0 -1
  26. package/dist/commands/setup.mjs +0 -856
  27. package/dist/commands/setup.mjs.map +0 -1
  28. package/dist/commands/start.mjs +0 -288
  29. package/dist/commands/start.mjs.map +0 -1
  30. package/dist/commands/status.mjs +0 -112
  31. package/dist/commands/status.mjs.map +0 -1
  32. package/dist/commands/stop.mjs +0 -105
  33. package/dist/commands/stop.mjs.map +0 -1
  34. package/dist/docker-CnGQK3ZK.mjs +0 -432
  35. package/dist/docker-CnGQK3ZK.mjs.map +0 -1
  36. package/dist/docker-guidance-ypN3IM3o.mjs +0 -21
  37. package/dist/docker-guidance-ypN3IM3o.mjs.map +0 -1
  38. package/dist/oclif-B7lBzh3R.mjs +0 -808
  39. package/dist/oclif-B7lBzh3R.mjs.map +0 -1
  40. package/dist/orca-U142Wrau.mjs +0 -2556
  41. package/dist/orca-U142Wrau.mjs.map +0 -1
  42. package/dist/ports-B09RjuHx.mjs +0 -111
  43. package/dist/ports-B09RjuHx.mjs.map +0 -1
  44. package/dist/processes-Cw8TO1SY.mjs +0 -120
  45. package/dist/processes-Cw8TO1SY.mjs.map +0 -1
  46. package/dist/project-Cd0L3PtM.mjs +0 -87
  47. package/dist/project-Cd0L3PtM.mjs.map +0 -1
  48. package/dist/sync-BojoQm2P.mjs +0 -733
  49. package/dist/sync-BojoQm2P.mjs.map +0 -1
@@ -1,569 +0,0 @@
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";
8
- import { Flags } from "@oclif/core";
9
- import consola from "consola";
10
- import { join } from "node:path";
11
- import { chmod, readFile, stat, writeFile } from "node:fs/promises";
12
- import "@zitadel/api/generated/endpoints/zitadelNextGen.zod";
13
- //#region src/commands/doctor/checks/types.ts
14
- /**
15
- * Base class for checks: subclasses declare `name`, `path`, and a success
16
- * `summary`, and implement the single {@link verify} method that throws on
17
- * failure. {@link run} wraps it so a thrown error becomes a `fail` outcome
18
- * carrying the error message, and success becomes a `pass` with `summary`.
19
- *
20
- * {@link fix} defaults to a no-op: checks whose failure has no safe automatic
21
- * remedy (a missing secret, an invalid user schema) simply do not override it.
22
- */
23
- var AbstractSanityCheck = class {
24
- async run(ctx) {
25
- try {
26
- await this.verify(ctx);
27
- return {
28
- name: this.name,
29
- status: "pass",
30
- message: this.summary,
31
- path: this.path
32
- };
33
- } catch (error) {
34
- return {
35
- name: this.name,
36
- status: "fail",
37
- message: error instanceof Error ? error.message : String(error),
38
- path: this.path
39
- };
40
- }
41
- }
42
- async fix(_ctx) {}
43
- };
44
- //#endregion
45
- //#region src/commands/doctor/checks/config.ts
46
- /** Verifies `zitadel.json` exists and parses. */
47
- var ConfigCheck = class extends AbstractSanityCheck {
48
- name = "config";
49
- path = "zitadel.json";
50
- summary = "zitadel.json parses";
51
- async verify(ctx) {
52
- await readZitadelConfig(ctx.cwd);
53
- }
54
- };
55
- //#endregion
56
- //#region src/commands/doctor/checks/secret.ts
57
- /** Verifies `.zitadel/secret` exists and parses. */
58
- var SecretCheck = class extends AbstractSanityCheck {
59
- name = "secret";
60
- path = ".zitadel/secret";
61
- summary = ".zitadel/secret parses";
62
- async verify(ctx) {
63
- await readZitadelSecret(ctx.cwd);
64
- }
65
- };
66
- //#endregion
67
- //#region src/commands/doctor/checks/secret-permissions.ts
68
- /** Verifies `.zitadel/secret` is locked down to `0600`, and re-locks it on fix. */
69
- var SecretPermissionsCheck = class extends AbstractSanityCheck {
70
- name = "secret-permissions";
71
- path = ".zitadel/secret";
72
- summary = ".zitadel/secret has 0600 permissions";
73
- async verify(ctx) {
74
- const mode = (await stat(join(ctx.cwd, ".zitadel/secret"))).mode & 511;
75
- if (mode !== 384) throw new Error(`expected 0600, got ${mode.toString(8)}`);
76
- }
77
- async fix(ctx) {
78
- if (ctx.dryRun) return;
79
- await chmod(join(ctx.cwd, ".zitadel/secret"), 384);
80
- }
81
- };
82
- //#endregion
83
- //#region src/commands/doctor/checks/gitignore.ts
84
- /** The entries `.gitignore` must carry to keep secrets and env files untracked. */
85
- const REQUIRED_ENTRIES = [
86
- ".zitadel/secret",
87
- ".env*",
88
- "!.env.example"
89
- ];
90
- /** Verifies `.gitignore` excludes the local secret and env files; appends any missing. */
91
- var GitignoreCheck = class extends AbstractSanityCheck {
92
- name = "gitignore";
93
- path = ".gitignore";
94
- summary = ".gitignore protects local secret/env files";
95
- async verify(ctx) {
96
- const lines = (await readFile(join(ctx.cwd, ".gitignore"), "utf8")).split(/\r?\n/g);
97
- for (const entry of REQUIRED_ENTRIES) if (!lines.includes(entry)) throw new Error(`missing ${entry}`);
98
- }
99
- async fix(ctx) {
100
- const path = join(ctx.cwd, ".gitignore");
101
- const existing = await readFile(path, "utf8").catch(() => "");
102
- const lines = existing.split(/\r?\n/g);
103
- const missing = REQUIRED_ENTRIES.filter((entry) => !lines.includes(entry));
104
- if (missing.length === 0 || ctx.dryRun) return;
105
- await writeFile(path, `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${missing.join("\n")}\n`);
106
- }
107
- };
108
- //#endregion
109
- //#region src/commands/doctor/checks/env-example.ts
110
- /** The keys `.env.example` must document for a Zitadel-managed project. */
111
- const REQUIRED_KEYS = [
112
- "ZITADEL_PROJECT_ID",
113
- "ZITADEL_ENVIRONMENT",
114
- "ZITADEL_ISSUER"
115
- ];
116
- /** Verifies `.env.example` documents the required Zitadel keys; appends any missing. */
117
- var EnvExampleCheck = class extends AbstractSanityCheck {
118
- name = "env-example";
119
- path = ".env.example";
120
- summary = ".env.example references required keys";
121
- async verify(ctx) {
122
- const contents = await readFile(join(ctx.cwd, ".env.example"), "utf8");
123
- for (const key of REQUIRED_KEYS) if (!contents.includes(`${key}=`)) throw new Error(`missing ${key}`);
124
- }
125
- async fix(ctx) {
126
- const path = join(ctx.cwd, ".env.example");
127
- const existing = await readFile(path, "utf8").catch(() => "");
128
- const missing = REQUIRED_KEYS.filter((key) => !existing.includes(`${key}=`));
129
- if (missing.length === 0 || ctx.dryRun) return;
130
- await writeFile(path, `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${missing.map((key) => `${key}=`).join("\n")}\n`);
131
- }
132
- };
133
- //#endregion
134
- //#region src/commands/doctor/checks/framework.ts
135
- /** Verifies the framework detected on disk matches the one recorded in config. */
136
- var FrameworkCheck = class extends AbstractSanityCheck {
137
- name = "framework";
138
- path = "zitadel.json";
139
- summary = "Detected framework matches recorded framework";
140
- async verify(ctx) {
141
- const config = await readZitadelConfig(ctx.cwd);
142
- const detected = await ctx.orca.detect(ctx.cwd);
143
- const recorded = isObject(config.framework) ? config.framework.id : void 0;
144
- if (recorded !== detected.id) throw new Error(`expected ${String(recorded)}, detected ${detected.id}`);
145
- }
146
- };
147
- //#endregion
148
- //#region src/commands/doctor/patch-context.ts
149
- /**
150
- * Reconstructs a {@link PatchContext} from the on-disk project (config, secret)
151
- * plus fresh framework detection, so a patcher repair can rebuild its plan.
152
- * Used by the dependency check's `fix`, which reclaims the framework-specific
153
- * SDK package via `patcher.repair`. The user schema and flow definition are
154
- * server-owned and no longer scaffolded locally, so nothing here reads them.
155
- */
156
- async function loadPatchContext(cwd, orca, cliVersion) {
157
- const config = await readZitadelConfig(cwd);
158
- const secret = await readZitadelSecret(cwd);
159
- const framework = await orca.detect(cwd);
160
- return {
161
- framework,
162
- rendererId: readRendererId(config),
163
- issuer: await resolveIssuer(cwd, config, framework),
164
- server: typeof config.server === "string" ? config.server : "",
165
- cliVersion,
166
- project: {
167
- id: secret.project_id,
168
- projectSecret: secret.project_secret,
169
- previewSecret: secret.preview_secret,
170
- previewOrigins: secret.preview_origins,
171
- createdAt: secret.created_at
172
- }
173
- };
174
- }
175
- async function resolveIssuer(cwd, config, facts) {
176
- const fromConfig = readDevelopmentIssuer(config);
177
- if (fromConfig && fromConfig.length > 0) return fromConfig;
178
- const state = await readState(cwd);
179
- if (typeof state?.dev_port === "number") return issuerFromPort(state.dev_port);
180
- return facts.url;
181
- }
182
- async function readState(cwd) {
183
- try {
184
- const contents = await readFile(join(cwd, ".zitadel/state.json"), "utf8");
185
- return JSON.parse(contents);
186
- } catch {
187
- return;
188
- }
189
- }
190
- //#endregion
191
- //#region src/commands/doctor/checks/dependency.ts
192
- /**
193
- * Verifies the project still declares a Zitadel SDK dependency in
194
- * `package.json`. The patcher adds a scoped package (e.g.
195
- * `@zitadel/sdk-next`); the check is generic over the `@zitadel*`
196
- * scope so any framework renderer's dependency satisfies it.
197
- */
198
- var DependencyCheck = class extends AbstractSanityCheck {
199
- name = "dependency";
200
- path = "package.json";
201
- summary = "package.json depends on a Zitadel SDK package";
202
- async verify(ctx) {
203
- const pkg = JSON.parse(await readFile(join(ctx.cwd, "package.json"), "utf8"));
204
- if (![...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})].some((name) => name.startsWith("@zitadel"))) throw new Error("no @zitadel* dependency found in package.json");
205
- }
206
- /**
207
- * Repairs by reclaiming the patcher's managed artifacts: rebuilds the
208
- * `PatchContext` from disk and calls `patcher.repair`, which re-adds the
209
- * SDK dependency via its `add-dep` op. The exact package name is framework
210
- * + renderer specific and known only to the patcher (which deliberately
211
- * hides its file-op plan behind the family-neutral `Patcher` interface),
212
- * so going through `repair` is the only sanctioned path.
213
- */
214
- async fix(ctx) {
215
- const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca, ctx.cliVersion);
216
- await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {
217
- cwd: ctx.cwd,
218
- dryRun: ctx.dryRun,
219
- force: true
220
- });
221
- }
222
- };
223
- //#endregion
224
- //#region src/commands/doctor/checks/project-match.ts
225
- /** Verifies `.zitadel/secret`'s project_id matches `zitadel.json`'s project. */
226
- var ProjectMatchCheck = class extends AbstractSanityCheck {
227
- name = "project-match";
228
- path = ".zitadel/secret";
229
- summary = ".zitadel/secret project_id matches zitadel.json project";
230
- async verify(ctx) {
231
- const config = await readZitadelConfig(ctx.cwd);
232
- const secret = await readZitadelSecret(ctx.cwd);
233
- const configProject = typeof config.project === "string" ? config.project : void 0;
234
- if (secret.project_id !== configProject) throw new Error(".zitadel/secret project_id does not match zitadel.json project");
235
- }
236
- };
237
- //#endregion
238
- //#region src/commands/doctor/checks/index.ts
239
- /** Every diagnostic the `doctor` command runs, in display order. */
240
- const SANITY_CHECKS = [
241
- new ConfigCheck(),
242
- new SecretCheck(),
243
- new SecretPermissionsCheck(),
244
- new GitignoreCheck(),
245
- new EnvExampleCheck(),
246
- new FrameworkCheck(),
247
- new DependencyCheck(),
248
- new ProjectMatchCheck()
249
- ];
250
- //#endregion
251
- //#region src/commands/doctor/index.ts
252
- const LOCAL_RUNTIME_CHECK_NAMES = new Set([
253
- "server-binary",
254
- "docker-cli",
255
- "image",
256
- "state-dir",
257
- "port",
258
- "runtime"
259
- ]);
260
- /**
261
- * `zitadel doctor` — verify generated files and local state.
262
- *
263
- * Runs every registered {@link SANITY_CHECKS} entry and emits the aggregate
264
- * result; if any check fails it throws `E_VALIDATION` carrying the full check
265
- * details. With `--fix`, each failing check first attempts its own repair (a
266
- * no-op for checks with no safe automatic remedy), then the battery re-runs.
267
- *
268
- * The `--fix` loop is best-effort: a repair that throws (e.g. a missing
269
- * prerequisite file the check itself would also flag) is logged at debug
270
- * level and skipped, not propagated — the post-fix re-verify still reports
271
- * whatever remains broken.
272
- */
273
- var Doctor = class Doctor extends BaseCommand {
274
- static description = "Verify local runtime and project state.";
275
- static flags = {
276
- fix: Flags.boolean({ description: "Re-apply missing managed files." }),
277
- image: Flags.string({ description: "Container image to check." }),
278
- port: Flags.integer({
279
- description: "Local HTTP port.",
280
- default: DEFAULT_LOCAL_SERVER_PORT
281
- }),
282
- runtime: Flags.string({
283
- description: "Local runtime backend.",
284
- options: ["binary", "docker"]
285
- })
286
- };
287
- async run() {
288
- const { flags } = await this.parse(Doctor);
289
- const port = flags.port ?? 8080;
290
- await this.toMeta(flags, {
291
- resolveServer: false,
292
- source: localServerUrl(port)
293
- });
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
- });
302
- const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? defaultLocalServerImageForCliVersion(this.meta.cliVersion);
303
- const runtimeChecks = await runLocalRuntimeChecks(cwd, runtimeBackend, image, port);
304
- const hasConfig = await hasZitadelConfig(cwd);
305
- const ctx = {
306
- cwd,
307
- orca: createOrca(),
308
- cliVersion: this.meta.cliVersion,
309
- dryRun
310
- };
311
- if (hasConfig && flags.fix) {
312
- const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));
313
- for (const [index, check] of SANITY_CHECKS.entries()) {
314
- if (before[index]?.status !== "fail") continue;
315
- try {
316
- await check.fix(ctx);
317
- } catch (error) {
318
- consola.debug(`doctor --fix: ${check.name} repair failed`, error);
319
- }
320
- }
321
- }
322
- const projectChecks = hasConfig ? await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx))) : [];
323
- const checks = [...runtimeChecks, ...projectChecks];
324
- const failed = checks.filter((check) => check.status === "fail");
325
- const warnings = checks.filter((check) => check.status === "warn");
326
- const warningAdvice = advisoryForWarnings(warnings, this.meta.cliVersion);
327
- const data = {
328
- title: failed.length > 0 ? "Zitadel doctor found issues." : warnings.length > 0 ? "Zitadel doctor passed with warnings." : "Zitadel doctor passed.",
329
- ok: failed.length === 0,
330
- runtime: runtimeBackend,
331
- ...runtimeBackend === "docker" ? { image } : {},
332
- port,
333
- project: { lifecycle: hasConfig ? "configured" : "not-configured" },
334
- checks,
335
- ...warningAdvice ? {
336
- next_actions: warningAdvice.nextActions,
337
- next_commands: warningAdvice.nextCommands
338
- } : {}
339
- };
340
- if (failed.length > 0) {
341
- const advice = failureAdvice(failed, image, port, this.meta.cliVersion);
342
- throw new ZitadelError(failed.some((check) => check.name === "port") ? "E_PORT_IN_USE" : "E_VALIDATION", "Zitadel doctor found issues", {
343
- hint: advice.hint,
344
- nextCommands: advice.nextCommands,
345
- details: data
346
- });
347
- }
348
- return this.emit({
349
- status: "ok",
350
- data,
351
- warnings: warnings.map((warning) => `${warning.name}: ${warning.message}`)
352
- });
353
- }
354
- };
355
- function failureAdvice(failed, image, port, cliVersion) {
356
- const failedNames = new Set(failed.map((check) => check.name));
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)]
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
- }
368
- if (failedNames.has("image")) return {
369
- hint: "The local Zitadel image is not available. Check Docker registry access, build it locally, or pass --image / ZITADEL_LOCAL_IMAGE.",
370
- nextCommands: [`docker pull ${image}`, publicCliCommand("doctor", cliVersion)]
371
- };
372
- if (failedNames.has("state-dir")) return {
373
- hint: "The local Zitadel state directory is not writable. Fix directory permissions, then rerun `zitadel doctor`.",
374
- nextCommands: [publicCliCommand("doctor", cliVersion)]
375
- };
376
- if (failedNames.has("port")) {
377
- const fallbackPort = port === 8080 ? port + 1 : DEFAULT_LOCAL_SERVER_PORT;
378
- return {
379
- hint: `Port ${String(port)} is already in use. Stop the process using it, or choose another port for local Zitadel.`,
380
- nextCommands: [publicCliCommand(`doctor --port ${String(fallbackPort)}`, cliVersion)]
381
- };
382
- }
383
- if (failedNames.has("runtime")) return {
384
- hint: "Existing local runtime metadata was found, but the local Zitadel server is not healthy. Start it again or reset stale local data.",
385
- nextCommands: [publicCliCommand("start", cliVersion), publicCliCommand("reset --force", cliVersion)]
386
- };
387
- if (failed.some((check) => !LOCAL_RUNTIME_CHECK_NAMES.has(check.name))) return {
388
- hint: `Run \`${publicCliCommand("doctor --fix", cliVersion)}\` to re-apply missing managed files.`,
389
- nextCommands: [publicCliCommand("doctor --fix", cliVersion)]
390
- };
391
- return {
392
- hint: "Fix the reported checks, then rerun `zitadel doctor`.",
393
- nextCommands: [publicCliCommand("doctor", cliVersion)]
394
- };
395
- }
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) {
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
- ];
429
- const docker = await check("docker-cli", "Docker is reachable", async () => {
430
- let result;
431
- try {
432
- result = await dockerAvailable();
433
- } catch (error) {
434
- throw new Error(dockerUnavailableMessage(error), { cause: error });
435
- }
436
- if (result.status !== 0) throw new Error(dockerUnavailableMessage(result.stderr || "docker version failed"));
437
- return `Docker engine ${result.stdout.trim() || "available"}`;
438
- }, "warn");
439
- return [
440
- docker,
441
- docker.status === "pass" ? await check("image", `Image ${image} is available`, async () => {
442
- try {
443
- return await imageAvailable(image) === "local" ? `Image ${image} is available locally` : `Image ${image} is available from the registry`;
444
- } catch (error) {
445
- throw new Error(imageUnavailableMessage(image, error), { cause: error });
446
- }
447
- }, "warn") : {
448
- name: "image",
449
- status: "warn",
450
- message: "Skipped image check because Docker is not reachable."
451
- },
452
- await check("state-dir", "Local state directory is writable", async () => {
453
- const probe = await assertLocalStateWritable(cwd);
454
- return probe.checkedPath === probe.targetPath ? `${probe.targetPath} is writable` : `${probe.targetPath} can be created (${probe.checkedPath} is writable)`;
455
- }, "warn"),
456
- await check("port", `Port ${String(port)} is available`, () => checkPortAvailability(runtime, port)),
457
- await checkRuntime(runtime, runtimeBackend),
458
- managedRuntimeCheck
459
- ];
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
- }
537
- async function check(name, fallback, run, failureStatus = "fail") {
538
- try {
539
- return {
540
- name,
541
- status: "pass",
542
- message: await run()
543
- };
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
- };
555
- return {
556
- name,
557
- status: failureStatus,
558
- message: error instanceof Error ? error.message : fallback
559
- };
560
- }
561
- }
562
- function imageUnavailableMessage(image, error) {
563
- const detail = error instanceof Error ? error.message : String(error);
564
- return `Image ${image} is not available to Docker${detail.trim() ? ` (${detail.trim()})` : ""}; \`zitadel start\` may need a pull or a different image.`;
565
- }
566
- //#endregion
567
- export { Doctor as default };
568
-
569
- //# sourceMappingURL=doctor.mjs.map