@zitadel/cli 0.1.0-alpha.16 → 0.1.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +145 -107
  2. package/SKILLS.md +98 -13
  3. package/dist/branding-BXj028-W.mjs +79 -0
  4. package/dist/branding-BXj028-W.mjs.map +1 -0
  5. package/dist/claim-state-BTGInAaN.mjs +66 -0
  6. package/dist/claim-state-BTGInAaN.mjs.map +1 -0
  7. package/dist/commands/apply.mjs +12 -7
  8. package/dist/commands/apply.mjs.map +1 -1
  9. package/dist/commands/branding/eject.mjs +125 -0
  10. package/dist/commands/branding/eject.mjs.map +1 -0
  11. package/dist/commands/claim.mjs +402 -0
  12. package/dist/commands/claim.mjs.map +1 -0
  13. package/dist/commands/doctor.mjs +477 -38
  14. package/dist/commands/doctor.mjs.map +1 -1
  15. package/dist/commands/eject.mjs +3 -3
  16. package/dist/commands/logs.mjs +2 -2
  17. package/dist/commands/plan.mjs +7 -5
  18. package/dist/commands/plan.mjs.map +1 -1
  19. package/dist/commands/reset.mjs +2 -2
  20. package/dist/commands/schemas/list.mjs +14 -10
  21. package/dist/commands/schemas/list.mjs.map +1 -1
  22. package/dist/commands/setup.mjs +331 -120
  23. package/dist/commands/setup.mjs.map +1 -1
  24. package/dist/commands/start.mjs +3 -4
  25. package/dist/commands/start.mjs.map +1 -1
  26. package/dist/commands/status.mjs +21 -13
  27. package/dist/commands/status.mjs.map +1 -1
  28. package/dist/commands/stop.mjs +3 -3
  29. package/dist/commands/stop.mjs.map +1 -1
  30. package/dist/{docker-BbX0yOpg.mjs → docker-Ciz9N6uI.mjs} +2 -2
  31. package/dist/{docker-BbX0yOpg.mjs.map → docker-Ciz9N6uI.mjs.map} +1 -1
  32. package/dist/{environment-ABj1IWZt.mjs → environment-CuHKxtIO.mjs} +1 -1
  33. package/dist/{environment-ABj1IWZt.mjs.map → environment-CuHKxtIO.mjs.map} +1 -1
  34. package/dist/{journey-guidance-E_3_UwGt.mjs → journey-guidance-CtQUfWar.mjs} +14 -3
  35. package/dist/{journey-guidance-E_3_UwGt.mjs.map → journey-guidance-CtQUfWar.mjs.map} +1 -1
  36. package/dist/{oclif-SjHLB_XK.mjs → oclif-CL6-M13m.mjs} +220 -43
  37. package/dist/oclif-CL6-M13m.mjs.map +1 -0
  38. package/dist/{orca-_TmrJQMw.mjs → orca-BWB4zOdw.mjs} +749 -192
  39. package/dist/orca-BWB4zOdw.mjs.map +1 -0
  40. package/dist/{ports-XujDtA47.mjs → ports-Cu2Y7aXQ.mjs} +25 -20
  41. package/dist/ports-Cu2Y7aXQ.mjs.map +1 -0
  42. package/dist/{processes-p2PPbKGG.mjs → processes-Bn2WngOi.mjs} +1 -1
  43. package/dist/{processes-p2PPbKGG.mjs.map → processes-Bn2WngOi.mjs.map} +1 -1
  44. package/dist/{project-DXc7q4wN.mjs → project-yejJSfdJ.mjs} +57 -4
  45. package/dist/project-yejJSfdJ.mjs.map +1 -0
  46. package/dist/scaffold-manifest-CKNFLX9T.mjs +81 -0
  47. package/dist/scaffold-manifest-CKNFLX9T.mjs.map +1 -0
  48. package/dist/{sync-Bu_iLIbb.mjs → sync-Cx2_4rXW.mjs} +178 -52
  49. package/dist/sync-Cx2_4rXW.mjs.map +1 -0
  50. package/dist/user-schema-W1OsXdpc.mjs +91 -0
  51. package/dist/user-schema-W1OsXdpc.mjs.map +1 -0
  52. package/oclif.manifest.json +227 -5
  53. package/package.json +7 -5
  54. package/dist/docker-guidance-3UZESTfb.mjs +0 -21
  55. package/dist/docker-guidance-3UZESTfb.mjs.map +0 -1
  56. package/dist/oclif-SjHLB_XK.mjs.map +0 -1
  57. package/dist/orca-_TmrJQMw.mjs.map +0 -1
  58. package/dist/ports-XujDtA47.mjs.map +0 -1
  59. package/dist/project-DXc7q4wN.mjs.map +0 -1
  60. package/dist/sync-Bu_iLIbb.mjs.map +0 -1
  61. package/dist/user-schema-DDz5-lX5.mjs +0 -13
  62. package/dist/user-schema-DDz5-lX5.mjs.map +0 -1
@@ -1,23 +1,33 @@
1
+ import { k as publicCliCommand } from "./oclif-CL6-M13m.mjs";
1
2
  import { execFile } from "node:child_process";
3
+ //#region src/lib/local-server/docker-guidance.ts
4
+ function dockerUnavailableMessage(error) {
5
+ const detail = error instanceof Error ? error.message : String(error);
6
+ return `Docker is not reachable${detail.trim() ? ` (${detail.trim()})` : ""}; managed local runtime commands need Docker, but remote or cloud setup can continue with --server <url>.`;
7
+ }
8
+ function dockerRuntimeGuidance(retryCommand, cliVersion) {
9
+ return {
10
+ hint: "Docker is required only for the managed local runtime (`zitadel start` and `--server local`). Start Docker, then retry; or target a remote server with `--server <url>`.",
11
+ nextActions: [
12
+ "Start Docker Desktop, Docker Engine, or a Docker-compatible runtime such as Colima.",
13
+ `Run \`docker version\`, then rerun \`${publicCliCommand(retryCommand, cliVersion)}\`.`,
14
+ "If you are not using the managed local runtime, run setup with `--server <url>` instead."
15
+ ],
16
+ nextCommands: ["docker version", publicCliCommand(retryCommand, cliVersion)]
17
+ };
18
+ }
19
+ //#endregion
2
20
  //#region src/lib/prober/ports.ts
