@zitadel/cli 0.1.0-alpha.9 → 1.0.0-alpha.20

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 (66) hide show
  1. package/README.md +250 -62
  2. package/SKILLS.md +221 -37
  3. package/dist/branding-cygSBPkV.mjs +79 -0
  4. package/dist/branding-cygSBPkV.mjs.map +1 -0
  5. package/dist/claim-state-DlfD6Res.mjs +66 -0
  6. package/dist/claim-state-DlfD6Res.mjs.map +1 -0
  7. package/dist/commands/apply.mjs +27 -8
  8. package/dist/commands/apply.mjs.map +1 -1
  9. package/dist/commands/branding/eject.mjs +104 -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 +594 -41
  14. package/dist/commands/doctor.mjs.map +1 -1
  15. package/dist/commands/eject.mjs +18 -6
  16. package/dist/commands/eject.mjs.map +1 -1
  17. package/dist/commands/logs.mjs +2 -2
  18. package/dist/commands/plan.mjs +19 -6
  19. package/dist/commands/plan.mjs.map +1 -1
  20. package/dist/commands/reset.mjs +2 -2
  21. package/dist/commands/schemas/list.mjs +146 -0
  22. package/dist/commands/schemas/list.mjs.map +1 -0
  23. package/dist/commands/setup.mjs +629 -214
  24. package/dist/commands/setup.mjs.map +1 -1
  25. package/dist/commands/start.mjs +4 -4
  26. package/dist/commands/start.mjs.map +1 -1
  27. package/dist/commands/status.mjs +52 -13
  28. package/dist/commands/status.mjs.map +1 -1
  29. package/dist/commands/stop.mjs +3 -3
  30. package/dist/commands/stop.mjs.map +1 -1
  31. package/dist/designs-Ckz18Dpo.mjs +38 -0
  32. package/dist/designs-Ckz18Dpo.mjs.map +1 -0
  33. package/dist/{docker-CnGQK3ZK.mjs → docker-DcRGTOJa.mjs} +5 -4
  34. package/dist/docker-DcRGTOJa.mjs.map +1 -0
  35. package/dist/environment-rjRVkJjW.mjs +17 -0
  36. package/dist/environment-rjRVkJjW.mjs.map +1 -0
  37. package/dist/journey-guidance-BGrOX_gT.mjs +40 -0
  38. package/dist/journey-guidance-BGrOX_gT.mjs.map +1 -0
  39. package/dist/oclif-CanO3zdt.mjs +1769 -0
  40. package/dist/oclif-CanO3zdt.mjs.map +1 -0
  41. package/dist/{orca-U142Wrau.mjs → orca-BzQIQqzt.mjs} +1663 -303
  42. package/dist/orca-BzQIQqzt.mjs.map +1 -0
  43. package/dist/package-manager-DFdLcDx1.mjs +194 -0
  44. package/dist/package-manager-DFdLcDx1.mjs.map +1 -0
  45. package/dist/{ports-B09RjuHx.mjs → ports-BM20XIZb.mjs} +25 -20
  46. package/dist/ports-BM20XIZb.mjs.map +1 -0
  47. package/dist/{processes-Cw8TO1SY.mjs → processes-DKHPkU8O.mjs} +1 -1
  48. package/dist/{processes-Cw8TO1SY.mjs.map → processes-DKHPkU8O.mjs.map} +1 -1
  49. package/dist/{project-Cd0L3PtM.mjs → project-CHhot85s.mjs} +57 -4
  50. package/dist/project-CHhot85s.mjs.map +1 -0
  51. package/dist/sync-BBlACKzT.mjs +1633 -0
  52. package/dist/sync-BBlACKzT.mjs.map +1 -0
  53. package/dist/user-schema-DTuOsdKE.mjs +91 -0
  54. package/dist/user-schema-DTuOsdKE.mjs.map +1 -0
  55. package/oclif.manifest.json +406 -5
  56. package/package.json +10 -5
  57. package/dist/docker-CnGQK3ZK.mjs.map +0 -1
  58. package/dist/docker-guidance-ypN3IM3o.mjs +0 -21
  59. package/dist/docker-guidance-ypN3IM3o.mjs.map +0 -1
  60. package/dist/oclif-B7lBzh3R.mjs +0 -808
  61. package/dist/oclif-B7lBzh3R.mjs.map +0 -1
  62. package/dist/orca-U142Wrau.mjs.map +0 -1
  63. package/dist/ports-B09RjuHx.mjs.map +0 -1
  64. package/dist/project-Cd0L3PtM.mjs.map +0 -1
  65. package/dist/sync-BojoQm2P.mjs +0 -733
  66. package/dist/sync-BojoQm2P.mjs.map +0 -1
package/SKILLS.md CHANGED
@@ -4,7 +4,7 @@ description: >-
4
4
  Set up and manage Zitadel authentication in a local project with the
5
5
  agent-friendly `zitadel` CLI. Use when the user wants to add login,
6
6
  registration, or session handling, create a Zitadel project, scaffold auth
7
- for a Next.js, React, Vue, Angular, or Nuxt app, or plan and apply Zitadel
7
+ for a Next.js, React, Vue, Angular, Nuxt, Solid, Svelte, or Qwik app, or plan and apply Zitadel
8
8
  config changes from repo state.
9
9
  ---
10
10
 
@@ -20,6 +20,10 @@ parse the result rather than scraping human output.
20
20
  - Always pass `--non-interactive --json`. The envelope is the contract.
21
21
  - Add `--cwd <path>` when operating outside the current working directory.
22
22
  - Never run interactive prompts; `--non-interactive` (and `--json`) disable them.
23
+ - The CLI sends anonymous usage telemetry by default. For automated/agent runs
24
+ that should stay silent, disable it with `--no-telemetry` (per invocation) or
25
+ `ZITADEL_TELEMETRY=0` / `DO_NOT_TRACK=1` (per environment); this also skips the
26
+ small end-of-command network flush.
23
27
  - See `README.md` (its commands section is generated from the CLI's own
24
28
  metadata) or run `zitadel <command> --help` for the full per-command flag list.
25
29
 
@@ -39,44 +43,199 @@ Each invocation prints one JSON object:
39
43
  - `cli_version`, `command`, `source`: always present.
40
44
  - On success: `data` with the command-specific payload.
41
45
  - On a no-op: `reason` (e.g. `no-framework-detected`, `orphaned-config`).
42
- - On failure: `code` (e.g. `E_VALIDATION`, `E_NETWORK`, `E_CONFLICT`) and
43
- `message`.
46
+ - On failure: `code` (e.g. `E_VALIDATION`, `E_NETWORK`, `E_NOT_FOUND`,
47
+ `E_CONFLICT`) and `message`.
44
48
  - `next_commands`: the suggested follow-ups. Prefer these over free-text hints.
