@zitadel/cli 0.1.0-alpha.0 → 0.1.0-alpha.1
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.
- package/README.md +131 -12
- package/SKILLS.md +18 -3
- package/dist/commands/apply.mjs +3 -2
- package/dist/commands/apply.mjs.map +1 -1
- package/dist/commands/doctor.mjs +123 -13
- package/dist/commands/doctor.mjs.map +1 -1
- package/dist/commands/eject.mjs +3 -2
- package/dist/commands/eject.mjs.map +1 -1
- package/dist/commands/logs.mjs +50 -0
- package/dist/commands/logs.mjs.map +1 -0
- package/dist/commands/plan.mjs +3 -2
- package/dist/commands/plan.mjs.map +1 -1
- package/dist/commands/reset.mjs +63 -0
- package/dist/commands/reset.mjs.map +1 -0
- package/dist/commands/setup.mjs +5 -3
- package/dist/commands/setup.mjs.map +1 -1
- package/dist/commands/start.mjs +132 -0
- package/dist/commands/start.mjs.map +1 -0
- package/dist/commands/status.mjs +68 -27
- package/dist/commands/status.mjs.map +1 -1
- package/dist/commands/stop.mjs +44 -0
- package/dist/commands/stop.mjs.map +1 -0
- package/dist/docker-C0aVpJqm.mjs +209 -0
- package/dist/docker-C0aVpJqm.mjs.map +1 -0
- package/dist/{project-C3pSfbao.mjs → oclif-DSPO9Sck.mjs} +216 -91
- package/dist/oclif-DSPO9Sck.mjs.map +1 -0
- package/dist/{orca-COsUnVoz.mjs → orca-DOxshV9n.mjs} +6 -3
- package/dist/{orca-COsUnVoz.mjs.map → orca-DOxshV9n.mjs.map} +1 -1
- package/dist/project-Dwb9WVAT.mjs +87 -0
- package/dist/project-Dwb9WVAT.mjs.map +1 -0
- package/dist/{sync-Cuyh-X1J.mjs → sync-DmtTYNmq.mjs} +3 -3
- package/dist/{sync-Cuyh-X1J.mjs.map → sync-DmtTYNmq.mjs.map} +1 -1
- package/oclif.manifest.json +351 -3
- package/package.json +1 -1
- package/dist/project-C3pSfbao.mjs.map +0 -1
package/dist/commands/eject.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as createOrca } from "../orca-
|
|
1
|
+
import { T as ZitadelError, t as BaseCommand } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { t as createOrca } from "../orca-DOxshV9n.mjs";
|
|
3
|
+
import { a as readZitadelConfig, i as readRendererId } from "../project-Dwb9WVAT.mjs";
|
|
3
4
|
import { readFile, rename, rm, stat } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
//#region src/commands/eject.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eject.mjs","names":[],"sources":["../../src/commands/eject.ts"],"sourcesContent":["import { readFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport { createOrca } from \"../lib/orca\";\nimport type { EjectActions } from \"../lib/orca/patchers/types\";\nimport { MANAGED_MARKER } from \"../lib/paths\";\nimport { readRendererId, readZitadelConfig } from \"../lib/project\";\n\n/**\n * Asks the framework patcher which artifacts it owns. Falls back to the\n * framework-agnostic set (`zitadel.json`, `.zitadel/`, `.env.local`) when the\n * framework or its patcher cannot be resolved, so an orphaned/partial project\n * can still be cleaned up.\n */\nasync function resolveEjectActions(cwd: string): Promise<EjectActions> {\n const fallback: EjectActions = {\n markedFiles: [],\n rootConfigFiles: [\"zitadel.json\"],\n directories: [\".zitadel\"],\n envBackups: [\".env.local\"],\n dependencies: [],\n };\n const orca = createOrca();\n const framework = await orca.tryDetect(cwd);\n if (!framework) {\n return fallback;\n }\n try {\n const config = await readZitadelConfig(cwd).catch(() => ({}) as Record<string, unknown>);\n return orca.patcherFor(framework.id).artifacts({\n framework,\n rendererId: readRendererId(config),\n });\n } catch {\n return fallback;\n }\n}\n\n/**\n * Builds the `next_commands` envelope field — manual follow-ups `eject` can't\n * safely run itself: deleting the `.env.local.ejected-*` backups it created\n * (only when some were made), and uninstalling the SDK packages the patcher\n * added (the CLI never modifies the user's `package.json` + lockfile +\n * `node_modules` directly; it just suggests the command).\n */\nfunction assembleNextCommands(\n backedUp: ReadonlyArray<unknown>,\n dependencies: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n const commands: string[] = [];\n if (backedUp.length > 0) {\n commands.push(\"rm -f .env.local.ejected-*\");\n }\n for (const dep of dependencies) {\n commands.push(`npm uninstall ${dep}`);\n }\n return commands;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * `zitadel eject` — remove managed files and local Zitadel state.\n *\n * Removes Zitadel-managed files from the project, leaving the remote project\n * untouched. The set of files comes from the framework patcher's\n * {@link import(\"../lib/orca/patchers/types\").Patcher.artifacts}, so the patcher\n * is the single source of truth for what its integration owns.\n *\n * Marked code files are removed only when they still carry the managed marker\n * (user-replaced files are preserved); `zitadel.json` is removed; `.env.local`\n * is renamed to a timestamped backup; and `.zitadel/` is removed wholesale.\n * `--dry-run` reports without touching the filesystem; non-interactive runs\n * require `--force`.\n */\nexport default class Eject extends BaseCommand {\n static override description = \"Remove managed files and local Zitadel state.\";\n static override aliases = [\"uninstall\"];\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Eject);\n await this.toMeta(flags);\n const { cwd, force, nonInteractive, dryRun } = this.meta;\n\n if (!force && nonInteractive) {\n throw new ZitadelError(\"E_VALIDATION\", \"Eject requires --force in non-interactive mode\", {\n hint: \"Re-run with --force to confirm deletion of managed files.\",\n });\n }\n\n const actions = await resolveEjectActions(cwd);\n const removed: string[] = [];\n const preserved: string[] = [];\n const backedUp: string[] = [];\n\n for (const rel of actions.markedFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n const contents = await readFile(abs, \"utf8\").catch(() => \"\");\n if (!contents.includes(MANAGED_MARKER)) {\n preserved.push(rel);\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.rootConfigFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.envBackups) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (dryRun) {\n backedUp.push(`${rel} -> ${rel}.ejected-<timestamp>`);\n continue;\n }\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backup = `${abs}.ejected-${stamp}`;\n await rename(abs, backup);\n backedUp.push(`${rel} -> ${backup.slice(cwd.length + 1)}`);\n }\n\n for (const rel of actions.directories) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { recursive: true, force: true });\n }\n removed.push(rel);\n }\n\n if (removed.length === 0 && backedUp.length === 0) {\n return this.emit({ status: \"skipped\", reason: \"nothing-to-eject\", data: { cwd } });\n }\n\n const nextCommands = assembleNextCommands(backedUp, actions.dependencies);\n\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Zitadel ejected. Remote project is untouched.\",\n files_removed: removed,\n files_preserved: preserved,\n backed_up: backedUp,\n next_commands: nextCommands,\n },\n });\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"eject.mjs","names":[],"sources":["../../src/commands/eject.ts"],"sourcesContent":["import { readFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport { createOrca } from \"../lib/orca\";\nimport type { EjectActions } from \"../lib/orca/patchers/types\";\nimport { MANAGED_MARKER } from \"../lib/paths\";\nimport { readRendererId, readZitadelConfig } from \"../lib/project\";\n\n/**\n * Asks the framework patcher which artifacts it owns. Falls back to the\n * framework-agnostic set (`zitadel.json`, `.zitadel/`, `.env.local`) when the\n * framework or its patcher cannot be resolved, so an orphaned/partial project\n * can still be cleaned up.\n */\nasync function resolveEjectActions(cwd: string): Promise<EjectActions> {\n const fallback: EjectActions = {\n markedFiles: [],\n rootConfigFiles: [\"zitadel.json\"],\n directories: [\".zitadel\"],\n envBackups: [\".env.local\"],\n dependencies: [],\n };\n const orca = createOrca();\n const framework = await orca.tryDetect(cwd);\n if (!framework) {\n return fallback;\n }\n try {\n const config = await readZitadelConfig(cwd).catch(() => ({}) as Record<string, unknown>);\n return orca.patcherFor(framework.id).artifacts({\n framework,\n rendererId: readRendererId(config),\n });\n } catch {\n return fallback;\n }\n}\n\n/**\n * Builds the `next_commands` envelope field — manual follow-ups `eject` can't\n * safely run itself: deleting the `.env.local.ejected-*` backups it created\n * (only when some were made), and uninstalling the SDK packages the patcher\n * added (the CLI never modifies the user's `package.json` + lockfile +\n * `node_modules` directly; it just suggests the command).\n */\nfunction assembleNextCommands(\n backedUp: ReadonlyArray<unknown>,\n dependencies: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n const commands: string[] = [];\n if (backedUp.length > 0) {\n commands.push(\"rm -f .env.local.ejected-*\");\n }\n for (const dep of dependencies) {\n commands.push(`npm uninstall ${dep}`);\n }\n return commands;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * `zitadel eject` — remove managed files and local Zitadel state.\n *\n * Removes Zitadel-managed files from the project, leaving the remote project\n * untouched. The set of files comes from the framework patcher's\n * {@link import(\"../lib/orca/patchers/types\").Patcher.artifacts}, so the patcher\n * is the single source of truth for what its integration owns.\n *\n * Marked code files are removed only when they still carry the managed marker\n * (user-replaced files are preserved); `zitadel.json` is removed; `.env.local`\n * is renamed to a timestamped backup; and `.zitadel/` is removed wholesale.\n * `--dry-run` reports without touching the filesystem; non-interactive runs\n * require `--force`.\n */\nexport default class Eject extends BaseCommand {\n static override description = \"Remove managed files and local Zitadel state.\";\n static override aliases = [\"uninstall\"];\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Eject);\n await this.toMeta(flags);\n const { cwd, force, nonInteractive, dryRun } = this.meta;\n\n if (!force && nonInteractive) {\n throw new ZitadelError(\"E_VALIDATION\", \"Eject requires --force in non-interactive mode\", {\n hint: \"Re-run with --force to confirm deletion of managed files.\",\n });\n }\n\n const actions = await resolveEjectActions(cwd);\n const removed: string[] = [];\n const preserved: string[] = [];\n const backedUp: string[] = [];\n\n for (const rel of actions.markedFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n const contents = await readFile(abs, \"utf8\").catch(() => \"\");\n if (!contents.includes(MANAGED_MARKER)) {\n preserved.push(rel);\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.rootConfigFiles) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { force: true });\n }\n removed.push(rel);\n }\n\n for (const rel of actions.envBackups) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (dryRun) {\n backedUp.push(`${rel} -> ${rel}.ejected-<timestamp>`);\n continue;\n }\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backup = `${abs}.ejected-${stamp}`;\n await rename(abs, backup);\n backedUp.push(`${rel} -> ${backup.slice(cwd.length + 1)}`);\n }\n\n for (const rel of actions.directories) {\n const abs = join(cwd, rel);\n if (!(await pathExists(abs))) {\n continue;\n }\n if (!dryRun) {\n await rm(abs, { recursive: true, force: true });\n }\n removed.push(rel);\n }\n\n if (removed.length === 0 && backedUp.length === 0) {\n return this.emit({ status: \"skipped\", reason: \"nothing-to-eject\", data: { cwd } });\n }\n\n const nextCommands = assembleNextCommands(backedUp, actions.dependencies);\n\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Zitadel ejected. Remote project is untouched.\",\n files_removed: removed,\n files_preserved: preserved,\n backed_up: backedUp,\n next_commands: nextCommands,\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { T as ZitadelError, _ as readRuntimeMetadata, t as BaseCommand } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { a as followContainerLogs, t as containerLogs } from "../docker-C0aVpJqm.mjs";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
//#region src/commands/logs.ts
|
|
5
|
+
var Logs = class Logs extends BaseCommand {
|
|
6
|
+
static description = "Show local Zitadel server logs.";
|
|
7
|
+
static flags = {
|
|
8
|
+
follow: Flags.boolean({ description: "Follow logs." }),
|
|
9
|
+
tail: Flags.integer({
|
|
10
|
+
description: "Number of lines to show.",
|
|
11
|
+
default: 200
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(Logs);
|
|
16
|
+
const runtime = await readRuntimeMetadata(flags.cwd ? String(flags.cwd) : process.cwd());
|
|
17
|
+
await this.toMeta(flags, {
|
|
18
|
+
resolveServer: false,
|
|
19
|
+
source: runtime?.server_url ?? "http://localhost:8080"
|
|
20
|
+
});
|
|
21
|
+
if (!runtime) throw new ZitadelError("E_VALIDATION", "Local Zitadel runtime has not been started", {
|
|
22
|
+
hint: "Run `zitadel start` first.",
|
|
23
|
+
nextCommands: ["zitadel start"]
|
|
24
|
+
});
|
|
25
|
+
const tail = flags.tail ?? 200;
|
|
26
|
+
if (!Number.isInteger(tail) || tail < 1) throw new ZitadelError("E_VALIDATION", `Invalid tail value ${String(tail)}`, { hint: "Use a positive integer." });
|
|
27
|
+
if (flags.follow) {
|
|
28
|
+
if (this.jsonEnabled()) throw new ZitadelError("E_VALIDATION", "Cannot stream logs with --json", { hint: "Run without --json, or omit --follow." });
|
|
29
|
+
await followContainerLogs(runtime.container_name, tail);
|
|
30
|
+
return this.emit({
|
|
31
|
+
status: "ok",
|
|
32
|
+
data: { title: "Stopped following local Zitadel logs." }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const logs = await containerLogs(runtime.container_name, tail);
|
|
36
|
+
return this.emit({
|
|
37
|
+
status: "ok",
|
|
38
|
+
pretty: logs.trimEnd(),
|
|
39
|
+
data: {
|
|
40
|
+
title: "Local Zitadel server logs.",
|
|
41
|
+
container_name: runtime.container_name,
|
|
42
|
+
logs
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { Logs as default };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=logs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.mjs","names":[],"sources":["../../src/commands/logs.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport { containerLogs, followContainerLogs } from \"../lib/local-server/docker\";\nimport { DEFAULT_LOCAL_SERVER_URL, readRuntimeMetadata } from \"../lib/local-server/runtime\";\n\nexport default class Logs extends BaseCommand {\n static override description = \"Show local Zitadel server logs.\";\n static override flags = {\n follow: Flags.boolean({ description: \"Follow logs.\" }),\n tail: Flags.integer({ description: \"Number of lines to show.\", default: 200 }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Logs);\n const runtime = await readRuntimeMetadata(flags.cwd ? String(flags.cwd) : process.cwd());\n await this.toMeta(flags, {\n resolveServer: false,\n source: runtime?.server_url ?? DEFAULT_LOCAL_SERVER_URL,\n });\n\n if (!runtime) {\n throw new ZitadelError(\"E_VALIDATION\", \"Local Zitadel runtime has not been started\", {\n hint: \"Run `zitadel start` first.\",\n nextCommands: [\"zitadel start\"],\n });\n }\n\n const tail = flags.tail ?? 200;\n if (!Number.isInteger(tail) || tail < 1) {\n throw new ZitadelError(\"E_VALIDATION\", `Invalid tail value ${String(tail)}`, {\n hint: \"Use a positive integer.\",\n });\n }\n\n if (flags.follow) {\n if (this.jsonEnabled()) {\n throw new ZitadelError(\"E_VALIDATION\", \"Cannot stream logs with --json\", {\n hint: \"Run without --json, or omit --follow.\",\n });\n }\n await followContainerLogs(runtime.container_name, tail);\n return this.emit({\n status: \"ok\",\n data: { title: \"Stopped following local Zitadel logs.\" },\n });\n }\n\n const logs = await containerLogs(runtime.container_name, tail);\n return this.emit({\n status: \"ok\",\n pretty: logs.trimEnd(),\n data: {\n title: \"Local Zitadel server logs.\",\n container_name: runtime.container_name,\n logs,\n },\n });\n }\n}\n"],"mappings":";;;;AAOA,IAAqB,OAArB,MAAqB,aAAa,YAAY;CAC5C,OAAgB,cAAc;CAC9B,OAAgB,QAAQ;EACtB,QAAQ,MAAM,QAAQ,EAAE,aAAa,gBAAgB,CAAC;EACtD,MAAM,MAAM,QAAQ;GAAE,aAAa;GAA4B,SAAS;GAAK,CAAC;EAC/E;CAED,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK;EACxC,MAAM,UAAU,MAAM,oBAAoB,MAAM,MAAM,OAAO,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC;AACxF,QAAM,KAAK,OAAO,OAAO;GACvB,eAAe;GACf,QAAQ,SAAS,cAAA;GAClB,CAAC;AAEF,MAAI,CAAC,QACH,OAAM,IAAI,aAAa,gBAAgB,8CAA8C;GACnF,MAAM;GACN,cAAc,CAAC,gBAAgB;GAChC,CAAC;EAGJ,MAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,EACpC,OAAM,IAAI,aAAa,gBAAgB,sBAAsB,OAAO,KAAK,IAAI,EAC3E,MAAM,2BACP,CAAC;AAGJ,MAAI,MAAM,QAAQ;AAChB,OAAI,KAAK,aAAa,CACpB,OAAM,IAAI,aAAa,gBAAgB,kCAAkC,EACvE,MAAM,yCACP,CAAC;AAEJ,SAAM,oBAAoB,QAAQ,gBAAgB,KAAK;AACvD,UAAO,KAAK,KAAK;IACf,QAAQ;IACR,MAAM,EAAE,OAAO,yCAAyC;IACzD,CAAC;;EAGJ,MAAM,OAAO,MAAM,cAAc,QAAQ,gBAAgB,KAAK;AAC9D,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,QAAQ,KAAK,SAAS;GACtB,MAAM;IACJ,OAAO;IACP,gBAAgB,QAAQ;IACxB;IACD;GACF,CAAC"}
|
package/dist/commands/plan.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { t as BaseCommand } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { o as readZitadelSecret } from "../project-Dwb9WVAT.mjs";
|
|
3
|
+
import { a as makeSyncers, n as summarizePlan, o as environmentSchema, r as buildSyncPlan, t as renderPlan } from "../sync-DmtTYNmq.mjs";
|
|
3
4
|
import { Flags } from "@oclif/core";
|
|
4
5
|
import { consola as consola$1 } from "consola";
|
|
5
6
|
import { createZitadelClient } from "@zitadel/api/client";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.mjs","names":[],"sources":["../../src/commands/plan.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\nimport { consola } from \"consola\";\n\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { environmentSchema } from \"../lib/environment\";\nimport { buildSyncPlan, makeSyncers, renderPlan, summarizePlan } from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\n\n/**\n * `zitadel plan` — validate config and preview the sync diff without mutating.\n *\n * The read-only counterpart of `apply`: it builds and renders the diff instead\n * of running the sync loop. All validation (structural shape and env-ref\n * presence) happens in the sync engine, so an invalid file fails the same way\n * `apply` would.\n */\nexport default class Plan extends BaseCommand {\n static override description = \"Validate config without mutation and preview the sync diff.\";\n // Temporarily hidden while we collapse the dev workflow around `setup`'s\n // auto-apply. The logic stays wired up so re-exposing this command is a\n // one-line flip when we settle on the surface area.\n static override hidden = true;\n static override flags = {\n environment: Flags.string({\n char: \"e\",\n description: \"Target environment (default: development).\",\n options: [...environmentSchema.options],\n }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Plan);\n await this.toMeta(flags);\n const { cwd, source, env, isTTY } = this.meta;\n\n const secret = await readZitadelSecret(cwd);\n consola.info(`Project ${secret.project_id}`);\n consola.info(`Server ${source}`);\n const client = createZitadelClient({\n baseUrl: source,\n token: secret.project_secret,\n });\n const syncers = makeSyncers({ client, projectId: secret.project_id, env });\n\n consola.start(\"Building plan\");\n const plan = await buildSyncPlan(cwd, syncers, true);\n const summary = summarizePlan(plan);\n consola.success(\n `Plan: ${summary.creates} create${summary.creates === 1 ? \"\" : \"s\"}, ` +\n `${summary.updates} update${summary.updates === 1 ? \"\" : \"s\"}, ` +\n `${summary.deletes} delete${summary.deletes === 1 ? \"\" : \"s\"}, ` +\n `${summary.total - summary.creates - summary.updates - summary.deletes} unchanged`,\n );\n return this.emit({\n status: \"ok\",\n data: summary,\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.mjs","names":[],"sources":["../../src/commands/plan.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\nimport { consola } from \"consola\";\n\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { environmentSchema } from \"../lib/environment\";\nimport { buildSyncPlan, makeSyncers, renderPlan, summarizePlan } from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\n\n/**\n * `zitadel plan` — validate config and preview the sync diff without mutating.\n *\n * The read-only counterpart of `apply`: it builds and renders the diff instead\n * of running the sync loop. All validation (structural shape and env-ref\n * presence) happens in the sync engine, so an invalid file fails the same way\n * `apply` would.\n */\nexport default class Plan extends BaseCommand {\n static override description = \"Validate config without mutation and preview the sync diff.\";\n // Temporarily hidden while we collapse the dev workflow around `setup`'s\n // auto-apply. The logic stays wired up so re-exposing this command is a\n // one-line flip when we settle on the surface area.\n static override hidden = true;\n static override flags = {\n environment: Flags.string({\n char: \"e\",\n description: \"Target environment (default: development).\",\n options: [...environmentSchema.options],\n }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Plan);\n await this.toMeta(flags);\n const { cwd, source, env, isTTY } = this.meta;\n\n const secret = await readZitadelSecret(cwd);\n consola.info(`Project ${secret.project_id}`);\n consola.info(`Server ${source}`);\n const client = createZitadelClient({\n baseUrl: source,\n token: secret.project_secret,\n });\n const syncers = makeSyncers({ client, projectId: secret.project_id, env });\n\n consola.start(\"Building plan\");\n const plan = await buildSyncPlan(cwd, syncers, true);\n const summary = summarizePlan(plan);\n consola.success(\n `Plan: ${summary.creates} create${summary.creates === 1 ? \"\" : \"s\"}, ` +\n `${summary.updates} update${summary.updates === 1 ? \"\" : \"s\"}, ` +\n `${summary.deletes} delete${summary.deletes === 1 ? \"\" : \"s\"}, ` +\n `${summary.total - summary.creates - summary.updates - summary.deletes} unchanged`,\n );\n return this.emit({\n status: \"ok\",\n data: summary,\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,IAAqB,OAArB,MAAqB,aAAa,YAAY;CAC5C,OAAgB,cAAc;CAI9B,OAAgB,SAAS;CACzB,OAAgB,QAAQ,EACtB,aAAa,MAAM,OAAO;EACxB,MAAM;EACN,aAAa;EACb,SAAS,CAAC,GAAG,kBAAkB,QAAQ;EACxC,CAAC,EACH;CAED,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK;AACxC,QAAM,KAAK,OAAO,MAAM;EACxB,MAAM,EAAE,KAAK,QAAQ,KAAK,UAAU,KAAK;EAEzC,MAAM,SAAS,MAAM,kBAAkB,IAAI;AAC3C,YAAQ,KAAK,aAAa,OAAO,aAAa;AAC9C,YAAQ,KAAK,aAAa,SAAS;EAKnC,MAAM,UAAU,YAAY;GAAE,QAJf,oBAAoB;IACjC,SAAS;IACT,OAAO,OAAO;IACf,CACmC;GAAE,WAAW,OAAO;GAAY;GAAK,CAAC;AAE1E,YAAQ,MAAM,gBAAgB;EAC9B,MAAM,OAAO,MAAM,cAAc,KAAK,SAAS,KAAK;EACpD,MAAM,UAAU,cAAc,KAAK;AACnC,YAAQ,QACN,SAAS,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC9D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,UAAU,QAAQ,QAAQ,YAC1E;AACD,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;GACN,QAAQ,WAAW,MAAM,MAAM;GAChC,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { T as ZitadelError, _ as readRuntimeMetadata, m as localContainerName, t as BaseCommand, v as removeLocalData, y as removeRuntimeMetadata } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { u as stopAndRemoveContainer } from "../docker-C0aVpJqm.mjs";
|
|
3
|
+
import { cancel, confirm, isCancel } from "@clack/prompts";
|
|
4
|
+
//#region src/commands/reset.ts
|
|
5
|
+
var Reset = class Reset extends BaseCommand {
|
|
6
|
+
static description = "Delete the local Zitadel server runtime and data.";
|
|
7
|
+
async run() {
|
|
8
|
+
const { flags } = await this.parse(Reset);
|
|
9
|
+
const runtime = await readRuntimeMetadata(flags.cwd ? String(flags.cwd) : process.cwd());
|
|
10
|
+
await this.toMeta(flags, {
|
|
11
|
+
resolveServer: false,
|
|
12
|
+
source: runtime?.server_url ?? "http://localhost:8080"
|
|
13
|
+
});
|
|
14
|
+
const containerName = runtime?.container_name ?? localContainerName(this.meta.cwd);
|
|
15
|
+
if (this.meta.dryRun) return this.emit({
|
|
16
|
+
status: "ok",
|
|
17
|
+
data: {
|
|
18
|
+
title: "Local Zitadel server runtime reset plan.",
|
|
19
|
+
runtime: {
|
|
20
|
+
container_name: containerName,
|
|
21
|
+
data_deleted: true
|
|
22
|
+
},
|
|
23
|
+
next_commands: ["zitadel reset --force"]
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
if (!this.meta.force) {
|
|
27
|
+
if (this.meta.nonInteractive) throw new ZitadelError("E_VALIDATION", "Reset requires --force in non-interactive mode", {
|
|
28
|
+
hint: "Pass --force to delete `.zitadel/local/nextgen-data`.",
|
|
29
|
+
nextCommands: ["zitadel reset --force"]
|
|
30
|
+
});
|
|
31
|
+
const answer = await confirm({
|
|
32
|
+
message: "Delete the local Zitadel container and .zitadel/local/nextgen-data?",
|
|
33
|
+
initialValue: false
|
|
34
|
+
});
|
|
35
|
+
if (isCancel(answer)) {
|
|
36
|
+
cancel("Reset cancelled.");
|
|
37
|
+
throw new ZitadelError("E_VALIDATION", "Reset cancelled by user");
|
|
38
|
+
}
|
|
39
|
+
if (!answer) return this.emit({
|
|
40
|
+
status: "skipped",
|
|
41
|
+
reason: "reset-cancelled"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
await stopAndRemoveContainer(containerName);
|
|
45
|
+
await removeLocalData(this.meta.cwd);
|
|
46
|
+
await removeRuntimeMetadata(this.meta.cwd);
|
|
47
|
+
return this.emit({
|
|
48
|
+
status: "ok",
|
|
49
|
+
data: {
|
|
50
|
+
title: "Local Zitadel server runtime reset.",
|
|
51
|
+
runtime: {
|
|
52
|
+
container_name: containerName,
|
|
53
|
+
data_deleted: true
|
|
54
|
+
},
|
|
55
|
+
next_commands: ["zitadel start"]
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { Reset as default };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=reset.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.mjs","names":[],"sources":["../../src/commands/reset.ts"],"sourcesContent":["import { cancel, confirm, isCancel } from \"@clack/prompts\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport { stopAndRemoveContainer } from \"../lib/local-server/docker\";\nimport {\n DEFAULT_LOCAL_SERVER_URL,\n localContainerName,\n readRuntimeMetadata,\n removeLocalData,\n removeRuntimeMetadata,\n} from \"../lib/local-server/runtime\";\n\nexport default class Reset extends BaseCommand {\n static override description = \"Delete the local Zitadel server runtime and data.\";\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Reset);\n const runtime = await readRuntimeMetadata(flags.cwd ? String(flags.cwd) : process.cwd());\n await this.toMeta(flags, {\n resolveServer: false,\n source: runtime?.server_url ?? DEFAULT_LOCAL_SERVER_URL,\n });\n\n const containerName = runtime?.container_name ?? localContainerName(this.meta.cwd);\n if (this.meta.dryRun) {\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Local Zitadel server runtime reset plan.\",\n runtime: {\n container_name: containerName,\n data_deleted: true,\n },\n next_commands: [\"zitadel reset --force\"],\n },\n });\n }\n\n if (!this.meta.force) {\n if (this.meta.nonInteractive) {\n throw new ZitadelError(\"E_VALIDATION\", \"Reset requires --force in non-interactive mode\", {\n hint: \"Pass --force to delete `.zitadel/local/nextgen-data`.\",\n nextCommands: [\"zitadel reset --force\"],\n });\n }\n const answer = await confirm({\n message: \"Delete the local Zitadel container and .zitadel/local/nextgen-data?\",\n initialValue: false,\n });\n if (isCancel(answer)) {\n cancel(\"Reset cancelled.\");\n throw new ZitadelError(\"E_VALIDATION\", \"Reset cancelled by user\");\n }\n if (!answer) {\n return this.emit({ status: \"skipped\", reason: \"reset-cancelled\" });\n }\n }\n\n await stopAndRemoveContainer(containerName);\n await removeLocalData(this.meta.cwd);\n await removeRuntimeMetadata(this.meta.cwd);\n\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Local Zitadel server runtime reset.\",\n runtime: {\n container_name: containerName,\n data_deleted: true,\n },\n next_commands: [\"zitadel start\"],\n },\n });\n }\n}\n"],"mappings":";;;;AAaA,IAAqB,QAArB,MAAqB,cAAc,YAAY;CAC7C,OAAgB,cAAc;CAE9B,MAAM,MAA6B;EACjC,MAAM,EAAE,UAAU,MAAM,KAAK,MAAM,MAAM;EACzC,MAAM,UAAU,MAAM,oBAAoB,MAAM,MAAM,OAAO,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC;AACxF,QAAM,KAAK,OAAO,OAAO;GACvB,eAAe;GACf,QAAQ,SAAS,cAAA;GAClB,CAAC;EAEF,MAAM,gBAAgB,SAAS,kBAAkB,mBAAmB,KAAK,KAAK,IAAI;AAClF,MAAI,KAAK,KAAK,OACZ,QAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;IACJ,OAAO;IACP,SAAS;KACP,gBAAgB;KAChB,cAAc;KACf;IACD,eAAe,CAAC,wBAAwB;IACzC;GACF,CAAC;AAGJ,MAAI,CAAC,KAAK,KAAK,OAAO;AACpB,OAAI,KAAK,KAAK,eACZ,OAAM,IAAI,aAAa,gBAAgB,kDAAkD;IACvF,MAAM;IACN,cAAc,CAAC,wBAAwB;IACxC,CAAC;GAEJ,MAAM,SAAS,MAAM,QAAQ;IAC3B,SAAS;IACT,cAAc;IACf,CAAC;AACF,OAAI,SAAS,OAAO,EAAE;AACpB,WAAO,mBAAmB;AAC1B,UAAM,IAAI,aAAa,gBAAgB,0BAA0B;;AAEnE,OAAI,CAAC,OACH,QAAO,KAAK,KAAK;IAAE,QAAQ;IAAW,QAAQ;IAAmB,CAAC;;AAItE,QAAM,uBAAuB,cAAc;AAC3C,QAAM,gBAAgB,KAAK,KAAK,IAAI;AACpC,QAAM,sBAAsB,KAAK,KAAK,IAAI;AAE1C,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;IACJ,OAAO;IACP,SAAS;KACP,gBAAgB;KAChB,cAAc;KACf;IACD,eAAe,CAAC,gBAAgB;IACjC;GACF,CAAC"}
|
package/dist/commands/setup.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as RENDERER_IDS, r as issuerFromPort, t as createOrca } from "../orca-
|
|
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";
|
|
3
4
|
import { Flags } from "@oclif/core";
|
|
4
5
|
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
5
6
|
import { consola as consola$1 } from "consola";
|
|
@@ -492,7 +493,8 @@ var Setup = class Setup extends BaseCommand {
|
|
|
492
493
|
rendererId: flags.renderer ?? "react",
|
|
493
494
|
project,
|
|
494
495
|
issuer,
|
|
495
|
-
server: answers.server
|
|
496
|
+
server: answers.server,
|
|
497
|
+
cliVersion: this.meta.cliVersion
|
|
496
498
|
};
|
|
497
499
|
consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
|
|
498
500
|
const result = await orca.patcherFor(framework.id).patch(ctx, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","names":["styleUrl","stylePath","styleId"],"sources":["../../src/commands/setup/prompts/cancel.ts","../../src/commands/setup/prompts/dev-port.ts","../../src/commands/setup/prompts/framework-confirm.ts","../../src/lib/prober/ports.ts","../../src/lib/prober/http.ts","../../src/commands/setup/prompts/server.ts","../../src/commands/setup/prompts/pick-framework.ts","../../src/commands/setup/prompts/index.ts","../../src/commands/setup/summary.ts","../../src/commands/setup/index.ts"],"sourcesContent":["import { cancel, isCancel } from \"@clack/prompts\";\n\nimport { ZitadelError } from \"../../../lib/errors\";\n\n/**\n * Converts a clack cancellation (Ctrl-C) into a thrown `E_VALIDATION` rather\n * than a partial answer. Every prompt funnels its clack return value through\n * this so the wizard never proceeds with a sentinel or missing value.\n */\nexport function bail<T>(value: T | symbol): asserts value is T {\n if (isCancel(value)) {\n cancel(\"Setup cancelled.\");\n throw new ZitadelError(\"E_VALIDATION\", \"Setup cancelled by user\");\n }\n}\n","import { text } from \"@clack/prompts\";\n\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/**\n * \"Dev server port\" — defaults to the detected port. The validated answer\n * becomes the issuer URL (`http://localhost:<port>`) via `issuerFromPort`.\n */\nexport class DevPortPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, _ctx: PromptContext): Promise<SetupAnswers> {\n const value = await text({\n message: \"Dev server port\",\n placeholder: String(answers.devPort),\n initialValue: String(answers.devPort),\n validate: (input) => {\n const num = Number.parseInt(input ?? \"\", 10);\n return Number.isFinite(num) && num > 0 && num < 65536\n ? undefined\n : \"Must be a port number\";\n },\n });\n bail(value);\n return { ...answers, devPort: Number.parseInt(String(value), 10) };\n }\n}\n","import { confirm } from \"@clack/prompts\";\n\nimport { ZitadelError } from \"../../../lib/errors\";\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/**\n * \"Detected `<framework>`. Proceed?\" — the wizard's first question. Accepting\n * leaves answers unchanged; declining throws `E_UNSUPPORTED_PROJECT_SHAPE`\n * (the user should re-run with an explicit `--framework`).\n */\nexport class FrameworkConfirmPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, ctx: PromptContext): Promise<SetupAnswers> {\n const ack = await confirm({\n message: `Detected ${ctx.framework.id}. Proceed?`,\n initialValue: true,\n });\n bail(ack);\n if (ack === false) {\n throw new ZitadelError(\n \"E_UNSUPPORTED_PROJECT_SHAPE\",\n \"Setup cancelled — framework declined\",\n { hint: `Re-run with --framework ${ctx.framework.id} when ready.` },\n );\n }\n return answers;\n }\n}\n","import { execFile } from \"node:child_process\";\n\n/**\n * Enumerate TCP ports currently in LISTEN state on the loopback interface\n * (`127.0.0.1`, `::1`, or the wildcard `*`). Spawns `lsof -iTCP -sTCP:LISTEN\n * -P -n -F n` and parses its machine-readable output. Returns the unique,\n * numerically-sorted list of ports.\n *\n * Never throws. Returns `[]` whenever lsof is unavailable (e.g. Windows,\n * unusual PATH), the spawn errors, exits non-zero, or the call exceeds\n * `timeoutMs` (default 1000ms). The caller treats an empty list the same as\n * \"no listeners worth probing.\"\n */\nexport async function listListeningPorts(opts?: {\n readonly timeoutMs?: number;\n}): Promise<ReadonlyArray<number>> {\n const timeoutMs = opts?.timeoutMs ?? 1000;\n try {\n const stdout = await runLsof(timeoutMs);\n return parseLsofPorts(stdout);\n } catch {\n return [];\n }\n}\n\n/**\n * Spawn `lsof` with the canned argv and resolve to its stdout. Hand-rolled\n * rather than `util.promisify(execFile)` because the latter resolves with a\n * `{stdout, stderr}` object via its custom-promisify symbol — a heavier shape\n * to mock and worse to read at the call site, where we only ever want stdout.\n */\nfunction runLsof(timeoutMs: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n execFile(\n \"lsof\",\n [\"-iTCP\", \"-sTCP:LISTEN\", \"-P\", \"-n\", \"-F\", \"n\"],\n { timeout: timeoutMs, encoding: \"utf8\" },\n (err, stdout) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(stdout);\n },\n );\n });\n}\n\n/**\n * Parse the `n` records emitted by `lsof -F n`. Each record is a single line\n * `n<address>` where `<address>` ends in `:<port>` (e.g. `n*:8080`,\n * `n127.0.0.1:3000`, `n[::1]:5050`). Only loopback/wildcard hosts are kept;\n * external interface bindings are ignored.\n */\nfunction parseLsofPorts(stdout: string): ReadonlyArray<number> {\n const ports = new Set<number>();\n for (const line of stdout.split(/\\r?\\n/)) {\n if (!line.startsWith(\"n\")) {\n continue;\n }\n const address = line.slice(1);\n const colon = address.lastIndexOf(\":\");\n if (colon < 0) {\n continue;\n }\n const host = address.slice(0, colon);\n const portStr = address.slice(colon + 1);\n if (!isLoopback(host)) {\n continue;\n }\n const port = Number.parseInt(portStr, 10);\n if (Number.isFinite(port) && port > 0 && port < 65536) {\n ports.add(port);\n }\n }\n return [...ports].sort((a, b) => a - b);\n}\n\n/** Recognised loopback host strings as emitted by `lsof -F n`. */\nfunction isLoopback(host: string): boolean {\n return host === \"*\" || host === \"127.0.0.1\" || host === \"[::1]\" || host === \"::1\";\n}\n","import type { ProbeMatch, ProbePredicate } from \"./types\";\n\n/**\n * Fetch `url` with a per-call timeout and pass the resulting `Response` to\n * `predicate`. Returns the predicate's value, or `null` on any failure: a\n * network error, an `AbortError` from the timeout, a thrown predicate, or the\n * predicate returning `null`.\n *\n * Never throws. The predicate decides what counts as a match — it gets the\n * raw `Response` and may read `.json()` / `.headers` / `.status` as needed.\n *\n * `timeoutMs` defaults to 500ms. The implementation uses\n * `AbortSignal.timeout`, so the underlying fetch is cancelled when the\n * timeout fires (no orphaned sockets).\n */\nexport async function probeUrl<T>(\n url: string,\n predicate: ProbePredicate<T>,\n opts?: { readonly timeoutMs?: number },\n): Promise<T | null> {\n const timeoutMs = opts?.timeoutMs ?? 500;\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n return await predicate(response);\n } catch {\n return null;\n }\n}\n\n/**\n * Probe many URLs in parallel and return the non-null matches, preserving\n * the input order. Each URL is bounded by `timeoutMs` independently — one\n * slow target can't hold up the others.\n */\nexport async function probeUrls<T>(\n urls: Iterable<string>,\n predicate: ProbePredicate<T>,\n opts?: { readonly timeoutMs?: number },\n): Promise<ReadonlyArray<ProbeMatch<T>>> {\n const list = [...urls];\n const results = await Promise.all(\n list.map(async (url): Promise<ProbeMatch<T> | null> => {\n const value = await probeUrl(url, predicate, opts);\n return value === null ? null : { url, value };\n }),\n );\n return results.filter((match): match is ProbeMatch<T> => match !== null);\n}\n","import { select, spinner, text } from \"@clack/prompts\";\n\nimport { DEFAULT_SERVER } from \"../../../lib/server\";\nimport { listListeningPorts, probeUrls } from \"../../../lib/prober\";\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/** Sentinel returned by the choice select when the user picks \"Custom URL\". */\nconst CUSTOM = \"__custom__\";\n\n/** Per-probe timeout for the localhost OIDC scan — generous enough for a TLS handshake on a busy laptop. */\nconst OIDC_PROBE_TIMEOUT_MS = 300;\n\n/**\n * \"Which server should `zitadel.json` point to?\" — Zitadel Cloud, a localhost\n * OIDC server we discovered, or a custom URL.\n *\n * Before asking, it scans the loopback for listening ports (via the generic\n * `lib/prober`) and `GET`s `/.well-known/openid-configuration` on each. Every\n * 2xx response with a string `issuer` becomes an extra option in the choice\n * list. Picking a discovered URL writes it directly to `answers.server` and\n * skips the follow-up text prompt. Picking \"Custom URL\" still asks for a\n * validated URL exactly as before.\n */\nexport class ServerPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, ctx: PromptContext): Promise<SetupAnswers> {\n // `--server <url>` overrides the wizard entirely. The base command\n // already validated and resolved the URL into `answers.server`;\n // asking the user to re-pick from a fixed select list when their\n // flag isn't in the list (Cloud / discovered / Custom) was a bug —\n // clack would silently default back to Cloud.\n if (ctx.serverFlag) {\n return answers;\n }\n const discovered = await discoverLocalOidc();\n\n const choice = await select({\n message: \"Which server should zitadel.json point to?\",\n options: [\n {\n value: DEFAULT_SERVER,\n label: \"Zitadel Cloud (api.zitadel.cloud)\",\n hint: \"recommended for real projects\",\n },\n ...discovered.map((server) => ({\n value: server,\n label: server,\n hint: \"detected — OIDC\",\n })),\n { value: CUSTOM, label: \"Custom URL (self-hosted)\" },\n ],\n initialValue: answers.server ?? DEFAULT_SERVER,\n });\n bail(choice);\n if (choice !== CUSTOM) {\n return { ...answers, server: choice as string };\n }\n const custom = await text({\n message: \"Server URL\",\n placeholder: \"https://zitadel.internal\",\n validate: (value) => {\n try {\n new URL(value ?? \"\");\n return;\n } catch {\n return \"Must be a valid URL\";\n }\n },\n });\n bail(custom);\n return { ...answers, server: custom as string };\n }\n}\n\n/**\n * Returns the loopback origins (`http://localhost:<port>`) whose\n * `/.well-known/openid-configuration` endpoint responds with a valid OIDC\n * discovery document. Sniffs purely via the generic prober — the only\n * Zitadel-shaped detail here is the OIDC predicate, which is the standard\n * \"any OIDC server\" contract (no `zitadel`-keyword check), so Keycloak/dex/\n * etc. also surface and the user picks the right one.\n */\nasync function discoverLocalOidc(): Promise<ReadonlyArray<string>> {\n const s = spinner();\n s.start(\"Scanning localhost for OIDC servers\");\n try {\n const ports = await listListeningPorts();\n if (ports.length === 0) {\n s.stop(\"No local servers detected.\");\n return [];\n }\n const matches = await probeUrls(\n ports.map((port) => `http://localhost:${port}/.well-known/openid-configuration`),\n async (response) => {\n if (!response.ok) {\n return null;\n }\n try {\n const body = (await response.json()) as { issuer?: unknown };\n return typeof body.issuer === \"string\" ? body.issuer : null;\n } catch {\n return null;\n }\n },\n { timeoutMs: OIDC_PROBE_TIMEOUT_MS },\n );\n const origins = matches.map((match) => new URL(match.url).origin);\n s.stop(\n origins.length === 0\n ? \"No local OIDC servers found.\"\n : `Found ${origins.length} local OIDC server${origins.length === 1 ? \"\" : \"s\"}.`,\n );\n return origins;\n } catch (error) {\n s.stop(\"Discovery skipped.\");\n throw error;\n }\n}\n","import { intro, select } from \"@clack/prompts\";\n\nimport { bail } from \"./cancel\";\n\n/**\n * \"Choose a framework to scaffold\" — the only prompt outside the main wizard.\n * Runs at the empty-directory branch (before any other detection), so it owns\n * its own `intro` heading. Returns the chosen framework id; choices come from\n * `Orca.availableFrameworks`.\n */\nexport class PickFrameworkPrompt {\n async ask(\n choices: ReadonlyArray<{ id: string; displayName: string }>,\n ): Promise<string> {\n intro(\"Zitadel setup — new project\");\n const picked = await select({\n message: \"Choose a framework to scaffold\",\n options: choices.map((choice) => ({ value: choice.id, label: choice.displayName })),\n });\n bail(picked);\n return picked as string;\n }\n}\n","/**\n * Public surface for the setup wizard prompts. The `setup` command imports\n * {@link SETUP_PROMPTS} and iterates every entry, threading the answers\n * through each prompt's `ask` so they decide whether to actually ask the\n * user. Add a new question by writing a class and appending an instance to\n * the registry below.\n *\n * {@link PickFrameworkPrompt} is intentionally **not** in {@link SETUP_PROMPTS}\n * — it runs at the empty-directory scaffold branch, before the main wizard\n * starts.\n */\nimport { DevPortPrompt } from \"./dev-port\";\nimport { FrameworkConfirmPrompt } from \"./framework-confirm\";\nimport { ServerPrompt } from \"./server\";\nimport type { SetupPrompt } from \"./types\";\n\nexport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\nexport { bail } from \"./cancel\";\nexport { DevPortPrompt } from \"./dev-port\";\nexport { FrameworkConfirmPrompt } from \"./framework-confirm\";\nexport { ServerPrompt } from \"./server\";\nexport { PickFrameworkPrompt } from \"./pick-framework\";\n\n/** Every question the main setup wizard asks, in ask order. */\nexport const SETUP_PROMPTS: ReadonlyArray<SetupPrompt> = [\n new FrameworkConfirmPrompt(),\n new ServerPrompt(),\n new DevPortPrompt(),\n];\n","import { readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\nimport pc from \"picocolors\";\n\n/**\n * Structured end-of-command summary, modelled on the report layout the\n * setup mock specifies: one line per fact, grouped under uppercase section\n * headers, with consistent `✓ label value` formatting. Each {@link Row}\n * carries the label, the primary value (already styled by the caller),\n * and an optional `secondary` shown after a dim `→` to mirror the\n * \"package → file\" lines in the mock.\n */\nexport type Row = {\n label: string;\n value: string;\n secondary?: string;\n};\n\nexport type Section = {\n title: string;\n rows: Row[];\n};\n\n/**\n * Renders the section list as a single multi-line string. Labels are\n * padded to a common width per section so the `✓ label value` columns\n * line up, the title prints in dim gray, and `✓` is green. Values come\n * in pre-styled — use the helpers below ({@link path}, {@link url},\n * {@link id}, {@link dim}) to keep the colour palette consistent with\n * the mock.\n */\nexport function renderSummary(sections: Section[]): string {\n const lines: string[] = [];\n for (const section of sections) {\n if (section.rows.length === 0) continue;\n if (lines.length > 0) lines.push(\"\");\n lines.push(pc.dim(section.title.toUpperCase()));\n const labelWidth = Math.max(...section.rows.map((r) => r.label.length)) + 3;\n for (const row of section.rows) {\n const labelCol = row.label.padEnd(labelWidth);\n const secondary = row.secondary ? ` ${pc.dim(\"→\")} ${row.secondary}` : \"\";\n lines.push(`${pc.green(\"✓\")} ${labelCol}${row.value}${secondary}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** Cyan, for filesystem paths the user can open. */\nexport const path = (s: string): string => pc.cyan(s);\n/** Cyan, for URLs (browser-clickable in most terminals). */\nexport const url = (s: string): string => pc.cyan(s);\n/** Yellow, for opaque ids the user shouldn't try to read. */\nexport const id = (s: string): string => pc.yellow(s);\n/** Dim, for separators and secondary detail. */\nexport const dim = (s: string): string => pc.dim(s);\n\n/**\n * Detected facts about the user's project, surfaced in the `DETECTED`\n * section. {@link detectProjectFacts} reads the on-disk markers (lock\n * file, tsconfig, framework dep version) without any platform call.\n */\nexport type ProjectFacts = {\n framework: string;\n frameworkVersion?: string;\n typescript: boolean;\n packageManager: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" | \"unknown\";\n};\n\n/**\n * Reads the project root to identify the framework version, TS presence,\n * and which package manager the user runs. Returns the worst-case\n * (`\"unknown\"` PM, no version) on any error so summary rendering can\n * always proceed.\n */\nexport async function detectProjectFacts(\n cwd: string,\n frameworkId: string,\n): Promise<ProjectFacts> {\n const facts: ProjectFacts = {\n framework: frameworkId,\n typescript: await fileExists(join(cwd, \"tsconfig.json\")),\n packageManager: await detectPackageManager(cwd),\n };\n try {\n const raw = await readFile(join(cwd, \"package.json\"), \"utf8\");\n const pj = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const depPkg = depFromFramework(frameworkId);\n const range = pj.dependencies?.[depPkg] ?? pj.devDependencies?.[depPkg];\n if (range) {\n facts.frameworkVersion = stripRange(range);\n }\n } catch {\n // Missing or unreadable package.json — version stays undefined; the\n // renderer skips that segment, which is the same as the user not\n // having declared the framework dep yet.\n }\n return facts;\n}\n\n/** Formats the project facts as the \"Next.js 15 · TypeScript · npm\" string the mock shows. */\nexport function formatFrameworkLine(facts: ProjectFacts): string {\n const segments: string[] = [];\n const pretty = prettyFramework(facts.framework);\n segments.push(facts.frameworkVersion ? `${pretty} ${facts.frameworkVersion}` : pretty);\n if (facts.typescript) segments.push(\"TypeScript\");\n if (facts.packageManager !== \"unknown\") segments.push(facts.packageManager);\n return segments.join(` ${pc.dim(\"·\")} `);\n}\n\nasync function detectPackageManager(cwd: string): Promise<ProjectFacts[\"packageManager\"]> {\n if (await fileExists(join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (await fileExists(join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (await fileExists(join(cwd, \"bun.lockb\"))) return \"bun\";\n if (await fileExists(join(cwd, \"package-lock.json\"))) return \"npm\";\n return \"unknown\";\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction depFromFramework(framework: string): string {\n switch (framework) {\n case \"next\":\n return \"next\";\n case \"nuxt\":\n return \"nuxt\";\n default:\n return framework;\n }\n}\n\nfunction prettyFramework(framework: string): string {\n switch (framework) {\n case \"next\":\n return \"Next.js\";\n case \"nuxt\":\n return \"Nuxt\";\n default:\n return framework;\n }\n}\n\n/** Strips `^`/`~`/`>=`/`v` prefixes from an npm range so the version reads cleanly in the summary. */\nfunction stripRange(range: string): string {\n const trimmed = range.replace(/^([\\^~]|>=?|v)/, \"\").trim();\n // Take the first dotted segment so multi-range strings (`^15.0.0 || ^14.0.0`)\n // collapse to a single readable version in the report.\n const first = trimmed.split(/\\s+/)[0];\n return first ?? trimmed;\n}\n\n/** Returns just the file name from a relative path, for the `→ filename` secondary detail in PACKAGE rows. */\nexport function fileNameOf(p: string): string {\n return basename(p);\n}\n","import { Flags } from \"@oclif/core\";\nimport { intro, outro } from \"@clack/prompts\";\nimport { consola } from \"consola\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { createOrca, issuerFromPort, type FrameworkFacts, type Orca } from \"../../lib/orca\";\nimport type { PatchContext } from \"../../lib/orca/patchers/types\";\nimport { RENDERER_IDS } from \"../../lib/orca/patchers/rule/next/renderers/registry\";\nimport type { CreateProject201 } from \"@zitadel/api/generated/model\";\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { hasZitadelConfig, hasZitadelSecret } from \"../../lib/project\";\nimport { PickFrameworkPrompt, SETUP_PROMPTS, type SetupAnswers } from \"./prompts\";\nimport {\n detectProjectFacts,\n fileNameOf,\n formatFrameworkLine,\n id as styleId,\n path as stylePath,\n renderSummary,\n url as styleUrl,\n type Row,\n type Section,\n} from \"./summary\";\n\n/**\n * The frameworks `--framework` accepts, derived from Orca's registry so the\n * flag can't drift from what the CLI can actually scaffold. `createOrca` is\n * pure (it only builds the in-memory registries), so this is safe at module\n * load.\n */\nconst FRAMEWORK_OPTIONS = createOrca()\n .availableFrameworks()\n .map((framework) => framework.id);\n\n/** `zitadel setup` — create a project and scaffold local auth.\n *\n * Detects (or, for an empty directory, scaffolds then re-detects) the\n * framework, runs the wizard prompts to fill in any answers not pre-supplied\n * by flags, creates the remote project (whose default user schema and login\n * flow are provisioned server-side), and patches the local files via\n * `Orca`'s framework patcher.\n *\n * Every interactive question lives in {@link SETUP_PROMPTS} (the main wizard\n * — each entry is a small class) and {@link PickFrameworkPrompt} (the\n * empty-directory framework choice, before the main wizard).\n */\nexport default class Setup extends BaseCommand {\n static override description = \"Create a Zitadel project and scaffold local auth.\";\n static override examples = [\"<%= config.bin %> setup --framework next\"];\n static override flags = {\n framework: Flags.string({ description: \"Framework to target.\", options: FRAMEWORK_OPTIONS }),\n renderer: Flags.string({ description: \"Renderer (default: react).\", options: [...RENDERER_IDS] }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Setup);\n await this.toMeta(flags);\n const { cwd, nonInteractive, dryRun, force } = this.meta;\n\n if (await hasZitadelConfig(cwd)) {\n return this.emit({ status: \"skipped\", reason: \"already-initialized\" });\n }\n if (await hasZitadelSecret(cwd)) {\n throw new ZitadelError(\"E_CONFLICT\", \".zitadel/secret exists without zitadel.json\", {\n hint: \"Move the secret aside or restore zitadel.json before running setup.\",\n });\n }\n\n const orca = createOrca();\n\n consola.start(`Detecting framework in ${shortPath(cwd)}`);\n let framework: FrameworkFacts;\n let scaffoldedFramework = false;\n try {\n framework = await orca.detect(cwd, flags.framework);\n consola.success(`Detected ${framework.id}${framework.devPort ? ` (dev port ${framework.devPort})` : \"\"}`);\n } catch (error) {\n if (\n error instanceof ZitadelError &&\n error.code === \"E_FRAMEWORK_NOT_DETECTED\" &&\n (await orca.isEmpty(cwd))\n ) {\n consola.info(\"Empty directory — scaffolding a fresh project\");\n framework = await orca.scaffold(cwd, await resolveScaffoldFramework(flags.framework, nonInteractive, orca));\n scaffoldedFramework = true;\n consola.success(`Scaffolded ${framework.id} skeleton`);\n } else {\n throw error;\n }\n }\n\n let answers: SetupAnswers = {\n server: this.meta.source,\n devPort: framework.devPort,\n };\n\n if (!nonInteractive && !dryRun) {\n intro(\"Zitadel setup\");\n const promptCtx = { framework, serverFlag: this.meta.serverFlag };\n for (const prompt of SETUP_PROMPTS) {\n answers = await prompt.ask(answers, promptCtx);\n }\n outro(\"Configuration captured\");\n }\n\n const issuer = issuerFromPort(answers.devPort);\n\n // `POST /projects` is unauthenticated. Creating the project also\n // provisions its default user schema and login flow server-side, so the\n // CLI no longer builds, scaffolds, or uploads those resources here.\n consola.start(`Creating project on ${answers.server}${dryRun ? \" (dry run)\" : \"\"}`);\n const unauthClient = createZitadelClient({ baseUrl: answers.server });\n const project = dryRun\n ? dryRunProject()\n : await unauthClient.createProject({ previewOrigins: [] });\n consola.success(`Created project ${project.id}`);\n\n const ctx: PatchContext = {\n framework,\n rendererId: flags.renderer ?? \"react\",\n project,\n issuer,\n server: answers.server,\n };\n consola.start(`Patching project files${dryRun ? \" (dry run)\" : \"\"}`);\n const result = await orca.patcherFor(framework.id).patch(ctx, { cwd, dryRun, force });\n for (const file of result.filesWritten) {\n const sentence = describeWrittenFile(relativeDisplay(cwd, file), dryRun);\n if (sentence) consola.info(sentence);\n }\n for (const file of result.filesSkipped) {\n consola.info(`Left ${relativeDisplay(cwd, file)} unchanged (already matches target)`);\n }\n consola.success(\n `Patched ${result.filesWritten.length} file${result.filesWritten.length === 1 ? \"\" : \"s\"}` +\n (result.filesSkipped.length > 0 ? ` (${result.filesSkipped.length} unchanged)` : \"\"),\n );\n\n const writtenRel = result.filesWritten.map((file) => relativeDisplay(cwd, file));\n // The structured report is human-only. Under `--json` we let the\n // envelope returned from `this.emit(...)` be the sole stdout\n // payload (oclif requires single-doc JSON).\n if (!this.jsonEnabled()) {\n const projectFacts = await detectProjectFacts(cwd, framework.id);\n const sections = buildSummary({\n projectFacts,\n writtenRel,\n project,\n server: answers.server,\n issuer,\n scaffoldedFramework,\n });\n // Frame the report in a consola box so it reads as a distinct\n // status panel separate from the per-step narration above it.\n // `box` accepts ANSI-styled text in the message body, so our\n // pre-coloured rows (path/url/id helpers) survive intact.\n consola.box({\n title: \"Zitadel is ready\",\n message: [\n renderSummary(sections),\n \"\",\n `Open your app on ${styleUrl(`${issuer}/login`)} and register your first user.`,\n ].join(\"\\n\"),\n style: { padding: 1, borderStyle: \"rounded\", borderColor: \"green\" },\n });\n }\n\n return this.emit({\n status: \"ok\",\n // Human-facing output was already shown via consola (box + per-step\n // narration). Pass an empty `pretty` so the base command's fallback\n // renderer doesn't duplicate the summary on stdout. The JSON envelope\n // still carries the full structured payload.\n pretty: \"\",\n data: {\n title: \"Zitadel is ready.\",\n project: { project_id: project.id, issuer },\n framework: framework.id,\n server: answers.server,\n files_written: result.filesWritten.map((file) => relativeDisplay(cwd, file)),\n files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),\n next_actions: [`Start your project: npm install && npm run dev (then open ${issuer}/login)`],\n next_commands: [\"npm install\", \"npm run dev\"],\n },\n });\n }\n}\n\n/**\n * Resolves which framework to scaffold into an empty directory: the explicit\n * `--framework`, else PickFrameworkPrompt, else a hard error in non-interactive\n * mode (an agent must pass `--framework`).\n */\nasync function resolveScaffoldFramework(\n framework: string | undefined,\n nonInteractive: boolean,\n orca: Orca,\n): Promise<string> {\n if (framework) {\n return framework;\n }\n if (nonInteractive) {\n throw new ZitadelError(\"E_FRAMEWORK_NOT_DETECTED\", \"Empty directory — pass --framework\", {\n hint: \"Example: --framework next\",\n });\n }\n return new PickFrameworkPrompt().ask(orca.availableFrameworks());\n}\n\n/** A deterministic stand-in project for `--dry-run`, so no remote call is made. */\nfunction dryRunProject(): CreateProject201 {\n return {\n id: \"dry-run-0000\",\n projectSecret: \"sk_proj_dry_run_full\",\n previewSecret: \"sk_proj_dry_run_preview\",\n previewOrigins: [],\n createdAt: \"2026-04-21T14:03:11.000Z\",\n };\n}\n\n/** Renders an absolute path relative to `cwd` for human-readable output. */\nfunction relativeDisplay(cwd: string, path: string): string {\n return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;\n}\n\n/**\n * Replaces the user's `$HOME` with `~` in a path for compact terminal output.\n * Falls back to the raw path when `HOME` isn't set or doesn't match.\n */\nfunction shortPath(absolute: string): string {\n const home = process.env.HOME;\n if (home && absolute.startsWith(home)) {\n return `~${absolute.slice(home.length)}`;\n }\n return absolute;\n}\n\n/**\n * Picks one written file by suffix so the corresponding INSTALLED row can\n * reference it without hard-coding the path the patcher chose. Returns\n * the first match; falls back to `undefined` when the patcher didn't\n * write that artifact (e.g. a renderer without a register page).\n */\nfunction pickWrittenFile(written: string[], suffix: string): string | undefined {\n return written.find((file) => file.endsWith(suffix));\n}\n\n/**\n * Translates a patcher-written path into a single sentence the user can\n * read at narration speed. Returns `null` for directories and other\n * scaffolding artefacts that aren't worth narrating individually — the\n * file count in the closing `success(...)` and the summary's INSTALLED\n * section already cover them. The verb tense flips for `--dry-run` so\n * the user sees a preview (\"Would write ...\") instead of a claim that\n * something happened.\n */\nfunction describeWrittenFile(relPath: string, dryRun: boolean): string | null {\n // Mkdir ops surface in `filesWritten` alongside actual file writes.\n // They're noise at the per-step layer (the files inside them get\n // narrated on their own lines), so swallow them here.\n if (\n relPath === \".zitadel\" ||\n relPath === \".zitadel/flows\" ||\n relPath === \".zitadel/schemas\"\n ) {\n return null;\n }\n const verb = dryRun ? \"Would write\" : \"Wrote\";\n const sentence = SENTENCE_BY_PATH[relPath];\n if (sentence) {\n return `${verb} ${sentence.subject} (${stylePath(relPath)})`;\n }\n return `${verb} ${stylePath(relPath)}`;\n}\n\n/**\n * Map from the patcher's deterministic output paths to a short noun\n * phrase describing what the file is for. Anything not in the map falls\n * back to the bare path in the narration; add an entry here when a new\n * scaffolded file deserves a clearer label.\n */\nconst SENTENCE_BY_PATH: Record<string, { subject: string }> = {\n \".gitignore\": { subject: \"the project's .gitignore additions\" },\n \".zitadel/secret\": { subject: \"the local project secret\" },\n \"zitadel.json\": { subject: \"the Zitadel project configuration\" },\n \".env.example\": { subject: \"the .env example template\" },\n \".env.local\": { subject: \"the local development environment variables\" },\n \".zitadel/state.json\": { subject: \"the empty sync state file\" },\n \"app/login/page.tsx\": { subject: \"the login page\" },\n \"app/register/page.tsx\": { subject: \"the registration page\" },\n \"app/profile/page.tsx\": { subject: \"the profile page\" },\n \"middleware.ts\": { subject: \"the Next.js middleware\" },\n \"custom-elements.d.ts\": { subject: \"the web-component type declarations\" },\n \"package.json\": { subject: \"package.json with the SDK dependency\" },\n};\n\n/** Builds the section list driving {@link renderSummary} for the setup command. */\nfunction buildSummary(opts: {\n projectFacts: Awaited<ReturnType<typeof detectProjectFacts>>;\n writtenRel: string[];\n project: CreateProject201;\n server: string;\n issuer: string;\n scaffoldedFramework: boolean;\n}): Section[] {\n const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework } = opts;\n const sdkPackage = \"@zitadel/sdk-next\";\n const packageJsonHit = pickWrittenFile(writtenRel, \"package.json\");\n\n const detected: Row[] = [\n { label: \"Framework\", value: formatFrameworkLine(projectFacts) },\n ];\n if (scaffoldedFramework) {\n detected.push({ label: \"Scaffold\", value: \"fresh project (no existing files)\" });\n }\n\n const installedRows: Row[] = [];\n if (packageJsonHit) {\n installedRows.push({\n label: \"Package\",\n value: sdkPackage,\n secondary: stylePath(fileNameOf(packageJsonHit)),\n });\n }\n for (const [label, suffix] of [\n [\"Login page\", \"app/login/page.tsx\"],\n [\"Register page\", \"app/register/page.tsx\"],\n [\"Profile page\", \"app/profile/page.tsx\"],\n [\"Middleware\", \"middleware.ts\"],\n [\"Env vars\", \".env.local\"],\n ] as const) {\n const hit = pickWrittenFile(writtenRel, suffix);\n if (hit) installedRows.push({ label, value: stylePath(hit) });\n }\n\n const projectRows: Row[] = [\n { label: \"Project id\", value: styleId(project.id) },\n { label: \"Server\", value: styleUrl(server) },\n { label: \"App will run\", value: styleUrl(issuer) },\n ];\n\n return [\n { title: \"Detected\", rows: detected },\n { title: \"Installed\", rows: installedRows },\n { title: \"Project\", rows: projectRows },\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":["styleUrl","stylePath","styleId"],"sources":["../../src/commands/setup/prompts/cancel.ts","../../src/commands/setup/prompts/dev-port.ts","../../src/commands/setup/prompts/framework-confirm.ts","../../src/lib/prober/ports.ts","../../src/lib/prober/http.ts","../../src/commands/setup/prompts/server.ts","../../src/commands/setup/prompts/pick-framework.ts","../../src/commands/setup/prompts/index.ts","../../src/commands/setup/summary.ts","../../src/commands/setup/index.ts"],"sourcesContent":["import { cancel, isCancel } from \"@clack/prompts\";\n\nimport { ZitadelError } from \"../../../lib/errors\";\n\n/**\n * Converts a clack cancellation (Ctrl-C) into a thrown `E_VALIDATION` rather\n * than a partial answer. Every prompt funnels its clack return value through\n * this so the wizard never proceeds with a sentinel or missing value.\n */\nexport function bail<T>(value: T | symbol): asserts value is T {\n if (isCancel(value)) {\n cancel(\"Setup cancelled.\");\n throw new ZitadelError(\"E_VALIDATION\", \"Setup cancelled by user\");\n }\n}\n","import { text } from \"@clack/prompts\";\n\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/**\n * \"Dev server port\" — defaults to the detected port. The validated answer\n * becomes the issuer URL (`http://localhost:<port>`) via `issuerFromPort`.\n */\nexport class DevPortPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, _ctx: PromptContext): Promise<SetupAnswers> {\n const value = await text({\n message: \"Dev server port\",\n placeholder: String(answers.devPort),\n initialValue: String(answers.devPort),\n validate: (input) => {\n const num = Number.parseInt(input ?? \"\", 10);\n return Number.isFinite(num) && num > 0 && num < 65536\n ? undefined\n : \"Must be a port number\";\n },\n });\n bail(value);\n return { ...answers, devPort: Number.parseInt(String(value), 10) };\n }\n}\n","import { confirm } from \"@clack/prompts\";\n\nimport { ZitadelError } from \"../../../lib/errors\";\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/**\n * \"Detected `<framework>`. Proceed?\" — the wizard's first question. Accepting\n * leaves answers unchanged; declining throws `E_UNSUPPORTED_PROJECT_SHAPE`\n * (the user should re-run with an explicit `--framework`).\n */\nexport class FrameworkConfirmPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, ctx: PromptContext): Promise<SetupAnswers> {\n const ack = await confirm({\n message: `Detected ${ctx.framework.id}. Proceed?`,\n initialValue: true,\n });\n bail(ack);\n if (ack === false) {\n throw new ZitadelError(\n \"E_UNSUPPORTED_PROJECT_SHAPE\",\n \"Setup cancelled — framework declined\",\n { hint: `Re-run with --framework ${ctx.framework.id} when ready.` },\n );\n }\n return answers;\n }\n}\n","import { execFile } from \"node:child_process\";\n\n/**\n * Enumerate TCP ports currently in LISTEN state on the loopback interface\n * (`127.0.0.1`, `::1`, or the wildcard `*`). Spawns `lsof -iTCP -sTCP:LISTEN\n * -P -n -F n` and parses its machine-readable output. Returns the unique,\n * numerically-sorted list of ports.\n *\n * Never throws. Returns `[]` whenever lsof is unavailable (e.g. Windows,\n * unusual PATH), the spawn errors, exits non-zero, or the call exceeds\n * `timeoutMs` (default 1000ms). The caller treats an empty list the same as\n * \"no listeners worth probing.\"\n */\nexport async function listListeningPorts(opts?: {\n readonly timeoutMs?: number;\n}): Promise<ReadonlyArray<number>> {\n const timeoutMs = opts?.timeoutMs ?? 1000;\n try {\n const stdout = await runLsof(timeoutMs);\n return parseLsofPorts(stdout);\n } catch {\n return [];\n }\n}\n\n/**\n * Spawn `lsof` with the canned argv and resolve to its stdout. Hand-rolled\n * rather than `util.promisify(execFile)` because the latter resolves with a\n * `{stdout, stderr}` object via its custom-promisify symbol — a heavier shape\n * to mock and worse to read at the call site, where we only ever want stdout.\n */\nfunction runLsof(timeoutMs: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n execFile(\n \"lsof\",\n [\"-iTCP\", \"-sTCP:LISTEN\", \"-P\", \"-n\", \"-F\", \"n\"],\n { timeout: timeoutMs, encoding: \"utf8\" },\n (err, stdout) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(stdout);\n },\n );\n });\n}\n\n/**\n * Parse the `n` records emitted by `lsof -F n`. Each record is a single line\n * `n<address>` where `<address>` ends in `:<port>` (e.g. `n*:8080`,\n * `n127.0.0.1:3000`, `n[::1]:5050`). Only loopback/wildcard hosts are kept;\n * external interface bindings are ignored.\n */\nfunction parseLsofPorts(stdout: string): ReadonlyArray<number> {\n const ports = new Set<number>();\n for (const line of stdout.split(/\\r?\\n/)) {\n if (!line.startsWith(\"n\")) {\n continue;\n }\n const address = line.slice(1);\n const colon = address.lastIndexOf(\":\");\n if (colon < 0) {\n continue;\n }\n const host = address.slice(0, colon);\n const portStr = address.slice(colon + 1);\n if (!isLoopback(host)) {\n continue;\n }\n const port = Number.parseInt(portStr, 10);\n if (Number.isFinite(port) && port > 0 && port < 65536) {\n ports.add(port);\n }\n }\n return [...ports].sort((a, b) => a - b);\n}\n\n/** Recognised loopback host strings as emitted by `lsof -F n`. */\nfunction isLoopback(host: string): boolean {\n return host === \"*\" || host === \"127.0.0.1\" || host === \"[::1]\" || host === \"::1\";\n}\n","import type { ProbeMatch, ProbePredicate } from \"./types\";\n\n/**\n * Fetch `url` with a per-call timeout and pass the resulting `Response` to\n * `predicate`. Returns the predicate's value, or `null` on any failure: a\n * network error, an `AbortError` from the timeout, a thrown predicate, or the\n * predicate returning `null`.\n *\n * Never throws. The predicate decides what counts as a match — it gets the\n * raw `Response` and may read `.json()` / `.headers` / `.status` as needed.\n *\n * `timeoutMs` defaults to 500ms. The implementation uses\n * `AbortSignal.timeout`, so the underlying fetch is cancelled when the\n * timeout fires (no orphaned sockets).\n */\nexport async function probeUrl<T>(\n url: string,\n predicate: ProbePredicate<T>,\n opts?: { readonly timeoutMs?: number },\n): Promise<T | null> {\n const timeoutMs = opts?.timeoutMs ?? 500;\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n return await predicate(response);\n } catch {\n return null;\n }\n}\n\n/**\n * Probe many URLs in parallel and return the non-null matches, preserving\n * the input order. Each URL is bounded by `timeoutMs` independently — one\n * slow target can't hold up the others.\n */\nexport async function probeUrls<T>(\n urls: Iterable<string>,\n predicate: ProbePredicate<T>,\n opts?: { readonly timeoutMs?: number },\n): Promise<ReadonlyArray<ProbeMatch<T>>> {\n const list = [...urls];\n const results = await Promise.all(\n list.map(async (url): Promise<ProbeMatch<T> | null> => {\n const value = await probeUrl(url, predicate, opts);\n return value === null ? null : { url, value };\n }),\n );\n return results.filter((match): match is ProbeMatch<T> => match !== null);\n}\n","import { select, spinner, text } from \"@clack/prompts\";\n\nimport { DEFAULT_SERVER } from \"../../../lib/server\";\nimport { listListeningPorts, probeUrls } from \"../../../lib/prober\";\nimport { bail } from \"./cancel\";\nimport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\n\n/** Sentinel returned by the choice select when the user picks \"Custom URL\". */\nconst CUSTOM = \"__custom__\";\n\n/** Per-probe timeout for the localhost OIDC scan — generous enough for a TLS handshake on a busy laptop. */\nconst OIDC_PROBE_TIMEOUT_MS = 300;\n\n/**\n * \"Which server should `zitadel.json` point to?\" — Zitadel Cloud, a localhost\n * OIDC server we discovered, or a custom URL.\n *\n * Before asking, it scans the loopback for listening ports (via the generic\n * `lib/prober`) and `GET`s `/.well-known/openid-configuration` on each. Every\n * 2xx response with a string `issuer` becomes an extra option in the choice\n * list. Picking a discovered URL writes it directly to `answers.server` and\n * skips the follow-up text prompt. Picking \"Custom URL\" still asks for a\n * validated URL exactly as before.\n */\nexport class ServerPrompt implements SetupPrompt {\n async ask(answers: SetupAnswers, ctx: PromptContext): Promise<SetupAnswers> {\n // `--server <url>` overrides the wizard entirely. The base command\n // already validated and resolved the URL into `answers.server`;\n // asking the user to re-pick from a fixed select list when their\n // flag isn't in the list (Cloud / discovered / Custom) was a bug —\n // clack would silently default back to Cloud.\n if (ctx.serverFlag) {\n return answers;\n }\n const discovered = await discoverLocalOidc();\n\n const choice = await select({\n message: \"Which server should zitadel.json point to?\",\n options: [\n {\n value: DEFAULT_SERVER,\n label: \"Zitadel Cloud (api.zitadel.cloud)\",\n hint: \"recommended for real projects\",\n },\n ...discovered.map((server) => ({\n value: server,\n label: server,\n hint: \"detected — OIDC\",\n })),\n { value: CUSTOM, label: \"Custom URL (self-hosted)\" },\n ],\n initialValue: answers.server ?? DEFAULT_SERVER,\n });\n bail(choice);\n if (choice !== CUSTOM) {\n return { ...answers, server: choice as string };\n }\n const custom = await text({\n message: \"Server URL\",\n placeholder: \"https://zitadel.internal\",\n validate: (value) => {\n try {\n new URL(value ?? \"\");\n return;\n } catch {\n return \"Must be a valid URL\";\n }\n },\n });\n bail(custom);\n return { ...answers, server: custom as string };\n }\n}\n\n/**\n * Returns the loopback origins (`http://localhost:<port>`) whose\n * `/.well-known/openid-configuration` endpoint responds with a valid OIDC\n * discovery document. Sniffs purely via the generic prober — the only\n * Zitadel-shaped detail here is the OIDC predicate, which is the standard\n * \"any OIDC server\" contract (no `zitadel`-keyword check), so Keycloak/dex/\n * etc. also surface and the user picks the right one.\n */\nasync function discoverLocalOidc(): Promise<ReadonlyArray<string>> {\n const s = spinner();\n s.start(\"Scanning localhost for OIDC servers\");\n try {\n const ports = await listListeningPorts();\n if (ports.length === 0) {\n s.stop(\"No local servers detected.\");\n return [];\n }\n const matches = await probeUrls(\n ports.map((port) => `http://localhost:${port}/.well-known/openid-configuration`),\n async (response) => {\n if (!response.ok) {\n return null;\n }\n try {\n const body = (await response.json()) as { issuer?: unknown };\n return typeof body.issuer === \"string\" ? body.issuer : null;\n } catch {\n return null;\n }\n },\n { timeoutMs: OIDC_PROBE_TIMEOUT_MS },\n );\n const origins = matches.map((match) => new URL(match.url).origin);\n s.stop(\n origins.length === 0\n ? \"No local OIDC servers found.\"\n : `Found ${origins.length} local OIDC server${origins.length === 1 ? \"\" : \"s\"}.`,\n );\n return origins;\n } catch (error) {\n s.stop(\"Discovery skipped.\");\n throw error;\n }\n}\n","import { intro, select } from \"@clack/prompts\";\n\nimport { bail } from \"./cancel\";\n\n/**\n * \"Choose a framework to scaffold\" — the only prompt outside the main wizard.\n * Runs at the empty-directory branch (before any other detection), so it owns\n * its own `intro` heading. Returns the chosen framework id; choices come from\n * `Orca.availableFrameworks`.\n */\nexport class PickFrameworkPrompt {\n async ask(\n choices: ReadonlyArray<{ id: string; displayName: string }>,\n ): Promise<string> {\n intro(\"Zitadel setup — new project\");\n const picked = await select({\n message: \"Choose a framework to scaffold\",\n options: choices.map((choice) => ({ value: choice.id, label: choice.displayName })),\n });\n bail(picked);\n return picked as string;\n }\n}\n","/**\n * Public surface for the setup wizard prompts. The `setup` command imports\n * {@link SETUP_PROMPTS} and iterates every entry, threading the answers\n * through each prompt's `ask` so they decide whether to actually ask the\n * user. Add a new question by writing a class and appending an instance to\n * the registry below.\n *\n * {@link PickFrameworkPrompt} is intentionally **not** in {@link SETUP_PROMPTS}\n * — it runs at the empty-directory scaffold branch, before the main wizard\n * starts.\n */\nimport { DevPortPrompt } from \"./dev-port\";\nimport { FrameworkConfirmPrompt } from \"./framework-confirm\";\nimport { ServerPrompt } from \"./server\";\nimport type { SetupPrompt } from \"./types\";\n\nexport type { PromptContext, SetupAnswers, SetupPrompt } from \"./types\";\nexport { bail } from \"./cancel\";\nexport { DevPortPrompt } from \"./dev-port\";\nexport { FrameworkConfirmPrompt } from \"./framework-confirm\";\nexport { ServerPrompt } from \"./server\";\nexport { PickFrameworkPrompt } from \"./pick-framework\";\n\n/** Every question the main setup wizard asks, in ask order. */\nexport const SETUP_PROMPTS: ReadonlyArray<SetupPrompt> = [\n new FrameworkConfirmPrompt(),\n new ServerPrompt(),\n new DevPortPrompt(),\n];\n","import { readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\nimport pc from \"picocolors\";\n\n/**\n * Structured end-of-command summary, modelled on the report layout the\n * setup mock specifies: one line per fact, grouped under uppercase section\n * headers, with consistent `✓ label value` formatting. Each {@link Row}\n * carries the label, the primary value (already styled by the caller),\n * and an optional `secondary` shown after a dim `→` to mirror the\n * \"package → file\" lines in the mock.\n */\nexport type Row = {\n label: string;\n value: string;\n secondary?: string;\n};\n\nexport type Section = {\n title: string;\n rows: Row[];\n};\n\n/**\n * Renders the section list as a single multi-line string. Labels are\n * padded to a common width per section so the `✓ label value` columns\n * line up, the title prints in dim gray, and `✓` is green. Values come\n * in pre-styled — use the helpers below ({@link path}, {@link url},\n * {@link id}, {@link dim}) to keep the colour palette consistent with\n * the mock.\n */\nexport function renderSummary(sections: Section[]): string {\n const lines: string[] = [];\n for (const section of sections) {\n if (section.rows.length === 0) continue;\n if (lines.length > 0) lines.push(\"\");\n lines.push(pc.dim(section.title.toUpperCase()));\n const labelWidth = Math.max(...section.rows.map((r) => r.label.length)) + 3;\n for (const row of section.rows) {\n const labelCol = row.label.padEnd(labelWidth);\n const secondary = row.secondary ? ` ${pc.dim(\"→\")} ${row.secondary}` : \"\";\n lines.push(`${pc.green(\"✓\")} ${labelCol}${row.value}${secondary}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** Cyan, for filesystem paths the user can open. */\nexport const path = (s: string): string => pc.cyan(s);\n/** Cyan, for URLs (browser-clickable in most terminals). */\nexport const url = (s: string): string => pc.cyan(s);\n/** Yellow, for opaque ids the user shouldn't try to read. */\nexport const id = (s: string): string => pc.yellow(s);\n/** Dim, for separators and secondary detail. */\nexport const dim = (s: string): string => pc.dim(s);\n\n/**\n * Detected facts about the user's project, surfaced in the `DETECTED`\n * section. {@link detectProjectFacts} reads the on-disk markers (lock\n * file, tsconfig, framework dep version) without any platform call.\n */\nexport type ProjectFacts = {\n framework: string;\n frameworkVersion?: string;\n typescript: boolean;\n packageManager: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" | \"unknown\";\n};\n\n/**\n * Reads the project root to identify the framework version, TS presence,\n * and which package manager the user runs. Returns the worst-case\n * (`\"unknown\"` PM, no version) on any error so summary rendering can\n * always proceed.\n */\nexport async function detectProjectFacts(\n cwd: string,\n frameworkId: string,\n): Promise<ProjectFacts> {\n const facts: ProjectFacts = {\n framework: frameworkId,\n typescript: await fileExists(join(cwd, \"tsconfig.json\")),\n packageManager: await detectPackageManager(cwd),\n };\n try {\n const raw = await readFile(join(cwd, \"package.json\"), \"utf8\");\n const pj = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const depPkg = depFromFramework(frameworkId);\n const range = pj.dependencies?.[depPkg] ?? pj.devDependencies?.[depPkg];\n if (range) {\n facts.frameworkVersion = stripRange(range);\n }\n } catch {\n // Missing or unreadable package.json — version stays undefined; the\n // renderer skips that segment, which is the same as the user not\n // having declared the framework dep yet.\n }\n return facts;\n}\n\n/** Formats the project facts as the \"Next.js 15 · TypeScript · npm\" string the mock shows. */\nexport function formatFrameworkLine(facts: ProjectFacts): string {\n const segments: string[] = [];\n const pretty = prettyFramework(facts.framework);\n segments.push(facts.frameworkVersion ? `${pretty} ${facts.frameworkVersion}` : pretty);\n if (facts.typescript) segments.push(\"TypeScript\");\n if (facts.packageManager !== \"unknown\") segments.push(facts.packageManager);\n return segments.join(` ${pc.dim(\"·\")} `);\n}\n\nasync function detectPackageManager(cwd: string): Promise<ProjectFacts[\"packageManager\"]> {\n if (await fileExists(join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (await fileExists(join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (await fileExists(join(cwd, \"bun.lockb\"))) return \"bun\";\n if (await fileExists(join(cwd, \"package-lock.json\"))) return \"npm\";\n return \"unknown\";\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction depFromFramework(framework: string): string {\n switch (framework) {\n case \"next\":\n return \"next\";\n case \"nuxt\":\n return \"nuxt\";\n default:\n return framework;\n }\n}\n\nfunction prettyFramework(framework: string): string {\n switch (framework) {\n case \"next\":\n return \"Next.js\";\n case \"nuxt\":\n return \"Nuxt\";\n default:\n return framework;\n }\n}\n\n/** Strips `^`/`~`/`>=`/`v` prefixes from an npm range so the version reads cleanly in the summary. */\nfunction stripRange(range: string): string {\n const trimmed = range.replace(/^([\\^~]|>=?|v)/, \"\").trim();\n // Take the first dotted segment so multi-range strings (`^15.0.0 || ^14.0.0`)\n // collapse to a single readable version in the report.\n const first = trimmed.split(/\\s+/)[0];\n return first ?? trimmed;\n}\n\n/** Returns just the file name from a relative path, for the `→ filename` secondary detail in PACKAGE rows. */\nexport function fileNameOf(p: string): string {\n return basename(p);\n}\n","import { Flags } from \"@oclif/core\";\nimport { intro, outro } from \"@clack/prompts\";\nimport { consola } from \"consola\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { createOrca, issuerFromPort, type FrameworkFacts, type Orca } from \"../../lib/orca\";\nimport type { PatchContext } from \"../../lib/orca/patchers/types\";\nimport { RENDERER_IDS } from \"../../lib/orca/patchers/rule/next/renderers/registry\";\nimport type { CreateProject201 } from \"@zitadel/api/generated/model\";\nimport { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { hasZitadelConfig, hasZitadelSecret } from \"../../lib/project\";\nimport { PickFrameworkPrompt, SETUP_PROMPTS, type SetupAnswers } from \"./prompts\";\nimport {\n detectProjectFacts,\n fileNameOf,\n formatFrameworkLine,\n id as styleId,\n path as stylePath,\n renderSummary,\n url as styleUrl,\n type Row,\n type Section,\n} from \"./summary\";\n\n/**\n * The frameworks `--framework` accepts, derived from Orca's registry so the\n * flag can't drift from what the CLI can actually scaffold. `createOrca` is\n * pure (it only builds the in-memory registries), so this is safe at module\n * load.\n */\nconst FRAMEWORK_OPTIONS = createOrca()\n .availableFrameworks()\n .map((framework) => framework.id);\n\n/** `zitadel setup` — create a project and scaffold local auth.\n *\n * Detects (or, for an empty directory, scaffolds then re-detects) the\n * framework, runs the wizard prompts to fill in any answers not pre-supplied\n * by flags, creates the remote project (whose default user schema and login\n * flow are provisioned server-side), and patches the local files via\n * `Orca`'s framework patcher.\n *\n * Every interactive question lives in {@link SETUP_PROMPTS} (the main wizard\n * — each entry is a small class) and {@link PickFrameworkPrompt} (the\n * empty-directory framework choice, before the main wizard).\n */\nexport default class Setup extends BaseCommand {\n static override description = \"Create a Zitadel project and scaffold local auth.\";\n static override examples = [\"<%= config.bin %> setup --framework next\"];\n static override flags = {\n framework: Flags.string({ description: \"Framework to target.\", options: FRAMEWORK_OPTIONS }),\n renderer: Flags.string({ description: \"Renderer (default: react).\", options: [...RENDERER_IDS] }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Setup);\n await this.toMeta(flags);\n const { cwd, nonInteractive, dryRun, force } = this.meta;\n\n if (await hasZitadelConfig(cwd)) {\n return this.emit({ status: \"skipped\", reason: \"already-initialized\" });\n }\n if (await hasZitadelSecret(cwd)) {\n throw new ZitadelError(\"E_CONFLICT\", \".zitadel/secret exists without zitadel.json\", {\n hint: \"Move the secret aside or restore zitadel.json before running setup.\",\n });\n }\n\n const orca = createOrca();\n\n consola.start(`Detecting framework in ${shortPath(cwd)}`);\n let framework: FrameworkFacts;\n let scaffoldedFramework = false;\n try {\n framework = await orca.detect(cwd, flags.framework);\n consola.success(`Detected ${framework.id}${framework.devPort ? ` (dev port ${framework.devPort})` : \"\"}`);\n } catch (error) {\n if (\n error instanceof ZitadelError &&\n error.code === \"E_FRAMEWORK_NOT_DETECTED\" &&\n (await orca.isEmpty(cwd))\n ) {\n consola.info(\"Empty directory — scaffolding a fresh project\");\n framework = await orca.scaffold(cwd, await resolveScaffoldFramework(flags.framework, nonInteractive, orca));\n scaffoldedFramework = true;\n consola.success(`Scaffolded ${framework.id} skeleton`);\n } else {\n throw error;\n }\n }\n\n let answers: SetupAnswers = {\n server: this.meta.source,\n devPort: framework.devPort,\n };\n\n if (!nonInteractive && !dryRun) {\n intro(\"Zitadel setup\");\n const promptCtx = { framework, serverFlag: this.meta.serverFlag };\n for (const prompt of SETUP_PROMPTS) {\n answers = await prompt.ask(answers, promptCtx);\n }\n outro(\"Configuration captured\");\n }\n\n const issuer = issuerFromPort(answers.devPort);\n\n // `POST /projects` is unauthenticated. Creating the project also\n // provisions its default user schema and login flow server-side, so the\n // CLI no longer builds, scaffolds, or uploads those resources here.\n consola.start(`Creating project on ${answers.server}${dryRun ? \" (dry run)\" : \"\"}`);\n const unauthClient = createZitadelClient({ baseUrl: answers.server });\n const project = dryRun\n ? dryRunProject()\n : await unauthClient.createProject({ previewOrigins: [] });\n consola.success(`Created project ${project.id}`);\n\n const ctx: PatchContext = {\n framework,\n rendererId: flags.renderer ?? \"react\",\n project,\n issuer,\n server: answers.server,\n cliVersion: this.meta.cliVersion,\n };\n consola.start(`Patching project files${dryRun ? \" (dry run)\" : \"\"}`);\n const result = await orca.patcherFor(framework.id).patch(ctx, { cwd, dryRun, force });\n for (const file of result.filesWritten) {\n const sentence = describeWrittenFile(relativeDisplay(cwd, file), dryRun);\n if (sentence) consola.info(sentence);\n }\n for (const file of result.filesSkipped) {\n consola.info(`Left ${relativeDisplay(cwd, file)} unchanged (already matches target)`);\n }\n consola.success(\n `Patched ${result.filesWritten.length} file${result.filesWritten.length === 1 ? \"\" : \"s\"}` +\n (result.filesSkipped.length > 0 ? ` (${result.filesSkipped.length} unchanged)` : \"\"),\n );\n\n const writtenRel = result.filesWritten.map((file) => relativeDisplay(cwd, file));\n // The structured report is human-only. Under `--json` we let the\n // envelope returned from `this.emit(...)` be the sole stdout\n // payload (oclif requires single-doc JSON).\n if (!this.jsonEnabled()) {\n const projectFacts = await detectProjectFacts(cwd, framework.id);\n const sections = buildSummary({\n projectFacts,\n writtenRel,\n project,\n server: answers.server,\n issuer,\n scaffoldedFramework,\n });\n // Frame the report in a consola box so it reads as a distinct\n // status panel separate from the per-step narration above it.\n // `box` accepts ANSI-styled text in the message body, so our\n // pre-coloured rows (path/url/id helpers) survive intact.\n consola.box({\n title: \"Zitadel is ready\",\n message: [\n renderSummary(sections),\n \"\",\n `Open your app on ${styleUrl(`${issuer}/login`)} and register your first user.`,\n ].join(\"\\n\"),\n style: { padding: 1, borderStyle: \"rounded\", borderColor: \"green\" },\n });\n }\n\n return this.emit({\n status: \"ok\",\n // Human-facing output was already shown via consola (box + per-step\n // narration). Pass an empty `pretty` so the base command's fallback\n // renderer doesn't duplicate the summary on stdout. The JSON envelope\n // still carries the full structured payload.\n pretty: \"\",\n data: {\n title: \"Zitadel is ready.\",\n project: { project_id: project.id, issuer },\n framework: framework.id,\n server: answers.server,\n files_written: result.filesWritten.map((file) => relativeDisplay(cwd, file)),\n files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),\n next_actions: [`Start your project: npm install && npm run dev (then open ${issuer}/login)`],\n next_commands: [\"npm install\", \"npm run dev\"],\n },\n });\n }\n}\n\n/**\n * Resolves which framework to scaffold into an empty directory: the explicit\n * `--framework`, else PickFrameworkPrompt, else a hard error in non-interactive\n * mode (an agent must pass `--framework`).\n */\nasync function resolveScaffoldFramework(\n framework: string | undefined,\n nonInteractive: boolean,\n orca: Orca,\n): Promise<string> {\n if (framework) {\n return framework;\n }\n if (nonInteractive) {\n throw new ZitadelError(\"E_FRAMEWORK_NOT_DETECTED\", \"Empty directory — pass --framework\", {\n hint: \"Example: --framework next\",\n });\n }\n return new PickFrameworkPrompt().ask(orca.availableFrameworks());\n}\n\n/** A deterministic stand-in project for `--dry-run`, so no remote call is made. */\nfunction dryRunProject(): CreateProject201 {\n return {\n id: \"dry-run-0000\",\n projectSecret: \"sk_proj_dry_run_full\",\n previewSecret: \"sk_proj_dry_run_preview\",\n previewOrigins: [],\n createdAt: \"2026-04-21T14:03:11.000Z\",\n };\n}\n\n/** Renders an absolute path relative to `cwd` for human-readable output. */\nfunction relativeDisplay(cwd: string, path: string): string {\n return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;\n}\n\n/**\n * Replaces the user's `$HOME` with `~` in a path for compact terminal output.\n * Falls back to the raw path when `HOME` isn't set or doesn't match.\n */\nfunction shortPath(absolute: string): string {\n const home = process.env.HOME;\n if (home && absolute.startsWith(home)) {\n return `~${absolute.slice(home.length)}`;\n }\n return absolute;\n}\n\n/**\n * Picks one written file by suffix so the corresponding INSTALLED row can\n * reference it without hard-coding the path the patcher chose. Returns\n * the first match; falls back to `undefined` when the patcher didn't\n * write that artifact (e.g. a renderer without a register page).\n */\nfunction pickWrittenFile(written: string[], suffix: string): string | undefined {\n return written.find((file) => file.endsWith(suffix));\n}\n\n/**\n * Translates a patcher-written path into a single sentence the user can\n * read at narration speed. Returns `null` for directories and other\n * scaffolding artefacts that aren't worth narrating individually — the\n * file count in the closing `success(...)` and the summary's INSTALLED\n * section already cover them. The verb tense flips for `--dry-run` so\n * the user sees a preview (\"Would write ...\") instead of a claim that\n * something happened.\n */\nfunction describeWrittenFile(relPath: string, dryRun: boolean): string | null {\n // Mkdir ops surface in `filesWritten` alongside actual file writes.\n // They're noise at the per-step layer (the files inside them get\n // narrated on their own lines), so swallow them here.\n if (\n relPath === \".zitadel\" ||\n relPath === \".zitadel/flows\" ||\n relPath === \".zitadel/schemas\"\n ) {\n return null;\n }\n const verb = dryRun ? \"Would write\" : \"Wrote\";\n const sentence = SENTENCE_BY_PATH[relPath];\n if (sentence) {\n return `${verb} ${sentence.subject} (${stylePath(relPath)})`;\n }\n return `${verb} ${stylePath(relPath)}`;\n}\n\n/**\n * Map from the patcher's deterministic output paths to a short noun\n * phrase describing what the file is for. Anything not in the map falls\n * back to the bare path in the narration; add an entry here when a new\n * scaffolded file deserves a clearer label.\n */\nconst SENTENCE_BY_PATH: Record<string, { subject: string }> = {\n \".gitignore\": { subject: \"the project's .gitignore additions\" },\n \".zitadel/secret\": { subject: \"the local project secret\" },\n \"zitadel.json\": { subject: \"the Zitadel project configuration\" },\n \".env.example\": { subject: \"the .env example template\" },\n \".env.local\": { subject: \"the local development environment variables\" },\n \".zitadel/state.json\": { subject: \"the empty sync state file\" },\n \"app/login/page.tsx\": { subject: \"the login page\" },\n \"app/register/page.tsx\": { subject: \"the registration page\" },\n \"app/profile/page.tsx\": { subject: \"the profile page\" },\n \"middleware.ts\": { subject: \"the Next.js middleware\" },\n \"custom-elements.d.ts\": { subject: \"the web-component type declarations\" },\n \"package.json\": { subject: \"package.json with the SDK dependency\" },\n};\n\n/** Builds the section list driving {@link renderSummary} for the setup command. */\nfunction buildSummary(opts: {\n projectFacts: Awaited<ReturnType<typeof detectProjectFacts>>;\n writtenRel: string[];\n project: CreateProject201;\n server: string;\n issuer: string;\n scaffoldedFramework: boolean;\n}): Section[] {\n const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework } = opts;\n const sdkPackage = \"@zitadel/sdk-next\";\n const packageJsonHit = pickWrittenFile(writtenRel, \"package.json\");\n\n const detected: Row[] = [\n { label: \"Framework\", value: formatFrameworkLine(projectFacts) },\n ];\n if (scaffoldedFramework) {\n detected.push({ label: \"Scaffold\", value: \"fresh project (no existing files)\" });\n }\n\n const installedRows: Row[] = [];\n if (packageJsonHit) {\n installedRows.push({\n label: \"Package\",\n value: sdkPackage,\n secondary: stylePath(fileNameOf(packageJsonHit)),\n });\n }\n for (const [label, suffix] of [\n [\"Login page\", \"app/login/page.tsx\"],\n [\"Register page\", \"app/register/page.tsx\"],\n [\"Profile page\", \"app/profile/page.tsx\"],\n [\"Middleware\", \"middleware.ts\"],\n [\"Env vars\", \".env.local\"],\n ] as const) {\n const hit = pickWrittenFile(writtenRel, suffix);\n if (hit) installedRows.push({ label, value: stylePath(hit) });\n }\n\n const projectRows: Row[] = [\n { label: \"Project id\", value: styleId(project.id) },\n { label: \"Server\", value: styleUrl(server) },\n { label: \"App will run\", value: styleUrl(issuer) },\n ];\n\n return [\n { title: \"Detected\", rows: detected },\n { title: \"Installed\", rows: installedRows },\n { title: \"Project\", rows: projectRows },\n ];\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { T as ZitadelError, d as ensureContainerIdentity, f as ensureLocalState, g as localServerUrl, m as localContainerName, s as DEFAULT_LOCAL_SERVER_PORT, t as BaseCommand, u as checkLocalServerHealth, x as writeRuntimeMetadata } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { c as metadataFromStart, i as ensureImage, l as startContainer, n as currentUser, s as inspectContainer, u as stopAndRemoveContainer } from "../docker-C0aVpJqm.mjs";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
import { setTimeout } from "node:timers/promises";
|
|
5
|
+
//#region src/commands/start.ts
|
|
6
|
+
const START_TIMEOUT_MS = 9e4;
|
|
7
|
+
var Start = class Start extends BaseCommand {
|
|
8
|
+
static description = "Start a local Zitadel server.";
|
|
9
|
+
static flags = {
|
|
10
|
+
image: Flags.string({ description: "Container image to run." }),
|
|
11
|
+
port: Flags.integer({
|
|
12
|
+
description: "Local HTTP port.",
|
|
13
|
+
default: DEFAULT_LOCAL_SERVER_PORT
|
|
14
|
+
})
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { flags } = await this.parse(Start);
|
|
18
|
+
const port = flags.port ?? 8080;
|
|
19
|
+
const serverUrl = localServerUrl(port);
|
|
20
|
+
await this.toMeta(flags, {
|
|
21
|
+
resolveServer: false,
|
|
22
|
+
source: serverUrl
|
|
23
|
+
});
|
|
24
|
+
validatePort(port);
|
|
25
|
+
const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? "ghcr.io/zitadel/nextgen:latest";
|
|
26
|
+
const containerName = localContainerName(this.meta.cwd);
|
|
27
|
+
if (this.meta.dryRun) return this.emit({
|
|
28
|
+
status: "ok",
|
|
29
|
+
data: {
|
|
30
|
+
title: "Local Zitadel server start plan.",
|
|
31
|
+
runtime: {
|
|
32
|
+
container_name: containerName,
|
|
33
|
+
image,
|
|
34
|
+
port
|
|
35
|
+
},
|
|
36
|
+
urls: {
|
|
37
|
+
api: serverUrl,
|
|
38
|
+
console: `${serverUrl}/ui/console/`,
|
|
39
|
+
login: `${serverUrl}/ui/login/`
|
|
40
|
+
},
|
|
41
|
+
next_commands: ["zitadel start"]
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const paths = await ensureLocalState(this.meta.cwd);
|
|
45
|
+
const existing = await inspectContainer(containerName);
|
|
46
|
+
if (existing.exists && existing.running && await checkLocalServerHealth(serverUrl)) {
|
|
47
|
+
const metadata = metadataFromStart({
|
|
48
|
+
cwdDataDir: paths.dataDir,
|
|
49
|
+
cliVersion: this.meta.cliVersion,
|
|
50
|
+
containerName,
|
|
51
|
+
containerId: existing.id ?? containerName,
|
|
52
|
+
image,
|
|
53
|
+
port,
|
|
54
|
+
serverUrl
|
|
55
|
+
});
|
|
56
|
+
await writeRuntimeMetadata(this.meta.cwd, metadata);
|
|
57
|
+
return this.emit({
|
|
58
|
+
status: "ok",
|
|
59
|
+
data: readyData(metadata, true)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (existing.exists) await stopAndRemoveContainer(containerName);
|
|
63
|
+
await ensureImage(image);
|
|
64
|
+
const containerId = await startContainer({
|
|
65
|
+
containerName,
|
|
66
|
+
image,
|
|
67
|
+
port,
|
|
68
|
+
dataDir: paths.dataDir,
|
|
69
|
+
identity: await ensureContainerIdentity(this.meta.cwd, currentUser())
|
|
70
|
+
});
|
|
71
|
+
await waitForHealth(serverUrl, containerName);
|
|
72
|
+
const metadata = metadataFromStart({
|
|
73
|
+
cwdDataDir: paths.dataDir,
|
|
74
|
+
cliVersion: this.meta.cliVersion,
|
|
75
|
+
containerName,
|
|
76
|
+
containerId,
|
|
77
|
+
image,
|
|
78
|
+
port,
|
|
79
|
+
serverUrl
|
|
80
|
+
});
|
|
81
|
+
await writeRuntimeMetadata(this.meta.cwd, metadata);
|
|
82
|
+
return this.emit({
|
|
83
|
+
status: "ok",
|
|
84
|
+
data: readyData(metadata, false)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function readyData(metadata, alreadyRunning) {
|
|
89
|
+
return {
|
|
90
|
+
title: alreadyRunning ? "Local Zitadel server is already running." : "Local Zitadel server is ready.",
|
|
91
|
+
runtime: {
|
|
92
|
+
container_name: metadata.container_name,
|
|
93
|
+
container_id: metadata.container_id,
|
|
94
|
+
image: metadata.image,
|
|
95
|
+
port: metadata.port,
|
|
96
|
+
data_dir: metadata.data_dir
|
|
97
|
+
},
|
|
98
|
+
urls: {
|
|
99
|
+
api: metadata.server_url,
|
|
100
|
+
console: `${metadata.server_url}/ui/console/`,
|
|
101
|
+
login: `${metadata.server_url}/ui/login/`
|
|
102
|
+
},
|
|
103
|
+
next_actions: ["Set up your app, then start its dev server."],
|
|
104
|
+
next_commands: [
|
|
105
|
+
"npx @zitadel/cli@alpha setup --framework next --server local",
|
|
106
|
+
"npm install",
|
|
107
|
+
"npm run dev"
|
|
108
|
+
]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function waitForHealth(serverUrl, containerName) {
|
|
112
|
+
const started = Date.now();
|
|
113
|
+
while (Date.now() - started < START_TIMEOUT_MS) {
|
|
114
|
+
if (await checkLocalServerHealth(serverUrl, 1e3)) return;
|
|
115
|
+
await setTimeout(1e3);
|
|
116
|
+
}
|
|
117
|
+
throw new ZitadelError("E_NETWORK", "Local Zitadel server did not become healthy", {
|
|
118
|
+
hint: "Inspect the container logs, then reset the local runtime if needed.",
|
|
119
|
+
nextCommands: ["zitadel logs", "zitadel reset --force"],
|
|
120
|
+
details: {
|
|
121
|
+
container_name: containerName,
|
|
122
|
+
server_url: serverUrl
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function validatePort(port) {
|
|
127
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new ZitadelError("E_VALIDATION", `Invalid port ${String(port)}`, { hint: "Use a TCP port between 1 and 65535." });
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { Start as default };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=start.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.mjs","names":["sleep"],"sources":["../../src/commands/start.ts"],"sourcesContent":["import { setTimeout as sleep } from \"node:timers/promises\";\n\nimport { Flags } from \"@oclif/core\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../lib/oclif\";\nimport { ZitadelError } from \"../lib/errors\";\nimport {\n currentUser,\n ensureImage,\n inspectContainer,\n metadataFromStart,\n startContainer,\n stopAndRemoveContainer,\n} from \"../lib/local-server/docker\";\nimport {\n DEFAULT_LOCAL_SERVER_IMAGE,\n DEFAULT_LOCAL_SERVER_PORT,\n checkLocalServerHealth,\n ensureContainerIdentity,\n ensureLocalState,\n localContainerName,\n localServerUrl,\n writeRuntimeMetadata,\n} from \"../lib/local-server/runtime\";\n\nconst START_TIMEOUT_MS = 90_000;\n\nexport default class Start extends BaseCommand {\n static override description = \"Start a local Zitadel server.\";\n static override flags = {\n image: Flags.string({ description: \"Container image to run.\" }),\n port: Flags.integer({ description: \"Local HTTP port.\", default: DEFAULT_LOCAL_SERVER_PORT }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Start);\n const port = flags.port ?? DEFAULT_LOCAL_SERVER_PORT;\n const serverUrl = localServerUrl(port);\n await this.toMeta(flags, { resolveServer: false, source: serverUrl });\n\n validatePort(port);\n const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? DEFAULT_LOCAL_SERVER_IMAGE;\n const containerName = localContainerName(this.meta.cwd);\n if (this.meta.dryRun) {\n return this.emit({\n status: \"ok\",\n data: {\n title: \"Local Zitadel server start plan.\",\n runtime: {\n container_name: containerName,\n image,\n port,\n },\n urls: {\n api: serverUrl,\n console: `${serverUrl}/ui/console/`,\n login: `${serverUrl}/ui/login/`,\n },\n next_commands: [\"zitadel start\"],\n },\n });\n }\n\n const paths = await ensureLocalState(this.meta.cwd);\n\n const existing = await inspectContainer(containerName);\n if (existing.exists && existing.running && (await checkLocalServerHealth(serverUrl))) {\n const metadata = metadataFromStart({\n cwdDataDir: paths.dataDir,\n cliVersion: this.meta.cliVersion,\n containerName,\n containerId: existing.id ?? containerName,\n image,\n port,\n serverUrl,\n });\n await writeRuntimeMetadata(this.meta.cwd, metadata);\n return this.emit({ status: \"ok\", data: readyData(metadata, true) });\n }\n\n if (existing.exists) {\n await stopAndRemoveContainer(containerName);\n }\n\n await ensureImage(image);\n const containerId = await startContainer({\n containerName,\n image,\n port,\n dataDir: paths.dataDir,\n identity: await ensureContainerIdentity(this.meta.cwd, currentUser()),\n });\n await waitForHealth(serverUrl, containerName);\n\n const metadata = metadataFromStart({\n cwdDataDir: paths.dataDir,\n cliVersion: this.meta.cliVersion,\n containerName,\n containerId,\n image,\n port,\n serverUrl,\n });\n await writeRuntimeMetadata(this.meta.cwd, metadata);\n return this.emit({ status: \"ok\", data: readyData(metadata, false) });\n }\n}\n\nfunction readyData(metadata: ReturnType<typeof metadataFromStart>, alreadyRunning: boolean) {\n return {\n title: alreadyRunning\n ? \"Local Zitadel server is already running.\"\n : \"Local Zitadel server is ready.\",\n runtime: {\n container_name: metadata.container_name,\n container_id: metadata.container_id,\n image: metadata.image,\n port: metadata.port,\n data_dir: metadata.data_dir,\n },\n urls: {\n api: metadata.server_url,\n console: `${metadata.server_url}/ui/console/`,\n login: `${metadata.server_url}/ui/login/`,\n },\n next_actions: [\"Set up your app, then start its dev server.\"],\n next_commands: [\n \"npx @zitadel/cli@alpha setup --framework next --server local\",\n \"npm install\",\n \"npm run dev\",\n ],\n };\n}\n\nasync function waitForHealth(serverUrl: string, containerName: string): Promise<void> {\n const started = Date.now();\n while (Date.now() - started < START_TIMEOUT_MS) {\n if (await checkLocalServerHealth(serverUrl, 1000)) {\n return;\n }\n await sleep(1000);\n }\n throw new ZitadelError(\"E_NETWORK\", \"Local Zitadel server did not become healthy\", {\n hint: \"Inspect the container logs, then reset the local runtime if needed.\",\n nextCommands: [\"zitadel logs\", \"zitadel reset --force\"],\n details: { container_name: containerName, server_url: serverUrl },\n });\n}\n\nfunction validatePort(port: number): void {\n if (!Number.isInteger(port) || port < 1 || port > 65_535) {\n throw new ZitadelError(\"E_VALIDATION\", `Invalid port ${String(port)}`, {\n hint: \"Use a TCP port between 1 and 65535.\",\n });\n }\n}\n"],"mappings":"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"}
|