@zitadel/cli 0.1.0-alpha.17 → 0.1.0-alpha.19
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 +210 -124
- package/SKILLS.md +159 -17
- package/dist/branding-cygSBPkV.mjs +79 -0
- package/dist/branding-cygSBPkV.mjs.map +1 -0
- package/dist/claim-state-DlfD6Res.mjs +66 -0
- package/dist/claim-state-DlfD6Res.mjs.map +1 -0
- package/dist/commands/apply.mjs +6 -5
- package/dist/commands/apply.mjs.map +1 -1
- package/dist/commands/branding/eject.mjs +104 -0
- package/dist/commands/branding/eject.mjs.map +1 -0
- package/dist/commands/claim.mjs +402 -0
- package/dist/commands/claim.mjs.map +1 -0
- package/dist/commands/doctor.mjs +500 -37
- package/dist/commands/doctor.mjs.map +1 -1
- package/dist/commands/eject.mjs +4 -4
- package/dist/commands/eject.mjs.map +1 -1
- package/dist/commands/logs.mjs +2 -2
- package/dist/commands/plan.mjs +6 -5
- package/dist/commands/plan.mjs.map +1 -1
- package/dist/commands/reset.mjs +2 -2
- package/dist/commands/schemas/list.mjs +11 -11
- package/dist/commands/schemas/list.mjs.map +1 -1
- package/dist/commands/setup.mjs +359 -132
- package/dist/commands/setup.mjs.map +1 -1
- package/dist/commands/start.mjs +3 -3
- package/dist/commands/status.mjs +21 -13
- package/dist/commands/status.mjs.map +1 -1
- package/dist/commands/stop.mjs +3 -3
- package/dist/commands/stop.mjs.map +1 -1
- package/dist/designs-Ckz18Dpo.mjs +38 -0
- package/dist/designs-Ckz18Dpo.mjs.map +1 -0
- package/dist/{docker-pGcTrDIY.mjs → docker-DcRGTOJa.mjs} +4 -3
- package/dist/docker-DcRGTOJa.mjs.map +1 -0
- package/dist/{environment-BQF7LeCz.mjs → environment-rjRVkJjW.mjs} +1 -1
- package/dist/{environment-BQF7LeCz.mjs.map → environment-rjRVkJjW.mjs.map} +1 -1
- package/dist/{journey-guidance-CYVpQszz.mjs → journey-guidance-BGrOX_gT.mjs} +14 -3
- package/dist/{journey-guidance-CYVpQszz.mjs.map → journey-guidance-BGrOX_gT.mjs.map} +1 -1
- package/dist/{oclif-BRdjVNne.mjs → oclif-CanO3zdt.mjs} +247 -43
- package/dist/oclif-CanO3zdt.mjs.map +1 -0
- package/dist/{orca-Dip6DPJ2.mjs → orca-Cu0iW1mv.mjs} +894 -196
- package/dist/orca-Cu0iW1mv.mjs.map +1 -0
- package/dist/package-manager-DFdLcDx1.mjs +194 -0
- package/dist/package-manager-DFdLcDx1.mjs.map +1 -0
- package/dist/{ports-V8JfiNgT.mjs → ports-BM20XIZb.mjs} +2 -2
- package/dist/{ports-V8JfiNgT.mjs.map → ports-BM20XIZb.mjs.map} +1 -1
- package/dist/{processes-BVqYsxT8.mjs → processes-DKHPkU8O.mjs} +1 -1
- package/dist/{processes-BVqYsxT8.mjs.map → processes-DKHPkU8O.mjs.map} +1 -1
- package/dist/{project-BGgv7Bba.mjs → project-CHhot85s.mjs} +57 -4
- package/dist/project-CHhot85s.mjs.map +1 -0
- package/dist/{sync-Cd3z7Q1R.mjs → sync-Bd6vASgg.mjs} +549 -75
- package/dist/sync-Bd6vASgg.mjs.map +1 -0
- package/dist/user-schema-DTuOsdKE.mjs +91 -0
- package/dist/user-schema-DTuOsdKE.mjs.map +1 -0
- package/oclif.manifest.json +227 -5
- package/package.json +9 -6
- package/dist/docker-pGcTrDIY.mjs.map +0 -1
- package/dist/oclif-BRdjVNne.mjs.map +0 -1
- package/dist/orca-Dip6DPJ2.mjs.map +0 -1
- package/dist/project-BGgv7Bba.mjs.map +0 -1
- package/dist/sync-Cd3z7Q1R.mjs.map +0 -1
- package/dist/user-schema-DDz5-lX5.mjs +0 -13
- package/dist/user-schema-DDz5-lX5.mjs.map +0 -1
package/dist/commands/setup.mjs
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { n as
|
|
1
|
+
import { n as brandingDesignLabel, t as BRANDING_DESIGN_INFO } from "../designs-Ckz18Dpo.mjs";
|
|
2
|
+
import { D as toZitadelError, E as ZitadelError, T as stableStringify, j as publicCliCommand, k as normalizePublicCliProse, n as DEFAULT_SERVER, t as BaseCommand, u as detectHealthyLocalServer } from "../oclif-CanO3zdt.mjs";
|
|
3
|
+
import { n as claimCommand, r as claimState, t as claimAction } from "../claim-state-DlfD6Res.mjs";
|
|
4
|
+
import { n as customizeAndPublishActions, r as verifyLoginAction, t as brandingGuidanceAction } from "../journey-guidance-BGrOX_gT.mjs";
|
|
5
|
+
import { i as RENDERER_IDS, n as inspectScaffoldTarget, r as AVAILABLE_RENDERER_IDS, s as issuerFromPort, t as createOrca } from "../orca-Cu0iW1mv.mjs";
|
|
6
|
+
import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-CHhot85s.mjs";
|
|
7
|
+
import { a as updateState, t as SCHEMAS_DIR } from "../user-schema-DTuOsdKE.mjs";
|
|
8
|
+
import { a as runPackageCommand, c as writeScaffoldManifest, i as installCommandFor, n as detectPackageManager, r as devCommandFor } from "../package-manager-DFdLcDx1.mjs";
|
|
9
|
+
import { i as toBrandingWireBody, t as BRANDING_DIR } from "../branding-cygSBPkV.mjs";
|
|
10
|
+
import { c as writeBackResource, o as hashForState, u as FLOWS_DIR } from "../sync-Bd6vASgg.mjs";
|
|
7
11
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
8
12
|
import { basename, dirname, join } from "node:path";
|
|
9
13
|
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
10
14
|
import { Flags } from "@oclif/core";
|
|
11
15
|
import { createZitadelClient } from "@zitadel/api/client";
|
|
12
|
-
import { DEFAULT_FLOW_CONFIG_PATH, DEFAULT_FLOW_SCHEMA_URI, DEFAULT_SCHEMA_CONFIG_PATH, DEFAULT_SETUP_PRESET, SETUP_PRESETS, flowsReadmeContent, getDefaultHumanUserSchema, getDefaultLoginFlow, schemasReadmeContent } from "@zitadel/config/defaults";
|
|
16
|
+
import { BRANDING_DESIGNS, DEFAULT_BRANDING_CONFIG_PATH, 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";
|
|
13
17
|
import { consola as consola$1 } from "consola";
|
|
14
|
-
import {
|
|
18
|
+
import { BRANDING_FILE_SCHEMA_REF } from "@zitadel/config/meta-schemas";
|
|
15
19
|
import { normalizeFlowBody, normalizeSchemaBody } from "@zitadel/config/normalize";
|
|
16
20
|
import pc from "picocolors";
|
|
21
|
+
//#region src/lib/orca/patchers/posture.ts
|
|
22
|
+
/**
|
|
23
|
+
* Frameworks whose patchers add route files without owning the app shell —
|
|
24
|
+
* the only ones where a pre-existing app keeps a layout for the widget
|
|
25
|
+
* posture to inherit (ADR 044). The SPA families write the app's root
|
|
26
|
+
* component, so nothing survives for a widget to embed into; they keep the
|
|
27
|
+
* page posture until a non-destructive route/layout insertion contract
|
|
28
|
+
* exists for their routers.
|
|
29
|
+
*/
|
|
30
|
+
const ROUTE_BASED_FRAMEWORKS = new Set(["next", "nuxt"]);
|
|
31
|
+
/**
|
|
32
|
+
* The default embedding posture of the scaffolded auth/profile pages
|
|
33
|
+
* (ADR 044), derived from the same hinge as the framework homepage: whether
|
|
34
|
+
* setup created the app skeleton itself. A fresh scaffold has no design to
|
|
35
|
+
* respect — full-page chrome is the strongest start. A pre-existing
|
|
36
|
+
* route-based app has its own shell and theme, so the pages embed
|
|
37
|
+
* `variant="widget"` cards in a layout-neutral wrapper instead of painting
|
|
38
|
+
* token-colored chrome underneath the host layout.
|
|
39
|
+
*
|
|
40
|
+
* Derived once at setup time and recorded in the scaffold manifest;
|
|
41
|
+
* `doctor --fix` restores from the record rather than re-deriving (a
|
|
42
|
+
* manifest-less legacy scaffold could not answer the hinge).
|
|
43
|
+
*/
|
|
44
|
+
function derivePosture(frameworkId, scaffoldedFramework) {
|
|
45
|
+
if (!ROUTE_BASED_FRAMEWORKS.has(frameworkId)) return "page";
|
|
46
|
+
return scaffoldedFramework ? "page" : "widget";
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
17
49
|
//#region src/lib/setup-resources.ts
|
|
18
50
|
/**
|
|
19
51
|
* Scaffolds the versioned local default resources for a new project, uploads
|
|
@@ -31,12 +63,16 @@ async function materializeSetupResources(opts) {
|
|
|
31
63
|
await mkdir(join(opts.cwd, SCHEMAS_DIR), { recursive: true });
|
|
32
64
|
const filesWritten = [];
|
|
33
65
|
const preset = opts.preset ?? DEFAULT_SETUP_PRESET;
|
|
34
|
-
const
|
|
66
|
+
const useCase = opts.useCase ?? DEFAULT_SETUP_USE_CASE;
|
|
67
|
+
const { $id: _templateId, ...schemaBody } = getDefaultHumanUserSchema({
|
|
68
|
+
preset,
|
|
69
|
+
useCase
|
|
70
|
+
});
|
|
35
71
|
if (await writeResourceFile(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH, schemaBody, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH));
|
|
36
72
|
const schemaId = requiredString((await opts.client.createSchema(schemaBody, { project_id: opts.projectId })).id, "created schema id");
|
|
37
73
|
let schemaHash = hashForState({ normalize: normalizeSchemaBody }, schemaBody);
|
|
38
74
|
try {
|
|
39
|
-
const canonical = await opts.client.getSchemaById(encodeURIComponent(schemaId)
|
|
75
|
+
const canonical = await opts.client.getSchemaById(encodeURIComponent(schemaId));
|
|
40
76
|
schemaHash = (await writeBackResource(opts.cwd, DEFAULT_SCHEMA_CONFIG_PATH, { normalize: normalizeSchemaBody }, canonical)).hash;
|
|
41
77
|
} catch (err) {
|
|
42
78
|
consola$1.debug(`fetch created schema ${schemaId} during setup failed:`, err);
|
|
@@ -47,7 +83,8 @@ async function materializeSetupResources(opts) {
|
|
|
47
83
|
});
|
|
48
84
|
const flowBody = getDefaultLoginFlow({
|
|
49
85
|
userSchemaUrl: schemaId,
|
|
50
|
-
preset
|
|
86
|
+
preset,
|
|
87
|
+
useCase
|
|
51
88
|
});
|
|
52
89
|
if (await writeResourceFile(opts.cwd, DEFAULT_FLOW_CONFIG_PATH, flowBody, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_FLOW_CONFIG_PATH));
|
|
53
90
|
const flow = await opts.client.createFlowDefinition({
|
|
@@ -66,13 +103,47 @@ async function materializeSetupResources(opts) {
|
|
|
66
103
|
name: flowBody.name,
|
|
67
104
|
status: flowBody.status
|
|
68
105
|
});
|
|
106
|
+
if (opts.design) {
|
|
107
|
+
await mkdir(join(opts.cwd, BRANDING_DIR), { recursive: true });
|
|
108
|
+
const { branding, template } = getDefaultBrandingConfig(opts.design);
|
|
109
|
+
const descriptor = {
|
|
110
|
+
$schema: BRANDING_FILE_SCHEMA_REF,
|
|
111
|
+
...branding
|
|
112
|
+
};
|
|
113
|
+
if (await writeResourceFile(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH, descriptor, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH));
|
|
114
|
+
if (await writeRawFile(opts.cwd, DEFAULT_BRANDING_TEMPLATE_PATH, template, opts.force)) filesWritten.push(join(opts.cwd, DEFAULT_BRANDING_TEMPLATE_PATH));
|
|
115
|
+
const brandingNormalize = (data) => toBrandingWireBody(opts.cwd, data);
|
|
116
|
+
const created = await opts.client.createBranding(brandingNormalize(descriptor), { project_id: opts.projectId });
|
|
117
|
+
await updateState(opts.cwd, DEFAULT_BRANDING_CONFIG_PATH, {
|
|
118
|
+
id: requiredString(created.id, "created branding revision id"),
|
|
119
|
+
hash: hashForState({ normalize: brandingNormalize }, descriptor)
|
|
120
|
+
});
|
|
121
|
+
const brandingReadme = join(BRANDING_DIR, "README.md");
|
|
122
|
+
if (await writeReadmeFile(opts.cwd, brandingReadme, normalizePublicCliProse(brandingReadmeContent(), opts.cliVersion))) filesWritten.push(join(opts.cwd, brandingReadme));
|
|
123
|
+
}
|
|
69
124
|
const schemasReadme = join(SCHEMAS_DIR, "README.md");
|
|
70
125
|
const flowsReadme = join(FLOWS_DIR, "README.md");
|
|
71
|
-
if (await writeReadmeFile(opts.cwd, schemasReadme, schemasReadmeContent())) filesWritten.push(join(opts.cwd, schemasReadme));
|
|
72
|
-
if (await writeReadmeFile(opts.cwd, flowsReadme, flowsReadmeContent())) filesWritten.push(join(opts.cwd, flowsReadme));
|
|
126
|
+
if (await writeReadmeFile(opts.cwd, schemasReadme, normalizePublicCliProse(schemasReadmeContent(), opts.cliVersion))) filesWritten.push(join(opts.cwd, schemasReadme));
|
|
127
|
+
if (await writeReadmeFile(opts.cwd, flowsReadme, normalizePublicCliProse(flowsReadmeContent(), opts.cliVersion))) filesWritten.push(join(opts.cwd, flowsReadme));
|
|
73
128
|
return { filesWritten };
|
|
74
129
|
}
|
|
75
130
|
/**
|
|
131
|
+
* Write a non-JSON scaffold file (the `.liquid` template) with the same
|
|
132
|
+
* conflict semantics as {@link writeResourceFile}: `--force` overwrites,
|
|
133
|
+
* otherwise an existing file is an `E_CONFLICT`.
|
|
134
|
+
*/
|
|
135
|
+
async function writeRawFile(cwd, relPath, content, force) {
|
|
136
|
+
const dest = join(cwd, relPath);
|
|
137
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
138
|
+
try {
|
|
139
|
+
await writeFile(dest, content, force ? void 0 : { flag: "wx" });
|
|
140
|
+
return true;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
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." });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
76
147
|
* Write a README file, but never overwrite an existing one. A developer who
|
|
77
148
|
* has edited the README should keep their edits when `setup --force` is
|
|
78
149
|
* re-run.
|
|
@@ -106,88 +177,6 @@ function isErrno(error, code) {
|
|
|
106
177
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
107
178
|
}
|
|
108
179
|
//#endregion
|
|
109
|
-
//#region src/lib/package-manager.ts
|
|
110
|
-
async function detectPackageManager(cwd) {
|
|
111
|
-
const declared = await packageManagerFromManifest(cwd);
|
|
112
|
-
if (declared) return declared;
|
|
113
|
-
if (await exists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
114
|
-
if (await exists(join(cwd, "yarn.lock"))) return "yarn";
|
|
115
|
-
if (await exists(join(cwd, "bun.lock"))) return "bun";
|
|
116
|
-
if (await exists(join(cwd, "bun.lockb"))) return "bun";
|
|
117
|
-
if (await exists(join(cwd, "package-lock.json"))) return "npm";
|
|
118
|
-
return "npm";
|
|
119
|
-
}
|
|
120
|
-
function installCommandFor(packageManager) {
|
|
121
|
-
return command(packageManager, ["install"]);
|
|
122
|
-
}
|
|
123
|
-
function devCommandFor(packageManager) {
|
|
124
|
-
switch (packageManager) {
|
|
125
|
-
case "npm": return command("npm", ["run", "dev"]);
|
|
126
|
-
case "pnpm": return command("pnpm", ["dev"]);
|
|
127
|
-
case "yarn": return command("yarn", ["dev"]);
|
|
128
|
-
case "bun": return command("bun", ["run", "dev"]);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
async function runPackageCommand(packageCommand, options) {
|
|
132
|
-
await new Promise((resolve, reject) => {
|
|
133
|
-
const child = spawn(packageCommand.command, packageCommand.args, {
|
|
134
|
-
cwd: options.cwd,
|
|
135
|
-
env: options.env ?? process.env,
|
|
136
|
-
stdio: options.redirectStdoutToStderr ? [
|
|
137
|
-
"ignore",
|
|
138
|
-
"pipe",
|
|
139
|
-
"pipe"
|
|
140
|
-
] : "inherit"
|
|
141
|
-
});
|
|
142
|
-
if (options.redirectStdoutToStderr) {
|
|
143
|
-
child.stdout?.on("data", (chunk) => process.stderr.write(chunk));
|
|
144
|
-
child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
145
|
-
}
|
|
146
|
-
child.on("error", reject);
|
|
147
|
-
child.on("close", (code, signal) => {
|
|
148
|
-
if (code === 0) {
|
|
149
|
-
resolve();
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const detail = signal ? `signal ${signal}` : `exit ${String(code ?? 1)}`;
|
|
153
|
-
const error = /* @__PURE__ */ new Error(`${packageCommand.display} failed with ${detail}`);
|
|
154
|
-
Object.assign(error, {
|
|
155
|
-
code: code ?? 1,
|
|
156
|
-
signal
|
|
157
|
-
});
|
|
158
|
-
reject(error);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function command(packageManager, args) {
|
|
163
|
-
return {
|
|
164
|
-
command: packageManager,
|
|
165
|
-
args,
|
|
166
|
-
display: [packageManager, ...args].join(" ")
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
async function packageManagerFromManifest(cwd) {
|
|
170
|
-
try {
|
|
171
|
-
const raw = await readFile(join(cwd, "package.json"), "utf8");
|
|
172
|
-
const parsed = JSON.parse(raw);
|
|
173
|
-
return typeof parsed.packageManager === "string" ? packageManagerFromString(parsed.packageManager) : void 0;
|
|
174
|
-
} catch {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function packageManagerFromString(value) {
|
|
179
|
-
const name = value.split("@")[0];
|
|
180
|
-
return name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" ? name : void 0;
|
|
181
|
-
}
|
|
182
|
-
async function exists(path) {
|
|
183
|
-
try {
|
|
184
|
-
await stat(path);
|
|
185
|
-
return true;
|
|
186
|
-
} catch {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
//#endregion
|
|
191
180
|
//#region src/commands/setup/install.ts
|
|
192
181
|
async function installDependenciesForSetup(input) {
|
|
193
182
|
const packageManager = await detectPackageManager(input.cwd);
|
|
@@ -294,6 +283,54 @@ function bail(value) {
|
|
|
294
283
|
}
|
|
295
284
|
}
|
|
296
285
|
//#endregion
|
|
286
|
+
//#region src/commands/setup/prompts/design.ts
|
|
287
|
+
/**
|
|
288
|
+
* Sentinel select value for "keep the built-in login" — clack option values
|
|
289
|
+
* must be strings, and `undefined` in {@link SetupAnswers.design} is the
|
|
290
|
+
* real representation of that choice.
|
|
291
|
+
*/
|
|
292
|
+
const BUILT_IN = "built-in";
|
|
293
|
+
/**
|
|
294
|
+
* "How should the login look?" — the last wizard question (#676). Asks after
|
|
295
|
+
* the sign-in preset so the order tells a story: the use case owns what is
|
|
296
|
+
* collected, the preset owns how users authenticate, the design owns how
|
|
297
|
+
* that experience looks.
|
|
298
|
+
*
|
|
299
|
+
* The built-in default is preselected and writes nothing. Picking a starter
|
|
300
|
+
* design forks its Liquid template into `.zitadel/branding/` and publishes
|
|
301
|
+
* it as branding revision 1 — from then on the template is repo-owned and
|
|
302
|
+
* stops tracking built-in improvements, so the option labels make the
|
|
303
|
+
* file-write explicit and keep ownership an opt-in. `--design` is
|
|
304
|
+
* authoritative; non-interactive runs keep the built-in template.
|
|
305
|
+
*/
|
|
306
|
+
var DesignPrompt = class {
|
|
307
|
+
async ask(answers, ctx) {
|
|
308
|
+
if (ctx.designFromFlag) return answers;
|
|
309
|
+
const value = await select({
|
|
310
|
+
message: "How should the login look? A starter design adds its editable template to .zitadel/branding/.",
|
|
311
|
+
initialValue: answers.design ?? BUILT_IN,
|
|
312
|
+
options: [{
|
|
313
|
+
value: BUILT_IN,
|
|
314
|
+
label: "Built-in",
|
|
315
|
+
hint: "no files added; pick a design anytime later with `branding eject`"
|
|
316
|
+
}, ...BRANDING_DESIGNS.map((design) => ({
|
|
317
|
+
value: design,
|
|
318
|
+
label: BRANDING_DESIGN_INFO[design].label,
|
|
319
|
+
hint: BRANDING_DESIGN_INFO[design].hint
|
|
320
|
+
}))]
|
|
321
|
+
});
|
|
322
|
+
bail(value);
|
|
323
|
+
if (value === BUILT_IN) return {
|
|
324
|
+
...answers,
|
|
325
|
+
design: void 0
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
...answers,
|
|
329
|
+
design: value
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
297
334
|
//#region src/commands/setup/prompts/dev-port.ts
|
|
298
335
|
/**
|
|
299
336
|
* "Dev server port" — defaults to the detected port. The validated answer
|
|
@@ -445,6 +482,48 @@ var SignInPresetPrompt = class {
|
|
|
445
482
|
}
|
|
446
483
|
};
|
|
447
484
|
//#endregion
|
|
485
|
+
//#region src/commands/setup/prompts/use-case.ts
|
|
486
|
+
/**
|
|
487
|
+
* "Who will sign in to your app?" — picks the use case, which owns the
|
|
488
|
+
* schema field set the scaffold collects (#448). Orthogonal to the sign-in
|
|
489
|
+
* preset, so this asks first: what a user *is* before *how* they sign in.
|
|
490
|
+
* `--use-case` is authoritative; non-interactive runs keep the minimal
|
|
491
|
+
* default seeded by the command.
|
|
492
|
+
*/
|
|
493
|
+
var UseCasePrompt = class {
|
|
494
|
+
async ask(answers, ctx) {
|
|
495
|
+
if (ctx.useCaseFromFlag) return answers;
|
|
496
|
+
const options = {
|
|
497
|
+
minimal: {
|
|
498
|
+
label: "Just me or a small group",
|
|
499
|
+
hint: "collects email"
|
|
500
|
+
},
|
|
501
|
+
consumer: {
|
|
502
|
+
label: "Consumers",
|
|
503
|
+
hint: "collects email, given & family name"
|
|
504
|
+
},
|
|
505
|
+
business: {
|
|
506
|
+
label: "Business",
|
|
507
|
+
hint: "collects email, given & family name, company"
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const value = await select({
|
|
511
|
+
message: "Who will sign in to your app? Collected fields stay editable after setup.",
|
|
512
|
+
initialValue: answers.useCase,
|
|
513
|
+
options: SETUP_USE_CASES.map((useCase) => ({
|
|
514
|
+
value: useCase,
|
|
515
|
+
label: options[useCase].label,
|
|
516
|
+
hint: options[useCase].hint
|
|
517
|
+
}))
|
|
518
|
+
});
|
|
519
|
+
bail(value);
|
|
520
|
+
return {
|
|
521
|
+
...answers,
|
|
522
|
+
useCase: value
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
//#endregion
|
|
448
527
|
//#region src/commands/setup/prompts/pick-framework.ts
|
|
449
528
|
/**
|
|
450
529
|
* "Choose a framework to scaffold" — the only prompt outside the main wizard.
|
|
@@ -484,11 +563,23 @@ const SETUP_PROMPTS = [
|
|
|
484
563
|
new FrameworkConfirmPrompt(),
|
|
485
564
|
new ServerPrompt(),
|
|
486
565
|
new DevPortPrompt(),
|
|
487
|
-
new
|
|
566
|
+
new UseCasePrompt(),
|
|
567
|
+
new SignInPresetPrompt(),
|
|
568
|
+
new DesignPrompt()
|
|
488
569
|
];
|
|
489
570
|
//#endregion
|
|
490
571
|
//#region src/commands/setup/summary.ts
|
|
491
572
|
/**
|
|
573
|
+
* Renders a path under `cwd` with stable POSIX separators. Setup's summary
|
|
574
|
+
* matches generated artifacts against repository-style suffixes such as
|
|
575
|
+
* `.zitadel/schemas/default-human-user.json`; normalizing here keeps those
|
|
576
|
+
* matches working when Node returns Windows paths with backslashes.
|
|
577
|
+
*/
|
|
578
|
+
function relativeDisplayPath(cwd, absolute) {
|
|
579
|
+
const boundary = absolute[cwd.length];
|
|
580
|
+
return (absolute.startsWith(cwd) && (boundary === "/" || boundary === "\\") ? absolute.slice(cwd.length + 1) : absolute).replaceAll("\\", "/");
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
492
583
|
* Renders the section list as a single multi-line string. Labels are
|
|
493
584
|
* padded to a common width per section so the `✓ label value` columns
|
|
494
585
|
* line up, the title prints in dim gray, and `✓` is green. Values come
|
|
@@ -517,6 +608,8 @@ const path = (s) => pc.cyan(s);
|
|
|
517
608
|
const url = (s) => pc.cyan(s);
|
|
518
609
|
/** Yellow, for opaque ids the user shouldn't try to read. */
|
|
519
610
|
const id = (s) => pc.yellow(s);
|
|
611
|
+
/** Dim, for separators and secondary detail. */
|
|
612
|
+
const dim = (s) => pc.dim(s);
|
|
520
613
|
/**
|
|
521
614
|
* Reads the project root to identify the framework version, TS presence,
|
|
522
615
|
* and which package manager the user runs. Returns safe defaults (npm
|
|
@@ -577,6 +670,29 @@ function stripRange(range) {
|
|
|
577
670
|
function fileNameOf(p) {
|
|
578
671
|
return basename(p);
|
|
579
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Layout caveats for the chosen login design, shown to humans after the
|
|
675
|
+
* summary box and carried in the JSON envelope's `warnings`.
|
|
676
|
+
*
|
|
677
|
+
* The split family's brand pane is keyed to the **login's own container**
|
|
678
|
+
* width — a `@container (max-width: 48rem)` query on the widget's mount, not
|
|
679
|
+
* a viewport media query. Above that the pane renders; below it the pane is
|
|
680
|
+
* `display: none` and the compact brand mark takes its place — and the split
|
|
681
|
+
* template only emits that mark when `branding.json` names `logo_url` or
|
|
682
|
+
* `hero_url`, so without one the narrow layout loses the branding entirely.
|
|
683
|
+
*
|
|
684
|
+
* Independent of posture, because the container query is: a full-page login
|
|
685
|
+
* hits the same collapse on a phone that an embedded card hits in a sidebar.
|
|
686
|
+
* Gating on `widget` would have been a guess about the container, and a wrong
|
|
687
|
+
* one in both directions — the wrapper setup scaffolds for a widget is
|
|
688
|
+
* full-width (the pane renders there), and a page-posture login on a phone is
|
|
689
|
+
* exactly the narrow case the advice is for. `hero` stays quiet: its compact
|
|
690
|
+
* fallback is editable text, so a narrow container never leaves it blank.
|
|
691
|
+
*/
|
|
692
|
+
function designWarnings(design) {
|
|
693
|
+
if (design !== "split" && design !== "split-right") return [];
|
|
694
|
+
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.`];
|
|
695
|
+
}
|
|
580
696
|
//#endregion
|
|
581
697
|
//#region src/commands/setup/index.ts
|
|
582
698
|
/**
|
|
@@ -586,6 +702,16 @@ function fileNameOf(p) {
|
|
|
586
702
|
* load.
|
|
587
703
|
*/
|
|
588
704
|
const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) => framework.id);
|
|
705
|
+
/**
|
|
706
|
+
* `--renderer` offers only ids `getRenderer` will resolve: a
|
|
707
|
+
* declared-but-unpublished renderer (ADR 006) keeps its registry entry to
|
|
708
|
+
* reserve the id, but is surfaced as unavailable in the flag description
|
|
709
|
+
* instead of in `options`, so `--help` never advertises a value that is
|
|
710
|
+
* guaranteed to fail and an explicit pass is rejected at parse time — before
|
|
711
|
+
* any remote project is created.
|
|
712
|
+
*/
|
|
713
|
+
const UNAVAILABLE_RENDERER_IDS = RENDERER_IDS.filter((id) => !AVAILABLE_RENDERER_IDS.includes(id));
|
|
714
|
+
const RENDERER_FLAG_DESCRIPTION = UNAVAILABLE_RENDERER_IDS.length === 0 ? "Renderer (default: react)." : `Renderer (default: react). Not yet available: ${UNAVAILABLE_RENDERER_IDS.join(", ")}.`;
|
|
589
715
|
/** `zitadel setup` — create a project and scaffold local auth.
|
|
590
716
|
*
|
|
591
717
|
* Detects (or, for an empty directory, scaffolds then re-detects) the
|
|
@@ -607,14 +733,22 @@ var Setup = class Setup extends BaseCommand {
|
|
|
607
733
|
options: FRAMEWORK_OPTIONS
|
|
608
734
|
}),
|
|
609
735
|
renderer: Flags.string({
|
|
610
|
-
description:
|
|
611
|
-
options: [...
|
|
736
|
+
description: RENDERER_FLAG_DESCRIPTION,
|
|
737
|
+
options: [...AVAILABLE_RENDERER_IDS]
|
|
612
738
|
}),
|
|
613
739
|
"dev-port": Flags.integer({ description: "Dev-server port; also the issuer origin registered with Zitadel. Defaults to the detected port. Use distinct ports to run several scaffolded apps side by side." }),
|
|
614
740
|
"skip-install": Flags.boolean({ description: "Do not install dependencies after setup updates package.json." }),
|
|
615
741
|
preset: Flags.string({
|
|
616
742
|
description: "Sign-in preset for the scaffolded schema and login flow (default: password-first).",
|
|
617
743
|
options: [...SETUP_PRESETS]
|
|
744
|
+
}),
|
|
745
|
+
"use-case": Flags.string({
|
|
746
|
+
description: "Use case for the scaffolded schema fields: who signs in to the app (default: minimal).",
|
|
747
|
+
options: [...SETUP_USE_CASES]
|
|
748
|
+
}),
|
|
749
|
+
design: Flags.string({
|
|
750
|
+
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).",
|
|
751
|
+
options: [...BRANDING_DESIGNS]
|
|
618
752
|
})
|
|
619
753
|
};
|
|
620
754
|
async run() {
|
|
@@ -654,6 +788,8 @@ var Setup = class Setup extends BaseCommand {
|
|
|
654
788
|
skip_install: Boolean(flags["skip-install"]),
|
|
655
789
|
dev_port_explicit: flags["dev-port"] !== void 0,
|
|
656
790
|
preset: flags.preset ?? DEFAULT_SETUP_PRESET,
|
|
791
|
+
use_case: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
|
|
792
|
+
design: flags.design ?? "built-in",
|
|
657
793
|
step: "framework_resolved"
|
|
658
794
|
});
|
|
659
795
|
if (flags["dev-port"] !== void 0) {
|
|
@@ -668,7 +804,9 @@ var Setup = class Setup extends BaseCommand {
|
|
|
668
804
|
let answers = {
|
|
669
805
|
server: this.meta.source,
|
|
670
806
|
devPort: framework.devPort,
|
|
671
|
-
preset: flags.preset ?? DEFAULT_SETUP_PRESET
|
|
807
|
+
preset: flags.preset ?? DEFAULT_SETUP_PRESET,
|
|
808
|
+
useCase: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
|
|
809
|
+
design: flags.design
|
|
672
810
|
};
|
|
673
811
|
if (!nonInteractive && !dryRun) {
|
|
674
812
|
intro("Zitadel setup");
|
|
@@ -677,12 +815,18 @@ var Setup = class Setup extends BaseCommand {
|
|
|
677
815
|
cwd,
|
|
678
816
|
serverFlag: this.meta.serverFlag,
|
|
679
817
|
devPortFromFlag: flags["dev-port"] !== void 0,
|
|
680
|
-
presetFromFlag: flags.preset !== void 0
|
|
818
|
+
presetFromFlag: flags.preset !== void 0,
|
|
819
|
+
useCaseFromFlag: flags["use-case"] !== void 0,
|
|
820
|
+
designFromFlag: flags.design !== void 0
|
|
681
821
|
};
|
|
682
822
|
for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
|
|
683
823
|
outro("Configuration captured");
|
|
684
824
|
}
|
|
685
|
-
this.recordTelemetry({
|
|
825
|
+
this.recordTelemetry({
|
|
826
|
+
preset: answers.preset,
|
|
827
|
+
use_case: answers.useCase,
|
|
828
|
+
design: answers.design ?? "built-in"
|
|
829
|
+
});
|
|
686
830
|
const issuer = issuerFromPort(answers.devPort);
|
|
687
831
|
framework = {
|
|
688
832
|
...framework,
|
|
@@ -696,10 +840,13 @@ var Setup = class Setup extends BaseCommand {
|
|
|
696
840
|
...retryOptionsFromFlags(flags),
|
|
697
841
|
framework: framework.id,
|
|
698
842
|
preset: answers.preset,
|
|
843
|
+
useCase: answers.useCase,
|
|
844
|
+
design: answers.design,
|
|
699
845
|
devPort: answers.devPort
|
|
700
846
|
});
|
|
701
847
|
consola$1.success(`Created project ${project.id}`);
|
|
702
848
|
this.recordTelemetry({ step: "project_created" });
|
|
849
|
+
const posture = derivePosture(framework.id, scaffoldedFramework);
|
|
703
850
|
const ctx = {
|
|
704
851
|
framework,
|
|
705
852
|
rendererId: flags.renderer ?? "react",
|
|
@@ -708,7 +855,9 @@ var Setup = class Setup extends BaseCommand {
|
|
|
708
855
|
server: answers.server,
|
|
709
856
|
cliVersion: this.meta.cliVersion,
|
|
710
857
|
scaffoldedFramework,
|
|
711
|
-
|
|
858
|
+
posture,
|
|
859
|
+
preset: answers.preset,
|
|
860
|
+
useCase: answers.useCase
|
|
712
861
|
};
|
|
713
862
|
consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
|
|
714
863
|
const result = await orca.patcherFor(framework.id).patch(ctx, {
|
|
@@ -717,21 +866,24 @@ var Setup = class Setup extends BaseCommand {
|
|
|
717
866
|
force
|
|
718
867
|
});
|
|
719
868
|
for (const file of result.filesWritten) {
|
|
720
|
-
const sentence = describeWrittenFile(
|
|
869
|
+
const sentence = describeWrittenFile(relativeDisplayPath(cwd, file), dryRun);
|
|
721
870
|
if (sentence) consola$1.info(sentence);
|
|
722
871
|
}
|
|
723
|
-
for (const file of result.filesSkipped) consola$1.info(`Left ${
|
|
872
|
+
for (const file of result.filesSkipped) consola$1.info(`Left ${relativeDisplayPath(cwd, file)} unchanged (already matches target)`);
|
|
724
873
|
let resourceResult;
|
|
725
874
|
try {
|
|
726
875
|
resourceResult = dryRun ? { filesWritten: [] } : await materializeSetupResources({
|
|
727
876
|
cwd,
|
|
728
877
|
client: createZitadelClient({
|
|
729
878
|
baseUrl: answers.server,
|
|
730
|
-
token: project.
|
|
879
|
+
token: project.project_secret
|
|
731
880
|
}),
|
|
732
881
|
projectId: project.id,
|
|
733
882
|
force,
|
|
734
|
-
preset: answers.preset
|
|
883
|
+
preset: answers.preset,
|
|
884
|
+
useCase: answers.useCase,
|
|
885
|
+
design: answers.design,
|
|
886
|
+
cliVersion: this.meta.cliVersion
|
|
735
887
|
});
|
|
736
888
|
} catch (error) {
|
|
737
889
|
await rm(join(cwd, "zitadel.json"), { force: true });
|
|
@@ -744,7 +896,7 @@ var Setup = class Setup extends BaseCommand {
|
|
|
744
896
|
});
|
|
745
897
|
}
|
|
746
898
|
for (const file of resourceResult.filesWritten) {
|
|
747
|
-
const sentence = describeWrittenFile(
|
|
899
|
+
const sentence = describeWrittenFile(relativeDisplayPath(cwd, file), dryRun);
|
|
748
900
|
if (sentence) consola$1.info(sentence);
|
|
749
901
|
}
|
|
750
902
|
const allFilesWritten = [...result.filesWritten, ...resourceResult.filesWritten];
|
|
@@ -753,6 +905,21 @@ var Setup = class Setup extends BaseCommand {
|
|
|
753
905
|
step: "files_patched",
|
|
754
906
|
files_written_count: allFilesWritten.length
|
|
755
907
|
});
|
|
908
|
+
if (!dryRun) try {
|
|
909
|
+
await writeScaffoldManifest({
|
|
910
|
+
cwd,
|
|
911
|
+
actions: orca.patcherFor(framework.id).artifacts({
|
|
912
|
+
framework,
|
|
913
|
+
rendererId: ctx.rendererId
|
|
914
|
+
}),
|
|
915
|
+
written: [...result.filesWritten, ...result.filesSkipped],
|
|
916
|
+
scaffoldedFramework,
|
|
917
|
+
devPort: answers.devPort,
|
|
918
|
+
posture
|
|
919
|
+
});
|
|
920
|
+
} catch (error) {
|
|
921
|
+
consola$1.debug("Failed to record the scaffold manifest", error);
|
|
922
|
+
}
|
|
756
923
|
const installOutcome = await installDependenciesForSetup({
|
|
757
924
|
cliVersion: this.meta.cliVersion,
|
|
758
925
|
cwd,
|
|
@@ -768,7 +935,18 @@ var Setup = class Setup extends BaseCommand {
|
|
|
768
935
|
step: "dependencies_installed",
|
|
769
936
|
package_manager: installOutcome.install.package_manager
|
|
770
937
|
});
|
|
771
|
-
const writtenRel = allFilesWritten.map((file) =>
|
|
938
|
+
const writtenRel = allFilesWritten.map((file) => relativeDisplayPath(cwd, file));
|
|
939
|
+
const claimNudge = claimState({
|
|
940
|
+
secret: {},
|
|
941
|
+
server: answers.server
|
|
942
|
+
}).kind === "detached" ? {
|
|
943
|
+
actions: [claimAction(this.meta.cliVersion)],
|
|
944
|
+
commands: [claimCommand(this.meta.cliVersion)]
|
|
945
|
+
} : {
|
|
946
|
+
actions: [],
|
|
947
|
+
commands: []
|
|
948
|
+
};
|
|
949
|
+
const warnings = designWarnings(answers.design);
|
|
772
950
|
if (!this.jsonEnabled()) {
|
|
773
951
|
const sections = buildSummary({
|
|
774
952
|
projectFacts: await detectProjectFacts(cwd, framework.id),
|
|
@@ -776,14 +954,15 @@ var Setup = class Setup extends BaseCommand {
|
|
|
776
954
|
project,
|
|
777
955
|
server: answers.server,
|
|
778
956
|
issuer,
|
|
779
|
-
scaffoldedFramework
|
|
957
|
+
scaffoldedFramework,
|
|
958
|
+
design: answers.design
|
|
780
959
|
});
|
|
781
960
|
consola$1.box({
|
|
782
961
|
title: "Zitadel is ready",
|
|
783
962
|
message: [
|
|
784
963
|
renderSummary(sections),
|
|
785
964
|
"",
|
|
786
|
-
installOutcome.boxActions.join("\n")
|
|
965
|
+
[...installOutcome.boxActions, ...claimNudge.actions].join("\n")
|
|
787
966
|
].join("\n"),
|
|
788
967
|
style: {
|
|
789
968
|
padding: 1,
|
|
@@ -791,9 +970,11 @@ var Setup = class Setup extends BaseCommand {
|
|
|
791
970
|
borderColor: "green"
|
|
792
971
|
}
|
|
793
972
|
});
|
|
973
|
+
for (const warning of warnings) consola$1.warn(warning);
|
|
794
974
|
}
|
|
795
975
|
return this.emit({
|
|
796
976
|
status: "ok",
|
|
977
|
+
warnings,
|
|
797
978
|
pretty: "",
|
|
798
979
|
data: {
|
|
799
980
|
title: "Zitadel is ready.",
|
|
@@ -803,11 +984,21 @@ var Setup = class Setup extends BaseCommand {
|
|
|
803
984
|
},
|
|
804
985
|
framework: framework.id,
|
|
805
986
|
server: answers.server,
|
|
806
|
-
files_written: allFilesWritten.map((file) =>
|
|
807
|
-
|
|
987
|
+
files_written: allFilesWritten.map((file) => relativeDisplayPath(cwd, file)),
|
|
988
|
+
files: result.files.map((file) => ({
|
|
989
|
+
path: relativeDisplayPath(cwd, file.path),
|
|
990
|
+
kind: file.kind,
|
|
991
|
+
action: file.action
|
|
992
|
+
})),
|
|
993
|
+
files_skipped: result.filesSkipped.map((file) => relativeDisplayPath(cwd, file)),
|
|
808
994
|
install: installOutcome.install,
|
|
809
|
-
|
|
810
|
-
|
|
995
|
+
design: answers.design ?? null,
|
|
996
|
+
next_actions: [
|
|
997
|
+
...installOutcome.nextActions,
|
|
998
|
+
brandingGuidanceAction(answers.design, this.meta.cliVersion),
|
|
999
|
+
...claimNudge.actions
|
|
1000
|
+
],
|
|
1001
|
+
next_commands: [...installOutcome.nextCommands, ...claimNudge.commands]
|
|
811
1002
|
}
|
|
812
1003
|
});
|
|
813
1004
|
}
|
|
@@ -836,10 +1027,10 @@ async function resolveScaffoldFramework(framework, nonInteractive, orca) {
|
|
|
836
1027
|
function dryRunProject(issuer) {
|
|
837
1028
|
return {
|
|
838
1029
|
id: "dry-run-0000",
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1030
|
+
project_secret: "sk_proj_dry_run_full",
|
|
1031
|
+
preview_secret: "sk_proj_dry_run_preview",
|
|
1032
|
+
preview_origins: [issuer],
|
|
1033
|
+
created_at: "2026-04-21T14:03:11.000Z"
|
|
843
1034
|
};
|
|
844
1035
|
}
|
|
845
1036
|
/**
|
|
@@ -851,6 +1042,8 @@ function setupRetryFlags(opts) {
|
|
|
851
1042
|
const parts = [];
|
|
852
1043
|
if (opts.framework) parts.push(`--framework ${opts.framework}`);
|
|
853
1044
|
if (opts.preset && opts.preset !== DEFAULT_SETUP_PRESET) parts.push(`--preset ${opts.preset}`);
|
|
1045
|
+
if (opts.useCase && opts.useCase !== DEFAULT_SETUP_USE_CASE) parts.push(`--use-case ${opts.useCase}`);
|
|
1046
|
+
if (opts.design) parts.push(`--design ${opts.design}`);
|
|
854
1047
|
if (opts.renderer && opts.renderer !== "react") parts.push(`--renderer ${opts.renderer}`);
|
|
855
1048
|
if (opts.devPort !== void 0) parts.push(`--dev-port ${opts.devPort}`);
|
|
856
1049
|
if (opts.nonInteractive) parts.push("--non-interactive");
|
|
@@ -866,6 +1059,8 @@ function retryOptionsFromFlags(flags) {
|
|
|
866
1059
|
return {
|
|
867
1060
|
framework: flags.framework,
|
|
868
1061
|
preset: flags.preset,
|
|
1062
|
+
useCase: flags["use-case"],
|
|
1063
|
+
design: flags.design,
|
|
869
1064
|
renderer: flags.renderer,
|
|
870
1065
|
devPort: flags["dev-port"],
|
|
871
1066
|
nonInteractive: Boolean(flags["non-interactive"])
|
|
@@ -875,8 +1070,8 @@ async function createProjectWithLocalHint(client, server, cliVersion, projectNam
|
|
|
875
1070
|
try {
|
|
876
1071
|
const payload = {
|
|
877
1072
|
name: projectName,
|
|
878
|
-
|
|
879
|
-
|
|
1073
|
+
preview_origins: [issuer],
|
|
1074
|
+
seed_defaults: false
|
|
880
1075
|
};
|
|
881
1076
|
return await client.createProject(payload);
|
|
882
1077
|
} catch (error) {
|
|
@@ -906,10 +1101,6 @@ function localSetupHint(error, retry, cliVersion) {
|
|
|
906
1101
|
details: normalized.details
|
|
907
1102
|
});
|
|
908
1103
|
}
|
|
909
|
-
/** Renders an absolute path relative to `cwd` for human-readable output. */
|
|
910
|
-
function relativeDisplay(cwd, path) {
|
|
911
|
-
return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;
|
|
912
|
-
}
|
|
913
1104
|
/**
|
|
914
1105
|
* Replaces the user's `$HOME` with `~` in a path for compact terminal output.
|
|
915
1106
|
* Falls back to the raw path when `HOME` isn't set or doesn't match.
|
|
@@ -930,15 +1121,13 @@ function pickWrittenFile(written, suffix) {
|
|
|
930
1121
|
}
|
|
931
1122
|
/**
|
|
932
1123
|
* Translates a patcher-written path into a single sentence the user can
|
|
933
|
-
* read at narration speed.
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
* something happened.
|
|
1124
|
+
* read at narration speed. The patch result's `filesWritten` carries
|
|
1125
|
+
* deduplicated file paths only (directories stay in the typed `files`
|
|
1126
|
+
* rows), so no artefact filtering is needed here. The verb tense flips
|
|
1127
|
+
* for `--dry-run` so the user sees a preview ("Would write ...") instead
|
|
1128
|
+
* of a claim that something happened.
|
|
939
1129
|
*/
|
|
940
1130
|
function describeWrittenFile(relPath, dryRun) {
|
|
941
|
-
if (relPath === ".zitadel" || relPath === ".zitadel/flows" || relPath === ".zitadel/schemas" || relPath === ".zitadel/meta") return null;
|
|
942
1131
|
const verb = dryRun ? "Would write" : "Wrote";
|
|
943
1132
|
const sentence = SENTENCE_BY_PATH[relPath];
|
|
944
1133
|
if (sentence) return `${verb} ${sentence.subject} (${path(relPath)})`;
|
|
@@ -964,6 +1153,10 @@ const SENTENCE_BY_PATH = {
|
|
|
964
1153
|
".zitadel/meta/flow-definition.json": { subject: "the flow dialect spec (editor $schema)" },
|
|
965
1154
|
".zitadel/meta/user-schema.json": { subject: "the user-schema dialect spec" },
|
|
966
1155
|
".zitadel/meta/user-property.json": { subject: "the user-property dialect spec" },
|
|
1156
|
+
".zitadel/meta/branding.json": { subject: "the branding dialect spec" },
|
|
1157
|
+
".zitadel/branding/branding.json": { subject: "the branding descriptor (layout + asset URLs)" },
|
|
1158
|
+
".zitadel/branding/login.liquid": { subject: "the editable login template" },
|
|
1159
|
+
".zitadel/branding/README.md": { subject: "the branding folder README" },
|
|
967
1160
|
"AGENTS.md": { subject: "the agent guidance (golden journey + config dialect)" },
|
|
968
1161
|
"README.md": { subject: "the README's Zitadel section" },
|
|
969
1162
|
"app/page.tsx": { subject: "the home page redirect" },
|
|
@@ -977,7 +1170,7 @@ const SENTENCE_BY_PATH = {
|
|
|
977
1170
|
};
|
|
978
1171
|
/** Builds the section list driving {@link renderSummary} for the setup command. */
|
|
979
1172
|
function buildSummary(opts) {
|
|
980
|
-
const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework } = opts;
|
|
1173
|
+
const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework, design } = opts;
|
|
981
1174
|
const sdkPackage = "@zitadel/sdk-next";
|
|
982
1175
|
const packageJsonHit = pickWrittenFile(writtenRel, "package.json");
|
|
983
1176
|
const detected = [{
|
|
@@ -1009,6 +1202,28 @@ function buildSummary(opts) {
|
|
|
1009
1202
|
value: path(hit)
|
|
1010
1203
|
});
|
|
1011
1204
|
}
|
|
1205
|
+
const customizeRows = [];
|
|
1206
|
+
for (const [label, suffix, dir] of [
|
|
1207
|
+
[
|
|
1208
|
+
"User schema",
|
|
1209
|
+
".zitadel/schemas/default-human-user.json",
|
|
1210
|
+
".zitadel/schemas/"
|
|
1211
|
+
],
|
|
1212
|
+
[
|
|
1213
|
+
"Login flow",
|
|
1214
|
+
".zitadel/flows/default-login.json",
|
|
1215
|
+
".zitadel/flows/"
|
|
1216
|
+
],
|
|
1217
|
+
[
|
|
1218
|
+
"Login template",
|
|
1219
|
+
".zitadel/branding/login.liquid",
|
|
1220
|
+
".zitadel/branding/"
|
|
1221
|
+
]
|
|
1222
|
+
]) if (pickWrittenFile(writtenRel, suffix)) customizeRows.push({
|
|
1223
|
+
label,
|
|
1224
|
+
value: path(dir),
|
|
1225
|
+
secondary: "see its README.md"
|
|
1226
|
+
});
|
|
1012
1227
|
const projectRows = [
|
|
1013
1228
|
{
|
|
1014
1229
|
label: "Project id",
|
|
@@ -1021,6 +1236,14 @@ function buildSummary(opts) {
|
|
|
1021
1236
|
{
|
|
1022
1237
|
label: "App will run",
|
|
1023
1238
|
value: url(issuer)
|
|
1239
|
+
},
|
|
1240
|
+
design ? {
|
|
1241
|
+
label: "Login design",
|
|
1242
|
+
value: brandingDesignLabel(design),
|
|
1243
|
+
secondary: `${design} · ${path(".zitadel/branding/")}`
|
|
1244
|
+
} : {
|
|
1245
|
+
label: "Login design",
|
|
1246
|
+
value: dim("built-in template")
|
|
1024
1247
|
}
|
|
1025
1248
|
];
|
|
1026
1249
|
return [
|
|
@@ -1032,6 +1255,10 @@ function buildSummary(opts) {
|
|
|
1032
1255
|
title: "Installed",
|
|
1033
1256
|
rows: installedRows
|
|
1034
1257
|
},
|
|
1258
|
+
{
|
|
1259
|
+
title: "Customize",
|
|
1260
|
+
rows: customizeRows
|
|
1261
|
+
},
|
|
1035
1262
|
{
|
|
1036
1263
|
title: "Project",
|
|
1037
1264
|
rows: projectRows
|