49
+ - `plan` and `apply` also emit `data.changes`: one row per touched resource
50
+ (`{kind, action, file, id?, previous_id?}`, action ∈ create/update/revision/
51
+ delete). Plan rows preview; apply rows report, with the resulting platform
52
+ ids. Use it to verify an edit did what you intended — `apply`'s
53
+ `files_updated` lists only local write-backs, not platform changes.
54
+ - `setup` emits `data.files`: one typed row per scaffolded artifact
55
+ (`{path, kind: file|dir, action: create|update}`), deduplicated. Use it to
56
+ see what setup created versus merged into (your `package.json` is an
57
+ `update`). `data.files_written` remains the flat list — deduplicated file
58
+ paths only, covering both scaffolded and `.zitadel/` resource files.
59
+ - `setup` also emits `data.design`: the starter login design it ejected and
60
+ published as branding revision 1, or `null` when the built-in template was
61
+ kept (no `.zitadel/branding/` files exist in that case). Use it to verify
62
+ the requested `--design` took effect without diffing the repo.
45
63
  - `E_LOCAL_SERVER_NOT_RUNNING`: start the local runtime with
46
64
  `npx @zitadel/cli@alpha start`, then retry with `--server local`.
65
+ - `E_NOT_FOUND`: an HTTP 404 from the target server. With the platform's
66
+ error envelope it names a missing resource (e.g. an unknown schema id);
67
+ without one the endpoint itself is missing — the `--server` value likely
68
+ points at something that is not a Zitadel platform API. Follow
69
+ `next_commands` (usually `start` + retry with `--server local`).
47
70
  - `E_PORT_IN_USE`: the requested local runtime port already has a listener.
48
71
  Stop that process, run `npx @zitadel/cli@alpha stop --all` for host-wide
49
72
  CLI-managed local runtimes, or choose another `start --port`.
50
73
 
51
- Exit codes mirror the error class (3 = validation, 4 = network, 5 = conflict,
52
- 1 = auth, 2 = not-implemented). An unknown command is handled by the CLI's help
53
- layer, not the envelope.
74
+ Capture stdout and stderr separately when scripting. Some terminals and agent
75
+ UIs display both streams together, but the machine contract is one parseable
76
+ JSON object on stdout; installer, audit, and package-manager progress belongs
77
+ on stderr.
78
+
79
+ Exit codes mirror the error class (3 = validation, 4 = network or not-found,
80
+ 5 = conflict, 1 = auth, 2 = not-implemented). An unknown command is handled by
81
+ the CLI's help layer, not the envelope.
54
82
 
55
83
  ## Commands
56
84
 
57
85
  - `setup` — create a Zitadel project and scaffold local auth (routes,
58
- middleware, `.zitadel/**`, env templates). The project's default user schema
59
- and login flow are provisioned server-side at creation, so setup neither
60
- scaffolds nor uploads them. Agents must pass `--framework` when scaffolding
61
- into a fresh directory; interactive humans can omit it and choose from the
62
- prompt. Flags: `--framework next|react|vue|angular|nuxt`, `--renderer
63
- react|web-component` (selects the Next.js auth-page renderer; accepted for any
64
- framework and recorded in `zitadel.json` branding, but only Next varies its
65
- generated templates by it), `--dev-port` (dev-server port, also the issuer
86
+ middleware, `.zitadel/**`, env templates). Setup writes the versioned local
87
+ default user schema and login flow into
88
+ `.zitadel/schemas/default-human-user.json` and
89
+ `.zitadel/flows/default-login.json`, uploads them through the schema and flow
90
+ APIs, then seeds `.zitadel/state.json` so `plan` is immediately empty. Agents
91
+ must pass `--framework` when scaffolding into a fresh directory; interactive
92
+ humans can omit it and choose from the prompt. Supported floors: Next.js 15+
93
+ and React 18+ `setup` and `doctor` fail with `E_UNSUPPORTED_PROJECT_SHAPE`
94
+ below them instead of degrading silently (an unparseable version passes).
95
+ Flags:
96
+ `--framework next|react|vue|angular|nuxt|solid|svelte|qwik`, `--renderer
97
+ react` (selects the Next.js auth-page renderer; accepted for any framework
98
+ and recorded in `zitadel.json` branding, but only Next varies its generated
99
+ templates by it; the planned `web-component` renderer is not yet available
100
+ and is rejected if passed), `--dev-port` (dev-server port, also the issuer
66
101
  origin registered with Zitadel — use distinct ports to run several scaffolded
67
- apps side by side), `--skip-install`.
102
+ apps side by side. The app must actually serve this port or the flow API
103
+ rejects its origin on the first submit, so setup makes the port explicit in
104
+ the app's own dev-server config: `server.port` + `strictPort` for Vite
105
+ frameworks, `serve.options.port` for Angular, and — because `next dev` and
106
+ `nuxt dev` take a port only from the command line — `--port` in the
107
+ `package.json` `dev` script for Next and Nuxt. On a pre-existing app that
108
+ means setup edits the `dev` script when it does not already name that port;
109
+ a script already on it is left untouched), `--preset password-first|passkey-first` (the sign-in
110
+ experience the scaffold starts from: `password-first` is the default —
111
+ email + password with passkey optional during registration; `passkey-first`
112
+ enters login on a one-tap passkey step with an email + password fallback;
113
+ recorded in `zitadel.json`), `--use-case minimal|consumer|business` (which
114
+ profile fields the scaffolded schema collects: `minimal` is the default —
115
+ email only; `consumer` adds given and family name; `business` also adds a
116
+ `companyName` attribute and overlays work-email copy on the generated auth
117
+ pages via the SDK's `businessLocales`; asked before `--preset` and recorded
118
+ in `zitadel.json`), `--design centered|split|split-right|hero|minimal`
119
+ (starter login design: ejects the design's template into
120
+ `.zitadel/branding/` and publishes it as branding revision 1 during setup;
121
+ the interactive wizard asks this as its final question with the built-in
122
+ template preselected — omit the flag in non-interactive runs to keep the
123
+ built-in template and no branding files), `--skip-install`.
124
+ On Next and Nuxt, the scaffolded auth/profile pages derive their embedding
125
+ posture from the app: a fresh scaffold (setup created the skeleton) pins
126
+ `variant="page"` full-page chrome, while a pre-existing app embeds
127
+ `variant="widget"` cards with `theme="auto"` in a layout-neutral wrapper.
128
+ `theme="auto"` follows the OS `prefers-color-scheme`, not the host app's
129
+ own theme — edit the generated page to set `theme="light"` or
130
+ `theme="dark"` when the app pins its scheme. Other frameworks always
131
+ scaffold the page posture. The chosen posture is recorded in the scaffold
132
+ manifest, `doctor --fix` restores managed pages in the recorded posture,
133
+ and editing the generated page is the supported way to change presentation
134
+ — there is no config knob.
135
+ Widget-posture embedding levers: host-page CSS sets `--zl-*` design-token
136
+ custom properties on the element to bridge the app's look through the
137
+ widget's shadow DOM (fonts `--zl-font-family-heading`/`-sans`, radii
138
+ `--zl-radius-*`, primary CTA `--zl-primary`/`--zl-primary-foreground`,
139
+ link color `--zl-link`); the `suppress-header` attribute
140
+ (wrapper prop `suppressHeader`) visually hides the widget's own heading
141
+ block when the page already carries one, keeping it in the accessibility
142
+ tree. Split-family designs collapse their brand pane by container width —
143
+ at card width they show the compact brand mark (`logo_url`, else
144
+ `hero_url`, from `.zitadel/branding/branding.json`; `hero` falls back to
145
+ editable text), and setup warns when a widget-posture app picks `split`
146
+ or `split-right`.
68
147
  - `plan` — validate config and preview the sync diff without mutating anything.
