@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/README.md +513 -655
- package/SKILLS.md +52 -45
- package/dist/index.mjs +230 -512
- package/dist/index.mjs.map +1 -1
- package/oclif.manifest.json +25 -581
- package/package.json +9 -6
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
|
-
*
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
|
2337
|
-
*
|
|
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/
|
|
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
|
|
2371
|
-
*
|
|
2372
|
-
* holds reaches
|
|
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
|
-
*
|
|
2376
|
-
*
|
|
2377
|
-
*
|
|
2378
|
-
*
|
|
2379
|
-
|
|
2380
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2402
|
-
*
|
|
2403
|
-
* `
|
|
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 (
|
|
2409
|
-
|
|
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
|
|
2420
|
-
*
|
|
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
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
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(
|
|
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
|
|
4518
|
-
*
|
|
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: "
|
|
4526
|
-
hint: "the built-in
|
|
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: "
|
|
4390
|
+
hint: "the same form without card chrome"
|
|
4543
4391
|
}
|
|
4544
4392
|
};
|
|
4545
|
-
/**
|
|
4546
|
-
|
|
4547
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
12656
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
13667
|
-
*
|
|
13668
|
-
*
|
|
13669
|
-
*
|
|
13670
|
-
*
|
|
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(
|
|
13673
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
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
|
|
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
|
-
*
|
|
15726
|
-
*
|
|
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
|
|
15730
|
-
static description = "Delete one variable from
|
|
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 --
|
|
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
|
|
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
|
-
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
15532
|
+
* The `variables get` topic command — read one variable by name at the project
|
|
15533
|
+
* level.
|
|
15797
15534
|
*
|
|
15798
|
-
* A name
|
|
15799
|
-
*
|
|
15800
|
-
* secret is found but not disclosed, so the answer says it is
|
|
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
|
|
15804
|
-
static description = "Get one variable from
|
|
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 --
|
|
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
|
|
15553
|
+
const { client, scope } = await this.connect();
|
|
15817
15554
|
const row = toVariableRow(name, await client.getVariable(name, scope));
|
|
15818
|
-
this.recordTelemetry({
|
|
15819
|
-
|
|
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, ["
|
|
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
|
|
15575
|
+
* The `variables list` topic command — show the variables entered at the
|
|
15576
|
+
* project level.
|
|
15848
15577
|
*
|
|
15849
|
-
* Owners are separate, not a ladder
|
|
15850
|
-
*
|
|
15851
|
-
*
|
|
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
|
|
15855
|
-
static description = "List the variables entered on
|
|
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 --
|
|
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
|
|
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 ?
|
|
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
|
|
15889
|
-
*
|
|
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
|
|
15897
|
-
static description = "Set one variable on
|
|
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 --
|
|
15901
|
-
"<%= config.bin %> variables set GOOGLE_CLIENT_SECRET --
|
|
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
|
|
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 ${
|
|
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,
|
|
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 ${
|
|
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
|
|
15980
|
-
*
|
|
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,
|
|
15700
|
+
function pipeHint(name, secret, type, cliVersion) {
|
|
15983
15701
|
return `${publicCliCommand([
|
|
15984
15702
|
"variables",
|
|
15985
15703
|
"set",
|
|
15986
15704
|
name,
|
|
15987
|
-
|
|
15705
|
+
"--project-level",
|
|
15988
15706
|
...secret ? ["--secret"] : [],
|
|
15989
15707
|
...type === "string" ? [] : ["--as", type]
|
|
15990
15708
|
].join(" "), cliVersion)} < value.txt`;
|