@zitadel/cli 0.1.0-alpha.0 → 0.1.0-alpha.10
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 +227 -18
- package/SKILLS.md +104 -11
- package/dist/commands/apply.mjs +4 -4
- package/dist/commands/apply.mjs.map +1 -1
- package/dist/commands/doctor.mjs +291 -17
- package/dist/commands/doctor.mjs.map +1 -1
- package/dist/commands/eject.mjs +14 -6
- package/dist/commands/eject.mjs.map +1 -1
- package/dist/commands/logs.mjs +58 -0
- package/dist/commands/logs.mjs.map +1 -0
- package/dist/commands/plan.mjs +4 -4
- package/dist/commands/plan.mjs.map +1 -1
- package/dist/commands/reset.mjs +79 -0
- package/dist/commands/reset.mjs.map +1 -0
- package/dist/commands/setup.mjs +267 -105
- package/dist/commands/setup.mjs.map +1 -1
- package/dist/commands/start.mjs +288 -0
- package/dist/commands/start.mjs.map +1 -0
- package/dist/commands/status.mjs +92 -27
- package/dist/commands/status.mjs.map +1 -1
- package/dist/commands/stop.mjs +105 -0
- package/dist/commands/stop.mjs.map +1 -0
- package/dist/docker-CnGQK3ZK.mjs +432 -0
- package/dist/docker-CnGQK3ZK.mjs.map +1 -0
- package/dist/docker-guidance-ypN3IM3o.mjs +21 -0
- package/dist/docker-guidance-ypN3IM3o.mjs.map +1 -0
- package/dist/{project-C3pSfbao.mjs → oclif-B7lBzh3R.mjs} +339 -119
- package/dist/oclif-B7lBzh3R.mjs.map +1 -0
- package/dist/orca-BoTFU8SI.mjs +2581 -0
- package/dist/orca-BoTFU8SI.mjs.map +1 -0
- package/dist/ports-B09RjuHx.mjs +111 -0
- package/dist/ports-B09RjuHx.mjs.map +1 -0
- package/dist/processes-Cw8TO1SY.mjs +120 -0
- package/dist/processes-Cw8TO1SY.mjs.map +1 -0
- package/dist/project-Cd0L3PtM.mjs +87 -0
- package/dist/project-Cd0L3PtM.mjs.map +1 -0
- package/dist/{sync-Cuyh-X1J.mjs → sync-BojoQm2P.mjs} +4 -4
- package/dist/{sync-Cuyh-X1J.mjs.map → sync-BojoQm2P.mjs.map} +1 -1
- package/oclif.manifest.json +399 -7
- package/package.json +8 -41
- package/dist/orca-COsUnVoz.mjs +0 -1006
- package/dist/orca-COsUnVoz.mjs.map +0 -1
- package/dist/project-C3pSfbao.mjs.map +0 -1
package/dist/commands/setup.mjs
CHANGED
|
@@ -1,13 +1,180 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
3
|
-
import {
|
|
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-BoTFU8SI.mjs";
|
|
3
|
+
import { n as hasZitadelSecret, t as hasZitadelConfig } from "../project-Cd0L3PtM.mjs";
|
|
4
|
+
import { t as listListeningPorts } from "../ports-B09RjuHx.mjs";
|
|
4
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";
|
|
5
8
|
import { consola as consola$1 } from "consola";
|
|
6
|
-
import { readFile, stat } from "node:fs/promises";
|
|
7
9
|
import { basename, join } from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { readFile, stat } from "node:fs/promises";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
10
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
|
|
11
178
|
//#region src/commands/setup/prompts/cancel.ts
|
|
12
179
|
/**
|
|
13
180
|
* Converts a clack cancellation (Ctrl-C) into a thrown `E_VALIDATION` rather
|
|
@@ -27,7 +194,8 @@ function bail(value) {
|
|
|
27
194
|
* becomes the issuer URL (`http://localhost:<port>`) via `issuerFromPort`.
|
|
28
195
|
*/
|
|
29
196
|
var DevPortPrompt = class {
|
|
30
|
-
async ask(answers,
|
|
197
|
+
async ask(answers, ctx) {
|
|
198
|
+
if (ctx.devPortFromFlag) return answers;
|
|
31
199
|
const value = await text({
|
|
32
200
|
message: "Dev server port",
|
|
33
201
|
placeholder: String(answers.devPort),
|
|
@@ -63,79 +231,6 @@ var FrameworkConfirmPrompt = class {
|
|
|
63
231
|
}
|
|
64
232
|
};
|
|
65
233
|
//#endregion
|
|
66
|
-
//#region src/lib/prober/ports.ts
|
|
67
|
-
/**
|
|
68
|
-
* Enumerate TCP ports currently in LISTEN state on the loopback interface
|
|
69
|
-
* (`127.0.0.1`, `::1`, or the wildcard `*`). Spawns `lsof -iTCP -sTCP:LISTEN
|
|
70
|
-
* -P -n -F n` and parses its machine-readable output. Returns the unique,
|
|
71
|
-
* numerically-sorted list of ports.
|
|
72
|
-
*
|
|
73
|
-
* Never throws. Returns `[]` whenever lsof is unavailable (e.g. Windows,
|
|
74
|
-
* unusual PATH), the spawn errors, exits non-zero, or the call exceeds
|
|
75
|
-
* `timeoutMs` (default 1000ms). The caller treats an empty list the same as
|
|
76
|
-
* "no listeners worth probing."
|
|
77
|
-
*/
|
|
78
|
-
async function listListeningPorts(opts) {
|
|
79
|
-
const timeoutMs = opts?.timeoutMs ?? 1e3;
|
|
80
|
-
try {
|
|
81
|
-
return parseLsofPorts(await runLsof(timeoutMs));
|
|
82
|
-
} catch {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Spawn `lsof` with the canned argv and resolve to its stdout. Hand-rolled
|
|
88
|
-
* rather than `util.promisify(execFile)` because the latter resolves with a
|
|
89
|
-
* `{stdout, stderr}` object via its custom-promisify symbol — a heavier shape
|
|
90
|
-
* to mock and worse to read at the call site, where we only ever want stdout.
|
|
91
|
-
*/
|
|
92
|
-
function runLsof(timeoutMs) {
|
|
93
|
-
return new Promise((resolve, reject) => {
|
|
94
|
-
execFile("lsof", [
|
|
95
|
-
"-iTCP",
|
|
96
|
-
"-sTCP:LISTEN",
|
|
97
|
-
"-P",
|
|
98
|
-
"-n",
|
|
99
|
-
"-F",
|
|
100
|
-
"n"
|
|
101
|
-
], {
|
|
102
|
-
timeout: timeoutMs,
|
|
103
|
-
encoding: "utf8"
|
|
104
|
-
}, (err, stdout) => {
|
|
105
|
-
if (err) {
|
|
106
|
-
reject(err);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
resolve(stdout);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Parse the `n` records emitted by `lsof -F n`. Each record is a single line
|
|
115
|
-
* `n<address>` where `<address>` ends in `:<port>` (e.g. `n*:8080`,
|
|
116
|
-
* `n127.0.0.1:3000`, `n[::1]:5050`). Only loopback/wildcard hosts are kept;
|
|
117
|
-
* external interface bindings are ignored.
|
|
118
|
-
*/
|
|
119
|
-
function parseLsofPorts(stdout) {
|
|
120
|
-
const ports = /* @__PURE__ */ new Set();
|
|
121
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
122
|
-
if (!line.startsWith("n")) continue;
|
|
123
|
-
const address = line.slice(1);
|
|
124
|
-
const colon = address.lastIndexOf(":");
|
|
125
|
-
if (colon < 0) continue;
|
|
126
|
-
const host = address.slice(0, colon);
|
|
127
|
-
const portStr = address.slice(colon + 1);
|
|
128
|
-
if (!isLoopback(host)) continue;
|
|
129
|
-
const port = Number.parseInt(portStr, 10);
|
|
130
|
-
if (Number.isFinite(port) && port > 0 && port < 65536) ports.add(port);
|
|
131
|
-
}
|
|
132
|
-
return [...ports].sort((a, b) => a - b);
|
|
133
|
-
}
|
|
134
|
-
/** Recognised loopback host strings as emitted by `lsof -F n`. */
|
|
135
|
-
function isLoopback(host) {
|
|
136
|
-
return host === "*" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
234
|
//#region src/lib/prober/http.ts
|
|
140
235
|
/**
|
|
141
236
|
* Fetch `url` with a per-call timeout and pass the resulting `Response` to
|
|
@@ -345,9 +440,8 @@ const url = (s) => pc.cyan(s);
|
|
|
345
440
|
const id = (s) => pc.yellow(s);
|
|
346
441
|
/**
|
|
347
442
|
* Reads the project root to identify the framework version, TS presence,
|
|
348
|
-
* and which package manager the user runs. Returns
|
|
349
|
-
*
|
|
350
|
-
* always proceed.
|
|
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.
|
|
351
445
|
*/
|
|
352
446
|
async function detectProjectFacts(cwd, frameworkId) {
|
|
353
447
|
const facts = {
|
|
@@ -370,16 +464,9 @@ function formatFrameworkLine(facts) {
|
|
|
370
464
|
const pretty = prettyFramework(facts.framework);
|
|
371
465
|
segments.push(facts.frameworkVersion ? `${pretty} ${facts.frameworkVersion}` : pretty);
|
|
372
466
|
if (facts.typescript) segments.push("TypeScript");
|
|
373
|
-
|
|
467
|
+
segments.push(facts.packageManager);
|
|
374
468
|
return segments.join(` ${pc.dim("·")} `);
|
|
375
469
|
}
|
|
376
|
-
async function detectPackageManager(cwd) {
|
|
377
|
-
if (await fileExists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
378
|
-
if (await fileExists(join(cwd, "yarn.lock"))) return "yarn";
|
|
379
|
-
if (await fileExists(join(cwd, "bun.lockb"))) return "bun";
|
|
380
|
-
if (await fileExists(join(cwd, "package-lock.json"))) return "npm";
|
|
381
|
-
return "unknown";
|
|
382
|
-
}
|
|
383
470
|
async function fileExists(p) {
|
|
384
471
|
try {
|
|
385
472
|
await stat(p);
|
|
@@ -434,7 +521,7 @@ const FRAMEWORK_OPTIONS = createOrca().availableFrameworks().map((framework) =>
|
|
|
434
521
|
*/
|
|
435
522
|
var Setup = class Setup extends BaseCommand {
|
|
436
523
|
static description = "Create a Zitadel project and scaffold local auth.";
|
|
437
|
-
static examples = ["<%= config.bin %> setup --framework next"];
|
|
524
|
+
static examples = ["<%= config.bin %> setup --framework next", "<%= config.bin %> setup --framework react --dev-port 3000"];
|
|
438
525
|
static flags = {
|
|
439
526
|
framework: Flags.string({
|
|
440
527
|
description: "Framework to target.",
|
|
@@ -443,11 +530,17 @@ var Setup = class Setup extends BaseCommand {
|
|
|
443
530
|
renderer: Flags.string({
|
|
444
531
|
description: "Renderer (default: react).",
|
|
445
532
|
options: [...RENDERER_IDS]
|
|
446
|
-
})
|
|
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." })
|
|
447
536
|
};
|
|
448
537
|
async run() {
|
|
449
538
|
const { flags } = await this.parse(Setup);
|
|
450
|
-
|
|
539
|
+
try {
|
|
540
|
+
await this.toMeta(flags);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw localSetupHint(error, flags.framework, this.config.version);
|
|
543
|
+
}
|
|
451
544
|
const { cwd, nonInteractive, dryRun, force } = this.meta;
|
|
452
545
|
if (await hasZitadelConfig(cwd)) return this.emit({
|
|
453
546
|
status: "skipped",
|
|
@@ -462,13 +555,24 @@ var Setup = class Setup extends BaseCommand {
|
|
|
462
555
|
framework = await orca.detect(cwd, flags.framework);
|
|
463
556
|
consola$1.success(`Detected ${framework.id}${framework.devPort ? ` (dev port ${framework.devPort})` : ""}`);
|
|
464
557
|
} catch (error) {
|
|
465
|
-
if (error instanceof ZitadelError && error.code === "E_FRAMEWORK_NOT_DETECTED"
|
|
466
|
-
|
|
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");
|
|
467
562
|
framework = await orca.scaffold(cwd, await resolveScaffoldFramework(flags.framework, nonInteractive, orca));
|
|
468
563
|
scaffoldedFramework = true;
|
|
469
564
|
consola$1.success(`Scaffolded ${framework.id} skeleton`);
|
|
470
565
|
} else throw error;
|
|
471
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
|
+
}
|
|
472
576
|
let answers = {
|
|
473
577
|
server: this.meta.source,
|
|
474
578
|
devPort: framework.devPort
|
|
@@ -477,22 +581,30 @@ var Setup = class Setup extends BaseCommand {
|
|
|
477
581
|
intro("Zitadel setup");
|
|
478
582
|
const promptCtx = {
|
|
479
583
|
framework,
|
|
480
|
-
serverFlag: this.meta.serverFlag
|
|
584
|
+
serverFlag: this.meta.serverFlag,
|
|
585
|
+
devPortFromFlag: flags["dev-port"] !== void 0
|
|
481
586
|
};
|
|
482
587
|
for (const prompt of SETUP_PROMPTS) answers = await prompt.ask(answers, promptCtx);
|
|
483
588
|
outro("Configuration captured");
|
|
484
589
|
}
|
|
485
590
|
const issuer = issuerFromPort(answers.devPort);
|
|
591
|
+
framework = {
|
|
592
|
+
...framework,
|
|
593
|
+
devPort: answers.devPort,
|
|
594
|
+
url: issuer
|
|
595
|
+
};
|
|
486
596
|
consola$1.start(`Creating project on ${answers.server}${dryRun ? " (dry run)" : ""}`);
|
|
487
597
|
const unauthClient = createZitadelClient({ baseUrl: answers.server });
|
|
488
|
-
const project = dryRun ? dryRunProject() : await unauthClient.
|
|
598
|
+
const project = dryRun ? dryRunProject(issuer) : await createProjectWithLocalHint(unauthClient, answers.server, this.meta.cliVersion, issuer, framework.id);
|
|
489
599
|
consola$1.success(`Created project ${project.id}`);
|
|
490
600
|
const ctx = {
|
|
491
601
|
framework,
|
|
492
602
|
rendererId: flags.renderer ?? "react",
|
|
493
603
|
project,
|
|
494
604
|
issuer,
|
|
495
|
-
server: answers.server
|
|
605
|
+
server: answers.server,
|
|
606
|
+
cliVersion: this.meta.cliVersion,
|
|
607
|
+
scaffoldedFramework
|
|
496
608
|
};
|
|
497
609
|
consola$1.start(`Patching project files${dryRun ? " (dry run)" : ""}`);
|
|
498
610
|
const result = await orca.patcherFor(framework.id).patch(ctx, {
|
|
@@ -506,6 +618,16 @@ var Setup = class Setup extends BaseCommand {
|
|
|
506
618
|
}
|
|
507
619
|
for (const file of result.filesSkipped) consola$1.info(`Left ${relativeDisplay(cwd, file)} unchanged (already matches target)`);
|
|
508
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
|
+
});
|
|
509
631
|
const writtenRel = result.filesWritten.map((file) => relativeDisplay(cwd, file));
|
|
510
632
|
if (!this.jsonEnabled()) {
|
|
511
633
|
const sections = buildSummary({
|
|
@@ -521,7 +643,7 @@ var Setup = class Setup extends BaseCommand {
|
|
|
521
643
|
message: [
|
|
522
644
|
renderSummary(sections),
|
|
523
645
|
"",
|
|
524
|
-
|
|
646
|
+
installOutcome.nextActions.join("\n")
|
|
525
647
|
].join("\n"),
|
|
526
648
|
style: {
|
|
527
649
|
padding: 1,
|
|
@@ -543,12 +665,23 @@ var Setup = class Setup extends BaseCommand {
|
|
|
543
665
|
server: answers.server,
|
|
544
666
|
files_written: result.filesWritten.map((file) => relativeDisplay(cwd, file)),
|
|
545
667
|
files_skipped: result.filesSkipped.map((file) => relativeDisplay(cwd, file)),
|
|
546
|
-
|
|
547
|
-
|
|
668
|
+
install: installOutcome.install,
|
|
669
|
+
next_actions: installOutcome.nextActions,
|
|
670
|
+
next_commands: installOutcome.nextCommands
|
|
548
671
|
}
|
|
549
672
|
});
|
|
550
673
|
}
|
|
551
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
|
+
}
|
|
552
685
|
/**
|
|
553
686
|
* Resolves which framework to scaffold into an empty directory: the explicit
|
|
554
687
|
* `--framework`, else PickFrameworkPrompt, else a hard error in non-interactive
|
|
@@ -556,19 +689,44 @@ var Setup = class Setup extends BaseCommand {
|
|
|
556
689
|
*/
|
|
557
690
|
async function resolveScaffoldFramework(framework, nonInteractive, orca) {
|
|
558
691
|
if (framework) return framework;
|
|
559
|
-
if (nonInteractive) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", "Empty directory — pass --framework", { hint: "
|
|
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." });
|
|
560
693
|
return new PickFrameworkPrompt().ask(orca.availableFrameworks());
|
|
561
694
|
}
|
|
562
695
|
/** A deterministic stand-in project for `--dry-run`, so no remote call is made. */
|
|
563
|
-
function dryRunProject() {
|
|
696
|
+
function dryRunProject(issuer) {
|
|
564
697
|
return {
|
|
565
698
|
id: "dry-run-0000",
|
|
566
699
|
projectSecret: "sk_proj_dry_run_full",
|
|
567
700
|
previewSecret: "sk_proj_dry_run_preview",
|
|
568
|
-
previewOrigins: [],
|
|
701
|
+
previewOrigins: [issuer],
|
|
569
702
|
createdAt: "2026-04-21T14:03:11.000Z"
|
|
570
703
|
};
|
|
571
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
|
+
}
|
|
572
730
|
/** Renders an absolute path relative to `cwd` for human-readable output. */
|
|
573
731
|
function relativeDisplay(cwd, path) {
|
|
574
732
|
return path.startsWith(cwd) ? path.slice(cwd.length + 1) : path;
|
|
@@ -620,10 +778,12 @@ const SENTENCE_BY_PATH = {
|
|
|
620
778
|
".env.example": { subject: "the .env example template" },
|
|
621
779
|
".env.local": { subject: "the local development environment variables" },
|
|
622
780
|
".zitadel/state.json": { subject: "the empty sync state file" },
|
|
781
|
+
"app/page.tsx": { subject: "the auth home page" },
|
|
623
782
|
"app/login/page.tsx": { subject: "the login page" },
|
|
624
783
|
"app/register/page.tsx": { subject: "the registration page" },
|
|
625
784
|
"app/profile/page.tsx": { subject: "the profile page" },
|
|
626
785
|
"middleware.ts": { subject: "the Next.js middleware" },
|
|
786
|
+
"proxy.ts": { subject: "the Next.js proxy" },
|
|
627
787
|
"custom-elements.d.ts": { subject: "the web-component type declarations" },
|
|
628
788
|
"package.json": { subject: "package.json with the SDK dependency" }
|
|
629
789
|
};
|
|
@@ -647,9 +807,11 @@ function buildSummary(opts) {
|
|
|
647
807
|
secondary: path(fileNameOf(packageJsonHit))
|
|
648
808
|
});
|
|
649
809
|
for (const [label, suffix] of [
|
|
810
|
+
["Home page", "app/page.tsx"],
|
|
650
811
|
["Login page", "app/login/page.tsx"],
|
|
651
812
|
["Register page", "app/register/page.tsx"],
|
|
652
813
|
["Profile page", "app/profile/page.tsx"],
|
|
814
|
+
["Request proxy", "proxy.ts"],
|
|
653
815
|
["Middleware", "middleware.ts"],
|
|
654
816
|
["Env vars", ".env.local"]
|
|
655
817
|
]) {
|