69
148
  - `apply` — validate and upload repo config to the platform.
149
+ - `plan` and `apply --dry-run` also emit `data.warnings`: non-blocking
150
+ findings as `{path, rule, message}`, the same text the human plan prints as
151
+ `# warning:` lines and `apply` prints through stderr. They never fail a run.
152
+ Two families exist today: flow-definition rules (`warn/…`, mirrored from the
153
+ server's validator) and branding asset reachability. `warn/asset-unreachable`
154
+ and `warn/asset-content-type` come from a bounded HEAD probe of
155
+ `logo_url` / `hero_url` — a URL that is well-formed but dead passes every
156
+ gate and then renders as a 0×0 image with nothing in the console, so the
157
+ probe is the only place it can be caught. It is advisory by design: the
158
+ machine planning is not necessarily the machine that renders the login page.
159
+ The probe only contacts public HTTPS destinations and re-checks every
160
+ redirect; loopback/private/internal targets stay inconclusive instead of
161
+ turning repo config into a network request from the planning host.
162
+ Set `ZITADEL_SKIP_ASSET_PROBE` to turn it off (offline, air-gapped CI, or a
163
+ CDN that only resolves from production) and `ZITADEL_ASSET_PROBE_TIMEOUT_MS`
164
+ to retune the per-URL budget (default 2500).
165
+ - In the human-readable plan, a multi-line field (branding's inlined
166
+ `liquid_template`) renders as `(<n> lines, sha256:…)` when it is created or
167
+ unchanged, and as a changed-line diff when it moved — not as one escaped
168
+ line. Read the file itself for full content.
169
+ - `schemas list` — inspect the revision history of a user-schema, filtered by
170
+ `--object-type` (e.g. `human-user`). Non-interactive/`--json` prints one row
171
+ per revision (newest first); interactive adds a picker that fetches and
172
+ pretty-prints the selected revision body.
70
173
  - `doctor` — verify generated app files and local state once `zitadel.json`
71
- exists. The default local runtime is the `@zitadel/server` npm binary;
72
- Docker checks apply only when using `--runtime docker` or `--image`.
73
- `--fix` re-applies missing managed files.
174
+ exists. The `managed-files` check compares the scaffolded app files against
175
+ the manifest setup recorded in `.zitadel/state.json`: a missing
176
+ infrastructure file (the request boundary, `custom-elements.d.ts`) fails,
177
+ a missing generated page warns, and files you edited (marker kept) or
178
+ replaced (marker removed) pass as `edited`/`adopted`. It also verifies the
179
+ managed config wirings (Vite/Nuxt proxy merges, Angular's `angular.json`
180
+ proxy and auth routes) through the patchers' idempotent transforms — a
181
+ detached or missing wiring config fails, an unverifiable one warns, and
182
+ `--fix` re-applies it. The Next/Nuxt `dev` script is verified the same way,
183
+ against the port recorded as the development issuer rather than the port the
184
+ script names today: a script moved off that port reports as an unapplied
185
+ config edit (a warning — a `dev` script is not the only way to choose a
186
+ port), and `--fix` restores the registered one. Boundary migrations converge: a pristine leftover
187
+ `middleware.ts` from a Next 15→16 upgrade is swapped for `proxy.ts`, while
188
+ an edited one is reported as a conflict instead of creating both (Next
189
+ rejects the pair). The default local
190
+ runtime is the `@zitadel/server` npm binary; Docker checks apply only when
191
+ using `--runtime docker` or `--image`. `--fix` restores missing managed
192
+ files and never replaces an existing scaffolded app file; additive repairs
193
+ (missing `.gitignore` entries, `.env.example` keys) still append to their
194
+ targets, and the SDK dependency is re-added only when absent — an existing
195
+ version pin is never rewritten. The `dependency-version` check warns when
196
+ an exactly-pinned `@zitadel/*` dependency does not match the CLI's own
197
+ version (the packages release as one train, and a floating
198
+ `npx @zitadel/cli@alpha` can run ahead of the app's pins); ranges,
199
+ dist-tags, and `file:`/`workspace:` specifiers express a deliberate choice
200
+ and are not compared. The repair — an exact-pin install command for the
201
+ project's detected package manager — is emitted in `data.next_commands`
202
+ and quoted in the warning message.
203
+ - `claim` — attach the project to a team so it becomes permanent. Mints a
204
+ short-lived link, opens it in a browser, and blocks until the developer
205
+ finishes signing in there, then records `claimed_at` and `team_id` in
206
+ `.zitadel/secret`. Nothing about the project changes: the issuer, users,
207
+ passkeys, and applications keep working, and the project secret is not
208
+ rotated. Re-running once the project belongs to a team is a clean
209
+ `status: "skipped"` with `reason: "already-claimed"`, so agents can retry
210
+ safely. The link is always printed before any browser opens, so a headless
211
+ machine, an SSH session, or `--no-open` needs no special handling — copy it
212
+ and open it anywhere. Links last 10 minutes; once one lapses the command
213
+ exits `E_VALIDATION` and points at a fresh run. `--dry-run` stops before
214
+ anything is minted and reports `status: "skipped"`, `reason: "dry-run"` —
215
+ there is nothing to preview, because a claim is decided in a browser.
216
+ Flags: `--no-open` (print the link instead of launching a browser),
217
+ `--timeout <seconds>` (stop waiting sooner than the link's own expiry).
218
+ `setup`, `status`, and `doctor` report whether a team is attached, reading
219
+ `claimed_at`/`team_id` from `.zitadel/secret` (no platform call). `status`
220
+ carries `data.project.claim` as `{"kind": "detached"}` or
221
+ `{"kind": "attached", "team_id": "team_01H…", "claimed_at": "2026-08-01T09:00:00.000Z"}`,
222
+ and `doctor` reports a
223
+ `claim` check. A project with no team is only ever a **warning**, never a
224
+ failure — it works exactly like one with a team, so `doctor` still exits 0
225
+ and `--fix` deliberately does nothing (a claim needs a human in a browser).
226
+ All three stay silent about teams when the project's `server` in
227
+ `zitadel.json` is local or self-hosted, where there is nothing to attach.
74
228
  - `status` — summarize the local runtime and project state.
75
229
  - `eject` (alias `uninstall`) — remove managed files and local Zitadel state;
76
230
  requires `--force` when non-interactive.
77
231
  - `start` — start the managed local Zitadel server and persist runtime metadata
78
- under `.zitadel/local/runtime.json`. Use `--runtime docker` or `--image` for
79
- the Docker backend.
232
+ under `.zitadel/local/runtime.json`. The binary runtime defaults to SQLite
233
+ under `.zitadel/local/nextgen-data/`. Runtime metadata reports the published
234
+ server package version; the repository contributor wrapper reports the
235
+ `dev+<short-commit>` source build it launched. That label names the revision
236
+ the binary was built from, which after a Moon cache hit can be an earlier
237
+ commit whose server sources are byte-identical. Use `--runtime docker` or
238
+ `--image` for the Docker backend.
80
239
  - `stop` — stop the managed runtime while preserving
81
240
  `.zitadel/local/nextgen-data`. Use `stop --all` to sweep all discovered
82
241
  host-wide CLI-managed local runtime processes, including healthy runtimes
@@ -117,10 +276,10 @@ Do not treat a rendered login or registration form as completion.
117
276
 
118
277
  `<zitadel-login>` and `<zitadel-logout>` are Lit elements with open shadow
119
278
  roots. The stable automation hooks live inside nested shadow roots, so a flat
120
- `document.querySelector('[data-testid="zitadel-field-email-input"]')` will not
121
- find them. Browser drivers with shadow-DOM-aware locators, such as Playwright,
122
- can target the hooks directly. Generic DOM-eval drivers should pierce shadow
123
- roots recursively:
279
+ `document.querySelector('[data-testid="zitadel-input-email"]')` will not find
280
+ the native control. Browser drivers with shadow-DOM-aware locators, such as
281
+ Playwright, can target the hooks directly. Generic DOM-eval drivers should
282
+ pierce shadow roots recursively:
124
283
 
125
284
  ```js
126
285
  function deepQuery(sel, root = document) {
@@ -136,22 +295,47 @@ function deepQuery(sel, root = document) {
136
295
  }
137
296
  ```
138
297
 
139
- Use `zitadel-field-email-input`, `zitadel-field-password-input`, and
140
- `zitadel-action-submit-button` for sign-in and registration. For sign-out, open
141
- the user menu button if needed, then pierce to `.signout-btn`; Playwright-style
142
- locators may use `zitadel-logout .signout-btn`. The canonical component hook
143
- list lives in `packages/components/README.md`.
298
+ Use host hooks such as `zitadel-field-email`, `zitadel-field-password`, and
299
+ `zitadel-action-submit` when targeting the Lit atoms. Use native shadow-control
300
+ hooks such as `zitadel-input-email`, `zitadel-input-password`, and
301
+ `zitadel-action-submit-button` when filling or clicking the underlying input or
302
+ button. Hooks stay method-named even when the flow engine names a credential
303
+ field `x-auth-methods#<method>`; only the `name` attribute carries that raw
304
+ form key. Enter inside a field submits the step's primary action, but only for
305
+ key events that carry `key: "Enter"` — drivers whose synthesized key events
306
+ omit it (some CDP wrappers) should click `zitadel-action-submit` instead. For sign-out, open the user menu button if needed, then pierce to
307
+ `.signout-btn`; Playwright-style locators may use `zitadel-logout .signout-btn`.
308
+ The canonical component hook list lives in `packages/components/README.md`.
144
309
 
145
310
  The checked-in automated regression path is `moon run workspace:journey`, which
146
311
  exercises fresh-app setup plus registration, logout, and login across the
147
312
  supported frameworks.
148
313
 
149
314
  Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
150
- then re-run `plan` and `apply`. Managed files carry a marker comment; `eject`
151
- removes only files that still carry it, preserving anything the user replaced.
152
- For app-local development, `--server local` resolves through
153
- `.zitadel/local/runtime.json` and requires a healthy `npx @zitadel/cli@alpha start`
154
- runtime. Runtime-only `.zitadel/local/**` state does not block fresh
155
- same-directory scaffolding. `setup` installs dependencies with the detected
156
- package manager by default; pass `--skip-install` when the agent or host
157
- workflow will install dependencies separately.
315
+ then re-run `plan` and `apply`. Schema and flow files are synced from
316
+ `.zitadel/schemas/*.json` and `.zitadel/flows/*.json`. Login templates
317
+ (branding) are synced from `.zitadel/branding/`: a single `branding.json`
318
+ descriptor (layout, asset URLs) plus a sibling `login.liquid` LiquidJS
319
+ template referenced via `liquid_template_file`. Scaffold them with the
320
+ `branding eject` command (`--design centered|split|split-right|hero|minimal`,
321
+ interactive picker on a TTY) or at project creation with
322
+ `setup --design <name>`, which also publishes revision 1. Branding is
323
+ revisioned and immutable: every edit — including a `.liquid`-only edit —
324
+ plans as a `revise` and `apply` publishes a new revision; the login serves
325
+ the newest one. `plan` validates templates with the authoritative LiquidJS
326
+ validator (`E_VALIDATION` lists rule ids such as `no-script-tag` and
327
+ `mandatory-gates`; every template must keep a trailing
328
+ `{% mandatory_gates %}` tag). `font_url` is not writable yet; asset URLs
329
+ must be absolute `https://`. Keep exactly one descriptor in
330
+ `.zitadel/branding/` — extra `*.json` files there fail the scan.
331
+ Server-provisioned defaults remain a fallback for non-CLI project
332
+ creation, but CLI-created projects are authored from local files first. Flow create, read, list,
333
+ update, and delete are available, while the server enforces lifecycle rules
334
+ such as draft-only edits. Managed files carry a marker comment; `eject` removes only
335
+ files that still carry it, preserving anything the user replaced. For app-local
336
+ development, `--server local` resolves through `.zitadel/local/runtime.json` and
337
+ requires a healthy
338
+ `npx @zitadel/cli@alpha start` runtime. Runtime-only `.zitadel/local/**` state
339
+ does not block fresh same-directory scaffolding. `setup` installs dependencies
340
+ with the detected package manager by default; pass `--skip-install` when the
341
+ agent or host workflow will install dependencies separately.
@@ -0,0 +1,79 @@
1
+ import { E as ZitadelError } from "./oclif-CanO3zdt.mjs";
2
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { readFileSync } from "node:fs";
4
+ //#region src/lib/branding/index.ts
5
+ /**
6
+ * Relative directory (from the project root) where local branding files live:
7
+ * the `branding.json` descriptor plus the sibling `.liquid` template it
8
+ * references. Owned here so commands, syncers, and tests share one source of
9
+ * truth; only `*.json` files are sync-discovered, the template rides along
10
+ * via `liquid_template_file`.
11
+ */
12
+ const BRANDING_DIR = ".zitadel/branding";
13
+ /**
14
+ * Resolves a descriptor's `liquid_template_file` reference to an absolute
15
+ * path. References are relative to the descriptor's directory (all
16
+ * descriptors live flat in {@link BRANDING_DIR}) and must stay inside the
17
+ * project — a reference escaping `cwd` is an error because apply would later
18
+ * write server state back to that path.
19
+ */
20
+ function resolveTemplatePath(cwd, ref) {
21
+ const absolute = isAbsolute(ref) ? ref : resolve(join(cwd, BRANDING_DIR), ref);
22
+ const rel = relative(cwd, absolute);
23
+ if (rel.startsWith("..") || isAbsolute(rel)) throw new ZitadelError("E_VALIDATION", `liquid_template_file ${JSON.stringify(ref)} points outside the project`, { hint: `Keep the template next to its descriptor in ${BRANDING_DIR}/.` });
24
+ return absolute;
25
+ }
26
+ /**
27
+ * Returns the template string a descriptor carries — the inline
28
+ * `liquid_template`, or the content of `liquid_template_file`. Returns
29
+ * undefined when the descriptor has neither (a legal descriptor that only
30
+ * sets layout/asset URLs).
31
+ */
32
+ function readDescriptorTemplate(cwd, data) {
33
+ const descriptor = data;
34
+ if (typeof descriptor.liquid_template === "string") return descriptor.liquid_template;
35
+ if (typeof descriptor.liquid_template_file !== "string") return;
36
+ const path = resolveTemplatePath(cwd, descriptor.liquid_template_file);
37
+ try {
38
+ return readFileSync(path, "utf8");
39
+ } catch (error) {
40
+ throw new ZitadelError("E_VALIDATION", `liquid_template_file ${JSON.stringify(descriptor.liquid_template_file)} cannot be read`, {
41
+ hint: "Create the template file or fix the reference; the `branding eject` command scaffolds a starting point.",
42
+ details: { cause: error instanceof Error ? error.message : String(error) }
43
+ });
44
+ }
45
+ }
46
+ /**
47
+ * Converts a local descriptor to the wire body of `POST /branding`: strips
48
+ * the editor `$schema` affordance and replaces `liquid_template_file` with
49
+ * the inlined template content. Non-throwing on a missing template file (the
50
+ * field is simply left out) — `validate` reports that case with a hint
51
+ * before any planning happens.
52
+ */
53
+ function toBrandingWireBody(cwd, data) {
54
+ const { $schema, liquid_template_file, ...rest } = data;
55
+ const out = { ...rest };
56
+ if (typeof liquid_template_file === "string" && out.liquid_template === void 0) try {
57
+ out.liquid_template = readFileSync(resolveTemplatePath(cwd, liquid_template_file), "utf8");
58
+ } catch {}
59
+ return out;
60
+ }
61
+ /**
62
+ * Converts the server's canonical wire body back to the local descriptor
63
+ * form: when the local descriptor references a template file, the template
64
+ * string moves back out of the JSON into that reference. The caller writes
65
+ * the template content to the referenced file separately.
66
+ */
67
+ function toLocalBrandingBody(canonicalWire, localData) {
68
+ const local = localData;
69
+ if (typeof local.liquid_template_file !== "string") return canonicalWire;
70
+ const { liquid_template, ...rest } = canonicalWire;
71
+ return {
72
+ ...rest,
73
+ liquid_template_file: local.liquid_template_file
74
+ };
75
+ }
76
+ //#endregion
77
+ export { toLocalBrandingBody as a, toBrandingWireBody as i, readDescriptorTemplate as n, resolveTemplatePath as r, BRANDING_DIR as t };
78
+
79
+ //# sourceMappingURL=branding-cygSBPkV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding-cygSBPkV.mjs","names":[],"sources":["../src/lib/branding/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\n\nimport { ZitadelError } from \"../errors\";\n\n/**\n * Relative directory (from the project root) where local branding files live:\n * the `branding.json` descriptor plus the sibling `.liquid` template it\n * references. Owned here so commands, syncers, and tests share one source of\n * truth; only `*.json` files are sync-discovered, the template rides along\n * via `liquid_template_file`.\n */\nexport const BRANDING_DIR = \".zitadel/branding\";\n\ntype BrandingDescriptor = {\n liquid_template?: string;\n liquid_template_file?: string;\n [key: string]: unknown;\n};\n\n/**\n * Resolves a descriptor's `liquid_template_file` reference to an absolute\n * path. References are relative to the descriptor's directory (all\n * descriptors live flat in {@link BRANDING_DIR}) and must stay inside the\n * project — a reference escaping `cwd` is an error because apply would later\n * write server state back to that path.\n */\nexport function resolveTemplatePath(cwd: string, ref: string): string {\n const absolute = isAbsolute(ref) ? ref : resolve(join(cwd, BRANDING_DIR), ref);\n const rel = relative(cwd, absolute);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n throw new ZitadelError(\n \"E_VALIDATION\",\n `liquid_template_file ${JSON.stringify(ref)} points outside the project`,\n { hint: `Keep the template next to its descriptor in ${BRANDING_DIR}/.` },\n );\n }\n return absolute;\n}\n\n/**\n * Returns the template string a descriptor carries — the inline\n * `liquid_template`, or the content of `liquid_template_file`. Returns\n * undefined when the descriptor has neither (a legal descriptor that only\n * sets layout/asset URLs).\n */\nexport function readDescriptorTemplate(cwd: string, data: object): string | undefined {\n const descriptor = data as BrandingDescriptor;\n if (typeof descriptor.liquid_template === \"string\") {\n return descriptor.liquid_template;\n }\n if (typeof descriptor.liquid_template_file !== \"string\") {\n return undefined;\n }\n const path = resolveTemplatePath(cwd, descriptor.liquid_template_file);\n try {\n return readFileSync(path, \"utf8\");\n } catch (error) {\n throw new ZitadelError(\n \"E_VALIDATION\",\n `liquid_template_file ${JSON.stringify(descriptor.liquid_template_file)} cannot be read`,\n {\n hint: \"Create the template file or fix the reference; the `branding eject` command scaffolds a starting point.\",\n details: { cause: error instanceof Error ? error.message : String(error) },\n },\n );\n }\n}\n\n/**\n * Converts a local descriptor to the wire body of `POST /branding`: strips\n * the editor `$schema` affordance and replaces `liquid_template_file` with\n * the inlined template content. Non-throwing on a missing template file (the\n * field is simply left out) — `validate` reports that case with a hint\n * before any planning happens.\n */\nexport function toBrandingWireBody(cwd: string, data: object): object {\n const { $schema, liquid_template_file, ...rest } = data as BrandingDescriptor & {\n $schema?: string;\n };\n void $schema;\n const out: Record<string, unknown> = { ...rest };\n if (typeof liquid_template_file === \"string\" && out.liquid_template === undefined) {\n try {\n out.liquid_template = readFileSync(resolveTemplatePath(cwd, liquid_template_file), \"utf8\");\n } catch {\n // Reported by validate(); keep normalize/hashing total.\n }\n }\n return out;\n}\n\n/**\n * Converts the server's canonical wire body back to the local descriptor\n * form: when the local descriptor references a template file, the template\n * string moves back out of the JSON into that reference. The caller writes\n * the template content to the referenced file separately.\n */\nexport function toLocalBrandingBody(canonicalWire: object, localData: object): object {\n const local = localData as BrandingDescriptor;\n if (typeof local.liquid_template_file !== \"string\") {\n return canonicalWire;\n }\n const { liquid_template, ...rest } = canonicalWire as BrandingDescriptor;\n void liquid_template;\n return { ...rest, liquid_template_file: local.liquid_template_file };\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,eAAe;;;;;;;;AAe5B,SAAgB,oBAAoB,KAAa,KAAqB;CACpE,MAAM,WAAW,WAAW,IAAI,GAAG,MAAM,QAAQ,KAAK,KAAK,aAAa,EAAE,IAAI;CAC9E,MAAM,MAAM,SAAS,KAAK,SAAS;AACnC,KAAI,IAAI,WAAW,KAAK,IAAI,WAAW,IAAI,CACzC,OAAM,IAAI,aACR,gBACA,wBAAwB,KAAK,UAAU,IAAI,CAAC,8BAC5C,EAAE,MAAM,+CAA+C,aAAa,KAAK,CAC1E;AAEH,QAAO;;;;;;;;AAST,SAAgB,uBAAuB,KAAa,MAAkC;CACpF,MAAM,aAAa;AACnB,KAAI,OAAO,WAAW,oBAAoB,SACxC,QAAO,WAAW;AAEpB,KAAI,OAAO,WAAW,yBAAyB,SAC7C;CAEF,MAAM,OAAO,oBAAoB,KAAK,WAAW,qBAAqB;AACtE,KAAI;AACF,SAAO,aAAa,MAAM,OAAO;UAC1B,OAAO;AACd,QAAM,IAAI,aACR,gBACA,wBAAwB,KAAK,UAAU,WAAW,qBAAqB,CAAC,kBACxE;GACE,MAAM;GACN,SAAS,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE;GAC3E,CACF;;;;;;;;;;AAWL,SAAgB,mBAAmB,KAAa,MAAsB;CACpE,MAAM,EAAE,SAAS,sBAAsB,GAAG,SAAS;CAInD,MAAM,MAA+B,EAAE,GAAG,MAAM;AAChD,KAAI,OAAO,yBAAyB,YAAY,IAAI,oBAAoB,KAAA,EACtE,KAAI;AACF,MAAI,kBAAkB,aAAa,oBAAoB,KAAK,qBAAqB,EAAE,OAAO;SACpF;AAIV,QAAO;;;;;;;;AAST,SAAgB,oBAAoB,eAAuB,WAA2B;CACpF,MAAM,QAAQ;AACd,KAAI,OAAO,MAAM,yBAAyB,SACxC,QAAO;CAET,MAAM,EAAE,iBAAiB,GAAG,SAAS;AAErC,QAAO;EAAE,GAAG;EAAM,sBAAsB,MAAM;EAAsB"}
@@ -0,0 +1,66 @@
1
+ import { M as serverKind, j as publicCliCommand } from "./oclif-CanO3zdt.mjs";
2
+ //#region src/lib/claim-state.ts
3
+ /**
4
+ * Whether the local record says a team owns this project.
5
+ *
6
+ * Both fields or neither: a half-written record is not an attachment, and
7
+ * treating it as one would hand callers a `team_id` with no idea when it
8
+ * landed. Shared with `zitadel claim`, which asks the same question before it
9
+ * decides to skip, so the two cannot drift as the record grows fields.
10
+ *
11
+ * Deliberately separate from {@link claimState}: the command must answer this
12
+ * on any server, while the nudges only ask it on the cloud.
13
+ */
14
+ function isAttached(secret) {
15
+ return Boolean(secret.claimed_at && secret.team_id);
16
+ }
17
+ /**
18
+ * Decides the attachment state from local state alone.
19
+ *
20
+ * **Why no network call.** The contract exposes no ambient claim-state read:
21
+ * `getClaimStatus` answers for one live challenge and rejects a secret that did
22
+ * not mint it, and claim attributes were deliberately kept off
23
+ * `GET /projects/{id}` because they belong to the grant, not the project. So
24
+ * `.zitadel/secret` is the only source, which is also why `status` and `doctor`
25
+ * stay fast and work offline. `zitadel claim` reaches the same conclusion the
26
+ * same way before it decides to skip.
27
+ *
28
+ * **Why cloud only.** Claiming attaches a project to a team on the platform.
29
+ * A local or self-hosted server has no such platform, and `--server local` is
30
+ * the documented dev loop, so an ungated nudge would advertise an impossible
31
+ * action for the entire life of a local project.
32
+ *
33
+ * The local record can be stale in one direction: a project claimed from
34
+ * another machine, or a `.zitadel/secret` restored from a backup, reads as
35
+ * `detached` here. That is tolerable because everything downstream is
36
+ * advisory, and `zitadel claim` itself turns the resulting `409` into a clean
37
+ * skip rather than an error.
38
+ */
39
+ function claimState(input) {
40
+ if (serverKind.value(input.server) !== "cloud") return { kind: "not-applicable" };
41
+ if (isAttached(input.secret)) return {
42
+ kind: "attached",
43
+ team_id: input.secret.team_id,
44
+ claimed_at: input.secret.claimed_at
45
+ };
46
+ return { kind: "detached" };
47
+ }
48
+ /**
49
+ * The nudge for `next_actions`, quoting the command the way `journey-guidance`
50
+ * does.
51
+ *
52
+ * Deliberately says nothing about deletion: unattached projects are framed as
53
+ * temporary, but the epic's 14-day lifetime is not enforced anywhere yet, so
54
+ * promising it would be a lie the product cannot keep.
55
+ */
56
+ function claimAction(cliVersion) {
57
+ return `This project is temporary until you attach it to a team: run ${publicCliCommand("claim", cliVersion)} to make it permanent. Nothing about the project changes, so users, passkeys, and the issuer keep working.`;
58
+ }
59
+ /** The command for `next_commands`. */
60
+ function claimCommand(cliVersion) {
61
+ return publicCliCommand("claim", cliVersion);
62
+ }
63
+ //#endregion
64
+ export { isAttached as i, claimCommand as n, claimState as r, claimAction as t };
65
+
66
+ //# sourceMappingURL=claim-state-DlfD6Res.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-state-DlfD6Res.mjs","names":[],"sources":["../src/lib/claim-state.ts"],"sourcesContent":["import { serverKind } from \"./oclif/server-kind\";\nimport type { ZitadelSecret } from \"./project\";\nimport { publicCliCommand } from \"./public-cli\";\n\n/**\n * Whether this project is attached to a team, as the CLI can tell locally.\n *\n * `not-applicable` is its own variant rather than a boolean flag on the caller\n * because \"there is nothing to attach here\" and \"nothing is attached yet\" want\n * opposite output: the first prints nothing at all, the second nudges.\n */\nexport type ClaimState =\n | { kind: \"attached\"; team_id: string; claimed_at: string }\n | { kind: \"detached\" }\n | { kind: \"not-applicable\" };\n\n/**\n * Whether the local record says a team owns this project.\n *\n * Both fields or neither: a half-written record is not an attachment, and\n * treating it as one would hand callers a `team_id` with no idea when it\n * landed. Shared with `zitadel claim`, which asks the same question before it\n * decides to skip, so the two cannot drift as the record grows fields.\n *\n * Deliberately separate from {@link claimState}: the command must answer this\n * on any server, while the nudges only ask it on the cloud.\n */\nexport function isAttached(\n secret: Pick<ZitadelSecret, \"claimed_at\" | \"team_id\">,\n): secret is { claimed_at: string; team_id: string } {\n return Boolean(secret.claimed_at && secret.team_id);\n}\n\n/**\n * Decides the attachment state from local state alone.\n *\n * **Why no network call.** The contract exposes no ambient claim-state read:\n * `getClaimStatus` answers for one live challenge and rejects a secret that did\n * not mint it, and claim attributes were deliberately kept off\n * `GET /projects/{id}` because they belong to the grant, not the project. So\n * `.zitadel/secret` is the only source, which is also why `status` and `doctor`\n * stay fast and work offline. `zitadel claim` reaches the same conclusion the\n * same way before it decides to skip.\n *\n * **Why cloud only.** Claiming attaches a project to a team on the platform.\n * A local or self-hosted server has no such platform, and `--server local` is\n * the documented dev loop, so an ungated nudge would advertise an impossible\n * action for the entire life of a local project.\n *\n * The local record can be stale in one direction: a project claimed from\n * another machine, or a `.zitadel/secret` restored from a backup, reads as\n * `detached` here. That is tolerable because everything downstream is\n * advisory, and `zitadel claim` itself turns the resulting `409` into a clean\n * skip rather than an error.\n */\nexport function claimState(input: {\n secret: Pick<ZitadelSecret, \"claimed_at\" | \"team_id\">;\n server: string;\n}): ClaimState {\n if (serverKind.value(input.server) !== \"cloud\") {\n return { kind: \"not-applicable\" };\n }\n if (isAttached(input.secret)) {\n return {\n kind: \"attached\",\n team_id: input.secret.team_id,\n claimed_at: input.secret.claimed_at,\n };\n }\n return { kind: \"detached\" };\n}\n\n/**\n * The nudge for `next_actions`, quoting the command the way `journey-guidance`\n * does.\n *\n * Deliberately says nothing about deletion: unattached projects are framed as\n * temporary, but the epic's 14-day lifetime is not enforced anywhere yet, so\n * promising it would be a lie the product cannot keep.\n */\nexport function claimAction(cliVersion: string): string {\n return (\n \"This project is temporary until you attach it to a team: run \" +\n `${publicCliCommand(\"claim\", cliVersion)} to make it permanent. ` +\n \"Nothing about the project changes, so users, passkeys, and the issuer keep working.\"\n );\n}\n\n/** The command for `next_commands`. */\nexport function claimCommand(cliVersion: string): string {\n return publicCliCommand(\"claim\", cliVersion);\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,SAAgB,WACd,QACmD;AACnD,QAAO,QAAQ,OAAO,cAAc,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;AAyBrD,SAAgB,WAAW,OAGZ;AACb,KAAI,WAAW,MAAM,MAAM,OAAO,KAAK,QACrC,QAAO,EAAE,MAAM,kBAAkB;AAEnC,KAAI,WAAW,MAAM,OAAO,CAC1B,QAAO;EACL,MAAM;EACN,SAAS,MAAM,OAAO;EACtB,YAAY,MAAM,OAAO;EAC1B;AAEH,QAAO,EAAE,MAAM,YAAY;;;;;;;;;;AAW7B,SAAgB,YAAY,YAA4B;AACtD,QACE,gEACG,iBAAiB,SAAS,WAAW,CAAC;;;AAM7C,SAAgB,aAAa,YAA4B;AACvD,QAAO,iBAAiB,SAAS,WAAW"}
@@ -1,6 +1,7 @@
1
- import { t as BaseCommand } from "../oclif-B7lBzh3R.mjs";
2
- import { o as readZitadelSecret } from "../project-Cd0L3PtM.mjs";
3
- import { a as makeSyncers, i as runSyncLoop, n as summarizePlan, o as environmentSchema, r as buildSyncPlan, t as renderPlan } from "../sync-BojoQm2P.mjs";
1
+ import { j as publicCliCommand, t as BaseCommand } from "../oclif-CanO3zdt.mjs";
2
+ import { l as readZitadelSecret } from "../project-CHhot85s.mjs";
3
+ import { a as buildSyncPlan, i as summarizePlan, l as makeSyncers, n as enumeratePlanResources, r as renderPlan, s as runSyncLoop, t as collectPlanWarnings } from "../sync-BBlACKzT.mjs";
4
+ import { t as environmentSchema } from "../environment-rjRVkJjW.mjs";
4
5
  import { Flags } from "@oclif/core";
5
6
  import { createZitadelClient } from "@zitadel/api/client";
6
7
  import { consola as consola$1 } from "consola";
@@ -34,24 +35,42 @@ var Apply = class Apply extends BaseCommand {
34
35
  token: secret.project_secret
35
36
  }),
36
37
  projectId: secret.project_id,
37
- env
38
+ env,
39
+ cwd
38
40
  });
