@zitadel/cli 0.1.0-alpha.0 → 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 (43) hide show
  1. package/README.md +227 -18
  2. package/SKILLS.md +104 -11
  3. package/dist/commands/apply.mjs +4 -4
  4. package/dist/commands/apply.mjs.map +1 -1
  5. package/dist/commands/doctor.mjs +291 -17
  6. package/dist/commands/doctor.mjs.map +1 -1
  7. package/dist/commands/eject.mjs +14 -6
  8. package/dist/commands/eject.mjs.map +1 -1
  9. package/dist/commands/logs.mjs +58 -0
  10. package/dist/commands/logs.mjs.map +1 -0
  11. package/dist/commands/plan.mjs +4 -4
  12. package/dist/commands/plan.mjs.map +1 -1
  13. package/dist/commands/reset.mjs +79 -0
  14. package/dist/commands/reset.mjs.map +1 -0
  15. package/dist/commands/setup.mjs +267 -105
  16. package/dist/commands/setup.mjs.map +1 -1
  17. package/dist/commands/start.mjs +288 -0
  18. package/dist/commands/start.mjs.map +1 -0
  19. package/dist/commands/status.mjs +92 -27
  20. package/dist/commands/status.mjs.map +1 -1
  21. package/dist/commands/stop.mjs +105 -0
  22. package/dist/commands/stop.mjs.map +1 -0
  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/{project-C3pSfbao.mjs → oclif-B7lBzh3R.mjs} +339 -119
  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-Cd0L3PtM.mjs +87 -0
  36. package/dist/project-Cd0L3PtM.mjs.map +1 -0
  37. package/dist/{sync-Cuyh-X1J.mjs → sync-BojoQm2P.mjs} +4 -4
  38. package/dist/{sync-Cuyh-X1J.mjs.map → sync-BojoQm2P.mjs.map} +1 -1
  39. package/oclif.manifest.json +399 -7
  40. package/package.json +8 -41
  41. package/dist/orca-COsUnVoz.mjs +0 -1006
  42. package/dist/orca-COsUnVoz.mjs.map +0 -1
  43. package/dist/project-C3pSfbao.mjs.map +0 -1
@@ -1,9 +1,11 @@
1
1
  import { Command, Flags } from "@oclif/core";
2
2
  import consola from "consola";
3
- import { readFile, stat } from "node:fs/promises";
4
- import { join, resolve } from "node:path";
5
3
  import { ApiError } from "@zitadel/api/runtime/fetch";
