@zitadel/cli 0.1.0-alpha.17 → 0.1.0-alpha.18
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 +145 -107
- package/SKILLS.md +88 -12
- package/dist/branding-BXj028-W.mjs +79 -0
- package/dist/branding-BXj028-W.mjs.map +1 -0
- package/dist/claim-state-BTGInAaN.mjs +66 -0
- package/dist/claim-state-BTGInAaN.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 +125 -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 +477 -37
- package/dist/commands/doctor.mjs.map +1 -1
- package/dist/commands/eject.mjs +3 -3
- 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 +8 -8
- package/dist/commands/schemas/list.mjs.map +1 -1
- package/dist/commands/setup.mjs +305 -36
- 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/{docker-pGcTrDIY.mjs → docker-Ciz9N6uI.mjs} +2 -2
- package/dist/{docker-pGcTrDIY.mjs.map → docker-Ciz9N6uI.mjs.map} +1 -1
- package/dist/{environment-BQF7LeCz.mjs → environment-CuHKxtIO.mjs} +1 -1
- package/dist/{environment-BQF7LeCz.mjs.map → environment-CuHKxtIO.mjs.map} +1 -1
- package/dist/{journey-guidance-CYVpQszz.mjs → journey-guidance-CtQUfWar.mjs} +14 -3
- package/dist/{journey-guidance-CYVpQszz.mjs.map → journey-guidance-CtQUfWar.mjs.map} +1 -1
- package/dist/{oclif-BRdjVNne.mjs → oclif-CL6-M13m.mjs} +201 -43
- package/dist/oclif-CL6-M13m.mjs.map +1 -0
- package/dist/{orca-Dip6DPJ2.mjs → orca-BWB4zOdw.mjs} +749 -192
- package/dist/orca-BWB4zOdw.mjs.map +1 -0
- package/dist/{ports-V8JfiNgT.mjs → ports-Cu2Y7aXQ.mjs} +2 -2
- package/dist/{ports-V8JfiNgT.mjs.map → ports-Cu2Y7aXQ.mjs.map} +1 -1
- package/dist/{processes-BVqYsxT8.mjs → processes-Bn2WngOi.mjs} +1 -1
- package/dist/{processes-BVqYsxT8.mjs.map → processes-Bn2WngOi.mjs.map} +1 -1
- package/dist/{project-BGgv7Bba.mjs → project-yejJSfdJ.mjs} +57 -4
- package/dist/project-yejJSfdJ.mjs.map +1 -0
- package/dist/scaffold-manifest-CKNFLX9T.mjs +81 -0
- package/dist/scaffold-manifest-CKNFLX9T.mjs.map +1 -0
- package/dist/{sync-Cd3z7Q1R.mjs → sync-Cx2_4rXW.mjs} +99 -50
- package/dist/sync-Cx2_4rXW.mjs.map +1 -0
- package/dist/user-schema-W1OsXdpc.mjs +91 -0
- package/dist/user-schema-W1OsXdpc.mjs.map +1 -0
- package/oclif.manifest.json +227 -5
- package/package.json +8 -6
- 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 { D as
|
|
2
|
-
import { n as
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
1
|
+
import { D as toZitadelError, E as ZitadelError, T as stableStringify, k as publicCliCommand, n as DEFAULT_SERVER, t as BaseCommand, u as detectHealthyLocalServer } from "../oclif-CL6-M13m.mjs";
|
|
2
|
+
import { n as claimCommand, r as claimState, t as claimAction } from "../claim-state-BTGInAaN.mjs";
|
|
3
|
+
import { n as customizeAndPublishActions, r as verifyLoginAction, t as brandingGuidanceAction } from "../journey-guidance-CtQUfWar.mjs";
|
|
4
|
+
import { i as RENDERER_IDS, n as inspectScaffoldTarget, r as AVAILABLE_RENDERER_IDS, s as issuerFromPort, t as createOrca } from "../orca-BWB4zOdw.mjs";
|
|
5
|
+
import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-yejJSfdJ.mjs";
|
|
6
|
+
import { a as updateState, t as SCHEMAS_DIR } from "../user-schema-W1OsXdpc.mjs";
|
|
7
|
+
import { r as writeScaffoldManifest } from "../scaffold-manifest-CKNFLX9T.mjs";
|
|
8
|
+
import { i as toBrandingWireBody, t as BRANDING_DIR } from "../branding-BXj028-W.mjs";
|
|
9
|
+
import { c as writeBackResource, o as hashForState, u as FLOWS_DIR } from "../sync-Cx2_4rXW.mjs";
|
|
7
10
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
8
11
|
import { basename, dirname, join } from "node:path";
|
|
9
12
|
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
10
13
|
import { Flags } from "@oclif/core";
|
|
11
14
|
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";
|
|
15
|
+
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
16
|
import { consola as consola$1 } from "consola";
|
|
17
|
+
import { BRANDING_FILE_SCHEMA_REF } from "@zitadel/config/meta-schemas";
|
|
14
18
|
import { spawn } from "node:child_process";
|
|
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,7 +63,11 @@ 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);
|
|
@@ -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,6 +103,24 @@ 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, brandingReadmeContent())) 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
126
|
if (await writeReadmeFile(opts.cwd, schemasReadme, schemasReadmeContent())) filesWritten.push(join(opts.cwd, schemasReadme));
|
|
@@ -73,6 +128,22 @@ async function materializeSetupResources(opts) {
|
|
|
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.
|
|
@@ -294,6 +365,77 @@ function bail(value) {
|
|
|
294
365
|
}
|
|
295
366
|
}
|
|
296
367
|
//#endregion
|
|
368
|
+
//#region src/commands/setup/prompts/design.ts
|
|
369
|
+
/**
|
|
370
|
+
* Sentinel select value for "keep the built-in login" — clack option values
|
|
371
|
+
* must be strings, and `undefined` in {@link SetupAnswers.design} is the
|
|
372
|
+
* real representation of that choice.
|
|
373
|
+
*/
|
|
374
|
+
const BUILT_IN = "built-in";
|
|
375
|
+
/**
|
|
376
|
+
* "How should the login look?" — the last wizard question (#676). Asks after
|
|
377
|
+
* the sign-in preset so the order tells a story: the use case owns what is
|
|
378
|
+
* collected, the preset owns how users authenticate, the design owns how
|
|
379
|
+
* that experience looks.
|
|
380
|
+
*
|
|
381
|
+
* The built-in default is preselected and writes nothing. Picking a starter
|
|
382
|
+
* design forks its Liquid template into `.zitadel/branding/` and publishes
|
|
383
|
+
* it as branding revision 1 — from then on the template is repo-owned and
|
|
384
|
+
* stops tracking built-in improvements, so the option labels make the
|
|
385
|
+
* file-write explicit and keep ownership an opt-in. `--design` is
|
|
386
|
+
* authoritative; non-interactive runs keep the built-in template.
|
|
387
|
+
*/
|
|
388
|
+
var DesignPrompt = class {
|
|
389
|
+
async ask(answers, ctx) {
|
|
390
|
+
if (ctx.designFromFlag) return answers;
|
|
391
|
+
const value = await select({
|
|
392
|
+
message: "How should the login look? A starter design adds its editable template to .zitadel/branding/.",
|
|
393
|
+
initialValue: answers.design ?? BUILT_IN,
|
|
394
|
+
options: [
|
|
395
|
+
{
|
|
396
|
+
value: BUILT_IN,
|
|
397
|
+
label: "Built-in",
|
|
398
|
+
hint: "no files added; pick a design anytime later with `branding eject`"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
value: "centered",
|
|
402
|
+
label: "Centered",
|
|
403
|
+
hint: "card centred on the page"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
value: "split",
|
|
407
|
+
label: "Split",
|
|
408
|
+
hint: "brand panel left, form right"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
value: "split-right",
|
|
412
|
+
label: "Split (reversed)",
|
|
413
|
+
hint: "form left, brand panel right"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
value: "hero",
|
|
417
|
+
label: "Hero",
|
|
418
|
+
hint: "landing-style brand pane left, form right"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
value: "minimal",
|
|
422
|
+
label: "Minimal",
|
|
423
|
+
hint: "no card chrome, fields straight on the page"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
});
|
|
427
|
+
bail(value);
|
|
428
|
+
if (value === BUILT_IN) return {
|
|
429
|
+
...answers,
|
|
430
|
+
design: void 0
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
...answers,
|
|
434
|
+
design: value
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
//#endregion
|
|
297
439
|
//#region src/commands/setup/prompts/dev-port.ts
|
|
298
440
|
/**
|
|
299
441
|
* "Dev server port" — defaults to the detected port. The validated answer
|
|
@@ -445,6 +587,48 @@ var SignInPresetPrompt = class {
|
|
|
445
587
|
}
|
|
446
588
|
};
|
|
447
589
|
//#endregion
|
|
590
|
+
//#region src/commands/setup/prompts/use-case.ts
|
|
591
|
+
/**
|
|
592
|
+
* "Who will sign in to your app?" — picks the use case, which owns the
|
|
593
|
+
* schema field set the scaffold collects (#448). Orthogonal to the sign-in
|
|
594
|
+
* preset, so this asks first: what a user *is* before *how* they sign in.
|
|
595
|
+
* `--use-case` is authoritative; non-interactive runs keep the minimal
|
|
596
|
+
* default seeded by the command.
|
|
597
|
+
*/
|
|
598
|
+
var UseCasePrompt = class {
|
|
599
|
+
async ask(answers, ctx) {
|
|
600
|
+
if (ctx.useCaseFromFlag) return answers;
|
|
601
|
+
const options = {
|
|
602
|
+
minimal: {
|
|
603
|
+
label: "Just me or a small group",
|
|
604
|
+
hint: "collects email"
|
|
605
|
+
},
|
|
606
|
+
consumer: {
|
|
607
|
+
label: "Consumers",
|
|
608
|
+
hint: "collects email, given & family name"
|
|
609
|
+
},
|
|
610
|
+
business: {
|
|
611
|
+
label: "Business",
|
|
612
|
+
hint: "collects email, given & family name, company"
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
const value = await select({
|
|
616
|
+
message: "Who will sign in to your app? Collected fields stay editable after setup.",
|
|
617
|
+
initialValue: answers.useCase,
|
|
618
|
+
options: SETUP_USE_CASES.map((useCase) => ({
|
|
619
|
+
value: useCase,
|
|
620
|
+
label: options[useCase].label,
|
|
621
|
+
hint: options[useCase].hint
|
|
622
|
+
}))
|
|
623
|
+
});
|
|
624
|
+
bail(value);
|
|
625
|
+
return {
|
|
626
|
+
...answers,
|
|
627
|
+
useCase: value
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
//#endregion
|
|
448
632
|
//#region src/commands/setup/prompts/pick-framework.ts
|
|
449
633
|
/**
|
|
450
634
|
* "Choose a framework to scaffold" — the only prompt outside the main wizard.
|
|
@@ -484,7 +668,9 @@ const SETUP_PROMPTS = [
|
|
|
484
668
|
new FrameworkConfirmPrompt(),
|
|
485
669
|
new ServerPrompt(),
|
|
486
670
|
new DevPortPrompt(),
|
|
487
|
-
new
|
|
671
|
+
new UseCasePrompt(),
|
|
672
|
+
new SignInPresetPrompt(),
|
|
673
|
+
new DesignPrompt()
|
|
488
674
|
];
|
|
489
675
|
//#endregion
|
|
490
676
|
//#region src/commands/setup/summary.ts
|
|
@@ -517,6 +703,8 @@ const path = (s) => pc.cyan(s);
|
|
|
517
703
|
const url = (s) => pc.cyan(s);
|
|
518
704
|
/** Yellow, for opaque ids the user shouldn't try to read. */
|
|
519
705
|
const id = (s) => pc.yellow(s);
|
|
706
|
+
/** Dim, for separators and secondary detail. */
|
|
707
|
+
const dim = (s) => pc.dim(s);
|
|
520
708
|
/**
|
|
521
709
|
* Reads the project root to identify the framework version, TS presence,
|
|
522
710
|
* and which package manager the user runs. Returns safe defaults (npm
|
|
@@ -586,6 +774,16 @@ function fileNameOf(p) {
|
|
|
586
774
|
* load.
|
|
587
775
|
*/
|
|
588
776
|
const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) => framework.id);
|
|
777
|
+
/**
|
|
778
|
+
* `--renderer` offers only ids `getRenderer` will resolve: a
|
|
779
|
+
* declared-but-unpublished renderer (ADR 006) keeps its registry entry to
|
|
780
|
+
* reserve the id, but is surfaced as unavailable in the flag description
|
|
781
|
+
* instead of in `options`, so `--help` never advertises a value that is
|
|
782
|
+
* guaranteed to fail and an explicit pass is rejected at parse time — before
|
|
783
|
+
* any remote project is created.
|
|
784
|
+
*/
|
|
785
|
+
const UNAVAILABLE_RENDERER_IDS = RENDERER_IDS.filter((id) => !AVAILABLE_RENDERER_IDS.includes(id));
|
|
786
|
+
const RENDERER_FLAG_DESCRIPTION = UNAVAILABLE_RENDERER_IDS.length === 0 ? "Renderer (default: react)." : `Renderer (default: react). Not yet available: ${UNAVAILABLE_RENDERER_IDS.join(", ")}.`;
|
|
589
787
|
/** `zitadel setup` — create a project and scaffold local auth.
|
|
590
788
|
*
|
|
591
789
|
* Detects (or, for an empty directory, scaffolds then re-detects) the
|
|
@@ -607,14 +805,22 @@ var Setup = class Setup extends BaseCommand {
|
|
|
607
805
|
options: FRAMEWORK_OPTIONS
|
|
608
806
|
}),
|
|
609
807
|
renderer: Flags.string({
|
|
610
|
-
description:
|
|
611
|
-
options: [...
|
|
808
|
+
description: RENDERER_FLAG_DESCRIPTION,
|
|
809
|
+
options: [...AVAILABLE_RENDERER_IDS]
|
|
612
810
|
}),
|
|
613
811
|
"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
812
|
"skip-install": Flags.boolean({ description: "Do not install dependencies after setup updates package.json." }),
|
|
615
813
|
preset: Flags.string({
|
|
616
814
|
description: "Sign-in preset for the scaffolded schema and login flow (default: password-first).",
|
|
617
815
|
options: [...SETUP_PRESETS]
|
|
816
|
+
}),
|
|
817
|
+
"use-case": Flags.string({
|
|
818
|
+
description: "Use case for the scaffolded schema fields: who signs in to the app (default: minimal).",
|
|
819
|
+
options: [...SETUP_USE_CASES]
|
|
820
|
+
}),
|
|
821
|
+
design: Flags.string({
|
|
822
|
+
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.",
|
|
823
|
+
options: [...BRANDING_DESIGNS]
|
|
618
824
|
})
|
|
619
825
|
};
|
|
620
826
|
async run() {
|
|
@@ -654,6 +860,8 @@ var Setup = class Setup extends BaseCommand {
|
|
|
654
860
|
skip_install: Boolean(flags["skip-install"]),
|
|
655
861
|
dev_port_explicit: flags["dev-port"] !== void 0,
|
|
656
862
|
preset: flags.preset ?? DEFAULT_SETUP_PRESET,
|
|
863
|
+
use_case: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
|
|
864
|
+
design: flags.design ?? "built-in",
|
|
657
865
|
step: "framework_resolved"
|
|
658
866
|
});
|
|
659
867
|
if (flags["dev-port"] !== void 0) {
|
|
@@ -668,7 +876,9 @@ var Setup = class Setup extends BaseCommand {
|
|
|
668
876
|
let answers = {
|
|
669
877
|
server: this.meta.source,
|
|
670
878
|
devPort: framework.devPort,
|
|
671
|
-
preset: flags.preset ?? DEFAULT_SETUP_PRESET
|
|
879
|
+
preset: flags.preset ?? DEFAULT_SETUP_PRESET,
|
|
880
|
+
useCase: flags["use-case"] ?? DEFAULT_SETUP_USE_CASE,
|
|
881
|
+
design: flags.design
|
|
672
882
|
};
|
|
673
883
|
if (!nonInteractive && !dryRun) {
|
|
674
884
|
intro("Zitadel setup");
|
|
@@ -677,12 +887,18 @@ var Setup = class Setup extends BaseCommand {
|
|
|
677
887
|
cwd,
|
|
678
888
|
serverFlag: this.meta.serverFlag,
|
|
679
889
|
devPortFromFlag: flags["dev-port"] !== void 0,
|
|
680
|
-
presetFromFlag: flags.preset !== void 0
|
|
890
|
+
presetFromFlag: flags.preset !== void 0,
|
|
891
|
+
useCaseFromFlag: flags["use-case"] !== void 0,
|
|
892
|
+
designFromFlag: flags.design !== void 0
|
|
681
893
|
};
|
|
682
894
|
for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
|
|
683
895
|
outro("Configuration captured");
|
|
684
896
|
}
|
|
685
|
-
this.recordTelemetry({
|
|
897
|
+
this.recordTelemetry({
|
|
898
|
+
preset: answers.preset,
|
|
899
|
+
use_case: answers.useCase,
|
|
900
|
+
design: answers.design ?? "built-in"
|
|
901
|
+
});
|
|
686
902
|
const issuer = issuerFromPort(answers.devPort);
|
|
687
903
|
framework = {
|
|
688
904
|
...framework,
|
|
@@ -696,10 +912,13 @@ var Setup = class Setup extends BaseCommand {
|
|
|
696
912
|
...retryOptionsFromFlags(flags),
|
|
697
913
|
framework: framework.id,
|
|
698
914
|
preset: answers.preset,
|
|
915
|
+
useCase: answers.useCase,
|
|
916
|
+
design: answers.design,
|
|
699
917
|
devPort: answers.devPort
|
|
700
918
|
});
|
|
701
919
|
consola$1.success(`Created project ${project.id}`);
|
|
702
920
|
this.recordTelemetry({ step: "project_created" });
|
|
921
|
+
const posture = derivePosture(framework.id, scaffoldedFramework);
|
|
703
922
|
const ctx = {
|
|
704
923
|
framework,
|
|
705
924
|
rendererId: flags.renderer ?? "react",
|
|
@@ -708,7 +927,9 @@ var Setup = class Setup extends BaseCommand {
|
|
|
708
927
|
server: answers.server,
|
|
709
928
|
cliVersion: this.meta.cliVersion,
|
|
710
929
|
scaffoldedFramework,
|
|
711
|
-
|
|
930
|
+
posture,
|
|
931
|
+
preset: answers.preset,
|
|
932
|
+
useCase: answers.useCase
|
|
712
933
|
};
|
|
713
934
|
consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
|
|
714
935
|
const result = await orca.patcherFor(framework.id).patch(ctx, {
|
|
@@ -727,11 +948,13 @@ var Setup = class Setup extends BaseCommand {
|
|
|
727
948
|
cwd,
|
|
728
949
|
client: createZitadelClient({
|
|
729
950
|
baseUrl: answers.server,
|
|
730
|
-
token: project.
|
|
951
|
+
token: project.project_secret
|
|
731
952
|
}),
|
|
732
953
|
projectId: project.id,
|
|
733
954
|
force,
|
|
734
|
-
preset: answers.preset
|
|
955
|
+
preset: answers.preset,
|
|
956
|
+
useCase: answers.useCase,
|
|
957
|
+
design: answers.design
|
|
735
958
|
});
|
|
736
959
|
} catch (error) {
|
|
737
960
|
await rm(join(cwd, "zitadel.json"), { force: true });
|
|
@@ -753,6 +976,21 @@ var Setup = class Setup extends BaseCommand {
|
|
|
753
976
|
step: "files_patched",
|
|
754
977
|
files_written_count: allFilesWritten.length
|
|
755
978
|
});
|
|
979
|
+
if (!dryRun) try {
|
|
980
|
+
await writeScaffoldManifest({
|
|
981
|
+
cwd,
|
|
982
|
+
actions: orca.patcherFor(framework.id).artifacts({
|
|
983
|
+
framework,
|
|
984
|
+
rendererId: ctx.rendererId
|
|
985
|
+
}),
|
|
986
|
+
written: [...result.filesWritten, ...result.filesSkipped],
|
|
987
|
+
scaffoldedFramework,
|
|
988
|
+
devPort: answers.devPort,
|
|
989
|
+
posture
|
|
990
|
+
});
|
|
991
|
+
} catch (error) {
|
|
992
|
+
consola$1.debug("Failed to record the scaffold manifest", error);
|
|
993
|
+
}
|
|
756
994
|
const installOutcome = await installDependenciesForSetup({
|
|
757
995
|
cliVersion: this.meta.cliVersion,
|
|
758
996
|
cwd,
|
|
@@ -769,6 +1007,16 @@ var Setup = class Setup extends BaseCommand {
|
|
|
769
1007
|
package_manager: installOutcome.install.package_manager
|
|
770
1008
|
});
|
|
771
1009
|
const writtenRel = allFilesWritten.map((file) => relativeDisplay(cwd, file));
|
|
1010
|
+
const claimNudge = claimState({
|
|
1011
|
+
secret: {},
|
|
1012
|
+
server: answers.server
|
|
1013
|
+
}).kind === "detached" ? {
|
|
1014
|
+
actions: [claimAction(this.meta.cliVersion)],
|
|
1015
|
+
commands: [claimCommand(this.meta.cliVersion)]
|
|
1016
|
+
} : {
|
|
1017
|
+
actions: [],
|
|
1018
|
+
commands: []
|
|
1019
|
+
};
|
|
772
1020
|
if (!this.jsonEnabled()) {
|
|
773
1021
|
const sections = buildSummary({
|
|
774
1022
|
projectFacts: await detectProjectFacts(cwd, framework.id),
|
|
@@ -776,14 +1024,15 @@ var Setup = class Setup extends BaseCommand {
|
|
|
776
1024
|
project,
|
|
777
1025
|
server: answers.server,
|
|
778
1026
|
issuer,
|
|
779
|
-
scaffoldedFramework
|
|
1027
|
+
scaffoldedFramework,
|
|
1028
|
+
design: answers.design
|
|
780
1029
|
});
|
|
781
1030
|
consola$1.box({
|
|
782
1031
|
title: "Zitadel is ready",
|
|
783
1032
|
message: [
|
|
784
1033
|
renderSummary(sections),
|
|
785
1034
|
"",
|
|
786
|
-
installOutcome.boxActions.join("\n")
|
|
1035
|
+
[...installOutcome.boxActions, ...claimNudge.actions].join("\n")
|
|
787
1036
|
].join("\n"),
|
|
788
1037
|
style: {
|
|
789
1038
|
padding: 1,
|
|
@@ -804,10 +1053,20 @@ var Setup = class Setup extends BaseCommand {
|
|
|
804
1053
|
framework: framework.id,
|
|
805
1054
|
server: answers.server,
|
|
806
1055
|
files_written: allFilesWritten.map((file) => relativeDisplay(cwd, file)),
|
|
1056
|
+
files: result.files.map((file) => ({
|
|
1057
|
+
path: relativeDisplay(cwd, file.path),
|
|
1058
|
+
kind: file.kind,
|
|
1059
|
+
action: file.action
|
|
1060
|
+
})),
|
|
807
1061
|
files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),
|
|
808
1062
|
install: installOutcome.install,
|
|
809
|
-
|
|
810
|
-
|
|
1063
|
+
design: answers.design ?? null,
|
|
1064
|
+
next_actions: [
|
|
1065
|
+
...installOutcome.nextActions,
|
|
1066
|
+
brandingGuidanceAction(answers.design, this.meta.cliVersion),
|
|
1067
|
+
...claimNudge.actions
|
|
1068
|
+
],
|
|
1069
|
+
next_commands: [...installOutcome.nextCommands, ...claimNudge.commands]
|
|
811
1070
|
}
|
|
812
1071
|
});
|
|
813
1072
|
}
|
|
@@ -836,10 +1095,10 @@ async function resolveScaffoldFramework(framework, nonInteractive, orca) {
|
|
|
836
1095
|
function dryRunProject(issuer) {
|
|
837
1096
|
return {
|
|
838
1097
|
id: "dry-run-0000",
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1098
|
+
project_secret: "sk_proj_dry_run_full",
|
|
1099
|
+
preview_secret: "sk_proj_dry_run_preview",
|
|
1100
|
+
preview_origins: [issuer],
|
|
1101
|
+
created_at: "2026-04-21T14:03:11.000Z"
|
|
843
1102
|
};
|
|
844
1103
|
}
|
|
845
1104
|
/**
|
|
@@ -851,6 +1110,8 @@ function setupRetryFlags(opts) {
|
|
|
851
1110
|
const parts = [];
|
|
852
1111
|
if (opts.framework) parts.push(`--framework ${opts.framework}`);
|
|
853
1112
|
if (opts.preset && opts.preset !== DEFAULT_SETUP_PRESET) parts.push(`--preset ${opts.preset}`);
|
|
1113
|
+
if (opts.useCase && opts.useCase !== DEFAULT_SETUP_USE_CASE) parts.push(`--use-case ${opts.useCase}`);
|
|
1114
|
+
if (opts.design) parts.push(`--design ${opts.design}`);
|
|
854
1115
|
if (opts.renderer && opts.renderer !== "react") parts.push(`--renderer ${opts.renderer}`);
|
|
855
1116
|
if (opts.devPort !== void 0) parts.push(`--dev-port ${opts.devPort}`);
|
|
856
1117
|
if (opts.nonInteractive) parts.push("--non-interactive");
|
|
@@ -866,6 +1127,8 @@ function retryOptionsFromFlags(flags) {
|
|
|
866
1127
|
return {
|
|
867
1128
|
framework: flags.framework,
|
|
868
1129
|
preset: flags.preset,
|
|
1130
|
+
useCase: flags["use-case"],
|
|
1131
|
+
design: flags.design,
|
|
869
1132
|
renderer: flags.renderer,
|
|
870
1133
|
devPort: flags["dev-port"],
|
|
871
1134
|
nonInteractive: Boolean(flags["non-interactive"])
|
|
@@ -875,8 +1138,8 @@ async function createProjectWithLocalHint(client, server, cliVersion, projectNam
|
|
|
875
1138
|
try {
|
|
876
1139
|
const payload = {
|
|
877
1140
|
name: projectName,
|
|
878
|
-
|
|
879
|
-
|
|
1141
|
+
preview_origins: [issuer],
|
|
1142
|
+
seed_defaults: false
|
|
880
1143
|
};
|
|
881
1144
|
return await client.createProject(payload);
|
|
882
1145
|
} catch (error) {
|
|
@@ -930,15 +1193,13 @@ function pickWrittenFile(written, suffix) {
|
|
|
930
1193
|
}
|
|
931
1194
|
/**
|
|
932
1195
|
* Translates a patcher-written path into a single sentence the user can
|
|
933
|
-
* read at narration speed.
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
* something happened.
|
|
1196
|
+
* read at narration speed. The patch result's `filesWritten` carries
|
|
1197
|
+
* deduplicated file paths only (directories stay in the typed `files`
|
|
1198
|
+
* rows), so no artefact filtering is needed here. The verb tense flips
|
|
1199
|
+
* for `--dry-run` so the user sees a preview ("Would write ...") instead
|
|
1200
|
+
* of a claim that something happened.
|
|
939
1201
|
*/
|
|
940
1202
|
function describeWrittenFile(relPath, dryRun) {
|
|
941
|
-
if (relPath === ".zitadel" || relPath === ".zitadel/flows" || relPath === ".zitadel/schemas" || relPath === ".zitadel/meta") return null;
|
|
942
1203
|
const verb = dryRun ? "Would write" : "Wrote";
|
|
943
1204
|
const sentence = SENTENCE_BY_PATH[relPath];
|
|
944
1205
|
if (sentence) return `${verb} ${sentence.subject} (${path(relPath)})`;
|
|
@@ -977,7 +1238,7 @@ const SENTENCE_BY_PATH = {
|
|
|
977
1238
|
};
|
|
978
1239
|
/** Builds the section list driving {@link renderSummary} for the setup command. */
|
|
979
1240
|
function buildSummary(opts) {
|
|
980
|
-
const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework } = opts;
|
|
1241
|
+
const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework, design } = opts;
|
|
981
1242
|
const sdkPackage = "@zitadel/sdk-next";
|
|
982
1243
|
const packageJsonHit = pickWrittenFile(writtenRel, "package.json");
|
|
983
1244
|
const detected = [{
|
|
@@ -1021,6 +1282,14 @@ function buildSummary(opts) {
|
|
|
1021
1282
|
{
|
|
1022
1283
|
label: "App will run",
|
|
1023
1284
|
value: url(issuer)
|
|
1285
|
+
},
|
|
1286
|
+
design ? {
|
|
1287
|
+
label: "Login design",
|
|
1288
|
+
value: design,
|
|
1289
|
+
secondary: path(".zitadel/branding/")
|
|
1290
|
+
} : {
|
|
1291
|
+
label: "Login design",
|
|
1292
|
+
value: dim("built-in template")
|
|
1024
1293
|
}
|
|
1025
1294
|
];
|
|
1026
1295
|
return [
|