39
41
  if (!dryRun) {
40
42
  consola$1.start("Syncing schemas and flows to Zitadel");
41
- await runSyncLoop(cwd, syncers);
43
+ const { filesUpdated, applied } = await runSyncLoop(cwd, syncers);
42
44
  consola$1.success("Sync complete");
43
45
  return this.emit({
44
46
  status: "ok",
45
- data: { synced: true }
47
+ data: {
48
+ synced: true,
49
+ changes: applied,
50
+ files_updated: filesUpdated,
51
+ next_actions: applied.length === 0 ? ["Everything is already in sync — no changes were applied."] : ["Changes are live — reload your app to see them.", "Re-run plan to confirm local config and platform are in sync."],
52
+ next_commands: [publicCliCommand("plan", this.meta.cliVersion)]
53
+ }
46
54
  });
47
55
  }
48
56
  consola$1.start("Building plan (dry run)");
49
57
  const plan = await buildSyncPlan(cwd, syncers, true);
50
58
  const summary = summarizePlan(plan);
51
- consola$1.success(`Plan: ${summary.creates} create${summary.creates === 1 ? "" : "s"}, ${summary.updates} update${summary.updates === 1 ? "" : "s"}, ${summary.deletes} delete${summary.deletes === 1 ? "" : "s"}`);
59
+ this.recordTelemetry({
60
+ creates: summary.creates,
61
+ updates: summary.updates,
62
+ revisions: summary.revisions,
63
+ deletes: summary.deletes,
64
+ total: summary.total
65
+ });
66
+ consola$1.success(`Plan: ${summary.creates} create${summary.creates === 1 ? "" : "s"}, ${summary.updates} update${summary.updates === 1 ? "" : "s"}, ${summary.revisions} new revision${summary.revisions === 1 ? "" : "s"}, ${summary.deletes} delete${summary.deletes === 1 ? "" : "s"}`);
52
67
  return this.emit({
53
68
  status: "ok",
54
- data: summary,
69
+ data: {
70
+ ...summary,
71
+ changes: enumeratePlanResources(plan),
72
+ warnings: collectPlanWarnings(plan)
73
+ },
55
74
  pretty: renderPlan(plan, isTTY)
56
75
  });
