@zitadel/cli 0.1.0-alpha.1 → 0.1.0-alpha.11

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.
Files changed (45) hide show
  1. package/README.md +116 -26
  2. package/SKILLS.md +102 -24
  3. package/dist/commands/apply.mjs +4 -5
  4. package/dist/commands/apply.mjs.map +1 -1
  5. package/dist/commands/doctor.mjs +214 -50
  6. package/dist/commands/doctor.mjs.map +1 -1
  7. package/dist/commands/eject.mjs +14 -7
  8. package/dist/commands/eject.mjs.map +1 -1
  9. package/dist/commands/logs.mjs +15 -7
  10. package/dist/commands/logs.mjs.map +1 -1
  11. package/dist/commands/plan.mjs +4 -5
  12. package/dist/commands/plan.mjs.map +1 -1
  13. package/dist/commands/reset.mjs +28 -12
  14. package/dist/commands/reset.mjs.map +1 -1
  15. package/dist/commands/setup.mjs +266 -106
  16. package/dist/commands/setup.mjs.map +1 -1
  17. package/dist/commands/start.mjs +180 -24
  18. package/dist/commands/start.mjs.map +1 -1
  19. package/dist/commands/status.mjs +40 -16
  20. package/dist/commands/status.mjs.map +1 -1
  21. package/dist/commands/stop.mjs +72 -11
  22. package/dist/commands/stop.mjs.map +1 -1
  23. package/dist/docker-CnGQK3ZK.mjs +432 -0
  24. package/dist/docker-CnGQK3ZK.mjs.map +1 -0
  25. package/dist/docker-guidance-ypN3IM3o.mjs +21 -0
  26. package/dist/docker-guidance-ypN3IM3o.mjs.map +1 -0
  27. package/dist/{oclif-DSPO9Sck.mjs → oclif-B7lBzh3R.mjs} +158 -63
  28. package/dist/oclif-B7lBzh3R.mjs.map +1 -0
  29. package/dist/orca-mzcHxDvu.mjs +3214 -0
  30. package/dist/orca-mzcHxDvu.mjs.map +1 -0
  31. package/dist/ports-B09RjuHx.mjs +111 -0
  32. package/dist/ports-B09RjuHx.mjs.map +1 -0
  33. package/dist/processes-Cw8TO1SY.mjs +120 -0
  34. package/dist/processes-Cw8TO1SY.mjs.map +1 -0
  35. package/dist/{project-Dwb9WVAT.mjs → project-Cd0L3PtM.mjs} +3 -3
  36. package/dist/{project-Dwb9WVAT.mjs.map → project-Cd0L3PtM.mjs.map} +1 -1
  37. package/dist/{sync-DmtTYNmq.mjs → sync-BojoQm2P.mjs} +3 -3
  38. package/dist/{sync-DmtTYNmq.mjs.map → sync-BojoQm2P.mjs.map} +1 -1
  39. package/oclif.manifest.json +52 -5
  40. package/package.json +8 -41
  41. package/dist/docker-C0aVpJqm.mjs +0 -209
  42. package/dist/docker-C0aVpJqm.mjs.map +0 -1
  43. package/dist/oclif-DSPO9Sck.mjs.map +0 -1
  44. package/dist/orca-DOxshV9n.mjs +0 -1009
  45. package/dist/orca-DOxshV9n.mjs.map +0 -1
