@zitadel/cli 0.1.0-alpha.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2758 -210
- package/bin/run.js +8 -0
- package/dist/index.mjs +25199 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/oclif/help.mjs +71 -0
- package/dist/lib/oclif/help.mjs.map +1 -0
- package/dist/skills-hint-BSspFzhr.mjs +173 -0
- package/dist/skills-hint-BSspFzhr.mjs.map +1 -0
- package/oclif.manifest.json +5836 -246
- package/package.json +73 -13
- package/skills/zitadel-cli/SKILL.md +442 -0
- package/skills/zitadel-cli/references/driving-login-ui.md +89 -0
- package/SKILLS.md +0 -157
- package/dist/commands/apply.mjs +0 -62
- package/dist/commands/apply.mjs.map +0 -1
- package/dist/commands/doctor.mjs +0 -569
- package/dist/commands/doctor.mjs.map +0 -1
- package/dist/commands/eject.mjs +0 -145
- package/dist/commands/eject.mjs.map +0 -1
- package/dist/commands/logs.mjs +0 -58
- package/dist/commands/logs.mjs.map +0 -1
- package/dist/commands/plan.mjs +0 -52
- package/dist/commands/plan.mjs.map +0 -1
- package/dist/commands/reset.mjs +0 -79
- package/dist/commands/reset.mjs.map +0 -1
- package/dist/commands/setup.mjs +0 -856
- package/dist/commands/setup.mjs.map +0 -1
- package/dist/commands/start.mjs +0 -288
- package/dist/commands/start.mjs.map +0 -1
- package/dist/commands/status.mjs +0 -112
- package/dist/commands/status.mjs.map +0 -1
- package/dist/commands/stop.mjs +0 -105
- package/dist/commands/stop.mjs.map +0 -1
- package/dist/docker-CnGQK3ZK.mjs +0 -432
- package/dist/docker-CnGQK3ZK.mjs.map +0 -1
- package/dist/docker-guidance-ypN3IM3o.mjs +0 -21
- package/dist/docker-guidance-ypN3IM3o.mjs.map +0 -1
- package/dist/oclif-B7lBzh3R.mjs +0 -808
- package/dist/oclif-B7lBzh3R.mjs.map +0 -1
- package/dist/orca-U142Wrau.mjs +0 -2556
- package/dist/orca-U142Wrau.mjs.map +0 -1
- package/dist/ports-B09RjuHx.mjs +0 -111
- package/dist/ports-B09RjuHx.mjs.map +0 -1
- package/dist/processes-Cw8TO1SY.mjs +0 -120
- package/dist/processes-Cw8TO1SY.mjs.map +0 -1
- package/dist/project-Cd0L3PtM.mjs +0 -87
- package/dist/project-Cd0L3PtM.mjs.map +0 -1
- package/dist/sync-BojoQm2P.mjs +0 -733
- package/dist/sync-BojoQm2P.mjs.map +0 -1
package/dist/commands/setup.mjs
DELETED
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
import { D as toZitadelError, E as ZitadelError, b as publicCliCommand, n as DEFAULT_SERVER, t as BaseCommand } from "../oclif-B7lBzh3R.mjs";
|
|
2
|
-
import { i as issuerFromPort, n as inspectScaffoldTarget, r as RENDERER_IDS, t as createOrca } from "../orca-U142Wrau.mjs";
|
|
3
|
-
import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-Cd0L3PtM.mjs";
|
|
4
|
-
import { t as listListeningPorts } from "../ports-B09RjuHx.mjs";
|
|
5
|
-
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
6
|
-
import { Flags } from "@oclif/core";
|
|
7
|
-
import { createZitadelClient } from "@zitadel/api/client";
|
|
8
|
-
import { consola as consola$1 } from "consola";
|
|
9
|
-
import { basename, join } from "node:path";
|
|
10
|
-
import { readFile, stat } from "node:fs/promises";
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
|
-
import pc from "picocolors";
|
|
13
|
-
//#region src/lib/package-manager.ts
|
|
14
|
-
async function detectPackageManager(cwd) {
|
|
15
|
-
const declared = await packageManagerFromManifest(cwd);
|
|
16
|
-
if (declared) return declared;
|
|
17
|
-
if (await exists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
18
|
-
if (await exists(join(cwd, "yarn.lock"))) return "yarn";
|
|
19
|
-
if (await exists(join(cwd, "bun.lock"))) return "bun";
|
|
20
|
-
if (await exists(join(cwd, "bun.lockb"))) return "bun";
|
|
21
|
-
if (await exists(join(cwd, "package-lock.json"))) return "npm";
|
|
22
|
-
return "npm";
|
|
23
|
-
}
|
|
24
|
-
function installCommandFor(packageManager) {
|
|
25
|
-
return command(packageManager, ["install"]);
|
|
26
|
-
}
|
|
27
|
-
function devCommandFor(packageManager) {
|
|
28
|
-
switch (packageManager) {
|
|
29
|
-
case "npm": return command("npm", ["run", "dev"]);
|
|
30
|
-
case "pnpm": return command("pnpm", ["dev"]);
|
|
31
|
-
case "yarn": return command("yarn", ["dev"]);
|
|
32
|
-
case "bun": return command("bun", ["run", "dev"]);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function runPackageCommand(packageCommand, options) {
|
|
36
|
-
await new Promise((resolve, reject) => {
|
|
37
|
-
const child = spawn(packageCommand.command, packageCommand.args, {
|
|
38
|
-
cwd: options.cwd,
|
|
39
|
-
env: options.env ?? process.env,
|
|
40
|
-
stdio: options.redirectStdoutToStderr ? [
|
|
41
|
-
"ignore",
|
|
42
|
-
"pipe",
|
|
43
|
-
"pipe"
|
|
44
|
-
] : "inherit"
|
|
45
|
-
});
|
|
46
|
-
if (options.redirectStdoutToStderr) {
|
|
47
|
-
child.stdout?.on("data", (chunk) => process.stderr.write(chunk));
|
|
48
|
-
child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
49
|
-
}
|
|
50
|
-
child.on("error", reject);
|
|
51
|
-
child.on("close", (code, signal) => {
|
|
52
|
-
if (code === 0) {
|
|
53
|
-
resolve();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const detail = signal ? `signal ${signal}` : `exit ${String(code ?? 1)}`;
|
|
57
|
-
const error = /* @__PURE__ */ new Error(`${packageCommand.display} failed with ${detail}`);
|
|
58
|
-
Object.assign(error, {
|
|
59
|
-
code: code ?? 1,
|
|
60
|
-
signal
|
|
61
|
-
});
|
|
62
|
-
reject(error);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function command(packageManager, args) {
|
|
67
|
-
return {
|
|
68
|
-
command: packageManager,
|
|
69
|
-
args,
|
|
70
|
-
display: [packageManager, ...args].join(" ")
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
async function packageManagerFromManifest(cwd) {
|
|
74
|
-
try {
|
|
75
|
-
const raw = await readFile(join(cwd, "package.json"), "utf8");
|
|
76
|
-
const parsed = JSON.parse(raw);
|
|
77
|
-
return typeof parsed.packageManager === "string" ? packageManagerFromString(parsed.packageManager) : void 0;
|
|
78
|
-
} catch {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function packageManagerFromString(value) {
|
|
83
|
-
const name = value.split("@")[0];
|
|
84
|
-
return name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" ? name : void 0;
|
|
85
|
-
}
|
|
86
|
-
async function exists(path) {
|
|
87
|
-
try {
|
|
88
|
-
await stat(path);
|
|
89
|
-
return true;
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/commands/setup/install.ts
|
|
96
|
-
async function installDependenciesForSetup(input) {
|
|
97
|
-
const packageManager = await detectPackageManager(input.cwd);
|
|
98
|
-
const installCommand = installCommandFor(packageManager);
|
|
99
|
-
const devCommand = devCommandFor(packageManager);
|
|
100
|
-
if (!(input.scaffoldedFramework || input.depsAdded.length > 0)) return outcome({
|
|
101
|
-
install: {
|
|
102
|
-
status: "not-needed",
|
|
103
|
-
package_manager: packageManager,
|
|
104
|
-
command: installCommand.display,
|
|
105
|
-
reason: "no-dependency-changes"
|
|
106
|
-
},
|
|
107
|
-
devCommand: devCommand.display,
|
|
108
|
-
issuer: input.issuer,
|
|
109
|
-
includeInstallCommand: false
|
|
110
|
-
});
|
|
111
|
-
if (input.dryRun || input.skipInstall) return outcome({
|
|
112
|
-
install: {
|
|
113
|
-
status: "skipped",
|
|
114
|
-
package_manager: packageManager,
|
|
115
|
-
command: installCommand.display,
|
|
116
|
-
reason: input.dryRun ? "dry-run" : "skip-install"
|
|
117
|
-
},
|
|
118
|
-
devCommand: devCommand.display,
|
|
119
|
-
issuer: input.issuer,
|
|
120
|
-
includeInstallCommand: true
|
|
121
|
-
});
|
|
122
|
-
consola$1.start(`Installing dependencies with ${installCommand.display}`);
|
|
123
|
-
try {
|
|
124
|
-
await (input.run ?? runPackageCommand)(installCommand, {
|
|
125
|
-
cwd: input.cwd,
|
|
126
|
-
env: input.env,
|
|
127
|
-
redirectStdoutToStderr: input.json
|
|
128
|
-
});
|
|
129
|
-
} catch (error) {
|
|
130
|
-
throw installFailed(error, installCommand, devCommand, input.cwd);
|
|
131
|
-
}
|
|
132
|
-
consola$1.success("Installed dependencies");
|
|
133
|
-
return outcome({
|
|
134
|
-
install: {
|
|
135
|
-
status: "completed",
|
|
136
|
-
package_manager: packageManager,
|
|
137
|
-
command: installCommand.display
|
|
138
|
-
},
|
|
139
|
-
devCommand: devCommand.display,
|
|
140
|
-
issuer: input.issuer,
|
|
141
|
-
includeInstallCommand: false
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
function outcome(input) {
|
|
145
|
-
const startAction = `Start your project: ${input.devCommand} (then open ${input.issuer})`;
|
|
146
|
-
const verifyAction = "Verify auth in the browser: register a user, log out, log in again with the same user, and confirm /profile shows Signed in.";
|
|
147
|
-
return {
|
|
148
|
-
install: input.install,
|
|
149
|
-
devCommand: input.devCommand,
|
|
150
|
-
nextActions: input.includeInstallCommand ? [
|
|
151
|
-
`Install dependencies: ${input.install.command}`,
|
|
152
|
-
startAction,
|
|
153
|
-
verifyAction
|
|
154
|
-
] : [startAction, verifyAction],
|
|
155
|
-
nextCommands: input.includeInstallCommand ? [input.install.command, input.devCommand] : [input.devCommand]
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function installFailed(error, installCommand, devCommand, cwd) {
|
|
159
|
-
return new ZitadelError("E_VALIDATION", `Dependency install failed: ${installCommand.display}`, {
|
|
160
|
-
hint: `Run ${installCommand.display} in ${cwd}, then start the app with ${devCommand.display}.`,
|
|
161
|
-
nextCommands: [installCommand.display],
|
|
162
|
-
details: {
|
|
163
|
-
command: installCommand.display,
|
|
164
|
-
cwd,
|
|
165
|
-
original: errorShape(error)
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function errorShape(error) {
|
|
170
|
-
if (error instanceof Error) return {
|
|
171
|
-
name: error.name,
|
|
172
|
-
message: error.message,
|
|
173
|
-
code: error.code
|
|
174
|
-
};
|
|
175
|
-
return { message: String(error) };
|
|
176
|
-
}
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/commands/setup/prompts/cancel.ts
|
|
179
|
-
/**
|
|
180
|
-
* Converts a clack cancellation (Ctrl-C) into a thrown `E_VALIDATION` rather
|
|
181
|
-
* than a partial answer. Every prompt funnels its clack return value through
|
|
182
|
-
* this so the wizard never proceeds with a sentinel or missing value.
|
|
183
|
-
*/
|
|
184
|
-
function bail(value) {
|
|
185
|
-
if (isCancel(value)) {
|
|
186
|
-
cancel("Setup cancelled.");
|
|
187
|
-
throw new ZitadelError("E_VALIDATION", "Setup cancelled by user");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/commands/setup/prompts/dev-port.ts
|
|
192
|
-
/**
|
|
193
|
-
* "Dev server port" — defaults to the detected port. The validated answer
|
|
194
|
-
* becomes the issuer URL (`http://localhost:<port>`) via `issuerFromPort`.
|
|
195
|
-
*/
|
|
196
|
-
var DevPortPrompt = class {
|
|
197
|
-
async ask(answers, ctx) {
|
|
198
|
-
if (ctx.devPortFromFlag) return answers;
|
|
199
|
-
const value = await text({
|
|
200
|
-
message: "Dev server port",
|
|
201
|
-
placeholder: String(answers.devPort),
|
|
202
|
-
initialValue: String(answers.devPort),
|
|
203
|
-
validate: (input) => {
|
|
204
|
-
const num = Number.parseInt(input ?? "", 10);
|
|
205
|
-
return Number.isFinite(num) && num > 0 && num < 65536 ? void 0 : "Must be a port number";
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
bail(value);
|
|
209
|
-
return {
|
|
210
|
-
...answers,
|
|
211
|
-
devPort: Number.parseInt(String(value), 10)
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
//#endregion
|
|
216
|
-
//#region src/commands/setup/prompts/framework-confirm.ts
|
|
217
|
-
/**
|
|
218
|
-
* "Detected `<framework>`. Proceed?" — the wizard's first question. Accepting
|
|
219
|
-
* leaves answers unchanged; declining throws `E_UNSUPPORTED_PROJECT_SHAPE`
|
|
220
|
-
* (the user should re-run with an explicit `--framework`).
|
|
221
|
-
*/
|
|
222
|
-
var FrameworkConfirmPrompt = class {
|
|
223
|
-
async ask(answers, ctx) {
|
|
224
|
-
const ack = await confirm({
|
|
225
|
-
message: `Detected ${ctx.framework.id}. Proceed?`,
|
|
226
|
-
initialValue: true
|
|
227
|
-
});
|
|
228
|
-
bail(ack);
|
|
229
|
-
if (ack === false) throw new ZitadelError("E_UNSUPPORTED_PROJECT_SHAPE", "Setup cancelled — framework declined", { hint: `Re-run with --framework ${ctx.framework.id} when ready.` });
|
|
230
|
-
return answers;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
//#endregion
|
|
234
|
-
//#region src/lib/prober/http.ts
|
|
235
|
-
/**
|
|
236
|
-
* Fetch `url` with a per-call timeout and pass the resulting `Response` to
|
|
237
|
-
* `predicate`. Returns the predicate's value, or `null` on any failure: a
|
|
238
|
-
* network error, an `AbortError` from the timeout, a thrown predicate, or the
|
|
239
|
-
* predicate returning `null`.
|
|
240
|
-
*
|
|
241
|
-
* Never throws. The predicate decides what counts as a match — it gets the
|
|
242
|
-
* raw `Response` and may read `.json()` / `.headers` / `.status` as needed.
|
|
243
|
-
*
|
|
244
|
-
* `timeoutMs` defaults to 500ms. The implementation uses
|
|
245
|
-
* `AbortSignal.timeout`, so the underlying fetch is cancelled when the
|
|
246
|
-
* timeout fires (no orphaned sockets).
|
|
247
|
-
*/
|
|
248
|
-
async function probeUrl(url, predicate, opts) {
|
|
249
|
-
const timeoutMs = opts?.timeoutMs ?? 500;
|
|
250
|
-
try {
|
|
251
|
-
return await predicate(await fetch(url, { signal: AbortSignal.timeout(timeoutMs) }));
|
|
252
|
-
} catch {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Probe many URLs in parallel and return the non-null matches, preserving
|
|
258
|
-
* the input order. Each URL is bounded by `timeoutMs` independently — one
|
|
259
|
-
* slow target can't hold up the others.
|
|
260
|
-
*/
|
|
261
|
-
async function probeUrls(urls, predicate, opts) {
|
|
262
|
-
const list = [...urls];
|
|
263
|
-
return (await Promise.all(list.map(async (url) => {
|
|
264
|
-
const value = await probeUrl(url, predicate, opts);
|
|
265
|
-
return value === null ? null : {
|
|
266
|
-
url,
|
|
267
|
-
value
|
|
268
|
-
};
|
|
269
|
-
}))).filter((match) => match !== null);
|
|
270
|
-
}
|
|
271
|
-
//#endregion
|
|
272
|
-
//#region src/commands/setup/prompts/server.ts
|
|
273
|
-
/** Sentinel returned by the choice select when the user picks "Custom URL". */
|
|
274
|
-
const CUSTOM = "__custom__";
|
|
275
|
-
/** Per-probe timeout for the localhost OIDC scan — generous enough for a TLS handshake on a busy laptop. */
|
|
276
|
-
const OIDC_PROBE_TIMEOUT_MS = 300;
|
|
277
|
-
/**
|
|
278
|
-
* "Which server should `zitadel.json` point to?" — Zitadel Cloud, a localhost
|
|
279
|
-
* OIDC server we discovered, or a custom URL.
|
|
280
|
-
*
|
|
281
|
-
* Before asking, it scans the loopback for listening ports (via the generic
|
|
282
|
-
* `lib/prober`) and `GET`s `/.well-known/openid-configuration` on each. Every
|
|
283
|
-
* 2xx response with a string `issuer` becomes an extra option in the choice
|
|
284
|
-
* list. Picking a discovered URL writes it directly to `answers.server` and
|
|
285
|
-
* skips the follow-up text prompt. Picking "Custom URL" still asks for a
|
|
286
|
-
* validated URL exactly as before.
|
|
287
|
-
*/
|
|
288
|
-
var ServerPrompt = class {
|
|
289
|
-
async ask(answers, ctx) {
|
|
290
|
-
if (ctx.serverFlag) return answers;
|
|
291
|
-
const discovered = await discoverLocalOidc();
|
|
292
|
-
const choice = await select({
|
|
293
|
-
message: "Which server should zitadel.json point to?",
|
|
294
|
-
options: [
|
|
295
|
-
{
|
|
296
|
-
value: DEFAULT_SERVER,
|
|
297
|
-
label: "Zitadel Cloud (api.zitadel.cloud)",
|
|
298
|
-
hint: "recommended for real projects"
|
|
299
|
-
},
|
|
300
|
-
...discovered.map((server) => ({
|
|
301
|
-
value: server,
|
|
302
|
-
label: server,
|
|
303
|
-
hint: "detected — OIDC"
|
|
304
|
-
})),
|
|
305
|
-
{
|
|
306
|
-
value: CUSTOM,
|
|
307
|
-
label: "Custom URL (self-hosted)"
|
|
308
|
-
}
|
|
309
|
-
],
|
|
310
|
-
initialValue: answers.server ?? "https://api.zitadel.cloud"
|
|
311
|
-
});
|
|
312
|
-
bail(choice);
|
|
313
|
-
if (choice !== CUSTOM) return {
|
|
314
|
-
...answers,
|
|
315
|
-
server: choice
|
|
316
|
-
};
|
|
317
|
-
const custom = await text({
|
|
318
|
-
message: "Server URL",
|
|
319
|
-
placeholder: "https://zitadel.internal",
|
|
320
|
-
validate: (value) => {
|
|
321
|
-
try {
|
|
322
|
-
new URL(value ?? "");
|
|
323
|
-
return;
|
|
324
|
-
} catch {
|
|
325
|
-
return "Must be a valid URL";
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
bail(custom);
|
|
330
|
-
return {
|
|
331
|
-
...answers,
|
|
332
|
-
server: custom
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
/**
|
|
337
|
-
* Returns the loopback origins (`http://localhost:<port>`) whose
|
|
338
|
-
* `/.well-known/openid-configuration` endpoint responds with a valid OIDC
|
|
339
|
-
* discovery document. Sniffs purely via the generic prober — the only
|
|
340
|
-
* Zitadel-shaped detail here is the OIDC predicate, which is the standard
|
|
341
|
-
* "any OIDC server" contract (no `zitadel`-keyword check), so Keycloak/dex/
|
|
342
|
-
* etc. also surface and the user picks the right one.
|
|
343
|
-
*/
|
|
344
|
-
async function discoverLocalOidc() {
|
|
345
|
-
const s = spinner();
|
|
346
|
-
s.start("Scanning localhost for OIDC servers");
|
|
347
|
-
try {
|
|
348
|
-
const ports = await listListeningPorts();
|
|
349
|
-
if (ports.length === 0) {
|
|
350
|
-
s.stop("No local servers detected.");
|
|
351
|
-
return [];
|
|
352
|
-
}
|
|
353
|
-
const origins = (await probeUrls(ports.map((port) => `http://localhost:${port}/.well-known/openid-configuration`), async (response) => {
|
|
354
|
-
if (!response.ok) return null;
|
|
355
|
-
try {
|
|
356
|
-
const body = await response.json();
|
|
357
|
-
return typeof body.issuer === "string" ? body.issuer : null;
|
|
358
|
-
} catch {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
}, { timeoutMs: OIDC_PROBE_TIMEOUT_MS })).map((match) => new URL(match.url).origin);
|
|
362
|
-
s.stop(origins.length === 0 ? "No local OIDC servers found." : `Found ${origins.length} local OIDC server${origins.length === 1 ? "" : "s"}.`);
|
|
363
|
-
return origins;
|
|
364
|
-
} catch (error) {
|
|
365
|
-
s.stop("Discovery skipped.");
|
|
366
|
-
throw error;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
//#endregion
|
|
370
|
-
//#region src/commands/setup/prompts/pick-framework.ts
|
|
371
|
-
/**
|
|
372
|
-
* "Choose a framework to scaffold" — the only prompt outside the main wizard.
|
|
373
|
-
* Runs at the empty-directory branch (before any other detection), so it owns
|
|
374
|
-
* its own `intro` heading. Returns the chosen framework id; choices come from
|
|
375
|
-
* `Orca.availableFrameworks`.
|
|
376
|
-
*/
|
|
377
|
-
var PickFrameworkPrompt = class {
|
|
378
|
-
async ask(choices) {
|
|
379
|
-
intro("Zitadel setup — new project");
|
|
380
|
-
const picked = await select({
|
|
381
|
-
message: "Choose a framework to scaffold",
|
|
382
|
-
options: choices.map((choice) => ({
|
|
383
|
-
value: choice.id,
|
|
384
|
-
label: choice.displayName
|
|
385
|
-
}))
|
|
386
|
-
});
|
|
387
|
-
bail(picked);
|
|
388
|
-
return picked;
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
//#endregion
|
|
392
|
-
//#region src/commands/setup/prompts/index.ts
|
|
393
|
-
/**
|
|
394
|
-
* Public surface for the setup wizard prompts. The `setup` command imports
|
|
395
|
-
* {@link SETUP_PROMPTS} and iterates every entry, threading the answers
|
|
396
|
-
* through each prompt's `ask` so they decide whether to actually ask the
|
|
397
|
-
* user. Add a new question by writing a class and appending an instance to
|
|
398
|
-
* the registry below.
|
|
399
|
-
*
|
|
400
|
-
* {@link PickFrameworkPrompt} is intentionally **not** in {@link SETUP_PROMPTS}
|
|
401
|
-
* — it runs at the empty-directory scaffold branch, before the main wizard
|
|
402
|
-
* starts.
|
|
403
|
-
*/
|
|
404
|
-
/** Every question the main setup wizard asks, in ask order. */
|
|
405
|
-
const SETUP_PROMPTS = [
|
|
406
|
-
new FrameworkConfirmPrompt(),
|
|
407
|
-
new ServerPrompt(),
|
|
408
|
-
new DevPortPrompt()
|
|
409
|
-
];
|
|
410
|
-
//#endregion
|
|
411
|
-
//#region src/commands/setup/summary.ts
|
|
412
|
-
/**
|
|
413
|
-
* Renders the section list as a single multi-line string. Labels are
|
|
414
|
-
* padded to a common width per section so the `✓ label value` columns
|
|
415
|
-
* line up, the title prints in dim gray, and `✓` is green. Values come
|
|
416
|
-
* in pre-styled — use the helpers below ({@link path}, {@link url},
|
|
417
|
-
* {@link id}, {@link dim}) to keep the colour palette consistent with
|
|
418
|
-
* the mock.
|
|
419
|
-
*/
|
|
420
|
-
function renderSummary(sections) {
|
|
421
|
-
const lines = [];
|
|
422
|
-
for (const section of sections) {
|
|
423
|
-
if (section.rows.length === 0) continue;
|
|
424
|
-
if (lines.length > 0) lines.push("");
|
|
425
|
-
lines.push(pc.dim(section.title.toUpperCase()));
|
|
426
|
-
const labelWidth = Math.max(...section.rows.map((r) => r.label.length)) + 3;
|
|
427
|
-
for (const row of section.rows) {
|
|
428
|
-
const labelCol = row.label.padEnd(labelWidth);
|
|
429
|
-
const secondary = row.secondary ? ` ${pc.dim("→")} ${row.secondary}` : "";
|
|
430
|
-
lines.push(`${pc.green("✓")} ${labelCol}${row.value}${secondary}`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return lines.join("\n");
|
|
434
|
-
}
|
|
435
|
-
/** Cyan, for filesystem paths the user can open. */
|
|
436
|
-
const path = (s) => pc.cyan(s);
|
|
437
|
-
/** Cyan, for URLs (browser-clickable in most terminals). */
|
|
438
|
-
const url = (s) => pc.cyan(s);
|
|
439
|
-
/** Yellow, for opaque ids the user shouldn't try to read. */
|
|
440
|
-
const id = (s) => pc.yellow(s);
|
|
441
|
-
/**
|
|
442
|
-
* Reads the project root to identify the framework version, TS presence,
|
|
443
|
-
* and which package manager the user runs. Returns safe defaults (npm
|
|
444
|
-
* fallback, no version) on any error so summary rendering can always proceed.
|
|
445
|
-
*/
|
|
446
|
-
async function detectProjectFacts(cwd, frameworkId) {
|
|
447
|
-
const facts = {
|
|
448
|
-
framework: frameworkId,
|
|
449
|
-
typescript: await fileExists(join(cwd, "tsconfig.json")),
|
|
450
|
-
packageManager: await detectPackageManager(cwd)
|
|
451
|
-
};
|
|
452
|
-
try {
|
|
453
|
-
const raw = await readFile(join(cwd, "package.json"), "utf8");
|
|
454
|
-
const pj = JSON.parse(raw);
|
|
455
|
-
const depPkg = depFromFramework(frameworkId);
|
|
456
|
-
const range = pj.dependencies?.[depPkg] ?? pj.devDependencies?.[depPkg];
|
|
457
|
-
if (range) facts.frameworkVersion = stripRange(range);
|
|
458
|
-
} catch {}
|
|
459
|
-
return facts;
|
|
460
|
-
}
|
|
461
|
-
/** Formats the project facts as the "Next.js 15 · TypeScript · npm" string the mock shows. */
|
|
462
|
-
function formatFrameworkLine(facts) {
|
|
463
|
-
const segments = [];
|
|
464
|
-
const pretty = prettyFramework(facts.framework);
|
|
465
|
-
segments.push(facts.frameworkVersion ? `${pretty} ${facts.frameworkVersion}` : pretty);
|
|
466
|
-
if (facts.typescript) segments.push("TypeScript");
|
|
467
|
-
segments.push(facts.packageManager);
|
|
468
|
-
return segments.join(` ${pc.dim("·")} `);
|
|
469
|
-
}
|
|
470
|
-
async function fileExists(p) {
|
|
471
|
-
try {
|
|
472
|
-
await stat(p);
|
|
473
|
-
return true;
|
|
474
|
-
} catch {
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
function depFromFramework(framework) {
|
|
479
|
-
switch (framework) {
|
|
480
|
-
case "next": return "next";
|
|
481
|
-
case "nuxt": return "nuxt";
|
|
482
|
-
default: return framework;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
function prettyFramework(framework) {
|
|
486
|
-
switch (framework) {
|
|
487
|
-
case "next": return "Next.js";
|
|
488
|
-
case "nuxt": return "Nuxt";
|
|
489
|
-
default: return framework;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
/** Strips `^`/`~`/`>=`/`v` prefixes from an npm range so the version reads cleanly in the summary. */
|
|
493
|
-
function stripRange(range) {
|
|
494
|
-
const trimmed = range.replace(/^([\^~]|>=?|v)/, "").trim();
|
|
495
|
-
return trimmed.split(/\s+/)[0] ?? trimmed;
|
|
496
|
-
}
|
|
497
|
-
/** Returns just the file name from a relative path, for the `→ filename` secondary detail in PACKAGE rows. */
|
|
498
|
-
function fileNameOf(p) {
|
|
499
|
-
return basename(p);
|
|
500
|
-
}
|
|
501
|
-
//#endregion
|
|
502
|
-
//#region src/commands/setup/index.ts
|
|
503
|
-
/**
|
|
504
|
-
* The frameworks `--framework` accepts, derived from Orca's registry so the
|
|
505
|
-
* flag can't drift from what the CLI can actually scaffold. `createOrca` is
|
|
506
|
-
* pure (it only builds the in-memory registries), so this is safe at module
|
|
507
|
-
* load.
|
|
508
|
-
*/
|
|
509
|
-
const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) => framework.id);
|
|
510
|
-
/** `zitadel setup` — create a project and scaffold local auth.
|
|
511
|
-
*
|
|
512
|
-
* Detects (or, for an empty directory, scaffolds then re-detects) the
|
|
513
|
-
* framework, runs the wizard prompts to fill in any answers not pre-supplied
|
|
514
|
-
* by flags, creates the remote project (whose default user schema and login
|
|
515
|
-
* flow are provisioned server-side), and patches the local files via
|
|
516
|
-
* `Orca`'s framework patcher.
|
|
517
|
-
*
|
|
518
|
-
* Every interactive question lives in {@link SETUP_PROMPTS} (the main wizard
|
|
519
|
-
* — each entry is a small class) and {@link PickFrameworkPrompt} (the
|
|
520
|
-
* empty-directory framework choice, before the main wizard).
|
|
521
|
-
*/
|
|
522
|
-
var Setup = class Setup extends BaseCommand {
|
|
523
|
-
static description = "Create a Zitadel project and scaffold local auth.";
|
|
524
|
-
static examples = ["<%= config.bin %> setup --framework next", "<%= config.bin %> setup --framework react --dev-port 3000"];
|
|
525
|
-
static flags = {
|
|
526
|
-
framework: Flags.string({
|
|
527
|
-
description: "Framework to target.",
|
|
528
|
-
options: FRAMEWORK_OPTIONS
|
|
529
|
-
}),
|
|
530
|
-
renderer: Flags.string({
|
|
531
|
-
description: "Renderer (default: react).",
|
|
532
|
-
options: [...RENDERER_IDS]
|
|
533
|
-
}),
|
|
534
|
-
"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." }),
|
|
535
|
-
"skip-install": Flags.boolean({ description: "Do not install dependencies after setup updates package.json." })
|
|
536
|
-
};
|
|
537
|
-
async run() {
|
|
538
|
-
const { flags } = await this.parse(Setup);
|
|
539
|
-
try {
|
|
540
|
-
await this.toMeta(flags);
|
|
541
|
-
} catch (error) {
|
|
542
|
-
throw localSetupHint(error, flags.framework, this.config.version);
|
|
543
|
-
}
|
|
544
|
-
const { cwd, nonInteractive, dryRun, force } = this.meta;
|
|
545
|
-
if (await hasZitadelConfig(cwd)) return this.emit({
|
|
546
|
-
status: "skipped",
|
|
547
|
-
reason: "already-initialized"
|
|
548
|
-
});
|
|
549
|
-
if (await hasZitadelSecret(cwd)) throw new ZitadelError("E_CONFLICT", ".zitadel/secret exists without zitadel.json", { hint: "Move the secret aside or restore zitadel.json before running setup." });
|
|
550
|
-
const orca = createOrca();
|
|
551
|
-
consola$1.start(`Detecting framework in ${shortPath(cwd)}`);
|
|
552
|
-
let framework;
|
|
553
|
-
let scaffoldedFramework = false;
|
|
554
|
-
try {
|
|
555
|
-
framework = await orca.detect(cwd, flags.framework);
|
|
556
|
-
consola$1.success(`Detected ${framework.id}${framework.devPort ? ` (dev port ${framework.devPort})` : ""}`);
|
|
557
|
-
} catch (error) {
|
|
558
|
-
if (error instanceof ZitadelError && error.code === "E_FRAMEWORK_NOT_DETECTED") {
|
|
559
|
-
const target = await inspectScaffoldTarget(cwd);
|
|
560
|
-
if (!target.scaffoldable) throw frameworkDetectionWithScaffoldTarget(error, cwd, target);
|
|
561
|
-
consola$1.info("Fresh app directory — scaffolding a fresh project");
|
|
562
|
-
framework = await orca.scaffold(cwd, await resolveScaffoldFramework(flags.framework, nonInteractive, orca));
|
|
563
|
-
scaffoldedFramework = true;
|
|
564
|
-
consola$1.success(`Scaffolded ${framework.id} skeleton`);
|
|
565
|
-
} else throw error;
|
|
566
|
-
}
|
|
567
|
-
if (flags["dev-port"] !== void 0) {
|
|
568
|
-
const devPort = flags["dev-port"];
|
|
569
|
-
if (!Number.isInteger(devPort) || devPort < 1 || devPort > 65535) throw new ZitadelError("E_VALIDATION", `--dev-port must be an integer in 1..65535, got ${devPort}`);
|
|
570
|
-
framework = {
|
|
571
|
-
...framework,
|
|
572
|
-
devPort,
|
|
573
|
-
url: issuerFromPort(devPort)
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
let answers = {
|
|
577
|
-
server: this.meta.source,
|
|
578
|
-
devPort: framework.devPort
|
|
579
|
-
};
|
|
580
|
-
if (!nonInteractive && !dryRun) {
|
|
581
|
-
intro("Zitadel setup");
|
|
582
|
-
const promptCtx = {
|
|
583
|
-
framework,
|
|
584
|
-
serverFlag: this.meta.serverFlag,
|
|
585
|
-
devPortFromFlag: flags["dev-port"] !== void 0
|
|
586
|
-
};
|
|
587
|
-
for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
|
|
588
|
-
outro("Configuration captured");
|
|
589
|
-
}
|
|
590
|
-
const issuer = issuerFromPort(answers.devPort);
|
|
591
|
-
framework = {
|
|
592
|
-
...framework,
|
|
593
|
-
devPort: answers.devPort,
|
|
594
|
-
url: issuer
|
|
595
|
-
};
|
|
596
|
-
consola$1.start(`Creating project on ${answers.server}${dryRun ? " (dry run)" : ""}`);
|
|
597
|
-
const unauthClient = createZitadelClient({ baseUrl: answers.server });
|
|
598
|
-
const project = dryRun ? dryRunProject(issuer) : await createProjectWithLocalHint(unauthClient, answers.server, this.meta.cliVersion, issuer, framework.id);
|
|
599
|
-
consola$1.success(`Created project ${project.id}`);
|
|
600
|
-
const ctx = {
|
|
601
|
-
framework,
|
|
602
|
-
rendererId: flags.renderer ?? "react",
|
|
603
|
-
project,
|
|
604
|
-
issuer,
|
|
605
|
-
server: answers.server,
|
|
606
|
-
cliVersion: this.meta.cliVersion,
|
|
607
|
-
scaffoldedFramework
|
|
608
|
-
};
|
|
609
|
-
consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
|
|
610
|
-
const result = await orca.patcherFor(framework.id).patch(ctx, {
|
|
611
|
-
cwd,
|
|
612
|
-
dryRun,
|
|
613
|
-
force
|
|
614
|
-
});
|
|
615
|
-
for (const file of result.filesWritten) {
|
|
616
|
-
const sentence = describeWrittenFile(relativeDisplay(cwd, file), dryRun);
|
|
617
|
-
if (sentence) consola$1.info(sentence);
|
|
618
|
-
}
|
|
619
|
-
for (const file of result.filesSkipped) consola$1.info(`Left ${relativeDisplay(cwd, file)} unchanged (already matches target)`);
|
|
620
|
-
consola$1.success(`Patched ${result.filesWritten.length} file${result.filesWritten.length === 1 ? "" : "s"}` + (result.filesSkipped.length > 0 ? ` (${result.filesSkipped.length} unchanged)` : ""));
|
|
621
|
-
const installOutcome = await installDependenciesForSetup({
|
|
622
|
-
cwd,
|
|
623
|
-
depsAdded: result.depsAdded,
|
|
624
|
-
dryRun,
|
|
625
|
-
env: this.meta.env,
|
|
626
|
-
issuer,
|
|
627
|
-
json: this.jsonEnabled(),
|
|
628
|
-
scaffoldedFramework,
|
|
629
|
-
skipInstall: Boolean(flags["skip-install"])
|
|
630
|
-
});
|
|
631
|
-
const writtenRel = result.filesWritten.map((file) => relativeDisplay(cwd, file));
|
|
632
|
-
if (!this.jsonEnabled()) {
|
|
633
|
-
const sections = buildSummary({
|
|
634
|
-
projectFacts: await detectProjectFacts(cwd, framework.id),
|
|
635
|
-
writtenRel,
|
|
636
|
-
project,
|
|
637
|
-
server: answers.server,
|
|
638
|
-
issuer,
|
|
639
|
-
scaffoldedFramework
|
|
640
|
-
});
|
|
641
|
-
consola$1.box({
|
|
642
|
-
title: "Zitadel is ready",
|
|
643
|
-
message: [
|
|
644
|
-
renderSummary(sections),
|
|
645
|
-
"",
|
|
646
|
-
installOutcome.nextActions.join("\n")
|
|
647
|
-
].join("\n"),
|
|
648
|
-
style: {
|
|
649
|
-
padding: 1,
|
|
650
|
-
borderStyle: "rounded",
|
|
651
|
-
borderColor: "green"
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
return this.emit({
|
|
656
|
-
status: "ok",
|
|
657
|
-
pretty: "",
|
|
658
|
-
data: {
|
|
659
|
-
title: "Zitadel is ready.",
|
|
660
|
-
project: {
|
|
661
|
-
project_id: project.id,
|
|
662
|
-
issuer
|
|
663
|
-
},
|
|
664
|
-
framework: framework.id,
|
|
665
|
-
server: answers.server,
|
|
666
|
-
files_written: result.filesWritten.map((file) => relativeDisplay(cwd, file)),
|
|
667
|
-
files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),
|
|
668
|
-
install: installOutcome.install,
|
|
669
|
-
next_actions: installOutcome.nextActions,
|
|
670
|
-
next_commands: installOutcome.nextCommands
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
function frameworkDetectionWithScaffoldTarget(error, cwd, target) {
|
|
676
|
-
return new ZitadelError(error.code, "Could not detect a supported app framework, and this directory is not a fresh scaffold target", {
|
|
677
|
-
hint: `${target.reason ?? "Directory is not empty."} Run setup from an empty directory to scaffold a new app, or run setup from an existing supported app project.`,
|
|
678
|
-
details: {
|
|
679
|
-
cwd,
|
|
680
|
-
entries: target.entries,
|
|
681
|
-
reason: target.reason
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Resolves which framework to scaffold into an empty directory: the explicit
|
|
687
|
-
* `--framework`, else PickFrameworkPrompt, else a hard error in non-interactive
|
|
688
|
-
* mode (an agent must pass `--framework`).
|
|
689
|
-
*/
|
|
690
|
-
async function resolveScaffoldFramework(framework, nonInteractive, orca) {
|
|
691
|
-
if (framework) return framework;
|
|
692
|
-
if (nonInteractive) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", "Empty directory — pass --framework", { hint: "Run without --json/--non-interactive to choose from a prompt, or pass --framework for scripted setup." });
|
|
693
|
-
return new PickFrameworkPrompt().ask(orca.availableFrameworks());
|
|
694
|
-
}
|
|
695
|
-
/** A deterministic stand-in project for `--dry-run`, so no remote call is made. */
|
|
696
|
-
function dryRunProject(issuer) {
|
|
697
|
-
return {
|
|
698
|
-
id: "dry-run-0000",
|
|
699
|
-
projectSecret: "sk_proj_dry_run_full",
|
|
700
|
-
previewSecret: "sk_proj_dry_run_preview",
|
|
701
|
-
previewOrigins: [issuer],
|
|
702
|
-
createdAt: "2026-04-21T14:03:11.000Z"
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
async function createProjectWithLocalHint(client, server, cliVersion, issuer, framework) {
|
|
706
|
-
try {
|
|
707
|
-
return await client.createProject({ previewOrigins: [issuer] });
|
|
708
|
-
} catch (error) {
|
|
709
|
-
const normalized = toZitadelError(error);
|
|
710
|
-
throw new ZitadelError(normalized.code, normalized.message, {
|
|
711
|
-
hint: `${normalized.hint ? `${normalized.hint} ` : ""}If you meant to use a local Zitadel server, start it first and retry setup with --framework ${framework} --server local.`,
|
|
712
|
-
nextCommands: [publicCliCommand("start", cliVersion), publicCliCommand(`setup --framework ${framework} --server local`, cliVersion)],
|
|
713
|
-
details: {
|
|
714
|
-
server,
|
|
715
|
-
original: normalized.details
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
function localSetupHint(error, framework, cliVersion) {
|
|
721
|
-
const normalized = toZitadelError(error);
|
|
722
|
-
if (normalized.code !== "E_LOCAL_SERVER_NOT_RUNNING") return error;
|
|
723
|
-
const setupCommand = framework ? `setup --framework ${framework} --server local` : "setup --server local";
|
|
724
|
-
return new ZitadelError(normalized.code, normalized.message, {
|
|
725
|
-
hint: `${normalized.hint ? `${normalized.hint} ` : ""}Start local Zitadel first, then rerun setup. After setup succeeds, follow its next_commands to start the app and verify registration, logout, and login in the browser.`,
|
|
726
|
-
nextCommands: [publicCliCommand("start", cliVersion), publicCliCommand(setupCommand, cliVersion)],
|
|
727
|
-
details: normalized.details
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
/** Renders an absolute path relative to `cwd` for human-readable output. */
|
|
731
|
-
function relativeDisplay(cwd, path) {
|
|
732
|
-
return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* Replaces the user's `$HOME` with `~` in a path for compact terminal output.
|
|
736
|
-
* Falls back to the raw path when `HOME` isn't set or doesn't match.
|
|
737
|
-
*/
|
|
738
|
-
function shortPath(absolute) {
|
|
739
|
-
const home = process.env.HOME;
|
|
740
|
-
if (home && absolute.startsWith(home)) return `~${absolute.slice(home.length)}`;
|
|
741
|
-
return absolute;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Picks one written file by suffix so the corresponding INSTALLED row can
|
|
745
|
-
* reference it without hard-coding the path the patcher chose. Returns
|
|
746
|
-
* the first match; falls back to `undefined` when the patcher didn't
|
|
747
|
-
* write that artifact (e.g. a renderer without a register page).
|
|
748
|
-
*/
|
|
749
|
-
function pickWrittenFile(written, suffix) {
|
|
750
|
-
return written.find((file) => file.endsWith(suffix));
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Translates a patcher-written path into a single sentence the user can
|
|
754
|
-
* read at narration speed. Returns `null` for directories and other
|
|
755
|
-
* scaffolding artefacts that aren't worth narrating individually — the
|
|
756
|
-
* file count in the closing `success(...)` and the summary's INSTALLED
|
|
757
|
-
* section already cover them. The verb tense flips for `--dry-run` so
|
|
758
|
-
* the user sees a preview ("Would write ...") instead of a claim that
|
|
759
|
-
* something happened.
|
|
760
|
-
*/
|
|
761
|
-
function describeWrittenFile(relPath, dryRun) {
|
|
762
|
-
if (relPath === ".zitadel" || relPath === ".zitadel/flows" || relPath === ".zitadel/schemas") return null;
|
|
763
|
-
const verb = dryRun ? "Would write" : "Wrote";
|
|
764
|
-
const sentence = SENTENCE_BY_PATH[relPath];
|
|
765
|
-
if (sentence) return `${verb} ${sentence.subject} (${path(relPath)})`;
|
|
766
|
-
return `${verb} ${path(relPath)}`;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Map from the patcher's deterministic output paths to a short noun
|
|
770
|
-
* phrase describing what the file is for. Anything not in the map falls
|
|
771
|
-
* back to the bare path in the narration; add an entry here when a new
|
|
772
|
-
* scaffolded file deserves a clearer label.
|
|
773
|
-
*/
|
|
774
|
-
const SENTENCE_BY_PATH = {
|
|
775
|
-
".gitignore": { subject: "the project's .gitignore additions" },
|
|
776
|
-
".zitadel/secret": { subject: "the local project secret" },
|
|
777
|
-
"zitadel.json": { subject: "the Zitadel project configuration" },
|
|
778
|
-
".env.example": { subject: "the .env example template" },
|
|
779
|
-
".env.local": { subject: "the local development environment variables" },
|
|
780
|
-
".zitadel/state.json": { subject: "the empty sync state file" },
|
|
781
|
-
"app/page.tsx": { subject: "the auth home page" },
|
|
782
|
-
"app/login/page.tsx": { subject: "the login page" },
|
|
783
|
-
"app/register/page.tsx": { subject: "the registration page" },
|
|
784
|
-
"app/profile/page.tsx": { subject: "the profile page" },
|
|
785
|
-
"middleware.ts": { subject: "the Next.js middleware" },
|
|
786
|
-
"proxy.ts": { subject: "the Next.js proxy" },
|
|
787
|
-
"custom-elements.d.ts": { subject: "the web-component type declarations" },
|
|
788
|
-
"package.json": { subject: "package.json with the SDK dependency" }
|
|
789
|
-
};
|
|
790
|
-
/** Builds the section list driving {@link renderSummary} for the setup command. */
|
|
791
|
-
function buildSummary(opts) {
|
|
792
|
-
const { projectFacts, writtenRel, project, server, issuer, scaffoldedFramework } = opts;
|
|
793
|
-
const sdkPackage = "@zitadel/sdk-next";
|
|
794
|
-
const packageJsonHit = pickWrittenFile(writtenRel, "package.json");
|
|
795
|
-
const detected = [{
|
|
796
|
-
label: "Framework",
|
|
797
|
-
value: formatFrameworkLine(projectFacts)
|
|
798
|
-
}];
|
|
799
|
-
if (scaffoldedFramework) detected.push({
|
|
800
|
-
label: "Scaffold",
|
|
801
|
-
value: "fresh project (no existing files)"
|
|
802
|
-
});
|
|
803
|
-
const installedRows = [];
|
|
804
|
-
if (packageJsonHit) installedRows.push({
|
|
805
|
-
label: "Package",
|
|
806
|
-
value: sdkPackage,
|
|
807
|
-
secondary: path(fileNameOf(packageJsonHit))
|
|
808
|
-
});
|
|
809
|
-
for (const [label, suffix] of [
|
|
810
|
-
["Home page", "app/page.tsx"],
|
|
811
|
-
["Login page", "app/login/page.tsx"],
|
|
812
|
-
["Register page", "app/register/page.tsx"],
|
|
813
|
-
["Profile page", "app/profile/page.tsx"],
|
|
814
|
-
["Request proxy", "proxy.ts"],
|
|
815
|
-
["Middleware", "middleware.ts"],
|
|
816
|
-
["Env vars", ".env.local"]
|
|
817
|
-
]) {
|
|
818
|
-
const hit = pickWrittenFile(writtenRel, suffix);
|
|
819
|
-
if (hit) installedRows.push({
|
|
820
|
-
label,
|
|
821
|
-
value: path(hit)
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
const projectRows = [
|
|
825
|
-
{
|
|
826
|
-
label: "Project id",
|
|
827
|
-
value: id(project.id)
|
|
828
|
-
},
|
|
829
|
-
{
|
|
830
|
-
label: "Server",
|
|
831
|
-
value: url(server)
|
|
832
|
-
},
|
|
833
|
-
{
|
|
834
|
-
label: "App will run",
|
|
835
|
-
value: url(issuer)
|
|
836
|
-
}
|
|
837
|
-
];
|
|
838
|
-
return [
|
|
839
|
-
{
|
|
840
|
-
title: "Detected",
|
|
841
|
-
rows: detected
|
|
842
|
-
},
|
|
843
|
-
{
|
|
844
|
-
title: "Installed",
|
|
845
|
-
rows: installedRows
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
title: "Project",
|
|
849
|
-
rows: projectRows
|
|
850
|
-
}
|
|
851
|
-
];
|
|
852
|
-
}
|
|
853
|
-
//#endregion
|
|
854
|
-
export { Setup as default };
|
|
855
|
-
|
|
856
|
-
//# sourceMappingURL=setup.mjs.map
|