@zitadel/cli 0.1.0-alpha.4 → 0.1.0-alpha.8

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 (39) hide show
  1. package/README.md +70 -63
  2. package/SKILLS.md +60 -18
  3. package/dist/commands/apply.mjs +3 -3
  4. package/dist/commands/doctor.mjs +75 -20
  5. package/dist/commands/doctor.mjs.map +1 -1
  6. package/dist/commands/eject.mjs +13 -6
  7. package/dist/commands/eject.mjs.map +1 -1
  8. package/dist/commands/logs.mjs +13 -5
  9. package/dist/commands/logs.mjs.map +1 -1
  10. package/dist/commands/plan.mjs +3 -3
  11. package/dist/commands/reset.mjs +16 -7
  12. package/dist/commands/reset.mjs.map +1 -1
  13. package/dist/commands/setup.mjs +56 -17
  14. package/dist/commands/setup.mjs.map +1 -1
  15. package/dist/commands/start.mjs +104 -11
  16. package/dist/commands/start.mjs.map +1 -1
  17. package/dist/commands/status.mjs +26 -7
  18. package/dist/commands/status.mjs.map +1 -1
  19. package/dist/commands/stop.mjs +15 -6
  20. package/dist/commands/stop.mjs.map +1 -1
  21. package/dist/docker-BA78SdC2.mjs +383 -0
  22. package/dist/docker-BA78SdC2.mjs.map +1 -0
  23. package/dist/docker-guidance-BvfpmsDj.mjs +21 -0
  24. package/dist/docker-guidance-BvfpmsDj.mjs.map +1 -0
  25. package/dist/{oclif-B3Qhw0cj.mjs → oclif-VkCTGIEk.mjs} +50 -15
  26. package/dist/oclif-VkCTGIEk.mjs.map +1 -0
  27. package/dist/orca-CfKDQRop.mjs +2556 -0
  28. package/dist/orca-CfKDQRop.mjs.map +1 -0
  29. package/dist/{project-kWWyS7fS.mjs → project-CKAHtHML.mjs} +2 -2
  30. package/dist/{project-kWWyS7fS.mjs.map → project-CKAHtHML.mjs.map} +1 -1
  31. package/dist/{sync-CHXZqYR7.mjs → sync-B5lqgQO3.mjs} +2 -2
  32. package/dist/{sync-CHXZqYR7.mjs.map → sync-B5lqgQO3.mjs.map} +1 -1
  33. package/oclif.manifest.json +37 -3
  34. package/package.json +8 -41
  35. package/dist/docker-B4zvLujy.mjs +0 -210
  36. package/dist/docker-B4zvLujy.mjs.map +0 -1
  37. package/dist/oclif-B3Qhw0cj.mjs.map +0 -1
  38. package/dist/orca-BGM8VCgQ.mjs +0 -1141
  39. package/dist/orca-BGM8VCgQ.mjs.map +0 -1