57
76
  }
@@ -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 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;CAC9B,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"}
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 {\n buildSyncPlan,\n collectPlanWarnings,\n enumeratePlanResources,\n makeSyncers,\n renderPlan,\n runSyncLoop,\n summarizePlan,\n} from \"../lib/sync\";\nimport { readZitadelSecret } from \"../lib/project\";\nimport { publicCliCommand } from \"../lib/public-cli\";\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 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({\n client,\n projectId: secret.project_id,\n env,\n cwd,\n });\n\n if (!dryRun) {\n consola.start(\"Syncing schemas and flows to Zitadel\");\n const { filesUpdated, applied } = await runSyncLoop(cwd, syncers);\n consola.success(\"Sync complete\");\n return this.emit({\n status: \"ok\",\n data: {\n synced: true,\n // Platform resources this run touched (with resulting ids);\n // `files_updated` stays the local write-backs only.\n changes: applied,\n files_updated: filesUpdated,\n next_actions:\n applied.length === 0\n ? [\"Everything is already in sync — no changes were applied.\"]\n : [\n \"Changes are live — reload your app to see them.\",\n \"Re-run plan to confirm local config and platform are in sync.\",\n ],\n next_commands: [publicCliCommand(\"plan\", this.meta.cliVersion)],\n },\n });\n }\n\n consola.start(\"Building plan (dry run)\");\n const plan = await buildSyncPlan(cwd, syncers, true);\n const summary = summarizePlan(plan);\n this.recordTelemetry({\n creates: summary.creates,\n updates: summary.updates,\n revisions: summary.revisions,\n deletes: summary.deletes,\n total: summary.total,\n });\n consola.success(\n `Plan: ${summary.creates} create${summary.creates === 1 ? \"\" : \"s\"}, ` +\n `${summary.updates} update${summary.updates === 1 ? \"\" : \"s\"}, ` +\n `${summary.revisions} new revision${summary.revisions === 1 ? \"\" : \"s\"}, ` +\n `${summary.deletes} delete${summary.deletes === 1 ? \"\" : \"s\"}`,\n );\n return this.emit({\n status: \"ok\",\n data: {\n ...summary,\n changes: enumeratePlanResources(plan),\n warnings: collectPlanWarnings(plan),\n },\n pretty: renderPlan(plan, isTTY),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,IAAqB,QAArB,MAAqB,cAAc,YAAY;CAC7C,OAAgB,cAAc;CAC9B,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;GAC1B,QALa,oBAAoB;IACjC,SAAS;IACT,OAAO,OAAO;IACf,CAEO;GACN,WAAW,OAAO;GAClB;GACA;GACD,CAAC;AAEF,MAAI,CAAC,QAAQ;AACX,aAAQ,MAAM,uCAAuC;GACrD,MAAM,EAAE,cAAc,YAAY,MAAM,YAAY,KAAK,QAAQ;AACjE,aAAQ,QAAQ,gBAAgB;AAChC,UAAO,KAAK,KAAK;IACf,QAAQ;IACR,MAAM;KACJ,QAAQ;KAGR,SAAS;KACT,eAAe;KACf,cACE,QAAQ,WAAW,IACf,CAAC,2DAA2D,GAC5D,CACE,mDACA,gEACD;KACP,eAAe,CAAC,iBAAiB,QAAQ,KAAK,KAAK,WAAW,CAAC;KAChE;IACF,CAAC;;AAGJ,YAAQ,MAAM,0BAA0B;EACxC,MAAM,OAAO,MAAM,cAAc,KAAK,SAAS,KAAK;EACpD,MAAM,UAAU,cAAc,KAAK;AACnC,OAAK,gBAAgB;GACnB,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,WAAW,QAAQ;GACnB,SAAS,QAAQ;GACjB,OAAO,QAAQ;GAChB,CAAC;AACF,YAAQ,QACN,SAAS,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC9D,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,IAAI,IAC1D,QAAQ,UAAU,eAAe,QAAQ,cAAc,IAAI,KAAK,IAAI,IACpE,QAAQ,QAAQ,SAAS,QAAQ,YAAY,IAAI,KAAK,MAC5D;AACD,SAAO,KAAK,KAAK;GACf,QAAQ;GACR,MAAM;IACJ,GAAG;IACH,SAAS,uBAAuB,KAAK;IACrC,UAAU,oBAAoB,KAAK;IACpC;GACD,QAAQ,WAAW,MAAM,MAAM;GAChC,CAAC"}