@@ -0,0 +1,3214 @@
1
+ import { C as isObject, E as ZitadelError, T as stableStringify, n as DEFAULT_SERVER, w as parseJsonObject, x as MANAGED_MARKER, y as npmDistTagForCliVersion } from "./oclif-B7lBzh3R.mjs";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { builders, generateCode, parseModule } from "magicast";
5
+ import { spawnSync } from "node:child_process";
6
+ //#region src/lib/orca/detectors/package-json.ts
7
+ /**
8
+ * Reads and parses the `package.json` at `cwd`. Rejects if the file is absent
9
+ * or malformed; callers that treat those as "not a project" are expected to
10
+ * catch and fall back rather than have detection swallow the error here.
11
+ */
12
+ async function readPackageJson(cwd) {
13
+ const contents = await readFile(join(cwd, "package.json"), "utf8");
14
+ return JSON.parse(contents);
15
+ }
16
+ /**
17
+ * Reports whether `name` appears in either `dependencies` or `devDependencies`.
18
+ * Both are checked because framework packages may legitimately live in either.
19
+ */
20
+ function hasDependency(pkg, name) {
21
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
22
+ }
23
+ function dependencyVersionMajor(pkg, name) {
24
+ const spec = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
25
+ if (!spec) return;
26
+ const match = spec.match(/\d+/);
27
+ return match ? Number(match[0]) : void 0;
28
+ }
29
+ //#endregion
30
+ //#region src/lib/orca/detectors/port.ts
31
+ /**
32
+ * Port assumed when no explicit dev port can be discovered. Matches Next.js's
33
+ * own default so the inferred issuer URL lines up with `next dev`.
34
+ */
35
+ const DEFAULT_DEV_PORT = 3e3;
36
+ /**
37
+ * Determines the local dev-server port for `cwd`. The `dev` script is the most
38
+ * authoritative source, then a `PORT` declaration in an env file, falling back
39
+ * to {@link DEFAULT_DEV_PORT}. Used to derive the local issuer URL.
40
+ */
41
+ async function detectDevPort(cwd, pkg) {
42
+ const dev = pkg.scripts?.dev;
43
+ const fromScript = typeof dev === "string" ? extractPort(dev) : void 0;
44
+ if (fromScript) return fromScript;
45
+ const fromEnvFile = await portFromEnvFile(cwd);
46
+ if (fromEnvFile) return fromEnvFile;
47
+ return DEFAULT_DEV_PORT;
48
+ }
49
+ async function portFromEnvFile(cwd) {
50
+ for (const candidate of [".env.local", ".env"]) try {
51
+ const rawPort = (await readFile(join(cwd, candidate), "utf8")).match(/^\s*PORT\s*=\s*(\d+)/m)?.[1];
52
+ if (rawPort) return Number.parseInt(rawPort, 10);
53
+ } catch {
54
+ continue;
55
+ }
56
+ }
57
+ /**
58
+ * Parses a dev port out of an npm `dev` script string, recognizing both flag
59
+ * forms (`-p`/`--port`) and a leading `PORT=` env assignment. Returns
60
+ * `undefined` when no valid positive port is present so callers can fall back.
61
+ */
62
+ function extractPort(script) {
63
+ const inline = script.match(/-p\s+(\d+)|--port[=\s]+(\d+)/);
64
+ if (inline) {
65
+ const raw = inline[1] ?? inline[2];
66
+ if (!raw) return;
67
+ const value = Number.parseInt(raw, 10);
68
+ if (Number.isFinite(value) && value > 0) return value;
69
+ }
70
+ const rawEnvPort = script.match(/(?:^|\s)PORT=(\d+)/)?.[1];
71
+ if (rawEnvPort) return Number.parseInt(rawEnvPort, 10);
72
+ }
73
+ /**
74
+ * Builds the local OIDC issuer URL for a given dev port. Centralized so the
75
+ * `localhost` origin convention is defined in exactly one place.
76
+ */
77
+ function issuerFromPort(port) {
78
+ return `http://localhost:${port}`;
79
+ }
80
+ //#endregion
81
+ //#region src/lib/orca/detectors/angular.ts
82
+ /** The lowest Angular major the generated templates compile on. */
83
+ const MIN_ANGULAR_MAJOR = 17;
84
+ /** Best-effort major version from a dependency range (`^17.3.0` → 17). */
85
+ function angularMajor(spec) {
86
+ const match = spec.match(/\d+/);
87
+ return match ? Number.parseInt(match[0], 10) : void 0;
88
+ }
89
+ /**
90
+ * Detects an Angular project by its `@angular/core` dependency. The app dir is
91
+ * `src/app` (where the patcher writes `app.ts`/`app.html`), the dev port comes
92
+ * from the project (else the default), and the issuer is derived from it.
93
+ * Angular's dev server (`@angular/build:dev-server`)
94
+ * is Vite-based but configured via `angular.json` + a `proxy.conf.cjs`, not a
95
+ * `vite.config.ts` — handled by the Angular patcher.
96
+ *
97
+ * The managed templates use Angular 17+ control flow (`@if`), so a clearly
98
+ * older project fails fast with `E_VALIDATION` instead of scaffolding files
99
+ * that won't compile.
100
+ */
101
+ var AngularDetector = class {
102
+ framework = "angular";
103
+ async detect(cwd) {
104
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
105
+ if (!pkg || !hasDependency(pkg, "@angular/core")) return null;
106
+ const spec = pkg.dependencies?.["@angular/core"] ?? pkg.devDependencies?.["@angular/core"];
107
+ const major = spec ? angularMajor(spec) : void 0;
108
+ if (major !== void 0 && major < MIN_ANGULAR_MAJOR) throw new ZitadelError("E_VALIDATION", `Angular ${major} is unsupported — the generated auth templates use Angular ${MIN_ANGULAR_MAJOR}+ control flow (@if).`, { hint: `Upgrade to Angular ${MIN_ANGULAR_MAJOR} or newer before running setup, or add the Zitadel components to your templates manually.` });
109
+ const devPort = await detectDevPort(cwd, pkg);
110
+ return {
111
+ id: "angular",
112
+ appDir: "src/app",
113
+ devPort,
114
+ url: issuerFromPort(devPort)
115
+ };
116
+ }
117
+ };
118
+ //#endregion
119
+ //#region src/lib/orca/detectors/next.ts
120
+ /**
121
+ * Detects a Next.js App Router project and extracts its facts: the App Router
122
+ * directory (`app` vs `src/app`), the dev-server port (parsed from the `dev`
123
+ * script / env file, else 3000), and the derived local issuer URL. Owns every
124
+ * Next-specific assumption so the orchestrator and commands stay generic.
125
+ */
126
+ var NextDetector = class {
127
+ framework = "next";
128
+ /**
129
+ * Returns `null` when `cwd` is not a Next.js project (no `next` dependency),
130
+ * so the orchestrator can try other detectors. Throws
131
+ * `E_UNSUPPORTED_PROJECT_SHAPE` when it is Next.js but lacks an App Router
132
+ * directory (e.g. a Pages Router project), which is a hard error rather than
133
+ * an empty directory to scaffold.
134
+ */
135
+ async detect(cwd) {
136
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
137
+ if (!pkg || !hasDependency(pkg, "next")) return null;
138
+ const appDir = await dirExists$1(join(cwd, "app")) ? "app" : await dirExists$1(join(cwd, "src/app")) ? "src/app" : void 0;
139
+ if (!appDir) throw new ZitadelError("E_UNSUPPORTED_PROJECT_SHAPE", "Next.js Pages Router projects are not supported in v1", { hint: "Create an App Router project with an app/ or src/app/ directory." });
140
+ const devPort = await detectDevPort(cwd, pkg);
141
+ return {
142
+ id: "next",
143
+ appDir,
144
+ devPort,
145
+ url: issuerFromPort(devPort),
146
+ versionMajor: dependencyVersionMajor(pkg, "next")
147
+ };
148
+ }
149
+ };
150
+ async function dirExists$1(path) {
151
+ try {
152
+ return (await stat(path)).isDirectory();
153
+ } catch (error) {
154
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return false;
155
+ throw error;
156
+ }
157
+ }
158
+ //#endregion
159
+ //#region src/lib/orca/detectors/nuxt.ts
160
+ /**
161
+ * Detects a Nuxt project by its `nuxt` dependency. Like Next.js, Nuxt proxies
162
+ * the auth backend through server middleware (`@zitadel/sdk-nuxt`), not a Vite
163
+ * dev-server proxy — so the patcher wires the module + a `nuxt.config.ts` edit.
164
+ * Runs before the Vue detector (which excludes Nuxt) since Nuxt ships Vue.
165
+ *
166
+ * `appDir` tracks the Nuxt srcDir: Nuxt 4 (what `nuxi init` now scaffolds) keeps
167
+ * `app.vue`/`pages/`/`plugins/` under `app/`, while Nuxt 3 keeps them at the
168
+ * root — so the patcher writes its files relative to whichever this project uses.
169
+ */
170
+ var NuxtDetector = class {
171
+ framework = "nuxt";
172
+ async detect(cwd) {
173
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
174
+ if (!pkg || !hasDependency(pkg, "nuxt")) return null;
175
+ const appDir = await dirExists(join(cwd, "app")) ? "app" : ".";
176
+ const devPort = await detectDevPort(cwd, pkg);
177
+ return {
178
+ id: "nuxt",
179
+ appDir,
180
+ devPort,
181
+ url: issuerFromPort(devPort)
182
+ };
183
+ }
184
+ };
185
+ async function dirExists(path) {
186
+ return stat(path).then((s) => s.isDirectory(), () => false);
187
+ }
188
+ //#endregion
189
+ //#region src/lib/orca/detectors/qwik.ts
190
+ /**
191
+ * Detects a Vite + Qwik single-page app: depends on `@builder.io/qwik` and
192
+ * `vite` but NOT `@builder.io/qwik-city` (Qwik City is its own meta-framework
193
+ * and ships Qwik). The source dir is `src`, the dev port comes from the
194
+ * project, and the issuer is derived from it.
195
+ */
196
+ var QwikDetector = class {
197
+ framework = "qwik";
198
+ async detect(cwd) {
199
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
200
+ if (!pkg || hasDependency(pkg, "@builder.io/qwik-city") || !hasDependency(pkg, "@builder.io/qwik") || !hasDependency(pkg, "vite")) return null;
201
+ const devPort = await detectDevPort(cwd, pkg);
202
+ return {
203
+ id: "qwik",
204
+ appDir: "src",
205
+ devPort,
206
+ url: issuerFromPort(devPort)
207
+ };
208
+ }
209
+ };
210
+ //#endregion
211
+ //#region src/lib/orca/detectors/react.ts
212
+ /**
213
+ * Detects a Vite + React single-page app and extracts its facts: the source
214
+ * directory (`src`), the dev-server port (parsed from the `dev` script / env
215
+ * file, else the framework default), and the derived local issuer URL.
216
+ *
217
+ * Recognises a project that depends on both `react` and `vite` but NOT `next`
218
+ * — Next.js ships React too, so the {@link import("./next").NextDetector} must
219
+ * run first (and does, by registry order) and this detector excludes it.
220
+ */
221
+ var ReactDetector = class {
222
+ framework = "react";
223
+ async detect(cwd) {
224
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
225
+ if (!pkg || hasDependency(pkg, "next") || !hasDependency(pkg, "react") || !hasDependency(pkg, "vite")) return null;
226
+ const devPort = await detectDevPort(cwd, pkg);
227
+ return {
228
+ id: "react",
229
+ appDir: "src",
230
+ devPort,
231
+ url: issuerFromPort(devPort)
232
+ };
233
+ }
234
+ };
235
+ //#endregion
236
+ //#region src/lib/orca/detectors/solid.ts
237
+ /**
238
+ * Detects a Vite + Solid single-page app: depends on `solid-js` and `vite` but
239
+ * NOT `@solidjs/start` (SolidStart is its own meta-framework and ships Solid).
240
+ * The source dir is `src`, the dev port comes from the project, and the issuer
241
+ * is derived from it.
242
+ */
243
+ var SolidDetector = class {
244
+ framework = "solid";
245
+ async detect(cwd) {
246
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
247
+ if (!pkg || hasDependency(pkg, "@solidjs/start") || !hasDependency(pkg, "solid-js") || !hasDependency(pkg, "vite")) return null;
248
+ const devPort = await detectDevPort(cwd, pkg);
249
+ return {
250
+ id: "solid",
251
+ appDir: "src",
252
+ devPort,
253
+ url: issuerFromPort(devPort)
254
+ };
255
+ }
256
+ };
257
+ //#endregion
258
+ //#region src/lib/orca/detectors/svelte.ts
259
+ /**
260
+ * Detects a Vite + Svelte single-page app: depends on `svelte` and `vite` but
261
+ * NOT `@sveltejs/kit` (SvelteKit is its own meta-framework and ships Svelte).
262
+ * The source dir is `src`, the dev port comes from the project, and the issuer
263
+ * is derived from it.
264
+ */
265
+ var SvelteDetector = class {
266
+ framework = "svelte";
267
+ async detect(cwd) {
268
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
269
+ if (!pkg || hasDependency(pkg, "@sveltejs/kit") || !hasDependency(pkg, "svelte") || !hasDependency(pkg, "vite")) return null;
270
+ const devPort = await detectDevPort(cwd, pkg);
271
+ return {
272
+ id: "svelte",
273
+ appDir: "src",
274
+ devPort,
275
+ url: issuerFromPort(devPort)
276
+ };
277
+ }
278
+ };
279
+ //#endregion
280
+ //#region src/lib/orca/detectors/vue.ts
281
+ /**
282
+ * Detects a Vite + Vue single-page app: depends on `vue` and `vite` but NOT
283
+ * `nuxt` (Nuxt is its own meta-framework and ships Vue) — so the source dir is
284
+ * `src`, the dev port comes from the project, and the issuer is derived from it.
285
+ */
286
+ var VueDetector = class {
287
+ framework = "vue";
288
+ async detect(cwd) {
289
+ const pkg = await readPackageJson(cwd).catch(() => void 0);
290
+ if (!pkg || hasDependency(pkg, "nuxt") || !hasDependency(pkg, "vue") || !hasDependency(pkg, "vite")) return null;
291
+ const devPort = await detectDevPort(cwd, pkg);
292
+ return {
293
+ id: "vue",
294
+ appDir: "src",
295
+ devPort,
296
+ url: issuerFromPort(devPort)
297
+ };
298
+ }
299
+ };
300
+ //#endregion
301
+ //#region src/lib/orca/detectors/index.ts
302
+ /**
303
+ * Active detectors, in probe order. The orchestrator tries each until one
304
+ * recognises the project. Add a framework by appending its detector here — no
305
+ * orchestrator changes needed. Meta-frameworks run before their base: Next
306
+ * before React (Next ships React), and the Vue detector excludes Nuxt — so a
307
+ * Next/Nuxt project is never mistaken for a bare React/Vue SPA.
308
+ */
309
+ const detectors = [
310
+ new NextDetector(),
311
+ new NuxtDetector(),
312
+ new ReactDetector(),
313
+ new VueDetector(),
314
+ new SolidDetector(),
315
+ new SvelteDetector(),
316
+ new QwikDetector(),
317
+ new AngularDetector()
318
+ ];
319
+ //#endregion
320
+ //#region src/lib/orca/patchers/rule/file-writer/index.ts
321
+ /**
322
+ * Applies a {@link ScaffoldPlan} to disk, executing its operations in order.
323
+ *
324
+ * Operations are idempotent: writes whose target already matches the desired
325
+ * contents are recorded as skipped rather than rewritten, so re-running setup
326
+ * is safe. With `dryRun` no filesystem changes are made but the result still
327
+ * reflects what would have been written. Existing files are only overwritten
328
+ * when `force` is set; otherwise an `E_CONFLICT` is thrown to protect
329
+ * user-authored content. Paths in the plan are resolved relative to `cwd`.
330
+ */
331
+ async function scaffold(plan, opts) {
332
+ const result = {
333
+ dryRun: opts.dryRun,
334
+ filesWritten: [],
335
+ filesSkipped: [],
336
+ depsAdded: []
337
+ };
338
+ for (const op of plan.ops) await applyOp(op, opts, result);
339
+ return result;
340
+ }
341
+ async function applyOp(op, opts, result) {
342
+ switch (op.kind) {
343
+ case "mkdir":
344
+ await ensureDir(abs(opts.cwd, op.path), op.mode, opts.dryRun, result);
345
+ break;
346
+ case "write":
347
+ await writeText(abs(opts.cwd, op.path), op.contents, {
348
+ mode: op.mode,
349
+ force: opts.force,
350
+ dryRun: opts.dryRun
351
+ }, result);
352
+ break;
353
+ case "append":
354
+ await appendText(abs(opts.cwd, op.path), op.contents, op.ifMissing, opts.dryRun, result);
355
+ break;
356
+ case "merge-env":
357
+ await mergeEnv(abs(opts.cwd, op.path), op.entries, opts.dryRun, result);
358
+ break;
359
+ case "merge-json":
360
+ await mergeJson(abs(opts.cwd, op.path), op.patch, opts.dryRun, result);
361
+ break;
362
+ case "append-gitignore":
363
+ await appendGitignore(abs(opts.cwd, ".gitignore"), op.entries, opts.dryRun, result);
364
+ break;
365
+ case "add-dep":
366
+ await addDependency(abs(opts.cwd, "package.json"), op, opts.dryRun, result);
367
+ break;
368
+ case "edit":
369
+ await editFile(opts.cwd, op.path, op.edit, opts.dryRun, result);
370
+ break;
371
+ }
372
+ }
373
+ /**
374
+ * Generic content edit: read the file, run the patcher-supplied transform, write
375
+ * the result. Framework knowledge lives entirely in `edit` (next to its
376
+ * patcher); this executor only owns candidate resolution, idempotency, dry-run,
377
+ * and the atomic write. `pathOrPaths` may be a single path or a priority list of
378
+ * candidates — the first that exists wins, else the first candidate.
379
+ */
380
+ async function editFile(cwd, pathOrPaths, edit, dryRun, result) {
381
+ const candidates = (typeof pathOrPaths === "string" ? [pathOrPaths] : pathOrPaths).map((p) => abs(cwd, p));
382
+ if (candidates.length === 0) throw new ZitadelError("E_VALIDATION", "An edit op needs at least one candidate path", { hint: "This is an internal patcher error — please report it if you hit it." });
383
+ let path = candidates[0];
384
+ let source;
385
+ let mode;
386
+ for (const candidate of candidates) {
387
+ const contents = await readIfExists(candidate);
388
+ if (contents !== void 0) {
389
+ path = candidate;
390
+ source = contents;
391
+ mode = (await stat(candidate)).mode & 511;
392
+ break;
393
+ }
394
+ }
395
+ const next = edit(source);
396
+ if (next === source) {
397
+ result.filesSkipped.push(path);
398
+ return;
399
+ }
400
+ if (dryRun) {
401
+ result.filesWritten.push(path);
402
+ return;
403
+ }
404
+ await mkdir(dirname(path), { recursive: true });
405
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
406
+ await writeFile(tmp, next);
407
+ if (mode !== void 0) await chmod(tmp, mode).catch(() => void 0);
408
+ await rename(tmp, path);
409
+ result.filesWritten.push(path);
410
+ }
411
+ async function ensureDir(path, mode, dryRun, result) {
412
+ if (dryRun) {
413
+ result.filesWritten.push(path);
414
+ return;
415
+ }
416
+ await mkdir(path, {
417
+ recursive: true,
418
+ mode
419
+ });
420
+ if (mode) await chmod(path, mode).catch(() => void 0);
421
+ result.filesWritten.push(path);
422
+ }
423
+ async function writeText(path, contents, opts, result) {
424
+ const existing = await readIfExists(path);
425
+ if (existing === contents) {
426
+ result.filesSkipped.push(path);
427
+ return;
428
+ }
429
+ if (existing !== void 0 && !opts.force) throw new ZitadelError("E_CONFLICT", `Refusing to overwrite ${path}`, {
430
+ hint: "Re-run with --force if you want the CLI to replace this file.",
431
+ details: { path }
432
+ });
433
+ if (opts.dryRun) {
434
+ result.filesWritten.push(path);
435
+ return;
436
+ }
437
+ await mkdir(dirname(path), { recursive: true });
438
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
439
+ await writeFile(tmp, contents, { mode: opts.mode });
440
+ if (opts.mode) await chmod(tmp, opts.mode).catch(() => void 0);
441
+ await rename(tmp, path);
442
+ result.filesWritten.push(path);
443
+ }
444
+ async function appendText(path, contents, ifMissing, dryRun, result) {
445
+ const existing = await readIfExists(path) ?? "";
446
+ if (ifMissing && existing.includes(ifMissing)) {
447
+ result.filesSkipped.push(path);
448
+ return;
449
+ }
450
+ const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${contents}`;
451
+ if (next === existing) {
452
+ result.filesSkipped.push(path);
453
+ return;
454
+ }
455
+ if (dryRun) {
456
+ result.filesWritten.push(path);
457
+ return;
458
+ }
459
+ await mkdir(dirname(path), { recursive: true });
460
+ await writeFile(path, next);
461
+ result.filesWritten.push(path);
462
+ }
463
+ async function mergeEnv(path, entries, dryRun, result) {
464
+ const existing = await readIfExists(path) ?? "";
465
+ const present = new Set(existing.split(/\r?\n/g).map((line) => line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/)?.[1]).filter((value) => Boolean(value)));
466
+ const additions = Object.entries(entries).filter(([key]) => !present.has(key));
467
+ if (additions.length === 0) {
468
+ result.filesSkipped.push(path);
469
+ return;
470
+ }
471
+ const block = additions.map(([key, value]) => `${key}=${value}`).join("\n");
472
+ const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${block}\n`;
473
+ if (dryRun) {
474
+ result.filesWritten.push(path);
475
+ return;
476
+ }
477
+ await mkdir(dirname(path), { recursive: true });
478
+ await writeFile(path, next);
479
+ result.filesWritten.push(path);
480
+ }
481
+ async function mergeJson(path, patch, dryRun, result) {
482
+ const existing = await readIfExists(path);
483
+ const contents = `${stableStringify(deepMerge(existing ? parseJsonObject(existing, path) : {}, patch))}\n`;
484
+ if (existing === contents) {
485
+ result.filesSkipped.push(path);
486
+ return;
487
+ }
488
+ if (dryRun) {
489
+ result.filesWritten.push(path);
490
+ return;
491
+ }
492
+ await mkdir(dirname(path), { recursive: true });
493
+ await writeFile(path, contents);
494
+ result.filesWritten.push(path);
495
+ }
496
+ async function appendGitignore(path, entries, dryRun, result) {
497
+ const existing = await readIfExists(path) ?? "";
498
+ const lines = new Set(existing.split(/\r?\n/g).map((line) => line.trim()));
499
+ const missing = entries.filter((entry) => !lines.has(entry));
500
+ if (missing.length === 0) {
501
+ result.filesSkipped.push(path);
502
+ return;
503
+ }
504
+ const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${missing.join("\n")}\n`;
505
+ if (dryRun) {
506
+ result.filesWritten.push(path);
507
+ return;
508
+ }
509
+ await writeFile(path, next);
510
+ result.filesWritten.push(path);
511
+ }
512
+ async function addDependency(path, op, dryRun, result) {
513
+ const existing = await readIfExists(path);
514
+ if (!existing) throw new ZitadelError("E_VALIDATION", "package.json is required to add Zitadel dependencies");
515
+ const current = parseJsonObject(existing, path);
516
+ const key = op.dev ? "devDependencies" : "dependencies";
517
+ const deps = isObject(current[key]) ? current[key] : {};
518
+ if (deps[op.name] === op.version) {
519
+ result.filesSkipped.push(path);
520
+ return;
521
+ }
522
+ current[key] = {
523
+ ...deps,
524
+ [op.name]: op.version
525
+ };
526
+ const contents = `${stableStringify(current)}\n`;
527
+ if (dryRun) {
528
+ result.filesWritten.push(path);
529
+ result.depsAdded.push(op.name);
530
+ return;
531
+ }
532
+ await writeFile(path, contents);
533
+ result.filesWritten.push(path);
534
+ result.depsAdded.push(op.name);
535
+ }
536
+ async function readIfExists(path) {
537
+ try {
538
+ return await readFile(path, "utf8");
539
+ } catch (error) {
540
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
541
+ throw error;
542
+ }
543
+ }
544
+ function abs(cwd, path) {
545
+ return join(cwd, path);
546
+ }
547
+ function deepMerge(target, patch) {
548
+ const out = { ...target };
549
+ for (const [key, value] of Object.entries(patch)) if (isObject(value) && isObject(out[key])) out[key] = deepMerge(out[key], value);
550
+ else out[key] = value;
551
+ return out;
552
+ }
553
+ //#endregion
554
+ //#region src/lib/orca/patchers/rule/reclaim.ts
555
+ /**
556
+ * The subset of a patcher plan's operations that `doctor --fix` re-applies:
557
+ * env merges, gitignore entries, dependency additions, marker-bearing managed
558
+ * files (framework routes/middleware), and the `edit` transforms — the
559
+ * `/__nextgen` dev proxy merged into `vite.config`/`nuxt.config`/`angular.json`
560
+ * and the Angular `dev` script added to `package.json`. Every `edit` transform
561
+ * is idempotent and only adds what is missing (an existing value is left as-is,
562
+ * the transform returning the source unchanged), so replaying one restores a
563
+ * removed managed block without clobbering the user's own edits. Deliberately
564
+ * excludes the unmarked `.zitadel/` resource writes and `zitadel.json` — those
565
+ * are user-editable and synced by `apply`, so `--fix` must not clobber them.
566
+ *
567
+ * Pure: filters a freshly-allocated list; the input plan is not mutated.
568
+ */
569
+ function reclaimableOps(plan) {
570
+ return plan.ops.filter((op) => op.kind === "merge-env" || op.kind === "append-gitignore" || op.kind === "add-dep" || op.kind === "edit" || op.kind === "write" && op.contents.includes("// zitadel-cli: managed-file v1"));
571
+ }
572
+ //#endregion
573
+ //#region src/lib/orca/patchers/rule/base.ts
574
+ /**
575
+ * Base for rule-based (deterministic, template-driven) patchers, as opposed to
576
+ * a future LLM-driven family. It applies the integration by building a
577
+ * file-operation plan and running the file-writer — that strategy stays
578
+ * entirely inside this family, so callers only ever see the family-neutral
579
+ * {@link Patcher} surface. Owns the framework-agnostic `.zitadel/` base files
580
+ * and the shared eject classification; subclasses contribute only their
581
+ * framework-specific routes/middleware.
582
+ */
583
+ var AbstractRulePatcher = class {
584
+ /** Apply the full plan (base `.zitadel/` files + framework routes). */
585
+ async patch(ctx, opts) {
586
+ return scaffold(this.plan(ctx), opts);
587
+ }
588
+ /**
589
+ * Re-apply only the reclaimable subset — env files, gitignore, the SDK
590
+ * dependency, and marker-bearing routes — leaving the user-editable
591
+ * `.zitadel/` resources untouched. Backs `doctor --fix`.
592
+ */
593
+ async repair(ctx, opts) {
594
+ const plan = this.plan(ctx);
595
+ return scaffold({
596
+ ops: reclaimableOps(plan),
597
+ summary: plan.summary
598
+ }, opts);
599
+ }
600
+ /** Shared base artifacts plus the subclass's marker-bearing route files. */
601
+ artifacts(view) {
602
+ return {
603
+ markedFiles: this.routeFiles(view),
604
+ rootConfigFiles: ["zitadel.json"],
605
+ directories: [".zitadel"],
606
+ envBackups: [".env.local"],
607
+ dependencies: this.routeDeps(view),
608
+ configEdits: this.routeConfigEdits(view)
609
+ };
610
+ }
611
+ /**
612
+ * User config files this patcher merges into via an `edit` op (e.g.
613
+ * `vite.config.ts`). `eject` can't reverse an in-place merge, so it lists
614
+ * these as manual cleanup steps. Defaults to none; patchers that edit a config
615
+ * (React/Vue/Angular/Nuxt) override it. Next writes whole marker-bearing files
616
+ * instead, so it has none.
617
+ */
618
+ routeConfigEdits(_view) {
619
+ return [];
620
+ }
621
+ /**
622
+ * The full file-operation plan this patcher would apply. Public so rule-family
623
+ * unit tests can assert the planned ops directly; the generic {@link Patcher}
624
+ * interface deliberately does not expose it (an LLM patcher has no such plan).
625
+ */
626
+ plan(ctx) {
627
+ return {
628
+ ops: [...this.baseOps(ctx), ...this.routeOps(ctx)],
629
+ summary: [this.summary(ctx)]
630
+ };
631
+ }
632
+ /**
633
+ * The framework-agnostic `.zitadel/` base files every rule patcher writes:
634
+ * the project secret, `zitadel.json`, env templates, and an empty sync
635
+ * state. The `schemas/` and `flows/` directories are created empty — the
636
+ * server provisions the default user schema and flow definition when the
637
+ * project is created, so nothing is scaffolded into them here. Pure: no
638
+ * filesystem or network.
639
+ */
640
+ baseOps(ctx) {
641
+ return [
642
+ {
643
+ kind: "mkdir",
644
+ path: ".zitadel",
645
+ mode: 448
646
+ },
647
+ {
648
+ kind: "mkdir",
649
+ path: ".zitadel/flows"
650
+ },
651
+ {
652
+ kind: "mkdir",
653
+ path: ".zitadel/schemas"
654
+ },
655
+ {
656
+ kind: "append-gitignore",
657
+ entries: [
658
+ ".zitadel/secret",
659
+ ".env*",
660
+ "!.env.example"
661
+ ]
662
+ },
663
+ {
664
+ kind: "write",
665
+ path: ".zitadel/secret",
666
+ mode: 384,
667
+ contents: `${stableStringify({
668
+ project_id: ctx.project.id,
669
+ project_secret: ctx.project.projectSecret,
670
+ preview_secret: ctx.project.previewSecret,
671
+ preview_origins: ctx.project.previewOrigins,
672
+ created_at: ctx.project.createdAt
673
+ })}\n`
674
+ },
675
+ {
676
+ kind: "write",
677
+ path: "zitadel.json",
678
+ contents: `${stableStringify(projectConfig(ctx))}\n`
679
+ },
680
+ {
681
+ kind: "merge-env",
682
+ path: ".env.example",
683
+ entries: {
684
+ ZITADEL_PROJECT_ID: "",
685
+ ZITADEL_PROJECT_SECRET: "",
686
+ ZITADEL_ENVIRONMENT: "",
687
+ ZITADEL_ISSUER: "",
688
+ ZITADEL_URL: ""
689
+ }
690
+ },
691
+ {
692
+ kind: "merge-env",
693
+ path: ".env.local",
694
+ entries: {
695
+ ZITADEL_PROJECT_ID: ctx.project.id,
696
+ ZITADEL_PROJECT_SECRET: ctx.project.projectSecret,
697
+ ZITADEL_ENVIRONMENT: "development",
698
+ ZITADEL_ISSUER: ctx.issuer,
699
+ ZITADEL_URL: ctx.server
700
+ }
701
+ },
702
+ {
703
+ kind: "write",
704
+ path: ".zitadel/state.json",
705
+ contents: `${stableStringify({
706
+ framework: ctx.framework.id,
707
+ resources: {}
708
+ })}\n`
709
+ }
710
+ ];
711
+ }
712
+ };
713
+ /** Builds the `zitadel.json` body persisted at the project root. */
714
+ function projectConfig(ctx) {
715
+ const environments = { development: { issuer: ctx.issuer } };
716
+ if (ctx.project.previewOrigins.length > 0) environments.preview = { issuer_pattern: [...ctx.project.previewOrigins] };
717
+ return {
718
+ $schema: "https://schemas.zitadel.com/v2/project.schema.json",
719
+ project: ctx.project.id,
720
+ server: resolveServerOrigin(ctx.server),
721
+ framework: { id: ctx.framework.id },
722
+ branding: {
723
+ renderer: ctx.rendererId,
724
+ attribution: "visible"
725
+ },
726
+ environments
727
+ };
728
+ }
729
+ /** Normalizes a server URL to its origin, falling back to {@link DEFAULT_SERVER}. */
730
+ function resolveServerOrigin(source) {
731
+ try {
732
+ return new URL(source).origin;
733
+ } catch {
734
+ return DEFAULT_SERVER;
735
+ }
736
+ }
737
+ //#endregion
738
+ //#region src/lib/orca/patchers/rule/angular/angular-json.ts
739
+ /**
740
+ * Builds the pure `edit` transform the file-writer applies to `angular.json`:
741
+ * wires a dev-server `proxyConfig` (and optional `port`) into the project's
742
+ * `serve` target. The project name is discovered from the file (`defaultProject`,
743
+ * else the sole project) rather than hardcoded, since it varies per app.
744
+ * Idempotent — already-set values are left as-is. Throws `E_VALIDATION` when the
745
+ * file is absent, the project/serve target cannot be located, or the workspace
746
+ * has several projects with no `defaultProject` to disambiguate (rather than
747
+ * guessing and wiring the proxy into an arbitrary one).
748
+ */
749
+ function angularProxyEdit(opts) {
750
+ return (source) => {
751
+ if (source === void 0) throw new ZitadelError("E_VALIDATION", "Cannot wire Angular proxy: angular.json not found", { hint: "Run setup from an Angular project." });
752
+ const root = parseJsonObject(source, "angular.json");
753
+ const projects = isObject(root.projects) ? root.projects : void 0;
754
+ const projectNames = Object.keys(projects ?? {});
755
+ let projectName;
756
+ if (typeof root.defaultProject === "string") projectName = root.defaultProject;
757
+ else if (projectNames.length === 1) projectName = projectNames[0];
758
+ else if (projectNames.length > 1) throw new ZitadelError("E_VALIDATION", "angular.json has multiple projects and no defaultProject to choose from", { hint: `Set "defaultProject" in angular.json, or add "proxyConfig" to the right project's serve target manually. Projects: ${projectNames.join(", ")}.` });
759
+ const project = projects && projectName ? projects[projectName] : void 0;
760
+ if (!isObject(project)) throw new ZitadelError("E_VALIDATION", "No project found in angular.json", { hint: "Add \"proxyConfig\" to your serve target manually." });
761
+ const targets = isObject(project.architect) ? project.architect : isObject(project.targets) ? project.targets : void 0;
762
+ const serve = targets && isObject(targets.serve) ? targets.serve : void 0;
763
+ if (!serve) throw new ZitadelError("E_VALIDATION", "No serve target in angular.json", { hint: "Add \"proxyConfig\" to your serve options manually." });
764
+ const options = isObject(serve.options) ? serve.options : {};
765
+ let changed = false;
766
+ if (options.proxyConfig === void 0) {
767
+ options.proxyConfig = opts.proxyConfig;
768
+ changed = true;
769
+ }
770
+ if (opts.port !== void 0 && options.port === void 0) {
771
+ options.port = opts.port;
772
+ changed = true;
773
+ }
774
+ if (!changed) return source;
775
+ serve.options = options;
776
+ return `${JSON.stringify(root, null, 2)}\n`;
777
+ };
778
+ }
779
+ //#endregion
780
+ //#region src/lib/orca/patchers/rule/utils/magicast.ts
781
+ /**
782
+ * Generic magicast helpers shared by the config-editing patchers (Vite, Nuxt).
783
+ * They navigate a module's default export — they carry no framework knowledge
784
+ * beyond "find the config object literal" and "is this import present".
785
+ */
786
+ /**
787
+ * Parses a config file with magicast, throwing a clean `E_VALIDATION` (instead
788
+ * of a raw parse error) when the source is missing or unparseable. `filename` is
789
+ * only used in the error message, so each patcher can name its own config file.
790
+ */
791
+ function parseConfigModule(source, filename) {
792
+ if (source === void 0) throw new ZitadelError("E_VALIDATION", `Cannot edit ${filename}: file not found`, { hint: `Run setup from a project that has ${filename}.` });
793
+ let mod;
794
+ try {
795
+ mod = parseModule(source);
796
+ } catch (error) {
797
+ throw new ZitadelError("E_VALIDATION", `Could not parse ${filename}`, {
798
+ hint: `Ensure ${filename} is valid, or apply the Zitadel changes manually.`,
799
+ details: { cause: error instanceof Error ? error.message : String(error) }
800
+ });
801
+ }
802
+ if (hasCommonJsExport(mod)) throw new ZitadelError("E_VALIDATION", `${filename} is a CommonJS module, which can't be edited`, { hint: `The Zitadel edits use ESM imports. Convert the config to ESM (a .ts/.mts file, or set "type": "module"), or add the Zitadel block manually.` });
803
+ return mod;
804
+ }
805
+ /**
806
+ * Whether the module has a top-level CommonJS export assignment —
807
+ * `module.exports = …`, `module.exports.x = …`, or `exports.x = …` — read from
808
+ * the parsed AST so comments and string literals can't trigger a false match.
809
+ */
810
+ function hasCommonJsExport(mod) {
811
+ return ((mod?.$ast?.program ?? mod?.$ast)?.body ?? []).some((node) => {
812
+ if (node?.type !== "ExpressionStatement" || node.expression?.type !== "AssignmentExpression") return false;
813
+ const left = node.expression.left;
814
+ if (left?.type !== "MemberExpression") return false;
815
+ const object = left.object;
816
+ if (object?.type === "Identifier" && object.name === "exports") return true;
817
+ if (object?.type === "Identifier" && object.name === "module" && left.property?.name === "exports") return true;
818
+ return object?.type === "MemberExpression" && object.object?.name === "module" && object.property?.name === "exports";
819
+ });
820
+ }
821
+ /**
822
+ * Reaches the object literal of a module's default export — the argument of
823
+ * `export default <call>({...})` (e.g. `defineConfig`/`defineNuxtConfig`) or a
824
+ * bare `export default {...}`. Throws `E_VALIDATION` for shapes magicast cannot
825
+ * safely edit (function-form, configs built elsewhere) so the caller can fall
826
+ * back to manual steps.
827
+ */
828
+ function resolveDefaultExportObject(mod, filename) {
829
+ const def = mod.exports?.default;
830
+ const unreachable = () => new ZitadelError("E_VALIDATION", `Could not locate the config object in ${filename}`, { hint: `Add the Zitadel configuration to ${filename} manually (see the SDK README).` });
831
+ if (!def) throw unreachable();
832
+ if (def.$type === "function-call") {
833
+ const arg = def.$args?.[0];
834
+ if (!arg || arg.$type !== "object") throw unreachable();
835
+ return arg;
836
+ }
837
+ if (def.$type === "object") return def;
838
+ throw unreachable();
839
+ }
840
+ function importIsPresent(mod, local, from) {
841
+ try {
842
+ return (mod.imports?.$items ?? []).some((item) => item.local === local && (from === void 0 || item.from === from));
843
+ } catch {
844
+ return false;
845
+ }
846
+ }
847
+ /**
848
+ * Appends `item` to a string array at `parent[key]`, creating the array when
849
+ * absent and skipping it when already present. Reads the proxified array by
850
+ * index so primitive elements compare as plain values. Returns `true` when it
851
+ * actually added the item, so callers can tell whether the edit changed
852
+ * anything (and skip rewriting an already-complete config).
853
+ */
854
+ function ensureArrayItem(parent, key, item) {
855
+ if (parent[key] === void 0) {
856
+ parent[key] = [item];
857
+ return true;
858
+ }
859
+ const arr = parent[key];
860
+ if (typeof arr?.push !== "function" || typeof arr?.length !== "number") throw new ZitadelError("E_VALIDATION", `Could not add "${item}" to "${key}"`, { hint: `Add "${item}" to "${key}" in your config manually.` });
861
+ if (!Array.from({ length: arr.length }, (_unused, i) => arr[i]).includes(item)) {
862
+ arr.push(item);
863
+ return true;
864
+ }
865
+ return false;
866
+ }
867
+ /**
868
+ * Returns the object literal at `parent[key]`, creating an empty one when
869
+ * absent, so callers can safely descend into it. Throws `E_VALIDATION` when the
870
+ * key already holds something that is not an inline object literal (an
871
+ * identifier, spread, or function call) — magicast cannot edit those, and
872
+ * assigning into them otherwise throws a raw proxy `TypeError`. The object
873
+ * sibling of {@link ensureArrayItem}.
874
+ */
875
+ function ensureEditableObject(parent, key) {
876
+ if (parent[key] === void 0) parent[key] = {};
877
+ const value = parent[key];
878
+ if (value?.$type !== "object") throw new ZitadelError("E_VALIDATION", `Could not edit "${key}" in the config`, { hint: `Set "${key}" to an inline object literal, or add the Zitadel settings manually.` });
879
+ return value;
880
+ }
881
+ //#endregion
882
+ //#region src/lib/orca/patchers/rule/angular/angular-routes.ts
883
+ const AUTH_ROUTE_PATHS = [
884
+ "login",
885
+ "register",
886
+ "profile"
887
+ ];
888
+ /**
889
+ * Angular's default `ng new` app enables the router with an empty route table.
890
+ * That router rejects direct `/login` and `/profile` navigations, then rewrites
891
+ * the URL back to `/`. Add componentless routes for the auth paths so the root
892
+ * component can keep rendering based on `window.location.pathname` without
893
+ * requiring a router outlet.
894
+ */
895
+ function angularRoutesEdit() {
896
+ return (source) => {
897
+ const label = "src/app/app.routes.ts";
898
+ const mod = parseConfigModule(source, label);
899
+ const routes = mod.exports?.routes;
900
+ if (routes?.$type !== "array" || typeof routes.push !== "function") throw new ZitadelError("E_VALIDATION", "Cannot wire Angular auth routes", { hint: `Set "routes" in ${label} to an inline Routes array, or add /login, /register, and /profile manually.` });
901
+ const present = new Set(Array.from({ length: routes.length }, (_unused, index) => routes[index]?.path).filter((path) => typeof path === "string"));
902
+ let changed = false;
903
+ for (const path of AUTH_ROUTE_PATHS) {
904
+ if (present.has(path)) continue;
905
+ routes.push(builders.raw(`{ path: ${JSON.stringify(path)}, children: [] }`));
906
+ changed = true;
907
+ }
908
+ if (!changed && source !== void 0) return source;
909
+ const code = generateCode(mod).code;
910
+ return code.endsWith("\n") ? code : `${code}\n`;
911
+ };
912
+ }
913
+ //#endregion
914
+ //#region src/lib/orca/patchers/rule/proxy.ts
915
+ /**
916
+ * The same-origin path the SDK widgets call (`configureZitadel({ proxyPath })`),
917
+ * which every framework's dev proxy forwards to the backend. Framework-agnostic:
918
+ * Vite (React/Vue), Angular's dev-server proxy, and Nuxt's server middleware all
919
+ * key off the same prefix, so it lives here rather than in any one framework's
920
+ * patcher.
921
+ */
922
+ const PROXY_PATH = "/__nextgen";
923
+ //#endregion
924
+ //#region src/lib/orca/patchers/rule/angular/templates.ts
925
+ /**
926
+ * The managed root component `src/app/app.ts`: a standalone component that
927
+ * renders the `@zitadel/sdk-angular` widgets based on the current path. The
928
+ * project id (public, not secret) is inlined; the dev proxy in `proxy.conf.cjs`
929
+ * attaches the project service-key secret as the bearer server-side (read from
930
+ * `.env.local`), and no secret reaches the browser.
931
+ */
932
+ function appComponentTemplate(projectId) {
933
+ return `${MANAGED_MARKER}
934
+ import { Component } from "@angular/core";
935
+ import {
936
+ ZitadelLoginComponent,
937
+ ZitadelLogoutComponent,
938
+ configureZitadel,
939
+ } from "@zitadel/sdk-angular";
940
+
941
+ @Component({
942
+ selector: "app-root",
943
+ standalone: true,
944
+ imports: [ZitadelLoginComponent, ZitadelLogoutComponent],
945
+ templateUrl: "./app.html",
946
+ })
947
+ export class App {
948
+ protected readonly project = configureZitadel({
949
+ projectId: ${JSON.stringify(projectId)},
950
+ proxyPath: "${PROXY_PATH}",
951
+ });
952
+ protected readonly path = window.location.pathname;
953
+ }
954
+ `;
955
+ }
956
+ /**
957
+ * The managed `src/app/app.html`. The marker lives in an HTML comment that still
958
+ * contains the literal managed-marker text, so eject/doctor stay marker-aware.
959
+ */
960
+ function appTemplateHtml() {
961
+ return `<!-- ${MANAGED_MARKER} -->
962
+ @if (path === '/') {
963
+ <main style="position:fixed;inset:0;padding:48px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:#0f0f11;color:#f4f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.5;letter-spacing:normal;text-align:center">
964
+ <section style="width:100%;max-width:560px">
965
+ <p style="margin:0 0 12px;color:#9ca3af;font-size:14px">Zitadel auth</p>
966
+ <h1 style="margin:0 0 24px;font-size:32px;line-height:1.15;font-weight:600;color:#f4f4f6">Sign in, create an account, or open your profile.</h1>
967
+ <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center">
968
+ <a href="/login" style="padding:10px 16px;border-radius:8px;background:#f4f4f6;color:#0f0f11;text-decoration:none;font-weight:600;font-size:14px">Sign in</a>
969
+ <a href="/register" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Create account</a>
970
+ <a href="/profile" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Profile</a>
971
+ </div>
972
+ </section>
973
+ </main>
974
+ } @else if (path.startsWith('/profile')) {
975
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
976
+ <zitadel-auth-logout [project]="project" [postSignOutUrl]="'/login'"></zitadel-auth-logout>
977
+ </div>
978
+ } @else if (path.startsWith('/register')) {
979
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
980
+ <zitadel-auth-login
981
+ [project]="project"
982
+ purpose="register"
983
+ [postSignInUrl]="'/profile'"
984
+ ></zitadel-auth-login>
985
+ </div>
986
+ } @else {
987
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
988
+ <zitadel-auth-login
989
+ [project]="project"
990
+ purpose="login"
991
+ [postSignInUrl]="'/profile'"
992
+ ></zitadel-auth-login>
993
+ </div>
994
+ }
995
+ `;
996
+ }
997
+ /**
998
+ * The managed `proxy.conf.cjs` for `ng serve`: forwards `/__nextgen/*` to the
999
+ * backend and attaches the project's service-key secret as the bearer on every
1000
+ * proxied request. Both the backend URL (`ZITADEL_URL`) and the secret
1001
+ * (`ZITADEL_PROJECT_SECRET`) are read from `.env.local`, which `zitadel setup`
1002
+ * writes and `.gitignore` excludes — Angular's CLI does not auto-load env files
1003
+ * into the dev-server process, so this file does it itself with a small inline
1004
+ * parser. The prefix strip and the bearer are each provided in both the
1005
+ * http-proxy-middleware form (`pathRewrite`/`onProxyReq`) and the Vite form
1006
+ * (`rewrite`/`configure`), so both fire whichever proxy layer Angular's
1007
+ * dev server uses.
1008
+ */
1009
+ function proxyConfTemplate() {
1010
+ return `${MANAGED_MARKER}
1011
+ const { readFileSync, existsSync } = require("node:fs");
1012
+
1013
+ function loadEnvLocal() {
1014
+ if (!existsSync(".env.local")) return {};
1015
+ const out = {};
1016
+ for (const line of readFileSync(".env.local", "utf8").split(/\\r?\\n/)) {
1017
+ const m = line.match(/^\\s*(?:export\\s+)?([A-Z_][A-Z0-9_]*)\\s*=\\s*(.*)$/);
1018
+ if (m) {
1019
+ const raw = m[2].trim();
1020
+ const quoted = raw.match(/^(['"])(.*)\\1\\s*(?:#.*)?$/);
1021
+ out[m[1]] = quoted ? quoted[2] : raw.replace(/\\s+#.*$/, "").trim();
1022
+ }
1023
+ }
1024
+ return out;
1025
+ }
1026
+
1027
+ const env = loadEnvLocal();
1028
+ const server = process.env.ZITADEL_URL ?? env.ZITADEL_URL;
1029
+ const secret = process.env.ZITADEL_PROJECT_SECRET ?? env.ZITADEL_PROJECT_SECRET;
1030
+ if (!server) {
1031
+ throw new Error("ZITADEL_URL is not set; add it to .env.local (zitadel setup writes it).");
1032
+ }
1033
+ if (!secret) {
1034
+ throw new Error("ZITADEL_PROJECT_SECRET is not set; add it to .env.local (zitadel setup writes it).");
1035
+ }
1036
+ const bearer = \`Bearer \${secret}\`;
1037
+
1038
+ function setBearer(proxyReq) {
1039
+ proxyReq.setHeader("authorization", bearer);
1040
+ }
1041
+
1042
+ function stripPrefix(path) {
1043
+ return path.replace(/^\\${PROXY_PATH}/, "").replace(/^(?!\\/)/, "/");
1044
+ }
1045
+
1046
+ module.exports = {
1047
+ "${PROXY_PATH}": {
1048
+ target: server,
1049
+ changeOrigin: false,
1050
+ pathRewrite: stripPrefix,
1051
+ rewrite: stripPrefix,
1052
+ onProxyReq: setBearer,
1053
+ configure: (proxy) => proxy.on("proxyReq", setBearer),
1054
+ },
1055
+ };
1056
+ `;
1057
+ }
1058
+ //#endregion
1059
+ //#region src/lib/orca/patchers/rule/angular/index.ts
1060
+ const SDK_DEPENDENCY$6 = "@zitadel/sdk-angular";
1061
+ /**
1062
+ * Adds a `dev: "ng serve"` script only when the project does not already define
1063
+ * one. `ng new` ships only a `start` script, but the CLI tells every framework
1064
+ * to run `npm run dev` (and `ng serve` reads the proxy + port from
1065
+ * `angular.json`). Non-destructive: an existing `dev` script is preserved, so
1066
+ * patching a project that already wires its own `dev` leaves it untouched.
1067
+ */
1068
+ function ensureDevScript(source) {
1069
+ if (source === void 0) throw new ZitadelError("E_VALIDATION", "package.json is required to add the dev script", { hint: "Run setup from a project that has a package.json." });
1070
+ const pkg = parseJsonObject(source, "package.json");
1071
+ const scripts = isObject(pkg.scripts) ? pkg.scripts : void 0;
1072
+ if (scripts?.dev !== void 0) return source;
1073
+ pkg.scripts = {
1074
+ ...scripts ?? {},
1075
+ dev: "ng serve"
1076
+ };
1077
+ return `${stableStringify(pkg)}\n`;
1078
+ }
1079
+ /**
1080
+ * Rule-based patcher for an Angular app. Inherits the shared `.zitadel/` base
1081
+ * files from {@link AbstractRulePatcher} and contributes the managed root
1082
+ * component (`app.ts`/`app.html`) that renders the `@zitadel/sdk-angular`
1083
+ * widgets, a `proxy.conf.cjs` dev proxy (attaching the project secret from
1084
+ * `ZITADEL_PROJECT_SECRET` to every proxied request) wired into `angular.json`,
1085
+ * and the SDK dep.
1086
+ *
1087
+ * Unlike React/Vue (whose dev proxy lives in `vite.config.ts`), Angular owns its
1088
+ * Vite config, so the proxy is a separate `proxy.conf.cjs` referenced from the
1089
+ * `serve` target. Production still needs `@zitadel/edge-proxy`.
1090
+ */
1091
+ var AngularPatcher = class extends AbstractRulePatcher {
1092
+ canPatch(framework) {
1093
+ return framework === "angular";
1094
+ }
1095
+ routeOps(ctx) {
1096
+ return [
1097
+ {
1098
+ kind: "write",
1099
+ path: "src/app/app.ts",
1100
+ contents: appComponentTemplate(ctx.project.id)
1101
+ },
1102
+ {
1103
+ kind: "write",
1104
+ path: "src/app/app.html",
1105
+ contents: appTemplateHtml()
1106
+ },
1107
+ {
1108
+ kind: "edit",
1109
+ path: "src/app/app.routes.ts",
1110
+ edit: angularRoutesEdit()
1111
+ },
1112
+ {
1113
+ kind: "write",
1114
+ path: "proxy.conf.cjs",
1115
+ contents: proxyConfTemplate()
1116
+ },
1117
+ {
1118
+ kind: "edit",
1119
+ path: "angular.json",
1120
+ edit: angularProxyEdit({
1121
+ proxyConfig: "proxy.conf.cjs",
1122
+ port: ctx.framework.devPort
1123
+ })
1124
+ },
1125
+ {
1126
+ kind: "edit",
1127
+ path: "package.json",
1128
+ edit: ensureDevScript
1129
+ },
1130
+ {
1131
+ kind: "add-dep",
1132
+ name: SDK_DEPENDENCY$6,
1133
+ version: npmDistTagForCliVersion(ctx.cliVersion)
1134
+ }
1135
+ ];
1136
+ }
1137
+ routeFiles(_view) {
1138
+ return [
1139
+ "src/app/app.ts",
1140
+ "src/app/app.html",
1141
+ "proxy.conf.cjs"
1142
+ ];
1143
+ }
1144
+ routeDeps(_view) {
1145
+ return [SDK_DEPENDENCY$6];
1146
+ }
1147
+ routeConfigEdits(_view) {
1148
+ return [
1149
+ "angular.json",
1150
+ "src/app/app.routes.ts",
1151
+ "package.json"
1152
+ ];
1153
+ }
1154
+ summary(_ctx) {
1155
+ return {
1156
+ title: "Angular integration",
1157
+ detail: "Wrote the app root component + proxy.conf.cjs, added auth routes, and wired the /__nextgen dev proxy into angular.json."
1158
+ };
1159
+ }
1160
+ };
1161
+ //#endregion
1162
+ //#region src/lib/orca/patchers/rule/next/renderers/lit/index.ts
1163
+ /**
1164
+ * Placeholder renderer for the `<zitadel-flow>` Lit web component. Declared
1165
+ * so the `web-component` renderer id resolves and surfaces a clear
1166
+ * "not yet published" error, while reserving the integration shape for when
1167
+ * `@zitadel/ui-lit` ships. The `authPage` template emits an illustrative
1168
+ * page only; this renderer is never selected for real scaffolding because
1169
+ * `getRenderer` rejects any `status: "not-implemented"` spec.
1170
+ */
1171
+ const litRenderer = {
1172
+ id: "web-component",
1173
+ displayName: "Web component (<zitadel-flow>)",
1174
+ status: "not-implemented",
1175
+ frameworks: [
1176
+ "next",
1177
+ "astro",
1178
+ "remix",
1179
+ "sveltekit",
1180
+ "nuxt",
1181
+ "vanilla"
1182
+ ],
1183
+ dependency: {
1184
+ name: "@zitadel/ui-lit",
1185
+ version: "workspace:*"
1186
+ },
1187
+ templates: { authPage(mode) {
1188
+ return {
1189
+ mode,
1190
+ contents: `${MANAGED_MARKER}
1191
+ // The web component renderer ships a <zitadel-flow> element. Until
1192
+ // @zitadel/ui-lit is published, this template only declares the
1193
+ // intended integration point. See docs/design/cli/bdui-renderer.md.
1194
+ import "@zitadel/ui-lit";
1195
+
1196
+ const environment =
1197
+ process.env.ZITADEL_ENVIRONMENT ??
1198
+ (process.env.NODE_ENV === "production" ? "production" : "development");
1199
+
1200
+ export default function ${mode === "login" ? "LoginPage" : "RegisterPage"}() {
1201
+ return (
1202
+ <zitadel-flow
1203
+ purpose="${mode === "login" ? "login" : "register"}"
1204
+ project-id={process.env.ZITADEL_PROJECT_ID}
1205
+ issuer={process.env.ZITADEL_ISSUER}
1206
+ environment={environment}
1207
+ />
1208
+ );
1209
+ }
1210
+ `
1211
+ };
1212
+ } }
1213
+ };
1214
+ //#endregion
1215
+ //#region src/lib/orca/patchers/rule/next/renderers/react/index.ts
1216
+ /**
1217
+ * The Next.js App Router renderer scaffolds `/login`, `/register`, and
1218
+ * `/profile` pages that drive the `<zitadel-login>` and `<zitadel-logout>`
1219
+ * Lit web components.
1220
+ *
1221
+ * Each page is a single client component (`"use client"`) that, inside a
1222
+ * `next/dynamic({ ssr: false })` loader, builds the SDK project handle with
1223
+ * `configureZitadel({ projectId, proxyPath: "/__nextgen" })` and passes it to
1224
+ * the widget via `project={...}`. It also imports
1225
+ * `@zitadel/sdk-next/client` for its `customElements.define`
1226
+ * side-effect — importing `@zitadel/components` directly would fail on
1227
+ * strict-resolution package managers (pnpm, yarn PnP) because the app only
1228
+ * declares `sdk-next` as a direct dep. SSR is disabled because Lit's element
1229
+ * registration needs a browser.
1230
+ *
1231
+ * The handle is passed as the `project` DOM property, which relies on React
1232
+ * 19's custom-element property binding (the scaffold targets the latest Next /
1233
+ * React). The backend URL never reaches the browser: the client talks to the
1234
+ * same-origin `/__nextgen` proxy path, and the scaffolded Next request boundary
1235
+ * forwards it to `ZITADEL_URL` server-side. `NEXT_PUBLIC_ZITADEL_PROJECT_ID` is
1236
+ * public — the project id is not sensitive and the widget needs it to start a
1237
+ * flow.
1238
+ */
1239
+ const reactRenderer = {
1240
+ id: "react",
1241
+ displayName: "React (Next.js App Router)",
1242
+ status: "available",
1243
+ frameworks: ["next"],
1244
+ dependency: {
1245
+ name: "@zitadel/sdk-next",
1246
+ version: "latest"
1247
+ },
1248
+ templates: {
1249
+ authPage(mode) {
1250
+ const componentName = mode === "login" ? "LoginPage" : "RegisterPage";
1251
+ const elementName = mode === "login" ? "ZitadelLogin" : "ZitadelRegister";
1252
+ return {
1253
+ mode,
1254
+ contents: `${MANAGED_MARKER}
1255
+ "use client";
1256
+
1257
+ import dynamic from "next/dynamic";
1258
+ import Link from "next/link";
1259
+
1260
+ const ${elementName} = dynamic(
1261
+ async () => {
1262
+ const { configureZitadel } = await import("@zitadel/sdk-next/client");
1263
+ // Build the SDK project handle and pass it to the component via the
1264
+ // \`project\` prop. The component reads config from this prop directly, so
1265
+ // it works regardless of how the SDK packages are bundled. The backend URL
1266
+ // stays server-side: requests go through the proxy path "/__nextgen",
1267
+ // which the scaffolded request boundary forwards to the Zitadel server.
1268
+ const project = configureZitadel({
1269
+ projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
1270
+ proxyPath: "/__nextgen",
1271
+ });
1272
+ return function ${elementName}Element() {
1273
+ return (
1274
+ <zitadel-login
1275
+ project={project}
1276
+ purpose="${mode}"
1277
+ post-sign-in-url="/profile"
1278
+ />
1279
+ );
1280
+ };
1281
+ },
1282
+ { ssr: false },
1283
+ );
1284
+
1285
+ export default function ${componentName}() {
1286
+ return (
1287
+ <main style={{ minHeight: "100vh", position: "relative", background: "#0f0f11" }}>
1288
+ <nav aria-label="Authentication" style={{ position: "absolute", top: "24px", right: "24px", zIndex: 1, display: "flex", gap: "12px" }}>
1289
+ <Link href="${mode === "login" ? "/register" : "/login"}" style={{ color: "#f4f4f6", fontWeight: 700, textDecoration: "none" }}>
1290
+ ${mode === "login" ? "Create account" : "Sign in"}
1291
+ </Link>
1292
+ </nav>
1293
+ <${elementName} />
1294
+ </main>
1295
+ );
1296
+ }
1297
+ `
1298
+ };
1299
+ },
1300
+ profilePage() {
1301
+ return { contents: `${MANAGED_MARKER}
1302
+ "use client";
1303
+
1304
+ import dynamic from "next/dynamic";
1305
+ import { useEffect, useState } from "react";
1306
+
1307
+ type SessionProof = {
1308
+ session_id?: string;
1309
+ state?: string;
1310
+ user_id?: string;
1311
+ };
1312
+
1313
+ const ZitadelLogout = dynamic(
1314
+ async () => {
1315
+ const { configureZitadel } = await import("@zitadel/sdk-next/client");
1316
+ const project = configureZitadel({
1317
+ projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
1318
+ proxyPath: "/__nextgen",
1319
+ });
1320
+ return function ZitadelLogoutElement() {
1321
+ return (
1322
+ <zitadel-logout
1323
+ project={project}
1324
+ post-sign-out-url="/login"
1325
+ />
1326
+ );
1327
+ };
1328
+ },
1329
+ { ssr: false },
1330
+ );
1331
+
1332
+ export default function ProfilePage() {
1333
+ const [session, setSession] = useState<SessionProof | null>(null);
1334
+ const [sessionError, setSessionError] = useState("");
1335
+
1336
+ useEffect(() => {
1337
+ let cancelled = false;
1338
+
1339
+ fetch("/__nextgen/sessions/me", { cache: "no-store" })
1340
+ .then(async (response) => {
1341
+ if (!response.ok) {
1342
+ throw new Error("Session check failed: " + String(response.status));
1343
+ }
1344
+ return response.json() as Promise<SessionProof>;
1345
+ })
1346
+ .then((nextSession) => {
1347
+ if (!cancelled) {
1348
+ setSession(nextSession);
1349
+ }
1350
+ })
1351
+ .catch((error: unknown) => {
1352
+ if (!cancelled) {
1353
+ setSessionError(error instanceof Error ? error.message : "Session check failed");
1354
+ }
1355
+ });
1356
+
1357
+ return () => {
1358
+ cancelled = true;
1359
+ };
1360
+ }, []);
1361
+
1362
+ return (
1363
+ <main style={{ padding: "48px", maxWidth: "680px", margin: "0 auto" }}>
1364
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "24px" }}>
1365
+ <h1 style={{ fontSize: "24px", fontWeight: 700, margin: 0 }}>Signed in</h1>
1366
+ <ZitadelLogout />
1367
+ </div>
1368
+ <p style={{ color: "#166534", fontWeight: 600 }}>Signed in profile loaded.</p>
1369
+ {session ? (
1370
+ <dl style={{ display: "grid", gap: "12px", marginTop: "24px" }}>
1371
+ <div>
1372
+ <dt style={{ color: "#6b7280", fontSize: "14px" }}>Session state</dt>
1373
+ <dd style={{ margin: 0, fontWeight: 600 }}>{session.state ?? "active"}</dd>
1374
+ </div>
1375
+ <div>
1376
+ <dt style={{ color: "#6b7280", fontSize: "14px" }}>User id</dt>
1377
+ <dd style={{ margin: 0, fontFamily: "monospace" }}>{session.user_id ?? "available"}</dd>
1378
+ </div>
1379
+ </dl>
1380
+ ) : (
1381
+ <p style={{ color: sessionError ? "#b91c1c" : "#6b7280" }}>
1382
+ {sessionError || "Checking session..."}
1383
+ </p>
1384
+ )}
1385
+ </main>
1386
+ );
1387
+ }
1388
+ ` };
1389
+ },
1390
+ customElementsDts() {
1391
+ return { contents: `${MANAGED_MARKER}
1392
+ import type React from "react";
1393
+ import type { ZitadelProject } from "@zitadel/sdk-next/client";
1394
+
1395
+ declare module "react" {
1396
+ namespace JSX {
1397
+ interface IntrinsicElements {
1398
+ "zitadel-login": React.HTMLAttributes<HTMLElement> & {
1399
+ project?: ZitadelProject;
1400
+ "session-exchange-path"?: string;
1401
+ "post-sign-in-url"?: string;
1402
+ purpose?: string;
1403
+ };
1404
+ "zitadel-logout": React.HTMLAttributes<HTMLElement> & {
1405
+ project?: ZitadelProject;
1406
+ "post-sign-out-url"?: string;
1407
+ };
1408
+ }
1409
+ }
1410
+ }
1411
+ ` };
1412
+ }
1413
+ }
1414
+ };
1415
+ //#endregion
1416
+ //#region src/lib/orca/patchers/rule/next/renderers/registry.ts
1417
+ /**
1418
+ * Runtime mirror of the {@link RendererId} union, used by {@link isRendererId}
1419
+ * to validate untrusted strings (a TS union has no runtime presence). Must stay
1420
+ * in sync with the {@link RendererId} type.
1421
+ */
1422
+ const RENDERER_IDS = ["react", "web-component"];
1423
+ /**
1424
+ * Type guard narrowing an arbitrary value to a {@link RendererId}, used to
1425
+ * validate renderer ids read from config before indexing {@link RENDERERS}.
1426
+ */
1427
+ function isRendererId(value) {
1428
+ return typeof value === "string" && RENDERER_IDS.includes(value);
1429
+ }
1430
+ /**
1431
+ * The single source of truth mapping each {@link RendererId} to its spec.
1432
+ * Keyed by id so {@link getRenderer} can look up and validate a renderer
1433
+ * chosen from persisted config (an arbitrary string) at runtime.
1434
+ */
1435
+ const RENDERERS = {
1436
+ react: reactRenderer,
1437
+ "web-component": litRenderer
1438
+ };
1439
+ /**
1440
+ * Resolves a renderer id (an untrusted string from config) to its spec,
1441
+ * throwing a typed {@link ZitadelError} rather than returning `undefined`
1442
+ * so callers get an actionable message. Rejects ids that are unknown
1443
+ * (`E_VALIDATION`) or declared-but-unpublished (`E_NOT_IMPLEMENTED`),
1444
+ * guaranteeing the returned spec is safe to scaffold from.
1445
+ */
1446
+ function getRenderer(id) {
1447
+ if (!isRendererId(id)) throw new ZitadelError("E_VALIDATION", `Unknown renderer "${id}"`, { hint: `Available renderers: ${Object.keys(RENDERERS).join(", ")}` });
1448
+ const renderer = RENDERERS[id];
1449
+ if (renderer.status === "not-implemented") throw new ZitadelError("E_NOT_IMPLEMENTED", `Renderer "${id}" is declared but not yet published`, { hint: "Use --renderer react for now; the <zitadel-flow> web component ships in a later package." });
1450
+ return renderer;
1451
+ }
1452
+ //#endregion
1453
+ //#region src/lib/orca/patchers/rule/next/index.ts
1454
+ /**
1455
+ * Next.js request-boundary file at the project root. Wires `nextgenMiddleware` so the
1456
+ * generated project config's `/__nextgen` proxy path is same-origin proxied
1457
+ * to `ZITADEL_URL` and `/profile` is gated. Next 16 renamed this convention to
1458
+ * `proxy.ts`; older projects keep `middleware.ts`.
1459
+ * Carries the managed marker so `doctor --fix` reclaims it and `eject` removes it.
1460
+ */
1461
+ function requestBoundaryTemplate(functionName) {
1462
+ return `${MANAGED_MARKER}
1463
+ import { nextgenMiddleware } from "@zitadel/sdk-next/middleware";
1464
+ import type { NextRequest } from "next/server";
1465
+
1466
+ export function ${functionName}(req: NextRequest) {
1467
+ return nextgenMiddleware(req, {
1468
+ url: process.env.ZITADEL_URL,
1469
+ protectedRoutes: ["/profile"],
1470
+ loginPath: "/login",
1471
+ });
1472
+ }
1473
+
1474
+ export const config = {
1475
+ matcher: ["/__nextgen/:path*", "/profile/:path*"],
1476
+ };
1477
+ `;
1478
+ }
1479
+ /**
1480
+ * Rule-based patcher for the Next.js App Router. Inherits the shared
1481
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
1482
+ * Next routes and request boundary whose templates come from the chosen renderer.
1483
+ */
1484
+ var NextPatcher = class extends AbstractRulePatcher {
1485
+ /** Returns true for Next.js projects. */
1486
+ canPatch(framework) {
1487
+ return framework === "next";
1488
+ }
1489
+ routeOps(ctx) {
1490
+ return nextCodeOps(ctx, getRenderer(ctx.rendererId));
1491
+ }
1492
+ routeFiles(view) {
1493
+ return nextCodeFilePaths(view.framework, getRenderer(view.rendererId));
1494
+ }
1495
+ routeDeps(view) {
1496
+ return [getRenderer(view.rendererId).dependency.name];
1497
+ }
1498
+ summary(ctx) {
1499
+ return {
1500
+ title: "Next.js integration",
1501
+ detail: `Scaffolded login/register/profile routes with renderer "${ctx.rendererId}".`
1502
+ };
1503
+ }
1504
+ };
1505
+ /**
1506
+ * Ordered paths of the framework code files the patcher writes. All carry the
1507
+ * managed marker. Shared by {@link NextPatcher.routeOps} (which adds contents)
1508
+ * and {@link NextPatcher.routeFiles} (which only needs the paths) so the two
1509
+ * cannot drift.
1510
+ */
1511
+ function nextCodeFilePaths(framework, renderer) {
1512
+ const appDir = framework.appDir;
1513
+ const paths = [
1514
+ join(appDir, "page.tsx"),
1515
+ join(appDir, "login/page.tsx"),
1516
+ join(appDir, "register/page.tsx")
1517
+ ];
1518
+ if (renderer.templates.profilePage) paths.push(join(appDir, "profile/page.tsx"));
1519
+ paths.push(join(appDir, `../${requestBoundaryFile(framework).filename}`));
1520
+ if (renderer.templates.provider) paths.push(join(appDir, renderer.templates.provider.filename));
1521
+ if (renderer.templates.customElementsDts) paths.push(join(appDir, "../custom-elements.d.ts"));
1522
+ return paths;
1523
+ }
1524
+ /** The Next route/request-boundary write ops plus the SDK dependency. */
1525
+ function nextCodeOps(ctx, renderer) {
1526
+ const appDir = ctx.framework.appDir;
1527
+ const profile = renderer.templates.profilePage?.();
1528
+ const provider = renderer.templates.provider;
1529
+ const dts = renderer.templates.customElementsDts?.();
1530
+ const boundary = requestBoundaryFile(ctx.framework);
1531
+ return [
1532
+ ctx.scaffoldedFramework ? {
1533
+ kind: "edit",
1534
+ path: join(appDir, "page.tsx"),
1535
+ edit: () => homePageTemplate()
1536
+ } : void 0,
1537
+ {
1538
+ kind: "write",
1539
+ path: join(appDir, "login/page.tsx"),
1540
+ contents: renderer.templates.authPage("login").contents
1541
+ },
1542
+ {
1543
+ kind: "write",
1544
+ path: join(appDir, "register/page.tsx"),
1545
+ contents: renderer.templates.authPage("register").contents
1546
+ },
1547
+ profile ? {
1548
+ kind: "write",
1549
+ path: join(appDir, "profile/page.tsx"),
1550
+ contents: profile.contents
1551
+ } : void 0,
1552
+ {
1553
+ kind: "write",
1554
+ path: join(appDir, `../${boundary.filename}`),
1555
+ contents: requestBoundaryTemplate(boundary.functionName)
1556
+ },
1557
+ provider ? {
1558
+ kind: "write",
1559
+ path: join(appDir, provider.filename),
1560
+ contents: provider.contents
1561
+ } : void 0,
1562
+ dts ? {
1563
+ kind: "write",
1564
+ path: join(appDir, "../custom-elements.d.ts"),
1565
+ contents: dts.contents
1566
+ } : void 0,
1567
+ {
1568
+ kind: "merge-env",
1569
+ path: ".env.example",
1570
+ entries: { NEXT_PUBLIC_ZITADEL_PROJECT_ID: "" }
1571
+ },
1572
+ {
1573
+ kind: "merge-env",
1574
+ path: ".env.local",
1575
+ entries: { NEXT_PUBLIC_ZITADEL_PROJECT_ID: ctx.project.id }
1576
+ },
1577
+ {
1578
+ kind: "add-dep",
1579
+ name: renderer.dependency.name,
1580
+ version: dependencyVersionForCli(ctx.cliVersion, renderer.dependency.version)
1581
+ }
1582
+ ].filter((op) => op !== void 0);
1583
+ }
1584
+ function homePageTemplate() {
1585
+ return `${MANAGED_MARKER}
1586
+ import Link from "next/link";
1587
+
1588
+ export default function Home() {
1589
+ return (
1590
+ <main style={{ position: "fixed", inset: 0, padding: "48px", boxSizing: "border-box", display: "flex", alignItems: "center", justifyContent: "center", background: "#0f0f11", color: "#f4f4f6", fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", lineHeight: 1.5, letterSpacing: "normal", textAlign: "center" }}>
1591
+ <section style={{ width: "100%", maxWidth: "560px" }}>
1592
+ <p style={{ margin: "0 0 12px", color: "#9ca3af", fontSize: "14px" }}>Zitadel auth</p>
1593
+ <h1 style={{ margin: "0 0 24px", fontSize: "32px", lineHeight: 1.15, fontWeight: 600, color: "#f4f4f6" }}>Sign in, create an account, or open your profile.</h1>
1594
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "12px" }}>
1595
+ <Link href="/login" style={{ padding: "10px 16px", borderRadius: "8px", background: "#f4f4f6", color: "#0f0f11", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>
1596
+ Sign in
1597
+ </Link>
1598
+ <Link href="/register" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>
1599
+ Create account
1600
+ </Link>
1601
+ <Link href="/profile" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>
1602
+ Profile
1603
+ </Link>
1604
+ </div>
1605
+ </section>
1606
+ </main>
1607
+ );
1608
+ }
1609
+ `;
1610
+ }
1611
+ function requestBoundaryFile(framework) {
1612
+ if ((framework.versionMajor ?? 0) >= 16) return {
1613
+ filename: "proxy.ts",
1614
+ functionName: "proxy"
1615
+ };
1616
+ return {
1617
+ filename: "middleware.ts",
1618
+ functionName: "middleware"
1619
+ };
1620
+ }
1621
+ function dependencyVersionForCli(cliVersion, fallback) {
1622
+ const normalized = cliVersion.trim().replace(/^v/, "");
1623
+ if (/^\d+\.\d+\.\d+-alpha\.\d+$/.test(normalized)) return normalized;
1624
+ return normalized.match(/^\d+\.\d+\.\d+-([0-9A-Za-z]+)(?:[.-]|$)/)?.[1] ?? fallback;
1625
+ }
1626
+ //#endregion
1627
+ //#region src/lib/orca/patchers/rule/config-paths.ts
1628
+ /**
1629
+ * The module extensions the config edits can actually write, in resolution
1630
+ * priority. magicast injects ESM `import`/`import.meta.url`, so only ESM-capable
1631
+ * extensions are editable; CommonJS (`cts`/`cjs`) is not.
1632
+ */
1633
+ const CONFIG_EXTENSIONS = [
1634
+ "ts",
1635
+ "mts",
1636
+ "js",
1637
+ "mjs"
1638
+ ];
1639
+ /**
1640
+ * The CommonJS extensions we still *probe* (after the ESM ones) so a project
1641
+ * whose only config is `*.cjs`/`*.cts` is found and rejected with a targeted
1642
+ * "CommonJS is unsupported" error, rather than a misleading "file not found".
1643
+ * {@link parseConfigModule} surfaces that error when it sees CommonJS source.
1644
+ */
1645
+ const COMMONJS_EXTENSIONS = ["cts", "cjs"];
1646
+ /**
1647
+ * Candidate config filenames for `basename`, ESM extensions first then the
1648
+ * CommonJS ones, e.g. `configCandidates("vite.config")` → `["vite.config.ts",
1649
+ * "vite.config.mts", "vite.config.js", "vite.config.mjs", "vite.config.cts",
1650
+ * "vite.config.cjs"]`. Handed to the
1651
+ * generic `edit` file-op, which patches the first one that exists — an ESM
1652
+ * config wins, and a CommonJS-only project is still read so the edit can emit a
1653
+ * clear unsupported-format error.
1654
+ */
1655
+ function configCandidates(basename) {
1656
+ return [...CONFIG_EXTENSIONS, ...COMMONJS_EXTENSIONS].map((ext) => `${basename}.${ext}`);
1657
+ }
1658
+ //#endregion
1659
+ //#region src/lib/orca/patchers/rule/nuxt/nuxt-config.ts
1660
+ const NUXT_MODULE = "@zitadel/sdk-nuxt/module";
1661
+ /**
1662
+ * Builds the pure `edit` transform the file-writer applies to the project's Nuxt
1663
+ * config (`nuxt.config.*`): registers the `@zitadel/sdk-nuxt` module (which wires
1664
+ * the server-side proxy + session middleware), sets the login path, seeds
1665
+ * `runtimeConfig` with the backend URL, the proxy path, and the project id, and
1666
+ * marks the `zitadel-*` Lit elements as custom elements for the Vue compiler —
1667
+ * preserving the user's existing config via magicast. Idempotent. Throws
1668
+ * `E_VALIDATION` when the file is absent or `defineNuxtConfig` cannot be reached.
1669
+ */
1670
+ function nuxtConfigEdit(opts) {
1671
+ return (source) => {
1672
+ const label = "the Nuxt config (nuxt.config.*)";
1673
+ const mod = parseConfigModule(source, label);
1674
+ const config = resolveDefaultExportObject(mod, label);
1675
+ let changed = ensureArrayItem(config, "modules", NUXT_MODULE);
1676
+ const nextgen = ensureEditableObject(config, "nextgen");
1677
+ if (nextgen.url === void 0) {
1678
+ nextgen.url = builders.raw(`process.env.ZITADEL_URL ?? ${JSON.stringify(opts.server)}`);
1679
+ changed = true;
1680
+ }
1681
+ if (nextgen.loginPath === void 0) {
1682
+ nextgen.loginPath = "/login";
1683
+ changed = true;
1684
+ }
1685
+ if (nextgen.protectedRoutes === void 0) {
1686
+ nextgen.protectedRoutes = ["/profile"];
1687
+ changed = true;
1688
+ }
1689
+ const runtimeConfig = ensureEditableObject(config, "runtimeConfig");
1690
+ if (runtimeConfig.zitadelUrl === void 0) {
1691
+ runtimeConfig.zitadelUrl = builders.raw(`process.env.ZITADEL_URL ?? ${JSON.stringify(opts.server)}`);
1692
+ changed = true;
1693
+ }
1694
+ const publicConfig = ensureEditableObject(runtimeConfig, "public");
1695
+ if (publicConfig.nextgenProxyPath === void 0) {
1696
+ publicConfig.nextgenProxyPath = PROXY_PATH;
1697
+ changed = true;
1698
+ }
1699
+ if (publicConfig.zitadelProjectId === void 0) {
1700
+ publicConfig.zitadelProjectId = builders.raw(`process.env.NUXT_PUBLIC_ZITADEL_PROJECT_ID ?? ${JSON.stringify(opts.projectId)}`);
1701
+ changed = true;
1702
+ }
1703
+ const build = ensureEditableObject(config, "build");
1704
+ for (const dep of [
1705
+ "@zitadel/api",
1706
+ "@zitadel/components",
1707
+ "@zitadel/shared-component-styles",
1708
+ "@zitadel/design-tokens"
1709
+ ]) if (ensureArrayItem(build, "transpile", dep)) changed = true;
1710
+ const compilerOptions = ensureEditableObject(ensureEditableObject(config, "vue"), "compilerOptions");
1711
+ if (compilerOptions.isCustomElement === void 0) {
1712
+ compilerOptions.isCustomElement = builders.raw(`(tag) => tag.startsWith("zitadel-")`);
1713
+ changed = true;
1714
+ }
1715
+ if (!changed && source !== void 0) return source;
1716
+ const code = generateCode(mod).code;
1717
+ return code.endsWith("\n") ? code : `${code}\n`;
1718
+ };
1719
+ }
1720
+ //#endregion
1721
+ //#region src/lib/orca/patchers/rule/nuxt/templates.ts
1722
+ const MAIN_STYLE = "min-height: 100vh; background: #0f0f11";
1723
+ /** `app.vue` — renders the page router. Marker in an HTML comment. */
1724
+ function appVueTemplate() {
1725
+ return `<!-- ${MANAGED_MARKER} -->
1726
+ <template>
1727
+ <NuxtPage />
1728
+ </template>
1729
+
1730
+ <style>
1731
+ body {
1732
+ margin: 0;
1733
+ font-family: sans-serif;
1734
+ }
1735
+ </style>
1736
+ `;
1737
+ }
1738
+ /** `pages/index.vue` — the landing chooser linking to login/register/profile. */
1739
+ function indexPageTemplate() {
1740
+ return `<!-- ${MANAGED_MARKER} -->
1741
+ <template>
1742
+ <main style="position:fixed;inset:0;padding:48px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:#0f0f11;color:#f4f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.5;letter-spacing:normal;text-align:center">
1743
+ <section style="width:100%;max-width:560px">
1744
+ <p style="margin:0 0 12px;color:#9ca3af;font-size:14px">Zitadel auth</p>
1745
+ <h1 style="margin:0 0 24px;font-size:32px;line-height:1.15;font-weight:600;color:#f4f4f6">Sign in, create an account, or open your profile.</h1>
1746
+ <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center">
1747
+ <NuxtLink to="/login" style="padding:10px 16px;border-radius:8px;background:#f4f4f6;color:#0f0f11;text-decoration:none;font-weight:600;font-size:14px">Sign in</NuxtLink>
1748
+ <NuxtLink to="/register" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Create account</NuxtLink>
1749
+ <NuxtLink to="/profile" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Profile</NuxtLink>
1750
+ </div>
1751
+ </section>
1752
+ </main>
1753
+ </template>
1754
+ `;
1755
+ }
1756
+ /** A login/register page rendering `<zitadel-login>` inside `<ClientOnly>`. */
1757
+ function authPage(purpose) {
1758
+ return `<script setup lang="ts">
1759
+ ${MANAGED_MARKER}
1760
+ import { useZitadelProject } from "@zitadel/sdk-nuxt";
1761
+
1762
+ const project = useZitadelProject();
1763
+ <\/script>
1764
+
1765
+ <template>
1766
+ <main style="${MAIN_STYLE}">
1767
+ <ClientOnly>
1768
+ <zitadel-login
1769
+ :project="project"${purpose === "register" ? "\n purpose=\"register\"" : ""}
1770
+ post-sign-in-url="/profile"
1771
+ />
1772
+ </ClientOnly>
1773
+ </main>
1774
+ </template>
1775
+ `;
1776
+ }
1777
+ function loginPageTemplate() {
1778
+ return authPage("login");
1779
+ }
1780
+ function registerPageTemplate() {
1781
+ return authPage("register");
1782
+ }
1783
+ /** `pages/profile.vue` — the signed-in view with the logout widget. */
1784
+ function profilePageTemplate() {
1785
+ return `<script setup lang="ts">
1786
+ ${MANAGED_MARKER}
1787
+ import { useZitadelProject } from "@zitadel/sdk-nuxt";
1788
+
1789
+ const project = useZitadelProject();
1790
+ <\/script>
1791
+
1792
+ <template>
1793
+ <main style="padding: 24px">
1794
+ <h1>Signed in (Nuxt)</h1>
1795
+ <ClientOnly>
1796
+ <zitadel-logout :project="project" post-sign-out-url="/login" />
1797
+ </ClientOnly>
1798
+ </main>
1799
+ </template>
1800
+ `;
1801
+ }
1802
+ /** `plugins/zitadel-components.client.ts` — register the Lit elements client-side. */
1803
+ function componentsPluginTemplate() {
1804
+ return `${MANAGED_MARKER}
1805
+ // Register Lit custom elements on the client only. Importing @zitadel/components
1806
+ // from a page <script setup> would run during SSR and break the widgets.
1807
+ import "@zitadel/components";
1808
+
1809
+ export default defineNuxtPlugin(() => {});
1810
+ `;
1811
+ }
1812
+ /** `plugins/auth.server.ts` — seed the client auth state from the server context. */
1813
+ function authPluginTemplate() {
1814
+ return `${MANAGED_MARKER}
1815
+ import { defineNuxtPlugin, useRequestEvent, useState } from "#imports";
1816
+ import type { ClientAuthResult } from "@zitadel/sdk-nuxt";
1817
+
1818
+ export default defineNuxtPlugin(() => {
1819
+ const event = useRequestEvent();
1820
+ const auth = event?.context.nextgenAuth ?? {
1821
+ isAuthenticated: false as const,
1822
+ session: null,
1823
+ };
1824
+
1825
+ // Strip the raw JWT before seeding useState — it must not appear in the SSR
1826
+ // payload where client-side scripts could read it.
1827
+ const clientAuth: ClientAuthResult = auth.isAuthenticated
1828
+ ? {
1829
+ isAuthenticated: true,
1830
+ session: {
1831
+ userId: auth.session.userId,
1832
+ email: auth.session.email,
1833
+ name: auth.session.name,
1834
+ },
1835
+ }
1836
+ : { isAuthenticated: false, session: null };
1837
+
1838
+ useState<ClientAuthResult>("nextgen-auth", () => clientAuth);
1839
+ });
1840
+ `;
1841
+ }
1842
+ //#endregion
1843
+ //#region src/lib/orca/patchers/rule/nuxt/index.ts
1844
+ const SDK_DEPENDENCY$5 = "@zitadel/sdk-nuxt";
1845
+ const NUXT_CONFIG_PATHS = configCandidates("nuxt.config");
1846
+ /**
1847
+ * Rule-based patcher for a Nuxt app. Like Next.js, Nuxt proxies the backend and
1848
+ * verifies the session through server middleware — here the `@zitadel/sdk-nuxt`
1849
+ * module, registered via a non-destructive `nuxt.config.*` edit. Contributes
1850
+ * the login/register/profile pages (the raw `<zitadel-login>`/`<zitadel-logout>`
1851
+ * elements), the client/server plugins, the `app.vue` router, and the SDK dep.
1852
+ */
1853
+ var NuxtPatcher = class extends AbstractRulePatcher {
1854
+ canPatch(framework) {
1855
+ return framework === "nuxt";
1856
+ }
1857
+ routeOps(ctx) {
1858
+ const src = (rel) => join(ctx.framework.appDir, rel);
1859
+ return [
1860
+ {
1861
+ kind: "write",
1862
+ path: src("app.vue"),
1863
+ contents: appVueTemplate()
1864
+ },
1865
+ {
1866
+ kind: "write",
1867
+ path: src("pages/index.vue"),
1868
+ contents: indexPageTemplate()
1869
+ },
1870
+ {
1871
+ kind: "write",
1872
+ path: src("pages/login.vue"),
1873
+ contents: loginPageTemplate()
1874
+ },
1875
+ {
1876
+ kind: "write",
1877
+ path: src("pages/register.vue"),
1878
+ contents: registerPageTemplate()
1879
+ },
1880
+ {
1881
+ kind: "write",
1882
+ path: src("pages/profile.vue"),
1883
+ contents: profilePageTemplate()
1884
+ },
1885
+ {
1886
+ kind: "write",
1887
+ path: src("plugins/zitadel-components.client.ts"),
1888
+ contents: componentsPluginTemplate()
1889
+ },
1890
+ {
1891
+ kind: "write",
1892
+ path: src("plugins/auth.server.ts"),
1893
+ contents: authPluginTemplate()
1894
+ },
1895
+ {
1896
+ kind: "edit",
1897
+ path: [...NUXT_CONFIG_PATHS],
1898
+ edit: nuxtConfigEdit({
1899
+ projectId: ctx.project.id,
1900
+ server: ctx.server
1901
+ })
1902
+ },
1903
+ {
1904
+ kind: "merge-env",
1905
+ path: ".env.example",
1906
+ entries: { NUXT_PUBLIC_ZITADEL_PROJECT_ID: "" }
1907
+ },
1908
+ {
1909
+ kind: "merge-env",
1910
+ path: ".env.local",
1911
+ entries: { NUXT_PUBLIC_ZITADEL_PROJECT_ID: ctx.project.id }
1912
+ },
1913
+ {
1914
+ kind: "add-dep",
1915
+ name: SDK_DEPENDENCY$5,
1916
+ version: npmDistTagForCliVersion(ctx.cliVersion)
1917
+ }
1918
+ ];
1919
+ }
1920
+ routeFiles(view) {
1921
+ const src = (rel) => join(view.framework.appDir, rel);
1922
+ return [
1923
+ src("app.vue"),
1924
+ src("pages/index.vue"),
1925
+ src("pages/login.vue"),
1926
+ src("pages/register.vue"),
1927
+ src("pages/profile.vue"),
1928
+ src("plugins/zitadel-components.client.ts"),
1929
+ src("plugins/auth.server.ts")
1930
+ ];
1931
+ }
1932
+ routeDeps(_view) {
1933
+ return [SDK_DEPENDENCY$5];
1934
+ }
1935
+ routeConfigEdits(_view) {
1936
+ return ["nuxt.config.*"];
1937
+ }
1938
+ summary(_ctx) {
1939
+ return {
1940
+ title: "Nuxt integration",
1941
+ detail: "Wrote landing/login/register/profile pages + plugins and registered @zitadel/sdk-nuxt in nuxt.config.*."
1942
+ };
1943
+ }
1944
+ };
1945
+ //#endregion
1946
+ //#region src/lib/orca/patchers/rule/vite-support.ts
1947
+ /**
1948
+ * Shared Vite dev-server proxy merged into the project's Vite config for the SPA
1949
+ * frameworks (React, Vue, Solid, Svelte, Qwik). It forwards same-origin
1950
+ * `/__nextgen/*` calls to the
1951
+ * backend, strips the prefix, and attaches the project's service-key secret
1952
+ * (read from `ZITADEL_PROJECT_SECRET` in `.env.local`) as the bearer on every
1953
+ * proxied request. The secret stays server-side: Vite only exposes vars with the
1954
+ * configured `envPrefix` (default `VITE_`) to client bundles, so this server-only
1955
+ * key never leaks into the browser.
1956
+ */
1957
+ function proxyEntryCode(server) {
1958
+ return `{
1959
+ target: ${JSON.stringify(server)},
1960
+ changeOrigin: false,
1961
+ rewrite: (path) => path.replace(/^\\${PROXY_PATH}/, "").replace(/^(?!\\/)/, "/"),
1962
+ configure: (proxy) => {
1963
+ const secret = loadEnv("development", process.cwd(), "ZITADEL_").ZITADEL_PROJECT_SECRET;
1964
+ if (!secret) {
1965
+ throw new Error("ZITADEL_PROJECT_SECRET is not set; add it to .env.local (zitadel setup writes it).");
1966
+ }
1967
+ const bearer = \`Bearer \${secret}\`;
1968
+ proxy.on("proxyReq", (proxyReq) => {
1969
+ proxyReq.setHeader("authorization", bearer);
1970
+ });
1971
+ },
1972
+ }`;
1973
+ }
1974
+ /** The imports that the injected proxy entry depends on. */
1975
+ const PROXY_IMPORTS = [{
1976
+ from: "vite",
1977
+ imported: "loadEnv",
1978
+ local: "loadEnv"
1979
+ }];
1980
+ /**
1981
+ * Builds the pure `edit` transform the file-writer applies to the project's Vite
1982
+ * config (`vite.config.*`): a non-destructive magicast merge that adds the
1983
+ * `/__nextgen` proxy and sets `server.port`/`strictPort` when they are unset,
1984
+ * preserving the user's plugins, options, and formatting. Leaves `server.host`
1985
+ * alone so the user can still opt into network binding (`--host`/`host: true`);
1986
+ * the issuer/origin requirement is about the port, not the bind host. Idempotent
1987
+ * — entries already present are left as-is. Throws `E_VALIDATION` when the file
1988
+ * is absent or the config object cannot be reached (function-built/exotic
1989
+ * configs), with a hint to add the block manually.
1990
+ */
1991
+ function viteProxyEdit(devPort, server) {
1992
+ return (source) => {
1993
+ const label = "the Vite config (vite.config.*)";
1994
+ const mod = parseConfigModule(source, label);
1995
+ const config = resolveDefaultExportObject(mod, label);
1996
+ let changed = false;
1997
+ const serverConfig = ensureEditableObject(config, "server");
1998
+ if (serverConfig.port === void 0) {
1999
+ serverConfig.port = devPort;
2000
+ changed = true;
2001
+ }
2002
+ if (serverConfig.strictPort === void 0) {
2003
+ serverConfig.strictPort = true;
2004
+ changed = true;
2005
+ }
2006
+ const proxyConfig = ensureEditableObject(serverConfig, "proxy");
2007
+ if (proxyConfig["/__nextgen"] === void 0) {
2008
+ proxyConfig[PROXY_PATH] = builders.raw(proxyEntryCode(server));
2009
+ changed = true;
2010
+ }
2011
+ for (const imp of PROXY_IMPORTS) if (!importIsPresent(mod, imp.local, imp.from)) {
2012
+ mod.imports.$add({ ...imp });
2013
+ changed = true;
2014
+ }
2015
+ if (!changed && source !== void 0) return source;
2016
+ const code = generateCode(mod).code;
2017
+ return code.endsWith("\n") ? code : `${code}\n`;
2018
+ };
2019
+ }
2020
+ /**
2021
+ * Candidate Vite config filenames, in resolution priority. The patcher hands
2022
+ * this list to the generic `edit` file-op, which patches the first one that
2023
+ * exists — so any project layout (`vite.config.ts`, `.mts`, `.js`, …) is covered.
2024
+ */
2025
+ const VITE_CONFIG_PATHS = configCandidates("vite.config");
2026
+ /** Builds the shared Vite-config proxy {@link FileOp} for a {@link ViteSupport} patcher. */
2027
+ function buildViteProxyOp(devPort, server) {
2028
+ return {
2029
+ kind: "edit",
2030
+ path: [...VITE_CONFIG_PATHS],
2031
+ edit: viteProxyEdit(devPort, server)
2032
+ };
2033
+ }
2034
+ //#endregion
2035
+ //#region src/lib/orca/patchers/rule/qwik/templates.ts
2036
+ /**
2037
+ * The managed `src/app.tsx`: a minimal path-based router that renders the
2038
+ * `@zitadel/sdk-qwik` widgets — a landing chooser at `/`, login at `/login`,
2039
+ * register at `/register`, and the logout widget at `/profile`. Exports a named `App`
2040
+ * (`component$`) to match the create-vite Qwik entry (`main.tsx` imports
2041
+ * `{ App }`). The project id comes from `VITE_ZITADEL_PROJECT_ID`. No secret
2042
+ * reaches the browser: the dev proxy in `vite.config.*` attaches the project
2043
+ * service-key secret (from `ZITADEL_PROJECT_SECRET`) server-side.
2044
+ */
2045
+ function appTemplate$4() {
2046
+ return `${MANAGED_MARKER}
2047
+ import { component$ } from "@builder.io/qwik";
2048
+ import { ZitadelLogin, ZitadelLogout, configureZitadel } from "@zitadel/sdk-qwik";
2049
+
2050
+ const project = configureZitadel({
2051
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
2052
+ proxyPath: "${PROXY_PATH}",
2053
+ });
2054
+
2055
+ export const App = component$(() => {
2056
+ const path = window.location.pathname;
2057
+
2058
+ if (path === "/") {
2059
+ return (
2060
+ <main style={{ position: "fixed", inset: "0", padding: "48px", boxSizing: "border-box", display: "flex", alignItems: "center", justifyContent: "center", background: "#0f0f11", color: "#f4f4f6", fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", lineHeight: "1.5", letterSpacing: "normal", textAlign: "center" }}>
2061
+ <section style={{ width: "100%", maxWidth: "560px" }}>
2062
+ <p style={{ margin: "0 0 12px", color: "#9ca3af", fontSize: "14px" }}>Zitadel auth</p>
2063
+ <h1 style={{ margin: "0 0 24px", fontSize: "32px", lineHeight: "1.15", fontWeight: "600", color: "#f4f4f6" }}>Sign in, create an account, or open your profile.</h1>
2064
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "12px", justifyContent: "center" }}>
2065
+ <a href="/login" style={{ padding: "10px 16px", borderRadius: "8px", background: "#f4f4f6", color: "#0f0f11", textDecoration: "none", fontWeight: "600", fontSize: "14px" }}>Sign in</a>
2066
+ <a href="/register" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: "600", fontSize: "14px" }}>Create account</a>
2067
+ <a href="/profile" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: "600", fontSize: "14px" }}>Profile</a>
2068
+ </div>
2069
+ </section>
2070
+ </main>
2071
+ );
2072
+ }
2073
+ if (path.startsWith("/profile")) {
2074
+ return (
2075
+ <div style={{ position: "fixed", inset: "0", overflow: "auto", background: "#0f0f11" }}>
2076
+ <ZitadelLogout project={project} postSignOutUrl="/login" />
2077
+ </div>
2078
+ );
2079
+ }
2080
+ if (path.startsWith("/register")) {
2081
+ return (
2082
+ <div style={{ position: "fixed", inset: "0", overflow: "auto", background: "#0f0f11" }}>
2083
+ <ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
2084
+ </div>
2085
+ );
2086
+ }
2087
+ return (
2088
+ <div style={{ position: "fixed", inset: "0", overflow: "auto", background: "#0f0f11" }}>
2089
+ <ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
2090
+ </div>
2091
+ );
2092
+ });
2093
+ `;
2094
+ }
2095
+ //#endregion
2096
+ //#region src/lib/orca/patchers/rule/qwik/index.ts
2097
+ const SDK_DEPENDENCY$4 = "@zitadel/sdk-qwik";
2098
+ /**
2099
+ * Rule-based patcher for a Vite + Qwik single-page app. Inherits the shared
2100
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
2101
+ * managed `src/app.tsx` auth entry, a non-destructive `vite.config.*` merge
2102
+ * that adds the `/__nextgen` dev proxy (attaching the project secret from
2103
+ * `ZITADEL_PROJECT_SECRET` to every proxied request), the `VITE_`-prefixed
2104
+ * project id, and the SDK dep.
2105
+ *
2106
+ * The create-vite Qwik template uses a lowercase `src/app.tsx` exporting a named
2107
+ * `App` (mounted by `main.tsx`), so this patcher writes that exact entry. Unlike
2108
+ * Next.js — whose middleware runs the proxy server-side — a SPA has no server,
2109
+ * so the dev proxy stands in for `@zitadel/edge-proxy` locally. Production
2110
+ * deployments still need that proxy.
2111
+ */
2112
+ var QwikPatcher = class extends AbstractRulePatcher {
2113
+ canPatch(framework) {
2114
+ return framework === "qwik";
2115
+ }
2116
+ viteProxyOp(devPort, server) {
2117
+ return buildViteProxyOp(devPort, server);
2118
+ }
2119
+ routeOps(ctx) {
2120
+ return [
2121
+ {
2122
+ kind: "write",
2123
+ path: "src/app.tsx",
2124
+ contents: appTemplate$4()
2125
+ },
2126
+ this.viteProxyOp(ctx.framework.devPort, ctx.server),
2127
+ {
2128
+ kind: "merge-env",
2129
+ path: ".env.example",
2130
+ entries: { VITE_ZITADEL_PROJECT_ID: "" }
2131
+ },
2132
+ {
2133
+ kind: "merge-env",
2134
+ path: ".env.local",
2135
+ entries: { VITE_ZITADEL_PROJECT_ID: ctx.project.id }
2136
+ },
2137
+ {
2138
+ kind: "add-dep",
2139
+ name: SDK_DEPENDENCY$4,
2140
+ version: npmDistTagForCliVersion(ctx.cliVersion)
2141
+ }
2142
+ ];
2143
+ }
2144
+ routeFiles(_view) {
2145
+ return ["src/app.tsx"];
2146
+ }
2147
+ routeDeps(_view) {
2148
+ return [SDK_DEPENDENCY$4];
2149
+ }
2150
+ routeConfigEdits(_view) {
2151
+ return ["vite.config.*"];
2152
+ }
2153
+ summary(_ctx) {
2154
+ return {
2155
+ title: "Qwik (Vite) integration",
2156
+ detail: "Wrote src/app.tsx auth entry and merged the /__nextgen dev proxy into vite.config.*."
2157
+ };
2158
+ }
2159
+ };
2160
+ //#endregion
2161
+ //#region src/lib/orca/patchers/rule/react/templates.ts
2162
+ /**
2163
+ * The managed `src/App.tsx`: a minimal path-based router that renders the
2164
+ * `@zitadel/sdk-react` widgets — a landing chooser at `/`, login at `/login`,
2165
+ * register at `/register`, and the logout widget at `/profile`. The project id comes from
2166
+ * `VITE_ZITADEL_PROJECT_ID` (Vite only exposes `VITE_`-prefixed env to the
2167
+ * client). No secret reaches the browser: the dev proxy in `vite.config.*`
2168
+ * attaches the project service-key secret (from `ZITADEL_PROJECT_SECRET`)
2169
+ * server-side.
2170
+ */
2171
+ function appTemplate$3() {
2172
+ return `${MANAGED_MARKER}
2173
+ import { ZitadelLogin, ZitadelLogout, configureZitadel } from "@zitadel/sdk-react";
2174
+
2175
+ const project = configureZitadel({
2176
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
2177
+ proxyPath: "${PROXY_PATH}",
2178
+ });
2179
+
2180
+ export default function App() {
2181
+ const path = window.location.pathname;
2182
+
2183
+ if (path === "/") {
2184
+ return (
2185
+ <main style={{ position: "fixed", inset: 0, padding: "48px", boxSizing: "border-box", display: "flex", alignItems: "center", justifyContent: "center", background: "#0f0f11", color: "#f4f4f6", fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", lineHeight: 1.5, letterSpacing: "normal", textAlign: "center" }}>
2186
+ <section style={{ width: "100%", maxWidth: "560px" }}>
2187
+ <p style={{ margin: "0 0 12px", color: "#9ca3af", fontSize: "14px" }}>Zitadel auth</p>
2188
+ <h1 style={{ margin: "0 0 24px", fontSize: "32px", lineHeight: 1.15, fontWeight: 600, color: "#f4f4f6" }}>Sign in, create an account, or open your profile.</h1>
2189
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "12px", justifyContent: "center" }}>
2190
+ <a href="/login" style={{ padding: "10px 16px", borderRadius: "8px", background: "#f4f4f6", color: "#0f0f11", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>Sign in</a>
2191
+ <a href="/register" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>Create account</a>
2192
+ <a href="/profile" style={{ padding: "10px 16px", borderRadius: "8px", border: "1px solid #3f3f46", color: "#f4f4f6", textDecoration: "none", fontWeight: 600, fontSize: "14px" }}>Profile</a>
2193
+ </div>
2194
+ </section>
2195
+ </main>
2196
+ );
2197
+ }
2198
+ if (path.startsWith("/profile")) {
2199
+ return (
2200
+ <div style={{ position: "fixed", inset: 0, overflow: "auto", background: "#0f0f11" }}>
2201
+ <ZitadelLogout project={project} postSignOutUrl="/login" />
2202
+ </div>
2203
+ );
2204
+ }
2205
+ if (path.startsWith("/register")) {
2206
+ return (
2207
+ <div style={{ position: "fixed", inset: 0, overflow: "auto", background: "#0f0f11" }}>
2208
+ <ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
2209
+ </div>
2210
+ );
2211
+ }
2212
+ return (
2213
+ <div style={{ position: "fixed", inset: 0, overflow: "auto", background: "#0f0f11" }}>
2214
+ <ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
2215
+ </div>
2216
+ );
2217
+ }
2218
+ `;
2219
+ }
2220
+ //#endregion
2221
+ //#region src/lib/orca/patchers/rule/react/index.ts
2222
+ const SDK_DEPENDENCY$3 = "@zitadel/sdk-react";
2223
+ /**
2224
+ * Rule-based patcher for a Vite + React single-page app. Inherits the shared
2225
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
2226
+ * managed `src/App.tsx` auth entry, a non-destructive `vite.config.*` merge
2227
+ * that adds the `/__nextgen` dev proxy (attaching the project secret from
2228
+ * `ZITADEL_PROJECT_SECRET` to every proxied request), the `VITE_`-prefixed
2229
+ * project id, and the SDK dep.
2230
+ *
2231
+ * Unlike Next.js — whose middleware runs the proxy and token exchange
2232
+ * server-side — a SPA has no server, so the dev proxy stands in for
2233
+ * `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
2234
+ */
2235
+ var ReactPatcher = class extends AbstractRulePatcher {
2236
+ canPatch(framework) {
2237
+ return framework === "react";
2238
+ }
2239
+ viteProxyOp(devPort, server) {
2240
+ return buildViteProxyOp(devPort, server);
2241
+ }
2242
+ routeOps(ctx) {
2243
+ return [
2244
+ {
2245
+ kind: "write",
2246
+ path: "src/App.tsx",
2247
+ contents: appTemplate$3()
2248
+ },
2249
+ this.viteProxyOp(ctx.framework.devPort, ctx.server),
2250
+ {
2251
+ kind: "merge-env",
2252
+ path: ".env.example",
2253
+ entries: { VITE_ZITADEL_PROJECT_ID: "" }
2254
+ },
2255
+ {
2256
+ kind: "merge-env",
2257
+ path: ".env.local",
2258
+ entries: { VITE_ZITADEL_PROJECT_ID: ctx.project.id }
2259
+ },
2260
+ {
2261
+ kind: "add-dep",
2262
+ name: SDK_DEPENDENCY$3,
2263
+ version: npmDistTagForCliVersion(ctx.cliVersion)
2264
+ }
2265
+ ];
2266
+ }
2267
+ routeFiles(_view) {
2268
+ return ["src/App.tsx"];
2269
+ }
2270
+ routeDeps(_view) {
2271
+ return [SDK_DEPENDENCY$3];
2272
+ }
2273
+ routeConfigEdits(_view) {
2274
+ return ["vite.config.*"];
2275
+ }
2276
+ summary(_ctx) {
2277
+ return {
2278
+ title: "React (Vite) integration",
2279
+ detail: "Wrote src/App.tsx auth entry and merged the /__nextgen dev proxy into vite.config.*."
2280
+ };
2281
+ }
2282
+ };
2283
+ //#endregion
2284
+ //#region src/lib/orca/patchers/rule/solid/templates.ts
2285
+ /**
2286
+ * The managed `src/App.tsx`: a minimal path-based router that renders the
2287
+ * `@zitadel/sdk-solid` widgets — a landing chooser at `/`, login at `/login`,
2288
+ * register at `/register`, and the logout widget at `/profile`. The project id comes from
2289
+ * `VITE_ZITADEL_PROJECT_ID` (Vite only exposes `VITE_`-prefixed env to the
2290
+ * client). No secret reaches the browser: the dev proxy in `vite.config.*`
2291
+ * attaches the project service-key secret (from `ZITADEL_PROJECT_SECRET`)
2292
+ * server-side.
2293
+ */
2294
+ function appTemplate$2() {
2295
+ return `${MANAGED_MARKER}
2296
+ import { ZitadelLogin, ZitadelLogout, configureZitadel } from "@zitadel/sdk-solid";
2297
+
2298
+ const project = configureZitadel({
2299
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
2300
+ proxyPath: "${PROXY_PATH}",
2301
+ });
2302
+
2303
+ export default function App() {
2304
+ const path = window.location.pathname;
2305
+
2306
+ if (path === "/") {
2307
+ return (
2308
+ <main style="position:fixed;inset:0;padding:48px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:#0f0f11;color:#f4f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.5;letter-spacing:normal;text-align:center">
2309
+ <section style="width:100%;max-width:560px">
2310
+ <p style="margin:0 0 12px;color:#9ca3af;font-size:14px">Zitadel auth</p>
2311
+ <h1 style="margin:0 0 24px;font-size:32px;line-height:1.15;font-weight:600;color:#f4f4f6">Sign in, create an account, or open your profile.</h1>
2312
+ <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center">
2313
+ <a href="/login" style="padding:10px 16px;border-radius:8px;background:#f4f4f6;color:#0f0f11;text-decoration:none;font-weight:600;font-size:14px">Sign in</a>
2314
+ <a href="/register" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Create account</a>
2315
+ <a href="/profile" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Profile</a>
2316
+ </div>
2317
+ </section>
2318
+ </main>
2319
+ );
2320
+ }
2321
+ if (path.startsWith("/profile")) {
2322
+ return (
2323
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2324
+ <ZitadelLogout project={project} postSignOutUrl="/login" />
2325
+ </div>
2326
+ );
2327
+ }
2328
+ if (path.startsWith("/register")) {
2329
+ return (
2330
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2331
+ <ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
2332
+ </div>
2333
+ );
2334
+ }
2335
+ return (
2336
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2337
+ <ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
2338
+ </div>
2339
+ );
2340
+ }
2341
+ `;
2342
+ }
2343
+ //#endregion
2344
+ //#region src/lib/orca/patchers/rule/solid/index.ts
2345
+ const SDK_DEPENDENCY$2 = "@zitadel/sdk-solid";
2346
+ /**
2347
+ * Rule-based patcher for a Vite + Solid single-page app. Inherits the shared
2348
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
2349
+ * managed `src/App.tsx` auth entry, a non-destructive `vite.config.*` merge
2350
+ * that adds the `/__nextgen` dev proxy (attaching the project secret from
2351
+ * `ZITADEL_PROJECT_SECRET` to every proxied request), the `VITE_`-prefixed
2352
+ * project id, and the SDK dep.
2353
+ *
2354
+ * Unlike Next.js — whose middleware runs the proxy and token exchange
2355
+ * server-side — a SPA has no server, so the dev proxy stands in for
2356
+ * `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
2357
+ */
2358
+ var SolidPatcher = class extends AbstractRulePatcher {
2359
+ canPatch(framework) {
2360
+ return framework === "solid";
2361
+ }
2362
+ viteProxyOp(devPort, server) {
2363
+ return buildViteProxyOp(devPort, server);
2364
+ }
2365
+ routeOps(ctx) {
2366
+ return [
2367
+ {
2368
+ kind: "write",
2369
+ path: "src/App.tsx",
2370
+ contents: appTemplate$2()
2371
+ },
2372
+ this.viteProxyOp(ctx.framework.devPort, ctx.server),
2373
+ {
2374
+ kind: "merge-env",
2375
+ path: ".env.example",
2376
+ entries: { VITE_ZITADEL_PROJECT_ID: "" }
2377
+ },
2378
+ {
2379
+ kind: "merge-env",
2380
+ path: ".env.local",
2381
+ entries: { VITE_ZITADEL_PROJECT_ID: ctx.project.id }
2382
+ },
2383
+ {
2384
+ kind: "add-dep",
2385
+ name: SDK_DEPENDENCY$2,
2386
+ version: npmDistTagForCliVersion(ctx.cliVersion)
2387
+ }
2388
+ ];
2389
+ }
2390
+ routeFiles(_view) {
2391
+ return ["src/App.tsx"];
2392
+ }
2393
+ routeDeps(_view) {
2394
+ return [SDK_DEPENDENCY$2];
2395
+ }
2396
+ routeConfigEdits(_view) {
2397
+ return ["vite.config.*"];
2398
+ }
2399
+ summary(_ctx) {
2400
+ return {
2401
+ title: "Solid (Vite) integration",
2402
+ detail: "Wrote src/App.tsx auth entry and merged the /__nextgen dev proxy into vite.config.*."
2403
+ };
2404
+ }
2405
+ };
2406
+ //#endregion
2407
+ //#region src/lib/orca/patchers/rule/svelte/templates.ts
2408
+ /**
2409
+ * The managed `src/App.svelte`: a minimal path-based router that renders the
2410
+ * `@zitadel/sdk-svelte` widgets — a landing chooser at `/`, login at `/login`,
2411
+ * register at `/register`, and the logout widget at `/profile`. The managed marker lives in
2412
+ * the `<script lang="ts">` block (a JS comment) so eject/doctor stay
2413
+ * marker-aware. The project id comes from `VITE_ZITADEL_PROJECT_ID`. No secret
2414
+ * reaches the browser: the dev proxy in `vite.config.*` attaches the project
2415
+ * service-key secret (from `ZITADEL_PROJECT_SECRET`) server-side.
2416
+ */
2417
+ function appTemplate$1() {
2418
+ return `<script lang="ts">
2419
+ ${MANAGED_MARKER}
2420
+ import { ZitadelLogin, ZitadelLogout, configureZitadel } from "@zitadel/sdk-svelte";
2421
+
2422
+ const project = configureZitadel({
2423
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
2424
+ proxyPath: "${PROXY_PATH}",
2425
+ });
2426
+
2427
+ const path = window.location.pathname;
2428
+ <\/script>
2429
+
2430
+ {#if path === "/"}
2431
+ <main style="position:fixed;inset:0;padding:48px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:#0f0f11;color:#f4f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.5;letter-spacing:normal;text-align:center">
2432
+ <section style="width:100%;max-width:560px">
2433
+ <p style="margin:0 0 12px;color:#9ca3af;font-size:14px">Zitadel auth</p>
2434
+ <h1 style="margin:0 0 24px;font-size:32px;line-height:1.15;font-weight:600;color:#f4f4f6">Sign in, create an account, or open your profile.</h1>
2435
+ <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center">
2436
+ <a href="/login" style="padding:10px 16px;border-radius:8px;background:#f4f4f6;color:#0f0f11;text-decoration:none;font-weight:600;font-size:14px">Sign in</a>
2437
+ <a href="/register" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Create account</a>
2438
+ <a href="/profile" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Profile</a>
2439
+ </div>
2440
+ </section>
2441
+ </main>
2442
+ {:else if path.startsWith("/profile")}
2443
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2444
+ <ZitadelLogout {project} postSignOutUrl="/login" />
2445
+ </div>
2446
+ {:else if path.startsWith("/register")}
2447
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2448
+ <ZitadelLogin {project} purpose="register" postSignInUrl="/profile" />
2449
+ </div>
2450
+ {:else}
2451
+ <div style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2452
+ <ZitadelLogin {project} purpose="login" postSignInUrl="/profile" />
2453
+ </div>
2454
+ {/if}
2455
+ `;
2456
+ }
2457
+ //#endregion
2458
+ //#region src/lib/orca/patchers/rule/svelte/index.ts
2459
+ const SDK_DEPENDENCY$1 = "@zitadel/sdk-svelte";
2460
+ /**
2461
+ * Rule-based patcher for a Vite + Svelte single-page app. Inherits the shared
2462
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
2463
+ * managed `src/App.svelte` auth entry, a non-destructive `vite.config.*` merge
2464
+ * that adds the `/__nextgen` dev proxy (attaching the project secret from
2465
+ * `ZITADEL_PROJECT_SECRET` to every proxied request), the `VITE_`-prefixed
2466
+ * project id, and the SDK dep.
2467
+ *
2468
+ * Unlike Next.js — whose middleware runs the proxy and token exchange
2469
+ * server-side — a SPA has no server, so the dev proxy stands in for
2470
+ * `@zitadel/edge-proxy` locally. Production deployments still need that proxy.
2471
+ */
2472
+ var SveltePatcher = class extends AbstractRulePatcher {
2473
+ canPatch(framework) {
2474
+ return framework === "svelte";
2475
+ }
2476
+ viteProxyOp(devPort, server) {
2477
+ return buildViteProxyOp(devPort, server);
2478
+ }
2479
+ routeOps(ctx) {
2480
+ return [
2481
+ {
2482
+ kind: "write",
2483
+ path: "src/App.svelte",
2484
+ contents: appTemplate$1()
2485
+ },
2486
+ this.viteProxyOp(ctx.framework.devPort, ctx.server),
2487
+ {
2488
+ kind: "merge-env",
2489
+ path: ".env.example",
2490
+ entries: { VITE_ZITADEL_PROJECT_ID: "" }
2491
+ },
2492
+ {
2493
+ kind: "merge-env",
2494
+ path: ".env.local",
2495
+ entries: { VITE_ZITADEL_PROJECT_ID: ctx.project.id }
2496
+ },
2497
+ {
2498
+ kind: "add-dep",
2499
+ name: SDK_DEPENDENCY$1,
2500
+ version: npmDistTagForCliVersion(ctx.cliVersion)
2501
+ }
2502
+ ];
2503
+ }
2504
+ routeFiles(_view) {
2505
+ return ["src/App.svelte"];
2506
+ }
2507
+ routeDeps(_view) {
2508
+ return [SDK_DEPENDENCY$1];
2509
+ }
2510
+ routeConfigEdits(_view) {
2511
+ return ["vite.config.*"];
2512
+ }
2513
+ summary(_ctx) {
2514
+ return {
2515
+ title: "Svelte (Vite) integration",
2516
+ detail: "Wrote src/App.svelte auth entry and merged the /__nextgen dev proxy into vite.config.*."
2517
+ };
2518
+ }
2519
+ };
2520
+ //#endregion
2521
+ //#region src/lib/orca/patchers/rule/vue/templates.ts
2522
+ /**
2523
+ * The managed `src/App.vue`: a minimal path-based router that renders the
2524
+ * `@zitadel/sdk-vue` widgets — a landing chooser at `/`, login at `/login`,
2525
+ * register at `/register`, and the logout widget at `/profile`. The managed marker lives in
2526
+ * the `<script setup>` block (a JS comment) so eject/doctor stay marker-aware.
2527
+ * The project id comes from `VITE_ZITADEL_PROJECT_ID`. No secret reaches the
2528
+ * browser: the dev proxy in `vite.config.*` attaches the project service-key
2529
+ * secret (from `ZITADEL_PROJECT_SECRET`) server-side.
2530
+ */
2531
+ function appTemplate() {
2532
+ return `<script setup lang="ts">
2533
+ ${MANAGED_MARKER}
2534
+ import { ZitadelLogin, ZitadelLogout, configureZitadel } from "@zitadel/sdk-vue";
2535
+
2536
+ const project = configureZitadel({
2537
+ projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
2538
+ proxyPath: "${PROXY_PATH}",
2539
+ });
2540
+
2541
+ const path = window.location.pathname;
2542
+ <\/script>
2543
+
2544
+ <template>
2545
+ <main
2546
+ v-if="path === '/'"
2547
+ style="position:fixed;inset:0;padding:48px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background:#0f0f11;color:#f4f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.5;letter-spacing:normal;text-align:center"
2548
+ >
2549
+ <section style="width:100%;max-width:560px">
2550
+ <p style="margin:0 0 12px;color:#9ca3af;font-size:14px">Zitadel auth</p>
2551
+ <h1 style="margin:0 0 24px;font-size:32px;line-height:1.15;font-weight:600;color:#f4f4f6">Sign in, create an account, or open your profile.</h1>
2552
+ <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center">
2553
+ <a href="/login" style="padding:10px 16px;border-radius:8px;background:#f4f4f6;color:#0f0f11;text-decoration:none;font-weight:600;font-size:14px">Sign in</a>
2554
+ <a href="/register" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Create account</a>
2555
+ <a href="/profile" style="padding:10px 16px;border-radius:8px;border:1px solid #3f3f46;color:#f4f4f6;text-decoration:none;font-weight:600;font-size:14px">Profile</a>
2556
+ </div>
2557
+ </section>
2558
+ </main>
2559
+ <div
2560
+ v-else-if="path.startsWith('/profile')"
2561
+ style="position:fixed;inset:0;overflow:auto;background:#0f0f11"
2562
+ >
2563
+ <ZitadelLogout :project="project" postSignOutUrl="/login" />
2564
+ </div>
2565
+ <div
2566
+ v-else-if="path.startsWith('/register')"
2567
+ style="position:fixed;inset:0;overflow:auto;background:#0f0f11"
2568
+ >
2569
+ <ZitadelLogin :project="project" purpose="register" postSignInUrl="/profile" />
2570
+ </div>
2571
+ <div v-else style="position:fixed;inset:0;overflow:auto;background:#0f0f11">
2572
+ <ZitadelLogin :project="project" purpose="login" postSignInUrl="/profile" />
2573
+ </div>
2574
+ </template>
2575
+ `;
2576
+ }
2577
+ //#endregion
2578
+ //#region src/lib/orca/patchers/rule/vue/index.ts
2579
+ const SDK_DEPENDENCY = "@zitadel/sdk-vue";
2580
+ /**
2581
+ * Rule-based patcher for a Vite + Vue single-page app. Inherits the shared
2582
+ * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
2583
+ * managed `src/App.vue` auth entry, a non-destructive `vite.config.*` merge
2584
+ * that adds the `/__nextgen` dev proxy (attaching the project secret from
2585
+ * `ZITADEL_PROJECT_SECRET` to every proxied request), the `VITE_`-prefixed
2586
+ * project id, and the SDK dep.
2587
+ */
2588
+ var VuePatcher = class extends AbstractRulePatcher {
2589
+ canPatch(framework) {
2590
+ return framework === "vue";
2591
+ }
2592
+ viteProxyOp(devPort, server) {
2593
+ return buildViteProxyOp(devPort, server);
2594
+ }
2595
+ routeOps(ctx) {
2596
+ return [
2597
+ {
2598
+ kind: "write",
2599
+ path: "src/App.vue",
2600
+ contents: appTemplate()
2601
+ },
2602
+ this.viteProxyOp(ctx.framework.devPort, ctx.server),
2603
+ {
2604
+ kind: "merge-env",
2605
+ path: ".env.example",
2606
+ entries: { VITE_ZITADEL_PROJECT_ID: "" }
2607
+ },
2608
+ {
2609
+ kind: "merge-env",
2610
+ path: ".env.local",
2611
+ entries: { VITE_ZITADEL_PROJECT_ID: ctx.project.id }
2612
+ },
2613
+ {
2614
+ kind: "add-dep",
2615
+ name: SDK_DEPENDENCY,
2616
+ version: npmDistTagForCliVersion(ctx.cliVersion)
2617
+ }
2618
+ ];
2619
+ }
2620
+ routeFiles(_view) {
2621
+ return ["src/App.vue"];
2622
+ }
2623
+ routeDeps(_view) {
2624
+ return [SDK_DEPENDENCY];
2625
+ }
2626
+ routeConfigEdits(_view) {
2627
+ return ["vite.config.*"];
2628
+ }
2629
+ summary(_ctx) {
2630
+ return {
2631
+ title: "Vue (Vite) integration",
2632
+ detail: "Wrote src/App.vue auth entry and merged the /__nextgen dev proxy into vite.config.*."
2633
+ };
2634
+ }
2635
+ };
2636
+ //#endregion
2637
+ //#region src/lib/orca/patchers/index.ts
2638
+ /**
2639
+ * Active patchers, in priority order; the first whose `canPatch` matches wins.
2640
+ *
2641
+ * Patchers are grouped by family under subdirectories: `rule/` holds the
2642
+ * deterministic, template-driven patchers (extending
2643
+ * {@link import("./rule/base").AbstractRulePatcher}). A future LLM-driven
2644
+ * family lives under `llm/` and registers its concrete patchers here — no
2645
+ * orchestrator or command changes needed. Only Next.js is supported today.
2646
+ */
2647
+ const patchers = [
2648
+ new NextPatcher(),
2649
+ new NuxtPatcher(),
2650
+ new ReactPatcher(),
2651
+ new VuePatcher(),
2652
+ new SolidPatcher(),
2653
+ new SveltePatcher(),
2654
+ new QwikPatcher(),
2655
+ new AngularPatcher()
2656
+ ];
2657
+ //#endregion
2658
+ //#region src/lib/orca/scaffolders/cli.ts
2659
+ /**
2660
+ * Base for scaffolders that delegate to an external CLI (e.g. create-next-app).
2661
+ * Subclasses implement {@link scaffold} and call {@link runCommand}.
2662
+ */
2663
+ var AbstractCLIScaffolder = class {
2664
+ /** True when the requested framework is in {@link supportedFrameworks}. */
2665
+ canScaffold(framework) {
2666
+ return this.supportedFrameworks.includes(framework);
2667
+ }
2668
+ /**
2669
+ * Runs an external command in `cwd`, throwing a typed {@link ZitadelError} on
2670
+ * failure so the cause surfaces as a categorized CLI error. Distinguishes
2671
+ * "binary not on PATH" (`ENOENT` from the spawn itself) from "binary ran but
2672
+ * exited non-zero" — the former previously got masked as a generic
2673
+ * `exited with status 1`, leaving users to guess. Tests stub
2674
+ * `node:child_process` to assert the command without spawning.
2675
+ */
2676
+ runCommand(command, args, cwd) {
2677
+ const result = spawnSync(command, [...args], {
2678
+ cwd,
2679
+ encoding: "utf8"
2680
+ });
2681
+ if (result.error) {
2682
+ const err = result.error;
2683
+ const notFound = err.code === "ENOENT";
2684
+ throw new ZitadelError("E_VALIDATION", notFound ? `Command not found: ${command}` : `Failed to spawn "${command}": ${err.message}`, {
2685
+ hint: notFound ? `Ensure '${command}' is installed and on PATH.` : void 0,
2686
+ details: {
2687
+ command,
2688
+ args: [...args],
2689
+ code: err.code
2690
+ }
2691
+ });
2692
+ }
2693
+ const status = result.status ?? 1;
2694
+ if (status !== 0) {
2695
+ const stdout = String(result.stdout ?? "");
2696
+ const stderr = String(result.stderr ?? "");
2697
+ const output = truncateCommandOutput([stderr, stdout].filter(Boolean).join("\n").trim());
2698
+ throw new ZitadelError("E_VALIDATION", `Command "${command} ${args.join(" ")}" exited with status ${String(status)}`, {
2699
+ hint: output ? `Command output:\n${output}` : "Run the command directly for more detail.",
2700
+ details: {
2701
+ command,
2702
+ args: [...args],
2703
+ cwd,
2704
+ stdout,
2705
+ stderr
2706
+ }
2707
+ });
2708
+ }
2709
+ }
2710
+ };
2711
+ function truncateCommandOutput(output) {
2712
+ const limit = 4e3;
2713
+ if (output.length <= limit) return output;
2714
+ return `${output.slice(0, limit)}\n... output truncated ...`;
2715
+ }
2716
+ //#endregion
2717
+ //#region src/lib/orca/scaffolders/angular.ts
2718
+ /**
2719
+ * Derives a valid Angular project name from the target directory. `ng new`
2720
+ * validates the name against `^[a-zA-Z0-9-~][a-zA-Z0-9-._~]*$` and rejects `.`,
2721
+ * so we slugify the directory's basename (lowercase, non-alphanumerics → `-`)
2722
+ * and guarantee a leading letter by prefixing `app-` when the slug does not
2723
+ * start with one (`app-zitadel` when the basename slugifies to nothing).
2724
+ */
2725
+ function angularProjectName(cwd) {
2726
+ const slug = basename(cwd).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
2727
+ return /^[a-z]/.test(slug) ? slug : `app-${slug || "zitadel"}`;
2728
+ }
2729
+ /**
2730
+ * Scaffolds a new Angular app with the Angular CLI, then removes the starter
2731
+ * `app.ts`/`app.html` root component (and its now-unreferenced `app.css`) so the
2732
+ * patcher can write the managed ones without colliding with boilerplate. The
2733
+ * managed component uses only `templateUrl`, so `app.css` would otherwise be a
2734
+ * dangling file eject never cleans up. Unlike `create-vite`/`nuxi`, `ng new`
2735
+ * needs a real project name plus `--directory .` to populate the current dir.
2736
+ * Requires a Node version Angular supports (^22.22.3 || ^24.15.0 || >=26).
2737
+ */
2738
+ var AngularScaffolder = class extends AbstractCLIScaffolder {
2739
+ displayName = "Angular";
2740
+ supportedFrameworks = ["angular"];
2741
+ async scaffold(cwd, _framework) {
2742
+ this.runCommand("npx", [
2743
+ "-y",
2744
+ "@angular/cli@latest",
2745
+ "new",
2746
+ angularProjectName(cwd),
2747
+ "--directory",
2748
+ ".",
2749
+ "--defaults",
2750
+ "--style=css",
2751
+ "--ssr=false",
2752
+ "--skip-git"
2753
+ ], cwd);
2754
+ await rm(join(cwd, "src/app/app.ts"), { force: true });
2755
+ await rm(join(cwd, "src/app/app.html"), { force: true });
2756
+ await rm(join(cwd, "src/app/app.css"), { force: true });
2757
+ }
2758
+ };
2759
+ //#endregion
2760
+ //#region src/lib/orca/scaffolders/next.ts
2761
+ const CREATE_NEXT_APP_VERSION = "16.2.4";
2762
+ /** Scaffolds a new Next.js App Router project with `create-next-app`. */
2763
+ var NextScaffolder = class extends AbstractCLIScaffolder {
2764
+ displayName = "Next.js";
2765
+ supportedFrameworks = ["next"];
2766
+ /**
2767
+ * Runs the pinned `create-next-app` version in `cwd`, creating a TypeScript
2768
+ * App Router project in place. `--yes` accepts all defaults so the command
2769
+ * runs unattended, and `--skip-install` leaves dependency installation to the
2770
+ * setup command's explicit next step after Zitadel patches package.json.
2771
+ */
2772
+ async scaffold(cwd, _framework) {
2773
+ this.runCommand("npx", [
2774
+ "--yes",
2775
+ `create-next-app@${CREATE_NEXT_APP_VERSION}`,
2776
+ ".",
2777
+ "--ts",
2778
+ "--app",
2779
+ "--use-npm",
2780
+ "--disable-git",
2781
+ "--yes",
2782
+ "--skip-install"
2783
+ ], cwd);
2784
+ }
2785
+ };
2786
+ //#endregion
2787
+ //#region src/lib/orca/scaffolders/nuxt.ts
2788
+ /**
2789
+ * Scaffolds a new Nuxt app with `nuxi init`, then removes the starter `app.vue`
2790
+ * so the patcher can write the managed one without colliding with boilerplate.
2791
+ * Nuxt 4 (what `nuxi init` scaffolds today) puts it under `app/`; older Nuxt put
2792
+ * it at the root, so both are removed. `nuxt.config.ts` is left in place — the
2793
+ * patcher merges into it via an `edit`, which preserves whatever `nuxi` generated.
2794
+ */
2795
+ var NuxtScaffolder = class extends AbstractCLIScaffolder {
2796
+ displayName = "Nuxt";
2797
+ supportedFrameworks = ["nuxt"];
2798
+ async scaffold(cwd, _framework) {
2799
+ this.runCommand("npx", [
2800
+ "-y",
2801
+ "nuxi@latest",
2802
+ "init",
2803
+ ".",
2804
+ "--template",
2805
+ "minimal",
2806
+ "--packageManager",
2807
+ "npm",
2808
+ "--no-gitInit",
2809
+ "--force"
2810
+ ], cwd);
2811
+ await rm(join(cwd, "app/app.vue"), { force: true });
2812
+ await rm(join(cwd, "app.vue"), { force: true });
2813
+ }
2814
+ };
2815
+ //#endregion
2816
+ //#region src/lib/orca/scaffolders/qwik.ts
2817
+ /**
2818
+ * Scaffolds a new Vite + Qwik (TypeScript) single-page app with `create-vite`,
2819
+ * then removes the starter `app.tsx`/`app.css` demo so the patcher can write the
2820
+ * managed `src/app.tsx` without colliding with boilerplate. The create-vite Qwik
2821
+ * template uses a lowercase `app.tsx` (named `App` export, mounted by `main.tsx`)
2822
+ * — the patched file keeps that same entry.
2823
+ */
2824
+ var QwikScaffolder = class extends AbstractCLIScaffolder {
2825
+ displayName = "Qwik (Vite)";
2826
+ supportedFrameworks = ["qwik"];
2827
+ async scaffold(cwd, _framework) {
2828
+ this.runCommand("npm", [
2829
+ "create",
2830
+ "vite@latest",
2831
+ ".",
2832
+ "--",
2833
+ "--template",
2834
+ "qwik-ts"
2835
+ ], cwd);
2836
+ await rm(join(cwd, "src/app.tsx"), { force: true });
2837
+ await rm(join(cwd, "src/app.css"), { force: true });
2838
+ this.runCommand("npm", [
2839
+ "pkg",
2840
+ "set",
2841
+ "devDependencies.vite=^7.3.5"
2842
+ ], cwd);
2843
+ }
2844
+ };
2845
+ //#endregion
2846
+ //#region src/lib/orca/scaffolders/react.ts
2847
+ /**
2848
+ * Scaffolds a new Vite + React (TypeScript) single-page app with `create-vite`,
2849
+ * then removes the starter `App.tsx`/`App.css` demo so the patcher can write the
2850
+ * managed `src/App.tsx` without colliding with boilerplate. `index.css` and
2851
+ * `main.tsx` are left in place — the patched `App.tsx` keeps the same entry.
2852
+ */
2853
+ var ReactScaffolder = class extends AbstractCLIScaffolder {
2854
+ displayName = "React (Vite)";
2855
+ supportedFrameworks = ["react"];
2856
+ async scaffold(cwd, _framework) {
2857
+ this.runCommand("npm", [
2858
+ "create",
2859
+ "vite@latest",
2860
+ ".",
2861
+ "--",
2862
+ "--template",
2863
+ "react-ts"
2864
+ ], cwd);
2865
+ await rm(join(cwd, "src/App.tsx"), { force: true });
2866
+ await rm(join(cwd, "src/App.css"), { force: true });
2867
+ }
2868
+ };
2869
+ //#endregion
2870
+ //#region src/lib/orca/scaffolders/solid.ts
2871
+ /**
2872
+ * Scaffolds a new Vite + Solid (TypeScript) single-page app with `create-vite`,
2873
+ * then removes the starter `App.tsx`/`App.css` demo so the patcher can write the
2874
+ * managed `src/App.tsx` without colliding with boilerplate. `index.css` and
2875
+ * `index.tsx` are left in place — the patched `App.tsx` keeps the same entry.
2876
+ */
2877
+ var SolidScaffolder = class extends AbstractCLIScaffolder {
2878
+ displayName = "Solid (Vite)";
2879
+ supportedFrameworks = ["solid"];
2880
+ async scaffold(cwd, _framework) {
2881
+ this.runCommand("npm", [
2882
+ "create",
2883
+ "vite@latest",
2884
+ ".",
2885
+ "--",
2886
+ "--template",
2887
+ "solid-ts"
2888
+ ], cwd);
2889
+ await rm(join(cwd, "src/App.tsx"), { force: true });
2890
+ await rm(join(cwd, "src/App.css"), { force: true });
2891
+ }
2892
+ };
2893
+ //#endregion
2894
+ //#region src/lib/orca/scaffolders/svelte.ts
2895
+ /**
2896
+ * Scaffolds a new Vite + Svelte (TypeScript) single-page app with `create-vite`,
2897
+ * then removes the starter `App.svelte`/`lib/Counter.svelte` demo so the patcher
2898
+ * can write the managed `src/App.svelte` without colliding with boilerplate.
2899
+ * `app.css` and `main.ts` are left in place — the patched `App.svelte` keeps the
2900
+ * same entry.
2901
+ */
2902
+ var SvelteScaffolder = class extends AbstractCLIScaffolder {
2903
+ displayName = "Svelte (Vite)";
2904
+ supportedFrameworks = ["svelte"];
2905
+ async scaffold(cwd, _framework) {
2906
+ this.runCommand("npm", [
2907
+ "create",
2908
+ "vite@latest",
2909
+ ".",
2910
+ "--",
2911
+ "--template",
2912
+ "svelte-ts"
2913
+ ], cwd);
2914
+ await rm(join(cwd, "src/App.svelte"), { force: true });
2915
+ await rm(join(cwd, "src/lib/Counter.svelte"), { force: true });
2916
+ }
2917
+ };
2918
+ //#endregion
2919
+ //#region src/lib/orca/scaffolders/vue.ts
2920
+ /**
2921
+ * Scaffolds a new Vite + Vue (TypeScript) single-page app with `create-vite`,
2922
+ * then removes the starter `App.vue`/`components/HelloWorld.vue` demo so the
2923
+ * patcher can write the managed `src/App.vue` without colliding with boilerplate.
2924
+ */
2925
+ var VueScaffolder = class extends AbstractCLIScaffolder {
2926
+ displayName = "Vue (Vite)";
2927
+ supportedFrameworks = ["vue"];
2928
+ async scaffold(cwd, _framework) {
2929
+ this.runCommand("npm", [
2930
+ "create",
2931
+ "vite@latest",
2932
+ ".",
2933
+ "--",
2934
+ "--template",
2935
+ "vue-ts"
2936
+ ], cwd);
2937
+ await rm(join(cwd, "src/App.vue"), { force: true });
2938
+ await rm(join(cwd, "src/components/HelloWorld.vue"), { force: true });
2939
+ }
2940
+ };
2941
+ //#endregion
2942
+ //#region src/lib/orca/scaffolders/index.ts
2943
+ /**
2944
+ * Active scaffolders, in priority order. The framework picker derives its
2945
+ * choices from this list. Add a new framework by appending its scaffolder
2946
+ * here — no orchestrator changes needed.
2947
+ */
2948
+ const scaffolders = [
2949
+ new NextScaffolder(),
2950
+ new NuxtScaffolder(),
2951
+ new ReactScaffolder(),
2952
+ new VueScaffolder(),
2953
+ new SolidScaffolder(),
2954
+ new SvelteScaffolder(),
2955
+ new QwikScaffolder(),
2956
+ new AngularScaffolder()
2957
+ ];
2958
+ //#endregion
2959
+ //#region src/lib/orca/index.ts
2960
+ /**
2961
+ * Orchestrates the three per-framework strategies — detectors (recognise an
2962
+ * existing project and extract its facts), scaffolders (create a project), and
2963
+ * patchers (integrate Zitadel) — over their respective registries. It resolves
2964
+ * the right strategy for a framework and drives the detect/scaffold lifecycle;
2965
+ * how a patcher applies its work (file operations vs an LLM agent) stays
2966
+ * internal to that patcher. Registries are injected so tests can supply fakes.
2967
+ */
2968
+ var Orca = class {
2969
+ constructor(detectors, scaffolders, patchers) {
2970
+ this.detectors = detectors;
2971
+ this.scaffolders = scaffolders;
2972
+ this.patchers = patchers;
2973
+ }
2974
+ /**
2975
+ * Detects the framework in `cwd` and extracts its {@link FrameworkFacts},
2976
+ * honouring an explicit `requested` framework. Throws
2977
+ * `E_FRAMEWORK_NOT_DETECTED` when nothing matches; a detector's
2978
+ * `E_UNSUPPORTED_PROJECT_SHAPE` (recognised but unsupported) propagates.
2979
+ */
2980
+ async detect(cwd, requested) {
2981
+ const candidates = requested ? this.detectors.filter((detector) => detector.framework === requested) : this.detectors;
2982
+ if (requested && candidates.length === 0) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", `Unsupported framework "${requested}"`, { hint: `Supported frameworks: ${this.frameworkIds().join(", ")}.` });
2983
+ for (const detector of candidates) {
2984
+ const facts = await detector.detect(cwd);
2985
+ if (facts) return facts;
2986
+ }
2987
+ throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", "Could not detect a supported app framework", { hint: "Run setup from your app project directory, pass --cwd <path-to-app>, or run setup from an empty directory to scaffold a new app." });
2988
+ }
2989
+ /**
2990
+ * Non-throwing detection: returns `undefined` instead of raising for a
2991
+ * project that is absent, unrecognised, or recognised-but-unsupported, so
2992
+ * callers (e.g. `eject`) can probe and degrade gracefully.
2993
+ */
2994
+ async tryDetect(cwd) {
2995
+ try {
2996
+ return await this.detect(cwd);
2997
+ } catch (error) {
2998
+ if (error instanceof ZitadelError && (error.code === "E_FRAMEWORK_NOT_DETECTED" || error.code === "E_UNSUPPORTED_PROJECT_SHAPE")) return;
2999
+ throw error;
3000
+ }
3001
+ }
3002
+ /** Whether `cwd` is safe for an in-place framework scaffold. */
3003
+ async isFreshScaffoldTarget(cwd) {
3004
+ return (await inspectScaffoldTarget(cwd)).scaffoldable;
3005
+ }
3006
+ /**
3007
+ * Creates a new `framework` project in `cwd`, then re-detects it to return
3008
+ * the resulting {@link FrameworkFacts}. Throws `E_CONFLICT` when the directory
3009
+ * already contains a project ("already scaffolded") and `E_VALIDATION` when no
3010
+ * scaffolder supports the framework.
3011
+ */
3012
+ async scaffold(cwd, framework) {
3013
+ const target = await inspectScaffoldTarget(cwd);
3014
+ if (!target.scaffoldable) throw new ZitadelError("E_CONFLICT", `Cannot scaffold: ${cwd} is not empty`, {
3015
+ hint: target.reason ?? "Run setup in an empty directory, or run setup from an existing supported app project.",
3016
+ details: { entries: target.entries }
3017
+ });
3018
+ assertNpmSafeScaffoldDirectoryName(cwd);
3019
+ const stash = await stashFreshScaffoldArtifacts(cwd, target);
3020
+ try {
3021
+ await this.scaffolderFor(framework).scaffold(cwd, framework);
3022
+ } finally {
3023
+ await restoreFreshScaffoldArtifacts(cwd, stash);
3024
+ }
3025
+ return this.detect(cwd, framework);
3026
+ }
3027
+ /**
3028
+ * Resolves the scaffolder for a framework, throwing `E_VALIDATION` (with the
3029
+ * available list) when none matches.
3030
+ */
3031
+ scaffolderFor(framework) {
3032
+ const scaffolder = this.scaffolders.find((candidate) => candidate.canScaffold(framework));
3033
+ if (!scaffolder) throw new ZitadelError("E_VALIDATION", `No scaffolder supports "${framework}"`, { hint: `Available frameworks: ${this.availableFrameworks().map((f) => f.id).join(", ")}.` });
3034
+ return scaffolder;
3035
+ }
3036
+ /**
3037
+ * Resolves the patcher for a framework, throwing `E_VALIDATION` when none
3038
+ * matches (e.g. a framework that can be scaffolded but not yet integrated).
3039
+ */
3040
+ patcherFor(framework) {
3041
+ const patcher = this.patchers.find((candidate) => candidate.canPatch(framework));
3042
+ if (!patcher) throw new ZitadelError("E_VALIDATION", `No patcher supports "${framework}"`, { hint: "Zitadel integration currently supports Next.js." });
3043
+ return patcher;
3044
+ }
3045
+ /** The frameworks that can be scaffolded, derived from the scaffolder registry. */
3046
+ availableFrameworks() {
3047
+ return this.scaffolders.map((scaffolder) => ({
3048
+ id: scaffolder.supportedFrameworks[0] ?? scaffolder.displayName,
3049
+ displayName: scaffolder.displayName
3050
+ }));
3051
+ }
3052
+ frameworkIds() {
3053
+ return this.detectors.map((detector) => detector.framework);
3054
+ }
3055
+ };
3056
+ function assertNpmSafeScaffoldDirectoryName(cwd) {
3057
+ const name = basename(cwd);
3058
+ const errors = npmPackageNameErrors(name);
3059
+ if (errors.length === 0) return;
3060
+ throw new ZitadelError("E_VALIDATION", `Fresh app directory name "${name}" is not npm-package-safe`, {
3061
+ hint: "Rename the directory to a lowercase npm-package-safe name, for example `my-zitadel-app`, then rerun setup.",
3062
+ details: {
3063
+ cwd,
3064
+ name,
3065
+ validation_errors: errors
3066
+ }
3067
+ });
3068
+ }
3069
+ function npmPackageNameErrors(name) {
3070
+ const errors = [];
3071
+ if (name.length === 0) errors.push("name is empty");
3072
+ if (name.length > 214) errors.push("name is longer than 214 characters");
3073
+ if (name !== name.trim()) errors.push("name contains leading or trailing whitespace");
3074
+ if (/[A-Z]/.test(name)) errors.push("name can no longer contain capital letters");
3075
+ if (name.startsWith(".") || name.startsWith("_")) errors.push("name cannot start with a period or underscore");
3076
+ if (!/^[a-z0-9][a-z0-9._~-]*$/.test(name)) errors.push("name may only contain lowercase letters, numbers, dots, underscores, tildes, and hyphens");
3077
+ if (name === "node_modules" || name === "favicon.ico") errors.push(`name "${name}" is reserved`);
3078
+ return [...new Set(errors)];
3079
+ }
3080
+ /** {@link Orca} wired with the default detector, scaffolder, and patcher registries. */
3081
+ function createOrca() {
3082
+ return new Orca(detectors, scaffolders, patchers);
3083
+ }
3084
+ async function inspectScaffoldTarget(cwd) {
3085
+ const entries = await readdir(cwd, { withFileTypes: true });
3086
+ const names = entries.map((entry) => entry.name).sort();
3087
+ let hasGitignore = false;
3088
+ let hasRuntimeOnlyZitadel = false;
3089
+ for (const entry of entries) {
3090
+ if (entry.name === ".gitignore") {
3091
+ if (!entry.isFile()) return {
3092
+ scaffoldable: false,
3093
+ hasGitignore: false,
3094
+ hasRuntimeOnlyZitadel: false,
3095
+ reason: ".gitignore exists but is not a file.",
3096
+ entries: names
3097
+ };
3098
+ hasGitignore = true;
3099
+ continue;
3100
+ }
3101
+ if (entry.name === ".zitadel") {
3102
+ if (!entry.isDirectory() || !await isRuntimeOnlyZitadelDir(join(cwd, ".zitadel"))) return {
3103
+ scaffoldable: false,
3104
+ hasGitignore,
3105
+ hasRuntimeOnlyZitadel: false,
3106
+ reason: ".zitadel contains project state. Move it aside or run setup from an empty app directory.",
3107
+ entries: names
3108
+ };
3109
+ hasRuntimeOnlyZitadel = true;
3110
+ continue;
3111
+ }
3112
+ return {
3113
+ scaffoldable: false,
3114
+ hasGitignore,
3115
+ hasRuntimeOnlyZitadel: false,
3116
+ reason: `Directory contains ${entry.name}. Run setup from an empty directory to scaffold a new app.`,
3117
+ entries: names
3118
+ };
3119
+ }
3120
+ return {
3121
+ scaffoldable: true,
3122
+ hasGitignore,
3123
+ hasRuntimeOnlyZitadel,
3124
+ entries: names
3125
+ };
3126
+ }
3127
+ async function isRuntimeOnlyZitadelDir(path) {
3128
+ const entries = await readdir(path, { withFileTypes: true });
3129
+ if (entries.length !== 1 || entries[0]?.name !== "local" || !entries[0].isDirectory()) return false;
3130
+ return true;
3131
+ }
3132
+ async function stashFreshScaffoldArtifacts(cwd, target) {
3133
+ if (!target.hasGitignore && !target.hasRuntimeOnlyZitadel) return;
3134
+ const root = join(dirname(cwd), `.${basename(cwd)}.fresh-scaffold-stash-${String(process.pid)}-${String(Date.now())}`);
3135
+ await mkdir(root, { mode: 448 });
3136
+ const stash = { root };
3137
+ if (target.hasRuntimeOnlyZitadel) {
3138
+ stash.zitadel = join(root, ".zitadel");
3139
+ await rename(join(cwd, ".zitadel"), stash.zitadel);
3140
+ }
3141
+ if (target.hasGitignore) {
3142
+ stash.gitignore = join(root, ".gitignore");
3143
+ await rename(join(cwd, ".gitignore"), stash.gitignore);
3144
+ }
3145
+ return stash;
3146
+ }
3147
+ async function restoreFreshScaffoldArtifacts(cwd, stash) {
3148
+ if (!stash) return;
3149
+ try {
3150
+ await restoreRuntimeOnlyZitadel(cwd, stash.zitadel);
3151
+ await restoreGitignore(cwd, stash.gitignore);
3152
+ } finally {
3153
+ await rm(stash.root, {
3154
+ recursive: true,
3155
+ force: true
3156
+ });
3157
+ }
3158
+ }
3159
+ async function restoreRuntimeOnlyZitadel(cwd, stash) {
3160
+ if (!stash) return;
3161
+ const target = join(cwd, ".zitadel");
3162
+ try {
3163
+ await rename(stash, target);
3164
+ await appendGitignoreEntry(cwd, ".zitadel/local/");
3165
+ return;
3166
+ } catch (error) {
3167
+ if (!isErrno(error, "EEXIST")) throw error;
3168
+ }
3169
+ await mkdir(target, {
3170
+ recursive: true,
3171
+ mode: 448
3172
+ });
3173
+ await rename(join(stash, "local"), join(target, "local"));
3174
+ await rm(stash, {
3175
+ recursive: true,
3176
+ force: true
3177
+ });
3178
+ await appendGitignoreEntry(cwd, ".zitadel/local/");
3179
+ }
3180
+ async function restoreGitignore(cwd, stash) {
3181
+ if (!stash) return;
3182
+ const path = join(cwd, ".gitignore");
3183
+ const stashed = await readFile(stash, "utf8");
3184
+ let current = "";
3185
+ try {
3186
+ current = await readFile(path, "utf8");
3187
+ } catch (error) {
3188
+ if (!isErrno(error, "ENOENT")) throw error;
3189
+ }
3190
+ const existingLines = new Set(current.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean));
3191
+ const missingLines = stashed.split(/\r?\n/g).map((line) => line.trim()).filter((line) => line.length > 0 && !existingLines.has(line));
3192
+ if (missingLines.length === 0) return;
3193
+ const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
3194
+ await writeFile(path, `${current}${prefix}${missingLines.join("\n")}\n`);
3195
+ }
3196
+ async function appendGitignoreEntry(cwd, entry) {
3197
+ const path = join(cwd, ".gitignore");
3198
+ let existing = "";
3199
+ try {
3200
+ existing = await readFile(path, "utf8");
3201
+ } catch (error) {
3202
+ if (!isErrno(error, "ENOENT")) throw error;
3203
+ }
3204
+ if (existing.split(/\r?\n/g).map((line) => line.trim()).includes(entry)) return;
3205
+ const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
3206
+ await writeFile(path, `${existing}${prefix}${entry}\n`);
3207
+ }
3208
+ function isErrno(error, code) {
3209
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
3210
+ }
3211
+ //#endregion
3212
+ export { issuerFromPort as i, inspectScaffoldTarget as n, RENDERER_IDS as r, createOrca as t };
3213
+
3214
+ //# sourceMappingURL=orca-mzcHxDvu.mjs.map