@@ -1,1141 +0,0 @@
1
- import { C as isObject, E as ZitadelError, T as stableStringify, n as DEFAULT_SERVER, w as parseJsonObject, x as MANAGED_MARKER } from "./oclif-B3Qhw0cj.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 { spawnSync } from "node:child_process";
5
- //#region src/lib/orca/detectors/package-json.ts
6
- /**
7
- * Reads and parses the `package.json` at `cwd`. Rejects if the file is absent
8
- * or malformed; callers that treat those as "not a project" are expected to
9
- * catch and fall back rather than have detection swallow the error here.
10
- */
11
- async function readPackageJson(cwd) {
12
- const contents = await readFile(join(cwd, "package.json"), "utf8");
13
- return JSON.parse(contents);
14
- }
15
- /**
16
- * Reports whether `name` appears in either `dependencies` or `devDependencies`.
17
- * Both are checked because framework packages may legitimately live in either.
18
- */
19
- function hasDependency(pkg, name) {
20
- return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
21
- }
22
- function dependencyVersionMajor(pkg, name) {
23
- const spec = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
24
- if (!spec) return;
25
- const match = spec.match(/\d+/);
26
- return match ? Number(match[0]) : void 0;
27
- }
28
- //#endregion
29
- //#region src/lib/orca/detectors/port.ts
30
- /**
31
- * Port assumed when no explicit dev port can be discovered. Matches Next.js's
32
- * own default so the inferred issuer URL lines up with `next dev`.
33
- */
34
- const DEFAULT_DEV_PORT = 3e3;
35
- /**
36
- * Determines the local dev-server port for `cwd`. The `dev` script is the most
37
- * authoritative source, then a `PORT` declaration in an env file, falling back
38
- * to {@link DEFAULT_DEV_PORT}. Used to derive the local issuer URL.
39
- */
40
- async function detectDevPort(cwd, pkg) {
41
- const dev = pkg.scripts?.dev;
42
- const fromScript = typeof dev === "string" ? extractPort(dev) : void 0;
43
- if (fromScript) return fromScript;
44
- const fromEnvFile = await portFromEnvFile(cwd);
45
- if (fromEnvFile) return fromEnvFile;
46
- return DEFAULT_DEV_PORT;
47
- }
48
- async function portFromEnvFile(cwd) {
49
- for (const candidate of [".env.local", ".env"]) try {
50
- const rawPort = (await readFile(join(cwd, candidate), "utf8")).match(/^\s*PORT\s*=\s*(\d+)/m)?.[1];
51
- if (rawPort) return Number.parseInt(rawPort, 10);
52
- } catch {
53
- continue;
54
- }
55
- }
56
- /**
57
- * Parses a dev port out of an npm `dev` script string, recognizing both flag
58
- * forms (`-p`/`--port`) and a leading `PORT=` env assignment. Returns
59
- * `undefined` when no valid positive port is present so callers can fall back.
60
- */
61
- function extractPort(script) {
62
- const inline = script.match(/-p\s+(\d+)|--port[=\s]+(\d+)/);
63
- if (inline) {
64
- const raw = inline[1] ?? inline[2];
65
- if (!raw) return;
66
- const value = Number.parseInt(raw, 10);
67
- if (Number.isFinite(value) && value > 0) return value;
68
- }
69
- const rawEnvPort = script.match(/(?:^|\s)PORT=(\d+)/)?.[1];
70
- if (rawEnvPort) return Number.parseInt(rawEnvPort, 10);
71
- }
72
- /**
73
- * Builds the local OIDC issuer URL for a given dev port. Centralized so the
74
- * `localhost` origin convention is defined in exactly one place.
75
- */
76
- function issuerFromPort(port) {
77
- return `http://localhost:${port}`;
78
- }
79
- //#endregion
80
- //#region src/lib/orca/detectors/next.ts
81
- /**
82
- * Detects a Next.js App Router project and extracts its facts: the App Router
83
- * directory (`app` vs `src/app`), the dev-server port (parsed from the `dev`
84
- * script / env file, else 3000), and the derived local issuer URL. Owns every
85
- * Next-specific assumption so the orchestrator and commands stay generic.
86
- */
87
- var NextDetector = class {
88
- framework = "next";
89
- /**
90
- * Returns `null` when `cwd` is not a Next.js project (no `next` dependency),
91
- * so the orchestrator can try other detectors. Throws
92
- * `E_UNSUPPORTED_PROJECT_SHAPE` when it is Next.js but lacks an App Router
93
- * directory (e.g. a Pages Router project), which is a hard error rather than
94
- * an empty directory to scaffold.
95
- */
96
- async detect(cwd) {
97
- const pkg = await readPackageJson(cwd).catch(() => void 0);
98
- if (!pkg || !hasDependency(pkg, "next")) return null;
99
- const appDir = await dirExists(join(cwd, "app")) ? "app" : await dirExists(join(cwd, "src/app")) ? "src/app" : void 0;
100
- 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." });
101
- const devPort = await detectDevPort(cwd, pkg);
102
- return {
103
- id: "next",
104
- appDir,
105
- devPort,
106
- url: issuerFromPort(devPort),
107
- versionMajor: dependencyVersionMajor(pkg, "next")
108
- };
109
- }
110
- };
111
- async function dirExists(path) {
112
- try {
113
- return (await stat(path)).isDirectory();
114
- } catch (error) {
115
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return false;
116
- throw error;
117
- }
118
- }
119
- //#endregion
120
- //#region src/lib/orca/detectors/index.ts
121
- /**
122
- * Active detectors, in probe order. The orchestrator tries each until one
123
- * recognises the project. Add a framework by appending its detector here — no
124
- * orchestrator changes needed.
125
- */
126
- const detectors = [new NextDetector()];
127
- //#endregion
128
- //#region src/lib/orca/patchers/rule/file-writer/index.ts
129
- /**
130
- * Applies a {@link ScaffoldPlan} to disk, executing its operations in order.
131
- *
132
- * Operations are idempotent: writes whose target already matches the desired
133
- * contents are recorded as skipped rather than rewritten, so re-running setup
134
- * is safe. With `dryRun` no filesystem changes are made but the result still
135
- * reflects what would have been written. Existing files are only overwritten
136
- * when `force` is set; otherwise an `E_CONFLICT` is thrown to protect
137
- * user-authored content. Paths in the plan are resolved relative to `cwd`.
138
- */
139
- async function scaffold(plan, opts) {
140
- const result = {
141
- dryRun: opts.dryRun,
142
- filesWritten: [],
143
- filesSkipped: [],
144
- depsAdded: []
145
- };
146
- for (const op of plan.ops) await applyOp(op, opts, result);
147
- return result;
148
- }
149
- async function applyOp(op, opts, result) {
150
- switch (op.kind) {
151
- case "mkdir":
152
- await ensureDir(abs(opts.cwd, op.path), op.mode, opts.dryRun, result);
153
- break;
154
- case "write":
155
- await writeText(abs(opts.cwd, op.path), op.contents, {
156
- mode: op.mode,
157
- force: opts.force,
158
- dryRun: opts.dryRun
159
- }, result);
160
- break;
161
- case "append":
162
- await appendText(abs(opts.cwd, op.path), op.contents, op.ifMissing, opts.dryRun, result);
163
- break;
164
- case "merge-env":
165
- await mergeEnv(abs(opts.cwd, op.path), op.entries, opts.dryRun, result);
166
- break;
167
- case "merge-json":
168
- await mergeJson(abs(opts.cwd, op.path), op.patch, opts.dryRun, result);
169
- break;
170
- case "append-gitignore":
171
- await appendGitignore(abs(opts.cwd, ".gitignore"), op.entries, opts.dryRun, result);
172
- break;
173
- case "add-dep":
174
- await addDependency(abs(opts.cwd, "package.json"), op, opts.dryRun, result);
175
- break;
176
- }
177
- }
178
- async function ensureDir(path, mode, dryRun, result) {
179
- if (dryRun) {
180
- result.filesWritten.push(path);
181
- return;
182
- }
183
- await mkdir(path, {
184
- recursive: true,
185
- mode
186
- });
187
- if (mode) await chmod(path, mode).catch(() => void 0);
188
- result.filesWritten.push(path);
189
- }
190
- async function writeText(path, contents, opts, result) {
191
- const existing = await readIfExists(path);
192
- if (existing === contents) {
193
- result.filesSkipped.push(path);
194
- return;
195
- }
196
- if (existing !== void 0 && !opts.force) throw new ZitadelError("E_CONFLICT", `Refusing to overwrite ${path}`, {
197
- hint: "Re-run with --force if you want the CLI to replace this file.",
198
- details: { path }
199
- });
200
- if (opts.dryRun) {
201
- result.filesWritten.push(path);
202
- return;
203
- }
204
- await mkdir(dirname(path), { recursive: true });
205
- const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
206
- await writeFile(tmp, contents, { mode: opts.mode });
207
- if (opts.mode) await chmod(tmp, opts.mode).catch(() => void 0);
208
- await rename(tmp, path);
209
- result.filesWritten.push(path);
210
- }
211
- async function appendText(path, contents, ifMissing, dryRun, result) {
212
- const existing = await readIfExists(path) ?? "";
213
- if (ifMissing && existing.includes(ifMissing)) {
214
- result.filesSkipped.push(path);
215
- return;
216
- }
217
- const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${contents}`;
218
- if (next === existing) {
219
- result.filesSkipped.push(path);
220
- return;
221
- }
222
- if (dryRun) {
223
- result.filesWritten.push(path);
224
- return;
225
- }
226
- await mkdir(dirname(path), { recursive: true });
227
- await writeFile(path, next);
228
- result.filesWritten.push(path);
229
- }
230
- async function mergeEnv(path, entries, dryRun, result) {
231
- const existing = await readIfExists(path) ?? "";
232
- 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)));
233
- const additions = Object.entries(entries).filter(([key]) => !present.has(key));
234
- if (additions.length === 0) {
235
- result.filesSkipped.push(path);
236
- return;
237
- }
238
- const block = additions.map(([key, value]) => `${key}=${value}`).join("\n");
239
- const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${block}\n`;
240
- if (dryRun) {
241
- result.filesWritten.push(path);
242
- return;
243
- }
244
- await mkdir(dirname(path), { recursive: true });
245
- await writeFile(path, next);
246
- result.filesWritten.push(path);
247
- }
248
- async function mergeJson(path, patch, dryRun, result) {
249
- const existing = await readIfExists(path);
250
- const contents = `${stableStringify(deepMerge(existing ? parseJsonObject(existing, path) : {}, patch))}\n`;
251
- if (existing === contents) {
252
- result.filesSkipped.push(path);
253
- return;
254
- }
255
- if (dryRun) {
256
- result.filesWritten.push(path);
257
- return;
258
- }
259
- await mkdir(dirname(path), { recursive: true });
260
- await writeFile(path, contents);
261
- result.filesWritten.push(path);
262
- }
263
- async function appendGitignore(path, entries, dryRun, result) {
264
- const existing = await readIfExists(path) ?? "";
265
- const lines = new Set(existing.split(/\r?\n/g).map((line) => line.trim()));
266
- const missing = entries.filter((entry) => !lines.has(entry));
267
- if (missing.length === 0) {
268
- result.filesSkipped.push(path);
269
- return;
270
- }
271
- const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${missing.join("\n")}\n`;
272
- if (dryRun) {
273
- result.filesWritten.push(path);
274
- return;
275
- }
276
- await writeFile(path, next);
277
- result.filesWritten.push(path);
278
- }
279
- async function addDependency(path, op, dryRun, result) {
280
- const existing = await readIfExists(path);
281
- if (!existing) throw new ZitadelError("E_VALIDATION", "package.json is required to add Zitadel dependencies");
282
- const current = parseJsonObject(existing, path);
283
- const key = op.dev ? "devDependencies" : "dependencies";
284
- const deps = isObject(current[key]) ? current[key] : {};
285
- if (deps[op.name] === op.version) {
286
- result.filesSkipped.push(path);
287
- return;
288
- }
289
- current[key] = {
290
- ...deps,
291
- [op.name]: op.version
292
- };
293
- const contents = `${stableStringify(current)}\n`;
294
- if (dryRun) {
295
- result.filesWritten.push(path);
296
- result.depsAdded.push(op.name);
297
- return;
298
- }
299
- await writeFile(path, contents);
300
- result.filesWritten.push(path);
301
- result.depsAdded.push(op.name);
302
- }
303
- async function readIfExists(path) {
304
- try {
305
- return await readFile(path, "utf8");
306
- } catch (error) {
307
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
308
- throw error;
309
- }
310
- }
311
- function abs(cwd, path) {
312
- return join(cwd, path);
313
- }
314
- function deepMerge(target, patch) {
315
- const out = { ...target };
316
- for (const [key, value] of Object.entries(patch)) if (isObject(value) && isObject(out[key])) out[key] = deepMerge(out[key], value);
317
- else out[key] = value;
318
- return out;
319
- }
320
- //#endregion
321
- //#region src/lib/orca/patchers/rule/reclaim.ts
322
- /**
323
- * The subset of a patcher plan's operations that `doctor --fix` re-applies:
324
- * env merges, gitignore entries, dependency additions, and marker-bearing
325
- * managed files (framework routes/middleware). Deliberately excludes the
326
- * unmarked `.zitadel/` resource writes and `zitadel.json` — those are
327
- * user-editable and synced by `apply`, so `--fix` must not clobber them.
328
- *
329
- * Pure: filters a freshly-allocated list; the input plan is not mutated.
330
- */
331
- function reclaimableOps(plan) {
332
- return plan.ops.filter((op) => op.kind === "merge-env" || op.kind === "append-gitignore" || op.kind === "add-dep" || op.kind === "write" && op.contents.includes("// zitadel-cli: managed-file v1"));
333
- }
334
- //#endregion
335
- //#region src/lib/orca/patchers/rule/base.ts
336
- /**
337
- * Base for rule-based (deterministic, template-driven) patchers, as opposed to
338
- * a future LLM-driven family. It applies the integration by building a
339
- * file-operation plan and running the file-writer — that strategy stays
340
- * entirely inside this family, so callers only ever see the family-neutral
341
- * {@link Patcher} surface. Owns the framework-agnostic `.zitadel/` base files
342
- * and the shared eject classification; subclasses contribute only their
343
- * framework-specific routes/middleware.
344
- */
345
- var AbstractRulePatcher = class {
346
- /** Apply the full plan (base `.zitadel/` files + framework routes). */
347
- async patch(ctx, opts) {
348
- return scaffold(this.plan(ctx), opts);
349
- }
350
- /**
351
- * Re-apply only the reclaimable subset — env files, gitignore, the SDK
352
- * dependency, and marker-bearing routes — leaving the user-editable
353
- * `.zitadel/` resources untouched. Backs `doctor --fix`.
354
- */
355
- async repair(ctx, opts) {
356
- const plan = this.plan(ctx);
357
- return scaffold({
358
- ops: reclaimableOps(plan),
359
- summary: plan.summary
360
- }, opts);
361
- }
362
- /** Shared base artifacts plus the subclass's marker-bearing route files. */
363
- artifacts(view) {
364
- return {
365
- markedFiles: this.routeFiles(view),
366
- rootConfigFiles: ["zitadel.json"],
367
- directories: [".zitadel"],
368
- envBackups: [".env.local"],
369
- dependencies: this.routeDeps(view)
370
- };
371
- }
372
- /**
373
- * The full file-operation plan this patcher would apply. Public so rule-family
374
- * unit tests can assert the planned ops directly; the generic {@link Patcher}
375
- * interface deliberately does not expose it (an LLM patcher has no such plan).
376
- */
377
- plan(ctx) {
378
- return {
379
- ops: [...this.baseOps(ctx), ...this.routeOps(ctx)],
380
- summary: [this.summary(ctx)]
381
- };
382
- }
383
- /**
384
- * The framework-agnostic `.zitadel/` base files every rule patcher writes:
385
- * the project secret, `zitadel.json`, env templates, and an empty sync
386
- * state. The `schemas/` and `flows/` directories are created empty — the
387
- * server provisions the default user schema and flow definition when the
388
- * project is created, so nothing is scaffolded into them here. Pure: no
389
- * filesystem or network.
390
- */
391
- baseOps(ctx) {
392
- return [
393
- {
394
- kind: "mkdir",
395
- path: ".zitadel",
396
- mode: 448
397
- },
398
- {
399
- kind: "mkdir",
400
- path: ".zitadel/flows"
401
- },
402
- {
403
- kind: "mkdir",
404
- path: ".zitadel/schemas"
405
- },
406
- {
407
- kind: "append-gitignore",
408
- entries: [
409
- ".zitadel/secret",
410
- ".env*",
411
- "!.env.example"
412
- ]
413
- },
414
- {
415
- kind: "write",
416
- path: ".zitadel/secret",
417
- mode: 384,
418
- contents: `${stableStringify({
419
- project_id: ctx.project.id,
420
- project_secret: ctx.project.projectSecret,
421
- preview_secret: ctx.project.previewSecret,
422
- preview_origins: ctx.project.previewOrigins,
423
- created_at: ctx.project.createdAt
424
- })}\n`
425
- },
426
- {
427
- kind: "write",
428
- path: "zitadel.json",
429
- contents: `${stableStringify(projectConfig(ctx))}\n`
430
- },
431
- {
432
- kind: "merge-env",
433
- path: ".env.example",
434
- entries: {
435
- ZITADEL_PROJECT_ID: "",
436
- ZITADEL_ENVIRONMENT: "",
437
- ZITADEL_ISSUER: "",
438
- ZITADEL_URL: "",
439
- NEXT_PUBLIC_ZITADEL_PROJECT_ID: ""
440
- }
441
- },
442
- {
443
- kind: "merge-env",
444
- path: ".env.local",
445
- entries: {
446
- ZITADEL_PROJECT_ID: ctx.project.id,
447
- ZITADEL_ENVIRONMENT: "development",
448
- ZITADEL_ISSUER: ctx.issuer,
449
- ZITADEL_URL: ctx.server,
450
- NEXT_PUBLIC_ZITADEL_PROJECT_ID: ctx.project.id
451
- }
452
- },
453
- {
454
- kind: "write",
455
- path: ".zitadel/state.json",
456
- contents: `${stableStringify({
457
- framework: ctx.framework.id,
458
- resources: {}
459
- })}\n`
460
- }
461
- ];
462
- }
463
- };
464
- /** Builds the `zitadel.json` body persisted at the project root. */
465
- function projectConfig(ctx) {
466
- const environments = { development: { issuer: ctx.issuer } };
467
- if (ctx.project.previewOrigins.length > 0) environments.preview = { issuer_pattern: ctx.project.previewOrigins.map((origin) => `https://${origin}`) };
468
- return {
469
- $schema: "https://schemas.zitadel.com/v2/project.schema.json",
470
- project: ctx.project.id,
471
- server: resolveServerOrigin(ctx.server),
472
- framework: { id: ctx.framework.id },
473
- branding: {
474
- renderer: ctx.rendererId,
475
- attribution: "visible"
476
- },
477
- environments
478
- };
479
- }
480
- /** Normalizes a server URL to its origin, falling back to {@link DEFAULT_SERVER}. */
481
- function resolveServerOrigin(source) {
482
- try {
483
- return new URL(source).origin;
484
- } catch {
485
- return DEFAULT_SERVER;
486
- }
487
- }
488
- //#endregion
489
- //#region src/lib/orca/patchers/rule/next/renderers/lit/index.ts
490
- /**
491
- * Placeholder renderer for the `<zitadel-flow>` Lit web component. Declared
492
- * so the `web-component` renderer id resolves and surfaces a clear
493
- * "not yet published" error, while reserving the integration shape for when
494
- * `@zitadel/ui-lit` ships. The `authPage` template emits an illustrative
495
- * page only; this renderer is never selected for real scaffolding because
496
- * `getRenderer` rejects any `status: "not-implemented"` spec.
497
- */
498
- const litRenderer = {
499
- id: "web-component",
500
- displayName: "Web component (<zitadel-flow>)",
501
- status: "not-implemented",
502
- frameworks: [
503
- "next",
504
- "astro",
505
- "remix",
506
- "sveltekit",
507
- "nuxt",
508
- "vanilla"
509
- ],
510
- dependency: {
511
- name: "@zitadel/ui-lit",
512
- version: "workspace:*"
513
- },
514
- templates: { authPage(mode) {
515
- return {
516
- mode,
517
- contents: `${MANAGED_MARKER}
518
- // The web component renderer ships a <zitadel-flow> element. Until
519
- // @zitadel/ui-lit is published, this template only declares the
520
- // intended integration point. See docs/design/cli/bdui-renderer.md.
521
- import "@zitadel/ui-lit";
522
-
523
- const environment =
524
- process.env.ZITADEL_ENVIRONMENT ??
525
- (process.env.NODE_ENV === "production" ? "production" : "development");
526
-
527
- export default function ${mode === "login" ? "LoginPage" : "RegisterPage"}() {
528
- return (
529
- <zitadel-flow
530
- purpose="${mode === "login" ? "login" : "register"}"
531
- project-id={process.env.ZITADEL_PROJECT_ID}
532
- issuer={process.env.ZITADEL_ISSUER}
533
- environment={environment}
534
- />
535
- );
536
- }
537
- `
538
- };
539
- } }
540
- };
541
- //#endregion
542
- //#region src/lib/orca/patchers/rule/next/renderers/react/index.ts
543
- /**
544
- * The Next.js App Router renderer scaffolds `/login`, `/register`, and
545
- * `/profile` pages that drive the `<zitadel-login>` and `<zitadel-logout>`
546
- * Lit web components.
547
- *
548
- * Each page is a single client component (`"use client"`) that, inside a
549
- * `next/dynamic({ ssr: false })` loader, builds the SDK project handle with
550
- * `configureZitadel({ projectId, proxyPath: "/__nextgen" })` and passes it to
551
- * the widget via `project={...}`. It also imports
552
- * `@zitadel/sdk-next/client` for its `customElements.define`
553
- * side-effect — importing `@zitadel/components` directly would fail on
554
- * strict-resolution package managers (pnpm, yarn PnP) because the app only
555
- * declares `sdk-next` as a direct dep. SSR is disabled because Lit's element
556
- * registration needs a browser.
557
- *
558
- * The handle is passed as the `project` DOM property, which relies on React
559
- * 19's custom-element property binding (the scaffold targets the latest Next /
560
- * React). The backend URL never reaches the browser: the client talks to the
561
- * same-origin `/__nextgen` proxy path, and the scaffolded Next request boundary
562
- * forwards it to `ZITADEL_URL` server-side. `NEXT_PUBLIC_ZITADEL_PROJECT_ID` is
563
- * public — the project id is not sensitive and the widget needs it to start a
564
- * flow.
565
- */
566
- const reactRenderer = {
567
- id: "react",
568
- displayName: "React (Next.js App Router)",
569
- status: "available",
570
- frameworks: ["next"],
571
- dependency: {
572
- name: "@zitadel/sdk-next",
573
- version: "latest"
574
- },
575
- templates: {
576
- authPage(mode) {
577
- const componentName = mode === "login" ? "LoginPage" : "RegisterPage";
578
- const elementName = mode === "login" ? "ZitadelLogin" : "ZitadelRegister";
579
- return {
580
- mode,
581
- contents: `${MANAGED_MARKER}
582
- "use client";
583
-
584
- import dynamic from "next/dynamic";
585
-
586
- const ${elementName} = dynamic(
587
- async () => {
588
- const { configureZitadel } = await import("@zitadel/sdk-next/client");
589
- // Build the SDK project handle and pass it to the component via the
590
- // \`project\` prop. The component reads config from this prop directly, so
591
- // it works regardless of how the SDK packages are bundled. The backend URL
592
- // stays server-side — requests go through the proxy path "/__nextgen",
593
- // which the scaffolded request boundary forwards to the Zitadel server.
594
- const project = configureZitadel({
595
- projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
596
- proxyPath: "/__nextgen",
597
- });
598
- return function ${elementName}Element() {
599
- return (
600
- <zitadel-login
601
- project={project}
602
- purpose="${mode}"
603
- post-sign-in-url="/profile"
604
- />
605
- );
606
- };
607
- },
608
- { ssr: false },
609
- );
610
-
611
- export default function ${componentName}() {
612
- return (
613
- <main style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center" }}>
614
- <${elementName} />
615
- </main>
616
- );
617
- }
618
- `
619
- };
620
- },
621
- profilePage() {
622
- return { contents: `${MANAGED_MARKER}
623
- "use client";
624
-
625
- import dynamic from "next/dynamic";
626
-
627
- const ZitadelLogout = dynamic(
628
- async () => {
629
- const { configureZitadel } = await import("@zitadel/sdk-next/client");
630
- const project = configureZitadel({
631
- projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
632
- proxyPath: "/__nextgen",
633
- });
634
- return function ZitadelLogoutElement() {
635
- return (
636
- <zitadel-logout
637
- project={project}
638
- post-sign-out-url="/login"
639
- />
640
- );
641
- };
642
- },
643
- { ssr: false },
644
- );
645
-
646
- export default function ProfilePage() {
647
- return (
648
- <main style={{ padding: "48px", maxWidth: "600px", margin: "0 auto" }}>
649
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "24px" }}>
650
- <h1 style={{ fontSize: "24px", fontWeight: 700, margin: 0 }}>Signed in</h1>
651
- <ZitadelLogout />
652
- </div>
653
- <p style={{ color: "#6b7280" }}>You are signed in. Use the button above to log out.</p>
654
- </main>
655
- );
656
- }
657
- ` };
658
- },
659
- customElementsDts() {
660
- return { contents: `${MANAGED_MARKER}
661
- import type React from "react";
662
- import type { ZitadelProject } from "@zitadel/sdk-next/client";
663
-
664
- declare module "react" {
665
- namespace JSX {
666
- interface IntrinsicElements {
667
- "zitadel-login": React.HTMLAttributes<HTMLElement> & {
668
- project?: ZitadelProject;
669
- "session-exchange-path"?: string;
670
- "post-sign-in-url"?: string;
671
- purpose?: string;
672
- };
673
- "zitadel-logout": React.HTMLAttributes<HTMLElement> & {
674
- project?: ZitadelProject;
675
- "post-sign-out-url"?: string;
676
- };
677
- }
678
- }
679
- }
680
- ` };
681
- }
682
- }
683
- };
684
- //#endregion
685
- //#region src/lib/orca/patchers/rule/next/renderers/registry.ts
686
- /**
687
- * Runtime mirror of the {@link RendererId} union, used by {@link isRendererId}
688
- * to validate untrusted strings (a TS union has no runtime presence). Must stay
689
- * in sync with the {@link RendererId} type.
690
- */
691
- const RENDERER_IDS = ["react", "web-component"];
692
- /**
693
- * Type guard narrowing an arbitrary value to a {@link RendererId}, used to
694
- * validate renderer ids read from config before indexing {@link RENDERERS}.
695
- */
696
- function isRendererId(value) {
697
- return typeof value === "string" && RENDERER_IDS.includes(value);
698
- }
699
- /**
700
- * The single source of truth mapping each {@link RendererId} to its spec.
701
- * Keyed by id so {@link getRenderer} can look up and validate a renderer
702
- * chosen from persisted config (an arbitrary string) at runtime.
703
- */
704
- const RENDERERS = {
705
- react: reactRenderer,
706
- "web-component": litRenderer
707
- };
708
- /**
709
- * Resolves a renderer id (an untrusted string from config) to its spec,
710
- * throwing a typed {@link ZitadelError} rather than returning `undefined`
711
- * so callers get an actionable message. Rejects ids that are unknown
712
- * (`E_VALIDATION`) or declared-but-unpublished (`E_NOT_IMPLEMENTED`),
713
- * guaranteeing the returned spec is safe to scaffold from.
714
- */
715
- function getRenderer(id) {
716
- if (!isRendererId(id)) throw new ZitadelError("E_VALIDATION", `Unknown renderer "${id}"`, { hint: `Available renderers: ${Object.keys(RENDERERS).join(", ")}` });
717
- const renderer = RENDERERS[id];
718
- 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." });
719
- return renderer;
720
- }
721
- //#endregion
722
- //#region src/lib/orca/patchers/rule/next/index.ts
723
- /**
724
- * Next.js request-boundary file at the project root. Wires `nextgenMiddleware` so the
725
- * generated project config's `/__nextgen` proxy path is same-origin proxied
726
- * to `ZITADEL_URL` and `/profile` is gated. Next 16 renamed this convention to
727
- * `proxy.ts`; older projects keep `middleware.ts`.
728
- * Carries the managed marker so `doctor --fix` reclaims it and `eject` removes it.
729
- */
730
- function requestBoundaryTemplate(functionName) {
731
- return `${MANAGED_MARKER}
732
- import { nextgenMiddleware } from "@zitadel/sdk-next/middleware";
733
- import type { NextRequest } from "next/server";
734
-
735
- export function ${functionName}(req: NextRequest) {
736
- return nextgenMiddleware(req, {
737
- url: process.env.ZITADEL_URL,
738
- protectedRoutes: ["/profile"],
739
- loginPath: "/login",
740
- });
741
- }
742
-
743
- export const config = {
744
- matcher: ["/__nextgen/:path*", "/profile/:path*"],
745
- };
746
- `;
747
- }
748
- /**
749
- * Rule-based patcher for the Next.js App Router. Inherits the shared
750
- * `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
751
- * Next routes and request boundary whose templates come from the chosen renderer.
752
- */
753
- var NextPatcher = class extends AbstractRulePatcher {
754
- /** Returns true for Next.js projects. */
755
- canPatch(framework) {
756
- return framework === "next";
757
- }
758
- routeOps(ctx) {
759
- return nextCodeOps(ctx, getRenderer(ctx.rendererId));
760
- }
761
- routeFiles(view) {
762
- return nextCodeFilePaths(view.framework, getRenderer(view.rendererId));
763
- }
764
- routeDeps(view) {
765
- return [getRenderer(view.rendererId).dependency.name];
766
- }
767
- summary(ctx) {
768
- return {
769
- title: "Next.js integration",
770
- detail: `Scaffolded login/register/profile routes with renderer "${ctx.rendererId}".`
771
- };
772
- }
773
- };
774
- /**
775
- * Ordered paths of the framework code files the patcher writes. All carry the
776
- * managed marker. Shared by {@link NextPatcher.routeOps} (which adds contents)
777
- * and {@link NextPatcher.routeFiles} (which only needs the paths) so the two
778
- * cannot drift.
779
- */
780
- function nextCodeFilePaths(framework, renderer) {
781
- const appDir = framework.appDir;
782
- const paths = [join(appDir, "login/page.tsx"), join(appDir, "register/page.tsx")];
783
- if (renderer.templates.profilePage) paths.push(join(appDir, "profile/page.tsx"));
784
- paths.push(join(appDir, `../${requestBoundaryFile(framework).filename}`));
785
- if (renderer.templates.provider) paths.push(join(appDir, renderer.templates.provider.filename));
786
- if (renderer.templates.customElementsDts) paths.push(join(appDir, "../custom-elements.d.ts"));
787
- return paths;
788
- }
789
- /** The Next route/request-boundary write ops plus the SDK dependency. */
790
- function nextCodeOps(ctx, renderer) {
791
- const appDir = ctx.framework.appDir;
792
- const ops = [{
793
- kind: "write",
794
- path: join(appDir, "login/page.tsx"),
795
- contents: renderer.templates.authPage("login").contents
796
- }, {
797
- kind: "write",
798
- path: join(appDir, "register/page.tsx"),
799
- contents: renderer.templates.authPage("register").contents
800
- }];
801
- const profile = renderer.templates.profilePage?.();
802
- if (profile) ops.push({
803
- kind: "write",
804
- path: join(appDir, "profile/page.tsx"),
805
- contents: profile.contents
806
- });
807
- const boundary = requestBoundaryFile(ctx.framework);
808
- ops.push({
809
- kind: "write",
810
- path: join(appDir, `../${boundary.filename}`),
811
- contents: requestBoundaryTemplate(boundary.functionName)
812
- });
813
- const provider = renderer.templates.provider;
814
- if (provider) ops.push({
815
- kind: "write",
816
- path: join(appDir, provider.filename),
817
- contents: provider.contents
818
- });
819
- const dts = renderer.templates.customElementsDts?.();
820
- if (dts) ops.push({
821
- kind: "write",
822
- path: join(appDir, "../custom-elements.d.ts"),
823
- contents: dts.contents
824
- });
825
- ops.push({
826
- kind: "add-dep",
827
- name: renderer.dependency.name,
828
- version: dependencyVersionForCli(ctx.cliVersion, renderer.dependency.version)
829
- });
830
- return ops;
831
- }
832
- function requestBoundaryFile(framework) {
833
- if ((framework.versionMajor ?? 0) >= 16) return {
834
- filename: "proxy.ts",
835
- functionName: "proxy"
836
- };
837
- return {
838
- filename: "middleware.ts",
839
- functionName: "middleware"
840
- };
841
- }
842
- function dependencyVersionForCli(cliVersion, fallback) {
843
- const normalized = cliVersion.trim().replace(/^v/, "");
844
- if (/^\d+\.\d+\.\d+-alpha\.\d+$/.test(normalized)) return normalized;
845
- return normalized.match(/^\d+\.\d+\.\d+-([0-9A-Za-z]+)(?:[.-]|$)/)?.[1] ?? fallback;
846
- }
847
- //#endregion
848
- //#region src/lib/orca/patchers/index.ts
849
- /**
850
- * Active patchers, in priority order; the first whose `canPatch` matches wins.
851
- *
852
- * Patchers are grouped by family under subdirectories: `rule/` holds the
853
- * deterministic, template-driven patchers (extending
854
- * {@link import("./rule/base").AbstractRulePatcher}). A future LLM-driven
855
- * family lives under `llm/` and registers its concrete patchers here — no
856
- * orchestrator or command changes needed. Only Next.js is supported today.
857
- */
858
- const patchers = [new NextPatcher()];
859
- //#endregion
860
- //#region src/lib/orca/scaffolders/cli.ts
861
- /**
862
- * Base for scaffolders that delegate to an external CLI (e.g. create-next-app).
863
- * Subclasses implement {@link scaffold} and call {@link runCommand}.
864
- */
865
- var AbstractCLIScaffolder = class {
866
- /** True when the requested framework is in {@link supportedFrameworks}. */
867
- canScaffold(framework) {
868
- return this.supportedFrameworks.includes(framework);
869
- }
870
- /**
871
- * Runs an external command in `cwd`, throwing a typed {@link ZitadelError} on
872
- * failure so the cause surfaces as a categorized CLI error. Distinguishes
873
- * "binary not on PATH" (`ENOENT` from the spawn itself) from "binary ran but
874
- * exited non-zero" — the former previously got masked as a generic
875
- * `exited with status 1`, leaving users to guess. Tests stub
876
- * `node:child_process` to assert the command without spawning.
877
- */
878
- runCommand(command, args, cwd) {
879
- const result = spawnSync(command, [...args], {
880
- cwd,
881
- encoding: "utf8"
882
- });
883
- if (result.error) {
884
- const err = result.error;
885
- const notFound = err.code === "ENOENT";
886
- throw new ZitadelError("E_VALIDATION", notFound ? `Command not found: ${command}` : `Failed to spawn "${command}": ${err.message}`, {
887
- hint: notFound ? `Ensure '${command}' is installed and on PATH.` : void 0,
888
- details: {
889
- command,
890
- args: [...args],
891
- code: err.code
892
- }
893
- });
894
- }
895
- const status = result.status ?? 1;
896
- if (status !== 0) {
897
- const stdout = String(result.stdout ?? "");
898
- const stderr = String(result.stderr ?? "");
899
- const output = truncateCommandOutput([stderr, stdout].filter(Boolean).join("\n").trim());
900
- throw new ZitadelError("E_VALIDATION", `Command "${command} ${args.join(" ")}" exited with status ${String(status)}`, {
901
- hint: output ? `Command output:\n${output}` : "Run the command directly for more detail.",
902
- details: {
903
- command,
904
- args: [...args],
905
- cwd,
906
- stdout,
907
- stderr
908
- }
909
- });
910
- }
911
- }
912
- };
913
- function truncateCommandOutput(output) {
914
- const limit = 4e3;
915
- if (output.length <= limit) return output;
916
- return `${output.slice(0, limit)}\n... output truncated ...`;
917
- }
918
- //#endregion
919
- //#region src/lib/orca/scaffolders/next.ts
920
- const CREATE_NEXT_APP_VERSION = "16.2.4";
921
- /** Scaffolds a new Next.js App Router project with `create-next-app`. */
922
- var NextScaffolder = class extends AbstractCLIScaffolder {
923
- displayName = "Next.js";
924
- supportedFrameworks = ["next"];
925
- /**
926
- * Runs the pinned `create-next-app` version in `cwd`, creating a TypeScript
927
- * App Router project in place. `--yes` accepts all defaults so the command
928
- * runs unattended, and `--skip-install` leaves dependency installation to the
929
- * setup command's explicit next step after Zitadel patches package.json.
930
- */
931
- async scaffold(cwd, _framework) {
932
- this.runCommand("npx", [
933
- "--yes",
934
- `create-next-app@${CREATE_NEXT_APP_VERSION}`,
935
- ".",
936
- "--ts",
937
- "--app",
938
- "--use-npm",
939
- "--disable-git",
940
- "--yes",
941
- "--skip-install"
942
- ], cwd);
943
- }
944
- };
945
- //#endregion
946
- //#region src/lib/orca/scaffolders/index.ts
947
- /**
948
- * Active scaffolders, in priority order. The framework picker derives its
949
- * choices from this list. Add a new framework by appending its scaffolder
950
- * here — no orchestrator changes needed.
951
- */
952
- const scaffolders = [new NextScaffolder()];
953
- //#endregion
954
- //#region src/lib/orca/index.ts
955
- /**
956
- * Orchestrates the three per-framework strategies — detectors (recognise an
957
- * existing project and extract its facts), scaffolders (create a project), and
958
- * patchers (integrate Zitadel) — over their respective registries. It resolves
959
- * the right strategy for a framework and drives the detect/scaffold lifecycle;
960
- * how a patcher applies its work (file operations vs an LLM agent) stays
961
- * internal to that patcher. Registries are injected so tests can supply fakes.
962
- */
963
- var Orca = class {
964
- constructor(detectors, scaffolders, patchers) {
965
- this.detectors = detectors;
966
- this.scaffolders = scaffolders;
967
- this.patchers = patchers;
968
- }
969
- /**
970
- * Detects the framework in `cwd` and extracts its {@link FrameworkFacts},
971
- * honouring an explicit `requested` framework. Throws
972
- * `E_FRAMEWORK_NOT_DETECTED` when nothing matches; a detector's
973
- * `E_UNSUPPORTED_PROJECT_SHAPE` (recognised but unsupported) propagates.
974
- */
975
- async detect(cwd, requested) {
976
- const candidates = requested ? this.detectors.filter((detector) => detector.framework === requested) : this.detectors;
977
- if (requested && candidates.length === 0) throw new ZitadelError("E_FRAMEWORK_NOT_DETECTED", `Unsupported framework "${requested}"`, { hint: `Supported frameworks: ${this.frameworkIds().join(", ")}.` });
978
- for (const detector of candidates) {
979
- const facts = await detector.detect(cwd);
980
- if (facts) return facts;
981
- }
982
- 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." });
983
- }
984
- /**
985
- * Non-throwing detection: returns `undefined` instead of raising for a
986
- * project that is absent, unrecognised, or recognised-but-unsupported, so
987
- * callers (e.g. `eject`) can probe and degrade gracefully.
988
- */
989
- async tryDetect(cwd) {
990
- try {
991
- return await this.detect(cwd);
992
- } catch (error) {
993
- if (error instanceof ZitadelError && (error.code === "E_FRAMEWORK_NOT_DETECTED" || error.code === "E_UNSUPPORTED_PROJECT_SHAPE")) return;
994
- throw error;
995
- }
996
- }
997
- /** Whether `cwd` is safe for an in-place framework scaffold. */
998
- async isFreshScaffoldTarget(cwd) {
999
- return (await inspectScaffoldTarget(cwd)).scaffoldable;
1000
- }
1001
- /**
1002
- * Creates a new `framework` project in `cwd`, then re-detects it to return
1003
- * the resulting {@link FrameworkFacts}. Throws `E_CONFLICT` when the directory
1004
- * already contains a project ("already scaffolded") and `E_VALIDATION` when no
1005
- * scaffolder supports the framework.
1006
- */
1007
- async scaffold(cwd, framework) {
1008
- const target = await inspectScaffoldTarget(cwd);
1009
- if (!target.scaffoldable) throw new ZitadelError("E_CONFLICT", `Cannot scaffold: ${cwd} is not empty`, {
1010
- hint: target.reason ?? "Run setup in an empty directory, or run setup from an existing supported app project.",
1011
- details: { entries: target.entries }
1012
- });
1013
- const stash = target.hasRuntimeOnlyZitadel ? await stashRuntimeOnlyZitadel(cwd) : void 0;
1014
- try {
1015
- await this.scaffolderFor(framework).scaffold(cwd, framework);
1016
- } finally {
1017
- await restoreRuntimeOnlyZitadel(cwd, stash);
1018
- }
1019
- return this.detect(cwd, framework);
1020
- }
1021
- /**
1022
- * Resolves the scaffolder for a framework, throwing `E_VALIDATION` (with the
1023
- * available list) when none matches.
1024
- */
1025
- scaffolderFor(framework) {
1026
- const scaffolder = this.scaffolders.find((candidate) => candidate.canScaffold(framework));
1027
- if (!scaffolder) throw new ZitadelError("E_VALIDATION", `No scaffolder supports "${framework}"`, { hint: `Available frameworks: ${this.availableFrameworks().map((f) => f.id).join(", ")}.` });
1028
- return scaffolder;
1029
- }
1030
- /**
1031
- * Resolves the patcher for a framework, throwing `E_VALIDATION` when none
1032
- * matches (e.g. a framework that can be scaffolded but not yet integrated).
1033
- */
1034
- patcherFor(framework) {
1035
- const patcher = this.patchers.find((candidate) => candidate.canPatch(framework));
1036
- if (!patcher) throw new ZitadelError("E_VALIDATION", `No patcher supports "${framework}"`, { hint: "Zitadel integration currently supports Next.js." });
1037
- return patcher;
1038
- }
1039
- /** The frameworks that can be scaffolded, derived from the scaffolder registry. */
1040
- availableFrameworks() {
1041
- return this.scaffolders.map((scaffolder) => ({
1042
- id: scaffolder.supportedFrameworks[0] ?? scaffolder.displayName,
1043
- displayName: scaffolder.displayName
1044
- }));
1045
- }
1046
- frameworkIds() {
1047
- return this.detectors.map((detector) => detector.framework);
1048
- }
1049
- };
1050
- /** {@link Orca} wired with the default detector, scaffolder, and patcher registries. */
1051
- function createOrca() {
1052
- return new Orca(detectors, scaffolders, patchers);
1053
- }
1054
- async function inspectScaffoldTarget(cwd) {
1055
- const entries = await readdir(cwd, { withFileTypes: true });
1056
- const names = entries.map((entry) => entry.name).sort();
1057
- let hasRuntimeOnlyZitadel = false;
1058
- for (const entry of entries) {
1059
- if (entry.name === ".gitignore") {
1060
- if (!entry.isFile()) return {
1061
- scaffoldable: false,
1062
- hasRuntimeOnlyZitadel: false,
1063
- reason: ".gitignore exists but is not a file.",
1064
- entries: names
1065
- };
1066
- continue;
1067
- }
1068
- if (entry.name === ".zitadel") {
1069
- if (!entry.isDirectory() || !await isRuntimeOnlyZitadelDir(join(cwd, ".zitadel"))) return {
1070
- scaffoldable: false,
1071
- hasRuntimeOnlyZitadel: false,
1072
- reason: ".zitadel contains project state. Move it aside or run setup from an empty app directory.",
1073
- entries: names
1074
- };
1075
- hasRuntimeOnlyZitadel = true;
1076
- continue;
1077
- }
1078
- return {
1079
- scaffoldable: false,
1080
- hasRuntimeOnlyZitadel: false,
1081
- reason: `Directory contains ${entry.name}. Run setup from an empty directory to scaffold a new app.`,
1082
- entries: names
1083
- };
1084
- }
1085
- return {
1086
- scaffoldable: true,
1087
- hasRuntimeOnlyZitadel,
1088
- entries: names
1089
- };
1090
- }
1091
- async function isRuntimeOnlyZitadelDir(path) {
1092
- const entries = await readdir(path, { withFileTypes: true });
1093
- if (entries.length !== 1 || entries[0]?.name !== "local" || !entries[0].isDirectory()) return false;
1094
- return true;
1095
- }
1096
- async function stashRuntimeOnlyZitadel(cwd) {
1097
- const source = join(cwd, ".zitadel");
1098
- const stash = join(dirname(cwd), `${`.${basename(cwd)}.zitadel-local-stash`}-${String(process.pid)}-${String(Date.now())}`);
1099
- await rename(source, stash);
1100
- return stash;
1101
- }
1102
- async function restoreRuntimeOnlyZitadel(cwd, stash) {
1103
- if (!stash) return;
1104
- const target = join(cwd, ".zitadel");
1105
- try {
1106
- await rename(stash, target);
1107
- await appendGitignoreEntry(cwd, ".zitadel/local/");
1108
- return;
1109
- } catch (error) {
1110
- if (!isErrno(error, "EEXIST")) throw error;
1111
- }
1112
- await mkdir(target, {
1113
- recursive: true,
1114
- mode: 448
1115
- });
1116
- await rename(join(stash, "local"), join(target, "local"));
1117
- await rm(stash, {
1118
- recursive: true,
1119
- force: true
1120
- });
1121
- await appendGitignoreEntry(cwd, ".zitadel/local/");
1122
- }
1123
- async function appendGitignoreEntry(cwd, entry) {
1124
- const path = join(cwd, ".gitignore");
1125
- let existing = "";
1126
- try {
1127
- existing = await readFile(path, "utf8");
1128
- } catch (error) {
1129
- if (!isErrno(error, "ENOENT")) throw error;
1130
- }
1131
- if (existing.split(/\r?\n/g).map((line) => line.trim()).includes(entry)) return;
1132
- const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
1133
- await writeFile(path, `${existing}${prefix}${entry}\n`);
1134
- }
1135
- function isErrno(error, code) {
1136
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
1137
- }
1138
- //#endregion
1139
- export { RENDERER_IDS as n, issuerFromPort as r, createOrca as t };
1140
-
1141
- //# sourceMappingURL=orca-BGM8VCgQ.mjs.map