@zitadel/cli 1.0.0-alpha.23 → 1.0.0-alpha.24

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/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { r as CommandGroups } from "./groups-CMB0fMzE.mjs";
2
2
  import { createRequire } from "node:module";
3
- import { Args, Command, Flags } from "@oclif/core";
4
3
  import consola, { consola as consola$1 } from "consola";
5
4
  import { createZitadelClient } from "@zitadel/api/client";
6
5
  import { ApiError, apiErrorMessage } from "@zitadel/api/runtime/fetch";
7
6
  import { createHash, pbkdf2Sync, randomBytes, randomUUID } from "node:crypto";
7
+ import { Args, Command, Flags } from "@oclif/core";
8
8
  import { stringify } from "safe-stable-stringify";
9
9
  import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
10
10
  import { access, chmod, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -14,8 +14,6 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
14
  import mixpanelLib from "mixpanel";
15
15
  import { homedir, release } from "node:os";
16
16
  import { Agent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
17
- import { cancel, confirm, intro, isCancel, outro, password, select, spinner, text } from "@clack/prompts";
18
- import { z } from "zod";
19
17
  import { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, DEFAULT_BRANDING_DESIGN, DEFAULT_BRANDING_TEMPLATE_PATH, DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, DEFAULT_SETUP_USE_CASE, SETUP_PRESETS, SETUP_USE_CASES, brandingReadmeContent, flowsReadmeContent, getDefaultBrandingConfig, getDefaultHumanUserSchema, getDefaultLoginFlow, schemasReadmeContent } from "@zitadel/config/defaults";
20
18
  import { normalizeFlowBody, normalizeSchemaBody } from "@zitadel/config/normalize";
21
19
  import { brandingConfigSchema, flowConfigSchema, schemaConfigSchema } from "@zitadel/config/schemas";
@@ -23,6 +21,7 @@ import { validateLoginTemplate } from "@zitadel/config/template";
23
21
  import { lookup } from "node:dns/promises";
24
22
  import { BlockList, isIP } from "node:net";
25
23
  import { validateFlowDefinition } from "@zitadel/config/validate";
24
+ import { cancel, confirm, intro, isCancel, outro, password, select, spinner, text } from "@clack/prompts";
26
25
  import { BRANDING_FILE_SCHEMA_REF, META_SCHEMA_DIR, metaSchemaFiles } from "@zitadel/config/meta-schemas";
27
26
  import pc from "picocolors";
28
27
  import wrapAnsi from "wrap-ansi";
@@ -913,8 +912,7 @@ const DEFAULT_SERVER = "https://api.zitadel.cloud";
913
912
  /**
914
913
  * Resolves which server the CLI should target, applying a fixed
915
914
  * precedence: explicit `--server` flag, then `ZITADEL_API_BASE`, then the
916
- * selected environment block in `zitadel.json`, then the config's
917
- * top-level `server`, falling back to {@link DEFAULT_SERVER}. Every
915
+ * `server` in `zitadel.json`, falling back to {@link DEFAULT_SERVER}. Every
918
916
  * candidate is validated to a normalised origin; an invalid URL throws a
919
917
  * `ZitadelError` rather than silently falling through.
920
918
  */
@@ -929,17 +927,10 @@ async function resolveServer(input) {
929
927
  origin: "env"
930
928
  });
931
929
  const config = await readConfig(input.cwd);
932
- if (config) {
933
- const envBranch = readEnvServer(config, input.environment);
934
- if (envBranch) return validate(input.cwd, {
935
- value: envBranch,
936
- origin: "config-env"
937
- });
938
- if (typeof config.server === "string") return validate(input.cwd, {
939
- value: config.server,
940
- origin: "config-top"
941
- });
942
- }
930
+ if (typeof config?.server === "string") return validate(input.cwd, {
931
+ value: config.server,
932
+ origin: "config-top"
933
+ });
943
934
  return {
944
935
  value: DEFAULT_SERVER,
945
936
  origin: "default"
@@ -973,14 +964,6 @@ async function readConfig(cwd) {
973
964
  throw error;
974
965
  }
975
966
  }
976
- function readEnvServer(config, environment) {
977
- if (!environment) return;
978
- const envs = config.environments;
979
- if (!isObject(envs)) return;
980
- const branch = envs[environment];
981
- if (!isObject(branch)) return;
982
- return typeof branch.server === "string" ? branch.server : void 0;
983
- }
984
967
  //#endregion
985
968
  //#region src/lib/telemetry/config.ts
986
969
  /**
@@ -1839,15 +1822,13 @@ var BaseCommand = class extends Command {
1839
1822
  async toMeta(flags, options = {}) {
1840
1823
  const cwd = resolveCwd(typeof flags.cwd === "string" ? flags.cwd : void 0);
1841
1824
  const serverFlag = typeof flags.server === "string" ? flags.server : void 0;
1842
- const environment = typeof flags.environment === "string" ? flags.environment : "development";
1843
1825
  const source = options.resolveServer === false ? {
1844
1826
  value: options.source ?? "",
1845
1827
  origin: "default"
1846
1828
  } : await resolveServer({
1847
1829
  cwd,
1848
1830
  env: process.env,
1849
- serverFlag,
1850
- environment
1831
+ serverFlag
1851
1832
  });
1852
1833
  const json = this.jsonEnabled();
1853
1834
  const isTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
@@ -2173,57 +2154,6 @@ function suffixBlock(opts) {
2173
2154
  return suffix ? ` ${suffix}` : "";
2174
2155
  }
2175
2156
  //#endregion
2176
- //#region src/lib/environment.ts
2177
- /**
2178
- * CLI-side deployment environment. Not an API model — it gates which
2179
- * `zitadel.json` environment block and server the commands target.
2180
- * Project request/response shapes live in `@zitadel/api`
2181
- * (generated from the OpenAPI spec).
2182
- *
2183
- * This is the older of the CLI's two meanings of "environment", used by
2184
- * `plan`, `apply` and the resource commands to pick a server. It does not match
2185
- * the platform's environments, which are open DNS-style names seeded as `dev`,
2186
- * `staging` and `prod`; {@link assertEnvironmentName} is the grammar for those.
2187
- */
2188
- const environmentSchema = z.enum([
2189
- "development",
2190
- "preview",
2191
- "production"
2192
- ]);
2193
- /**
2194
- * The platform's environment-name grammar, from `environment-name.yaml`: a
2195
- * lowercase DNS-style label. Checked before any request so a malformed name
2196
- * fails with the CLI's own message instead of a 400 from the edge.
2197
- */
2198
- const ENVIRONMENT_NAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
2199
- /** Longest name the platform accepts, from the same schema. */
2200
- const ENVIRONMENT_NAME_MAX = 63;
2201
- /** How an owner is named in output: an environment, or the project level. */
2202
- function ownerLabel(environment) {
2203
- return environment ?? "the project";
2204
- }
2205
- /** Reject a name the platform would refuse, naming it. */
2206
- function assertEnvironmentName(name) {
2207
- if (!ENVIRONMENT_NAME.test(name) || name.length > ENVIRONMENT_NAME_MAX) throw new ZitadelError("E_VALIDATION", `Invalid environment name ${JSON.stringify(name)}.`, { hint: "An environment name is a lowercase DNS-style label: letters, digits, and hyphens between them." });
2208
- }
2209
- /**
2210
- * Every environment of a project, by name, in the platform's order. The list is
2211
- * paginated, so it is drained; a project holds a handful, so this is one call in
2212
- * practice.
2213
- */
2214
- async function listEnvironmentNames(client, projectId) {
2215
- const drain = async (pageToken) => {
2216
- const page = await client.listEnvironments({
2217
- project_id: projectId,
2218
- limit: 100,
2219
- page_token: pageToken
2220
- });
2221
- const names = page.environments.map((environment) => environment.name);
2222
- return page.next_page_token ? [...names, ...await drain(page.next_page_token)] : names;
2223
- };
2224
- return drain();
2225
- }
2226
- //#endregion
2227
2157
  //#region src/lib/project.ts
2228
2158
  /**
2229
2159
  * Reports whether `cwd` has already been initialized, i.e. a committed
@@ -2333,11 +2263,8 @@ function readUseCase(config) {
2333
2263
  * Reads the top-level `server` origin from a parsed `zitadel.json`, falling
2334
2264
  * back to {@link DEFAULT_SERVER} when the key is absent.
2335
2265
  *
2336
- * Top-level only, unlike `resolveServer`, which prefers the selected
2337
- * environment's `server` before falling back here. That is the point: an
2338
- * environment override says where one invocation should talk, not where the
2339
- * project lives. No practical difference today, since setup always writes a
2340
- * concrete top-level `server`.
2266
+ * Top-level only: `zitadel.json` holds no other server, and setup always writes
2267
+ * a concrete one.
2341
2268
  *
2342
2269
  * This is the server the project actually lives on, which is not always the one
2343
2270
  * a command is talking to: `doctor` pins its own source to the local runtime URL
@@ -2358,66 +2285,42 @@ function isNotFound(error) {
2358
2285
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2359
2286
  }
2360
2287
  //#endregion
2361
- //#region src/lib/oclif/environment-command.ts
2362
- /**
2363
- * The prompt's answer for the project level. Parentheses fall outside the
2364
- * environment-name grammar, so no environment can ever be named this — and a
2365
- * string, unlike a symbol, survives `isCancel`'s narrowing, which types every
2366
- * symbol as the cancel signal.
2367
- */
2368
- const PROJECT_LEVEL = "(project-level)";
2288
+ //#region src/lib/oclif/owner-command.ts
2369
2289
  /**
2370
- * A command that addresses one owner on the platform: an environment, or the
2371
- * project level. Owners are separate rather than a ladder — nothing an owner
2372
- * holds reaches another (ADR 062 §4) — so a value meant for `prod` has to be
2373
- * written on `prod`, and the target must never be picked by accident.
2290
+ * A command that addresses one owner of a project's variables. The platform has
2291
+ * two kinds — an environment, or the project level — and they are separate
2292
+ * rather than a ladder: nothing one holds reaches the other (ADR 062 §4).
2374
2293
  *
2375
- * The owner is therefore never defaulted. `--environment` names one and
2376
- * `--project-level` names the project; with neither, a person is asked and an
2377
- * agent or CI run is refused, the rule ADR 035 sets for `deploy`. Leaving the
2378
- * flag off cannot quietly land a value where no environment will read it.
2379
- */
2380
- var EnvironmentCommand = class extends BaseCommand {
2294
+ * Only the project level can be addressed from the CLI today. The platform's
2295
+ * environments are still unsettled (#1258 may rename or drop them), and a
2296
+ * half-meant `--environment` is worse than none: a value written on a name the
2297
+ * platform later spells differently is a value nothing will ever read. The
2298
+ * owner is therefore named rather than assumed — `--project-level` is required
2299
+ * — so that when `--environment` returns, every command already written keeps
2300
+ * meaning exactly what it means now.
2301
+ */
2302
+ var OwnerCommand = class extends BaseCommand {
2381
2303
  static baseFlags = {
2382
2304
  ...BaseCommand.baseFlags,
2383
- environment: Flags.string({
2384
- char: "e",
2385
- aliases: ["env"],
2386
- description: "Environment to address, by name.",
2387
- exclusive: ["project-level"]
2388
- }),
2389
- "project-level": Flags.boolean({
2390
- description: "Address the project level instead of an environment. Environments do not inherit project-level values.",
2391
- exclusive: ["environment"]
2392
- })
2305
+ "project-level": Flags.boolean({ description: "Address the project level. Required: it is the only owner the CLI can address today." })
2393
2306
  };
2394
- /** The `--environment` value, validated; `undefined` when none was passed. */
2395
- requestedEnvironment;
2396
- /** Whether `--project-level` was passed. */
2397
- projectLevel = false;
2398
2307
  /**
2399
- * Validate the owner flags, then build the invocation context without them.
2308
+ * Refuse an invocation that names no owner, then build the invocation
2309
+ * context. The refusal is the same on a terminal and in a script: with one
2310
+ * owner available there is nothing to ask about, and a prompt offering a
2311
+ * single answer is a keystroke, not a choice.
2400
2312
  *
2401
- * `--environment` names an owner on the one instance the CLI talks to; it is
2402
- * not a `zitadel.json` block. `BaseCommand.toMeta` passes a
2403
- * `flags.environment` to the server resolver, where a matching
2404
- * `environments.<name>.server` would redirect the request, so it is withheld
2405
- * and the server resolves as it does for a command with no such flag.
2313
+ * It comes before `super.toMeta`, which resolves the server — a run that
2314
+ * named no owner is refused whatever `--server` says, rather than answering
2315
+ * `E_LOCAL_SERVER_NOT_RUNNING` for a `--server local` with nothing running.
2406
2316
  */
2407
2317
  async toMeta(flags, options = {}) {
2408
- if (typeof flags.environment === "string") {
2409
- assertEnvironmentName(flags.environment);
2410
- this.requestedEnvironment = flags.environment;
2411
- }
2412
- this.projectLevel = flags["project-level"] === true;
2413
- return super.toMeta({
2414
- ...flags,
2415
- environment: void 0
2416
- }, options);
2318
+ if (flags["project-level"] !== true) throw new ZitadelError("E_VALIDATION", "Name the owner: --project-level", { hint: "The project level is the only owner the CLI can address today; --environment returns when the platform's environments settle." });
2319
+ return super.toMeta(flags, options);
2417
2320
  }
2418
2321
  /**
2419
- * Open the platform connection and settle the owner, the counterpart of the
2420
- * resource commands' `connect`.
2322
+ * Open the platform connection, the counterpart of the resource commands'
2323
+ * `connect`.
2421
2324
  *
2422
2325
  * Which project and server the command is about is worth stating to a person,
2423
2326
  * but those lines share stdout with the result, so they are printed only on a
@@ -2431,49 +2334,14 @@ var EnvironmentCommand = class extends BaseCommand {
2431
2334
  consola$1.info(`Project ${secret.project_id}`);
2432
2335
  consola$1.info(`Server ${source}`);
2433
2336
  }
2434
- const client = createZitadelClient$1({
2435
- baseUrl: source,
2436
- token: secret.project_secret
2437
- });
2438
- const environment = await this.resolveOwner(client, secret.project_id);
2439
2337
  return {
2440
- client,
2441
- environment,
2442
- scope: {
2443
- project_id: secret.project_id,
2444
- ...environment === void 0 ? {} : { environment_name: environment }
2445
- }
2338
+ client: createZitadelClient$1({
2339
+ baseUrl: source,
2340
+ token: secret.project_secret
2341
+ }),
2342
+ scope: { project_id: secret.project_id }
2446
2343
  };
2447
2344
  }
2448
- /**
2449
- * The owner this invocation addresses: an environment's name, or `undefined`
2450
- * for the project level. Asks a person when neither flag was passed, and
2451
- * refuses a non-interactive run, naming the environments the project has.
2452
- */
2453
- async resolveOwner(client, projectId) {
2454
- if (this.projectLevel) return;
2455
- if (this.requestedEnvironment !== void 0) return this.requestedEnvironment;
2456
- if (this.meta.nonInteractive) {
2457
- const environments = await listEnvironmentNames(client, projectId).catch(() => []);
2458
- throw new ZitadelError("E_VALIDATION", "Name the owner: --environment <name>, or --project-level", { hint: environments.length > 0 ? `Environments do not inherit project-level values. This project's environments: ${environments.join(", ")}.` : "Environments do not inherit project-level values." });
2459
- }
2460
- const choice = await select({
2461
- message: "Which environment?",
2462
- options: [...(await listEnvironmentNames(client, projectId)).map((name) => ({
2463
- value: name,
2464
- label: name
2465
- })), {
2466
- value: PROJECT_LEVEL,
2467
- label: "The project level",
2468
- hint: "not inherited by any environment"
2469
- }]
2470
- });
2471
- if (isCancel(choice)) {
2472
- cancel("Cancelled.");
2473
- throw new ZitadelError("E_VALIDATION", "No owner chosen");
2474
- }
2475
- return choice === PROJECT_LEVEL ? void 0 : choice;
2476
- }
2477
2345
  };
2478
2346
  //#endregion
2479
2347
  //#region src/lib/local-files.ts
@@ -2852,7 +2720,7 @@ var SchemaSyncer = class {
2852
2720
  throw new ZitadelError("E_NOT_IMPLEMENTED", `schema delete is not supported (${id})`);
2853
2721
  }
2854
2722
  async fetch(id) {
2855
- return (await this.client.getSchemaById(encodeURIComponent(id))).schema;
2723
+ return (await this.client.getSchemaById(id)).schema;
2856
2724
  }
2857
2725
  };
2858
2726
  /**
@@ -4453,11 +4321,6 @@ var Apply = class Apply extends BaseCommand {
4453
4321
  static description = "Validate and upload repo config to the platform.";
4454
4322
  static group = CommandGroups.configuration;
4455
4323
  static groupOrder = 2;
4456
- static flags = { environment: Flags.string({
4457
- char: "e",
4458
- description: "Target environment (default: development).",
4459
- options: [...environmentSchema.options]
4460
- }) };
4461
4324
  async run() {
4462
4325
  const { flags } = await this.parse(Apply);
4463
4326
  await this.toMeta(flags);
@@ -4514,37 +4377,57 @@ var Apply = class Apply extends BaseCommand {
4514
4377
  //#endregion
4515
4378
  //#region src/lib/branding/designs.ts
4516
4379
  /**
4517
- * Human-facing name and one-line hint for each shipped login design. Shared
4518
- * by the setup wizard, `branding eject`, and the setup summary so the label a
4519
- * user picks ("Split (reversed)") and the slug the machine records
4520
- * (`split-right`) stay visibly paired everywhere — the summary echoing a slug
4521
- * the prompt never showed reads as "did my selection apply?".
4380
+ * Human-facing name and one-line hint for each shipped login design, shown by
4381
+ * the `branding eject` design picker. Setup no longer offers designs (#1039).
4522
4382
  */
4523
4383
  const BRANDING_DESIGN_INFO = {
4524
4384
  centered: {
4525
- label: "Centered",
4526
- hint: "the built-in look, forked as an editable template"
4527
- },
4528
- split: {
4529
- label: "Split",
4530
- hint: "brand panel left, form right; narrow containers show a compact brand mark"
4531
- },
4532
- "split-right": {
4533
- label: "Split (reversed)",
4534
- hint: "form left, brand panel right; narrow containers show a compact brand mark"
4535
- },
4536
- hero: {
4537
- label: "Hero",
4538
- hint: "landing-style brand pane left, form right; editable text fallback when narrow"
4385
+ label: "Default card",
4386
+ hint: "the built-in widget, forked as an editable template"
4539
4387
  },
4540
4388
  minimal: {
4541
4389
  label: "Minimal",
4542
- hint: "no card chrome, fields straight on the page"
4390
+ hint: "the same form without card chrome"
4543
4391
  }
4544
4392
  };
4545
- /** The prompt label for a design slug, falling back to the slug itself. */
4546
- function brandingDesignLabel(design) {
4547
- return BRANDING_DESIGN_INFO[design]?.label ?? design;
4393
+ /**
4394
+ * Designs the eject catalog used to ship (#1039). They were page layout
4395
+ * around the default card, not widget structure, so they are refused by name
4396
+ * with a pointer to where page layout lives now — scripts and agents that
4397
+ * learned `--design split` get an actionable error instead of oclif's generic
4398
+ * "expected one of".
4399
+ */
4400
+ const RETIRED_BRANDING_DESIGNS = [
4401
+ "split",
4402
+ "split-right",
4403
+ "hero"
4404
+ ];
4405
+ /** Where page layout lives now, shared by every retired-design error. */
4406
+ const PAGE_LAYOUT_HINT = "Build page layout (split screens, hero panes, marketing copy) in your app around <zitadel-login>, and theme the widget with --zl-* custom properties in your stylesheet.";
4407
+ /**
4408
+ * Resolves a `branding eject --design` value to a shipped design. A retired
4409
+ * design fails with a hint naming the replacement, an unknown one lists the
4410
+ * catalog.
4411
+ */
4412
+ function resolveBrandingDesign(design, cliVersion) {
4413
+ if (BRANDING_DESIGNS.includes(design)) return design;
4414
+ const catalog = BRANDING_DESIGNS.join(", ");
4415
+ if (RETIRED_BRANDING_DESIGNS.includes(design)) throw new ZitadelError("E_VALIDATION", `The ${design} design was retired: it was page layout, not widget structure`, {
4416
+ hint: `${PAGE_LAYOUT_HINT} To own the widget's structure, eject one of: ${catalog}.`,
4417
+ nextCommands: [publicCliCommand("branding eject --design centered", cliVersion)]
4418
+ });
4419
+ throw new ZitadelError("E_VALIDATION", `Unknown design ${JSON.stringify(design)}`, { hint: `Pick one of: ${catalog}.` });
4420
+ }
4421
+ /**
4422
+ * The error for `setup --design`, which setup kept as a hidden flag only to
4423
+ * explain its removal (#1039): setup embeds the maintained login and never
4424
+ * applies a template.
4425
+ */
4426
+ function setupDesignRemovedError(cliVersion) {
4427
+ return new ZitadelError("E_VALIDATION", "setup no longer applies a login template, so --design was removed", {
4428
+ hint: `Rerun setup without --design. ${PAGE_LAYOUT_HINT} To own the widget's structure after setup, eject one of: ${BRANDING_DESIGNS.join(", ")}.`,
4429
+ nextCommands: [publicCliCommand("branding eject", cliVersion)]
4430
+ });
4548
4431
  }
4549
4432
  //#endregion
4550
4433
  //#region src/commands/branding/eject.ts
@@ -4568,20 +4451,18 @@ var BrandingEject = class BrandingEject extends BaseCommand {
4568
4451
  char: "f",
4569
4452
  description: "Overwrite an existing branding file."
4570
4453
  }),
4571
- design: Flags.string({
4572
- description: `Design to start from (default: ${DEFAULT_BRANDING_DESIGN}).`,
4573
- options: [...BRANDING_DESIGNS]
4574
- })
4454
+ design: Flags.string({ description: `Design to start from: ${BRANDING_DESIGNS.join(" or ")} (default: ${DEFAULT_BRANDING_DESIGN}).` })
4575
4455
  };
4576
4456
  async run() {
4577
4457
  const { flags } = await this.parse(BrandingEject);
4578
4458
  await this.toMeta(flags);
4579
4459
  const { cwd, force, nonInteractive } = this.meta;
4460
+ const flagDesign = flags.design === void 0 ? void 0 : resolveBrandingDesign(flags.design, this.meta.cliVersion);
4580
4461
  if (!await hasZitadelConfig(cwd)) throw new ZitadelError("E_VALIDATION", "This directory is not a Zitadel project", {
4581
4462
  hint: "Run setup first; branding files live under .zitadel/.",
4582
4463
  nextCommands: [publicCliCommand("setup", this.meta.cliVersion)]
4583
4464
  });
4584
- const design = flags.design ?? await this.promptDesign(nonInteractive);
4465
+ const design = flagDesign ?? await this.promptDesign(nonInteractive);
4585
4466
  this.recordTelemetry({
4586
4467
  design,
4587
4468
  force
@@ -6839,7 +6720,7 @@ function agentsGuidanceSection(ctx) {
6839
6720
  const passkeyVerifyNote = ctx.preset === "passkey-first" ? " Agents: automated browsers can't complete passkey ceremonies — verify the loop via the email/password fallback actions, or attach a CDP WebAuthn virtual authenticator." : "";
6840
6721
  const jsxTypesNote = ctx.framework.id === "next" ? " React JSX types for the `<zitadel-*>` elements ship with the SDK — `custom-elements.d.ts` references `@zitadel/sdk-next/jsx`." : "";
6841
6722
  const presentationParagraph = ctx.posture === "widget" ? "Presentation, by contrast, is edited in the generated pages: this app pre-dates setup, so they embed the sign-in widgets as `variant=\"widget\"` cards (with `theme=\"auto\"`) inside your existing layout; switch to `variant=\"page\"` for the widget's own full-page chrome, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme — `auto` follows the OS `prefers-color-scheme`, not this app's own theme, so pin `light` or `dark` when the app fixes its scheme." : "Presentation, by contrast, is edited in the generated pages: they pin the sign-in widgets to `variant=\"page\"` (full-page chrome); switch to `variant=\"widget\"` to embed a card inside your own layout, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme — `auto` follows the OS `prefers-color-scheme`, not this app's own theme, so pin `light` or `dark` when the app fixes its scheme.";
6842
- const themingParagraph = "Theming the widgets from your app: `--zl-*` design-token custom properties set on the element in your own stylesheet pierce the widgets' shadow DOM. The bridge covers your fonts (`--zl-font-family-heading`, `--zl-font-family-sans`), radii (`--zl-radius-md`/`-lg`/`-xl`), the primary CTA pair (`--zl-primary` / `--zl-primary-foreground`), and the link color (`--zl-link`). When your page already carries the heading, set the `suppress-header` attribute on `<zitadel-login>` / `<zitadel-session>` (SDK wrappers: `suppressHeader`) to visually hide the widget's own heading block — it stays in the accessibility tree. Starter designs from `.zitadel/branding/` collapse their brand pane by container width, not viewport: an embedded card usually renders the compact brand mark instead (`logo_url`, else `hero_url`, from `branding.json`; the `hero` design falls back to editable text).";
6723
+ const themingParagraph = "Theming the widgets from your app: `--zl-*` design-token custom properties set on the element in your own stylesheet pierce the widgets' shadow DOM. The bridge covers your fonts (`--zl-font-family-heading`, `--zl-font-family-sans`), radii (`--zl-radius-md`/`-lg`/`-xl`), the primary CTA pair (`--zl-primary` / `--zl-primary-foreground`), and the link color (`--zl-link`). When your page already carries the heading, set the `suppress-header` attribute on `<zitadel-login>` / `<zitadel-session>` (SDK wrappers: `suppressHeader`) to visually hide the widget's own heading block — it stays in the accessibility tree. Page layout around the widget (split screens, hero panes, marketing copy) is your app's code, not a Zitadel template.";
6843
6724
  const sessionStateParagraph = ctx.framework.id === "next" ? "Your app's own chrome (header navigation, account menus) reads session state with `getSession()` from `@zitadel/sdk-next/session` — a no-store client read of the same-origin `/__nextgen/sessions/me` that the session card itself uses. It returns identity only for 200 with a non-empty `user_id`, and signed-out only for the backend's canonical `401/auth.unauthorized` or `404/sess.not_found`; any other response is unknown/error, never signed-out. In Server Components on routes covered by the request-boundary `matcher`, `auth()` from `@zitadel/sdk-next/server` works too — extend the `matcher` in the request boundary to cover your public routes when you want server-rendered, flash-free session state in shared chrome (the trade: the middleware then runs on those routes). Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load without extra wiring." : ctx.framework.id === "nuxt" ? "Your app's own chrome (header navigation, account menus) reads session state with the auto-imported `useAuth()` composable — seeded from the server on every render by the scaffolded auth plugin. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so the state is fresh on every page load." : "Your app's own chrome (header navigation, account menus) can read session state from the same-origin `/__nextgen/sessions/me` — the same server-answered read the session card performs. Fetch with `credentials: \"include\"`, `cache: \"no-store\"`, and `Accept: application/json`; treat 200 as signed-in only with a non-empty `user_id`, and signed-out only for the canonical error envelope `401/auth.unauthorized` or `404/sess.not_found`. Any other response is unknown/error, never signed-out. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load.";
6844
6725
  return `## Authentication (Zitadel)
6845
6726
 
@@ -11675,11 +11556,6 @@ var Plan = class Plan extends BaseCommand {
11675
11556
  static description = "Validate config without mutation and preview the sync diff.";
11676
11557
  static group = CommandGroups.configuration;
11677
11558
  static groupOrder = 1;
11678
- static flags = { environment: Flags.string({
11679
- char: "e",
11680
- description: "Target environment (default: development).",
11681
- options: [...environmentSchema.options]
11682
- }) };
11683
11559
  async run() {
11684
11560
  const { flags } = await this.parse(Plan);
11685
11561
  await this.toMeta(flags);
@@ -11958,7 +11834,7 @@ const parseOrThrow = (schema, value, message, hint) => {
11958
11834
  const parsed = schema.safeParse(value);
11959
11835
  if (!parsed.success) throw new ZitadelError("E_VALIDATION", message, {
11960
11836
  hint,
11961
- details: { issues: parsed.error?.issues }
11837
+ details: { issues: parsed.error.issues }
11962
11838
  });
11963
11839
  return parsed.data;
11964
11840
  };
@@ -12619,7 +12495,6 @@ const RESERVED = new Set([
12619
12495
  "cwd",
12620
12496
  "server",
12621
12497
  "force",
12622
- "environment",
12623
12498
  "help"
12624
12499
  ]);
12625
12500
  const kebab = (name) => name.replaceAll("_", "-");
@@ -12647,13 +12522,24 @@ const kindOf = (type) => {
12647
12522
  }
12648
12523
  };
12649
12524
  /**
12525
+ * The field map of a generated object schema, or `undefined` for anything that
12526
+ * is not a plain object. Zod's own schema type does not surface `shape` on its
12527
+ * public face, so the read is expressed against the {@link ZodLike} view the
12528
+ * rest of this factory walks — declared once because both the field reader and
12529
+ * {@link needsRawBody} need it.
12530
+ */
12531
+ const objectShape = (schema) => {
12532
+ const shape = schema.shape;
12533
+ return isObject(shape) ? shape : void 0;
12534
+ };
12535
+ /**
12650
12536
  * Read the body fields of a generated request schema. Returns an empty list
12651
12537
  * for anything that is not a plain object schema, so the caller degrades to
12652
12538
  * `--data` / `--file`.
12653
12539
  */
12654
12540
  const describeBody = (schema) => {
12655
- const shape = schema.shape;
12656
- if (!isObject(shape)) return [];
12541
+ const shape = objectShape(schema);
12542
+ if (!shape) return [];
12657
12543
  return Object.entries(shape).flatMap(([name, field]) => {
12658
12544
  const flag = kebab(name);
12659
12545
  if (RESERVED.has(flag)) return [];
@@ -12746,6 +12632,18 @@ const parsePair = (raw, flag) => {
12746
12632
  if (plain <= 0) throw new ZitadelError("E_VALIDATION", `Invalid --${flag} "${raw}"`, { hint });
12747
12633
  return [secretChecked(raw.slice(0, plain), flag), raw.slice(plain + 1)];
12748
12634
  };
12635
+ /**
12636
+ * Whether the schema carries a property no flag can express — a nested object
12637
+ * or an array, which {@link describeBody} leaves out. Such a body can only be
12638
+ * given in full through `--data` / `--file`, so a command that has one offers
12639
+ * no flags-only example: `grants create --relation viewer` names no principal,
12640
+ * and the platform answers `grant.invalid` rather than creating anything.
12641
+ */
12642
+ const needsRawBody = (schema) => {
12643
+ const shape = objectShape(schema);
12644
+ if (!shape) return false;
12645
+ return Object.values(shape).some((field) => kindOf(unwrap(field)) === void 0);
12646
+ };
12749
12647
  /** One example invocation using the required fields, for a command's `examples`. */
12750
12648
  const fieldExample = (fields) => {
12751
12649
  const required = fields.filter((field) => field.required);
@@ -12778,7 +12676,7 @@ const rawBodyFlags = {
12778
12676
  */
12779
12677
  const describeWrite = ({ topic, resource, spec }, options, verb) => {
12780
12678
  const fields = describeBody(spec.schema);
12781
- const example = fieldExample(fields);
12679
+ const example = needsRawBody(spec.schema) ? void 0 : fieldExample(fields);
12782
12680
  const target = verb === "create" ? topic : `${topic} update <id>`;
12783
12681
  return {
12784
12682
  description: verb === "create" ? `Create ${article(resource.singular)} ${resource.singular}.` : `Update ${article(resource.singular)} ${resource.singular} by id.`,
@@ -12926,6 +12824,15 @@ const buildResourceCommands = (registry, options) => Object.fromEntries(Object.e
12926
12824
  }));
12927
12825
  //#endregion
12928
12826
  //#region src/commands/resources.ts
12827
+ const create = (schema, call) => ({
12828
+ schema,
12829
+ call
12830
+ });
12831
+ const update = (schema, call) => ({
12832
+ schema,
12833
+ call
12834
+ });
12835
+ const list = (spec) => spec;
12929
12836
  /** Filter operations of `POST /<resource>/query` endpoints (ADR 031). */
12930
12837
  const FILTER_OPERATIONS = [
12931
12838
  "equals",
@@ -12965,7 +12872,7 @@ const RESOURCES = {
12965
12872
  "metadata.created_at",
12966
12873
  "metadata.updated_at"
12967
12874
  ],
12968
- list: {
12875
+ list: list({
12969
12876
  items: "users",
12970
12877
  body: QueryUsersBody,
12971
12878
  response: QueryUsersResponse,
@@ -13003,19 +12910,13 @@ const RESOURCES = {
13003
12910
  "lifecycle_owner_team_id"
13004
12911
  ],
13005
12912
  call: ({ client }, body) => client.queryUsers(body)
13006
- },
12913
+ }),
13007
12914
  get: {
13008
12915
  call: ({ client }, id) => client.getUserByID(id),
13009
12916
  response: GetUserByIDResponse
13010
12917
  },
13011
- create: {
13012
- schema: CreateUserBody,
13013
- call: ({ client, projectId }, body) => client.createUser(body, { project_id: projectId })
13014
- },
13015
- update: {
13016
- schema: PatchUserByIDBody,
13017
- call: ({ client }, id, body) => client.patchUserByID(id, body)
13018
- },
12918
+ create: create(CreateUserBody, ({ client, projectId }, body) => client.createUser(body, { project_id: projectId })),
12919
+ update: update(PatchUserByIDBody, ({ client }, id, body) => client.patchUserByID(id, body)),
13019
12920
  delete: { call: ({ client }, id) => client.deleteUserByID(id) }
13020
12921
  },
13021
12922
  teams: {
@@ -13035,7 +12936,7 @@ const RESOURCES = {
13035
12936
  "created_at",
13036
12937
  "updated_at"
13037
12938
  ],
13038
- list: {
12939
+ list: list({
13039
12940
  items: "teams",
13040
12941
  body: QueryTeamsBody,
13041
12942
  response: QueryTeamsResponse,
@@ -13059,19 +12960,13 @@ const RESOURCES = {
13059
12960
  "status"
13060
12961
  ],
13061
12962
  call: ({ client, projectId }, body) => client.queryTeams(body, { project_id: projectId })
13062
- },
12963
+ }),
13063
12964
  get: {
13064
12965
  call: ({ client }, id) => client.getTeam(id),
13065
12966
  response: GetTeamResponse
13066
12967
  },
13067
- create: {
13068
- schema: CreateTeamBody,
13069
- call: ({ client, projectId }, body) => client.createTeam(body, { project_id: projectId })
13070
- },
13071
- update: {
13072
- schema: UpdateTeamBody,
13073
- call: ({ client }, id, body) => client.updateTeam(id, body)
13074
- },
12968
+ create: create(CreateTeamBody, ({ client, projectId }, body) => client.createTeam(body, { project_id: projectId })),
12969
+ update: update(UpdateTeamBody, ({ client }, id, body) => client.updateTeam(id, body)),
13075
12970
  delete: {
13076
12971
  verb: "deactivate",
13077
12972
  call: ({ client }, id) => client.deleteTeam(id)
@@ -13096,7 +12991,7 @@ const RESOURCES = {
13096
12991
  "created_at",
13097
12992
  "expires_at"
13098
12993
  ],
13099
- list: {
12994
+ list: list({
13100
12995
  items: "sessions",
13101
12996
  body: QuerySessionsBody,
13102
12997
  response: QuerySessionsResponse,
@@ -13120,7 +13015,7 @@ const RESOURCES = {
13120
13015
  ],
13121
13016
  sorts: ["created_at", "user_id"],
13122
13017
  call: ({ client, projectId }, body) => client.querySessions(body, { project_id: projectId })
13123
- },
13018
+ }),
13124
13019
  get: {
13125
13020
  call: ({ client }, id) => client.getSession(id),
13126
13021
  response: GetSessionResponse
@@ -13241,7 +13136,7 @@ const RESOURCES = {
13241
13136
  "created_at",
13242
13137
  "expires_at"
13243
13138
  ],
13244
- list: {
13139
+ list: list({
13245
13140
  items: "grants",
13246
13141
  body: QueryGrantsBody,
13247
13142
  response: QueryGrantsResponse,
@@ -13273,15 +13168,12 @@ const RESOURCES = {
13273
13168
  "id"
13274
13169
  ],
13275
13170
  call: ({ client, projectId }, body) => client.queryGrants(body, { project_id: projectId })
13276
- },
13171
+ }),
13277
13172
  get: {
13278
13173
  call: ({ client, projectId }, id) => client.getGrant(id, { project_id: projectId }),
13279
13174
  response: GetGrantResponse
13280
13175
  },
13281
- create: {
13282
- schema: CreateGrantBody,
13283
- call: ({ client, projectId }, body) => client.createGrant(body, { project_id: projectId })
13284
- },
13176
+ create: create(CreateGrantBody, ({ client, projectId }, body) => client.createGrant(body, { project_id: projectId })),
13285
13177
  delete: { call: ({ client, projectId }, id) => client.deleteGrant(id, { project_id: projectId }) }
13286
13178
  },
13287
13179
  idps: {
@@ -13303,7 +13195,7 @@ const RESOURCES = {
13303
13195
  "created_at",
13304
13196
  "updated_at"
13305
13197
  ],
13306
- list: {
13198
+ list: list({
13307
13199
  items: "idps",
13308
13200
  body: QueryIdpsBody,
13309
13201
  response: QueryIdpsResponse,
@@ -13316,15 +13208,12 @@ const RESOURCES = {
13316
13208
  }],
13317
13209
  sorts: ["slug", "created_at"],
13318
13210
  call: ({ client, projectId }, body) => client.queryIdps(body, { project_id: projectId })
13319
- },
13211
+ }),
13320
13212
  get: {
13321
13213
  call: ({ client, projectId }, id) => client.getIdpById(id, { project_id: projectId }),
13322
13214
  response: GetIdpByIdResponse
13323
13215
  },
13324
- create: {
13325
- schema: CreateIdpBody,
13326
- call: ({ client, projectId }, body) => client.createIdp(body, { project_id: projectId })
13327
- }
13216
+ create: create(CreateIdpBody, ({ client, projectId }, body) => client.createIdp(body, { project_id: projectId }))
13328
13217
  },
13329
13218
  projects: {
13330
13219
  group: CommandGroups.resources,
@@ -13342,7 +13231,7 @@ const RESOURCES = {
13342
13231
  "created_at",
13343
13232
  "updated_at"
13344
13233
  ],
13345
- list: {
13234
+ list: list({
13346
13235
  items: "projects",
13347
13236
  body: QueryProjectsBody,
13348
13237
  response: QueryProjectsResponse,
@@ -13352,15 +13241,12 @@ const RESOURCES = {
13352
13241
  }],
13353
13242
  sorts: ["created_at"],
13354
13243
  call: ({ client }, body) => client.queryProjects(body)
13355
- },
13244
+ }),
13356
13245
  get: {
13357
13246
  call: ({ client }, id) => client.getProject(id),
13358
13247
  response: GetProjectResponse
13359
13248
  },
13360
- update: {
13361
- schema: PatchProjectBody,
13362
- call: ({ client }, id, body) => client.patchProject(id, body)
13363
- }
13249
+ update: update(PatchProjectBody, ({ client }, id, body) => client.patchProject(id, body))
13364
13250
  },
13365
13251
  schemas: {
13366
13252
  group: CommandGroups.configuration,
@@ -13408,14 +13294,18 @@ const RESOURCES = {
13408
13294
  get: {
13409
13295
  response: GetSchemaByIdResponse,
13410
13296
  call: async ({ client, projectId }, ref) => {
13411
- if (ref.startsWith("sch_") || ref.includes("://")) return client.getSchemaById(ref);
13297
+ try {
13298
+ return await client.getSchemaById(ref);
13299
+ } catch (error) {
13300
+ if (!(error instanceof ApiError) || error.status !== 404) throw error;
13301
+ }
13412
13302
  const current = (await client.listSchemas({
13413
13303
  project_id: projectId,
13414
13304
  object_type: ref,
13415
13305
  revisions: "latest",
13416
13306
  limit: 1
13417
13307
  })).schemas[0];
13418
- if (!current) throw new ZitadelError("E_NOT_FOUND", `No schema for object type "${ref}"`, { hint: "Run `schemas list` to see the object types this project has." });
13308
+ if (!current) throw new ZitadelError("E_NOT_FOUND", `No schema named "${ref}"`, { hint: "Run `schemas list` to see the object types this project has." });
13419
13309
  return current;
13420
13310
  }
13421
13311
  }
@@ -13564,11 +13454,6 @@ const RESOURCES = {
13564
13454
  */
13565
13455
  const RESOURCE_COMMANDS = buildResourceCommands(RESOURCES, {
13566
13456
  operations: FILTER_OPERATIONS,
13567
- flags: { environment: Flags.string({
13568
- char: "e",
13569
- description: "Target environment (default: development).",
13570
- options: [...environmentSchema.options]
13571
- }) },
13572
13457
  connect: async ({ cwd, source }) => {
13573
13458
  const secret = await readZitadelSecret(cwd);
13574
13459
  if (process.stdout.isTTY) {
@@ -13663,15 +13548,14 @@ function customizeAndPublishActions(cliVersion) {
13663
13548
  return ["Customize what you ask for and how people sign in: edit .zitadel/schemas/ to change the information you collect, and .zitadel/flows/ to change the sign-up/sign-in experience. Each folder has a README with examples.", `See your changes before they go live: ${publicCliCommand("plan", cliVersion)} to preview, then ${publicCliCommand("apply", cliVersion)} to publish.`];
13664
13549
  }
13665
13550
  /**
13666
- * How to change the login's look, depending on whether setup already ejected
13667
- * a starter design. With a design, revision 1 is already live and the
13668
- * template is repo-owned — point at the files and the plan/apply loop.
13669
- * Without one, point at `branding eject` so the built-in template's
13670
- * customization path stays discoverable after setup.
13551
+ * How to change the login's look after setup. Setup never ejects a template
13552
+ * or publishes a branding revision (#1039): the app embeds the maintained
13553
+ * `<zitadel-login>`, so its look is the app's own CSS (`--zl-*` tokens) and
13554
+ * layout around it. Taking ownership of the widget's structure stays the
13555
+ * opt-in `branding eject`, surfaced here so it remains discoverable.
13671
13556
  */
13672
- function brandingGuidanceAction(design, cliVersion) {
13673
- if (design) return `Make the login yours: the ${design} design is live as branding revision 1. Edit .zitadel/branding/login.liquid (logo and hero image URLs live in branding.json), then ${publicCliCommand("plan", cliVersion)} and ${publicCliCommand("apply", cliVersion)} to publish the next revision.`;
13674
- return `Change how the login looks: ${publicCliCommand("branding eject", cliVersion)} scaffolds .zitadel/branding/ from a starter design (centered, split, split-right, hero, minimal) for you to edit.`;
13557
+ function brandingGuidanceAction(cliVersion) {
13558
+ return `Change how the login looks: it is a component in your app, so theme it with \`--zl-*\` custom properties in your own stylesheet and build any page layout around it in your code. To change the widget's structure, ${publicCliCommand("branding eject", cliVersion)} scaffolds an editable template in .zitadel/branding/.`;
13675
13559
  }
13676
13560
  //#endregion
13677
13561
  //#region src/lib/local-server/claim-as-admin.ts
@@ -13748,7 +13632,7 @@ async function materializeSetupResources(opts) {
13748
13632
  const schemaId = requiredString((await opts.client.createSchema(schemaBody, { project_id: opts.projectId })).id, "created schema id");
13749
13633
  let schemaHash = hashForState({ normalize: normalizeSchemaBody }, schemaBody);
13750
13634
  try {
13751
- const canonical = (await opts.client.getSchemaById(encodeURIComponent(schemaId))).schema;
13635
+ const canonical = (await opts.client.getSchemaById(schemaId)).schema;
13752
13636
  schemaHash = (await writeBackResource(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH, { normalize: normalizeSchemaBody }, canonical)).hash;
13753
13637
  } catch (err) {
13754
13638
  consola$1.debug(`fetch created schema ${schemaId} during setup failed:`, err);
@@ -13779,24 +13663,6 @@ async function materializeSetupResources(opts) {
13779
13663
  name: flowBody.name,
13780
13664
  status: flowBody.status
13781
13665
  });
13782
- if (opts.design) {
13783
- await mkdir(join(opts.cwd, BRANDING_DIR), { recursive: true });
13784
- const { branding, template } = getDefaultBrandingConfig(opts.design);
13785
- const descriptor = {
13786
- $schema: BRANDING_FILE_SCHEMA_REF,
13787
- ...branding
13788
- };
13789
- if (await writeResourceFile(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH, descriptor, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH));
13790
- if (await writeRawFile(opts.cwd, DEFAULT_BRANDING_TEMPLATE_PATH, template, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_BRANDING_TEMPLATE_PATH));
13791
- const brandingNormalize = (data) => toBrandingWireBody(opts.cwd, data);
13792
- const created = await opts.client.createBranding(brandingNormalize(descriptor), { project_id: opts.projectId });
13793
- await updateState(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH, {
13794
- id: requiredString(created.id, "created branding revision id"),
13795
- hash: hashForState({ normalize: brandingNormalize }, descriptor)
13796
- });
13797
- const brandingReadme = join(BRANDING_DIR, "README.md");
13798
- if (await writeReadmeFile(opts.cwd, brandingReadme, normalizePublicCliProse(brandingReadmeContent(), opts.cliVersion))) filesWritten.push(join(opts.cwd, brandingReadme));
13799
- }
13800
13666
  const schemasReadme = join(SCHEMAS_DIR, "README.md");
13801
13667
  const flowsReadme = join(FLOWS_DIR, "README.md");
13802
13668
  if (await writeReadmeFile(opts.cwd, schemasReadme, normalizePublicCliProse(schemasReadmeContent(), opts.cliVersion))) filesWritten.push(join(opts.cwd, schemasReadme));
@@ -13804,22 +13670,6 @@ async function materializeSetupResources(opts) {
13804
13670
  return { filesWritten };
13805
13671
  }
13806
13672
  /**
13807
- * Write a non-JSON scaffold file (the `.liquid` template) with the same
13808
- * conflict semantics as {@link writeResourceFile}: `--force` overwrites,
13809
- * otherwise an existing file is an `E_CONFLICT`.
13810
- */
13811
- async function writeRawFile(cwd, relPath, content, force) {
13812
- const dest = join(cwd, relPath);
13813
- await mkdir(dirname(dest), { recursive: true });
13814
- try {
13815
- await writeFile(dest, content, force ? void 0 : { flag: "wx" });
13816
- return true;
13817
- } catch (error) {
13818
- if (isErrno(error, "EEXIST")) throw new ZitadelError("E_CONFLICT", `${relPath} already exists`, { hint: "Move the file aside or rerun setup with --force if you want setup to replace it." });
13819
- throw error;
13820
- }
13821
- }
13822
- /**
13823
13673
  * Write a README file, but never overwrite an existing one. A developer who
13824
13674
  * has edited the README should keep their edits when `setup --force` is
13825
13675
  * re-run.
@@ -13973,54 +13823,6 @@ function bail(value) {
13973
13823
  }
13974
13824
  }
13975
13825
  //#endregion
13976
- //#region src/commands/setup/prompts/design.ts
13977
- /**
13978
- * Sentinel select value for "keep the built-in login" — clack option values
13979
- * must be strings, and `undefined` in {@link SetupAnswers.design} is the
13980
- * real representation of that choice.
13981
- */
13982
- const BUILT_IN = "built-in";
13983
- /**
13984
- * "How should the login look?" — the last wizard question (#676). Asks after
13985
- * the sign-in preset so the order tells a story: the use case owns what is
13986
- * collected, the preset owns how users authenticate, the design owns how
13987
- * that experience looks.
13988
- *
13989
- * The built-in default is preselected and writes nothing. Picking a starter
13990
- * design forks its Liquid template into `.zitadel/branding/` and publishes
13991
- * it as branding revision 1 — from then on the template is repo-owned and
13992
- * stops tracking built-in improvements, so the option labels make the
13993
- * file-write explicit and keep ownership an opt-in. `--design` is
13994
- * authoritative; non-interactive runs keep the built-in template.
13995
- */
13996
- var DesignPrompt = class {
13997
- async ask(answers, ctx) {
13998
- if (ctx.designFromFlag) return answers;
13999
- const value = await select({
14000
- message: "How should the login look? A starter design adds its editable template to .zitadel/branding/.",
14001
- initialValue: answers.design ?? BUILT_IN,
14002
- options: [{
14003
- value: BUILT_IN,
14004
- label: "Built-in",
14005
- hint: "no files added; pick a design anytime later with `branding eject`"
14006
- }, ...BRANDING_DESIGNS.map((design) => ({
14007
- value: design,
14008
- label: BRANDING_DESIGN_INFO[design].label,
14009
- hint: BRANDING_DESIGN_INFO[design].hint
14010
- }))]
14011
- });
14012
- bail(value);
14013
- if (value === BUILT_IN) return {
14014
- ...answers,
14015
- design: void 0
14016
- };
14017
- return {
14018
- ...answers,
14019
- design: value
14020
- };
14021
- }
14022
- };
14023
- //#endregion
14024
13826
  //#region src/commands/setup/prompts/dev-port.ts
14025
13827
  /**
14026
13828
  * "Dev server port" — defaults to the detected port. The validated answer
@@ -14254,8 +14056,7 @@ const SETUP_PROMPTS = [
14254
14056
  new ServerPrompt(),
14255
14057
  new DevPortPrompt(),
14256
14058
  new UseCasePrompt(),
14257
- new SignInPresetPrompt(),
14258
- new DesignPrompt()
14059
+ new SignInPresetPrompt()
14259
14060
  ];
14260
14061
  //#endregion
14261
14062
  //#region src/commands/setup/summary.ts
@@ -14298,8 +14099,6 @@ const path = (s) => pc.cyan(s);
14298
14099
  const url = (s) => pc.cyan(s);
14299
14100
  /** Yellow, for opaque ids the user shouldn't try to read. */
14300
14101
  const id = (s) => pc.yellow(s);
14301
- /** Dim, for separators and secondary detail. */
14302
- const dim = (s) => pc.dim(s);
14303
14102
  /**
14304
14103
  * Reads the project root to identify the framework version, TS presence,
14305
14104
  * and which package manager the user runs. Returns safe defaults (npm
@@ -14360,29 +14159,6 @@ function stripRange(range) {
14360
14159
  function fileNameOf(p) {
14361
14160
  return basename(p);
14362
14161
  }
14363
- /**
14364
- * Layout caveats for the chosen login design, shown to humans after the
14365
- * summary box and carried in the JSON envelope's `warnings`.
14366
- *
14367
- * The split family's brand pane is keyed to the **login's own container**
14368
- * width — a `@container (max-width: 48rem)` query on the widget's mount, not
14369
- * a viewport media query. Above that the pane renders; below it the pane is
14370
- * `display: none` and the compact brand mark takes its place — and the split
14371
- * template only emits that mark when `branding.json` names `logo_url` or
14372
- * `hero_url`, so without one the narrow layout loses the branding entirely.
14373
- *
14374
- * Independent of posture, because the container query is: a full-page login
14375
- * hits the same collapse on a phone that an embedded card hits in a sidebar.
14376
- * Gating on `widget` would have been a guess about the container, and a wrong
14377
- * one in both directions — the wrapper setup scaffolds for a widget is
14378
- * full-width (the pane renders there), and a page-posture login on a phone is
14379
- * exactly the narrow case the advice is for. `hero` stays quiet: its compact
14380
- * fallback is editable text, so a narrow container never leaves it blank.
14381
- */
14382
- function designWarnings(design) {
14383
- if (design !== "split" && design !== "split-right") return [];
14384
- return [`The ${design} design shows its brand pane only when the login's container is wide; a narrow container — a card-width embed, or any phone-width viewport — collapses it to the compact brand mark. Set logo_url (or hero_url) in .zitadel/branding/branding.json so that mark isn't empty.`];
14385
- }
14386
14162
  //#endregion
14387
14163
  //#region src/commands/setup/index.ts
14388
14164
  /**
@@ -14442,13 +14218,11 @@ var Setup = class Setup extends BaseCommand {
14442
14218
  description: "Use case for the scaffolded schema fields: who signs in to the app (default: minimal).",
14443
14219
  options: [...SETUP_USE_CASES]
14444
14220
  }),
14445
- design: Flags.string({
14446
- description: "Login design to eject into .zitadel/branding/ and publish as branding revision 1. Skips the wizard's design question. When omitted in non-interactive runs, the login uses the built-in template; run the `branding eject` command later to customize. Split-family designs (split, split-right, hero) collapse their brand pane by container width: narrow containers — including widget-posture embeds at card width — render the compact brand mark instead (logo_url, else hero_url, from .zitadel/branding/branding.json; hero falls back to editable text).",
14447
- options: [...BRANDING_DESIGNS]
14448
- })
14221
+ design: Flags.string({ hidden: true })
14449
14222
  };
14450
14223
  async run() {
14451
14224
  const { flags } = await this.parse(Setup);
14225
+ if (flags.design !== void 0) throw setupDesignRemovedError(this.config.version);
14452
14226
  try {
14453
14227
  await this.toMeta(flags);
14454
14228
  } catch (error) {
@@ -14485,7 +14259,6 @@ var Setup = class Setup extends BaseCommand {
14485
14259
  dev_port_explicit: flags["dev-port"] !== void 0,
14486
14260
  preset: flags.preset ?? DEFAULT_SETUP_PRESET,
14487
14261
  use_case: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
14488
- design: flags.design ?? "built-in",
14489
14262
  step: "framework_resolved"
14490
14263
  });
14491
14264
  if (flags["dev-port"] !== void 0) {
@@ -14501,8 +14274,7 @@ var Setup = class Setup extends BaseCommand {
14501
14274
  server: this.meta.source,
14502
14275
  devPort: framework.devPort,
14503
14276
  preset: flags.preset ?? DEFAULT_SETUP_PRESET,
14504
- useCase: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
14505
- design: flags.design
14277
+ useCase: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE
14506
14278
  };
14507
14279
  if (!nonInteractive && !dryRun) {
14508
14280
  intro("Zitadel setup");
@@ -14512,16 +14284,14 @@ var Setup = class Setup extends BaseCommand {
14512
14284
  serverFlag: this.meta.serverFlag,
14513
14285
  devPortFromFlag: flags["dev-port"] !== void 0,
14514
14286
  presetFromFlag: flags.preset !== void 0,
14515
- useCaseFromFlag: flags["use-case"] !== void 0,
14516
- designFromFlag: flags.design !== void 0
14287
+ useCaseFromFlag: flags["use-case"] !== void 0
14517
14288
  };
14518
14289
  for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
14519
14290
  outro("Configuration captured");
14520
14291
  }
14521
14292
  this.recordTelemetry({
14522
14293
  preset: answers.preset,
14523
- use_case: answers.useCase,
14524
- design: answers.design ?? "built-in"
14294
+ use_case: answers.useCase
14525
14295
  });
14526
14296
  const issuer = issuerFromPort(answers.devPort);
14527
14297
  framework = {
@@ -14537,7 +14307,6 @@ var Setup = class Setup extends BaseCommand {
14537
14307
  framework: framework.id,
14538
14308
  preset: answers.preset,
14539
14309
  useCase: answers.useCase,
14540
- design: answers.design,
14541
14310
  devPort: answers.devPort
14542
14311
  });
14543
14312
  consola$1.success(`Created project ${project.id}`);
@@ -14578,7 +14347,6 @@ var Setup = class Setup extends BaseCommand {
14578
14347
  force,
14579
14348
  preset: answers.preset,
14580
14349
  useCase: answers.useCase,
14581
- design: answers.design,
14582
14350
  cliVersion: this.meta.cliVersion
14583
14351
  });
14584
14352
  } catch (error) {
@@ -14669,7 +14437,6 @@ var Setup = class Setup extends BaseCommand {
14669
14437
  boxActions: [],
14670
14438
  commands: []
14671
14439
  };
14672
- const warnings = designWarnings(answers.design);
14673
14440
  if (!this.jsonEnabled()) {
14674
14441
  const sections = buildSummary({
14675
14442
  projectFacts: await detectProjectFacts(cwd, framework.id),
@@ -14678,8 +14445,7 @@ var Setup = class Setup extends BaseCommand {
14678
14445
  project,
14679
14446
  server: answers.server,
14680
14447
  issuer,
14681
- scaffoldedFramework,
14682
- design: answers.design
14448
+ scaffoldedFramework
14683
14449
  });
14684
14450
  consola$1.box({
14685
14451
  title: "Zitadel is ready",
@@ -14694,11 +14460,9 @@ var Setup = class Setup extends BaseCommand {
14694
14460
  borderColor: "green"
14695
14461
  }
14696
14462
  });
14697
- for (const warning of warnings) consola$1.warn(warning);
14698
14463
  }
14699
14464
  return this.emit({
14700
14465
  status: "ok",
14701
- warnings,
14702
14466
  pretty: "",
14703
14467
  data: {
14704
14468
  title: "Zitadel is ready.",
@@ -14716,10 +14480,9 @@ var Setup = class Setup extends BaseCommand {
14716
14480
  })),
14717
14481
  files_skipped: result.filesSkipped.map((file) => relativeDisplayPath(cwd, file)),
14718
14482
  install: installOutcome.install,
14719
- design: answers.design ?? null,
14720
14483
  next_actions: [
14721
14484
  ...installOutcome.nextActions,
14722
- brandingGuidanceAction(answers.design, this.meta.cliVersion),
14485
+ brandingGuidanceAction(this.meta.cliVersion),
14723
14486
  ...claimNudge.actions
14724
14487
  ],
14725
14488
  next_commands: [...installOutcome.nextCommands, ...claimNudge.commands]
@@ -14782,7 +14545,6 @@ function setupRetryFlags(opts) {
14782
14545
  if (opts.framework) parts.push(`--framework ${opts.framework}`);
14783
14546
  if (opts.preset && opts.preset !== DEFAULT_SETUP_PRESET) parts.push(`--preset ${opts.preset}`);
14784
14547
  if (opts.useCase && opts.useCase !== DEFAULT_SETUP_USE_CASE) parts.push(`--use-case ${opts.useCase}`);
14785
- if (opts.design) parts.push(`--design ${opts.design}`);
14786
14548
  if (opts.renderer && opts.renderer !== "react") parts.push(`--renderer ${opts.renderer}`);
14787
14549
  if (opts.devPort !== void 0) parts.push(`--dev-port ${opts.devPort}`);
14788
14550
  if (opts.nonInteractive) parts.push("--non-interactive");
@@ -14799,7 +14561,6 @@ function retryOptionsFromFlags(flags) {
14799
14561
  framework: flags.framework,
14800
14562
  preset: flags.preset,
14801
14563
  useCase: flags["use-case"],
14802
- design: flags.design,
14803
14564
  renderer: flags.renderer,
14804
14565
  devPort: flags["dev-port"],
14805
14566
  nonInteractive: Boolean(flags["non-interactive"])
@@ -14893,9 +14654,6 @@ const SENTENCE_BY_PATH = {
14893
14654
  ".zitadel/meta/user-schema.json": { subject: "the user-schema dialect spec" },
14894
14655
  ".zitadel/meta/user-property.json": { subject: "the user-property dialect spec" },
14895
14656
  ".zitadel/meta/branding.json": { subject: "the branding dialect spec" },
14896
- ".zitadel/branding/branding.json": { subject: "the branding descriptor (layout + asset URLs)" },
14897
- ".zitadel/branding/login.liquid": { subject: "the editable login template" },
14898
- ".zitadel/branding/README.md": { subject: "the branding folder README" },
14899
14657
  "AGENTS.md": { subject: "the agent guidance (golden journey + config dialect)" },
14900
14658
  "README.md": { subject: "the README's Zitadel section" },
14901
14659
  "app/page.tsx": { subject: "the home page redirect" },
@@ -14909,7 +14667,7 @@ const SENTENCE_BY_PATH = {
14909
14667
  };
14910
14668
  /** Builds the section list driving {@link renderSummary} for the setup command. */
14911
14669
  function buildSummary(opts) {
14912
- const { projectFacts, writtenRel, depsAdded, project, server, issuer, scaffoldedFramework, design } = opts;
14670
+ const { projectFacts, writtenRel, depsAdded, project, server, issuer, scaffoldedFramework } = opts;
14913
14671
  const packageJsonHit = pickWrittenFile(writtenRel, "package.json");
14914
14672
  const detected = [{
14915
14673
  label: "Framework",
@@ -14958,23 +14716,15 @@ function buildSummary(opts) {
14958
14716
  value: path(viteConfigHit)
14959
14717
  });
14960
14718
  const customizeRows = [];
14961
- for (const [label, suffix, dir] of [
14962
- [
14963
- "User schema",
14964
- ".zitadel/schemas/default-human-user.json",
14965
- ".zitadel/schemas/"
14966
- ],
14967
- [
14968
- "Login flow",
14969
- ".zitadel/flows/default-login.json",
14970
- ".zitadel/flows/"
14971
- ],
14972
- [
14973
- "Login template",
14974
- ".zitadel/branding/login.liquid",
14975
- ".zitadel/branding/"
14976
- ]
14977
- ]) if (pickWrittenFile(writtenRel, suffix)) customizeRows.push({
14719
+ for (const [label, suffix, dir] of [[
14720
+ "User schema",
14721
+ ".zitadel/schemas/default-human-user.json",
14722
+ ".zitadel/schemas/"
14723
+ ], [
14724
+ "Login flow",
14725
+ ".zitadel/flows/default-login.json",
14726
+ ".zitadel/flows/"
14727
+ ]]) if (pickWrittenFile(writtenRel, suffix)) customizeRows.push({
14978
14728
  label,
14979
14729
  value: path(dir),
14980
14730
  secondary: "see its README.md"
@@ -14991,14 +14741,6 @@ function buildSummary(opts) {
14991
14741
  {
14992
14742
  label: "App will run",
14993
14743
  value: url(issuer)
14994
- },
14995
- design ? {
14996
- label: "Login design",
14997
- value: brandingDesignLabel(design),
14998
- secondary: `${design} · ${path(".zitadel/branding/")}`
14999
- } : {
15000
- label: "Login design",
15001
- value: dim("built-in template")
15002
14744
  }
15003
14745
  ];
15004
14746
  return [
@@ -15471,7 +15213,7 @@ async function detectUserPresence(cwd, source) {
15471
15213
  const probe = createZitadelClient$1({
15472
15214
  baseUrl: source,
15473
15215
  token: (await readZitadelSecret(cwd)).project_secret
15474
- }).queryUsers({ limit: 1 }, { signal: AbortSignal.timeout(PRESENCE_PROBE_TIMEOUT_MS) }).then(({ users }) => users.length > 0 ? "some" : "none").catch(() => "unknown");
15216
+ }).queryUsers({ limit: 1 }, void 0, { signal: AbortSignal.timeout(PRESENCE_PROBE_TIMEOUT_MS) }).then(({ users }) => users.length > 0 ? "some" : "none").catch(() => "unknown");
15475
15217
  return await Promise.race([probe, new Promise((resolve) => {
15476
15218
  setTimeout(() => resolve("unknown"), PRESENCE_PROBE_TIMEOUT_MS + 250).unref();
15477
15219
  })]);
@@ -15705,7 +15447,7 @@ function parseVariableValue(raw, type) {
15705
15447
  }
15706
15448
  /**
15707
15449
  * Read a value from stdin, for the non-interactive path
15708
- * (`variables set NAME --secret < secret.txt`).
15450
+ * (`variables set NAME --project-level --secret < secret.txt`).
15709
15451
  *
15710
15452
  * A single trailing newline is stripped — a here-doc or `echo` adds one and it
15711
15453
  * is not part of the credential — but nothing else is trimmed, because
@@ -15719,17 +15461,18 @@ async function readStdin(stream) {
15719
15461
  //#endregion
15720
15462
  //#region src/commands/variables/delete.ts
15721
15463
  /**
15722
- * The `variables delete` topic command — remove one variable from one owner.
15464
+ * The `variables delete` topic command — remove one variable from the project
15465
+ * level.
15723
15466
  *
15724
- * A variable is removable only by the owner that entered it: deleting a name
15725
- * another owner of the same project holds answers `var.not_found` and leaves
15726
- * that owner's value standing (ADR 062 §4). Destructive, so it takes the same
15467
+ * A variable is removable only by the owner that entered it: deleting a name an
15468
+ * environment of the same project holds answers `var.not_found` and leaves that
15469
+ * owner's value standing (ADR 062 §4). Destructive, so it takes the same
15727
15470
  * `--force`-or-confirm gate the resource commands' `delete` uses.
15728
15471
  */
15729
- var VariablesDelete = class VariablesDelete extends EnvironmentCommand {
15730
- static description = "Delete one variable from an environment or the project.";
15472
+ var VariablesDelete = class VariablesDelete extends OwnerCommand {
15473
+ static description = "Delete one variable from the project.";
15731
15474
  static group = CommandGroups.configuration;
15732
- static examples = ["<%= config.bin %> variables delete GOOGLE_CLIENT_ID --environment prod", "<%= config.bin %> variables delete GOOGLE_CLIENT_ID --project-level --force"];
15475
+ static examples = ["<%= config.bin %> variables delete GOOGLE_CLIENT_ID --project-level", "<%= config.bin %> variables delete GOOGLE_CLIENT_ID --project-level --force"];
15733
15476
  static args = { name: Args.string({
15734
15477
  required: true,
15735
15478
  description: "Variable name to delete."
@@ -15744,29 +15487,24 @@ var VariablesDelete = class VariablesDelete extends EnvironmentCommand {
15744
15487
  const { nonInteractive, dryRun, force, cliVersion } = this.meta;
15745
15488
  const name = args.name;
15746
15489
  assertVariableName(name);
15747
- const { client, scope, environment } = await this.connect();
15748
- const where = ownerLabel(environment);
15490
+ const { client, scope } = await this.connect();
15749
15491
  if (dryRun) {
15750
15492
  const preview = dryRunResult("delete", "variables", name);
15751
15493
  return this.emit({
15752
15494
  ...preview,
15753
- data: {
15754
- ...preview.data,
15755
- environment: environment ?? null
15756
- },
15757
- pretty: `${preview.pretty} from ${where}`
15495
+ pretty: `${preview.pretty} from the project`
15758
15496
  });
15759
15497
  }
15760
15498
  if (!force) {
15761
15499
  if (nonInteractive) {
15762
- const retry = publicCliCommand(`variables delete ${name} ${environment ? `--environment ${environment}` : "--project-level"} --force`, cliVersion);
15500
+ const retry = publicCliCommand(`variables delete ${name} --project-level --force`, cliVersion);
15763
15501
  throw new ZitadelError("E_VALIDATION", "Deleting a variable requires --force in non-interactive mode", {
15764
- hint: `Pass --force to delete ${name} from ${where}.`,
15502
+ hint: `Pass --force to delete ${name} from the project.`,
15765
15503
  nextCommands: [retry]
15766
15504
  });
15767
15505
  }
15768
15506
  const answer = await confirm({
15769
- message: `Delete ${name} from ${where}?`,
15507
+ message: `Delete ${name} from the project?`,
15770
15508
  initialValue: false
15771
15509
  });
15772
15510
  if (isCancel(answer) || !answer) {
@@ -15778,32 +15516,31 @@ var VariablesDelete = class VariablesDelete extends EnvironmentCommand {
15778
15516
  }
15779
15517
  }
15780
15518
  await client.deleteVariable(name, scope);
15781
- this.recordTelemetry({ is_environment_scoped: environment !== void 0 });
15782
15519
  return this.emit({
15783
15520
  status: "ok",
15784
15521
  data: {
15785
- environment: environment ?? null,
15786
15522
  name,
15787
15523
  deleted: true
15788
15524
  },
15789
- pretty: `Deleted ${name} from ${where}`
15525
+ pretty: `Deleted ${name} from the project`
15790
15526
  });
15791
15527
  }
15792
15528
  };
15793
15529
  //#endregion
15794
15530
  //#region src/commands/variables/get.ts
15795
15531
  /**
15796
- * The `variables get` topic command — read one variable by name at one owner.
15532
+ * The `variables get` topic command — read one variable by name at the project
15533
+ * level.
15797
15534
  *
15798
- * A name that owner has not entered answers `var.not_found`, even when another
15799
- * owner of the same project holds it: nothing is inherited (ADR 062 §4). A
15800
- * secret is found but not disclosed, so the answer says it is held and nothing
15801
- * more.
15535
+ * A name the project level has not entered answers `var.not_found`, even when
15536
+ * an environment of the same project holds it: nothing is inherited
15537
+ * (ADR 062 §4). A secret is found but not disclosed, so the answer says it is
15538
+ * held and nothing more.
15802
15539
  */
15803
- var VariablesGet = class VariablesGet extends EnvironmentCommand {
15804
- static description = "Get one variable from an environment or the project.";
15540
+ var VariablesGet = class VariablesGet extends OwnerCommand {
15541
+ static description = "Get one variable from the project.";
15805
15542
  static group = CommandGroups.configuration;
15806
- static examples = ["<%= config.bin %> variables get GOOGLE_CLIENT_ID --environment prod", "<%= config.bin %> variables get GOOGLE_CLIENT_ID --environment prod --json"];
15543
+ static examples = ["<%= config.bin %> variables get GOOGLE_CLIENT_ID --project-level", "<%= config.bin %> variables get GOOGLE_CLIENT_ID --project-level --json"];
15807
15544
  static args = { name: Args.string({
15808
15545
  required: true,
15809
15546
  description: "Variable name to read."
@@ -15813,16 +15550,10 @@ var VariablesGet = class VariablesGet extends EnvironmentCommand {
15813
15550
  await this.toMeta(flags);
15814
15551
  const name = args.name;
15815
15552
  assertVariableName(name);
15816
- const { client, scope, environment } = await this.connect();
15553
+ const { client, scope } = await this.connect();
15817
15554
  const row = toVariableRow(name, await client.getVariable(name, scope));
15818
- this.recordTelemetry({
15819
- is_secret: row.secret,
15820
- is_environment_scoped: environment !== void 0
15821
- });
15822
- const data = {
15823
- environment: environment ?? null,
15824
- ...row
15825
- };
15555
+ this.recordTelemetry({ is_secret: row.secret });
15556
+ const data = row;
15826
15557
  if (!process.stdout.isTTY) return this.emit({
15827
15558
  status: "ok",
15828
15559
  data,
@@ -15832,10 +15563,7 @@ var VariablesGet = class VariablesGet extends EnvironmentCommand {
15832
15563
  return this.emit({
15833
15564
  status: "ok",
15834
15565
  data,
15835
- pretty: renderDetail(name, ["environment", "value"], {
15836
- environment: ownerLabel(environment),
15837
- value: cell?.value === "" ? "\"\"" : cell?.value
15838
- })
15566
+ pretty: renderDetail(name, ["value"], { value: cell?.value === "" ? "\"\"" : cell?.value })
15839
15567
  });
15840
15568
  }
15841
15569
  };
@@ -15844,37 +15572,33 @@ var VariablesGet = class VariablesGet extends EnvironmentCommand {
15844
15572
  /** The columns `list` renders, in order. */
15845
15573
  const COLUMNS = ["name", "value"];
15846
15574
  /**
15847
- * The `variables list` topic command — show the variables entered at one owner.
15575
+ * The `variables list` topic command — show the variables entered at the
15576
+ * project level.
15848
15577
  *
15849
- * Owners are separate, not a ladder: an environment does not see the project
15850
- * level's variables, nor the project level an environment's (ADR 062 §4). A
15851
- * secret is reported as held without its value, because the platform never
15852
- * discloses one.
15578
+ * Owners are separate, not a ladder (ADR 062 §4), so this is the project's own
15579
+ * set and not a merge of anything an environment holds. A secret is reported as
15580
+ * held without its value, because the platform never discloses one.
15853
15581
  */
15854
- var VariablesList = class VariablesList extends EnvironmentCommand {
15855
- static description = "List the variables entered on an environment or the project.";
15582
+ var VariablesList = class VariablesList extends OwnerCommand {
15583
+ static description = "List the variables entered on the project.";
15856
15584
  static group = CommandGroups.configuration;
15857
- static examples = ["<%= config.bin %> variables list --environment prod", "<%= config.bin %> variables list --project-level --json"];
15585
+ static examples = ["<%= config.bin %> variables list --project-level", "<%= config.bin %> variables list --project-level --json"];
15858
15586
  static flags = { plain: Flags.boolean({ description: "Tab-separated rows with no header, for piping. Implied when stdout is not a terminal." }) };
15859
15587
  async run() {
15860
15588
  const { flags } = await this.parse(VariablesList);
15861
15589
  await this.toMeta(flags);
15862
- const { client, scope, environment } = await this.connect();
15590
+ const { client, scope } = await this.connect();
15863
15591
  const rows = toVariableRows(await client.getVariables(scope));
15864
- this.recordTelemetry({
15865
- variable_count: rows.length,
15866
- is_environment_scoped: environment !== void 0
15867
- });
15592
+ this.recordTelemetry({ variable_count: rows.length });
15868
15593
  const cells = variableCells(rows);
15869
15594
  const plain = flags.plain || !process.stdout.isTTY;
15870
15595
  return this.emit({
15871
15596
  status: "ok",
15872
15597
  data: {
15873
- environment: environment ?? null,
15874
15598
  variables: rows,
15875
15599
  count: rows.length
15876
15600
  },
15877
- pretty: plain ? renderRows(COLUMNS, cells) : rows.length === 0 ? `No variables on ${ownerLabel(environment)}.` : [
15601
+ pretty: plain ? renderRows(COLUMNS, cells) : rows.length === 0 ? "No variables on the project." : [
15878
15602
  renderTable(COLUMNS, cells),
15879
15603
  "",
15880
15604
  `${rows.length} ${rows.length === 1 ? "variable" : "variables"}`
@@ -15885,20 +15609,20 @@ var VariablesList = class VariablesList extends EnvironmentCommand {
15885
15609
  //#endregion
15886
15610
  //#region src/commands/variables/set.ts
15887
15611
  /**
15888
- * The `variables set` topic command — enter or replace one variable at one
15889
- * owner.
15612
+ * The `variables set` topic command — enter or replace one variable at the
15613
+ * project level.
15890
15614
  *
15891
15615
  * The value never comes from a flag. It is prompted for, or read from stdin
15892
15616
  * when scripting, so a credential cannot land in shell history, a process
15893
15617
  * listing, or CI logs. `--secret` stores it encrypted under the project's own
15894
15618
  * key, after which it can be replaced but never read back (ADR 062 §7).
15895
15619
  */
15896
- var VariablesSet = class VariablesSet extends EnvironmentCommand {
15897
- static description = "Set one variable on an environment or the project.";
15620
+ var VariablesSet = class VariablesSet extends OwnerCommand {
15621
+ static description = "Set one variable on the project.";
15898
15622
  static group = CommandGroups.configuration;
15899
15623
  static examples = [
15900
- "<%= config.bin %> variables set GOOGLE_CLIENT_ID --environment prod",
15901
- "<%= config.bin %> variables set GOOGLE_CLIENT_SECRET --environment prod --secret < secret.txt",
15624
+ "<%= config.bin %> variables set GOOGLE_CLIENT_ID --project-level",
15625
+ "<%= config.bin %> variables set GOOGLE_CLIENT_SECRET --project-level --secret < secret.txt",
15902
15626
  "<%= config.bin %> variables set SESSION_TTL --project-level --as number"
15903
15627
  ];
15904
15628
  static args = { name: Args.string({
@@ -15924,23 +15648,21 @@ var VariablesSet = class VariablesSet extends EnvironmentCommand {
15924
15648
  const type = flags.as;
15925
15649
  assertVariableName(name);
15926
15650
  if (flags.secret && type !== "string") throw new ZitadelError("E_VALIDATION", "A secret is stored as a string.", { hint: "Drop --as, or drop --secret for a non-credential number or boolean." });
15927
- const { client, scope, environment } = await this.connect();
15928
- const where = ownerLabel(environment);
15651
+ const { client, scope } = await this.connect();
15929
15652
  if (dryRun) {
15930
15653
  const preview = dryRunResult("set", "variables", name);
15931
15654
  return this.emit({
15932
15655
  ...preview,
15933
15656
  data: {
15934
15657
  ...preview.data,
15935
- environment: environment ?? null,
15936
15658
  secret: flags.secret,
15937
15659
  as: type
15938
15660
  },
15939
- pretty: `${preview.pretty} on ${where}${flags.secret ? " (secret)" : ""}${type === "string" ? "" : ` as ${type}`}`
15661
+ pretty: `${preview.pretty} on the project${flags.secret ? " (secret)" : ""}${type === "string" ? "" : ` as ${type}`}`
15940
15662
  });
15941
15663
  }
15942
15664
  if (nonInteractive && process.stdin.isTTY) {
15943
- const retry = pipeHint(name, environment, flags.secret, type, cliVersion);
15665
+ const retry = pipeHint(name, flags.secret, type, cliVersion);
15944
15666
  throw new ZitadelError("E_VALIDATION", `No value supplied for ${name}.`, {
15945
15667
  hint: `Pipe the value in: ${retry}`,
15946
15668
  nextCommands: [retry]
@@ -15956,19 +15678,15 @@ var VariablesSet = class VariablesSet extends EnvironmentCommand {
15956
15678
  value,
15957
15679
  secret: flags.secret
15958
15680
  } }, scope);
15959
- this.recordTelemetry({
15960
- is_secret: flags.secret,
15961
- is_environment_scoped: environment !== void 0
15962
- });
15681
+ this.recordTelemetry({ is_secret: flags.secret });
15963
15682
  return this.emit({
15964
15683
  status: "ok",
15965
15684
  data: {
15966
- environment: environment ?? null,
15967
15685
  name,
15968
15686
  secret: flags.secret,
15969
15687
  type
15970
15688
  },
15971
- pretty: `Set ${name} on ${where}${flags.secret ? " (secret)" : ""}`
15689
+ pretty: `Set ${name} on the project${flags.secret ? " (secret)" : ""}`
15972
15690
  });
15973
15691
  }
15974
15692
  };
@@ -15976,15 +15694,15 @@ var VariablesSet = class VariablesSet extends EnvironmentCommand {
15976
15694
  * The scripted form to suggest when a run supplied no value.
15977
15695
  *
15978
15696
  * It carries the owner, the secret flag and the type the run actually used.
15979
- * Dropping any of them would hand back a command that writes somewhere else,
15980
- * writes a credential as a readable value, or stores a number as a string.
15697
+ * Dropping any of them would hand back a command that writes a credential as a
15698
+ * readable value, or stores a number as a string.
15981
15699
  */
15982
- function pipeHint(name, environment, secret, type, cliVersion) {
15700
+ function pipeHint(name, secret, type, cliVersion) {
15983
15701
  return `${publicCliCommand([
15984
15702
  "variables",
15985
15703
  "set",
15986
15704
  name,
15987
- ...environment ? ["--environment", environment] : ["--project-level"],
15705
+ "--project-level",
15988
15706
  ...secret ? ["--secret"] : [],
15989
15707
  ...type === "string" ? [] : ["--as", type]
15990
15708
  ].join(" "), cliVersion)} < value.txt`;