3
21
  /**
4
- * Enumerate TCP ports currently in LISTEN state on the loopback interface
5
- * (`127.0.0.1`, `::1`, or the wildcard `*`). Spawns `lsof -iTCP -sTCP:LISTEN
6
- * -P -n -F n` and parses its machine-readable output. Returns the unique,
7
- * numerically-sorted list of ports.
22
+ * Enumerate TCP LISTEN sockets on loopback/wildcard interfaces (`127.0.0.1`,
23
+ * `::1`, or the wildcard `*`) with enough metadata for CLI diagnostics.
24
+ * Spawns `lsof -iTCP -sTCP:LISTEN -P -n -F pcn` and parses its
25
+ * machine-readable output.
8
26
  *
9
27
  * Never throws. Returns `[]` whenever lsof is unavailable (e.g. Windows,
10
28
  * unusual PATH), the spawn errors, exits non-zero, or the call exceeds
11
29
  * `timeoutMs` (default 1000ms). The caller treats an empty list the same as
12
- * "no listeners worth probing."
13
- */
14
- async function listListeningPorts(opts) {
15
- return uniqueSortedPorts(await listListeningTcpListeners(opts));
16
- }
17
- /**
18
- * Enumerate TCP LISTEN sockets on loopback/wildcard interfaces with enough
19
- * metadata for CLI diagnostics. Never throws; an empty array means either no
20
- * visible listeners or that process inspection is unavailable.
30
+ * "no visible listeners."
21
31
  */
22
32
  async function listListeningTcpListeners(opts) {
23
33
  const timeoutMs = opts?.timeoutMs ?? 1e3;
@@ -96,16 +106,11 @@ function parseLsofListeners(stdout) {
96
106
  }
97
107
  return listeners.sort((a, b) => a.port - b.port || (a.pid ?? 0) - (b.pid ?? 0));
98
108
  }
99
- function uniqueSortedPorts(listeners) {
100
- const ports = /* @__PURE__ */ new Set();
101
- for (const listener of listeners) ports.add(listener.port);
102
- return [...ports].sort((a, b) => a - b);
103
- }
104
109
  /** Recognised loopback host strings as emitted by `lsof -F n`. */
105
110
  function isLoopback(host) {
106
111
  return host === "*" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
107
112
  }
108
113
  //#endregion
109
- export { listenersForPort as n, listListeningPorts as t };
114
+ export { dockerRuntimeGuidance as n, dockerUnavailableMessage as r, listenersForPort as t };
110
115
 