6
4
  import { stringify } from "safe-stable-stringify";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { access, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
7
+ import { createHash } from "node:crypto";
8
+ import { constants } from "node:fs";
7
9
  //#region src/lib/errors.ts
8
10
  /**
9
11
  * Maps each {@link ZitadelErrorCode} to the process exit code the CLI
@@ -17,6 +19,8 @@ const EXIT_CODES = {
17
19
  E_NETWORK: 4,
18
20
  E_AUTH: 1,
19
21
  E_CONFLICT: 5,
22
+ E_LOCAL_SERVER_NOT_RUNNING: 4,
23
+ E_PORT_IN_USE: 5,
20
24
  E_VALIDATION: 3,
21
25
  E_NOT_IMPLEMENTED: 2
22
26
  };
@@ -144,6 +148,287 @@ function isObject(value) {
144
148
  return typeof value === "object" && value !== null && !Array.isArray(value);
145
149
  }
146
150
  //#endregion
151
+ //#region src/lib/paths.ts
152
+ /**
153
+ * Resolve the working directory the CLI should operate against, defaulting to
154
+ * the process CWD when no `--cwd` override is given. Always returns an
155
+ * absolute path so downstream `join`/`readFile` calls are unaffected by later
156
+ * `process.chdir` or relative-path ambiguity.
157
+ */
158
+ function resolveCwd(cwd) {
159
+ return resolve(cwd ?? process.cwd());
160
+ }
161
+ /**
162
+ * Sentinel comment stamped at the top of every file the CLI generates and
163
+ * owns. Commands like `doctor` and `eject` look for this marker to decide
164
+ * whether a file is safe to touch; the trailing `v1` lets the format evolve
165
+ * without mistaking newer managed files for hand-edited ones.
166
+ */
167
+ const MANAGED_MARKER = "// zitadel-cli: managed-file v1";
168
+ //#endregion
169
+ //#region src/lib/public-cli.ts
170
+ const CLI_PACKAGE_NAME = "@zitadel/cli";
171
+ function npmDistTagForCliVersion(cliVersion) {
172
+ return cliVersion.trim().replace(/^v/, "").match(/^\d+\.\d+\.\d+-([0-9A-Za-z][0-9A-Za-z-]*)/)?.[1] ?? "latest";
173
+ }
174
+ function npmSelectorForCliVersion(cliVersion) {
175
+ const normalized = cliVersion.trim().replace(/^v/, "");
176
+ if (/^\d+\.\d+\.\d+-alpha\.\d+$/.test(normalized)) return normalized;
177
+ return npmDistTagForCliVersion(normalized);
178
+ }
179
+ function publicCliCommand(args, cliVersion) {
180
+ const prefix = `npx ${CLI_PACKAGE_NAME}@${npmSelectorForCliVersion(cliVersion)}`;
181
+ return args.length > 0 ? `${prefix} ${args}` : prefix;
182
+ }
183
+ function normalizePublicCliCommand(command, cliVersion) {
184
+ if (command === "zitadel") return publicCliCommand("", cliVersion);
185
+ if (command.startsWith("zitadel ")) return publicCliCommand(command.slice(8), cliVersion);
186
+ return command;
187
+ }
188
+ function normalizePublicCliCommands(commands, cliVersion) {
189
+ return commands?.map((command) => normalizePublicCliCommand(command, cliVersion));
190
+ }
191
+ //#endregion
192
+ //#region src/lib/local-server/runtime.ts
193
+ const LOCAL_SERVER_IMAGE_NAME = "ghcr.io/zitadel/nextgen";
194
+ const DEFAULT_LOCAL_SERVER_IMAGE = `${LOCAL_SERVER_IMAGE_NAME}:latest`;
195
+ const DEFAULT_LOCAL_SERVER_PORT = 8080;
196
+ const DEFAULT_LOCAL_SERVER_URL = "http://localhost:8080";
197
+ const LOCAL_RUNTIME_DIR = ".zitadel/local";
198
+ const LOCAL_DATA_DIR = ".zitadel/local/nextgen-data";
199
+ const LOCAL_RUNTIME_FILE = ".zitadel/local/runtime.json";
200
+ const LOCAL_SERVER_LOG_FILE = ".zitadel/local/server.log";
201
+ const LOCAL_CONTAINER_PASSWD_FILE = ".zitadel/local/container-passwd";
202
+ const LOCAL_CONTAINER_GROUP_FILE = ".zitadel/local/container-group";
203
+ const CONTAINER_DATA_DIR = "/var/lib/zitadel/nextgen-data";
204
+ const CONTAINER_HTTP_PORT = 8080;
205
+ function localRuntimePaths(cwd) {
206
+ return {
207
+ runtimeDir: join(cwd, LOCAL_RUNTIME_DIR),
208
+ dataDir: join(cwd, LOCAL_DATA_DIR),
209
+ runtimeFile: join(cwd, LOCAL_RUNTIME_FILE),
210
+ logFile: join(cwd, LOCAL_SERVER_LOG_FILE),
211
+ containerPasswdFile: join(cwd, LOCAL_CONTAINER_PASSWD_FILE),
212
+ containerGroupFile: join(cwd, LOCAL_CONTAINER_GROUP_FILE)
213
+ };
214
+ }
215
+ function localContainerName(cwd) {
216
+ return `zitadel-server-${createHash("sha256").update(resolve(cwd)).digest("hex").slice(0, 12)}`;
217
+ }
218
+ function localServerUrl(port) {
219
+ return `http://localhost:${port}`;
220
+ }
221
+ function defaultLocalServerImageForCliVersion(cliVersion) {
222
+ const normalized = cliVersion.trim().replace(/^v/, "");
223
+ if (/^\d+\.\d+\.\d+-alpha\.\d+$/.test(normalized)) return `${LOCAL_SERVER_IMAGE_NAME}:${normalized}`;
224
+ return DEFAULT_LOCAL_SERVER_IMAGE;
225
+ }
226
+ async function ensureLocalState(cwd) {
227
+ const paths = localRuntimePaths(cwd);
228
+ await mkdir(paths.dataDir, {
229
+ recursive: true,
230
+ mode: 448
231
+ });
232
+ await appendGitignoreEntry(cwd, `${LOCAL_RUNTIME_DIR}/`);
233
+ return paths;
234
+ }
235
+ async function assertLocalStateWritable(cwd) {
236
+ const paths = localRuntimePaths(cwd);
237
+ const checkedPath = await nearestExistingDirectory(paths.dataDir);
238
+ await access(checkedPath, constants.W_OK);
239
+ return {
240
+ targetPath: paths.dataDir,
241
+ checkedPath
242
+ };
243
+ }
244
+ async function ensureContainerIdentity(cwd, user) {
245
+ if (user.uid === void 0 || user.uid <= 0) return;
246
+ const gid = user.gid ?? user.uid;
247
+ const paths = localRuntimePaths(cwd);
248
+ await mkdir(paths.runtimeDir, {
249
+ recursive: true,
250
+ mode: 448
251
+ });
252
+ await writeFile(paths.containerPasswdFile, [
253
+ "root:x:0:0:root:/root:/bin/sh",
254
+ "nonroot:x:65532:65532:nonroot:/nonexistent:/usr/sbin/nologin",
255
+ `zitadel-local:x:${String(user.uid)}:${String(gid)}:Zitadel local user:/tmp:/usr/sbin/nologin`,
256
+ ""
257
+ ].join("\n"), { mode: 420 });
258
+ await writeFile(paths.containerGroupFile, [
259
+ "root:x:0:",
260
+ "nonroot:x:65532:",
261
+ `zitadel-local:x:${String(gid)}:`,
262
+ ""
263
+ ].join("\n"), { mode: 420 });
264
+ return {
265
+ uid: user.uid,
266
+ gid,
267
+ passwdFile: paths.containerPasswdFile,
268
+ groupFile: paths.containerGroupFile
269
+ };
270
+ }
271
+ async function readRuntimeMetadata(cwd) {
272
+ const paths = localRuntimePaths(cwd);
273
+ let raw;
274
+ try {
275
+ raw = await readFile(paths.runtimeFile, "utf8");
276
+ } catch (error) {
277
+ if (isErrno(error, "ENOENT")) return;
278
+ throw error;
279
+ }
280
+ return normalizeRuntimeMetadata(parseJsonObject(raw, LOCAL_RUNTIME_FILE));
281
+ }
282
+ async function writeRuntimeMetadata(cwd, metadata) {
283
+ const paths = localRuntimePaths(cwd);
284
+ await mkdir(paths.runtimeDir, {
285
+ recursive: true,
286
+ mode: 448
287
+ });
288
+ await writeFile(paths.runtimeFile, `${JSON.stringify(metadata, null, 2)}\n`, { mode: 384 });
289
+ }
290
+ async function removeRuntimeMetadata(cwd) {
291
+ await rm(localRuntimePaths(cwd).runtimeFile, { force: true });
292
+ }
293
+ async function removeLocalData(cwd) {
294
+ await rm(localRuntimePaths(cwd).dataDir, {
295
+ recursive: true,
296
+ force: true
297
+ });
298
+ }
299
+ async function checkLocalServerHealth(serverUrl, timeoutMs = 1500) {
300
+ try {
301
+ const healthUrl = new URL("/healthz", serverUrl);
302
+ return (await fetch(healthUrl, { signal: AbortSignal.timeout(timeoutMs) })).ok;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+ async function resolveLocalServer(cwd) {
308
+ const runtime = await readRuntimeMetadata(cwd);
309
+ if (runtime) {
310
+ if (await checkLocalServerHealth(runtime.server_url)) return runtime.server_url;
311
+ throw localServerNotRunning(runtime.server_url);
312
+ }
313
+ if (await checkLocalServerHealth("http://localhost:8080")) return DEFAULT_LOCAL_SERVER_URL;
314
+ throw localServerNotRunning(DEFAULT_LOCAL_SERVER_URL);
315
+ }
316
+ function localServerNotRunning(serverUrl) {
317
+ return new ZitadelError("E_LOCAL_SERVER_NOT_RUNNING", "Local Zitadel server is not running", {
318
+ hint: `No healthy local server responded at ${serverUrl}.`,
319
+ nextCommands: ["zitadel start"],
320
+ details: { server_url: serverUrl }
321
+ });
322
+ }
323
+ async function appendGitignoreEntry(cwd, entry) {
324
+ const path = join(cwd, ".gitignore");
325
+ let existing = "";
326
+ try {
327
+ existing = await readFile(path, "utf8");
328
+ } catch (error) {
329
+ if (!isErrno(error, "ENOENT")) throw error;
330
+ }
331
+ if (existing.split(/\r?\n/).map((line) => line.trim()).includes(entry)) return;
332
+ const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
333
+ await writeFile(path, `${existing}${prefix}${entry}\n`);
334
+ }
335
+ function normalizeRuntimeMetadata(input) {
336
+ if (input.schema_version !== 1 || typeof input.port !== "number" || !isValidPort(input.port) || typeof input.server_url !== "string" || !isValidServerUrl(input.server_url, input.port) || typeof input.data_dir !== "string" || typeof input.created_at !== "string" || typeof input.cli_version !== "string") throw malformedRuntime(input);
337
+ const backend = input.backend === void 0 ? "docker" : input.backend;
338
+ const base = {
339
+ schema_version: 1,
340
+ port: input.port,
341
+ server_url: input.server_url,
342
+ data_dir: input.data_dir,
343
+ created_at: input.created_at,
344
+ cli_version: input.cli_version
345
+ };
346
+ if (backend === "binary") {
347
+ if (typeof input.pid !== "number" || !Number.isInteger(input.pid) || input.pid <= 0 || typeof input.command !== "string" || typeof input.log_path !== "string" || typeof input.server_package !== "string" || typeof input.server_version !== "string") throw malformedRuntime(input);
348
+ return {
349
+ ...base,
350
+ backend: "binary",
351
+ pid: input.pid,
352
+ command: input.command,
353
+ log_path: input.log_path,
354
+ server_package: input.server_package,
355
+ server_version: input.server_version
356
+ };
357
+ }
358
+ if (backend !== "docker" || typeof input.container_name !== "string" || typeof input.container_id !== "string" || typeof input.image !== "string") throw malformedRuntime(input);
359
+ return {
360
+ ...base,
361
+ backend: "docker",
362
+ container_name: input.container_name,
363
+ container_id: input.container_id,
364
+ image: input.image
365
+ };
366
+ }
367
+ async function nearestExistingDirectory(path) {
368
+ let current = path;
369
+ while (true) try {
370
+ if (!(await stat(current)).isDirectory()) throw new Error(`${current} exists but is not a directory`);
371
+ return current;
372
+ } catch (error) {
373
+ if (!isErrno(error, "ENOENT")) throw error;
374
+ const parent = dirname(current);
375
+ if (parent === current) throw error;
376
+ current = parent;
377
+ }
378
+ }
379
+ function isErrno(error, code) {
380
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
381
+ }
382
+ function runtimeSummary(metadata) {
383
+ if (!metadata) return { configured: false };
384
+ const base = {
385
+ configured: true,
386
+ backend: metadata.backend,
387
+ port: metadata.port,
388
+ server_url: metadata.server_url,
389
+ data_dir: metadata.data_dir,
390
+ created_at: metadata.created_at
391
+ };
392
+ if (metadata.backend === "binary") return {
393
+ ...base,
394
+ pid: metadata.pid,
395
+ command: metadata.command,
396
+ log_path: metadata.log_path,
397
+ server_package: metadata.server_package,
398
+ server_version: metadata.server_version
399
+ };
400
+ return {
401
+ ...base,
402
+ container_name: metadata.container_name,
403
+ container_id: metadata.container_id,
404
+ image: metadata.image
405
+ };
406
+ }
407
+ function isValidPort(value) {
408
+ return Number.isInteger(value) && value >= 1 && value <= 65535;
409
+ }
410
+ function isValidServerUrl(value, port) {
411
+ try {
412
+ const url = new URL(value);
413
+ return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0 && explicitUrlPort(value) === port;
414
+ } catch {
415
+ return false;
416
+ }
417
+ }
418
+ function explicitUrlPort(value) {
419
+ const match = value.match(/^[a-z][a-z\d+\-.]*:\/\/(?:\[[^\]]+\]|[^/?#:]+):(\d+)(?:[/?#]|$)/i);
420
+ if (!match) return;
421
+ const port = Number(match[1]);
422
+ return isValidPort(port) ? port : void 0;
423
+ }
424
+ function malformedRuntime(input) {
425
+ return new ZitadelError("E_VALIDATION", `${LOCAL_RUNTIME_FILE} is malformed`, {
426
+ hint: "Run `zitadel reset --force`, then `zitadel start`.",
427
+ nextCommands: ["zitadel reset --force", "zitadel start"],
428
+ details: input
429
+ });
430
+ }
431
+ //#endregion
147
432
  //#region src/lib/server.ts
148
433
  /**
149
434
  * Server URL used when nothing else resolves. Also surfaced in hints and
@@ -160,23 +445,23 @@ const DEFAULT_SERVER = "https://api.zitadel.cloud";
160
445
  * `ZitadelError` rather than silently falling through.
161
446
  */
162
447
  async function resolveServer(input) {
163
- if (input.serverFlag) return validate({
448
+ if (input.serverFlag) return validate(input.cwd, {
164
449
  value: input.serverFlag,
165
450
  origin: "flag"
166
451
  });
167
452
  const envValue = input.env.ZITADEL_API_BASE;
168
- if (envValue) return validate({
453
+ if (envValue) return validate(input.cwd, {
169
454
  value: envValue,
170
455
  origin: "env"
171
456
  });
172
457
  const config = await readConfig(input.cwd);
173
458
  if (config) {
174
459
  const envBranch = readEnvServer(config, input.environment);
175
- if (envBranch) return validate({
460
+ if (envBranch) return validate(input.cwd, {
176
461
  value: envBranch,
177
462
  origin: "config-env"
178
463
  });
179
- if (typeof config.server === "string") return validate({
464
+ if (typeof config.server === "string") return validate(input.cwd, {
180
465
  value: config.server,
181
466
  origin: "config-top"
182
467
  });
@@ -186,7 +471,11 @@ async function resolveServer(input) {
186
471
  origin: "default"
187
472
  };
188
473
  }
189
- function validate(resolved) {
474
+ async function validate(cwd, resolved) {
475
+ if (resolved.value === "local") return {
476
+ value: await resolveLocalServer(cwd),
477
+ origin: "local"
478
+ };
190
479
  try {
191
480
  const url = new URL(resolved.value);
192
481
  if (url.protocol !== "https:" && url.protocol !== "http:") throw new ZitadelError("E_VALIDATION", `Server URL must use http(s): ${resolved.value}`, { hint: `Set "server" in zitadel.json to a URL like ${DEFAULT_SERVER}.` });
@@ -219,24 +508,6 @@ function readEnvServer(config, environment) {
219
508
  return typeof branch.server === "string" ? branch.server : void 0;
220
509
  }
221
510
  //#endregion
222
- //#region src/lib/paths.ts
223
- /**
224
- * Resolve the working directory the CLI should operate against, defaulting to
225
- * the process CWD when no `--cwd` override is given. Always returns an
226
- * absolute path so downstream `join`/`readFile` calls are unaffected by later
227
- * `process.chdir` or relative-path ambiguity.
228
- */
229
- function resolveCwd(cwd) {
230
- return resolve(cwd ?? process.cwd());
231
- }
232
- /**
233
- * Sentinel comment stamped at the top of every file the CLI generates and
234
- * owns. Commands like `doctor` and `eject` look for this marker to decide
235
- * whether a file is safe to touch; the trailing `v1` lets the format evolve
236
- * without mistaking newer managed files for hand-edited ones.
237
- */
238
- const MANAGED_MARKER = "// zitadel-cli: managed-file v1";
239
- //#endregion
240
511
  //#region src/lib/oclif/base.ts
241
512
  /**
242
513
  * Base class for every oclif command. Owns the global flags, builds the
@@ -281,11 +552,14 @@ var BaseCommand = class extends Command {
281
552
  * `source` by the documented precedence and storing the result on
282
553
  * `this.meta` so the error handler can render a complete envelope.
283
554
  */
284
- async toMeta(flags) {
555
+ async toMeta(flags, options = {}) {
285
556
  const cwd = resolveCwd(typeof flags.cwd === "string" ? flags.cwd : void 0);
286
557
  const serverFlag = typeof flags.server === "string" ? flags.server : void 0;
287
558
  const environment = typeof flags.environment === "string" ? flags.environment : "development";
288
- const source = await resolveServer({
559
+ const source = options.resolveServer === false ? {
560
+ value: options.source ?? "",
561
+ origin: "default"
562
+ } : await resolveServer({
289
563
  cwd,
290
564
  env: process.env,
291
565
  serverFlag,
@@ -324,8 +598,9 @@ var BaseCommand = class extends Command {
324
598
  * envelope so oclif's `--json` path serialises it.
325
599
  */
326
600
  emit(result) {
327
- this.log(renderPretty(result, this.meta));
328
- return toEnvelope(result, this.meta);
601
+ const normalized = normalizeCommandResult(result, this.meta);
602
+ this.log(renderPretty(normalized, this.meta));
603
+ return toEnvelope(normalized, this.meta);
329
604
  }
330
605
  /**
331
606
  * Renders any thrown error as the failure envelope and exits with its code.
@@ -340,7 +615,7 @@ var BaseCommand = class extends Command {
340
615
  };
341
616
  const zitadelError = toZitadelError(error);
342
617
  if (this.jsonEnabled()) this.logJson(toErrorEnvelope(zitadelError, meta));
343
- else this.logToStderr(renderError(zitadelError));
618
+ else this.logToStderr(renderError(zitadelError, meta));
344
619
  return this.exit(zitadelError.exitCode);
345
620
  }
346
621
  /**
@@ -364,6 +639,24 @@ var BaseCommand = class extends Command {
364
639
  };
365
640
  }
366
641
  };
642
+ function normalizeCommandResult(result, meta) {
643
+ if (result.status === "ok") return {
644
+ ...result,
645
+ data: normalizeDataNextCommands(result.data, meta)
646
+ };
647
+ return {
648
+ ...result,
649
+ data: normalizeDataNextCommands(result.data, meta),
650
+ nextCommands: normalizePublicCliCommands(result.nextCommands, meta.cliVersion)
651
+ };
652
+ }
653
+ function normalizeDataNextCommands(data, meta) {
654
+ if (!isObject(data) || !Array.isArray(data.next_commands)) return data;
655
+ return {
656
+ ...data,
657
+ next_commands: data.next_commands.map((command) => typeof command === "string" ? normalizePublicCliCommand(command, meta.cliVersion) : command)
658
+ };
659
+ }
367
660
  /** Wraps a {@link CommandResult} with the invocation metadata into the final envelope. */
368
661
  function toEnvelope(result, meta) {
369
662
  const base = {
@@ -395,7 +688,7 @@ function toErrorEnvelope(error, meta) {
395
688
  code: error.code,
396
689
  message: error.message,
397
690
  hint: error.hint,
398
- next_commands: error.nextCommands,
691
+ next_commands: normalizePublicCliCommands(error.nextCommands, meta.cliVersion),
399
692
  details: error.details
400
693
  };
401
694
  }
@@ -419,12 +712,13 @@ function renderPretty(result, meta) {
419
712
  * Renders a {@link ZitadelError} as a human-readable block for stderr: the
420
713
  * coded message, an optional hint, and any suggested next commands.
421
714
  */
422
- function renderError(error) {
715
+ function renderError(error, meta) {
423
716
  const lines = [`Error ${error.code}: ${error.message}`];
424
717
  if (error.hint) lines.push(error.hint);
425
- if (error.nextCommands && error.nextCommands.length > 0) {
718
+ const nextCommands = normalizePublicCliCommands(error.nextCommands, meta.cliVersion);
719
+ if (nextCommands && nextCommands.length > 0) {
426
720
  lines.push("Next:");
427
- for (const cmd of error.nextCommands) lines.push(` $ ${cmd}`);
721
+ for (const cmd of nextCommands) lines.push(` $ ${cmd}`);
428
722
  }
429
723
  return lines.join("\n");
430
724
  }
@@ -453,9 +747,16 @@ function formatData(data, warnings, opts) {
453
747
  for (const cmd of data.next_commands) lines.push(` $ ${String(cmd)}`);
454
748
  }
455
749
  }
456
- for (const warning of warnings) lines.push(`Warning: ${warning}`);
750
+ for (const warning of warnings.filter((warning) => !warningRenderedInChecks(data, warning))) lines.push(`Warning: ${warning}`);
457
751
  return lines.join("\n");
458
752
  }
753
+ function warningRenderedInChecks(data, warning) {
754
+ if (!isObject(data) || !Array.isArray(data.checks)) return false;
755
+ return data.checks.some((check) => {
756
+ if (!isObject(check) || check.status !== "warn") return false;
757
+ return warning === `${String(check.name ?? "check")}: ${String(check.message ?? "")}`;
758
+ });
759
+ }
459
760
  function renderKnownSections(lines, data) {
460
761
  if (isObject(data.project)) {
461
762
  const project = data.project;
@@ -483,7 +784,7 @@ function renderKnownSections(lines, data) {
483
784
  lines.push("Checks:");
484
785
  for (const check of data.checks) {
485
786
  if (!isObject(check)) continue;
486
- const status = check.status === "pass" ? "ok" : "fail";
787
+ const status = check.status === "pass" ? "ok" : check.status === "warn" ? "warn" : "fail";
487
788
  lines.push(` [${status}] ${String(check.name ?? "check")}: ${String(check.message ?? "")}`);
488
789
  }
489
790
  }
@@ -502,87 +803,6 @@ function suffixBlock(opts) {
502
803
  return suffix ? ` ${suffix}` : "";
503
804
  }
504
805
  //#endregion
505
- //#region src/lib/project.ts
506
- /**
507
- * Reports whether `cwd` has already been initialized, i.e. a committed
508
- * `zitadel.json` exists. Used to decide whether setup should run or skip.
509
- */
510
- async function hasZitadelConfig(cwd) {
511
- return exists(join(cwd, "zitadel.json"));
512
- }
513
- /**
514
- * Reports whether local secret material (`.zitadel/secret`) is present. Gates
515
- * commands that need credentials, and signals that secrets were already pulled.
516
- */
517
- async function hasZitadelSecret(cwd) {
518
- return exists(join(cwd, ".zitadel/secret"));
519
- }
520
- async function exists(path) {
521
- try {
522
- await stat(path);
523
- return true;
524
- } catch (error) {
525
- if (isNotFound(error)) return false;
526
- throw error;
527
- }
528
- }
529
- /**
530
- * Reads and parses `zitadel.json` into a plain object. Translates a missing
531
- * file into an actionable `E_VALIDATION` error pointing at `zitadel setup`;
532
- * other errors (e.g. malformed JSON) propagate unchanged.
533
- */
534
- async function readZitadelConfig(cwd) {
535
- try {
536
- return parseJsonObject(await readFile(join(cwd, "zitadel.json"), "utf8"), "zitadel.json");
537
- } catch (error) {
538
- if (isNotFound(error)) throw new ZitadelError("E_VALIDATION", "zitadel.json was not found", {
539
- hint: "Run `zitadel setup` first.",
540
- nextCommands: ["zitadel setup"]
541
- });
542
- throw error;
543
- }
544
- }
545
- /**
546
- * Reads, parses, and structurally validates `.zitadel/secret`, returning it
547
- * as a {@link ZitadelSecret}. A missing file becomes an actionable
548
- * `E_VALIDATION` error pointing at `zitadel setup` / `zitadel doctor --fix`;
549
- * a present-but-incomplete file throws so callers never proceed with partial
550
- * credentials.
551
- */
552
- async function readZitadelSecret(cwd) {
553
- try {
554
- const secret = parseJsonObject(await readFile(join(cwd, ".zitadel/secret"), "utf8"), ".zitadel/secret");
555
- if (typeof secret.project_id !== "string" || typeof secret.project_secret !== "string" || typeof secret.preview_secret !== "string" || !Array.isArray(secret.preview_origins)) throw new Error(".zitadel/secret is missing required fields");
556
- return secret;
557
- } catch (error) {
558
- if (isNotFound(error)) throw new ZitadelError("E_VALIDATION", ".zitadel/secret was not found", {
559
- hint: "Run `zitadel setup` first, or restore the project secret with `zitadel doctor --fix`.",
560
- nextCommands: ["zitadel setup", "zitadel doctor --fix"]
561
- });
562
- throw error;
563
- }
564
- }
565
- /**
566
- * Reads the configured renderer id from a parsed `zitadel.json`, normalising the
567
- * legacy `default` alias to `react` and falling back to `react` when unset. The
568
- * value is validated downstream by `getRenderer`, so callers need not re-check.
569
- */
570
- function readRendererId(config) {
571
- const branding = isObject(config.branding) ? config.branding : void 0;
572
- const value = branding && typeof branding.renderer === "string" ? branding.renderer : "react";
573
- return value === "default" ? "react" : value;
574
- }
575
- /** Reads `environments.development.issuer` from a parsed `zitadel.json`, if present. */
576
- function readDevelopmentIssuer(config) {
577
- if (isObject(config.environments) && isObject(config.environments.development)) {
578
- const issuer = config.environments.development.issuer;
579
- return typeof issuer === "string" ? issuer : void 0;
580
- }
581
- }
582
- function isNotFound(error) {
583
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
584
- }
585
- //#endregion
586
- export { readZitadelConfig as a, MANAGED_MARKER as c, parseJsonObject as d, stableStringify as f, readRendererId as i, DEFAULT_SERVER as l, hasZitadelSecret as n, readZitadelSecret as o, ZitadelError as p, readDevelopmentIssuer as r, BaseCommand as s, hasZitadelConfig as t, isObject as u };
806
+ export { isObject as C, toZitadelError as D, ZitadelError as E, resolveCwd as S, stableStringify as T, runtimeSummary as _, DEFAULT_LOCAL_SERVER_PORT as a, publicCliCommand as b, checkLocalServerHealth as c, ensureLocalState as d, localContainerName as f, removeRuntimeMetadata as g, removeLocalData as h, CONTAINER_HTTP_PORT as i, defaultLocalServerImageForCliVersion as l, readRuntimeMetadata as m, DEFAULT_SERVER as n, DEFAULT_LOCAL_SERVER_URL as o, localServerUrl as p, CONTAINER_DATA_DIR as r, assertLocalStateWritable as s, BaseCommand as t, ensureContainerIdentity as u, writeRuntimeMetadata as v, parseJsonObject as w, MANAGED_MARKER as x, npmDistTagForCliVersion as y };
587
807
 
588
- //# sourceMappingURL=project-C3pSfbao.mjs.map
808
+ //# sourceMappingURL=oclif-B7lBzh3R.mjs.map