@zitadel/cli 0.1.0 → 0.3.0
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 +2219 -840
- package/dist/index.mjs +9497 -9715
- package/dist/index.mjs.map +1 -1
- package/dist/lib/oclif/help.mjs +66 -20
- package/dist/lib/oclif/help.mjs.map +1 -1
- package/dist/skills-hint-N1H5-ny1.mjs +4444 -0
- package/dist/skills-hint-N1H5-ny1.mjs.map +1 -0
- package/oclif.manifest.json +4221 -1311
- package/package.json +50 -18
- package/skills/zitadel-cli/SKILL.md +198 -58
- package/skills/zitadel-cli/references/driving-login-ui.md +50 -15
- package/dist/skills-hint-BSspFzhr.mjs +0 -173
- package/dist/skills-hint-BSspFzhr.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zitadel/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Agent-friendly Zitadel CLI",
|
|
5
5
|
"homepage": "https://github.com/zitadel/nextgen/tree/main/apps/cli#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -49,31 +49,31 @@
|
|
|
49
49
|
],
|
|
50
50
|
"topics": {
|
|
51
51
|
"users": {
|
|
52
|
-
"description": "
|
|
52
|
+
"description": "Create and manage users."
|
|
53
53
|
},
|
|
54
54
|
"teams": {
|
|
55
|
-
"description": "
|
|
55
|
+
"description": "Create and manage teams."
|
|
56
56
|
},
|
|
57
57
|
"sessions": {
|
|
58
|
-
"description": "
|
|
58
|
+
"description": "View and revoke sessions."
|
|
59
59
|
},
|
|
60
60
|
"events": {
|
|
61
|
-
"description": "
|
|
61
|
+
"description": "View events."
|
|
62
62
|
},
|
|
63
63
|
"grants": {
|
|
64
|
-
"description": "Manage
|
|
64
|
+
"description": "Manage access grants."
|
|
65
65
|
},
|
|
66
66
|
"projects": {
|
|
67
|
-
"description": "
|
|
67
|
+
"description": "View and update projects."
|
|
68
68
|
},
|
|
69
69
|
"schemas": {
|
|
70
|
-
"description": "
|
|
70
|
+
"description": "View user schemas."
|
|
71
71
|
},
|
|
72
72
|
"branding": {
|
|
73
|
-
"description": "
|
|
73
|
+
"description": "Manage the login branding."
|
|
74
74
|
},
|
|
75
75
|
"auth-method": {
|
|
76
|
-
"description": "
|
|
76
|
+
"description": "Configure how users sign in."
|
|
77
77
|
},
|
|
78
78
|
"auth-method:password": {
|
|
79
79
|
"description": "Turn password sign-in on or off."
|
|
@@ -83,6 +83,36 @@
|
|
|
83
83
|
},
|
|
84
84
|
"auth-method:sso": {
|
|
85
85
|
"description": "Add or remove identity providers."
|
|
86
|
+
},
|
|
87
|
+
"origin": {
|
|
88
|
+
"description": "Manage the project's allowed origins."
|
|
89
|
+
},
|
|
90
|
+
"release": {
|
|
91
|
+
"description": "Build and manage releases."
|
|
92
|
+
},
|
|
93
|
+
"deployment": {
|
|
94
|
+
"description": "Deploy releases and roll them back."
|
|
95
|
+
},
|
|
96
|
+
"preview": {
|
|
97
|
+
"description": "Deploy and manage pull-request preview URLs."
|
|
98
|
+
},
|
|
99
|
+
"variable": {
|
|
100
|
+
"description": "Manage project variables."
|
|
101
|
+
},
|
|
102
|
+
"idps": {
|
|
103
|
+
"description": "Manage identity provider connections."
|
|
104
|
+
},
|
|
105
|
+
"flow-definitions": {
|
|
106
|
+
"description": "View login flows."
|
|
107
|
+
},
|
|
108
|
+
"policies": {
|
|
109
|
+
"description": "View and test policies."
|
|
110
|
+
},
|
|
111
|
+
"env": {
|
|
112
|
+
"description": "Show and bind environments."
|
|
113
|
+
},
|
|
114
|
+
"api": {
|
|
115
|
+
"description": "Call any operation of the Zitadel API."
|
|
86
116
|
}
|
|
87
117
|
}
|
|
88
118
|
},
|
|
@@ -97,20 +127,20 @@
|
|
|
97
127
|
"@oclif/plugin-search": "^1.2.50",
|
|
98
128
|
"@oclif/plugin-version": "^2.2.46",
|
|
99
129
|
"@oclif/plugin-which": "^3.2.55",
|
|
100
|
-
"@zitadel/api": "0.
|
|
101
|
-
"@zitadel/config": "0.
|
|
102
|
-
"@zitadel/server": "0.
|
|
130
|
+
"@zitadel/api": "0.3.0",
|
|
131
|
+
"@zitadel/config": "0.3.0",
|
|
132
|
+
"@zitadel/server": "0.3.0",
|
|
103
133
|
"ajv": "^8.20.0",
|
|
104
134
|
"consola": "^3.4.2",
|
|
105
135
|
"magicast": "^0.5.3",
|
|
106
136
|
"mixpanel": "^0.18.1",
|
|
137
|
+
"picocolors": "^1.1.1",
|
|
138
|
+
"rosie-skills": "^0.8.5",
|
|
107
139
|
"safe-stable-stringify": "^2.5.0",
|
|
108
140
|
"semver": "^7.7.3",
|
|
109
141
|
"undici": "^7.29.1",
|
|
110
142
|
"wrap-ansi": "^7.0.0",
|
|
111
|
-
"zod": "^4.3.6"
|
|
112
|
-
"picocolors": "^1.1.1",
|
|
113
|
-
"rosie-skills": "^0.8.5"
|
|
143
|
+
"zod": "^4.3.6"
|
|
114
144
|
},
|
|
115
145
|
"devDependencies": {
|
|
116
146
|
"@types/node": "^24.19.1",
|
|
@@ -118,13 +148,15 @@
|
|
|
118
148
|
"@types/wrap-ansi": "^3.0.0",
|
|
119
149
|
"@vitest/coverage-v8": "^4.1.11",
|
|
120
150
|
"@zitadel/api-mock": "0.0.0",
|
|
121
|
-
"@zitadel/sdk-next": "0.
|
|
151
|
+
"@zitadel/sdk-next": "0.3.0",
|
|
152
|
+
"@zitadel/vitest-config": "0.0.0",
|
|
122
153
|
"msw": "^2.14.6",
|
|
123
154
|
"oclif": "^4.17.46",
|
|
124
155
|
"tsdown": "^0.23.0",
|
|
125
156
|
"typescript": "^6.0.3",
|
|
126
157
|
"vite": "^8.3.1",
|
|
127
|
-
"vitest": "^4.1.11"
|
|
158
|
+
"vitest": "^4.1.11",
|
|
159
|
+
"yaml": "^2.9.0"
|
|
128
160
|
},
|
|
129
161
|
"scripts": {
|
|
130
162
|
"build": "tsdown",
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
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
7
|
for a Next.js, Nuxt, SvelteKit, SolidStart, Qwik Router, TanStack Start, React,
|
|
8
|
-
Vue, Angular, Solid, Svelte, or Qwik app, or
|
|
8
|
+
Vue, Angular, Solid, Svelte, or Qwik app, or build and deploy Zitadel config
|
|
9
9
|
changes from repo state.
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -67,11 +67,47 @@ upgrade, so upgrade the app and rerun rather than retrying.
|
|
|
67
67
|
drops records or fields, so you end up reasoning from partial data. It is
|
|
68
68
|
finite — read it whole, or narrow it *at the source*: use a `list` verb's
|
|
69
69
|
`filter_fields` and pagination to fetch fewer records, or pipe to `jq` to pull
|
|
70
|
-
just what you need (e.g. `… resources --json | jq '.data.
|
|
70
|
+
just what you need (e.g. `… resources --json | jq '.data.items[].topic'`).
|
|
71
71
|
`--help` output is bounded too — read it in full rather than `head`-ing it, or
|
|
72
72
|
you will miss flags further down.
|
|
73
73
|
- **Working directory.** Add `--cwd <path>` when operating outside the current
|
|
74
|
-
working directory.
|
|
74
|
+
working directory. A relative `--env-file` path is still read from the
|
|
75
|
+
current directory, not from `--cwd` (`--cwd app --env-file app/.env.ci`):
|
|
76
|
+
Node reads `--env-file` itself before the CLI starts and exits 9 with
|
|
77
|
+
`node: <path>: not found`, and no envelope, when the file is not there.
|
|
78
|
+
- **Server and project.** A command that talks to a project reads
|
|
79
|
+
`ZITADEL_URL`, `ZITADEL_PROJECT_ID` and `ZITADEL_PROJECT_SECRET`, each from the
|
|
80
|
+
first source that has it: `--server` (the URL only), `--env-file <path>`, the
|
|
81
|
+
process environment, `.env.<env>.local`, `.env.local`, `.env.<env>`, `.env`,
|
|
82
|
+
then `.zitadel/secret`. Only the default `development` environment reads
|
|
83
|
+
`.env.local` and `.zitadel/secret` (setup's project); `--env-file` reads that
|
|
84
|
+
file and the process environment only.
|
|
85
|
+
`<env>` is `--env`, else `ZITADEL_ENV`, else a hosting platform's own signal
|
|
86
|
+
(`VERCEL_ENV`, or Netlify's `CONTEXT` inside a Netlify build), else
|
|
87
|
+
`development`. A missing value fails with `E_VALIDATION` naming the variable
|
|
88
|
+
and the files it looked in; there is no cloud default outside `setup`, and
|
|
89
|
+
the server is never read from `zitadel.json`. `zitadel env` shows what resolved and where
|
|
90
|
+
each value came from; `zitadel env add <name>` binds another environment to a
|
|
91
|
+
project. The project it creates has never had a release, so the environment
|
|
92
|
+
serves the server's own defaults until something is deployed to it: run the
|
|
93
|
+
`deploy` the result's `next_commands` names, against the file it wrote
|
|
94
|
+
(`--env-file .env.<name>.local`). A person at a terminal is asked whether to
|
|
95
|
+
deploy there and then; your runs never are, so they never deploy — run that
|
|
96
|
+
`deploy` yourself. `data.deployment` carries the `release_id`,
|
|
97
|
+
`content_hash`, `deployment_id` and `origins` when a person accepted, and is
|
|
98
|
+
null otherwise — a failed deploy is a warning, never a failed `env add`.
|
|
99
|
+
- **Origins.** `zitadel origin add <url> --kind primary|preview`, `origin list`
|
|
100
|
+
and `origin rm <url>` manage the URLs a project serves: a `primary` origin is
|
|
101
|
+
where the app is served, a `preview` pattern (it may hold a `*`) is where
|
|
102
|
+
pull-request builds run. They need the project secret, not the preview token.
|
|
103
|
+
An empty list admits every origin, so removing the last one opens the project
|
|
104
|
+
up; `origin rm` asks first and needs `--force` when non-interactive. Removing
|
|
105
|
+
a preview pattern leaves its live previews served until they expire.
|
|
106
|
+
`deploy` lands only on primary origins and fails with `E_VALIDATION` without
|
|
107
|
+
one; its `next_commands` name the step that gives the project one. `setup`
|
|
108
|
+
0.1.0 registered the app's URL (`ZITADEL_ISSUER`) as a `preview` origin,
|
|
109
|
+
which `origin add --kind primary` refuses with `E_CONFLICT`: `doctor` warns
|
|
110
|
+
(`issuer-origin`) and `doctor --fix` makes it primary.
|
|
75
111
|
- **Telemetry.** The CLI sends anonymous usage telemetry by default, and
|
|
76
112
|
agent-driven runs are exactly the ones worth learning from — leave it on.
|
|
77
113
|
Don't add `--no-telemetry` to commands. An opt-out exists for users who need
|
|
@@ -122,36 +158,40 @@ upgrade, so upgrade the app and rerun rather than retrying.
|
|
|
122
158
|
`${{ NAME }}` reference to a project variable is not a secret and may stay on
|
|
123
159
|
argv (a connection's `client_secret` takes nothing else); write it in single
|
|
124
160
|
quotes, since inside double quotes the shell reads `${` as its own expansion.
|
|
125
|
-
- **Streams.** Capture stdout and stderr separately when scripting
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
- **Streams.** Capture stdout and stderr separately when scripting, into files
|
|
162
|
+
outside the app directory: `setup` scaffolds a new app only into an empty
|
|
163
|
+
directory. The machine contract is one parseable JSON object on stdout;
|
|
164
|
+
installer, audit, and package-manager progress belongs on stderr.
|
|
128
165
|
|
|
129
166
|
## Discovering commands
|
|
130
167
|
|
|
131
168
|
The CLI ships its own command list and per-command flags, always correct for the
|
|
132
169
|
installed version. **Do not guess a command name.** Before running any command you
|
|
133
|
-
are not certain of,
|
|
170
|
+
are not certain of, look it up first and use the exact name the CLI prints:
|
|
134
171
|
command and resource names — including whether a resource is singular or plural
|
|
135
|
-
and the exact verb — are whatever
|
|
136
|
-
wrong guess costs a failed call and a round of recovery; one cheap
|
|
137
|
-
front avoids it. `zitadel --help`
|
|
138
|
-
|
|
172
|
+
and the exact verb — are whatever the CLI shows, never what you would assume. A
|
|
173
|
+
wrong guess costs a failed call and a round of recovery; one cheap lookup up
|
|
174
|
+
front avoids it. `zitadel --help` lists each family of commands (`users`,
|
|
175
|
+
`release`) as one row, so it stays short; `zitadel commands` is the flat list of
|
|
176
|
+
every command, verbs included, and `zitadel <family> --help` lists one family's
|
|
177
|
+
verbs. Treat the CLI's own output as authoritative over anything you remember:
|
|
139
178
|
|
|
140
179
|
```sh
|
|
141
|
-
npx @zitadel/cli@latest --help # every command, grouped by purpose
|
|
180
|
+
npx @zitadel/cli@latest --help # every command family, grouped by purpose
|
|
142
181
|
npx @zitadel/cli@latest commands # flat list of all commands
|
|
143
182
|
npx @zitadel/cli@latest <command> --help # a command's flags, which are required, and examples
|
|
144
183
|
npx @zitadel/cli@latest resources --json # the resource surface in one call (see below); contacts no server
|
|
145
184
|
npx @zitadel/cli@latest api ls --json # every API operation, its parameters, body and paging; contacts no server
|
|
146
185
|
```
|
|
147
186
|
|
|
148
|
-
`--help` groups the
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
memory, since they change as the CLI gains commands.
|
|
187
|
+
`--help` groups the commands by what you are doing: getting started, local
|
|
188
|
+
development, users and teams, authentication, configuration and releases, and
|
|
189
|
+
diagnostics and utilities. A family such as `users` is one row, and
|
|
190
|
+
`zitadel users --help` lists its verbs. Read the groups from `--help` rather than
|
|
191
|
+
from memory, since they change as the CLI gains commands.
|
|
153
192
|
|
|
154
|
-
Use `zitadel --help` to find *which* command to run
|
|
193
|
+
Use `zitadel --help` to find *which* family or command to run, and
|
|
194
|
+
`zitadel commands` for the exact verb. Use `resources --json` when
|
|
155
195
|
you need a resource's *fields*: it reports the whole runtime-resource surface at
|
|
156
196
|
once — every resource, its verbs, each verb's `create_fields` / `update_fields`
|
|
157
197
|
(flag name, kind, `required`, any closed value set), its `filter_fields` /
|
|
@@ -161,6 +201,11 @@ over reading `<resource> --help` one at a time. A `list` verb's envelope carries
|
|
|
161
201
|
you pass `--all`, so a "how many …?" question is one `list --all --json` call:
|
|
162
202
|
read the count, do not page and tally.
|
|
163
203
|
|
|
204
|
+
To find the users still on an older revision of a schema, filter on the
|
|
205
|
+
schema's object type and exclude the newest revision's URL:
|
|
206
|
+
`users list --filter schema_object_type=equals:<type> --filter
|
|
207
|
+
schema=not_equals:<newest-url> --all --json`.
|
|
208
|
+
|
|
164
209
|
## Reading the envelope
|
|
165
210
|
|
|
166
211
|
Each invocation prints one JSON object:
|
|
@@ -181,19 +226,30 @@ Each invocation prints one JSON object:
|
|
|
181
226
|
success: a warning usually means a follow-up step is needed.
|
|
182
227
|
- A `list` verb emits `data: { items, count, next_page_token }`; when a page
|
|
183
228
|
remains, `data.next_commands` carries the exact command for the next page.
|
|
184
|
-
- `
|
|
185
|
-
(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
229
|
+
- `deploy` emits `data.content_hash` (the release digest), `data.origins`
|
|
230
|
+
(the origins it went live on) and `data.resources` (how many it pinned). A
|
|
231
|
+
re-run on unchanged config returns the same digest. Use it to confirm what
|
|
232
|
+
went live.
|
|
233
|
+
- `preview` is `deploy` for a pull-request build: it deploys the release to the
|
|
234
|
+
build's preview URLs (from the platform, or `--origin`) for `--ttl` and emits
|
|
235
|
+
`data.previews` plus `data.release_env` (the `NEXT_PUBLIC_ZITADEL_RELEASE`
|
|
236
|
+
line to pin). It uses `ZITADEL_PROJECT_SECRET` until the server accepts the narrower preview token (#1575), never the other credential, and
|
|
237
|
+
outside a preview or development build it is a no-op — even with an explicit
|
|
238
|
+
`--origin`, which selects targets but never authorizes a preview deploy. A CI
|
|
239
|
+
build other than Vercel or Netlify must set `ZITADEL_ENV=preview`, or it is
|
|
240
|
+
skipped too. `preview list` and `preview rm <url>` read and end live previews.
|
|
241
|
+
- `deployment rollback` emits `data.release_id` (the release live again),
|
|
242
|
+
`data.origins` and `data.pointers`, the `{kind, handle, revision_id}`
|
|
243
|
+
revisions that release pins; the `schema` one is what new registrations use.
|
|
244
|
+
Users keep their schema revision and attributes: a rollback does not migrate
|
|
245
|
+
them back. Without `data.pointers`, a warning says the release could not be
|
|
246
|
+
read.
|
|
247
|
+
- `deploy --dry-run` and `deploy` emit `data.diff`, the git diff the terminal
|
|
248
|
+
shows, against the live release's commit (`diff.against`): `diff.files` holds
|
|
249
|
+
one `{path, status, previous_path?, patch}` per changed file (`status` is
|
|
250
|
+
`added`, `modified`, `deleted`, `renamed` or `copied`; `patch` is its unified
|
|
251
|
+
diff). Review it before you deploy. `files` is `[]` when nothing changed and
|
|
252
|
+
`null` when there is nothing to diff against, with `diff.note` saying why.
|
|
197
253
|
- A write verb's `--dry-run` reports what it would send without calling the
|
|
198
254
|
platform, so you can preview a mutation before making it.
|
|
199
255
|
- `api` emits the response body as `data`, exactly as the API reference
|
|
@@ -208,11 +264,12 @@ Common error codes point at their own fix in `next_commands`:
|
|
|
208
264
|
`details` often lists the missing or available values.
|
|
209
265
|
- `E_CREDENTIAL_MISSING`: a connection's `client_id` or `client_secret` names a
|
|
210
266
|
project variable that does not exist; `details.variable` is the name. Publish it
|
|
211
|
-
with `
|
|
267
|
+
with `variable set NAME` (add `--secret` for the secret), then retry.
|
|
212
268
|
- `E_LOCAL_SERVER_NOT_RUNNING`: start the local runtime, then retry with
|
|
213
269
|
`--server local`.
|
|
214
|
-
- `E_NOT_FOUND`: an HTTP 404 — a missing resource,
|
|
215
|
-
|
|
270
|
+
- `E_NOT_FOUND`: an HTTP 404 — a missing resource, an operation the server's
|
|
271
|
+
version does not have (`details.body.code` is `req.not_found`), or a
|
|
272
|
+
`--server` that is not a Zitadel platform API. Follow `next_commands`.
|
|
216
273
|
- `E_PORT_IN_USE`: the requested local runtime port already has a listener; stop
|
|
217
274
|
that process or choose another port.
|
|
218
275
|
|
|
@@ -230,8 +287,9 @@ Exit codes mirror the error class (3 = validation, 4 = network or not-found,
|
|
|
230
287
|
5 = conflict, 1 = auth, 2 = not-implemented, 130 = cancelled). `E_NETWORK`
|
|
231
288
|
covers a server that answered 5xx and one that never answered — refused,
|
|
232
289
|
unresolvable, or silent past the 30-second request deadline. Ctrl-C exits
|
|
233
|
-
130: a command waiting on the server
|
|
234
|
-
any other command exits within a second, possibly
|
|
290
|
+
130: a command waiting on the server, or on an answer at a prompt, stops at
|
|
291
|
+
once with `E_CANCELLED`, and any other command exits within a second, possibly
|
|
292
|
+
without an envelope. In human mode a cancel prints no error block. An
|
|
235
293
|
unknown command is handled by the CLI's help layer, not the envelope.
|
|
236
294
|
|
|
237
295
|
## Clarify before you configure
|
|
@@ -276,7 +334,7 @@ like. Read the current sets from `setup --help`, but know the traps:
|
|
|
276
334
|
enabled**; `passkey-first` leads with a passkey. Neither one gives you
|
|
277
335
|
password-only. If the developer does not want passkeys, turn them off
|
|
278
336
|
yourself: set `x-auth-methods.passkey.enabled` to `false` in
|
|
279
|
-
`.zitadel/schemas/*.json`, then `
|
|
337
|
+
`.zitadel/schemas/*.json`, then `deploy`.
|
|
280
338
|
|
|
281
339
|
Translate the developer's answers into the right axis values, then **verify
|
|
282
340
|
against what actually deployed**: read the live schema back
|
|
@@ -291,13 +349,12 @@ invocation) or were explicitly told to just set it up.
|
|
|
291
349
|
## Answering the user
|
|
292
350
|
|
|
293
351
|
Report what changed, in the user's terms — not how you drove the CLI. The
|
|
294
|
-
flags, `--json`, the
|
|
352
|
+
flags, `--json`, the deploy flow, exit codes, and the number of
|
|
295
353
|
commands you ran are your internal business; the user asked for an outcome, not
|
|
296
354
|
a transcript. Say what they got.
|
|
297
355
|
|
|
298
356
|
- Good: "Added an optional phone number to your user schema and deployed it."
|
|
299
|
-
- Avoid: "I ran `
|
|
300
|
-
both returned ok."
|
|
357
|
+
- Avoid: "I ran `deploy` with `--non-interactive --json` and it returned ok."
|
|
301
358
|
|
|
302
359
|
Quote a command or flag only when the user asks *how* something works, or when
|
|
303
360
|
they need to run something themselves. Otherwise keep the mechanics out of the
|
|
@@ -314,21 +371,51 @@ npx @zitadel/cli@latest doctor --non-interactive --json
|
|
|
314
371
|
npx @zitadel/cli@latest start --non-interactive --json
|
|
315
372
|
npx @zitadel/cli@latest setup --framework next --server local --non-interactive --json
|
|
316
373
|
npx @zitadel/cli@latest doctor --non-interactive --json
|
|
317
|
-
npx @zitadel/cli@latest
|
|
318
|
-
npx @zitadel/cli@latest apply --non-interactive --json
|
|
374
|
+
npx @zitadel/cli@latest deploy -m "<why this release changes>" --non-interactive --json
|
|
319
375
|
```
|
|
320
376
|
|
|
377
|
+
`setup` commits the project it scaffolds: `git init` first when the directory
|
|
378
|
+
is not yet a repository, then one commit, `chore: scaffold the project with
|
|
379
|
+
zitadel setup`. A release records the commit it was built from and `deploy`
|
|
380
|
+
diffs `.zitadel/` against it, so without this the first release carries no
|
|
381
|
+
commit and the next deploy reports no diff.
|
|
382
|
+
|
|
383
|
+
Setup refuses with `E_CONFLICT` when the repository already has uncommitted
|
|
384
|
+
work, untracked files included, which `details.untracked` lists separately
|
|
385
|
+
from `details.modified`. The commit covers the project directory, so it can
|
|
386
|
+
only tell its own files from yours when the tree starts clean. Commit or stash
|
|
387
|
+
first, then rerun. Only what `start` leaves is exempt: `.zitadel/local/` and
|
|
388
|
+
the `.gitignore` line that ignores it. A connection under `.zitadel/idps/` is
|
|
389
|
+
yours and is not exempt.
|
|
390
|
+
|
|
391
|
+
Setup deploys nothing, and it does not need to: a project with no release
|
|
392
|
+
resolves the newest revisions, which are the ones setup just uploaded, so
|
|
393
|
+
sign-in already runs on `.zitadel/`. Deploying pins them into a release, which
|
|
394
|
+
is what the server will require once that fallback goes (#1616). The publish
|
|
395
|
+
step says so and names the command. `data.git` reports what setup did
|
|
396
|
+
(`initialized`, and the `commit`, or null). `--no-git` skips both the refusal
|
|
397
|
+
and the commit, and nothing prompts, so this is the same interactively and in
|
|
398
|
+
CI.
|
|
399
|
+
|
|
321
400
|
`setup` scaffolds into an empty directory, or patches an existing app project
|
|
322
401
|
where it detects a framework. What `start` leaves in a directory —
|
|
323
402
|
`.zitadel/local/` and the `.gitignore` line that ignores it — still counts as
|
|
324
403
|
empty, so the golden path runs `setup` right after `start` in the same
|
|
325
|
-
directory.
|
|
326
|
-
|
|
404
|
+
directory. A `--cwd` that does not exist yet counts as empty too: setup creates
|
|
405
|
+
it with the app, and removes it again when setup fails before the app is kept.
|
|
406
|
+
A `--cwd` that is a file fails with `E_VALIDATION`. Any other non-empty
|
|
407
|
+
directory stops setup with a hint to pass `--force`. Pass it only for a
|
|
408
|
+
directory whose contents may be replaced: files
|
|
327
409
|
the new app also has, such as `package.json`, are overwritten. Without a
|
|
328
410
|
terminal `setup` cannot ask which framework to scaffold, so pass `--framework`;
|
|
329
411
|
the error lists the choices in `details.frameworks`. `setup --dry-run` creates
|
|
330
412
|
no app: where it would scaffold one, it previews the project, and
|
|
331
413
|
`data.files_written` is empty, because those files depend on the generated app.
|
|
414
|
+
`setup` also writes and publishes the password policy
|
|
415
|
+
(`.zitadel/policies/auth.factor.password.set.json`) with a minimum length of
|
|
416
|
+
15, NIST's minimum for password-only sign-in and the wizard's default. Pass
|
|
417
|
+
`--password-min-length <n>` only when the user asks for another value (`0` sets
|
|
418
|
+
none), and give test users passwords at least that long.
|
|
332
419
|
When setup fails after scaffolding a new app, before its default schema and
|
|
333
420
|
flow are uploaded, it removes the app again and puts back what the directory
|
|
334
421
|
held, so rerunning the same `setup` starts over.
|
|
@@ -347,15 +434,22 @@ replaces a file at a path setup writes, but not the app's root component or
|
|
|
347
434
|
request boundary at another path.
|
|
348
435
|
|
|
349
436
|
The app may also live in a subdirectory of the one `start` ran in: `setup`,
|
|
350
|
-
`console`, `--server local` and `doctor` read `.zitadel/local/` from the working
|
|
437
|
+
`console`, `logs`, `--server local` and `doctor` read `.zitadel/local/` from the working
|
|
351
438
|
directory or the nearest parent that has it (up to the home directory), so
|
|
352
439
|
`mkdir my-app && cd my-app` after `start` works too. `doctor` checks that
|
|
353
440
|
server and its port, but runs its project checks in the working directory, so run
|
|
354
441
|
it from the app. When that server is unhealthy it suggests `start` and
|
|
355
442
|
`reset --force` with `--cwd` set to the directory that holds it (when that path needs
|
|
356
443
|
shell quoting the commands are left out, the hint names the directory, and
|
|
357
|
-
`doctor --json` returns the argument lists in `details.suggested_args` to run as they are).
|
|
358
|
-
|
|
444
|
+
`doctor --json` returns the argument lists in `details.suggested_args` to run as they are).
|
|
445
|
+
The hint says the server is the parent's and that `reset --force` deletes the data in that
|
|
446
|
+
directory, and it adds a plain `start`, which gives the working directory a server of its own
|
|
447
|
+
because `start` does not look in parents; a healthy parent server is reported in the `runtime`
|
|
448
|
+
check as started in the parent folder. A
|
|
449
|
+
local server is healthy once its `/readyz` answers, which takes its database
|
|
450
|
+
answering too: `start` waits for that, and the commands that read a server
|
|
451
|
+
treat one whose database does not answer as unhealthy. `stop`,
|
|
452
|
+
`reset` and `status` read only the working directory's
|
|
359
453
|
`.zitadel/local/`, so run them where `start` ran. On a local
|
|
360
454
|
server that hosts the platform project, `setup` attaches the project to the
|
|
361
455
|
local admin's team and ends `data.next_commands` with `console`; when it finds
|
|
@@ -363,10 +457,14 @@ no local admin for that server on the way up, it says so in the envelope's
|
|
|
363
457
|
`warnings` and the project stays unattached, so the local console does not
|
|
364
458
|
list it.
|
|
365
459
|
|
|
366
|
-
|
|
460
|
+
For a project set up against a hosted server, `doctor` checks no local
|
|
461
|
+
runtime, unless a local server was started in its directory or a parent, or
|
|
462
|
+
`--port`, `--runtime` or `--image` asks about one.
|
|
463
|
+
|
|
464
|
+
When `setup --sso` or `auth-method sso enable` adds a social sign-in provider, the user
|
|
367
465
|
must register the connection's redirect URI with the provider themselves: give
|
|
368
466
|
them `data.sso[].callback_uri` from `setup`, or `data.callback_uri` from
|
|
369
|
-
`sso enable`, verbatim. Each connection has its own URI, so a URI from another
|
|
467
|
+
`auth-method sso enable`, verbatim. Each connection has its own URI, so a URI from another
|
|
370
468
|
provider or an older CLI version does not work.
|
|
371
469
|
|
|
372
470
|
After `setup`, follow `data.next_commands` to start the app. Prove the generated
|
|
@@ -374,9 +472,18 @@ auth flow in a visible browser by registering a unique user, logging out, loggin
|
|
|
374
472
|
back in with the same email/password, and ending on the signed-in profile page.
|
|
375
473
|
Do not treat a rendered login or registration form as completion.
|
|
376
474
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
475
|
+
When a step against the local server fails without saying why (the login says a
|
|
476
|
+
sign-in provider "is not available right now", or a request returns 500), the
|
|
477
|
+
server logged the cause. Read it with `logs --level warn --json`: `data.logs`
|
|
478
|
+
holds the warnings and errors, and `--tail` counts only those lines. `logs
|
|
479
|
+
--follow` streams the log for a person at a terminal, with warnings yellow and
|
|
480
|
+
errors red; it refuses `--json`. `start`, and `doctor` when it finds the server
|
|
481
|
+
running, name it in `data.next_actions`.
|
|
482
|
+
|
|
483
|
+
To move a set-up app to another dev port (setup's `warnings` say the port is
|
|
484
|
+
already in use, the dev server exits with `EADDRINUSE`, or another app needs
|
|
485
|
+
the port), run `setup --dev-port <port>` in the app; never change the port in
|
|
486
|
+
one file by hand. In a directory with a
|
|
380
487
|
`zitadel.json`, `setup` returns `status: "skipped"` with `reason:
|
|
381
488
|
"already-initialized"`, unless `--dev-port` is given: then it moves the app.
|
|
382
489
|
It allows the new origin on the server first, and a refusal changes nothing.
|
|
@@ -399,6 +506,13 @@ than one:
|
|
|
399
506
|
stdin when non-interactive. Any other OpenID Connect provider is
|
|
400
507
|
`--provider custom` with `--slug`, `--display-name`, `--issuer` and optionally
|
|
401
508
|
`--scopes` (default `openid profile email`).
|
|
509
|
+
In a terminal, a custom provider's settings you leave out are asked for, after
|
|
510
|
+
the checks that need no answer; a connection that already has that slug
|
|
511
|
+
supplies its own. A scripted, `--json`, `--dry-run` or `--non-interactive`
|
|
512
|
+
run must pass all three and gets an error naming the missing ones. After
|
|
513
|
+
writing a new connection the text output prints the claim mappings and names
|
|
514
|
+
the file to edit them in; `--json` does not carry them, so read the
|
|
515
|
+
connection file.
|
|
402
516
|
An issuer on `http://localhost:<port>` (a test provider on this machine)
|
|
403
517
|
works against a server `zitadel start` runs as a process: `start` sets
|
|
404
518
|
`NEXTGEN_HTTPCLIENT_ALLOW_LIST=localhost,127.0.0.0/8,::1/128` unless
|
|
@@ -410,7 +524,11 @@ than one:
|
|
|
410
524
|
|
|
411
525
|
The commands change only local files, apart from
|
|
412
526
|
`auth-method sso enable`, which also stores the provider's credentials on the
|
|
413
|
-
project.
|
|
527
|
+
project. A provider added by `sso enable` is configured locally, not live: its
|
|
528
|
+
`data.title` says so, `data.next_actions` says to deploy, and `data.next_commands`
|
|
529
|
+
holds `deploy --dry-run` then `deploy`. Only run those once the provider's OAuth
|
|
530
|
+
application exists. `data.title` claims the credentials are stored on the project
|
|
531
|
+
only when they reached it. When they refuse, they write and publish nothing, on a dry run too.
|
|
414
532
|
They refuse to disable a method a login flow still asks for, to change a method
|
|
415
533
|
while a login flow has errors that prevent checking it, to enable password on a
|
|
416
534
|
schema without `x-identifier`, to edit a value that is not the shape they edit,
|
|
@@ -423,15 +541,37 @@ and warns that no way to sign in would be left. A method that is enabled but not
|
|
|
423
541
|
offered by any active flow is reported in `warnings` (and in `data.not_offered`
|
|
424
542
|
for password and passkey). Every command reports `data.changed`, the
|
|
425
543
|
project-relative files it wrote (on a dry run, the files it would write), empty
|
|
426
|
-
when nothing changed. Follow `data.next_commands` (`
|
|
427
|
-
`
|
|
544
|
+
when nothing changed. Follow `data.next_commands` (`deploy --dry-run`, `deploy`, and
|
|
545
|
+
`variable set` when a credential did not reach the project) to publish the
|
|
428
546
|
change. When a path or schema name would need shell quoting, `next_commands` is
|
|
429
547
|
empty and `data.next_args` (or `details.suggested_args` on a refusal) holds the
|
|
430
548
|
same commands as argument lists.
|
|
431
549
|
|
|
432
550
|
Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
|
|
433
|
-
then re-run `
|
|
434
|
-
and the config-sync details.
|
|
551
|
+
then re-run `deploy`. See the reference below for driving the login UI
|
|
552
|
+
and the config-sync details. The editor schemas in `.zitadel/meta/` are
|
|
553
|
+
generated by the CLI, not config: never edit them. `doctor` warns when one is
|
|
554
|
+
missing or outdated (after an upgrade that changed a dialect), and
|
|
555
|
+
`doctor --fix` writes the current ones, unless the project pins a newer
|
|
556
|
+
`@zitadel/*` version (run that version's `doctor --fix`) or a symlink is on
|
|
557
|
+
the path; `deploy --dry-run` validates without them either way.
|
|
558
|
+
|
|
559
|
+
## Claiming a project
|
|
560
|
+
|
|
561
|
+
A project on a hosted server stays temporary until a person claims it, within
|
|
562
|
+
14 days of `setup`. Claiming signs that person in with their own Zitadel
|
|
563
|
+
account in a browser, so it is their step: hand them the link, and never open
|
|
564
|
+
it yourself. Plain `claim` waits for the browser step and prints nothing on
|
|
565
|
+
stdout until it ends, so run `claim --no-wait --non-interactive --json`
|
|
566
|
+
instead. It returns at once with `data.claim_url`, the link to give the person
|
|
567
|
+
verbatim, and `data.expires_at`, when it stops working (about 10 minutes).
|
|
568
|
+
Then run the `claim --challenge-id …` command in `data.next_commands`: it
|
|
569
|
+
waits for the person to finish, returns `data.dashboard_url`, and records the
|
|
570
|
+
owning team in `.zitadel/secret`. When the link expires, or `--challenge-id`
|
|
571
|
+
names one the platform does not know (`E_NOT_FOUND`), `next_commands` starts a
|
|
572
|
+
new `--no-wait` link. A project that already has a team is `skipped` with
|
|
573
|
+
`reason: "already-claimed"`; one past its 14 days fails with `E_VALIDATION`
|
|
574
|
+
and no `next_commands`, since a new link cannot reopen it.
|
|
435
575
|
|
|
436
576
|
## Reference files
|
|
437
577
|
|
|
@@ -439,4 +579,4 @@ Load on demand:
|
|
|
439
579
|
|
|
440
580
|
- [`references/driving-login-ui.md`](references/driving-login-ui.md) — driving the
|
|
441
581
|
`<zitadel-login>` component in a browser, and the authoritative repo-config
|
|
442
|
-
workflow (schemas, flows, branding).
|
|
582
|
+
workflow (schemas, flows, branding, policies and their tests).
|
|
@@ -45,7 +45,8 @@ exercises fresh-app setup plus registration, logout, and login across the
|
|
|
45
45
|
supported frameworks.
|
|
46
46
|
|
|
47
47
|
Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
|
|
48
|
-
then re-run `
|
|
48
|
+
then re-run `deploy` (or `deploy --dry-run` to validate and preview the diff
|
|
49
|
+
without publishing). Schema and flow files are synced from
|
|
49
50
|
`.zitadel/schemas/*.json` and `.zitadel/flows/*.json`. Login templates
|
|
50
51
|
(branding) are synced from `.zitadel/branding/`: a single `branding.json`
|
|
51
52
|
descriptor (layout, asset URLs) plus a sibling `login.liquid` LiquidJS
|
|
@@ -53,9 +54,10 @@ template referenced as `"liquid_template": { "$file": "./login.liquid" }`. Scaff
|
|
|
53
54
|
`branding eject` command (`--design centered|minimal`,
|
|
54
55
|
interactive picker on a TTY); setup never scaffolds them. Branding is
|
|
55
56
|
revisioned and immutable: every edit — including a `.liquid`-only edit —
|
|
56
|
-
|
|
57
|
-
the newest
|
|
58
|
-
validator (`
|
|
57
|
+
is published as part of the next release when you `deploy`, and the login
|
|
58
|
+
serves the newest release. `deploy` validates templates with the authoritative
|
|
59
|
+
LiquidJS validator (and `deploy --dry-run` runs the same validation without
|
|
60
|
+
publishing; `E_VALIDATION` lists rule ids such as `no-script-tag` and
|
|
59
61
|
`mandatory-gates`; every template must keep a `{% mandatory_gates %}` tag,
|
|
60
62
|
where the login adds any required field and the primary action the template
|
|
61
63
|
left out). Asset URLs must be absolute `https://`; during development, images
|
|
@@ -67,17 +69,50 @@ directory. Keep exactly one descriptor in
|
|
|
67
69
|
`.zitadel/branding/` — extra `*.json` files there fail the scan.
|
|
68
70
|
Server-provisioned defaults remain a fallback for non-CLI project
|
|
69
71
|
creation, but CLI-created projects are authored from local files first. Flows are
|
|
70
|
-
revisioned like branding: an edit
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
collects needs that step's `fields` updated, and `
|
|
75
|
-
|
|
76
|
-
revision; an unpinned login serves the newest active unscoped
|
|
77
|
-
purpose in the project, whatever its flow's name. Older
|
|
78
|
-
so an `audience` added after a flow was
|
|
79
|
-
earlier revision out of the running. Removing a
|
|
80
|
-
flow
|
|
72
|
+
revisioned like branding: an edit is published as a new immutable flow revision
|
|
73
|
+
in the next `deploy`. A flow names its schema by `objectType` in `user_schema`
|
|
74
|
+
and runs against the schema's newest revision, so a schema revise needs no
|
|
75
|
+
change to `user_schema`; one that removes or renames a property a step
|
|
76
|
+
collects needs that step's `fields` updated, and `deploy` checks each flow
|
|
77
|
+
against the schema it names. A login pinned by `flow-name` serves that flow's
|
|
78
|
+
newest active revision; an unpinned login serves the newest active unscoped
|
|
79
|
+
revision for its purpose in the project, whatever its flow's name. Older
|
|
80
|
+
revisions stay active, so an `audience` added after a flow was deployed
|
|
81
|
+
without one doesn't take that earlier revision out of the running. Removing a
|
|
82
|
+
flow file does not retire the
|
|
83
|
+
flow. Operation policies live in `.zitadel/policies/`: one `<operation>.json`
|
|
84
|
+
per guarded operation, named after its `operation`, holding
|
|
85
|
+
`{"$schema": "../meta/policy.json", "operation": "auth.factor.password.set",
|
|
86
|
+
"config": {"min_length": 15}}`. `deploy` checks each file against
|
|
87
|
+
`.zitadel/meta/policy.json` (known operation, only the template's settings,
|
|
88
|
+
within their bounds) and its name before anything is sent; settings that
|
|
89
|
+
contradict each other (a `min_length` above `max_length`) are refused by the
|
|
90
|
+
server's release build with `rel.invalid`, the policy's entry carrying
|
|
91
|
+
`pol.invalid`. A changed file publishes a new revision of the same policy,
|
|
92
|
+
which applies the next time a password is set. A setting left out is off, and
|
|
93
|
+
`"config": {}` switches every rule off. A sign-in or sign-up uses the policy
|
|
94
|
+
the deployed release carries, so deploying without the file switches it off
|
|
95
|
+
there; a password set through the API, or before the first deploy, uses the
|
|
96
|
+
newest revision, which only `"config": {}` switches off. `policies list` and
|
|
97
|
+
`policies get <id|operation>` read them back. A policy's cases live beside it
|
|
98
|
+
in `<operation>.test.json` (`.zitadel/meta/policy-test.json`): each names an
|
|
99
|
+
`input` in the operation's terms (`{"password": "…"}`), optional `facts` the
|
|
100
|
+
operation would look up (`self_service`, `history_matches`), and the expected
|
|
101
|
+
decision (`allowed`, optionally `code` and exactly the `violations`). The
|
|
102
|
+
decision and rules hold in the login too; `code` is the API's answer, and a
|
|
103
|
+
flow shows each rule as `error.x-auth-methods#password_<rule>` instead.
|
|
104
|
+
`policies test [operation]` evaluates every case on the server against the
|
|
105
|
+
sibling policy file, or with `--release <id>` against the revision that
|
|
106
|
+
release pins; a missing policy is tested as none. It fails with `E_VALIDATION`
|
|
107
|
+
(exit 3) when a case does not hold, `data`/`details` carrying `source`,
|
|
108
|
+
`operations[].cases[]` (`name`, `passed`, `expected`, `actual`), `passed` and
|
|
109
|
+
`failed`; with no test files it is `skipped` (`no-policy-tests`). `deploy`
|
|
110
|
+
never carries test files. Until #2331, a release that pins no flow is tested
|
|
111
|
+
with a warning: its sign-ins use the newest revisions instead. Write cases from
|
|
112
|
+
what the user wants enforced; never invent expectations for them. `setup` writes and publishes
|
|
113
|
+
the password policy with a `min_length` of 15; pass `--password-min-length <n>`
|
|
114
|
+
only when the user asks for another value (`0` sets none). Managed files carry
|
|
115
|
+
a marker comment; `eject` removes only
|
|
81
116
|
files that still carry it, preserving anything the user replaced. For app-local
|
|
82
117
|
development, `--server local` resolves through `.zitadel/local/runtime.json` and
|
|
83
118
|
requires a healthy
|