111
- //# sourceMappingURL=ports-XujDtA47.mjs.map
116
+ //# sourceMappingURL=ports-Cu2Y7aXQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports-Cu2Y7aXQ.mjs","names":[],"sources":["../src/lib/local-server/docker-guidance.ts","../src/lib/prober/ports.ts"],"sourcesContent":["import { publicCliCommand } from \"../public-cli\";\n\nexport type DockerRuntimeGuidance = {\n hint: string;\n nextActions: string[];\n nextCommands: string[];\n};\n\nexport function dockerUnavailableMessage(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n const suffix = detail.trim() ? ` (${detail.trim()})` : \"\";\n return `Docker is not reachable${suffix}; managed local runtime commands need Docker, but remote or cloud setup can continue with --server <url>.`;\n}\n\nexport function dockerRuntimeGuidance(\n retryCommand: \"doctor\" | \"start\",\n cliVersion: string,\n): DockerRuntimeGuidance {\n return {\n hint:\n \"Docker is required only for the managed local runtime (`zitadel start` and `--server local`). Start Docker, then retry; or target a remote server with `--server <url>`.\",\n nextActions: [\n \"Start Docker Desktop, Docker Engine, or a Docker-compatible runtime such as Colima.\",\n `Run \\`docker version\\`, then rerun \\`${publicCliCommand(retryCommand, cliVersion)}\\`.`,\n \"If you are not using the managed local runtime, run setup with `--server <url>` instead.\",\n ],\n nextCommands: [\"docker version\", publicCliCommand(retryCommand, cliVersion)],\n };\n}\n","import { execFile } from \"node:child_process\";\n\nexport type TcpListener = Readonly<{\n address: string;\n command?: string;\n host: string;\n pid?: number;\n port: number;\n}>;\n\n/**\n * Enumerate TCP LISTEN sockets on loopback/wildcard interfaces (`127.0.0.1`,\n * `::1`, or the wildcard `*`) with enough metadata for CLI diagnostics.\n * Spawns `lsof -iTCP -sTCP:LISTEN -P -n -F pcn` and parses its\n * machine-readable output.\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 visible listeners.\"\n */\nexport async function listListeningTcpListeners(opts?: {\n readonly timeoutMs?: number;\n}): Promise<ReadonlyArray<TcpListener>> {\n const timeoutMs = opts?.timeoutMs ?? 1000;\n try {\n const stdout = await runLsof(timeoutMs);\n return parseLsofListeners(stdout);\n } catch {\n return [];\n }\n}\n\nexport async function listenersForPort(\n port: number,\n opts?: { readonly timeoutMs?: number },\n): Promise<ReadonlyArray<TcpListener>> {\n return (await listListeningTcpListeners(opts)).filter((listener) => listener.port === port);\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\", \"pcn\"],\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 pid (`p`), command (`c`), and name/address (`n`) records emitted by\n * `lsof -F pcn`. Address records end 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 parseLsofListeners(stdout: string): ReadonlyArray<TcpListener> {\n const listeners: TcpListener[] = [];\n let pid: number | undefined;\n let command: string | undefined;\n for (const line of stdout.split(/\\r?\\n/)) {\n if (line.startsWith(\"p\")) {\n const parsed = Number.parseInt(line.slice(1), 10);\n pid = Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n command = undefined;\n continue;\n }\n if (line.startsWith(\"c\")) {\n command = line.slice(1) || undefined;\n continue;\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 listeners.push({ address, command, host, pid, port });\n }\n }\n return listeners.sort((a, b) => a.port - b.port || (a.pid ?? 0) - (b.pid ?? 0));\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"],"mappings":";;;AAQA,SAAgB,yBAAyB,OAAwB;CAC/D,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAErE,QAAO,0BADQ,OAAO,MAAM,GAAG,KAAK,OAAO,MAAM,CAAC,KAAK,GACf;;AAG1C,SAAgB,sBACd,cACA,YACuB;AACvB,QAAO;EACL,MACE;EACF,aAAa;GACX;GACA,wCAAwC,iBAAiB,cAAc,WAAW,CAAC;GACnF;GACD;EACD,cAAc,CAAC,kBAAkB,iBAAiB,cAAc,WAAW,CAAC;EAC7E;;;;;;;;;;;;;;;ACNH,eAAsB,0BAA0B,MAER;CACtC,MAAM,YAAY,MAAM,aAAa;AACrC,KAAI;AAEF,SAAO,mBAAmB,MADL,QAAQ,UAAU,CACN;SAC3B;AACN,SAAO,EAAE;;;AAIb,eAAsB,iBACpB,MACA,MACqC;AACrC,SAAQ,MAAM,0BAA0B,KAAK,EAAE,QAAQ,aAAa,SAAS,SAAS,KAAK;;;;;;;;AAS7F,SAAS,QAAQ,WAAoC;AACnD,QAAO,IAAI,SAAiB,SAAS,WAAW;AAC9C,WACE,QACA;GAAC;GAAS;GAAgB;GAAM;GAAM;GAAM;GAAM,EAClD;GAAE,SAAS;GAAW,UAAU;GAAQ,GACvC,KAAK,WAAW;AACf,OAAI,KAAK;AACP,WAAO,IAAI;AACX;;AAEF,WAAQ,OAAO;IAElB;GACD;;;;;;;;AASJ,SAAS,mBAAmB,QAA4C;CACtE,MAAM,YAA2B,EAAE;CACnC,IAAI;CACJ,IAAI;AACJ,MAAK,MAAM,QAAQ,OAAO,MAAM,QAAQ,EAAE;AACxC,MAAI,KAAK,WAAW,IAAI,EAAE;GACxB,MAAM,SAAS,OAAO,SAAS,KAAK,MAAM,EAAE,EAAE,GAAG;AACjD,SAAM,OAAO,SAAS,OAAO,IAAI,SAAS,IAAI,SAAS,KAAA;AACvD,aAAU,KAAA;AACV;;AAEF,MAAI,KAAK,WAAW,IAAI,EAAE;AACxB,aAAU,KAAK,MAAM,EAAE,IAAI,KAAA;AAC3B;;AAEF,MAAI,CAAC,KAAK,WAAW,IAAI,CACvB;EAEF,MAAM,UAAU,KAAK,MAAM,EAAE;EAC7B,MAAM,QAAQ,QAAQ,YAAY,IAAI;AACtC,MAAI,QAAQ,EACV;EAEF,MAAM,OAAO,QAAQ,MAAM,GAAG,MAAM;EACpC,MAAM,UAAU,QAAQ,MAAM,QAAQ,EAAE;AACxC,MAAI,CAAC,WAAW,KAAK,CACnB;EAEF,MAAM,OAAO,OAAO,SAAS,SAAS,GAAG;AACzC,MAAI,OAAO,SAAS,KAAK,IAAI,OAAO,KAAK,OAAO,MAC9C,WAAU,KAAK;GAAE;GAAS;GAAS;GAAM;GAAK;GAAM,CAAC;;AAGzD,QAAO,UAAU,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,MAAM,EAAE,OAAO,GAAG;;;AAIjF,SAAS,WAAW,MAAuB;AACzC,QAAO,SAAS,OAAO,SAAS,eAAe,SAAS,WAAW,SAAS"}
@@ -117,4 +117,4 @@ function errorMessage(error) {
117
117
  //#endregion
118
118
  export { discoverManagedRuntimeProcesses as t };
119
119
 
120
- //# sourceMappingURL=processes-p2PPbKGG.mjs.map
120
+ //# sourceMappingURL=processes-Bn2WngOi.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"processes-p2PPbKGG.mjs","names":[],"sources":["../src/lib/local-server/processes.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\nexport type ManagedRuntimeProcess = Readonly<{\n command: string;\n data_dir?: string;\n kind: \"server-binary\" | \"server-wrapper\";\n pid: number;\n ppid?: number;\n}>;\n\nexport type ManagedRuntimeDiscovery = Readonly<{\n error?: string;\n processes: ReadonlyArray<ManagedRuntimeProcess>;\n supported: boolean;\n}>;\n\ntype ProcessRow = Readonly<{\n command: string;\n pid: number;\n ppid?: number;\n}>;\n\nexport async function discoverManagedRuntimeProcesses(opts?: {\n readonly timeoutMs?: number;\n}): Promise<ManagedRuntimeDiscovery> {\n if (process.platform === \"win32\") {\n return { supported: false, processes: [], error: \"process sweep is not available on Windows\" };\n }\n\n let rows: ReadonlyArray<ProcessRow>;\n try {\n rows = parsePsRows(await runPs([\"axo\", \"pid=,ppid=,command=\"], opts?.timeoutMs ?? 1000));\n } catch (error) {\n return { supported: false, processes: [], error: errorMessage(error) };\n }\n\n const candidates = rows.filter((row) => isRuntimeCandidate(row.command));\n const hydrated = await Promise.all(\n candidates.map(async (row) => ({\n ...row,\n command: await hydrateCommand(row.pid, row.command, opts?.timeoutMs ?? 1000),\n })),\n );\n const processes = hydrated\n .map(toManagedRuntimeProcess)\n .filter((candidate): candidate is ManagedRuntimeProcess => candidate !== undefined);\n return { supported: true, processes: uniqueByPid(processes) };\n}\n\nfunction runPs(args: string[], timeoutMs: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n execFile(\"ps\", args, { timeout: timeoutMs, encoding: \"utf8\" }, (err, stdout) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(stdout);\n });\n });\n}\n\nasync function hydrateCommand(pid: number, fallback: string, timeoutMs: number): Promise<string> {\n try {\n const hydrated = await runPs([\"eww\", \"-p\", String(pid), \"-o\", \"command=\"], timeoutMs);\n return hydrated.trim() || fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction parsePsRows(stdout: string): ReadonlyArray<ProcessRow> {\n const rows: ProcessRow[] = [];\n for (const line of stdout.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n const match = trimmed.match(/^(\\d+)\\s+(\\d+)\\s+(.+)$/);\n if (!match) {\n continue;\n }\n rows.push({\n pid: Number(match[1]),\n ppid: Number(match[2]),\n command: match[3] ?? \"\",\n });\n }\n return rows;\n}\n\nfunction toManagedRuntimeProcess(row: ProcessRow): ManagedRuntimeProcess | undefined {\n const dataDir = extractDataDir(row.command);\n const wrapper = isServerWrapper(row.command);\n const binary = isServerBinary(row.command);\n if (!wrapper && !binary) {\n return undefined;\n }\n if (!wrapper && !isCliLocalDataDir(dataDir)) {\n return undefined;\n }\n return {\n pid: row.pid,\n ppid: row.ppid,\n command: stripEnvironmentNoise(row.command),\n kind: wrapper ? \"server-wrapper\" : \"server-binary\",\n ...(dataDir ? { data_dir: dataDir } : {}),\n };\n}\n\nfunction isRuntimeCandidate(command: string): boolean {\n return (\n isServerWrapper(command) ||\n isServerBinary(command) ||\n command.includes(\"NEXTGEN_SERVER_DATA_DIR=\") ||\n basename(command.split(/\\s+/)[0] ?? \"\") === \"nextgen\"\n );\n}\n\nfunction isServerWrapper(command: string): boolean {\n return command.includes(\"zitadel-server.js\");\n}\n\nfunction isServerBinary(command: string): boolean {\n if (/@zitadel\\/server-[^/\\s]+\\/bin\\/nextgen(?:\\.exe)?(?:\\s|$)/.test(command)) {\n return true;\n }\n return (\n isCliLocalDataDir(extractDataDir(command)) &&\n commandTokens(command).some((token) => basename(token) === \"nextgen\")\n );\n}\n\nfunction extractDataDir(command: string): string | undefined {\n const match = command.match(/NEXTGEN_SERVER_DATA_DIR=(\"[^\"]+\"|'[^']+'|[^\\s]+)/);\n const value = match?.[1];\n if (!value) {\n return undefined;\n }\n return value.replace(/^[\"']|[\"']$/g, \"\");\n}\n\nfunction isCliLocalDataDir(path: string | undefined): boolean {\n return Boolean(path && /(?:^|\\/)\\.zitadel\\/local\\/nextgen-data\\/?$/.test(path));\n}\n\nfunction stripEnvironmentNoise(command: string): string {\n return command\n .replace(/(^|\\s)[A-Z_][A-Z0-9_]*=(?:\"[^\"]+\"|'[^']+'|[^\\s]+)/g, \" \")\n .replace(/\\s{2,}/g, \" \")\n .trim();\n}\n\nfunction commandTokens(command: string): string[] {\n return command.split(/\\s+/).filter((token) => !/^[A-Z_][A-Z0-9_]*=/.test(token));\n}\n\nfunction uniqueByPid(\n processes: ReadonlyArray<ManagedRuntimeProcess>,\n): ReadonlyArray<ManagedRuntimeProcess> {\n const byPid = new Map<number, ManagedRuntimeProcess>();\n for (const processInfo of processes) {\n byPid.set(processInfo.pid, processInfo);\n }\n return [...byPid.values()].sort((a, b) => a.pid - b.pid);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"processes-Bn2WngOi.mjs","names":[],"sources":["../src/lib/local-server/processes.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { basename } from \"node:path\";\n\nexport type ManagedRuntimeProcess = Readonly<{\n command: string;\n data_dir?: string;\n kind: \"server-binary\" | \"server-wrapper\";\n pid: number;\n ppid?: number;\n}>;\n\nexport type ManagedRuntimeDiscovery = Readonly<{\n error?: string;\n processes: ReadonlyArray<ManagedRuntimeProcess>;\n supported: boolean;\n}>;\n\ntype ProcessRow = Readonly<{\n command: string;\n pid: number;\n ppid?: number;\n}>;\n\nexport async function discoverManagedRuntimeProcesses(opts?: {\n readonly timeoutMs?: number;\n}): Promise<ManagedRuntimeDiscovery> {\n if (process.platform === \"win32\") {\n return { supported: false, processes: [], error: \"process sweep is not available on Windows\" };\n }\n\n let rows: ReadonlyArray<ProcessRow>;\n try {\n rows = parsePsRows(await runPs([\"axo\", \"pid=,ppid=,command=\"], opts?.timeoutMs ?? 1000));\n } catch (error) {\n return { supported: false, processes: [], error: errorMessage(error) };\n }\n\n const candidates = rows.filter((row) => isRuntimeCandidate(row.command));\n const hydrated = await Promise.all(\n candidates.map(async (row) => ({\n ...row,\n command: await hydrateCommand(row.pid, row.command, opts?.timeoutMs ?? 1000),\n })),\n );\n const processes = hydrated\n .map(toManagedRuntimeProcess)\n .filter((candidate): candidate is ManagedRuntimeProcess => candidate !== undefined);\n return { supported: true, processes: uniqueByPid(processes) };\n}\n\nfunction runPs(args: string[], timeoutMs: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n execFile(\"ps\", args, { timeout: timeoutMs, encoding: \"utf8\" }, (err, stdout) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(stdout);\n });\n });\n}\n\nasync function hydrateCommand(pid: number, fallback: string, timeoutMs: number): Promise<string> {\n try {\n const hydrated = await runPs([\"eww\", \"-p\", String(pid), \"-o\", \"command=\"], timeoutMs);\n return hydrated.trim() || fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction parsePsRows(stdout: string): ReadonlyArray<ProcessRow> {\n const rows: ProcessRow[] = [];\n for (const line of stdout.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n const match = trimmed.match(/^(\\d+)\\s+(\\d+)\\s+(.+)$/);\n if (!match) {\n continue;\n }\n rows.push({\n pid: Number(match[1]),\n ppid: Number(match[2]),\n command: match[3] ?? \"\",\n });\n }\n return rows;\n}\n\nfunction toManagedRuntimeProcess(row: ProcessRow): ManagedRuntimeProcess | undefined {\n const dataDir = extractDataDir(row.command);\n const wrapper = isServerWrapper(row.command);\n const binary = isServerBinary(row.command);\n if (!wrapper && !binary) {\n return undefined;\n }\n if (!wrapper && !isCliLocalDataDir(dataDir)) {\n return undefined;\n }\n return {\n pid: row.pid,\n ppid: row.ppid,\n command: stripEnvironmentNoise(row.command),\n kind: wrapper ? \"server-wrapper\" : \"server-binary\",\n ...(dataDir ? { data_dir: dataDir } : {}),\n };\n}\n\nfunction isRuntimeCandidate(command: string): boolean {\n return (\n isServerWrapper(command) ||\n isServerBinary(command) ||\n command.includes(\"NEXTGEN_SERVER_DATA_DIR=\") ||\n basename(command.split(/\\s+/)[0] ?? \"\") === \"nextgen\"\n );\n}\n\nfunction isServerWrapper(command: string): boolean {\n return command.includes(\"zitadel-server.js\");\n}\n\nfunction isServerBinary(command: string): boolean {\n if (/@zitadel\\/server-[^/\\s]+\\/bin\\/nextgen(?:\\.exe)?(?:\\s|$)/.test(command)) {\n return true;\n }\n return (\n isCliLocalDataDir(extractDataDir(command)) &&\n commandTokens(command).some((token) => basename(token) === \"nextgen\")\n );\n}\n\nfunction extractDataDir(command: string): string | undefined {\n const match = command.match(/NEXTGEN_SERVER_DATA_DIR=(\"[^\"]+\"|'[^']+'|[^\\s]+)/);\n const value = match?.[1];\n if (!value) {\n return undefined;\n }\n return value.replace(/^[\"']|[\"']$/g, \"\");\n}\n\nfunction isCliLocalDataDir(path: string | undefined): boolean {\n return Boolean(path && /(?:^|\\/)\\.zitadel\\/local\\/nextgen-data\\/?$/.test(path));\n}\n\nfunction stripEnvironmentNoise(command: string): string {\n return command\n .replace(/(^|\\s)[A-Z_][A-Z0-9_]*=(?:\"[^\"]+\"|'[^']+'|[^\\s]+)/g, \" \")\n .replace(/\\s{2,}/g, \" \")\n .trim();\n}\n\nfunction commandTokens(command: string): string[] {\n return command.split(/\\s+/).filter((token) => !/^[A-Z_][A-Z0-9_]*=/.test(token));\n}\n\nfunction uniqueByPid(\n processes: ReadonlyArray<ManagedRuntimeProcess>,\n): ReadonlyArray<ManagedRuntimeProcess> {\n const byPid = new Map<number, ManagedRuntimeProcess>();\n for (const processInfo of processes) {\n byPid.set(processInfo.pid, processInfo);\n }\n return [...byPid.values()].sort((a, b) => a.pid - b.pid);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { C as isObject, E as ZitadelError, w as parseJsonObject } from "./oclif-SjHLB_XK.mjs";
2
- import { readFile, stat } from "node:fs/promises";
1
+ import { C as parseJsonObject, E as ZitadelError, S as isObject, T as stableStringify, n as DEFAULT_SERVER } from "./oclif-CL6-M13m.mjs";
2
+ import { chmod, readFile, rename, stat, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  //#region src/lib/project.ts
5
5
  /**
@@ -62,6 +62,25 @@ async function readZitadelSecret(cwd) {
62
62
  }
63
63
  }
64
64
  /**
65
+ * Writes `.zitadel/secret` back after a command mutated it (today only
66
+ * `zitadel claim`, which records `claimed_at` and `team_id`).
67
+ *
68
+ * Atomic by construction: the bytes land in a sibling temp file that is
69
+ * `chmod`ed and then `rename`d over the target, so a crash mid-write can never
70
+ * leave a truncated or world-readable credentials file — the same
71
+ * temp-then-rename the file-writer uses for managed files. Serialised with
72
+ * {@link stableStringify} at mode `0600` to match exactly how setup's patcher
73
+ * first wrote it, so a claim does not churn the file's formatting or loosen
74
+ * the permissions `doctor` asserts.
75
+ */
76
+ async function writeZitadelSecret(cwd, secret) {
77
+ const path = join(cwd, ".zitadel/secret");
78
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
79
+ await writeFile(tmp, `${stableStringify(secret)}\n`, { mode: 384 });
80
+ await chmod(tmp, 384).catch(() => void 0);
81
+ await rename(tmp, path);
82
+ }
83
+ /**
65
84
  * Reads the configured renderer id from a parsed `zitadel.json`, normalising the
66
85
  * legacy `default` alias to `react` and falling back to `react` when unset. The
67
86
  * value is validated downstream by `getRenderer`, so callers need not re-check.
@@ -71,6 +90,40 @@ function readRendererId(config) {
71
90
  const value = branding && typeof branding.renderer === "string" ? branding.renderer : "react";
72
91
  return value === "default" ? "react" : value;
73
92
  }
93
+ /**
94
+ * Reads the recorded sign-in preset from a parsed `zitadel.json`, if present.
95
+ * Setup persists it (see the patcher's `projectConfig`); absent means
96
+ * password-first per the setup default.
97
+ */
98
+ function readPreset(config) {
99
+ return typeof config.preset === "string" ? config.preset : void 0;
100
+ }
101
+ /**
102
+ * Reads the recorded use case from a parsed `zitadel.json`, if present.
103
+ * Guidance-only, like the field itself — behavior lives in the generated
104
+ * schema/flow files.
105
+ */
106
+ function readUseCase(config) {
107
+ return typeof config.useCase === "string" ? config.useCase : void 0;
108
+ }
109
+ /**
110
+ * Reads the top-level `server` origin from a parsed `zitadel.json`, falling
111
+ * back to {@link DEFAULT_SERVER} when the key is absent.
112
+ *
113
+ * Top-level only, unlike `resolveServer`, which prefers the selected
114
+ * environment's `server` before falling back here. That is the point: an
115
+ * environment override says where one invocation should talk, not where the
116
+ * project lives. No practical difference today, since setup always writes a
117
+ * concrete top-level `server`.
118
+ *
119
+ * This is the server the project actually lives on, which is not always the one
120
+ * a command is talking to: `doctor` pins its own source to the local runtime URL
121
+ * and `status --server local` overrides it. Anything reasoning about the
122
+ * project itself (rather than about this invocation) has to read it from here.
123
+ */
124
+ function readProjectServer(config) {
125
+ return typeof config.server === "string" ? config.server : DEFAULT_SERVER;
126
+ }
74
127
  /** Reads `environments.development.issuer` from a parsed `zitadel.json`, if present. */
75
128
  function readDevelopmentIssuer(config) {
76
129
  if (isObject(config.environments) && isObject(config.environments.development)) {
@@ -82,6 +135,6 @@ function isNotFound(error) {
82
135
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
83
136
  }
84
137
  //#endregion
85
- export { readZitadelConfig as a, readRendererId as i, hasZitadelSecret as n, readZitadelSecret as o, readDevelopmentIssuer as r, hasZitadelConfig as t };
138
+ export { readProjectServer as a, readZitadelConfig as c, readPreset as i, readZitadelSecret as l, hasZitadelSecret as n, readRendererId as o, readDevelopmentIssuer as r, readUseCase as s, hasZitadelConfig as t, writeZitadelSecret as u };
86
139
 
87
- //# sourceMappingURL=project-DXc7q4wN.mjs.map
140
+ //# sourceMappingURL=project-yejJSfdJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-yejJSfdJ.mjs","names":[],"sources":["../src/lib/project.ts"],"sourcesContent":["import { chmod, readFile, rename, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ZitadelError } from \"./errors\";\nimport { isObject, parseJsonObject, stableStringify } from \"./json\";\nimport { DEFAULT_SERVER } from \"./server\";\n\n/**\n * Reports whether `cwd` has already been initialized, i.e. a committed\n * `zitadel.json` exists. Used to decide whether setup should run or skip.\n */\nexport async function hasZitadelConfig(cwd: string): Promise<boolean> {\n return exists(join(cwd, \"zitadel.json\"));\n}\n\n/**\n * Reports whether local secret material (`.zitadel/secret`) is present. Gates\n * commands that need credentials, and signals that secrets were already pulled.\n */\nexport async function hasZitadelSecret(cwd: string): Promise<boolean> {\n return exists(join(cwd, \".zitadel/secret\"));\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch (error) {\n if (isNotFound(error)) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Shape of the project secret persisted at `.zitadel/secret`. Holds the\n * project identity plus the credentials used to talk to the platform in\n * preview and production. Validated structurally by {@link readZitadelSecret}.\n */\nexport type ZitadelSecret = {\n project_id: string;\n project_secret: string;\n preview_secret: string;\n preview_origins: string[];\n created_at: string;\n /**\n * When the project was attached to an owning team, recorded by\n * `zitadel claim`. Absent until then. The durable record lives on the\n * platform (the team's project grant); this is a local cache so commands can\n * answer \"is this already attached?\" without a round trip.\n */\n claimed_at?: string;\n /** The team that owns the project, recorded alongside {@link claimed_at}. */\n team_id?: string;\n};\n\n/**\n * Reads and parses `zitadel.json` into a plain object. Translates a missing\n * file into an actionable `E_VALIDATION` error pointing at `zitadel setup`;\n * other errors (e.g. malformed JSON) propagate unchanged.\n */\nexport async function readZitadelConfig(cwd: string): Promise<Record<string, unknown>> {\n try {\n return parseJsonObject(await readFile(join(cwd, \"zitadel.json\"), \"utf8\"), \"zitadel.json\");\n } catch (error) {\n if (isNotFound(error)) {\n throw new ZitadelError(\"E_VALIDATION\", \"zitadel.json was not found\", {\n hint: \"Run `zitadel setup` first.\",\n nextCommands: [\"zitadel setup\"],\n });\n }\n throw error;\n }\n}\n\n/**\n * Reads, parses, and structurally validates `.zitadel/secret`, returning it\n * as a {@link ZitadelSecret}. A missing file becomes an actionable\n * `E_VALIDATION` error pointing at `zitadel setup` / `zitadel doctor --fix`;\n * a present-but-incomplete file throws so callers never proceed with partial\n * credentials.\n */\nexport async function readZitadelSecret(cwd: string): Promise<ZitadelSecret> {\n try {\n const secret = parseJsonObject(\n await readFile(join(cwd, \".zitadel/secret\"), \"utf8\"),\n \".zitadel/secret\",\n );\n if (\n typeof secret.project_id !== \"string\" ||\n typeof secret.project_secret !== \"string\" ||\n typeof secret.preview_secret !== \"string\" ||\n !Array.isArray(secret.preview_origins)\n ) {\n throw new Error(\".zitadel/secret is missing required fields\");\n }\n return secret as ZitadelSecret;\n } catch (error) {\n if (isNotFound(error)) {\n throw new ZitadelError(\"E_VALIDATION\", \".zitadel/secret was not found\", {\n hint: \"Run `zitadel setup` first, or restore the project secret with `zitadel doctor --fix`.\",\n nextCommands: [\"zitadel setup\", \"zitadel doctor --fix\"],\n });\n }\n throw error;\n }\n}\n\n/**\n * Writes `.zitadel/secret` back after a command mutated it (today only\n * `zitadel claim`, which records `claimed_at` and `team_id`).\n *\n * Atomic by construction: the bytes land in a sibling temp file that is\n * `chmod`ed and then `rename`d over the target, so a crash mid-write can never\n * leave a truncated or world-readable credentials file — the same\n * temp-then-rename the file-writer uses for managed files. Serialised with\n * {@link stableStringify} at mode `0600` to match exactly how setup's patcher\n * first wrote it, so a claim does not churn the file's formatting or loosen\n * the permissions `doctor` asserts.\n */\nexport async function writeZitadelSecret(cwd: string, secret: ZitadelSecret): Promise<void> {\n const path = join(cwd, \".zitadel/secret\");\n const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;\n await writeFile(tmp, `${stableStringify(secret)}\\n`, { mode: 0o600 });\n // `writeFile`'s mode is masked by the process umask, so re-assert it.\n await chmod(tmp, 0o600).catch(() => undefined);\n await rename(tmp, path);\n}\n\n/**\n * Reads the configured renderer id from a parsed `zitadel.json`, normalising the\n * legacy `default` alias to `react` and falling back to `react` when unset. The\n * value is validated downstream by `getRenderer`, so callers need not re-check.\n */\nexport function readRendererId(config: Record<string, unknown>): string {\n const branding = isObject(config.branding) ? config.branding : undefined;\n const value = branding && typeof branding.renderer === \"string\" ? branding.renderer : \"react\";\n return value === \"default\" ? \"react\" : value;\n}\n\n/**\n * Reads the recorded sign-in preset from a parsed `zitadel.json`, if present.\n * Setup persists it (see the patcher's `projectConfig`); absent means\n * password-first per the setup default.\n */\nexport function readPreset(config: Record<string, unknown>): string | undefined {\n return typeof config.preset === \"string\" ? config.preset : undefined;\n}\n\n/**\n * Reads the recorded use case from a parsed `zitadel.json`, if present.\n * Guidance-only, like the field itself — behavior lives in the generated\n * schema/flow files.\n */\nexport function readUseCase(config: Record<string, unknown>): string | undefined {\n return typeof config.useCase === \"string\" ? config.useCase : undefined;\n}\n\n/**\n * Reads the top-level `server` origin from a parsed `zitadel.json`, falling\n * back to {@link DEFAULT_SERVER} when the key is absent.\n *\n * Top-level only, unlike `resolveServer`, which prefers the selected\n * environment's `server` before falling back here. That is the point: an\n * environment override says where one invocation should talk, not where the\n * project lives. No practical difference today, since setup always writes a\n * concrete top-level `server`.\n *\n * This is the server the project actually lives on, which is not always the one\n * a command is talking to: `doctor` pins its own source to the local runtime URL\n * and `status --server local` overrides it. Anything reasoning about the\n * project itself (rather than about this invocation) has to read it from here.\n */\nexport function readProjectServer(config: Record<string, unknown>): string {\n return typeof config.server === \"string\" ? config.server : DEFAULT_SERVER;\n}\n\n/** Reads `environments.development.issuer` from a parsed `zitadel.json`, if present. */\nexport function readDevelopmentIssuer(config: Record<string, unknown>): string | undefined {\n if (isObject(config.environments) && isObject(config.environments.development)) {\n const issuer = config.environments.development.issuer;\n return typeof issuer === \"string\" ? issuer : undefined;\n }\n return undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code?: string }).code === \"ENOENT\"\n );\n}\n"],"mappings":";;;;;;;;AAWA,eAAsB,iBAAiB,KAA+B;AACpE,QAAO,OAAO,KAAK,KAAK,eAAe,CAAC;;;;;;AAO1C,eAAsB,iBAAiB,KAA+B;AACpE,QAAO,OAAO,KAAK,KAAK,kBAAkB,CAAC;;AAG7C,eAAe,OAAO,MAAgC;AACpD,KAAI;AACF,QAAM,KAAK,KAAK;AAChB,SAAO;UACA,OAAO;AACd,MAAI,WAAW,MAAM,CACnB,QAAO;AAET,QAAM;;;;;;;;AA+BV,eAAsB,kBAAkB,KAA+C;AACrF,KAAI;AACF,SAAO,gBAAgB,MAAM,SAAS,KAAK,KAAK,eAAe,EAAE,OAAO,EAAE,eAAe;UAClF,OAAO;AACd,MAAI,WAAW,MAAM,CACnB,OAAM,IAAI,aAAa,gBAAgB,8BAA8B;GACnE,MAAM;GACN,cAAc,CAAC,gBAAgB;GAChC,CAAC;AAEJ,QAAM;;;;;;;;;;AAWV,eAAsB,kBAAkB,KAAqC;AAC3E,KAAI;EACF,MAAM,SAAS,gBACb,MAAM,SAAS,KAAK,KAAK,kBAAkB,EAAE,OAAO,EACpD,kBACD;AACD,MACE,OAAO,OAAO,eAAe,YAC7B,OAAO,OAAO,mBAAmB,YACjC,OAAO,OAAO,mBAAmB,YACjC,CAAC,MAAM,QAAQ,OAAO,gBAAgB,CAEtC,OAAM,IAAI,MAAM,6CAA6C;AAE/D,SAAO;UACA,OAAO;AACd,MAAI,WAAW,MAAM,CACnB,OAAM,IAAI,aAAa,gBAAgB,iCAAiC;GACtE,MAAM;GACN,cAAc,CAAC,iBAAiB,uBAAuB;GACxD,CAAC;AAEJ,QAAM;;;;;;;;;;;;;;;AAgBV,eAAsB,mBAAmB,KAAa,QAAsC;CAC1F,MAAM,OAAO,KAAK,KAAK,kBAAkB;CACzC,MAAM,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,GAAG,KAAK,KAAK;AACpD,OAAM,UAAU,KAAK,GAAG,gBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,KAAO,CAAC;AAErE,OAAM,MAAM,KAAK,IAAM,CAAC,YAAY,KAAA,EAAU;AAC9C,OAAM,OAAO,KAAK,KAAK;;;;;;;AAQzB,SAAgB,eAAe,QAAyC;CACtE,MAAM,WAAW,SAAS,OAAO,SAAS,GAAG,OAAO,WAAW,KAAA;CAC/D,MAAM,QAAQ,YAAY,OAAO,SAAS,aAAa,WAAW,SAAS,WAAW;AACtF,QAAO,UAAU,YAAY,UAAU;;;;;;;AAQzC,SAAgB,WAAW,QAAqD;AAC9E,QAAO,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,KAAA;;;;;;;AAQ7D,SAAgB,YAAY,QAAqD;AAC/E,QAAO,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA;;;;;;;;;;;;;;;;;AAkB/D,SAAgB,kBAAkB,QAAyC;AACzE,QAAO,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;;;AAI7D,SAAgB,sBAAsB,QAAqD;AACzF,KAAI,SAAS,OAAO,aAAa,IAAI,SAAS,OAAO,aAAa,YAAY,EAAE;EAC9E,MAAM,SAAS,OAAO,aAAa,YAAY;AAC/C,SAAO,OAAO,WAAW,WAAW,SAAS,KAAA;;;AAKjD,SAAS,WAAW,OAAyB;AAC3C,QACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAA4B,SAAS"}
@@ -0,0 +1,81 @@
1
+ import { S as isObject } from "./oclif-CL6-M13m.mjs";
2
+ import { i as updateScaffold, n as readState } from "./user-schema-W1OsXdpc.mjs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { isAbsolute, join, relative, sep } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ //#region src/lib/scaffold-manifest.ts
7
+ /**
8
+ * Content hash of one scaffolded file as recorded in the scaffold manifest.
9
+ * Deliberately hashes the raw text (not a parsed structure): the manifest
10
+ * answers "are these still the bytes setup wrote?", nothing more. The sync
11
+ * loop's hashers work on JSON objects and serve a different contract.
12
+ */
13
+ function hashScaffoldFile(contents) {
14
+ return createHash("sha256").update(contents).digest("hex");
15
+ }
16
+ /**
17
+ * Read the scaffold manifest from `.zitadel/state.json`, or `undefined` when
18
+ * the file, the section, its `files` record, or any file entry is missing or
19
+ * malformed (each entry needs a string `hash` and a known `class`).
20
+ * `undefined` sends the doctor managed-files check into its template-derived
21
+ * fallback mode, so a corrupt or future-shaped manifest degrades rather than
22
+ * breaks.
23
+ */
24
+ async function readScaffoldManifest(cwd) {
25
+ try {
26
+ const scaffold = (await readState(cwd)).scaffold;
27
+ if (!isObject(scaffold)) return;
28
+ if (!isObject(scaffold.files)) return;
29
+ for (const entry of Object.values(scaffold.files)) {
30
+ if (!isObject(entry)) return;
31
+ if (typeof entry.hash !== "string") return;
32
+ if (entry.class !== "infrastructure" && entry.class !== "presentation") return;
33
+ }
34
+ if (scaffold.posture !== void 0 && scaffold.posture !== "page" && scaffold.posture !== "widget") return;
35
+ return scaffold;
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ /**
41
+ * Record which managed app files `zitadel setup` actually wrote (or found
42
+ * already matching), keyed by project-root-relative posix path with the
43
+ * content hash and ownership class. Only marked files that the scaffold run
44
+ * touched are recorded — a conditional file whose op never ran (e.g. the
45
+ * framework home page on a pre-existing app) is simply absent, which is what
46
+ * lets `doctor` later distinguish "CLI wrote this and it vanished" from "the
47
+ * CLI never owned this".
48
+ */
49
+ async function writeScaffoldManifest(options) {
50
+ const { cwd, actions, written, scaffoldedFramework, devPort, posture } = options;
51
+ const touched = new Set(written.map((path) => relativePosix(cwd, path)));
52
+ const files = {};
53
+ for (const path of actions.markedFiles) {
54
+ const key = relativePosix(cwd, path);
55
+ if (!touched.has(key)) continue;
56
+ let contents;
57
+ try {
58
+ contents = await readFile(join(cwd, key), "utf8");
59
+ } catch {
60
+ continue;
61
+ }
62
+ files[key] = {
63
+ hash: hashScaffoldFile(contents),
64
+ class: actions.fileClasses?.[path] ?? "presentation"
65
+ };
66
+ }
67
+ await updateScaffold(cwd, {
68
+ files,
69
+ ...scaffoldedFramework ? { scaffolded_framework: true } : {},
70
+ ...typeof devPort === "number" ? { dev_port: devPort } : {},
71
+ ...posture === void 0 ? {} : { posture }
72
+ });
73
+ }
74
+ /** Normalizes an absolute or relative path to a cwd-relative posix key. */
75
+ function relativePosix(cwd, path) {
76
+ return (isAbsolute(path) ? relative(cwd, path) : path).split(sep).join("/");
77
+ }
78
+ //#endregion
79
+ export { readScaffoldManifest as n, writeScaffoldManifest as r, hashScaffoldFile as t };
80
+
81
+ //# sourceMappingURL=scaffold-manifest-CKNFLX9T.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-manifest-CKNFLX9T.mjs","names":[],"sources":["../src/lib/scaffold-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, isAbsolute, relative, sep } from \"node:path\";\n\nimport { isObject } from \"./json\";\nimport type { EjectActions } from \"./orca/patchers/types\";\nimport { readState, updateScaffold } from \"./sync/state\";\nimport type { ScaffoldManifest, ScaffoldPosture } from \"./sync/types\";\n\n/**\n * Content hash of one scaffolded file as recorded in the scaffold manifest.\n * Deliberately hashes the raw text (not a parsed structure): the manifest\n * answers \"are these still the bytes setup wrote?\", nothing more. The sync\n * loop's hashers work on JSON objects and serve a different contract.\n */\nexport function hashScaffoldFile(contents: string): string {\n return createHash(\"sha256\").update(contents).digest(\"hex\");\n}\n\n/**\n * Read the scaffold manifest from `.zitadel/state.json`, or `undefined` when\n * the file, the section, its `files` record, or any file entry is missing or\n * malformed (each entry needs a string `hash` and a known `class`).\n * `undefined` sends the doctor managed-files check into its template-derived\n * fallback mode, so a corrupt or future-shaped manifest degrades rather than\n * breaks.\n */\nexport async function readScaffoldManifest(cwd: string): Promise<ScaffoldManifest | undefined> {\n try {\n const state = await readState(cwd);\n const scaffold = state.scaffold;\n // isObject rejects arrays too — `typeof [] === \"object\"`, and an array\n // `files` would otherwise enter manifest mode as \"0 tracked\".\n if (!isObject(scaffold)) {\n return undefined;\n }\n if (!isObject(scaffold.files)) {\n return undefined;\n }\n for (const entry of Object.values(scaffold.files)) {\n if (!isObject(entry)) {\n return undefined;\n }\n if (typeof entry.hash !== \"string\") {\n return undefined;\n }\n if (entry.class !== \"infrastructure\" && entry.class !== \"presentation\") {\n return undefined;\n }\n }\n // A posture value this CLI does not know is a future-shaped manifest —\n // degrade like any other malformed section rather than restoring a\n // posture the templates cannot render.\n if (\n scaffold.posture !== undefined &&\n scaffold.posture !== \"page\" &&\n scaffold.posture !== \"widget\"\n ) {\n return undefined;\n }\n return scaffold as unknown as ScaffoldManifest;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Record which managed app files `zitadel setup` actually wrote (or found\n * already matching), keyed by project-root-relative posix path with the\n * content hash and ownership class. Only marked files that the scaffold run\n * touched are recorded — a conditional file whose op never ran (e.g. the\n * framework home page on a pre-existing app) is simply absent, which is what\n * lets `doctor` later distinguish \"CLI wrote this and it vanished\" from \"the\n * CLI never owned this\".\n */\nexport async function writeScaffoldManifest(options: {\n cwd: string;\n actions: EjectActions;\n /** Paths the scaffold reported written or skipped-as-identical. */\n written: ReadonlyArray<string>;\n scaffoldedFramework?: boolean;\n devPort?: number;\n posture?: ScaffoldPosture;\n}): Promise<void> {\n const { cwd, actions, written, scaffoldedFramework, devPort, posture } = options;\n const touched = new Set(written.map((path) => relativePosix(cwd, path)));\n const files: ScaffoldManifest[\"files\"] = {};\n for (const path of actions.markedFiles) {\n const key = relativePosix(cwd, path);\n if (!touched.has(key)) {\n continue;\n }\n let contents: string;\n try {\n contents = await readFile(join(cwd, key), \"utf8\");\n } catch {\n continue;\n }\n files[key] = {\n hash: hashScaffoldFile(contents),\n class: actions.fileClasses?.[path] ?? \"presentation\",\n };\n }\n await updateScaffold(cwd, {\n files,\n ...(scaffoldedFramework ? { scaffolded_framework: true } : {}),\n ...(typeof devPort === \"number\" ? { dev_port: devPort } : {}),\n ...(posture === undefined ? {} : { posture }),\n });\n}\n\n/** Normalizes an absolute or relative path to a cwd-relative posix key. */\nfunction relativePosix(cwd: string, path: string): string {\n const rel = isAbsolute(path) ? relative(cwd, path) : path;\n return rel.split(sep).join(\"/\");\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAgB,iBAAiB,UAA0B;AACzD,QAAO,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,MAAM;;;;;;;;;;AAW5D,eAAsB,qBAAqB,KAAoD;AAC7F,KAAI;EAEF,MAAM,YAAW,MADG,UAAU,IAAI,EACX;AAGvB,MAAI,CAAC,SAAS,SAAS,CACrB;AAEF,MAAI,CAAC,SAAS,SAAS,MAAM,CAC3B;AAEF,OAAK,MAAM,SAAS,OAAO,OAAO,SAAS,MAAM,EAAE;AACjD,OAAI,CAAC,SAAS,MAAM,CAClB;AAEF,OAAI,OAAO,MAAM,SAAS,SACxB;AAEF,OAAI,MAAM,UAAU,oBAAoB,MAAM,UAAU,eACtD;;AAMJ,MACE,SAAS,YAAY,KAAA,KACrB,SAAS,YAAY,UACrB,SAAS,YAAY,SAErB;AAEF,SAAO;SACD;AACN;;;;;;;;;;;;AAaJ,eAAsB,sBAAsB,SAQ1B;CAChB,MAAM,EAAE,KAAK,SAAS,SAAS,qBAAqB,SAAS,YAAY;CACzE,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,SAAS,cAAc,KAAK,KAAK,CAAC,CAAC;CACxE,MAAM,QAAmC,EAAE;AAC3C,MAAK,MAAM,QAAQ,QAAQ,aAAa;EACtC,MAAM,MAAM,cAAc,KAAK,KAAK;AACpC,MAAI,CAAC,QAAQ,IAAI,IAAI,CACnB;EAEF,IAAI;AACJ,MAAI;AACF,cAAW,MAAM,SAAS,KAAK,KAAK,IAAI,EAAE,OAAO;UAC3C;AACN;;AAEF,QAAM,OAAO;GACX,MAAM,iBAAiB,SAAS;GAChC,OAAO,QAAQ,cAAc,SAAS;GACvC;;AAEH,OAAM,eAAe,KAAK;EACxB;EACA,GAAI,sBAAsB,EAAE,sBAAsB,MAAM,GAAG,EAAE;EAC7D,GAAI,OAAO,YAAY,WAAW,EAAE,UAAU,SAAS,GAAG,EAAE;EAC5D,GAAI,YAAY,KAAA,IAAY,EAAE,GAAG,EAAE,SAAS;EAC7C,CAAC;;;AAIJ,SAAS,cAAc,KAAa,MAAsB;AAExD,SADY,WAAW,KAAK,GAAG,SAAS,KAAK,KAAK,GAAG,MAC1C,MAAM,IAAI,CAAC,KAAK,IAAI"}