@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/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Scaffolds Zitadel auth (login, register, profile, middleware) into a Next.js app.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx @zitadel/cli@latest
|
|
6
|
+
npx @zitadel/cli@latest start
|
|
7
|
+
npx @zitadel/cli@latest setup --framework next --server local
|
|
7
8
|
```
|
|
8
9
|
|
|
9
10
|
> **Beta.** This is the **next-generation Zitadel**, a ground-up rewrite of the platform. It is distinct from the established Zitadel at [github.com/zitadel/zitadel](https://github.com/zitadel/zitadel). APIs and CLI flags will change.
|
|
@@ -11,21 +12,29 @@ npx @zitadel/cli@latest setup --framework next --server <your-zitadel-server>
|
|
|
11
12
|
## Requirements
|
|
12
13
|
|
|
13
14
|
- Node 20+
|
|
15
|
+
- Docker for the managed local Zitadel runtime
|
|
14
16
|
- A Next.js project created with `create-next-app`
|
|
15
|
-
- A running next-generation Zitadel server to point at:
|
|
16
|
-
- **Zitadel Cloud** — coming soon
|
|
17
|
-
- **Self-hosted** — grab a binary from [github.com/zitadel/nextgen/releases](https://github.com/zitadel/nextgen/releases) and run it locally
|
|
18
17
|
|
|
19
18
|
## Quickstart
|
|
20
19
|
|
|
21
20
|
```sh
|
|
22
21
|
npx create-next-app@latest my-app
|
|
23
22
|
cd my-app
|
|
24
|
-
npx @zitadel/cli@latest
|
|
23
|
+
npx @zitadel/cli@latest doctor
|
|
24
|
+
npx @zitadel/cli@latest start
|
|
25
|
+
npx @zitadel/cli@latest setup --framework next --server local
|
|
26
|
+
npm install
|
|
25
27
|
npm run dev
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
`
|
|
30
|
+
`start` runs a Docker-backed local Zitadel server and stores runtime data
|
|
31
|
+
under `.zitadel/local/`. It uses `ghcr.io/zitadel/nextgen:latest` unless
|
|
32
|
+
overridden with `--image` or `ZITADEL_LOCAL_IMAGE`. `setup --server local`
|
|
33
|
+
creates a project on that local server, scaffolds `app/login`, `app/register`, and
|
|
34
|
+
`middleware.ts`, and writes `.env.local` and `.zitadel/`. The project's default
|
|
35
|
+
user schema and login flow are provisioned server-side at creation time, so the
|
|
36
|
+
CLI does not scaffold or upload them. Open `http://localhost:3000/login` to see
|
|
37
|
+
the login page.
|
|
29
38
|
|
|
30
39
|
The default project flow supports password registration/login, passkey
|
|
31
40
|
registration/login, and optional passkey setup after password registration.
|
|
@@ -34,9 +43,10 @@ passkey can sign in with either credential.
|
|
|
34
43
|
|
|
35
44
|
## Other commands
|
|
36
45
|
|
|
37
|
-
- `zitadel doctor` — verify the
|
|
38
|
-
- `zitadel status` — summarise the local project
|
|
46
|
+
- `zitadel doctor` — verify the local Docker runtime and generated project files
|
|
47
|
+
- `zitadel status` — summarise the local Docker runtime and project
|
|
39
48
|
- `zitadel eject` — remove what setup wrote (alias: `zitadel uninstall`)
|
|
49
|
+
- `zitadel start|stop|logs|reset` — manage the local Docker runtime
|
|
40
50
|
|
|
41
51
|
## Reference
|
|
42
52
|
|
|
@@ -49,9 +59,13 @@ passkey can sign in with either credential.
|
|
|
49
59
|
* [`zitadel doctor`](#zitadel-doctor)
|
|
50
60
|
* [`zitadel eject`](#zitadel-eject)
|
|
51
61
|
* [`zitadel help [COMMAND]`](#zitadel-help-command)
|
|
62
|
+
* [`zitadel logs`](#zitadel-logs)
|
|
63
|
+
* [`zitadel reset`](#zitadel-reset)
|
|
52
64
|
* [`zitadel search`](#zitadel-search)
|
|
53
65
|
* [`zitadel setup`](#zitadel-setup)
|
|
66
|
+
* [`zitadel start`](#zitadel-start)
|
|
54
67
|
* [`zitadel status`](#zitadel-status)
|
|
68
|
+
* [`zitadel stop`](#zitadel-stop)
|
|
55
69
|
* [`zitadel uninstall`](#zitadel-uninstall)
|
|
56
70
|
* [`zitadel version`](#zitadel-version)
|
|
57
71
|
* [`zitadel which`](#zitadel-which)
|
|
@@ -118,11 +132,12 @@ _See code: [@oclif/plugin-commands](https://github.com/oclif/plugin-commands/blo
|
|
|
118
132
|
|
|
119
133
|
## `zitadel doctor`
|
|
120
134
|
|
|
121
|
-
Verify
|
|
135
|
+
Verify local runtime and project state.
|
|
122
136
|
|
|
123
137
|
```
|
|
124
138
|
USAGE
|
|
125
139
|
$ zitadel doctor [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug] [--fix]
|
|
140
|
+
[--image <value>] [--port <value>]
|
|
126
141
|
|
|
127
142
|
FLAGS
|
|
128
143
|
-c, --cwd=<value> Project directory to operate on.
|
|
@@ -132,13 +147,15 @@ FLAGS
|
|
|
132
147
|
--debug Debug logging.
|
|
133
148
|
--dry-run Preview without mutating files or the platform.
|
|
134
149
|
--fix Re-apply missing managed files.
|
|
150
|
+
--image=<value> Container image to check.
|
|
151
|
+
--port=<value> [default: 8080] Local HTTP port.
|
|
135
152
|
--verbose Verbose logging.
|
|
136
153
|
|
|
137
154
|
GLOBAL FLAGS
|
|
138
155
|
--json Format output as json.
|
|
139
156
|
|
|
140
157
|
DESCRIPTION
|
|
141
|
-
Verify
|
|
158
|
+
Verify local runtime and project state.
|
|
142
159
|
```
|
|
143
160
|
|
|
144
161
|
## `zitadel eject`
|
|
@@ -188,6 +205,57 @@ DESCRIPTION
|
|
|
188
205
|
|
|
189
206
|
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.49/src/commands/help.ts)_
|
|
190
207
|
|
|
208
|
+
## `zitadel logs`
|
|
209
|
+
|
|
210
|
+
Show local Zitadel server logs.
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
USAGE
|
|
214
|
+
$ zitadel logs [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug] [--follow]
|
|
215
|
+
[--tail <value>]
|
|
216
|
+
|
|
217
|
+
FLAGS
|
|
218
|
+
-c, --cwd=<value> Project directory to operate on.
|
|
219
|
+
-f, --force Overwrite protected files on conflict.
|
|
220
|
+
-n, --non-interactive Disable prompts. Required when scripting or running as an agent.
|
|
221
|
+
-s, --server=<value> Override the resolved server URL.
|
|
222
|
+
--debug Debug logging.
|
|
223
|
+
--dry-run Preview without mutating files or the platform.
|
|
224
|
+
--follow Follow logs.
|
|
225
|
+
--tail=<value> [default: 200] Number of lines to show.
|
|
226
|
+
--verbose Verbose logging.
|
|
227
|
+
|
|
228
|
+
GLOBAL FLAGS
|
|
229
|
+
--json Format output as json.
|
|
230
|
+
|
|
231
|
+
DESCRIPTION
|
|
232
|
+
Show local Zitadel server logs.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## `zitadel reset`
|
|
236
|
+
|
|
237
|
+
Delete the local Zitadel server runtime and data.
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
USAGE
|
|
241
|
+
$ zitadel reset [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug]
|
|
242
|
+
|
|
243
|
+
FLAGS
|
|
244
|
+
-c, --cwd=<value> Project directory to operate on.
|
|
245
|
+
-f, --force Overwrite protected files on conflict.
|
|
246
|
+
-n, --non-interactive Disable prompts. Required when scripting or running as an agent.
|
|
247
|
+
-s, --server=<value> Override the resolved server URL.
|
|
248
|
+
--debug Debug logging.
|
|
249
|
+
--dry-run Preview without mutating files or the platform.
|
|
250
|
+
--verbose Verbose logging.
|
|
251
|
+
|
|
252
|
+
GLOBAL FLAGS
|
|
253
|
+
--json Format output as json.
|
|
254
|
+
|
|
255
|
+
DESCRIPTION
|
|
256
|
+
Delete the local Zitadel server runtime and data.
|
|
257
|
+
```
|
|
258
|
+
|
|
191
259
|
## `zitadel search`
|
|
192
260
|
|
|
193
261
|
Search for a command.
|
|
@@ -236,9 +304,36 @@ EXAMPLES
|
|
|
236
304
|
$ zitadel setup --framework next
|
|
237
305
|
```
|
|
238
306
|
|
|
307
|
+
## `zitadel start`
|
|
308
|
+
|
|
309
|
+
Start a local Zitadel server.
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
USAGE
|
|
313
|
+
$ zitadel start [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug] [--image
|
|
314
|
+
<value>] [--port <value>]
|
|
315
|
+
|
|
316
|
+
FLAGS
|
|
317
|
+
-c, --cwd=<value> Project directory to operate on.
|
|
318
|
+
-f, --force Overwrite protected files on conflict.
|
|
319
|
+
-n, --non-interactive Disable prompts. Required when scripting or running as an agent.
|
|
320
|
+
-s, --server=<value> Override the resolved server URL.
|
|
321
|
+
--debug Debug logging.
|
|
322
|
+
--dry-run Preview without mutating files or the platform.
|
|
323
|
+
--image=<value> Container image to run.
|
|
324
|
+
--port=<value> [default: 8080] Local HTTP port.
|
|
325
|
+
--verbose Verbose logging.
|
|
326
|
+
|
|
327
|
+
GLOBAL FLAGS
|
|
328
|
+
--json Format output as json.
|
|
329
|
+
|
|
330
|
+
DESCRIPTION
|
|
331
|
+
Start a local Zitadel server.
|
|
332
|
+
```
|
|
333
|
+
|
|
239
334
|
## `zitadel status`
|
|
240
335
|
|
|
241
|
-
Summarize the local project state.
|
|
336
|
+
Summarize the local Zitadel server and project state.
|
|
242
337
|
|
|
243
338
|
```
|
|
244
339
|
USAGE
|
|
@@ -257,7 +352,31 @@ GLOBAL FLAGS
|
|
|
257
352
|
--json Format output as json.
|
|
258
353
|
|
|
259
354
|
DESCRIPTION
|
|
260
|
-
Summarize the local project state.
|
|
355
|
+
Summarize the local Zitadel server and project state.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## `zitadel stop`
|
|
359
|
+
|
|
360
|
+
Stop the local Zitadel server.
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
USAGE
|
|
364
|
+
$ zitadel stop [--json] [-c <value>] [-s <value>] [-n] [-f] [--dry-run] [--verbose] [--debug]
|
|
365
|
+
|
|
366
|
+
FLAGS
|
|
367
|
+
-c, --cwd=<value> Project directory to operate on.
|
|
368
|
+
-f, --force Overwrite protected files on conflict.
|
|
369
|
+
-n, --non-interactive Disable prompts. Required when scripting or running as an agent.
|
|
370
|
+
-s, --server=<value> Override the resolved server URL.
|
|
371
|
+
--debug Debug logging.
|
|
372
|
+
--dry-run Preview without mutating files or the platform.
|
|
373
|
+
--verbose Verbose logging.
|
|
374
|
+
|
|
375
|
+
GLOBAL FLAGS
|
|
376
|
+
--json Format output as json.
|
|
377
|
+
|
|
378
|
+
DESCRIPTION
|
|
379
|
+
Stop the local Zitadel server.
|
|
261
380
|
```
|
|
262
381
|
|
|
263
382
|
## `zitadel uninstall`
|
package/SKILLS.md
CHANGED
|
@@ -38,6 +38,8 @@ Each invocation prints one JSON object:
|
|
|
38
38
|
- On failure: `code` (e.g. `E_VALIDATION`, `E_NETWORK`, `E_CONFLICT`) and
|
|
39
39
|
`message`.
|
|
40
40
|
- `next_commands`: the suggested follow-ups. Prefer these over free-text hints.
|
|
41
|
+
- `E_LOCAL_SERVER_NOT_RUNNING`: start the local Docker runtime with
|
|
42
|
+
`zitadel start`, then retry with `--server local`.
|
|
41
43
|
|
|
42
44
|
Exit codes mirror the error class (3 = validation, 4 = network, 5 = conflict,
|
|
43
45
|
1 = auth, 2 = not-implemented). An unknown command is handled by the CLI's help
|
|
@@ -51,16 +53,26 @@ layer, not the envelope.
|
|
|
51
53
|
scaffolds nor uploads them. Flags: `--framework`, `--renderer`.
|
|
52
54
|
- `plan` — validate config and preview the sync diff without mutating anything.
|
|
53
55
|
- `apply` — validate and upload repo config to the platform.
|
|
54
|
-
- `doctor` — verify
|
|
56
|
+
- `doctor` — verify local Docker runtime prerequisites and, once `zitadel.json`
|
|
57
|
+
exists, generated app files and local state; `--fix` re-applies missing
|
|
55
58
|
managed files.
|
|
56
|
-
- `status` — summarize the local project state.
|
|
59
|
+
- `status` — summarize the local Docker runtime and project state.
|
|
57
60
|
- `eject` (alias `uninstall`) — remove managed files and local Zitadel state;
|
|
58
61
|
requires `--force` when non-interactive.
|
|
62
|
+
- `start` — start the managed local Zitadel container and persist runtime
|
|
63
|
+
metadata under `.zitadel/local/runtime.json`.
|
|
64
|
+
- `stop` — stop/remove the managed container while preserving
|
|
65
|
+
`.zitadel/local/nextgen-data`.
|
|
66
|
+
- `logs` — print managed container logs; `--follow` streams in human mode.
|
|
67
|
+
- `reset` — stop/remove the managed container and delete local runtime data;
|
|
68
|
+
requires `--force` when non-interactive.
|
|
59
69
|
|
|
60
70
|
## Golden path
|
|
61
71
|
|
|
62
72
|
```sh
|
|
63
|
-
npx @zitadel/cli@latest
|
|
73
|
+
npx @zitadel/cli@latest doctor --non-interactive --json
|
|
74
|
+
npx @zitadel/cli@latest start --non-interactive --json
|
|
75
|
+
npx @zitadel/cli@latest setup --framework next --server local --non-interactive --json
|
|
64
76
|
npx @zitadel/cli@latest doctor --non-interactive --json
|
|
65
77
|
npx @zitadel/cli@latest plan --non-interactive --json
|
|
66
78
|
npx @zitadel/cli@latest apply --non-interactive --json
|
|
@@ -69,3 +81,6 @@ npx @zitadel/cli@latest apply --non-interactive --json
|
|
|
69
81
|
Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
|
|
70
82
|
then re-run `plan` and `apply`. Managed files carry a marker comment; `eject`
|
|
71
83
|
removes only files that still carry it, preserving anything the user replaced.
|
|
84
|
+
For app-local development, `--server local` resolves through
|
|
85
|
+
`.zitadel/local/runtime.json` and requires a healthy `zitadel start`
|
|
86
|
+
runtime.
|
package/dist/commands/apply.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, i as runSyncLoop, 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":"apply.mjs","names":[],"sources":["../../src/commands/apply.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, runSyncLoop, summarizePlan } from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\n\n/**\n * `zitadel apply` — validate and upload repo config to the platform.\n *\n * Runs the sync loop to convergence, or (with `--dry-run`) previews the diff\n * without mutating. All validation — structural shape and `${VAR}` / `*_env`\n * reference presence — happens inside the sync engine ({@link buildSyncPlan}),\n * so an invalid or under-configured file fails with `E_VALIDATION` before any\n * platform call.\n */\nexport default class Apply extends BaseCommand {\n static override description = \"Validate and upload repo config to the platform.\";\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(Apply);\n await this.toMeta(flags);\n const { cwd, source, env, dryRun, 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 if (!dryRun) {\n consola.start(\"Syncing schemas and flows to Zitadel\");\n await runSyncLoop(cwd, syncers);\n consola.success(\"Sync complete\");\n return this.emit({ status: \"ok\", data: { synced: true } });\n }\n\n consola.start(\"Building plan (dry run)\");\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 );\n return this.emit({\n status: \"ok\",\n data: summary,\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"apply.mjs","names":[],"sources":["../../src/commands/apply.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, runSyncLoop, summarizePlan } from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\n\n/**\n * `zitadel apply` — validate and upload repo config to the platform.\n *\n * Runs the sync loop to convergence, or (with `--dry-run`) previews the diff\n * without mutating. All validation — structural shape and `${VAR}` / `*_env`\n * reference presence — happens inside the sync engine ({@link buildSyncPlan}),\n * so an invalid or under-configured file fails with `E_VALIDATION` before any\n * platform call.\n */\nexport default class Apply extends BaseCommand {\n static override description = \"Validate and upload repo config to the platform.\";\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(Apply);\n await this.toMeta(flags);\n const { cwd, source, env, dryRun, 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 if (!dryRun) {\n consola.start(\"Syncing schemas and flows to Zitadel\");\n await runSyncLoop(cwd, syncers);\n consola.success(\"Sync complete\");\n return this.emit({ status: \"ok\", data: { synced: true } });\n }\n\n consola.start(\"Building plan (dry run)\");\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 );\n return this.emit({\n status: \"ok\",\n data: summary,\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAqB,QAArB,MAAqB,cAAc,YAAY;CAC7C,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,MAAM;AACzC,QAAM,KAAK,OAAO,MAAM;EACxB,MAAM,EAAE,KAAK,QAAQ,KAAK,QAAQ,UAAU,KAAK;EAEjD,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,MAAI,CAAC,QAAQ;AACX,aAAQ,MAAM,uCAAuC;AACrD,SAAM,YAAY,KAAK,QAAQ;AAC/B,aAAQ,QAAQ,gBAAgB;AAChC,UAAO,KAAK,KAAK;IAAE,QAAQ;IAAM,MAAM,EAAE,QAAQ,MAAM;IAAE,CAAC;;AAG5D,YAAQ,MAAM,0BAA0B;EACxC,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,MAC5D;AACD,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;GACN,QAAQ,WAAW,MAAM,MAAM;GAChC,CAAC"}
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as issuerFromPort, t as createOrca } from "../orca-
|
|
1
|
+
import { S as isObject, T as ZitadelError, _ as readRuntimeMetadata, g as localServerUrl, h as localRuntimePaths, l as assertWritableDirectory, p as isPortAvailable, s as DEFAULT_LOCAL_SERVER_PORT, t as BaseCommand, u as checkLocalServerHealth } from "../oclif-DSPO9Sck.mjs";
|
|
2
|
+
import { r as issuerFromPort, t as createOrca } from "../orca-DOxshV9n.mjs";
|
|
3
|
+
import { a as readZitadelConfig, i as readRendererId, o as readZitadelSecret, r as readDevelopmentIssuer, t as hasZitadelConfig } from "../project-Dwb9WVAT.mjs";
|
|
4
|
+
import { o as imageAvailable, r as dockerAvailable } from "../docker-C0aVpJqm.mjs";
|
|
3
5
|
import { Flags } from "@oclif/core";
|
|
4
6
|
import consola from "consola";
|
|
5
7
|
import { chmod, readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -148,7 +150,7 @@ var FrameworkCheck = class extends AbstractSanityCheck {
|
|
|
148
150
|
* SDK package via `patcher.repair`. The user schema and flow definition are
|
|
149
151
|
* server-owned and no longer scaffolded locally, so nothing here reads them.
|
|
150
152
|
*/
|
|
151
|
-
async function loadPatchContext(cwd, orca) {
|
|
153
|
+
async function loadPatchContext(cwd, orca, cliVersion) {
|
|
152
154
|
const config = await readZitadelConfig(cwd);
|
|
153
155
|
const secret = await readZitadelSecret(cwd);
|
|
154
156
|
const framework = await orca.detect(cwd);
|
|
@@ -157,6 +159,7 @@ async function loadPatchContext(cwd, orca) {
|
|
|
157
159
|
rendererId: readRendererId(config),
|
|
158
160
|
issuer: await resolveIssuer(cwd, config, framework),
|
|
159
161
|
server: typeof config.server === "string" ? config.server : "",
|
|
162
|
+
cliVersion,
|
|
160
163
|
project: {
|
|
161
164
|
id: secret.project_id,
|
|
162
165
|
projectSecret: secret.project_secret,
|
|
@@ -206,7 +209,7 @@ var DependencyCheck = class extends AbstractSanityCheck {
|
|
|
206
209
|
* so going through `repair` is the only sanctioned path.
|
|
207
210
|
*/
|
|
208
211
|
async fix(ctx) {
|
|
209
|
-
const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca);
|
|
212
|
+
const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca, ctx.cliVersion);
|
|
210
213
|
await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {
|
|
211
214
|
cwd: ctx.cwd,
|
|
212
215
|
dryRun: ctx.dryRun,
|
|
@@ -243,6 +246,13 @@ const SANITY_CHECKS = [
|
|
|
243
246
|
];
|
|
244
247
|
//#endregion
|
|
245
248
|
//#region src/commands/doctor/index.ts
|
|
249
|
+
const LOCAL_RUNTIME_CHECK_NAMES = new Set([
|
|
250
|
+
"docker-cli",
|
|
251
|
+
"image",
|
|
252
|
+
"state-dir",
|
|
253
|
+
"port",
|
|
254
|
+
"runtime"
|
|
255
|
+
]);
|
|
246
256
|
/**
|
|
247
257
|
* `zitadel doctor` — verify generated files and local state.
|
|
248
258
|
*
|
|
@@ -257,18 +267,33 @@ const SANITY_CHECKS = [
|
|
|
257
267
|
* whatever remains broken.
|
|
258
268
|
*/
|
|
259
269
|
var Doctor = class Doctor extends BaseCommand {
|
|
260
|
-
static description = "Verify
|
|
261
|
-
static flags = {
|
|
270
|
+
static description = "Verify local runtime and project state.";
|
|
271
|
+
static flags = {
|
|
272
|
+
fix: Flags.boolean({ description: "Re-apply missing managed files." }),
|
|
273
|
+
image: Flags.string({ description: "Container image to check." }),
|
|
274
|
+
port: Flags.integer({
|
|
275
|
+
description: "Local HTTP port.",
|
|
276
|
+
default: DEFAULT_LOCAL_SERVER_PORT
|
|
277
|
+
})
|
|
278
|
+
};
|
|
262
279
|
async run() {
|
|
263
280
|
const { flags } = await this.parse(Doctor);
|
|
264
|
-
|
|
281
|
+
const port = flags.port ?? 8080;
|
|
282
|
+
await this.toMeta(flags, {
|
|
283
|
+
resolveServer: false,
|
|
284
|
+
source: localServerUrl(port)
|
|
285
|
+
});
|
|
265
286
|
const { cwd, dryRun } = this.meta;
|
|
287
|
+
const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? "ghcr.io/zitadel/nextgen:latest";
|
|
288
|
+
const runtimeChecks = await runLocalRuntimeChecks(cwd, image, port);
|
|
289
|
+
const hasConfig = await hasZitadelConfig(cwd);
|
|
266
290
|
const ctx = {
|
|
267
291
|
cwd,
|
|
268
292
|
orca: createOrca(),
|
|
293
|
+
cliVersion: this.meta.cliVersion,
|
|
269
294
|
dryRun
|
|
270
295
|
};
|
|
271
|
-
if (flags.fix) {
|
|
296
|
+
if (hasConfig && flags.fix) {
|
|
272
297
|
const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));
|
|
273
298
|
for (const [index, check] of SANITY_CHECKS.entries()) {
|
|
274
299
|
if (before[index]?.status !== "fail") continue;
|
|
@@ -279,23 +304,108 @@ var Doctor = class Doctor extends BaseCommand {
|
|
|
279
304
|
}
|
|
280
305
|
}
|
|
281
306
|
}
|
|
282
|
-
const
|
|
307
|
+
const projectChecks = hasConfig ? await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx))) : [];
|
|
308
|
+
const checks = [...runtimeChecks, ...projectChecks];
|
|
283
309
|
const failed = checks.filter((check) => check.status === "fail");
|
|
284
310
|
const data = {
|
|
285
311
|
title: failed.length === 0 ? "Zitadel doctor passed." : "Zitadel doctor found issues.",
|
|
286
312
|
ok: failed.length === 0,
|
|
313
|
+
image,
|
|
314
|
+
port,
|
|
315
|
+
project: { lifecycle: hasConfig ? "configured" : "not-configured" },
|
|
287
316
|
checks
|
|
288
317
|
};
|
|
289
|
-
if (failed.length > 0)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
318
|
+
if (failed.length > 0) {
|
|
319
|
+
const advice = failureAdvice(failed, image, port);
|
|
320
|
+
throw new ZitadelError("E_VALIDATION", "Zitadel doctor found issues", {
|
|
321
|
+
hint: advice.hint,
|
|
322
|
+
nextCommands: advice.nextCommands,
|
|
323
|
+
details: data
|
|
324
|
+
});
|
|
325
|
+
}
|
|
293
326
|
return this.emit({
|
|
294
327
|
status: "ok",
|
|
295
328
|
data
|
|
296
329
|
});
|
|
297
330
|
}
|
|
298
331
|
};
|
|
332
|
+
function failureAdvice(failed, image, port) {
|
|
333
|
+
const failedNames = new Set(failed.map((check) => check.name));
|
|
334
|
+
if (failedNames.has("docker-cli")) return {
|
|
335
|
+
hint: "Docker is required for `zitadel start`, but the Docker daemon is not reachable. Install or start Docker, then rerun `zitadel doctor`.",
|
|
336
|
+
nextCommands: ["docker version", "zitadel doctor"]
|
|
337
|
+
};
|
|
338
|
+
if (failedNames.has("image")) return {
|
|
339
|
+
hint: "The local Zitadel image is not available. Check Docker registry access, build it locally, or pass --image / ZITADEL_LOCAL_IMAGE.",
|
|
340
|
+
nextCommands: [`docker pull ${image}`, "zitadel doctor"]
|
|
341
|
+
};
|
|
342
|
+
if (failedNames.has("state-dir")) return {
|
|
343
|
+
hint: "The local Zitadel state directory is not writable. Fix directory permissions, then rerun `zitadel doctor`.",
|
|
344
|
+
nextCommands: ["zitadel doctor"]
|
|
345
|
+
};
|
|
346
|
+
if (failedNames.has("port")) {
|
|
347
|
+
const fallbackPort = port === 8080 ? port + 1 : DEFAULT_LOCAL_SERVER_PORT;
|
|
348
|
+
return {
|
|
349
|
+
hint: `Port ${String(port)} is already in use. Stop the process using it, or choose another port for local Zitadel.`,
|
|
350
|
+
nextCommands: [`zitadel doctor --port ${String(fallbackPort)}`]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (failedNames.has("runtime")) return {
|
|
354
|
+
hint: "Existing local runtime metadata was found, but the local Zitadel server is not healthy. Start it again or reset stale local data.",
|
|
355
|
+
nextCommands: ["zitadel start", "zitadel reset --force"]
|
|
356
|
+
};
|
|
357
|
+
if (failed.some((check) => !LOCAL_RUNTIME_CHECK_NAMES.has(check.name))) return {
|
|
358
|
+
hint: "Run `npx @zitadel/cli@alpha doctor --fix` to re-apply missing managed files.",
|
|
359
|
+
nextCommands: ["zitadel doctor --fix"]
|
|
360
|
+
};
|
|
361
|
+
return {
|
|
362
|
+
hint: "Fix the reported checks, then rerun `zitadel doctor`.",
|
|
363
|
+
nextCommands: ["zitadel doctor"]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function runLocalRuntimeChecks(cwd, image, port) {
|
|
367
|
+
const runtime = await readRuntimeMetadata(cwd);
|
|
368
|
+
return [
|
|
369
|
+
await check("docker-cli", "Docker is reachable", async () => {
|
|
370
|
+
const result = await dockerAvailable();
|
|
371
|
+
if (result.status !== 0) throw new Error(result.stderr || "docker version failed");
|
|
372
|
+
return `Docker engine ${result.stdout.trim() || "available"}`;
|
|
373
|
+
}),
|
|
374
|
+
await check("image", `Image ${image} is available`, async () => {
|
|
375
|
+
return await imageAvailable(image) === "local" ? `Image ${image} is available locally` : `Image ${image} is available from the registry`;
|
|
376
|
+
}),
|
|
377
|
+
await check("state-dir", "Local state directory is writable", async () => {
|
|
378
|
+
const paths = localRuntimePaths(cwd);
|
|
379
|
+
await assertWritableDirectory(paths.dataDir);
|
|
380
|
+
return `${paths.dataDir} is writable`;
|
|
381
|
+
}),
|
|
382
|
+
await check("port", `Port ${String(port)} is available`, async () => {
|
|
383
|
+
if (runtime && await checkLocalServerHealth(runtime.server_url)) return `${runtime.server_url} is already healthy`;
|
|
384
|
+
if (!await isPortAvailable(port)) throw new Error(`Port ${String(port)} is already in use`);
|
|
385
|
+
return `Port ${String(port)} is available`;
|
|
386
|
+
}),
|
|
387
|
+
await check("runtime", "Existing local runtime is healthy", async () => {
|
|
388
|
+
if (!runtime) return "No existing runtime metadata";
|
|
389
|
+
if (!await checkLocalServerHealth(runtime.server_url)) throw new Error(`${runtime.server_url} did not respond to /healthz`);
|
|
390
|
+
return `${runtime.server_url} is healthy`;
|
|
391
|
+
})
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
async function check(name, fallback, run) {
|
|
395
|
+
try {
|
|
396
|
+
return {
|
|
397
|
+
name,
|
|
398
|
+
status: "pass",
|
|
399
|
+
message: await run()
|
|
400
|
+
};
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return {
|
|
403
|
+
name,
|
|
404
|
+
status: "fail",
|
|
405
|
+
message: error instanceof Error ? error.message : fallback
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
299
409
|
//#endregion
|
|
300
410
|
export { Doctor as default };
|
|
301
411
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.mjs","names":[],"sources":["../../src/commands/doctor/checks/types.ts","../../src/commands/doctor/checks/config.ts","../../src/commands/doctor/checks/secret.ts","../../src/commands/doctor/checks/secret-permissions.ts","../../src/commands/doctor/checks/gitignore.ts","../../src/commands/doctor/checks/env-example.ts","../../src/commands/doctor/checks/framework.ts","../../src/commands/doctor/patch-context.ts","../../src/commands/doctor/checks/dependency.ts","../../src/commands/doctor/checks/project-match.ts","../../src/commands/doctor/checks/index.ts","../../src/commands/doctor/index.ts"],"sourcesContent":["import type { Orca } from \"../../../lib/orca\";\n\n/** Pass/fail outcome of a single {@link SanityCheck}. */\nexport type CheckOutcome = {\n name: string;\n status: \"pass\" | \"fail\";\n message: string;\n path?: string;\n};\n\n/** Everything a check needs to inspect or repair a project. */\nexport type CheckContext = {\n readonly cwd: string;\n readonly orca: Orca;\n /** When true, {@link SanityCheck.fix} must preview without writing. */\n readonly dryRun: boolean;\n};\n\n/**\n * One diagnostic the `doctor` command runs. Each concrete check is a small\n * standalone class that both verifies its concern ({@link run}) and knows how\n * to repair it ({@link fix}); the command executes every registered check,\n * aggregates the {@link CheckOutcome}s, and (under `--fix`) repairs the ones\n * that failed.\n */\nexport interface SanityCheck {\n /** Stable identifier surfaced in logs and the JSON envelope. */\n readonly name: string;\n run(ctx: CheckContext): Promise<CheckOutcome>;\n /** Repair what this check verifies. A no-op when there is no safe auto-fix. */\n fix(ctx: CheckContext): Promise<void>;\n}\n\n/**\n * Base class for checks: subclasses declare `name`, `path`, and a success\n * `summary`, and implement the single {@link verify} method that throws on\n * failure. {@link run} wraps it so a thrown error becomes a `fail` outcome\n * carrying the error message, and success becomes a `pass` with `summary`.\n *\n * {@link fix} defaults to a no-op: checks whose failure has no safe automatic\n * remedy (a missing secret, an invalid user schema) simply do not override it.\n */\nexport abstract class AbstractSanityCheck implements SanityCheck {\n abstract readonly name: string;\n abstract readonly path: string;\n protected abstract readonly summary: string;\n\n /** Throw to signal failure; the thrown message is surfaced to the user. */\n protected abstract verify(ctx: CheckContext): Promise<void>;\n\n async run(ctx: CheckContext): Promise<CheckOutcome> {\n try {\n await this.verify(ctx);\n return { name: this.name, status: \"pass\", message: this.summary, path: this.path };\n } catch (error) {\n return {\n name: this.name,\n status: \"fail\",\n message: error instanceof Error ? error.message : String(error),\n path: this.path,\n };\n }\n }\n\n async fix(_ctx: CheckContext): Promise<void> {\n return;\n }\n}\n","import { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `zitadel.json` exists and parses. */\nexport class ConfigCheck extends AbstractSanityCheck {\n readonly name = \"config\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"zitadel.json parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelConfig(ctx.cwd);\n }\n}\n","import { readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` exists and parses. */\nexport class SecretCheck extends AbstractSanityCheck {\n readonly name = \"secret\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelSecret(ctx.cwd);\n }\n}\n","import { chmod, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` is locked down to `0600`, and re-locks it on fix. */\nexport class SecretPermissionsCheck extends AbstractSanityCheck {\n readonly name = \"secret-permissions\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret has 0600 permissions\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const mode = (await stat(join(ctx.cwd, \".zitadel/secret\"))).mode & 0o777;\n if (mode !== 0o600) {\n throw new Error(`expected 0600, got ${mode.toString(8)}`);\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n if (ctx.dryRun) {\n return;\n }\n await chmod(join(ctx.cwd, \".zitadel/secret\"), 0o600);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The entries `.gitignore` must carry to keep secrets and env files untracked. */\nconst REQUIRED_ENTRIES = [\".zitadel/secret\", \".env*\", \"!.env.example\"];\n\n/** Verifies `.gitignore` excludes the local secret and env files; appends any missing. */\nexport class GitignoreCheck extends AbstractSanityCheck {\n readonly name = \"gitignore\";\n readonly path = \".gitignore\";\n protected readonly summary = \".gitignore protects local secret/env files\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const lines = (await readFile(join(ctx.cwd, \".gitignore\"), \"utf8\")).split(/\\r?\\n/g);\n for (const entry of REQUIRED_ENTRIES) {\n if (!lines.includes(entry)) {\n throw new Error(`missing ${entry}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".gitignore\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const lines = existing.split(/\\r?\\n/g);\n const missing = REQUIRED_ENTRIES.filter((entry) => !lines.includes(entry));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.join(\"\\n\")}\\n`);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The keys `.env.example` must document for a Zitadel-managed project. */\nconst REQUIRED_KEYS = [\"ZITADEL_PROJECT_ID\", \"ZITADEL_ENVIRONMENT\", \"ZITADEL_ISSUER\"];\n\n/** Verifies `.env.example` documents the required Zitadel keys; appends any missing. */\nexport class EnvExampleCheck extends AbstractSanityCheck {\n readonly name = \"env-example\";\n readonly path = \".env.example\";\n protected readonly summary = \".env.example references required keys\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const contents = await readFile(join(ctx.cwd, \".env.example\"), \"utf8\");\n for (const key of REQUIRED_KEYS) {\n if (!contents.includes(`${key}=`)) {\n throw new Error(`missing ${key}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".env.example\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const missing = REQUIRED_KEYS.filter((key) => !existing.includes(`${key}=`));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.map((key) => `${key}=`).join(\"\\n\")}\\n`);\n }\n}\n","import { isObject } from \"../../../lib/json\";\nimport { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies the framework detected on disk matches the one recorded in config. */\nexport class FrameworkCheck extends AbstractSanityCheck {\n readonly name = \"framework\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"Detected framework matches recorded framework\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const detected = await ctx.orca.detect(ctx.cwd);\n const recorded = isObject(config.framework) ? config.framework.id : undefined;\n if (recorded !== detected.id) {\n throw new Error(`expected ${String(recorded)}, detected ${detected.id}`);\n }\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { issuerFromPort, type FrameworkFacts, type Orca } from \"../../lib/orca\";\nimport type { PatchContext } from \"../../lib/orca/patchers/types\";\nimport { readDevelopmentIssuer, readRendererId, readZitadelConfig, readZitadelSecret } from \"../../lib/project\";\n\n/**\n * Reconstructs a {@link PatchContext} from the on-disk project (config, secret)\n * plus fresh framework detection, so a patcher repair can rebuild its plan.\n * Used by the dependency check's `fix`, which reclaims the framework-specific\n * SDK package via `patcher.repair`. The user schema and flow definition are\n * server-owned and no longer scaffolded locally, so nothing here reads them.\n */\nexport async function loadPatchContext(cwd: string, orca: Orca): Promise<PatchContext> {\n const config = await readZitadelConfig(cwd);\n const secret = await readZitadelSecret(cwd);\n const framework = await orca.detect(cwd);\n return {\n framework,\n rendererId: readRendererId(config),\n issuer: await resolveIssuer(cwd, config, framework),\n server: typeof config.server === \"string\" ? config.server : \"\",\n project: {\n id: secret.project_id,\n projectSecret: secret.project_secret,\n previewSecret: secret.preview_secret,\n previewOrigins: secret.preview_origins,\n createdAt: secret.created_at,\n },\n };\n}\n\nasync function resolveIssuer(\n cwd: string,\n config: Record<string, unknown>,\n facts: FrameworkFacts,\n): Promise<string> {\n const fromConfig = readDevelopmentIssuer(config);\n if (fromConfig && fromConfig.length > 0) {\n return fromConfig;\n }\n const state = await readState(cwd);\n if (typeof state?.dev_port === \"number\") {\n return issuerFromPort(state.dev_port);\n }\n return facts.url;\n}\n\nasync function readState(cwd: string): Promise<{ dev_port?: number } | undefined> {\n try {\n const contents = await readFile(join(cwd, \".zitadel/state.json\"), \"utf8\");\n return JSON.parse(contents) as { dev_port?: number };\n } catch {\n return undefined;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { loadPatchContext } from \"../patch-context\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/**\n * Verifies the project still declares a Zitadel SDK dependency in\n * `package.json`. The patcher adds a scoped package (e.g.\n * `@zitadel/sdk-next`); the check is generic over the `@zitadel*`\n * scope so any framework renderer's dependency satisfies it.\n */\nexport class DependencyCheck extends AbstractSanityCheck {\n readonly name = \"dependency\";\n readonly path = \"package.json\";\n protected readonly summary = \"package.json depends on a Zitadel SDK package\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const pkg = JSON.parse(await readFile(join(ctx.cwd, \"package.json\"), \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const names = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n if (!names.some((name) => name.startsWith(\"@zitadel\"))) {\n throw new Error(\"no @zitadel* dependency found in package.json\");\n }\n }\n\n /**\n * Repairs by reclaiming the patcher's managed artifacts: rebuilds the\n * `PatchContext` from disk and calls `patcher.repair`, which re-adds the\n * SDK dependency via its `add-dep` op. The exact package name is framework\n * + renderer specific and known only to the patcher (which deliberately\n * hides its file-op plan behind the family-neutral `Patcher` interface),\n * so going through `repair` is the only sanctioned path.\n */\n override async fix(ctx: CheckContext): Promise<void> {\n const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca);\n await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {\n cwd: ctx.cwd,\n dryRun: ctx.dryRun,\n force: true,\n });\n }\n}\n","import { readZitadelConfig, readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret`'s project_id matches `zitadel.json`'s project. */\nexport class ProjectMatchCheck extends AbstractSanityCheck {\n readonly name = \"project-match\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret project_id matches zitadel.json project\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const secret = await readZitadelSecret(ctx.cwd);\n const configProject = typeof config.project === \"string\" ? config.project : undefined;\n if (secret.project_id !== configProject) {\n throw new Error(\".zitadel/secret project_id does not match zitadel.json project\");\n }\n }\n}\n","/**\n * Public surface for the doctor sanity checks. The `doctor` command imports\n * {@link SANITY_CHECKS} and runs every entry, aggregating the outcomes. Each\n * check is a small standalone class (see its own file); add a new diagnostic\n * by writing a class and appending an instance to the registry below.\n */\nimport type { SanityCheck } from \"./types\";\nimport { ConfigCheck } from \"./config\";\nimport { SecretCheck } from \"./secret\";\nimport { SecretPermissionsCheck } from \"./secret-permissions\";\nimport { GitignoreCheck } from \"./gitignore\";\nimport { EnvExampleCheck } from \"./env-example\";\nimport { FrameworkCheck } from \"./framework\";\nimport { DependencyCheck } from \"./dependency\";\nimport { ProjectMatchCheck } from \"./project-match\";\n\nexport type { SanityCheck, CheckContext, CheckOutcome } from \"./types\";\nexport { AbstractSanityCheck } from \"./types\";\nexport { ConfigCheck } from \"./config\";\nexport { SecretCheck } from \"./secret\";\nexport { SecretPermissionsCheck } from \"./secret-permissions\";\nexport { GitignoreCheck } from \"./gitignore\";\nexport { EnvExampleCheck } from \"./env-example\";\nexport { FrameworkCheck } from \"./framework\";\nexport { SchemaCheck } from \"./schema\";\nexport { DependencyCheck } from \"./dependency\";\nexport { ProjectMatchCheck } from \"./project-match\";\n\n/** Every diagnostic the `doctor` command runs, in display order. */\nexport const SANITY_CHECKS: ReadonlyArray<SanityCheck> = [\n new ConfigCheck(),\n new SecretCheck(),\n new SecretPermissionsCheck(),\n new GitignoreCheck(),\n new EnvExampleCheck(),\n new FrameworkCheck(),\n // SchemaCheck is disabled: the user schema is now provisioned server-side\n // and no longer scaffolded into `.zitadel/schemas/user.json`, so there is\n // no local file to verify. The check is kept (imported/exported below) for\n // the future pull-based workflow that will re-introduce local resources.\n // new SchemaCheck(),\n new DependencyCheck(),\n new ProjectMatchCheck(),\n];\n","import { Flags } from \"@oclif/core\";\nimport consola from \"consola\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { createOrca } from \"../../lib/orca\";\nimport { SANITY_CHECKS, type CheckContext } from \"./checks\";\n\n/**\n * `zitadel doctor` — verify generated files and local state.\n *\n * Runs every registered {@link SANITY_CHECKS} entry and emits the aggregate\n * result; if any check fails it throws `E_VALIDATION` carrying the full check\n * details. With `--fix`, each failing check first attempts its own repair (a\n * no-op for checks with no safe automatic remedy), then the battery re-runs.\n *\n * The `--fix` loop is best-effort: a repair that throws (e.g. a missing\n * prerequisite file the check itself would also flag) is logged at debug\n * level and skipped, not propagated — the post-fix re-verify still reports\n * whatever remains broken.\n */\nexport default class Doctor extends BaseCommand {\n static override description = \"Verify generated files and local state.\";\n static override flags = {\n fix: Flags.boolean({ description: \"Re-apply missing managed files.\" }),\n };\n\n async run(): Promise<JsonEnvelope> {\n const { flags } = await this.parse(Doctor);\n await this.toMeta(flags);\n const { cwd, dryRun } = this.meta;\n const ctx: CheckContext = { cwd, orca: createOrca(), dryRun };\n\n if (flags.fix) {\n const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));\n for (const [index, check] of SANITY_CHECKS.entries()) {\n if (before[index]?.status !== \"fail\") {\n continue;\n }\n try {\n await check.fix(ctx);\n } catch (error) {\n consola.debug(`doctor --fix: ${check.name} repair failed`, error);\n }\n }\n }\n\n const checks = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));\n const failed = checks.filter((check) => check.status === \"fail\");\n const data = {\n title: failed.length === 0 ? \"Zitadel doctor passed.\" : \"Zitadel doctor found issues.\",\n ok: failed.length === 0,\n checks,\n };\n\n if (failed.length > 0) {\n throw new ZitadelError(\"E_VALIDATION\", \"Zitadel doctor found issues\", {\n hint: \"Run `npx @zitadel/cli@latest doctor --fix` to re-apply missing managed files.\",\n details: data,\n });\n }\n\n return this.emit({ status: \"ok\", data });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"doctor.mjs","names":[],"sources":["../../src/commands/doctor/checks/types.ts","../../src/commands/doctor/checks/config.ts","../../src/commands/doctor/checks/secret.ts","../../src/commands/doctor/checks/secret-permissions.ts","../../src/commands/doctor/checks/gitignore.ts","../../src/commands/doctor/checks/env-example.ts","../../src/commands/doctor/checks/framework.ts","../../src/commands/doctor/patch-context.ts","../../src/commands/doctor/checks/dependency.ts","../../src/commands/doctor/checks/project-match.ts","../../src/commands/doctor/checks/index.ts","../../src/commands/doctor/index.ts"],"sourcesContent":["import type { Orca } from \"../../../lib/orca\";\n\n/** Pass/fail outcome of a single {@link SanityCheck}. */\nexport type CheckOutcome = {\n name: string;\n status: \"pass\" | \"fail\";\n message: string;\n path?: string;\n};\n\n/** Everything a check needs to inspect or repair a project. */\nexport type CheckContext = {\n readonly cwd: string;\n readonly orca: Orca;\n readonly cliVersion: string;\n /** When true, {@link SanityCheck.fix} must preview without writing. */\n readonly dryRun: boolean;\n};\n\n/**\n * One diagnostic the `doctor` command runs. Each concrete check is a small\n * standalone class that both verifies its concern ({@link run}) and knows how\n * to repair it ({@link fix}); the command executes every registered check,\n * aggregates the {@link CheckOutcome}s, and (under `--fix`) repairs the ones\n * that failed.\n */\nexport interface SanityCheck {\n /** Stable identifier surfaced in logs and the JSON envelope. */\n readonly name: string;\n run(ctx: CheckContext): Promise<CheckOutcome>;\n /** Repair what this check verifies. A no-op when there is no safe auto-fix. */\n fix(ctx: CheckContext): Promise<void>;\n}\n\n/**\n * Base class for checks: subclasses declare `name`, `path`, and a success\n * `summary`, and implement the single {@link verify} method that throws on\n * failure. {@link run} wraps it so a thrown error becomes a `fail` outcome\n * carrying the error message, and success becomes a `pass` with `summary`.\n *\n * {@link fix} defaults to a no-op: checks whose failure has no safe automatic\n * remedy (a missing secret, an invalid user schema) simply do not override it.\n */\nexport abstract class AbstractSanityCheck implements SanityCheck {\n abstract readonly name: string;\n abstract readonly path: string;\n protected abstract readonly summary: string;\n\n /** Throw to signal failure; the thrown message is surfaced to the user. */\n protected abstract verify(ctx: CheckContext): Promise<void>;\n\n async run(ctx: CheckContext): Promise<CheckOutcome> {\n try {\n await this.verify(ctx);\n return { name: this.name, status: \"pass\", message: this.summary, path: this.path };\n } catch (error) {\n return {\n name: this.name,\n status: \"fail\",\n message: error instanceof Error ? error.message : String(error),\n path: this.path,\n };\n }\n }\n\n async fix(_ctx: CheckContext): Promise<void> {\n return;\n }\n}\n","import { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `zitadel.json` exists and parses. */\nexport class ConfigCheck extends AbstractSanityCheck {\n readonly name = \"config\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"zitadel.json parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelConfig(ctx.cwd);\n }\n}\n","import { readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` exists and parses. */\nexport class SecretCheck extends AbstractSanityCheck {\n readonly name = \"secret\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret parses\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n await readZitadelSecret(ctx.cwd);\n }\n}\n","import { chmod, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret` is locked down to `0600`, and re-locks it on fix. */\nexport class SecretPermissionsCheck extends AbstractSanityCheck {\n readonly name = \"secret-permissions\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret has 0600 permissions\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const mode = (await stat(join(ctx.cwd, \".zitadel/secret\"))).mode & 0o777;\n if (mode !== 0o600) {\n throw new Error(`expected 0600, got ${mode.toString(8)}`);\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n if (ctx.dryRun) {\n return;\n }\n await chmod(join(ctx.cwd, \".zitadel/secret\"), 0o600);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The entries `.gitignore` must carry to keep secrets and env files untracked. */\nconst REQUIRED_ENTRIES = [\".zitadel/secret\", \".env*\", \"!.env.example\"];\n\n/** Verifies `.gitignore` excludes the local secret and env files; appends any missing. */\nexport class GitignoreCheck extends AbstractSanityCheck {\n readonly name = \"gitignore\";\n readonly path = \".gitignore\";\n protected readonly summary = \".gitignore protects local secret/env files\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const lines = (await readFile(join(ctx.cwd, \".gitignore\"), \"utf8\")).split(/\\r?\\n/g);\n for (const entry of REQUIRED_ENTRIES) {\n if (!lines.includes(entry)) {\n throw new Error(`missing ${entry}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".gitignore\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const lines = existing.split(/\\r?\\n/g);\n const missing = REQUIRED_ENTRIES.filter((entry) => !lines.includes(entry));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.join(\"\\n\")}\\n`);\n }\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** The keys `.env.example` must document for a Zitadel-managed project. */\nconst REQUIRED_KEYS = [\"ZITADEL_PROJECT_ID\", \"ZITADEL_ENVIRONMENT\", \"ZITADEL_ISSUER\"];\n\n/** Verifies `.env.example` documents the required Zitadel keys; appends any missing. */\nexport class EnvExampleCheck extends AbstractSanityCheck {\n readonly name = \"env-example\";\n readonly path = \".env.example\";\n protected readonly summary = \".env.example references required keys\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const contents = await readFile(join(ctx.cwd, \".env.example\"), \"utf8\");\n for (const key of REQUIRED_KEYS) {\n if (!contents.includes(`${key}=`)) {\n throw new Error(`missing ${key}`);\n }\n }\n }\n\n override async fix(ctx: CheckContext): Promise<void> {\n const path = join(ctx.cwd, \".env.example\");\n const existing = await readFile(path, \"utf8\").catch(() => \"\");\n const missing = REQUIRED_KEYS.filter((key) => !existing.includes(`${key}=`));\n if (missing.length === 0 || ctx.dryRun) {\n return;\n }\n const prefix = existing.length > 0 && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await writeFile(path, `${existing}${prefix}${missing.map((key) => `${key}=`).join(\"\\n\")}\\n`);\n }\n}\n","import { isObject } from \"../../../lib/json\";\nimport { readZitadelConfig } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies the framework detected on disk matches the one recorded in config. */\nexport class FrameworkCheck extends AbstractSanityCheck {\n readonly name = \"framework\";\n readonly path = \"zitadel.json\";\n protected readonly summary = \"Detected framework matches recorded framework\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const detected = await ctx.orca.detect(ctx.cwd);\n const recorded = isObject(config.framework) ? config.framework.id : undefined;\n if (recorded !== detected.id) {\n throw new Error(`expected ${String(recorded)}, detected ${detected.id}`);\n }\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { issuerFromPort, type FrameworkFacts, type Orca } from \"../../lib/orca\";\nimport type { PatchContext } from \"../../lib/orca/patchers/types\";\nimport { readDevelopmentIssuer, readRendererId, readZitadelConfig, readZitadelSecret } from \"../../lib/project\";\n\n/**\n * Reconstructs a {@link PatchContext} from the on-disk project (config, secret)\n * plus fresh framework detection, so a patcher repair can rebuild its plan.\n * Used by the dependency check's `fix`, which reclaims the framework-specific\n * SDK package via `patcher.repair`. The user schema and flow definition are\n * server-owned and no longer scaffolded locally, so nothing here reads them.\n */\nexport async function loadPatchContext(\n cwd: string,\n orca: Orca,\n cliVersion: string,\n): Promise<PatchContext> {\n const config = await readZitadelConfig(cwd);\n const secret = await readZitadelSecret(cwd);\n const framework = await orca.detect(cwd);\n return {\n framework,\n rendererId: readRendererId(config),\n issuer: await resolveIssuer(cwd, config, framework),\n server: typeof config.server === \"string\" ? config.server : \"\",\n cliVersion,\n project: {\n id: secret.project_id,\n projectSecret: secret.project_secret,\n previewSecret: secret.preview_secret,\n previewOrigins: secret.preview_origins,\n createdAt: secret.created_at,\n },\n };\n}\n\nasync function resolveIssuer(\n cwd: string,\n config: Record<string, unknown>,\n facts: FrameworkFacts,\n): Promise<string> {\n const fromConfig = readDevelopmentIssuer(config);\n if (fromConfig && fromConfig.length > 0) {\n return fromConfig;\n }\n const state = await readState(cwd);\n if (typeof state?.dev_port === \"number\") {\n return issuerFromPort(state.dev_port);\n }\n return facts.url;\n}\n\nasync function readState(cwd: string): Promise<{ dev_port?: number } | undefined> {\n try {\n const contents = await readFile(join(cwd, \".zitadel/state.json\"), \"utf8\");\n return JSON.parse(contents) as { dev_port?: number };\n } catch {\n return undefined;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { loadPatchContext } from \"../patch-context\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/**\n * Verifies the project still declares a Zitadel SDK dependency in\n * `package.json`. The patcher adds a scoped package (e.g.\n * `@zitadel/sdk-next`); the check is generic over the `@zitadel*`\n * scope so any framework renderer's dependency satisfies it.\n */\nexport class DependencyCheck extends AbstractSanityCheck {\n readonly name = \"dependency\";\n readonly path = \"package.json\";\n protected readonly summary = \"package.json depends on a Zitadel SDK package\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const pkg = JSON.parse(await readFile(join(ctx.cwd, \"package.json\"), \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const names = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ];\n if (!names.some((name) => name.startsWith(\"@zitadel\"))) {\n throw new Error(\"no @zitadel* dependency found in package.json\");\n }\n }\n\n /**\n * Repairs by reclaiming the patcher's managed artifacts: rebuilds the\n * `PatchContext` from disk and calls `patcher.repair`, which re-adds the\n * SDK dependency via its `add-dep` op. The exact package name is framework\n * + renderer specific and known only to the patcher (which deliberately\n * hides its file-op plan behind the family-neutral `Patcher` interface),\n * so going through `repair` is the only sanctioned path.\n */\n override async fix(ctx: CheckContext): Promise<void> {\n const patchCtx = await loadPatchContext(ctx.cwd, ctx.orca, ctx.cliVersion);\n await ctx.orca.patcherFor(patchCtx.framework.id).repair(patchCtx, {\n cwd: ctx.cwd,\n dryRun: ctx.dryRun,\n force: true,\n });\n }\n}\n","import { readZitadelConfig, readZitadelSecret } from \"../../../lib/project\";\nimport { AbstractSanityCheck, type CheckContext } from \"./types\";\n\n/** Verifies `.zitadel/secret`'s project_id matches `zitadel.json`'s project. */\nexport class ProjectMatchCheck extends AbstractSanityCheck {\n readonly name = \"project-match\";\n readonly path = \".zitadel/secret\";\n protected readonly summary = \".zitadel/secret project_id matches zitadel.json project\";\n\n protected async verify(ctx: CheckContext): Promise<void> {\n const config = await readZitadelConfig(ctx.cwd);\n const secret = await readZitadelSecret(ctx.cwd);\n const configProject = typeof config.project === \"string\" ? config.project : undefined;\n if (secret.project_id !== configProject) {\n throw new Error(\".zitadel/secret project_id does not match zitadel.json project\");\n }\n }\n}\n","/**\n * Public surface for the doctor sanity checks. The `doctor` command imports\n * {@link SANITY_CHECKS} and runs every entry, aggregating the outcomes. Each\n * check is a small standalone class (see its own file); add a new diagnostic\n * by writing a class and appending an instance to the registry below.\n */\nimport type { SanityCheck } from \"./types\";\nimport { ConfigCheck } from \"./config\";\nimport { SecretCheck } from \"./secret\";\nimport { SecretPermissionsCheck } from \"./secret-permissions\";\nimport { GitignoreCheck } from \"./gitignore\";\nimport { EnvExampleCheck } from \"./env-example\";\nimport { FrameworkCheck } from \"./framework\";\nimport { DependencyCheck } from \"./dependency\";\nimport { ProjectMatchCheck } from \"./project-match\";\n\nexport type { SanityCheck, CheckContext, CheckOutcome } from \"./types\";\nexport { AbstractSanityCheck } from \"./types\";\nexport { ConfigCheck } from \"./config\";\nexport { SecretCheck } from \"./secret\";\nexport { SecretPermissionsCheck } from \"./secret-permissions\";\nexport { GitignoreCheck } from \"./gitignore\";\nexport { EnvExampleCheck } from \"./env-example\";\nexport { FrameworkCheck } from \"./framework\";\nexport { SchemaCheck } from \"./schema\";\nexport { DependencyCheck } from \"./dependency\";\nexport { ProjectMatchCheck } from \"./project-match\";\n\n/** Every diagnostic the `doctor` command runs, in display order. */\nexport const SANITY_CHECKS: ReadonlyArray<SanityCheck> = [\n new ConfigCheck(),\n new SecretCheck(),\n new SecretPermissionsCheck(),\n new GitignoreCheck(),\n new EnvExampleCheck(),\n new FrameworkCheck(),\n // SchemaCheck is disabled: the user schema is now provisioned server-side\n // and no longer scaffolded into `.zitadel/schemas/user.json`, so there is\n // no local file to verify. The check is kept (imported/exported below) for\n // the future pull-based workflow that will re-introduce local resources.\n // new SchemaCheck(),\n new DependencyCheck(),\n new ProjectMatchCheck(),\n];\n","import { Flags } from \"@oclif/core\";\nimport consola from \"consola\";\n\nimport { BaseCommand, type JsonEnvelope } from \"../../lib/oclif\";\nimport { ZitadelError } from \"../../lib/errors\";\nimport { dockerAvailable, imageAvailable } from \"../../lib/local-server/docker\";\nimport {\n DEFAULT_LOCAL_SERVER_IMAGE,\n DEFAULT_LOCAL_SERVER_PORT,\n assertWritableDirectory,\n checkLocalServerHealth,\n isPortAvailable,\n localRuntimePaths,\n localServerUrl,\n readRuntimeMetadata,\n} from \"../../lib/local-server/runtime\";\nimport { createOrca } from \"../../lib/orca\";\nimport { hasZitadelConfig } from \"../../lib/project\";\nimport { SANITY_CHECKS, type CheckContext, type CheckOutcome } from \"./checks\";\n\nconst LOCAL_RUNTIME_CHECK_NAMES = new Set([\"docker-cli\", \"image\", \"state-dir\", \"port\", \"runtime\"]);\n\n/**\n * `zitadel doctor` — verify generated files and local state.\n *\n * Runs every registered {@link SANITY_CHECKS} entry and emits the aggregate\n * result; if any check fails it throws `E_VALIDATION` carrying the full check\n * details. With `--fix`, each failing check first attempts its own repair (a\n * no-op for checks with no safe automatic remedy), then the battery re-runs.\n *\n * The `--fix` loop is best-effort: a repair that throws (e.g. a missing\n * prerequisite file the check itself would also flag) is logged at debug\n * level and skipped, not propagated — the post-fix re-verify still reports\n * whatever remains broken.\n */\nexport default class Doctor extends BaseCommand {\n static override description = \"Verify local runtime and project state.\";\n static override flags = {\n fix: Flags.boolean({ description: \"Re-apply missing managed files.\" }),\n image: Flags.string({ description: \"Container image to check.\" }),\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(Doctor);\n const port = flags.port ?? DEFAULT_LOCAL_SERVER_PORT;\n await this.toMeta(flags, { resolveServer: false, source: localServerUrl(port) });\n const { cwd, dryRun } = this.meta;\n const image = flags.image ?? this.meta.env.ZITADEL_LOCAL_IMAGE ?? DEFAULT_LOCAL_SERVER_IMAGE;\n const runtimeChecks = await runLocalRuntimeChecks(cwd, image, port);\n const hasConfig = await hasZitadelConfig(cwd);\n const ctx: CheckContext = { cwd, orca: createOrca(), cliVersion: this.meta.cliVersion, dryRun };\n\n if (hasConfig && flags.fix) {\n const before = await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)));\n for (const [index, check] of SANITY_CHECKS.entries()) {\n if (before[index]?.status !== \"fail\") {\n continue;\n }\n try {\n await check.fix(ctx);\n } catch (error) {\n consola.debug(`doctor --fix: ${check.name} repair failed`, error);\n }\n }\n }\n\n const projectChecks = hasConfig\n ? await Promise.all(SANITY_CHECKS.map((check) => check.run(ctx)))\n : [];\n const checks = [...runtimeChecks, ...projectChecks];\n const failed = checks.filter((check) => check.status === \"fail\");\n const data = {\n title: failed.length === 0 ? \"Zitadel doctor passed.\" : \"Zitadel doctor found issues.\",\n ok: failed.length === 0,\n image,\n port,\n project: {\n lifecycle: hasConfig ? \"configured\" : \"not-configured\",\n },\n checks,\n };\n\n if (failed.length > 0) {\n const advice = failureAdvice(failed, image, port);\n throw new ZitadelError(\"E_VALIDATION\", \"Zitadel doctor found issues\", {\n hint: advice.hint,\n nextCommands: advice.nextCommands,\n details: data,\n });\n }\n\n return this.emit({ status: \"ok\", data });\n }\n}\n\nfunction failureAdvice(\n failed: CheckOutcome[],\n image: string,\n port: number,\n): { hint: string; nextCommands: string[] } {\n const failedNames = new Set(failed.map((check) => check.name));\n\n if (failedNames.has(\"docker-cli\")) {\n return {\n hint:\n \"Docker is required for `zitadel start`, but the Docker daemon is not reachable. Install or start Docker, then rerun `zitadel doctor`.\",\n nextCommands: [\"docker version\", \"zitadel doctor\"],\n };\n }\n\n if (failedNames.has(\"image\")) {\n return {\n hint:\n \"The local Zitadel image is not available. Check Docker registry access, build it locally, or pass --image / ZITADEL_LOCAL_IMAGE.\",\n nextCommands: [`docker pull ${image}`, \"zitadel doctor\"],\n };\n }\n\n if (failedNames.has(\"state-dir\")) {\n return {\n hint: \"The local Zitadel state directory is not writable. Fix directory permissions, then rerun `zitadel doctor`.\",\n nextCommands: [\"zitadel doctor\"],\n };\n }\n\n if (failedNames.has(\"port\")) {\n const fallbackPort = port === DEFAULT_LOCAL_SERVER_PORT ? port + 1 : DEFAULT_LOCAL_SERVER_PORT;\n return {\n hint: `Port ${String(port)} is already in use. Stop the process using it, or choose another port for local Zitadel.`,\n nextCommands: [`zitadel doctor --port ${String(fallbackPort)}`],\n };\n }\n\n if (failedNames.has(\"runtime\")) {\n return {\n hint:\n \"Existing local runtime metadata was found, but the local Zitadel server is not healthy. Start it again or reset stale local data.\",\n nextCommands: [\"zitadel start\", \"zitadel reset --force\"],\n };\n }\n\n const hasProjectFailure = failed.some((check) => !LOCAL_RUNTIME_CHECK_NAMES.has(check.name));\n if (hasProjectFailure) {\n return {\n hint: \"Run `npx @zitadel/cli@alpha doctor --fix` to re-apply missing managed files.\",\n nextCommands: [\"zitadel doctor --fix\"],\n };\n }\n\n return {\n hint: \"Fix the reported checks, then rerun `zitadel doctor`.\",\n nextCommands: [\"zitadel doctor\"],\n };\n}\n\nasync function runLocalRuntimeChecks(\n cwd: string,\n image: string,\n port: number,\n): Promise<CheckOutcome[]> {\n const runtime = await readRuntimeMetadata(cwd);\n return [\n await check(\"docker-cli\", \"Docker is reachable\", async () => {\n const result = await dockerAvailable();\n if (result.status !== 0) {\n throw new Error(result.stderr || \"docker version failed\");\n }\n return `Docker engine ${result.stdout.trim() || \"available\"}`;\n }),\n await check(\"image\", `Image ${image} is available`, async () => {\n const source = await imageAvailable(image);\n return source === \"local\"\n ? `Image ${image} is available locally`\n : `Image ${image} is available from the registry`;\n }),\n await check(\"state-dir\", \"Local state directory is writable\", async () => {\n const paths = localRuntimePaths(cwd);\n await assertWritableDirectory(paths.dataDir);\n return `${paths.dataDir} is writable`;\n }),\n await check(\"port\", `Port ${String(port)} is available`, async () => {\n if (runtime && (await checkLocalServerHealth(runtime.server_url))) {\n return `${runtime.server_url} is already healthy`;\n }\n if (!(await isPortAvailable(port))) {\n throw new Error(`Port ${String(port)} is already in use`);\n }\n return `Port ${String(port)} is available`;\n }),\n await check(\"runtime\", \"Existing local runtime is healthy\", async () => {\n if (!runtime) {\n return \"No existing runtime metadata\";\n }\n if (!(await checkLocalServerHealth(runtime.server_url))) {\n throw new Error(`${runtime.server_url} did not respond to /healthz`);\n }\n return `${runtime.server_url} is healthy`;\n }),\n ];\n}\n\nasync function check(\n name: string,\n fallback: string,\n run: () => Promise<string>,\n): Promise<CheckOutcome> {\n try {\n return { name, status: \"pass\", message: await run() };\n } catch (error) {\n return {\n name,\n status: \"fail\",\n message: error instanceof Error ? error.message : fallback,\n };\n }\n}\n"],"mappings":"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"}
|