@zitadel/cli 0.1.0-alpha.16 → 0.1.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -107
- package/SKILLS.md +98 -13
- package/dist/branding-BXj028-W.mjs +79 -0
- package/dist/branding-BXj028-W.mjs.map +1 -0
- package/dist/claim-state-BTGInAaN.mjs +66 -0
- package/dist/claim-state-BTGInAaN.mjs.map +1 -0
- package/dist/commands/apply.mjs +12 -7
- package/dist/commands/apply.mjs.map +1 -1
- package/dist/commands/branding/eject.mjs +125 -0
- package/dist/commands/branding/eject.mjs.map +1 -0
- package/dist/commands/claim.mjs +402 -0
- package/dist/commands/claim.mjs.map +1 -0
- package/dist/commands/doctor.mjs +477 -38
- package/dist/commands/doctor.mjs.map +1 -1
- package/dist/commands/eject.mjs +3 -3
- package/dist/commands/logs.mjs +2 -2
- package/dist/commands/plan.mjs +7 -5
- package/dist/commands/plan.mjs.map +1 -1
- package/dist/commands/reset.mjs +2 -2
- package/dist/commands/schemas/list.mjs +14 -10
- package/dist/commands/schemas/list.mjs.map +1 -1
- package/dist/commands/setup.mjs +331 -120
- package/dist/commands/setup.mjs.map +1 -1
- package/dist/commands/start.mjs +3 -4
- package/dist/commands/start.mjs.map +1 -1
- package/dist/commands/status.mjs +21 -13
- package/dist/commands/status.mjs.map +1 -1
- package/dist/commands/stop.mjs +3 -3
- package/dist/commands/stop.mjs.map +1 -1
- package/dist/{docker-BbX0yOpg.mjs → docker-Ciz9N6uI.mjs} +2 -2
- package/dist/{docker-BbX0yOpg.mjs.map → docker-Ciz9N6uI.mjs.map} +1 -1
- package/dist/{environment-ABj1IWZt.mjs → environment-CuHKxtIO.mjs} +1 -1
- package/dist/{environment-ABj1IWZt.mjs.map → environment-CuHKxtIO.mjs.map} +1 -1
- package/dist/{journey-guidance-E_3_UwGt.mjs → journey-guidance-CtQUfWar.mjs} +14 -3
- package/dist/{journey-guidance-E_3_UwGt.mjs.map → journey-guidance-CtQUfWar.mjs.map} +1 -1
- package/dist/{oclif-SjHLB_XK.mjs → oclif-CL6-M13m.mjs} +220 -43
- package/dist/oclif-CL6-M13m.mjs.map +1 -0
- package/dist/{orca-_TmrJQMw.mjs → orca-BWB4zOdw.mjs} +749 -192
- package/dist/orca-BWB4zOdw.mjs.map +1 -0
- package/dist/{ports-XujDtA47.mjs → ports-Cu2Y7aXQ.mjs} +25 -20
- package/dist/ports-Cu2Y7aXQ.mjs.map +1 -0
- package/dist/{processes-p2PPbKGG.mjs → processes-Bn2WngOi.mjs} +1 -1
- package/dist/{processes-p2PPbKGG.mjs.map → processes-Bn2WngOi.mjs.map} +1 -1
- package/dist/{project-DXc7q4wN.mjs → project-yejJSfdJ.mjs} +57 -4
- package/dist/project-yejJSfdJ.mjs.map +1 -0
- package/dist/scaffold-manifest-CKNFLX9T.mjs +81 -0
- package/dist/scaffold-manifest-CKNFLX9T.mjs.map +1 -0
- package/dist/{sync-Bu_iLIbb.mjs → sync-Cx2_4rXW.mjs} +178 -52
- package/dist/sync-Cx2_4rXW.mjs.map +1 -0
- package/dist/user-schema-W1OsXdpc.mjs +91 -0
- package/dist/user-schema-W1OsXdpc.mjs.map +1 -0
- package/oclif.manifest.json +227 -5
- package/package.json +7 -5
- package/dist/docker-guidance-3UZESTfb.mjs +0 -21
- package/dist/docker-guidance-3UZESTfb.mjs.map +0 -1
- package/dist/oclif-SjHLB_XK.mjs.map +0 -1
- package/dist/orca-_TmrJQMw.mjs.map +0 -1
- package/dist/ports-XujDtA47.mjs.map +0 -1
- package/dist/project-DXc7q4wN.mjs.map +0 -1
- package/dist/sync-Bu_iLIbb.mjs.map +0 -1
- package/dist/user-schema-DDz5-lX5.mjs +0 -13
- package/dist/user-schema-DDz5-lX5.mjs.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
1
|
+
import { C as parseJsonObject, E as ZitadelError, O as npmDistTagForCliVersion, S as isObject, T as stableStringify, b as MANAGED_MARKER, k as publicCliCommand, n as DEFAULT_SERVER, w as setTopLevelJsonKey } from "./oclif-CL6-M13m.mjs";
|
|
2
|
+
import { access, chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import semver from "semver";
|
|
4
5
|
import { META_SCHEMA_DIR, metaSchemaFiles } from "@zitadel/config/meta-schemas";
|
|
5
6
|
import { builders, generateCode, parseModule } from "magicast";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -21,12 +22,33 @@ async function readPackageJson(cwd) {
|
|
|
21
22
|
function hasDependency(pkg, name) {
|
|
22
23
|
return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Best-effort major version of a declared dependency: the first number in the
|
|
27
|
+
* spec. A heuristic for scaffold decisions (e.g. which Next request-boundary
|
|
28
|
+
* convention to emit), NOT for support gating — range semantics like `<16`
|
|
29
|
+
* are beyond it. Floors use {@link dependencySpecProvablyBelowMajor}.
|
|
30
|
+
*/
|
|
24
31
|
function dependencyVersionMajor(pkg, name) {
|
|
25
32
|
const spec = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
26
33
|
if (!spec) return;
|
|
27
34
|
const match = spec.match(/\d+/);
|
|
28
35
|
return match ? Number(match[0]) : void 0;
|
|
29
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the declared spec for `name` when that spec provably cannot resolve
|
|
39
|
+
* to `floorMajor` or newer: a valid semver range with no overlap with
|
|
40
|
+
* `>=floorMajor.0.0-0`. Everything unprovable returns `undefined` — protocol
|
|
41
|
+
* specs (`file:`, `link:`, `workspace:`, git URLs), dist-tags (`latest`,
|
|
42
|
+
* `canary`), absent deps, and ranges that also admit a compliant version
|
|
43
|
+
* (`>=14`, `14 || 16`). The floor comparator includes prereleases so a
|
|
44
|
+
* `15.0.0-rc` user is not rejected by the 15 floor.
|
|
45
|
+
*/
|
|
46
|
+
function dependencySpecProvablyBelowMajor(pkg, name, floorMajor) {
|
|
47
|
+
const spec = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
48
|
+
if (!spec || semver.validRange(spec) === null) return;
|
|
49
|
+
const atOrAboveFloor = `>=${String(floorMajor)}.0.0-0`;
|
|
50
|
+
return semver.intersects(spec, atOrAboveFloor, { includePrerelease: true }) ? void 0 : spec;
|
|
51
|
+
}
|
|
30
52
|
//#endregion
|
|
31
53
|
//#region src/lib/orca/detectors/port.ts
|
|
32
54
|
/**
|
|
@@ -136,15 +158,18 @@ var NextDetector = class {
|
|
|
136
158
|
async detect(cwd) {
|
|
137
159
|
const pkg = await readPackageJson(cwd).catch(() => void 0);
|
|
138
160
|
if (!pkg || !hasDependency(pkg, "next")) return null;
|
|
161
|
+
const belowFloor = dependencySpecProvablyBelowMajor(pkg, "next", 15);
|
|
162
|
+
if (belowFloor !== void 0) throw new ZitadelError("E_UNSUPPORTED_PROJECT_SHAPE", `Next.js "${belowFloor}" is below the supported floor — the CLI integrates Next.js 15 and newer`, { hint: "Upgrade the app to Next 15+ (e.g. `npx @next/codemod@latest upgrade`) and rerun." });
|
|
139
163
|
const appDir = await dirExists$1(join(cwd, "app")) ? "app" : await dirExists$1(join(cwd, "src/app")) ? "src/app" : void 0;
|
|
140
164
|
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." });
|
|
141
165
|
const devPort = await detectDevPort(cwd, pkg);
|
|
166
|
+
const versionMajor = dependencyVersionMajor(pkg, "next");
|
|
142
167
|
return {
|
|
143
168
|
id: "next",
|
|
144
169
|
appDir,
|
|
145
170
|
devPort,
|
|
146
171
|
url: issuerFromPort(devPort),
|
|
147
|
-
versionMajor:
|
|
172
|
+
...versionMajor === void 0 ? {} : { versionMajor }
|
|
148
173
|
};
|
|
149
174
|
}
|
|
150
175
|
};
|
|
@@ -224,12 +249,16 @@ var ReactDetector = class {
|
|
|
224
249
|
async detect(cwd) {
|
|
225
250
|
const pkg = await readPackageJson(cwd).catch(() => void 0);
|
|
226
251
|
if (!pkg || hasDependency(pkg, "next") || !hasDependency(pkg, "react") || !hasDependency(pkg, "vite")) return null;
|
|
252
|
+
const belowFloor = dependencySpecProvablyBelowMajor(pkg, "react", 18);
|
|
253
|
+
if (belowFloor !== void 0) throw new ZitadelError("E_UNSUPPORTED_PROJECT_SHAPE", `React "${belowFloor}" is below the supported floor — the CLI integrates React 18 and newer`, { hint: "Upgrade the app to React 18+ and rerun." });
|
|
227
254
|
const devPort = await detectDevPort(cwd, pkg);
|
|
255
|
+
const versionMajor = dependencyVersionMajor(pkg, "react");
|
|
228
256
|
return {
|
|
229
257
|
id: "react",
|
|
230
258
|
appDir: "src",
|
|
231
259
|
devPort,
|
|
232
|
-
url: issuerFromPort(devPort)
|
|
260
|
+
url: issuerFromPort(devPort),
|
|
261
|
+
...versionMajor === void 0 ? {} : { versionMajor }
|
|
233
262
|
};
|
|
234
263
|
}
|
|
235
264
|
};
|
|
@@ -320,6 +349,21 @@ const detectors = [
|
|
|
320
349
|
//#endregion
|
|
321
350
|
//#region src/lib/orca/patchers/rule/file-writer/index.ts
|
|
322
351
|
/**
|
|
352
|
+
* Records one touched artifact, deduplicating by path: several plan ops can
|
|
353
|
+
* legitimately hit the same file (the base and framework op lists both merge
|
|
354
|
+
* into `.env.local`, for example), but the report should carry it once, with
|
|
355
|
+
* the first action as the net one — a file created and then extended in the
|
|
356
|
+
* same run was created by the run.
|
|
357
|
+
*/
|
|
358
|
+
function record(result, path, kind, action) {
|
|
359
|
+
if (result.files.some((file) => file.path === path)) return;
|
|
360
|
+
result.files.push({
|
|
361
|
+
path,
|
|
362
|
+
kind,
|
|
363
|
+
action
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
323
367
|
* Applies a {@link ScaffoldPlan} to disk, executing its operations in order.
|
|
324
368
|
*
|
|
325
369
|
* Operations are idempotent: writes whose target already matches the desired
|
|
@@ -332,12 +376,19 @@ const detectors = [
|
|
|
332
376
|
async function scaffold(plan, opts) {
|
|
333
377
|
const result = {
|
|
334
378
|
dryRun: opts.dryRun,
|
|
335
|
-
|
|
379
|
+
files: [],
|
|
336
380
|
filesSkipped: [],
|
|
337
381
|
depsAdded: []
|
|
338
382
|
};
|
|
339
383
|
for (const op of plan.ops) await applyOp(op, opts, result);
|
|
340
|
-
|
|
384
|
+
const written = new Set(result.files.map((file) => file.path));
|
|
385
|
+
return {
|
|
386
|
+
dryRun: result.dryRun,
|
|
387
|
+
files: result.files,
|
|
388
|
+
filesWritten: result.files.filter((file) => file.kind === "file").map((file) => file.path),
|
|
389
|
+
filesSkipped: [...new Set(result.filesSkipped)].filter((path) => !written.has(path)),
|
|
390
|
+
depsAdded: result.depsAdded
|
|
391
|
+
};
|
|
341
392
|
}
|
|
342
393
|
async function applyOp(op, opts, result) {
|
|
343
394
|
switch (op.kind) {
|
|
@@ -398,8 +449,9 @@ async function editFile(cwd, pathOrPaths, edit, dryRun, result) {
|
|
|
398
449
|
result.filesSkipped.push(path);
|
|
399
450
|
return;
|
|
400
451
|
}
|
|
452
|
+
const action = source === void 0 ? "create" : "update";
|
|
401
453
|
if (dryRun) {
|
|
402
|
-
result
|
|
454
|
+
record(result, path, "file", action);
|
|
403
455
|
return;
|
|
404
456
|
}
|
|
405
457
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -407,11 +459,16 @@ async function editFile(cwd, pathOrPaths, edit, dryRun, result) {
|
|
|
407
459
|
await writeFile(tmp, next);
|
|
408
460
|
if (mode !== void 0) await chmod(tmp, mode).catch(() => void 0);
|
|
409
461
|
await rename(tmp, path);
|
|
410
|
-
result
|
|
462
|
+
record(result, path, "file", action);
|
|
411
463
|
}
|
|
412
464
|
async function ensureDir(path, mode, dryRun, result) {
|
|
465
|
+
const pre = await stat(path).catch(() => void 0);
|
|
466
|
+
const existed = pre?.isDirectory() ?? false;
|
|
467
|
+
const healsMode = existed && mode !== void 0 && process.platform !== "win32" && (pre.mode & 511) !== mode;
|
|
413
468
|
if (dryRun) {
|
|
414
|
-
result
|
|
469
|
+
if (!existed) record(result, path, "dir", "create");
|
|
470
|
+
else if (healsMode) record(result, path, "dir", "update");
|
|
471
|
+
else result.filesSkipped.push(path);
|
|
415
472
|
return;
|
|
416
473
|
}
|
|
417
474
|
await mkdir(path, {
|
|
@@ -419,7 +476,9 @@ async function ensureDir(path, mode, dryRun, result) {
|
|
|
419
476
|
mode
|
|
420
477
|
});
|
|
421
478
|
if (mode) await chmod(path, mode).catch(() => void 0);
|
|
422
|
-
result
|
|
479
|
+
if (!existed) record(result, path, "dir", "create");
|
|
480
|
+
else if (healsMode) record(result, path, "dir", "update");
|
|
481
|
+
else result.filesSkipped.push(path);
|
|
423
482
|
}
|
|
424
483
|
async function writeText(path, contents, opts, result) {
|
|
425
484
|
const existing = await readIfExists(path);
|
|
@@ -431,8 +490,9 @@ async function writeText(path, contents, opts, result) {
|
|
|
431
490
|
hint: "Re-run with --force if you want the CLI to replace this file.",
|
|
432
491
|
details: { path }
|
|
433
492
|
});
|
|
493
|
+
const action = existing === void 0 ? "create" : "update";
|
|
434
494
|
if (opts.dryRun) {
|
|
435
|
-
result
|
|
495
|
+
record(result, path, "file", action);
|
|
436
496
|
return;
|
|
437
497
|
}
|
|
438
498
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -440,10 +500,11 @@ async function writeText(path, contents, opts, result) {
|
|
|
440
500
|
await writeFile(tmp, contents, { mode: opts.mode });
|
|
441
501
|
if (opts.mode) await chmod(tmp, opts.mode).catch(() => void 0);
|
|
442
502
|
await rename(tmp, path);
|
|
443
|
-
result
|
|
503
|
+
record(result, path, "file", action);
|
|
444
504
|
}
|
|
445
505
|
async function appendText(path, contents, ifMissing, dryRun, result) {
|
|
446
|
-
const
|
|
506
|
+
const raw = await readIfExists(path);
|
|
507
|
+
const existing = raw ?? "";
|
|
447
508
|
if (ifMissing && existing.includes(ifMissing)) {
|
|
448
509
|
result.filesSkipped.push(path);
|
|
449
510
|
return;
|
|
@@ -453,16 +514,18 @@ async function appendText(path, contents, ifMissing, dryRun, result) {
|
|
|
453
514
|
result.filesSkipped.push(path);
|
|
454
515
|
return;
|
|
455
516
|
}
|
|
517
|
+
const action = raw === void 0 ? "create" : "update";
|
|
456
518
|
if (dryRun) {
|
|
457
|
-
result
|
|
519
|
+
record(result, path, "file", action);
|
|
458
520
|
return;
|
|
459
521
|
}
|
|
460
522
|
await mkdir(dirname(path), { recursive: true });
|
|
461
523
|
await writeFile(path, next);
|
|
462
|
-
result
|
|
524
|
+
record(result, path, "file", action);
|
|
463
525
|
}
|
|
464
526
|
async function mergeEnv(path, entries, dryRun, result) {
|
|
465
|
-
const
|
|
527
|
+
const raw = await readIfExists(path);
|
|
528
|
+
const existing = raw ?? "";
|
|
466
529
|
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)));
|
|
467
530
|
const additions = Object.entries(entries).filter(([key]) => !present.has(key));
|
|
468
531
|
if (additions.length === 0) {
|
|
@@ -471,13 +534,14 @@ async function mergeEnv(path, entries, dryRun, result) {
|
|
|
471
534
|
}
|
|
472
535
|
const block = additions.map(([key, value]) => `${key}=${value}`).join("\n");
|
|
473
536
|
const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${block}\n`;
|
|
537
|
+
const action = raw === void 0 ? "create" : "update";
|
|
474
538
|
if (dryRun) {
|
|
475
|
-
result
|
|
539
|
+
record(result, path, "file", action);
|
|
476
540
|
return;
|
|
477
541
|
}
|
|
478
542
|
await mkdir(dirname(path), { recursive: true });
|
|
479
543
|
await writeFile(path, next);
|
|
480
|
-
result
|
|
544
|
+
record(result, path, "file", action);
|
|
481
545
|
}
|
|
482
546
|
async function mergeJson(path, patch, dryRun, result) {
|
|
483
547
|
const existing = await readIfExists(path);
|
|
@@ -486,16 +550,18 @@ async function mergeJson(path, patch, dryRun, result) {
|
|
|
486
550
|
result.filesSkipped.push(path);
|
|
487
551
|
return;
|
|
488
552
|
}
|
|
553
|
+
const action = existing === void 0 ? "create" : "update";
|
|
489
554
|
if (dryRun) {
|
|
490
|
-
result
|
|
555
|
+
record(result, path, "file", action);
|
|
491
556
|
return;
|
|
492
557
|
}
|
|
493
558
|
await mkdir(dirname(path), { recursive: true });
|
|
494
559
|
await writeFile(path, contents);
|
|
495
|
-
result
|
|
560
|
+
record(result, path, "file", action);
|
|
496
561
|
}
|
|
497
562
|
async function appendGitignore(path, entries, dryRun, result) {
|
|
498
|
-
const
|
|
563
|
+
const raw = await readIfExists(path);
|
|
564
|
+
const existing = raw ?? "";
|
|
499
565
|
const lines = new Set(existing.split(/\r?\n/g).map((line) => line.trim()));
|
|
500
566
|
const missing = entries.filter((entry) => !lines.has(entry));
|
|
501
567
|
if (missing.length === 0) {
|
|
@@ -503,12 +569,13 @@ async function appendGitignore(path, entries, dryRun, result) {
|
|
|
503
569
|
return;
|
|
504
570
|
}
|
|
505
571
|
const next = `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${missing.join("\n")}\n`;
|
|
572
|
+
const action = raw === void 0 ? "create" : "update";
|
|
506
573
|
if (dryRun) {
|
|
507
|
-
result
|
|
574
|
+
record(result, path, "file", action);
|
|
508
575
|
return;
|
|
509
576
|
}
|
|
510
577
|
await writeFile(path, next);
|
|
511
|
-
result
|
|
578
|
+
record(result, path, "file", action);
|
|
512
579
|
}
|
|
513
580
|
async function addDependency(path, op, dryRun, result) {
|
|
514
581
|
const existing = await readIfExists(path);
|
|
@@ -520,20 +587,23 @@ async function addDependency(path, op, dryRun, result) {
|
|
|
520
587
|
result.filesSkipped.push(path);
|
|
521
588
|
return;
|
|
522
589
|
}
|
|
523
|
-
|
|
590
|
+
const contents = setTopLevelJsonKey(existing, path, key, sortByKey({
|
|
524
591
|
...deps,
|
|
525
592
|
[op.name]: op.version
|
|
526
|
-
};
|
|
527
|
-
const contents = `${stableStringify(current)}\n`;
|
|
593
|
+
}));
|
|
528
594
|
if (dryRun) {
|
|
529
|
-
result
|
|
595
|
+
record(result, path, "file", "update");
|
|
530
596
|
result.depsAdded.push(op.name);
|
|
531
597
|
return;
|
|
532
598
|
}
|
|
533
599
|
await writeFile(path, contents);
|
|
534
|
-
result
|
|
600
|
+
record(result, path, "file", "update");
|
|
535
601
|
result.depsAdded.push(op.name);
|
|
536
602
|
}
|
|
603
|
+
/** Rebuilds an object with lexicographically sorted keys (dependency maps). */
|
|
604
|
+
function sortByKey(value) {
|
|
605
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
606
|
+
}
|
|
537
607
|
async function readIfExists(path) {
|
|
538
608
|
try {
|
|
539
609
|
return await readFile(path, "utf8");
|
|
@@ -594,6 +664,9 @@ function agentsGuidanceSection(ctx) {
|
|
|
594
664
|
const plan = publicCliCommand("plan", ctx.cliVersion);
|
|
595
665
|
const apply = publicCliCommand("apply", ctx.cliVersion);
|
|
596
666
|
const passkeyVerifyNote = ctx.preset === "passkey-first" ? " Agents: automated browsers can't complete passkey ceremonies — verify the loop via the email/password fallback actions, or attach a CDP WebAuthn virtual authenticator." : "";
|
|
667
|
+
const jsxTypesNote = ctx.framework.id === "next" ? " React JSX types for the `<zitadel-*>` elements ship with the SDK — `custom-elements.d.ts` references `@zitadel/sdk-next/jsx`." : "";
|
|
668
|
+
const presentationParagraph = ctx.posture === "widget" ? "Presentation, by contrast, is edited in the generated pages: this app pre-dates setup, so they embed the sign-in widgets as `variant=\"widget\"` cards (with `theme=\"auto\"`) inside your existing layout; switch to `variant=\"page\"` for the widget's own full-page chrome, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme." : "Presentation, by contrast, is edited in the generated pages: they pin the sign-in widgets to `variant=\"page\"` (full-page chrome); switch to `variant=\"widget\"` to embed a card inside your own layout, and set `theme` (`light` | `dark` | `auto`) to pick the color scheme.";
|
|
669
|
+
const sessionStateParagraph = ctx.framework.id === "next" ? "Your app's own chrome (header navigation, account menus) reads session state with `getSession()` from `@zitadel/sdk-next/session` — a no-store client read of the same-origin `/__nextgen/sessions/me` that the session card itself uses. It returns identity only for 200 with a non-empty `user_id`, and signed-out only for the backend's canonical `401/auth.unauthorized` or `404/sess.not_found`; any other response is unknown/error, never signed-out. In Server Components on routes covered by the request-boundary `matcher`, `auth()` from `@zitadel/sdk-next/server` works too. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load without extra wiring." : ctx.framework.id === "nuxt" ? "Your app's own chrome (header navigation, account menus) reads session state with the auto-imported `useAuth()` composable — seeded from the server on every render by the scaffolded auth plugin. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so the state is fresh on every page load." : "Your app's own chrome (header navigation, account menus) can read session state from the same-origin `/__nextgen/sessions/me` — the same server-answered read the session card performs. Fetch with `credentials: \"include\"`, `cache: \"no-store\"`, and `Accept: application/json`; treat 200 as signed-in only with a non-empty `user_id`, and signed-out only for the canonical error envelope `401/auth.unauthorized` or `404/sess.not_found`. Any other response is unknown/error, never signed-out. Sign-in and sign-out navigate (`post-sign-in-url` / `post-sign-out-url`), so chrome re-reads on the next page load.";
|
|
597
670
|
return `## Authentication (Zitadel)
|
|
598
671
|
|
|
599
672
|
This app's login is managed by Zitadel. Local config is the source of truth; never change auth behavior by editing generated route files.
|
|
@@ -610,6 +683,10 @@ The golden path:
|
|
|
610
683
|
- \`${apply}\`
|
|
611
684
|
- Agents: append \`--non-interactive --json\` to both. \`plan\` validates flow invariants with the server's own rules **before** anything uploads — fix what it reports and re-run.
|
|
612
685
|
|
|
686
|
+
${presentationParagraph}${jsxTypesNote}
|
|
687
|
+
|
|
688
|
+
${sessionStateParagraph}
|
|
689
|
+
|
|
613
690
|
Machine-readable dialect (read these before authoring flow or schema edits):
|
|
614
691
|
|
|
615
692
|
- Flow files carry \`"$schema": "../meta/flow-definition.json"\` — the flow dialect spec (steps, actions and their kinds, transitions, reserved outcomes like \`user_not_found\`). Editors validate against it.
|
|
@@ -658,6 +735,70 @@ Guidance for AI agents working in this repository.
|
|
|
658
735
|
function reclaimableOps(plan) {
|
|
659
736
|
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"));
|
|
660
737
|
}
|
|
738
|
+
/**
|
|
739
|
+
* Narrow a reclaimable op list to what a missing-only repair may replay:
|
|
740
|
+
* content ops (`write`, and `edit` marked `overwrites`) survive only when
|
|
741
|
+
* their target file does not exist, so the repair can restore a deleted
|
|
742
|
+
* managed file but can never touch an edited or user-adopted one. Merging
|
|
743
|
+
* `edit` transforms and the additive kinds (`merge-env`, `append-gitignore`)
|
|
744
|
+
* pass through — they are idempotent and only add what is missing. `add-dep`
|
|
745
|
+
* is additive only while the dependency is absent: `addDependency` replaces
|
|
746
|
+
* a differing existing version, so under missing-only semantics the op is
|
|
747
|
+
* dropped whenever the dependency is already declared at *any* version —
|
|
748
|
+
* repairing an unrelated missing file must never rewrite a user-pinned
|
|
749
|
+
* range. Reads the filesystem; paths resolve against `cwd` like the
|
|
750
|
+
* file-writer's.
|
|
751
|
+
*/
|
|
752
|
+
async function withoutExistingTargets(ops, cwd) {
|
|
753
|
+
const kept = [];
|
|
754
|
+
for (const op of ops) {
|
|
755
|
+
if (op.kind === "write") {
|
|
756
|
+
if (!await exists(join(cwd, op.path))) kept.push(op);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (op.kind === "edit" && op.overwrites) {
|
|
760
|
+
const candidates = typeof op.path === "string" ? [op.path] : op.path;
|
|
761
|
+
if (!(await Promise.all(candidates.map((candidate) => exists(join(cwd, candidate))))).includes(true)) kept.push(op);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
if (op.kind === "add-dep") {
|
|
765
|
+
if (!await dependencyDeclared(cwd, op.name)) kept.push(op);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
kept.push(op);
|
|
769
|
+
}
|
|
770
|
+
return kept;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Whether `package.json` already declares `name` in dependencies or
|
|
774
|
+
* devDependencies, at any version. A missing or unparsable `package.json`
|
|
775
|
+
* counts as "not declared" so the op is kept and the file-writer's own
|
|
776
|
+
* error handling reports the real problem.
|
|
777
|
+
*/
|
|
778
|
+
async function dependencyDeclared(cwd, name) {
|
|
779
|
+
let contents;
|
|
780
|
+
try {
|
|
781
|
+
contents = await readFile(join(cwd, "package.json"), "utf8");
|
|
782
|
+
} catch {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
const pkg = JSON.parse(contents);
|
|
787
|
+
if (!isObject(pkg)) return false;
|
|
788
|
+
const declared = (key) => isObject(pkg[key]) && name in pkg[key];
|
|
789
|
+
return declared("dependencies") || declared("devDependencies");
|
|
790
|
+
} catch {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function exists(path) {
|
|
795
|
+
try {
|
|
796
|
+
await access(path);
|
|
797
|
+
return true;
|
|
798
|
+
} catch {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
661
802
|
//#endregion
|
|
662
803
|
//#region src/lib/orca/patchers/rule/base.ts
|
|
663
804
|
/**
|
|
@@ -677,28 +818,131 @@ var AbstractRulePatcher = class {
|
|
|
677
818
|
/**
|
|
678
819
|
* Re-apply only the reclaimable subset — env files, gitignore, the SDK
|
|
679
820
|
* dependency, and marker-bearing routes — leaving the user-editable
|
|
680
|
-
* `.zitadel/` resources untouched. Backs `doctor --fix`.
|
|
821
|
+
* `.zitadel/` resources untouched. Backs `doctor --fix`. With
|
|
822
|
+
* `opts.missingOnly` the content ops are further narrowed to files that do
|
|
823
|
+
* not exist (see {@link withoutExistingTargets}) and the writer runs without
|
|
824
|
+
* `force`, so the repair restores deleted managed files and can never
|
|
825
|
+
* overwrite an edited or user-adopted one.
|
|
681
826
|
*/
|
|
682
827
|
async repair(ctx, opts) {
|
|
683
828
|
const plan = this.plan(ctx);
|
|
829
|
+
let ops = opts.missingOnly ? await withoutExistingTargets(reclaimableOps(plan), opts.cwd) : reclaimableOps(plan);
|
|
830
|
+
const excluded = new Set(opts.excludePaths ?? []);
|
|
831
|
+
if (excluded.size > 0) ops = ops.filter((op) => {
|
|
832
|
+
if (op.kind === "write") return !excluded.has(op.path);
|
|
833
|
+
if (op.kind === "edit" && op.overwrites) return !(typeof op.path === "string" ? [op.path] : op.path).some((candidate) => excluded.has(candidate));
|
|
834
|
+
return true;
|
|
835
|
+
});
|
|
836
|
+
const execOpts = opts.missingOnly ? {
|
|
837
|
+
...opts,
|
|
838
|
+
force: false
|
|
839
|
+
} : opts;
|
|
684
840
|
return scaffold({
|
|
685
|
-
ops
|
|
841
|
+
ops,
|
|
686
842
|
summary: plan.summary
|
|
687
|
-
},
|
|
843
|
+
}, execOpts);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Probe whether the labelled config wirings are still applied, using each
|
|
847
|
+
* merge transform's own idempotency: the transforms only add what is
|
|
848
|
+
* missing and return the source unchanged when everything they manage is
|
|
849
|
+
* present, so a changed output means the wiring is absent from the user's
|
|
850
|
+
* config. Every labelled wiring gets a verdict: a missing host file is
|
|
851
|
+
* `detached` (the wiring cannot be applied in a file that does not exist),
|
|
852
|
+
* and a transform that throws on restructured content yields `unknown` —
|
|
853
|
+
* surfaced rather than silently dropped, so a deleted `angular.json` can
|
|
854
|
+
* never read as healthy. Read-only.
|
|
855
|
+
*/
|
|
856
|
+
async verify(ctx, opts) {
|
|
857
|
+
const statuses = [];
|
|
858
|
+
for (const op of this.plan(ctx).ops) {
|
|
859
|
+
if (op.kind !== "edit" || !op.wiring) continue;
|
|
860
|
+
const candidates = typeof op.path === "string" ? [op.path] : op.path;
|
|
861
|
+
let path = candidates[0];
|
|
862
|
+
let source;
|
|
863
|
+
for (const candidate of candidates) {
|
|
864
|
+
const contents = await readTextIfExists(join(opts.cwd, candidate));
|
|
865
|
+
if (contents !== void 0) {
|
|
866
|
+
path = candidate;
|
|
867
|
+
source = contents;
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (source === void 0) {
|
|
872
|
+
statuses.push({
|
|
873
|
+
path,
|
|
874
|
+
wiring: op.wiring,
|
|
875
|
+
state: "detached"
|
|
876
|
+
});
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
statuses.push({
|
|
881
|
+
path,
|
|
882
|
+
wiring: op.wiring,
|
|
883
|
+
state: op.edit(source) === source ? "applied" : "detached"
|
|
884
|
+
});
|
|
885
|
+
} catch (error) {
|
|
886
|
+
statuses.push({
|
|
887
|
+
path,
|
|
888
|
+
wiring: op.wiring,
|
|
889
|
+
state: "unknown",
|
|
890
|
+
reason: error instanceof Error ? error.message : "verification failed"
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return statuses;
|
|
688
895
|
}
|
|
689
896
|
/** Shared base artifacts plus the subclass's marker-bearing route files. */
|
|
690
897
|
artifacts(view) {
|
|
898
|
+
const infrastructure = new Set(this.infrastructureFiles(view));
|
|
899
|
+
const markedFiles = this.routeFiles(view);
|
|
691
900
|
return {
|
|
692
|
-
markedFiles
|
|
901
|
+
markedFiles,
|
|
693
902
|
rootConfigFiles: ["zitadel.json"],
|
|
694
903
|
directories: [".zitadel"],
|
|
695
904
|
envBackups: [".env.local"],
|
|
696
905
|
dependencies: this.routeDeps(view),
|
|
697
906
|
configEdits: this.routeConfigEdits(view),
|
|
698
|
-
guidanceFiles: ["AGENTS.md", "README.md"]
|
|
907
|
+
guidanceFiles: ["AGENTS.md", "README.md"],
|
|
908
|
+
fileClasses: Object.fromEntries(markedFiles.map((path) => [path, infrastructure.has(path) ? "infrastructure" : "presentation"])),
|
|
909
|
+
conditionalFiles: this.conditionallyScaffoldedFiles(view),
|
|
910
|
+
retiredAlternates: this.retiredAlternateFiles(view)
|
|
699
911
|
};
|
|
700
912
|
}
|
|
701
913
|
/**
|
|
914
|
+
* Current marked files mapped to sibling paths that older templates wrote
|
|
915
|
+
* in their place and the framework rejects alongside them. Drives the
|
|
916
|
+
* managed-files boundary migration. Defaults to none; Next overrides it
|
|
917
|
+
* for the `middleware.ts`/`proxy.ts` pair.
|
|
918
|
+
*/
|
|
919
|
+
retiredAlternateFiles(_view) {
|
|
920
|
+
return {};
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* The subset of {@link routeFiles} that is load-bearing for the integration
|
|
924
|
+
* (request boundary, proxies, plugins, type declarations). The `doctor`
|
|
925
|
+
* managed-files check fails when one is missing; everything else is a
|
|
926
|
+
* presentation starting point and only warns. Every patcher whose marked
|
|
927
|
+
* files include plumbing must override this — Next (boundary/provider/dts),
|
|
928
|
+
* Angular (proxy.conf.cjs), and Nuxt (plugins) do. The Vite SPAs' only
|
|
929
|
+
* marked file is the root component (presentation); their plumbing lives in
|
|
930
|
+
* `vite.config.*`, which is a config-edit merge into a user file and out of
|
|
931
|
+
* the file check's reach. Defaults to none.
|
|
932
|
+
*/
|
|
933
|
+
infrastructureFiles(_view) {
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Marked files only written on some scaffolds (e.g. a framework home page
|
|
938
|
+
* that setup replaces only when it created the app skeleton itself). The
|
|
939
|
+
* managed-files check excludes them when no scaffold manifest recorded what
|
|
940
|
+
* was actually written. Defaults to none.
|
|
941
|
+
*/
|
|
942
|
+
conditionallyScaffoldedFiles(_view) {
|
|
943
|
+
return [];
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
702
946
|
* User config files this patcher merges into via an `edit` op (e.g.
|
|
703
947
|
* `vite.config.ts`). `eject` can't reverse an in-place merge, so it lists
|
|
704
948
|
* these as manual cleanup steps. Defaults to none; patchers that edit a config
|
|
@@ -755,10 +999,10 @@ var AbstractRulePatcher = class {
|
|
|
755
999
|
mode: 384,
|
|
756
1000
|
contents: `${stableStringify({
|
|
757
1001
|
project_id: ctx.project.id,
|
|
758
|
-
project_secret: ctx.project.
|
|
759
|
-
preview_secret: ctx.project.
|
|
760
|
-
preview_origins: ctx.project.
|
|
761
|
-
created_at: ctx.project.
|
|
1002
|
+
project_secret: ctx.project.project_secret,
|
|
1003
|
+
preview_secret: ctx.project.preview_secret,
|
|
1004
|
+
preview_origins: ctx.project.preview_origins,
|
|
1005
|
+
created_at: ctx.project.created_at
|
|
762
1006
|
})}\n`
|
|
763
1007
|
},
|
|
764
1008
|
{
|
|
@@ -782,7 +1026,7 @@ var AbstractRulePatcher = class {
|
|
|
782
1026
|
path: ".env.local",
|
|
783
1027
|
entries: {
|
|
784
1028
|
ZITADEL_PROJECT_ID: ctx.project.id,
|
|
785
|
-
ZITADEL_PROJECT_SECRET: ctx.project.
|
|
1029
|
+
ZITADEL_PROJECT_SECRET: ctx.project.project_secret,
|
|
786
1030
|
ZITADEL_ENVIRONMENT: "development",
|
|
787
1031
|
ZITADEL_ISSUER: ctx.issuer,
|
|
788
1032
|
ZITADEL_URL: ctx.server
|
|
@@ -818,10 +1062,18 @@ var AbstractRulePatcher = class {
|
|
|
818
1062
|
];
|
|
819
1063
|
}
|
|
820
1064
|
};
|
|
1065
|
+
/** Reads a text file, mapping every failure to `undefined`. */
|
|
1066
|
+
async function readTextIfExists(path) {
|
|
1067
|
+
try {
|
|
1068
|
+
return await readFile(path, "utf8");
|
|
1069
|
+
} catch {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
821
1073
|
/** Builds the `zitadel.json` body persisted at the project root. */
|
|
822
1074
|
function projectConfig(ctx) {
|
|
823
1075
|
const environments = { development: { issuer: ctx.issuer } };
|
|
824
|
-
if (ctx.project.
|
|
1076
|
+
if (ctx.project.preview_origins.length > 0) environments.preview = { issuer_pattern: [...ctx.project.preview_origins] };
|
|
825
1077
|
return {
|
|
826
1078
|
$schema: "https://schemas.zitadel.com/v2/project.schema.json",
|
|
827
1079
|
project: ctx.project.id,
|
|
@@ -832,7 +1084,8 @@ function projectConfig(ctx) {
|
|
|
832
1084
|
attribution: "visible"
|
|
833
1085
|
},
|
|
834
1086
|
environments,
|
|
835
|
-
...ctx.preset === void 0 ? {} : { preset: ctx.preset }
|
|
1087
|
+
...ctx.preset === void 0 ? {} : { preset: ctx.preset },
|
|
1088
|
+
...ctx.useCase === void 0 ? {} : { useCase: ctx.useCase }
|
|
836
1089
|
};
|
|
837
1090
|
}
|
|
838
1091
|
/** Normalizes a server URL to its origin, falling back to {@link DEFAULT_SERVER}. */
|
|
@@ -1030,22 +1283,72 @@ function angularRoutesEdit() {
|
|
|
1030
1283
|
*/
|
|
1031
1284
|
const PROXY_PATH = "/__nextgen";
|
|
1032
1285
|
//#endregion
|
|
1286
|
+
//#region src/lib/orca/patchers/rule/proxy-credential-policy.ts
|
|
1287
|
+
/** Marker emitted after the exchange-only credential policy was introduced. */
|
|
1288
|
+
const PROXY_CREDENTIAL_POLICY_MARKER = "zitadel:proxy:v2";
|
|
1289
|
+
/**
|
|
1290
|
+
* Rejects a browser-reachable proxy that still appears to attach the project
|
|
1291
|
+
* secret without the complete exchange-only guard. Known generated legacy
|
|
1292
|
+
* bytes are migrated before this runs; anything else stays user-owned and is
|
|
1293
|
+
* surfaced for manual review instead of being silently counted as healthy by
|
|
1294
|
+
* `zitadel doctor`.
|
|
1295
|
+
*/
|
|
1296
|
+
function assertNoUnreviewedProjectSecretProxy(source, label) {
|
|
1297
|
+
if (!source.includes("/__nextgen") || !source.includes("ZITADEL_PROJECT_SECRET") || !/authorization/i.test(source) || hasExchangeOnlyGuard(source)) return;
|
|
1298
|
+
throw new ZitadelError("E_VALIDATION", `${label} may forward ZITADEL_PROJECT_SECRET outside POST /sessions/exchange`, { hint: "Review the proxy manually: restrict the bearer to the exact POST /sessions/exchange pathname and preserve caller-provided authorization." });
|
|
1299
|
+
}
|
|
1300
|
+
function hasExchangeOnlyGuard(source) {
|
|
1301
|
+
const setterOffsets = [...source.matchAll(/proxyReq\s*\.\s*setHeader\s*\(\s*["']authorization["']\s*,/gi)].map((match) => match.index);
|
|
1302
|
+
if (setterOffsets.length === 0) return false;
|
|
1303
|
+
const safeBodyRanges = [];
|
|
1304
|
+
for (const match of source.matchAll(/if\s*\(([\s\S]*?)\)\s*\{([\s\S]*?)\}/g)) {
|
|
1305
|
+
const condition = match[1];
|
|
1306
|
+
const body = match[2];
|
|
1307
|
+
if (!/proxyReq\s*\.\s*method\s*===\s*["']POST["']/i.test(condition) || !/pathname\s*===\s*["']\/sessions\/exchange["']/i.test(condition) || !/!\s*proxyReq\s*\.\s*getHeader\s*\(\s*["']authorization["']\s*\)/i.test(condition) || !/proxyReq\s*\.\s*setHeader\s*\(\s*["']authorization["']\s*,/i.test(body)) continue;
|
|
1308
|
+
const bodyOffset = match[0].lastIndexOf(body);
|
|
1309
|
+
const start = match.index + bodyOffset;
|
|
1310
|
+
safeBodyRanges.push({
|
|
1311
|
+
start,
|
|
1312
|
+
end: start + body.length
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return setterOffsets.every((offset) => safeBodyRanges.some(({ start, end }) => offset >= start && offset < end));
|
|
1316
|
+
}
|
|
1317
|
+
//#endregion
|
|
1033
1318
|
//#region src/lib/orca/patchers/rule/angular/templates.ts
|
|
1034
1319
|
/**
|
|
1035
1320
|
* The managed root component `src/app/app.ts`: a standalone component that
|
|
1036
1321
|
* renders the `@zitadel/sdk-angular` widgets based on the current path. The
|
|
1037
1322
|
* project id (public, not secret) is inlined; the dev proxy in `proxy.conf.cjs`
|
|
1038
|
-
* attaches the project service-key secret as the bearer server-side
|
|
1039
|
-
* `.env.local`), and no secret reaches
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1323
|
+
* attaches the project service-key secret as the bearer server-side only to
|
|
1324
|
+
* `POST /sessions/exchange` (read from `.env.local`), and no secret reaches
|
|
1325
|
+
* the browser.
|
|
1326
|
+
*
|
|
1327
|
+
* Projects set up with the business use case additionally expose the SDK's
|
|
1328
|
+
* `businessLocales` overlay as a class property, which `app.html` binds to the
|
|
1329
|
+
* login widgets via `[locales]` — restoring work-email copy on top of the
|
|
1330
|
+
* widget's neutral built-in dictionaries.
|
|
1331
|
+
*/
|
|
1332
|
+
function appComponentTemplate(ctx) {
|
|
1333
|
+
const business = ctx.useCase === "business";
|
|
1334
|
+
const importNames = business ? `
|
|
1045
1335
|
ZitadelLoginComponent,
|
|
1046
1336
|
ZitadelSessionComponent,
|
|
1337
|
+
businessLocales,
|
|
1047
1338
|
configureZitadel,
|
|
1048
|
-
|
|
1339
|
+
` : `
|
|
1340
|
+
ZitadelLoginComponent,
|
|
1341
|
+
ZitadelSessionComponent,
|
|
1342
|
+
configureZitadel,
|
|
1343
|
+
`;
|
|
1344
|
+
const localesProperty = business ? `
|
|
1345
|
+
// Set up for a business audience: businessLocales overlays work-email copy
|
|
1346
|
+
// on the login widget's neutral built-in dictionaries. Remove it (and the
|
|
1347
|
+
// [locales] binding in app.html) to fall back to the neutral wording.
|
|
1348
|
+
protected readonly locales = businessLocales;` : "";
|
|
1349
|
+
return `${MANAGED_MARKER}
|
|
1350
|
+
import { Component, OnInit } from "@angular/core";
|
|
1351
|
+
import {${importNames}} from "@zitadel/sdk-angular";
|
|
1049
1352
|
|
|
1050
1353
|
@Component({
|
|
1051
1354
|
selector: "app-root",
|
|
@@ -1055,10 +1358,10 @@ import {
|
|
|
1055
1358
|
})
|
|
1056
1359
|
export class App implements OnInit {
|
|
1057
1360
|
protected readonly project = configureZitadel({
|
|
1058
|
-
projectId: ${JSON.stringify(
|
|
1361
|
+
projectId: ${JSON.stringify(ctx.project.id)},
|
|
1059
1362
|
proxyPath: "${PROXY_PATH}",
|
|
1060
1363
|
});
|
|
1061
|
-
protected readonly path = window.location.pathname
|
|
1364
|
+
protected readonly path = window.location.pathname;${localesProperty}
|
|
1062
1365
|
|
|
1063
1366
|
ngOnInit(): void {
|
|
1064
1367
|
if (this.path === "/") {
|
|
@@ -1071,8 +1374,11 @@ export class App implements OnInit {
|
|
|
1071
1374
|
/**
|
|
1072
1375
|
* The managed `src/app/app.html`. The marker lives in an HTML comment that still
|
|
1073
1376
|
* contains the literal managed-marker text, so eject/doctor stay marker-aware.
|
|
1377
|
+
* Business-use-case scaffolds bind the component's `locales` overlay property
|
|
1378
|
+
* onto the login widgets.
|
|
1074
1379
|
*/
|
|
1075
|
-
function appTemplateHtml() {
|
|
1380
|
+
function appTemplateHtml(ctx) {
|
|
1381
|
+
const localesBinding = ctx.useCase === "business" ? `\n [locales]="locales"` : "";
|
|
1076
1382
|
return `<!-- ${MANAGED_MARKER} -->
|
|
1077
1383
|
@if (path.startsWith('/profile')) {
|
|
1078
1384
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
@@ -1084,7 +1390,7 @@ function appTemplateHtml() {
|
|
|
1084
1390
|
} @else if (path.startsWith('/register')) {
|
|
1085
1391
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
1086
1392
|
<zitadel-auth-login
|
|
1087
|
-
[project]="project"
|
|
1393
|
+
[project]="project"${localesBinding}
|
|
1088
1394
|
purpose="register"
|
|
1089
1395
|
[postSignInUrl]="'/profile'"
|
|
1090
1396
|
></zitadel-auth-login>
|
|
@@ -1092,7 +1398,7 @@ function appTemplateHtml() {
|
|
|
1092
1398
|
} @else {
|
|
1093
1399
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
1094
1400
|
<zitadel-auth-login
|
|
1095
|
-
[project]="project"
|
|
1401
|
+
[project]="project"${localesBinding}
|
|
1096
1402
|
purpose="login"
|
|
1097
1403
|
[postSignInUrl]="'/profile'"
|
|
1098
1404
|
></zitadel-auth-login>
|
|
@@ -1102,8 +1408,9 @@ function appTemplateHtml() {
|
|
|
1102
1408
|
}
|
|
1103
1409
|
/**
|
|
1104
1410
|
* The managed `proxy.conf.cjs` for `ng serve`: forwards `/__nextgen/*` to the
|
|
1105
|
-
* backend and attaches the project's service-key secret
|
|
1106
|
-
*
|
|
1411
|
+
* backend and attaches the project's service-key secret only to the current
|
|
1412
|
+
* app-plane `POST /sessions/exchange` request. Both the backend URL
|
|
1413
|
+
* (`ZITADEL_URL`) and the secret
|
|
1107
1414
|
* (`ZITADEL_PROJECT_SECRET`) are read from `.env.local`, which `zitadel setup`
|
|
1108
1415
|
* writes and `.gitignore` excludes — Angular's CLI does not auto-load env files
|
|
1109
1416
|
* into the dev-server process, so this file does it itself with a small inline
|
|
@@ -1141,9 +1448,7 @@ if (!secret) {
|
|
|
1141
1448
|
}
|
|
1142
1449
|
const bearer = \`Bearer \${secret}\`;
|
|
1143
1450
|
|
|
1144
|
-
|
|
1145
|
-
proxyReq.setHeader("authorization", bearer);
|
|
1146
|
-
}
|
|
1451
|
+
${EXCHANGE_ONLY_BEARER}
|
|
1147
1452
|
|
|
1148
1453
|
function stripPrefix(path) {
|
|
1149
1454
|
return path.replace(/^\\${PROXY_PATH}/, "").replace(/^(?!\\/)/, "/");
|
|
@@ -1161,6 +1466,34 @@ module.exports = {
|
|
|
1161
1466
|
};
|
|
1162
1467
|
`;
|
|
1163
1468
|
}
|
|
1469
|
+
const LEGACY_UNCONDITIONAL_BEARER = `function setBearer(proxyReq) {
|
|
1470
|
+
proxyReq.setHeader("authorization", bearer);
|
|
1471
|
+
}`;
|
|
1472
|
+
const EXCHANGE_ONLY_BEARER = `/* ${PROXY_CREDENTIAL_POLICY_MARKER} */
|
|
1473
|
+
function setBearer(proxyReq) {
|
|
1474
|
+
const pathname = new URL(proxyReq.path, "http://zitadel.local").pathname;
|
|
1475
|
+
if (
|
|
1476
|
+
proxyReq.method === "POST" &&
|
|
1477
|
+
pathname === "/sessions/exchange" &&
|
|
1478
|
+
!proxyReq.getHeader("authorization")
|
|
1479
|
+
) {
|
|
1480
|
+
proxyReq.setHeader("authorization", bearer);
|
|
1481
|
+
}
|
|
1482
|
+
}`;
|
|
1483
|
+
/**
|
|
1484
|
+
* Creates the managed Angular proxy and upgrades the exact insecure hook the
|
|
1485
|
+
* CLI emitted before the exchange-only policy. Other edits are preserved; a
|
|
1486
|
+
* safe custom proxy is left untouched, while an unrecognized proxy that may
|
|
1487
|
+
* still over-forward the project secret is surfaced for manual review.
|
|
1488
|
+
*/
|
|
1489
|
+
function proxyConfEdit() {
|
|
1490
|
+
return (source) => {
|
|
1491
|
+
if (source === void 0) return proxyConfTemplate();
|
|
1492
|
+
if (source.includes("// zitadel-cli: managed-file v1") && source.includes(LEGACY_UNCONDITIONAL_BEARER)) return source.replace(LEGACY_UNCONDITIONAL_BEARER, EXCHANGE_ONLY_BEARER);
|
|
1493
|
+
assertNoUnreviewedProjectSecretProxy(source, "proxy.conf.cjs");
|
|
1494
|
+
return source;
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1164
1497
|
//#endregion
|
|
1165
1498
|
//#region src/lib/orca/patchers/rule/angular/index.ts
|
|
1166
1499
|
const SDK_DEPENDENCY$6 = "@zitadel/sdk-angular";
|
|
@@ -1176,23 +1509,24 @@ function ensureDevScript(source) {
|
|
|
1176
1509
|
const pkg = parseJsonObject(source, "package.json");
|
|
1177
1510
|
const scripts = isObject(pkg.scripts) ? pkg.scripts : void 0;
|
|
1178
1511
|
if (scripts?.dev !== void 0) return source;
|
|
1179
|
-
|
|
1512
|
+
return setTopLevelJsonKey(source, "package.json", "scripts", {
|
|
1180
1513
|
...scripts ?? {},
|
|
1181
1514
|
dev: "ng serve"
|
|
1182
|
-
};
|
|
1183
|
-
return `${stableStringify(pkg)}\n`;
|
|
1515
|
+
});
|
|
1184
1516
|
}
|
|
1185
1517
|
/**
|
|
1186
1518
|
* Rule-based patcher for an Angular app. Inherits the shared `.zitadel/` base
|
|
1187
1519
|
* files from {@link AbstractRulePatcher} and contributes the managed root
|
|
1188
1520
|
* component (`app.ts`/`app.html`) that renders the `@zitadel/sdk-angular`
|
|
1189
1521
|
* widgets, a `proxy.conf.cjs` dev proxy (attaching the project secret from
|
|
1190
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
1522
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`) wired into `angular.json`,
|
|
1191
1523
|
* and the SDK dep.
|
|
1192
1524
|
*
|
|
1193
1525
|
* Unlike React/Vue (whose dev proxy lives in `vite.config.ts`), Angular owns its
|
|
1194
1526
|
* Vite config, so the proxy is a separate `proxy.conf.cjs` referenced from the
|
|
1195
|
-
* `serve` target.
|
|
1527
|
+
* `serve` target. In production the same-origin path comes from a platform
|
|
1528
|
+
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
|
|
1529
|
+
* issue #560.
|
|
1196
1530
|
*/
|
|
1197
1531
|
var AngularPatcher = class extends AbstractRulePatcher {
|
|
1198
1532
|
canPatch(framework) {
|
|
@@ -1203,22 +1537,24 @@ var AngularPatcher = class extends AbstractRulePatcher {
|
|
|
1203
1537
|
{
|
|
1204
1538
|
kind: "write",
|
|
1205
1539
|
path: "src/app/app.ts",
|
|
1206
|
-
contents: appComponentTemplate(ctx
|
|
1540
|
+
contents: appComponentTemplate(ctx)
|
|
1207
1541
|
},
|
|
1208
1542
|
{
|
|
1209
1543
|
kind: "write",
|
|
1210
1544
|
path: "src/app/app.html",
|
|
1211
|
-
contents: appTemplateHtml()
|
|
1545
|
+
contents: appTemplateHtml(ctx)
|
|
1212
1546
|
},
|
|
1213
1547
|
{
|
|
1214
1548
|
kind: "edit",
|
|
1215
1549
|
path: "src/app/app.routes.ts",
|
|
1216
|
-
edit: angularRoutesEdit()
|
|
1550
|
+
edit: angularRoutesEdit(),
|
|
1551
|
+
wiring: "infrastructure"
|
|
1217
1552
|
},
|
|
1218
1553
|
{
|
|
1219
|
-
kind: "
|
|
1554
|
+
kind: "edit",
|
|
1220
1555
|
path: "proxy.conf.cjs",
|
|
1221
|
-
|
|
1556
|
+
edit: proxyConfEdit(),
|
|
1557
|
+
wiring: "infrastructure"
|
|
1222
1558
|
},
|
|
1223
1559
|
{
|
|
1224
1560
|
kind: "edit",
|
|
@@ -1226,12 +1562,14 @@ var AngularPatcher = class extends AbstractRulePatcher {
|
|
|
1226
1562
|
edit: angularProxyEdit({
|
|
1227
1563
|
proxyConfig: "proxy.conf.cjs",
|
|
1228
1564
|
port: ctx.framework.devPort
|
|
1229
|
-
})
|
|
1565
|
+
}),
|
|
1566
|
+
wiring: "infrastructure"
|
|
1230
1567
|
},
|
|
1231
1568
|
{
|
|
1232
1569
|
kind: "edit",
|
|
1233
1570
|
path: "package.json",
|
|
1234
|
-
edit: ensureDevScript
|
|
1571
|
+
edit: ensureDevScript,
|
|
1572
|
+
wiring: "convenience"
|
|
1235
1573
|
},
|
|
1236
1574
|
{
|
|
1237
1575
|
kind: "add-dep",
|
|
@@ -1247,6 +1585,9 @@ var AngularPatcher = class extends AbstractRulePatcher {
|
|
|
1247
1585
|
"proxy.conf.cjs"
|
|
1248
1586
|
];
|
|
1249
1587
|
}
|
|
1588
|
+
infrastructureFiles(_view) {
|
|
1589
|
+
return ["proxy.conf.cjs"];
|
|
1590
|
+
}
|
|
1250
1591
|
routeDeps(_view) {
|
|
1251
1592
|
return [SDK_DEPENDENCY$6];
|
|
1252
1593
|
}
|
|
@@ -1341,6 +1682,15 @@ export default function ${mode === "login" ? "LoginPage" : "RegisterPage"}() {
|
|
|
1341
1682
|
* forwards it to `ZITADEL_URL` server-side. `NEXT_PUBLIC_ZITADEL_PROJECT_ID` is
|
|
1342
1683
|
* public — the project id is not sensitive and the widget needs it to start a
|
|
1343
1684
|
* flow.
|
|
1685
|
+
*
|
|
1686
|
+
* Projects set up with the business use case additionally wire the SDK's
|
|
1687
|
+
* `businessLocales` overlay onto the auth pages, restoring work-email copy on
|
|
1688
|
+
* top of the widget's neutral built-in dictionaries. The overlay is assigned
|
|
1689
|
+
* via a ref callback, not a JSX prop: sdk-next supports React >=18, and only
|
|
1690
|
+
* React 19 binds non-primitive custom-element props as properties — on 18 a
|
|
1691
|
+
* `locales={...}` prop would decay to a useless attribute and silently keep
|
|
1692
|
+
* the neutral copy (the `project` prop tolerates this because
|
|
1693
|
+
* `configureZitadel()` registers a global fallback; `locales` has none).
|
|
1344
1694
|
*/
|
|
1345
1695
|
const reactRenderer = {
|
|
1346
1696
|
id: "react",
|
|
@@ -1352,9 +1702,24 @@ const reactRenderer = {
|
|
|
1352
1702
|
version: "latest"
|
|
1353
1703
|
},
|
|
1354
1704
|
templates: {
|
|
1355
|
-
authPage(mode) {
|
|
1705
|
+
authPage(mode, context) {
|
|
1356
1706
|
const componentName = mode === "login" ? "LoginPage" : "RegisterPage";
|
|
1357
1707
|
const elementName = mode === "login" ? "ZitadelLogin" : "ZitadelRegister";
|
|
1708
|
+
const business = context.useCase === "business";
|
|
1709
|
+
const importNames = business ? "businessLocales, configureZitadel" : "configureZitadel";
|
|
1710
|
+
const localesComment = business ? `
|
|
1711
|
+
// Set up for a business audience: businessLocales overlays work-email
|
|
1712
|
+
// copy on the widget's neutral built-in dictionaries. It is assigned
|
|
1713
|
+
// through the ref because React binds non-primitive custom-element
|
|
1714
|
+
// props as properties only from v19 — the ref works on React 18 too.
|
|
1715
|
+
// Remove the assignment to fall back to the neutral wording.` : "";
|
|
1716
|
+
const localesAttr = business ? `
|
|
1717
|
+
ref={(element) => {
|
|
1718
|
+
if (element) {
|
|
1719
|
+
element.locales = businessLocales;
|
|
1720
|
+
}
|
|
1721
|
+
}}` : "";
|
|
1722
|
+
const widget = context.posture === "widget";
|
|
1358
1723
|
return {
|
|
1359
1724
|
mode,
|
|
1360
1725
|
contents: `${MANAGED_MARKER}
|
|
@@ -1364,7 +1729,7 @@ import dynamic from "next/dynamic";
|
|
|
1364
1729
|
|
|
1365
1730
|
const ${elementName} = dynamic(
|
|
1366
1731
|
async () => {
|
|
1367
|
-
const {
|
|
1732
|
+
const { ${importNames} } = await import("@zitadel/sdk-next/client");
|
|
1368
1733
|
// Build the SDK project handle and pass it to the component via the
|
|
1369
1734
|
// \`project\` prop. The component reads config from this prop directly, so
|
|
1370
1735
|
// it works regardless of how the SDK packages are bundled. The backend URL
|
|
@@ -1374,9 +1739,10 @@ const ${elementName} = dynamic(
|
|
|
1374
1739
|
projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
|
|
1375
1740
|
proxyPath: "/__nextgen",
|
|
1376
1741
|
});
|
|
1377
|
-
return function ${elementName}Element() {
|
|
1742
|
+
return function ${elementName}Element() {${localesComment}
|
|
1378
1743
|
return (
|
|
1379
1744
|
<zitadel-login
|
|
1745
|
+
${widget ? `variant="widget"\n theme="auto"` : `variant="page"`}${localesAttr}
|
|
1380
1746
|
project={project}
|
|
1381
1747
|
purpose="${mode}"
|
|
1382
1748
|
post-sign-in-url="/profile"
|
|
@@ -1388,16 +1754,24 @@ const ${elementName} = dynamic(
|
|
|
1388
1754
|
);
|
|
1389
1755
|
|
|
1390
1756
|
export default function ${componentName}() {
|
|
1757
|
+
${widget ? ` // variant="widget" embeds the sign-in card in this app's existing layout,
|
|
1758
|
+
// and theme="auto" follows its color scheme; the wrapper below only centers
|
|
1759
|
+
// the card — restyle or replace it freely. Switch to variant="page" for the
|
|
1760
|
+
// widget's own full-page chrome (viewport height, surface background from
|
|
1761
|
+
// design tokens).` : ` // variant="page" makes the widget paint the full-page chrome itself
|
|
1762
|
+
// (viewport height, surface background) from design tokens; switch to
|
|
1763
|
+
// variant="widget" to embed the sign-in card inside a layout you own.`}
|
|
1391
1764
|
return (
|
|
1392
|
-
|
|
1765
|
+
${widget ? `<div style={{ display: "flex", justifyContent: "center", padding: "4rem 1rem" }}>` : `<main style={{ colorScheme: "dark" }}>`}
|
|
1393
1766
|
<${elementName} />
|
|
1394
|
-
|
|
1767
|
+
${widget ? `</div>` : `</main>`}
|
|
1395
1768
|
);
|
|
1396
1769
|
}
|
|
1397
1770
|
`
|
|
1398
1771
|
};
|
|
1399
1772
|
},
|
|
1400
|
-
profilePage() {
|
|
1773
|
+
profilePage(context) {
|
|
1774
|
+
const widget = context.posture === "widget";
|
|
1401
1775
|
return { contents: `${MANAGED_MARKER}
|
|
1402
1776
|
"use client";
|
|
1403
1777
|
|
|
@@ -1408,7 +1782,9 @@ const ZitadelSession = dynamic(
|
|
|
1408
1782
|
const { configureZitadel } = await import("@zitadel/sdk-next/client");
|
|
1409
1783
|
// Build the SDK project handle and pass it to the session card via the
|
|
1410
1784
|
// \`project\` prop. The card reads identity from "/__nextgen/sessions/me"
|
|
1411
|
-
// and exposes a Sign out action.
|
|
1785
|
+
// and exposes a Sign out action. Your own components (a header, an
|
|
1786
|
+
// account menu) can make the same read with getSession() from
|
|
1787
|
+
// "@zitadel/sdk-next/session" to swap sign-in CTAs for account chrome.
|
|
1412
1788
|
const project = configureZitadel({
|
|
1413
1789
|
projectId: process.env.NEXT_PUBLIC_ZITADEL_PROJECT_ID ?? "",
|
|
1414
1790
|
proxyPath: "/__nextgen",
|
|
@@ -1416,6 +1792,7 @@ const ZitadelSession = dynamic(
|
|
|
1416
1792
|
return function ZitadelSessionElement() {
|
|
1417
1793
|
return (
|
|
1418
1794
|
<zitadel-session
|
|
1795
|
+
${widget ? `variant="widget"\n theme="auto"` : `variant="page"`}
|
|
1419
1796
|
project={project}
|
|
1420
1797
|
post-sign-out-url="/login"
|
|
1421
1798
|
/>
|
|
@@ -1426,42 +1803,27 @@ const ZitadelSession = dynamic(
|
|
|
1426
1803
|
);
|
|
1427
1804
|
|
|
1428
1805
|
export default function ProfilePage() {
|
|
1806
|
+
${widget ? ` // variant="widget" embeds the session card in this app's existing layout,
|
|
1807
|
+
// and theme="auto" follows its color scheme; the wrapper below only centers
|
|
1808
|
+
// the card — restyle or replace it freely. Switch to variant="page" for the
|
|
1809
|
+
// card's own full-page chrome (viewport height, surface background from
|
|
1810
|
+
// design tokens).` : ` // variant="page" makes the session card paint the full-page chrome itself
|
|
1811
|
+
// (viewport height, surface background) from design tokens; switch to
|
|
1812
|
+
// variant="widget" to embed the card inside a layout you own.`}
|
|
1429
1813
|
return (
|
|
1430
|
-
|
|
1814
|
+
${widget ? `<div style={{ display: "flex", justifyContent: "center", padding: "4rem 1rem" }}>` : `<main style={{ colorScheme: "dark" }}>`}
|
|
1431
1815
|
<ZitadelSession />
|
|
1432
|
-
|
|
1816
|
+
${widget ? `</div>` : `</main>`}
|
|
1433
1817
|
);
|
|
1434
1818
|
}
|
|
1435
1819
|
` };
|
|
1436
1820
|
},
|
|
1437
1821
|
customElementsDts() {
|
|
1438
1822
|
return { contents: `${MANAGED_MARKER}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
namespace JSX {
|
|
1444
|
-
interface IntrinsicElements {
|
|
1445
|
-
"zitadel-login": React.HTMLAttributes<HTMLElement> & {
|
|
1446
|
-
project?: ZitadelProject;
|
|
1447
|
-
"session-exchange-path"?: string;
|
|
1448
|
-
"post-sign-in-url"?: string;
|
|
1449
|
-
purpose?: string;
|
|
1450
|
-
"flow-name"?: string;
|
|
1451
|
-
};
|
|
1452
|
-
"zitadel-logout": React.HTMLAttributes<HTMLElement> & {
|
|
1453
|
-
project?: ZitadelProject;
|
|
1454
|
-
"post-sign-out-url"?: string;
|
|
1455
|
-
};
|
|
1456
|
-
"zitadel-session": React.HTMLAttributes<HTMLElement> & {
|
|
1457
|
-
project?: ZitadelProject;
|
|
1458
|
-
"post-sign-out-url"?: string;
|
|
1459
|
-
heading?: string;
|
|
1460
|
-
"logout-label"?: string;
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1823
|
+
// React JSX declarations for the <zitadel-*> custom elements ship with the
|
|
1824
|
+
// SDK, so the scaffold stays aligned with the real element surface instead
|
|
1825
|
+
// of carrying a hand-maintained copy that drifts.
|
|
1826
|
+
/// <reference types="@zitadel/sdk-next/jsx" />
|
|
1465
1827
|
` };
|
|
1466
1828
|
}
|
|
1467
1829
|
}
|
|
@@ -1491,6 +1853,15 @@ const RENDERERS = {
|
|
|
1491
1853
|
"web-component": litRenderer
|
|
1492
1854
|
};
|
|
1493
1855
|
/**
|
|
1856
|
+
* The subset of {@link RENDERER_IDS} that {@link getRenderer} resolves rather
|
|
1857
|
+
* than rejects (`status: "available"`). Anything advertising renderer choices
|
|
1858
|
+
* — flag options, error hints — must derive from this list, not
|
|
1859
|
+
* {@link RENDERER_IDS}: a declared-but-unpublished renderer (ADR 006) keeps
|
|
1860
|
+
* its registry entry to reserve the id, but must never be offered as a usable
|
|
1861
|
+
* value.
|
|
1862
|
+
*/
|
|
1863
|
+
const AVAILABLE_RENDERER_IDS = RENDERER_IDS.filter((id) => RENDERERS[id].status === "available");
|
|
1864
|
+
/**
|
|
1494
1865
|
* Resolves a renderer id (an untrusted string from config) to its spec,
|
|
1495
1866
|
* throwing a typed {@link ZitadelError} rather than returning `undefined`
|
|
1496
1867
|
* so callers get an actionable message. Rejects ids that are unknown
|
|
@@ -1498,7 +1869,7 @@ const RENDERERS = {
|
|
|
1498
1869
|
* guaranteeing the returned spec is safe to scaffold from.
|
|
1499
1870
|
*/
|
|
1500
1871
|
function getRenderer(id) {
|
|
1501
|
-
if (!isRendererId(id)) throw new ZitadelError("E_VALIDATION", `Unknown renderer "${id}"`, { hint: `Available renderers: ${
|
|
1872
|
+
if (!isRendererId(id)) throw new ZitadelError("E_VALIDATION", `Unknown renderer "${id}"`, { hint: `Available renderers: ${AVAILABLE_RENDERER_IDS.join(", ")}` });
|
|
1502
1873
|
const renderer = RENDERERS[id];
|
|
1503
1874
|
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." });
|
|
1504
1875
|
return renderer;
|
|
@@ -1546,6 +1917,16 @@ var NextPatcher = class extends AbstractRulePatcher {
|
|
|
1546
1917
|
routeFiles(view) {
|
|
1547
1918
|
return nextCodeFilePaths(view.framework, getRenderer(view.rendererId));
|
|
1548
1919
|
}
|
|
1920
|
+
infrastructureFiles(view) {
|
|
1921
|
+
return nextInfrastructureFilePaths(view.framework, getRenderer(view.rendererId));
|
|
1922
|
+
}
|
|
1923
|
+
conditionallyScaffoldedFiles(view) {
|
|
1924
|
+
return [join(view.framework.appDir, "page.tsx")];
|
|
1925
|
+
}
|
|
1926
|
+
retiredAlternateFiles(view) {
|
|
1927
|
+
if (requestBoundaryFile(view.framework).filename !== "proxy.ts") return {};
|
|
1928
|
+
return { [join(view.framework.appDir, "../proxy.ts")]: [join(view.framework.appDir, "../middleware.ts")] };
|
|
1929
|
+
}
|
|
1549
1930
|
routeDeps(view) {
|
|
1550
1931
|
return [getRenderer(view.rendererId).dependency.name];
|
|
1551
1932
|
}
|
|
@@ -1575,10 +1956,28 @@ function nextCodeFilePaths(framework, renderer) {
|
|
|
1575
1956
|
if (renderer.templates.customElementsDts) paths.push(join(appDir, "../custom-elements.d.ts"));
|
|
1576
1957
|
return paths;
|
|
1577
1958
|
}
|
|
1959
|
+
/**
|
|
1960
|
+
* The subset of {@link nextCodeFilePaths} that is load-bearing for the auth
|
|
1961
|
+
* integration rather than a presentation starting point: the request boundary
|
|
1962
|
+
* (proxy/middleware), the provider file, and the custom-elements declarations.
|
|
1963
|
+
* Shares path construction with {@link nextCodeFilePaths} so the two cannot
|
|
1964
|
+
* drift; the `doctor` managed-files check fails when one of these is missing.
|
|
1965
|
+
*/
|
|
1966
|
+
function nextInfrastructureFilePaths(framework, renderer) {
|
|
1967
|
+
const appDir = framework.appDir;
|
|
1968
|
+
const paths = [join(appDir, `../${requestBoundaryFile(framework).filename}`)];
|
|
1969
|
+
if (renderer.templates.provider) paths.push(join(appDir, renderer.templates.provider.filename));
|
|
1970
|
+
if (renderer.templates.customElementsDts) paths.push(join(appDir, "../custom-elements.d.ts"));
|
|
1971
|
+
return paths;
|
|
1972
|
+
}
|
|
1578
1973
|
/** The Next route/request-boundary write ops plus the SDK dependency. */
|
|
1579
1974
|
function nextCodeOps(ctx, renderer) {
|
|
1580
1975
|
const appDir = ctx.framework.appDir;
|
|
1581
|
-
const
|
|
1976
|
+
const pageContext = {
|
|
1977
|
+
useCase: ctx.useCase,
|
|
1978
|
+
posture: ctx.posture
|
|
1979
|
+
};
|
|
1980
|
+
const profile = renderer.templates.profilePage?.(pageContext);
|
|
1582
1981
|
const provider = renderer.templates.provider;
|
|
1583
1982
|
const dts = renderer.templates.customElementsDts?.();
|
|
1584
1983
|
const boundary = requestBoundaryFile(ctx.framework);
|
|
@@ -1586,17 +1985,18 @@ function nextCodeOps(ctx, renderer) {
|
|
|
1586
1985
|
ctx.scaffoldedFramework ? {
|
|
1587
1986
|
kind: "edit",
|
|
1588
1987
|
path: join(appDir, "page.tsx"),
|
|
1589
|
-
edit: () => homePageTemplate()
|
|
1988
|
+
edit: () => homePageTemplate(),
|
|
1989
|
+
overwrites: true
|
|
1590
1990
|
} : void 0,
|
|
1591
1991
|
{
|
|
1592
1992
|
kind: "write",
|
|
1593
1993
|
path: join(appDir, "login/page.tsx"),
|
|
1594
|
-
contents: renderer.templates.authPage("login").contents
|
|
1994
|
+
contents: renderer.templates.authPage("login", pageContext).contents
|
|
1595
1995
|
},
|
|
1596
1996
|
{
|
|
1597
1997
|
kind: "write",
|
|
1598
1998
|
path: join(appDir, "register/page.tsx"),
|
|
1599
|
-
contents: renderer.templates.authPage("register").contents
|
|
1999
|
+
contents: renderer.templates.authPage("register", pageContext).contents
|
|
1600
2000
|
},
|
|
1601
2001
|
profile ? {
|
|
1602
2002
|
kind: "write",
|
|
@@ -1755,7 +2155,6 @@ function nuxtConfigEdit(opts) {
|
|
|
1755
2155
|
}
|
|
1756
2156
|
//#endregion
|
|
1757
2157
|
//#region src/lib/orca/patchers/rule/nuxt/templates.ts
|
|
1758
|
-
const MAIN_STYLE = "min-height: 100vh; background: #0f0f11; color: #f4f4f6; color-scheme: dark";
|
|
1759
2158
|
/** `app.vue` — renders the page router. Marker in an HTML comment. */
|
|
1760
2159
|
function appVueTemplate() {
|
|
1761
2160
|
return `<!-- ${MANAGED_MARKER} -->
|
|
@@ -1784,35 +2183,68 @@ await navigateTo("/login", { replace: true });
|
|
|
1784
2183
|
<\/script>
|
|
1785
2184
|
`;
|
|
1786
2185
|
}
|
|
1787
|
-
/**
|
|
1788
|
-
|
|
2186
|
+
/**
|
|
2187
|
+
* A login/register page rendering `<zitadel-login>` inside `<ClientOnly>`.
|
|
2188
|
+
* Business-use-case projects additionally bind the SDK's `businessLocales`
|
|
2189
|
+
* overlay, restoring work-email copy on top of the widget's neutral built-in
|
|
2190
|
+
* dictionaries. A plain `:locales` binding suffices even on the raw custom
|
|
2191
|
+
* element: Vue sets bindings whose key exists on the element as DOM
|
|
2192
|
+
* properties (unlike React 18, which is why the Next template needs a ref).
|
|
2193
|
+
*/
|
|
2194
|
+
function authPage(purpose, ctx) {
|
|
2195
|
+
const business = ctx.useCase === "business";
|
|
2196
|
+
const importNames = business ? "businessLocales, useZitadelProject" : "useZitadelProject";
|
|
2197
|
+
const localesComment = business ? `
|
|
2198
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
2199
|
+
// the login widget's neutral built-in dictionaries. Remove the :locales
|
|
2200
|
+
// binding to fall back to the neutral wording.` : "";
|
|
2201
|
+
const localesAttr = business ? "\n :locales=\"businessLocales\"" : "";
|
|
2202
|
+
const purposeAttr = purpose === "register" ? "\n purpose=\"register\"" : "";
|
|
2203
|
+
const widget = ctx.posture === "widget";
|
|
1789
2204
|
return `<script setup lang="ts">
|
|
1790
2205
|
${MANAGED_MARKER}
|
|
1791
|
-
import {
|
|
2206
|
+
import { ${importNames} } from "@zitadel/sdk-nuxt";${localesComment}
|
|
1792
2207
|
|
|
1793
2208
|
const project = useZitadelProject();
|
|
1794
2209
|
<\/script>
|
|
1795
2210
|
|
|
1796
2211
|
<template>
|
|
1797
|
-
<main style
|
|
2212
|
+
${widget ? "<div style=\"display: flex; justify-content: center; padding: 4rem 1rem\">" : "<main style=\"color-scheme: dark\">"}
|
|
1798
2213
|
<ClientOnly>
|
|
2214
|
+
${widget ? `<!-- variant="widget" embeds the card in this app's own layout, and
|
|
2215
|
+
theme="auto" follows its color scheme; variant="page" would paint
|
|
2216
|
+
the widget's full-page chrome instead. -->` : `<!-- variant="page" paints the widget's full-page chrome from design
|
|
2217
|
+
tokens; variant="widget" embeds the card inside a layout you own. -->`}
|
|
1799
2218
|
<zitadel-login
|
|
1800
|
-
|
|
2219
|
+
${widget ? "variant=\"widget\"\n theme=\"auto\"" : "variant=\"page\""}
|
|
2220
|
+
:project="project"${localesAttr}${purposeAttr}
|
|
1801
2221
|
post-sign-in-url="/profile"
|
|
1802
2222
|
/>
|
|
1803
2223
|
</ClientOnly>
|
|
1804
|
-
</main>
|
|
2224
|
+
${widget ? "</div>" : "</main>"}
|
|
1805
2225
|
</template>
|
|
1806
2226
|
`;
|
|
1807
2227
|
}
|
|
1808
|
-
function loginPageTemplate() {
|
|
1809
|
-
return authPage("login");
|
|
2228
|
+
function loginPageTemplate(ctx) {
|
|
2229
|
+
return authPage("login", ctx);
|
|
1810
2230
|
}
|
|
1811
|
-
function registerPageTemplate() {
|
|
1812
|
-
return authPage("register");
|
|
2231
|
+
function registerPageTemplate(ctx) {
|
|
2232
|
+
return authPage("register", ctx);
|
|
1813
2233
|
}
|
|
1814
2234
|
/** `pages/profile.vue` — the post-sign-in "signed in as" session card. */
|
|
1815
|
-
function profilePageTemplate() {
|
|
2235
|
+
function profilePageTemplate(ctx) {
|
|
2236
|
+
const widget = ctx.posture === "widget";
|
|
2237
|
+
const variantAttrs = widget ? "variant=\"widget\"\n theme=\"auto\"" : "variant=\"page\"";
|
|
2238
|
+
const postureComment = widget ? `<!-- variant="widget" embeds the session card in this app's own layout,
|
|
2239
|
+
and theme="auto" follows its color scheme; variant="page" would
|
|
2240
|
+
paint the card's full-page chrome instead. Your own components (a
|
|
2241
|
+
header, an account menu) read the same session state with the
|
|
2242
|
+
auto-imported useAuth() composable. -->` : `<!-- variant="page" paints the session card's full-page chrome from design
|
|
2243
|
+
tokens; variant="widget" embeds the card inside a layout you own.
|
|
2244
|
+
Your own components (a header, an account menu) read the same session
|
|
2245
|
+
state with the auto-imported useAuth() composable. -->`;
|
|
2246
|
+
const wrapperOpen = widget ? "<div style=\"display: flex; justify-content: center; padding: 4rem 1rem\">" : "<main style=\"color-scheme: dark\">";
|
|
2247
|
+
const wrapperClose = widget ? "</div>" : "</main>";
|
|
1816
2248
|
return `<script setup lang="ts">
|
|
1817
2249
|
${MANAGED_MARKER}
|
|
1818
2250
|
import { useZitadelProject } from "@zitadel/sdk-nuxt";
|
|
@@ -1821,11 +2253,16 @@ const project = useZitadelProject();
|
|
|
1821
2253
|
<\/script>
|
|
1822
2254
|
|
|
1823
2255
|
<template>
|
|
1824
|
-
|
|
2256
|
+
${wrapperOpen}
|
|
1825
2257
|
<ClientOnly>
|
|
1826
|
-
|
|
2258
|
+
${postureComment}
|
|
2259
|
+
${widget ? `<zitadel-session
|
|
2260
|
+
${variantAttrs}
|
|
2261
|
+
:project="project"
|
|
2262
|
+
post-sign-out-url="/login"
|
|
2263
|
+
/>` : `<zitadel-session variant="page" :project="project" post-sign-out-url="/login" />`}
|
|
1827
2264
|
</ClientOnly>
|
|
1828
|
-
|
|
2265
|
+
${wrapperClose}
|
|
1829
2266
|
</template>
|
|
1830
2267
|
`;
|
|
1831
2268
|
}
|
|
@@ -1887,30 +2324,29 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
1887
2324
|
routeOps(ctx) {
|
|
1888
2325
|
const src = (rel) => join(ctx.framework.appDir, rel);
|
|
1889
2326
|
return [
|
|
1890
|
-
{
|
|
2327
|
+
...ctx.scaffoldedFramework ? [{
|
|
1891
2328
|
kind: "write",
|
|
1892
2329
|
path: src("app.vue"),
|
|
1893
2330
|
contents: appVueTemplate()
|
|
1894
|
-
},
|
|
1895
|
-
{
|
|
2331
|
+
}, {
|
|
1896
2332
|
kind: "write",
|
|
1897
2333
|
path: src("pages/index.vue"),
|
|
1898
2334
|
contents: indexPageTemplate()
|
|
1899
|
-
},
|
|
2335
|
+
}] : [],
|
|
1900
2336
|
{
|
|
1901
2337
|
kind: "write",
|
|
1902
2338
|
path: src("pages/login.vue"),
|
|
1903
|
-
contents: loginPageTemplate()
|
|
2339
|
+
contents: loginPageTemplate(ctx)
|
|
1904
2340
|
},
|
|
1905
2341
|
{
|
|
1906
2342
|
kind: "write",
|
|
1907
2343
|
path: src("pages/register.vue"),
|
|
1908
|
-
contents: registerPageTemplate()
|
|
2344
|
+
contents: registerPageTemplate(ctx)
|
|
1909
2345
|
},
|
|
1910
2346
|
{
|
|
1911
2347
|
kind: "write",
|
|
1912
2348
|
path: src("pages/profile.vue"),
|
|
1913
|
-
contents: profilePageTemplate()
|
|
2349
|
+
contents: profilePageTemplate(ctx)
|
|
1914
2350
|
},
|
|
1915
2351
|
{
|
|
1916
2352
|
kind: "write",
|
|
@@ -1928,7 +2364,8 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
1928
2364
|
edit: nuxtConfigEdit({
|
|
1929
2365
|
projectId: ctx.project.id,
|
|
1930
2366
|
server: ctx.server
|
|
1931
|
-
})
|
|
2367
|
+
}),
|
|
2368
|
+
wiring: "infrastructure"
|
|
1932
2369
|
},
|
|
1933
2370
|
{
|
|
1934
2371
|
kind: "merge-env",
|
|
@@ -1959,6 +2396,14 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
1959
2396
|
src("plugins/auth.server.ts")
|
|
1960
2397
|
];
|
|
1961
2398
|
}
|
|
2399
|
+
infrastructureFiles(view) {
|
|
2400
|
+
const src = (rel) => join(view.framework.appDir, rel);
|
|
2401
|
+
return [src("plugins/zitadel-components.client.ts"), src("plugins/auth.server.ts")];
|
|
2402
|
+
}
|
|
2403
|
+
conditionallyScaffoldedFiles(view) {
|
|
2404
|
+
const src = (rel) => join(view.framework.appDir, rel);
|
|
2405
|
+
return [src("app.vue"), src("pages/index.vue")];
|
|
2406
|
+
}
|
|
1962
2407
|
routeDeps(_view) {
|
|
1963
2408
|
return [SDK_DEPENDENCY$5];
|
|
1964
2409
|
}
|
|
@@ -1979,13 +2424,13 @@ var NuxtPatcher = class extends AbstractRulePatcher {
|
|
|
1979
2424
|
* frameworks (React, Vue, Solid, Svelte, Qwik). It forwards same-origin
|
|
1980
2425
|
* `/__nextgen/*` calls to the
|
|
1981
2426
|
* backend, strips the prefix, and attaches the project's service-key secret
|
|
1982
|
-
* (read from `ZITADEL_PROJECT_SECRET` in `.env.local`)
|
|
1983
|
-
*
|
|
1984
|
-
* configured `envPrefix` (default `VITE_`) to
|
|
1985
|
-
* key never leaks into the browser.
|
|
2427
|
+
* (read from `ZITADEL_PROJECT_SECRET` in `.env.local`) only to the current
|
|
2428
|
+
* app-plane `POST /sessions/exchange` operation. The secret stays server-side:
|
|
2429
|
+
* Vite only exposes vars with the configured `envPrefix` (default `VITE_`) to
|
|
2430
|
+
* client bundles, so this server-only key never leaks into the browser.
|
|
1986
2431
|
*/
|
|
1987
2432
|
function proxyEntryCode(server) {
|
|
1988
|
-
return
|
|
2433
|
+
return `/* ${PROXY_CREDENTIAL_POLICY_MARKER} */ {
|
|
1989
2434
|
target: ${JSON.stringify(server)},
|
|
1990
2435
|
changeOrigin: false,
|
|
1991
2436
|
rewrite: (path) => path.replace(/^\\${PROXY_PATH}/, "").replace(/^(?!\\/)/, "/"),
|
|
@@ -1996,11 +2441,45 @@ function proxyEntryCode(server) {
|
|
|
1996
2441
|
}
|
|
1997
2442
|
const bearer = \`Bearer \${secret}\`;
|
|
1998
2443
|
proxy.on("proxyReq", (proxyReq) => {
|
|
1999
|
-
proxyReq.
|
|
2444
|
+
const pathname = new URL(proxyReq.path, "http://zitadel.local").pathname;
|
|
2445
|
+
if (
|
|
2446
|
+
proxyReq.method === "POST" &&
|
|
2447
|
+
pathname === "/sessions/exchange" &&
|
|
2448
|
+
!proxyReq.getHeader("authorization")
|
|
2449
|
+
) {
|
|
2450
|
+
proxyReq.setHeader("authorization", bearer);
|
|
2451
|
+
}
|
|
2000
2452
|
});
|
|
2001
2453
|
},
|
|
2002
2454
|
}`;
|
|
2003
2455
|
}
|
|
2456
|
+
const LEGACY_PROXY_REQUEST_LISTENER = /(^[\t ]*)proxy\.on\("proxyReq", \(proxyReq\) => \{\r?\n\1 {2}proxyReq\.setHeader\("authorization", bearer\);\r?\n\1\}\);/m;
|
|
2457
|
+
function exchangeOnlyProxyRequestListener(indent) {
|
|
2458
|
+
return `${indent}/* ${PROXY_CREDENTIAL_POLICY_MARKER} */
|
|
2459
|
+
${indent}proxy.on("proxyReq", (proxyReq) => {
|
|
2460
|
+
${indent} const pathname = new URL(proxyReq.path, "http://zitadel.local").pathname;
|
|
2461
|
+
${indent} if (
|
|
2462
|
+
${indent} proxyReq.method === "POST" &&
|
|
2463
|
+
${indent} pathname === "/sessions/exchange" &&
|
|
2464
|
+
${indent} !proxyReq.getHeader("authorization")
|
|
2465
|
+
${indent} ) {
|
|
2466
|
+
${indent} proxyReq.setHeader("authorization", bearer);
|
|
2467
|
+
${indent} }
|
|
2468
|
+
${indent}});`;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Identifies the exact proxy shape emitted before the exchange-only credential
|
|
2472
|
+
* policy. Setup may replace that generated listener, but must preserve the
|
|
2473
|
+
* surrounding `/__nextgen` proxy and any user changes elsewhere in the entry.
|
|
2474
|
+
*/
|
|
2475
|
+
function isLegacyManagedProxy(source) {
|
|
2476
|
+
if (source.includes("zitadel:proxy:v2")) return false;
|
|
2477
|
+
return [
|
|
2478
|
+
"loadEnv(\"development\", process.cwd(), \"ZITADEL_\").ZITADEL_PROJECT_SECRET",
|
|
2479
|
+
"throw new Error(\"ZITADEL_PROJECT_SECRET is not set; add it to .env.local (zitadel setup writes it).\")",
|
|
2480
|
+
"const bearer = `Bearer ${secret}`;"
|
|
2481
|
+
].every((fingerprint) => source.includes(fingerprint)) && LEGACY_PROXY_REQUEST_LISTENER.test(source);
|
|
2482
|
+
}
|
|
2004
2483
|
/** The imports that the injected proxy entry depends on. */
|
|
2005
2484
|
const PROXY_IMPORTS = [{
|
|
2006
2485
|
from: "vite",
|
|
@@ -2021,9 +2500,12 @@ const PROXY_IMPORTS = [{
|
|
|
2021
2500
|
function viteProxyEdit(devPort, server) {
|
|
2022
2501
|
return (source) => {
|
|
2023
2502
|
const label = "the Vite config (vite.config.*)";
|
|
2024
|
-
|
|
2503
|
+
let workingSource = source;
|
|
2504
|
+
if (workingSource !== void 0 && isLegacyManagedProxy(workingSource)) workingSource = workingSource.replace(LEGACY_PROXY_REQUEST_LISTENER, (_listener, indent) => exchangeOnlyProxyRequestListener(indent));
|
|
2505
|
+
else if (workingSource !== void 0) assertNoUnreviewedProjectSecretProxy(workingSource, label);
|
|
2506
|
+
const mod = parseConfigModule(workingSource, label);
|
|
2025
2507
|
const config = resolveDefaultExportObject(mod, label);
|
|
2026
|
-
let changed =
|
|
2508
|
+
let changed = workingSource !== source;
|
|
2027
2509
|
const serverConfig = ensureEditableObject(config, "server");
|
|
2028
2510
|
if (serverConfig.port === void 0) {
|
|
2029
2511
|
serverConfig.port = devPort;
|
|
@@ -2058,7 +2540,8 @@ function buildViteProxyOp(devPort, server) {
|
|
|
2058
2540
|
return {
|
|
2059
2541
|
kind: "edit",
|
|
2060
2542
|
path: [...VITE_CONFIG_PATHS],
|
|
2061
|
-
edit: viteProxyEdit(devPort, server)
|
|
2543
|
+
edit: viteProxyEdit(devPort, server),
|
|
2544
|
+
wiring: "infrastructure"
|
|
2062
2545
|
};
|
|
2063
2546
|
}
|
|
2064
2547
|
//#endregion
|
|
@@ -2071,12 +2554,25 @@ function buildViteProxyOp(devPort, server) {
|
|
|
2071
2554
|
* (`main.tsx` imports `{ App }`). The project id comes from
|
|
2072
2555
|
* `VITE_ZITADEL_PROJECT_ID`. No secret reaches the browser: the dev proxy in
|
|
2073
2556
|
* `vite.config.*` attaches the project service-key secret (from
|
|
2074
|
-
* `ZITADEL_PROJECT_SECRET`) server-side
|
|
2075
|
-
|
|
2076
|
-
|
|
2557
|
+
* `ZITADEL_PROJECT_SECRET`) server-side only to `POST /sessions/exchange`.
|
|
2558
|
+
*
|
|
2559
|
+
* Projects set up with the business use case additionally pass the SDK's
|
|
2560
|
+
* `businessLocales` overlay to the login widgets, restoring work-email copy on
|
|
2561
|
+
* top of the widget's neutral built-in dictionaries. A plain `locales` prop
|
|
2562
|
+
* suffices here: the wrapper component assigns it as a DOM property internally.
|
|
2563
|
+
*/
|
|
2564
|
+
function appTemplate$4(ctx) {
|
|
2565
|
+
const business = ctx.useCase === "business";
|
|
2566
|
+
const importNames = business ? "ZitadelLogin, ZitadelSession, businessLocales, configureZitadel" : "ZitadelLogin, ZitadelSession, configureZitadel";
|
|
2567
|
+
const localesComment = business ? `
|
|
2568
|
+
|
|
2569
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
2570
|
+
// the login widget's neutral built-in dictionaries. Remove the locales prop to
|
|
2571
|
+
// fall back to the neutral wording.` : "";
|
|
2572
|
+
const localesAttr = business ? " locales={businessLocales}" : "";
|
|
2077
2573
|
return `${MANAGED_MARKER}
|
|
2078
2574
|
import { component$, useVisibleTask$ } from "@builder.io/qwik";
|
|
2079
|
-
import {
|
|
2575
|
+
import { ${importNames} } from "@zitadel/sdk-qwik";${localesComment}
|
|
2080
2576
|
|
|
2081
2577
|
const project = configureZitadel({
|
|
2082
2578
|
projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
|
|
@@ -2105,13 +2601,13 @@ export const App = component$(() => {
|
|
|
2105
2601
|
if (path.startsWith("/register")) {
|
|
2106
2602
|
return (
|
|
2107
2603
|
<div style={{ position: "fixed", inset: "0", overflow: "auto", background: "#0f0f11", colorScheme: "dark" }}>
|
|
2108
|
-
<ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
|
|
2604
|
+
<ZitadelLogin project={project}${localesAttr} purpose="register" postSignInUrl="/profile" />
|
|
2109
2605
|
</div>
|
|
2110
2606
|
);
|
|
2111
2607
|
}
|
|
2112
2608
|
return (
|
|
2113
2609
|
<div style={{ position: "fixed", inset: "0", overflow: "auto", background: "#0f0f11", colorScheme: "dark" }}>
|
|
2114
|
-
<ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
|
|
2610
|
+
<ZitadelLogin project={project}${localesAttr} purpose="login" postSignInUrl="/profile" />
|
|
2115
2611
|
</div>
|
|
2116
2612
|
);
|
|
2117
2613
|
});
|
|
@@ -2125,14 +2621,15 @@ const SDK_DEPENDENCY$4 = "@zitadel/sdk-qwik";
|
|
|
2125
2621
|
* `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
|
|
2126
2622
|
* managed `src/app.tsx` auth entry, a non-destructive `vite.config.*` merge
|
|
2127
2623
|
* that adds the `/__nextgen` dev proxy (attaching the project secret from
|
|
2128
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
2624
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`), the `VITE_`-prefixed
|
|
2129
2625
|
* project id, and the SDK dep.
|
|
2130
2626
|
*
|
|
2131
2627
|
* The create-vite Qwik template uses a lowercase `src/app.tsx` exporting a named
|
|
2132
2628
|
* `App` (mounted by `main.tsx`), so this patcher writes that exact entry. Unlike
|
|
2133
2629
|
* Next.js — whose middleware runs the proxy server-side — a SPA has no server,
|
|
2134
|
-
* so the dev proxy
|
|
2135
|
-
*
|
|
2630
|
+
* so the dev proxy provides the same-origin `/__nextgen` path locally. In
|
|
2631
|
+
* production that path comes from a platform rewrite or minimal worker
|
|
2632
|
+
* (ADR 036); CLI scaffolding for it is tracked in issue #560.
|
|
2136
2633
|
*/
|
|
2137
2634
|
var QwikPatcher = class extends AbstractRulePatcher {
|
|
2138
2635
|
canPatch(framework) {
|
|
@@ -2146,7 +2643,7 @@ var QwikPatcher = class extends AbstractRulePatcher {
|
|
|
2146
2643
|
{
|
|
2147
2644
|
kind: "write",
|
|
2148
2645
|
path: "src/app.tsx",
|
|
2149
|
-
contents: appTemplate$4()
|
|
2646
|
+
contents: appTemplate$4(ctx)
|
|
2150
2647
|
},
|
|
2151
2648
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
2152
2649
|
{
|
|
@@ -2191,12 +2688,27 @@ var QwikPatcher = class extends AbstractRulePatcher {
|
|
|
2191
2688
|
* The project id comes from `VITE_ZITADEL_PROJECT_ID` (Vite only exposes
|
|
2192
2689
|
* `VITE_`-prefixed env to the client). No secret reaches the browser: the dev
|
|
2193
2690
|
* proxy in `vite.config.*` attaches the project service-key secret (from
|
|
2194
|
-
* `ZITADEL_PROJECT_SECRET`) server-side
|
|
2195
|
-
|
|
2196
|
-
|
|
2691
|
+
* `ZITADEL_PROJECT_SECRET`) server-side only to `POST /sessions/exchange`.
|
|
2692
|
+
*
|
|
2693
|
+
* Projects set up with the business use case additionally pass the SDK's
|
|
2694
|
+
* `businessLocales` overlay to the login widgets, restoring work-email copy on
|
|
2695
|
+
* top of the widget's neutral built-in dictionaries. A plain `locales` prop
|
|
2696
|
+
* suffices here: the wrapper component assigns it as a DOM property internally,
|
|
2697
|
+
* so the React-18 attribute-decay caveat of the raw custom element does not
|
|
2698
|
+
* apply.
|
|
2699
|
+
*/
|
|
2700
|
+
function appTemplate$3(ctx) {
|
|
2701
|
+
const business = ctx.useCase === "business";
|
|
2702
|
+
const importNames = business ? "ZitadelLogin, ZitadelSession, businessLocales, configureZitadel" : "ZitadelLogin, ZitadelSession, configureZitadel";
|
|
2703
|
+
const localesComment = business ? `
|
|
2704
|
+
|
|
2705
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
2706
|
+
// the login widget's neutral built-in dictionaries. Remove the locales prop to
|
|
2707
|
+
// fall back to the neutral wording.` : "";
|
|
2708
|
+
const localesAttr = business ? " locales={businessLocales}" : "";
|
|
2197
2709
|
return `${MANAGED_MARKER}
|
|
2198
2710
|
import { useEffect } from "react";
|
|
2199
|
-
import {
|
|
2711
|
+
import { ${importNames} } from "@zitadel/sdk-react";${localesComment}
|
|
2200
2712
|
|
|
2201
2713
|
const project = configureZitadel({
|
|
2202
2714
|
projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
|
|
@@ -2225,13 +2737,13 @@ export default function App() {
|
|
|
2225
2737
|
if (path.startsWith("/register")) {
|
|
2226
2738
|
return (
|
|
2227
2739
|
<div style={{ position: "fixed", inset: 0, overflow: "auto", background: "#0f0f11", colorScheme: "dark" }}>
|
|
2228
|
-
<ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
|
|
2740
|
+
<ZitadelLogin project={project}${localesAttr} purpose="register" postSignInUrl="/profile" />
|
|
2229
2741
|
</div>
|
|
2230
2742
|
);
|
|
2231
2743
|
}
|
|
2232
2744
|
return (
|
|
2233
2745
|
<div style={{ position: "fixed", inset: 0, overflow: "auto", background: "#0f0f11", colorScheme: "dark" }}>
|
|
2234
|
-
<ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
|
|
2746
|
+
<ZitadelLogin project={project}${localesAttr} purpose="login" postSignInUrl="/profile" />
|
|
2235
2747
|
</div>
|
|
2236
2748
|
);
|
|
2237
2749
|
}
|
|
@@ -2245,12 +2757,14 @@ const SDK_DEPENDENCY$3 = "@zitadel/sdk-react";
|
|
|
2245
2757
|
* `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
|
|
2246
2758
|
* managed `src/App.tsx` auth entry, a non-destructive `vite.config.*` merge
|
|
2247
2759
|
* that adds the `/__nextgen` dev proxy (attaching the project secret from
|
|
2248
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
2760
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`), the `VITE_`-prefixed
|
|
2249
2761
|
* project id, and the SDK dep.
|
|
2250
2762
|
*
|
|
2251
2763
|
* Unlike Next.js — whose middleware runs the proxy and token exchange
|
|
2252
|
-
* server-side — a SPA has no server, so the dev proxy
|
|
2253
|
-
*
|
|
2764
|
+
* server-side — a SPA has no server, so the dev proxy provides the same-origin
|
|
2765
|
+
* `/__nextgen` path locally. In production that path comes from a platform
|
|
2766
|
+
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
|
|
2767
|
+
* issue #560.
|
|
2254
2768
|
*/
|
|
2255
2769
|
var ReactPatcher = class extends AbstractRulePatcher {
|
|
2256
2770
|
canPatch(framework) {
|
|
@@ -2264,7 +2778,7 @@ var ReactPatcher = class extends AbstractRulePatcher {
|
|
|
2264
2778
|
{
|
|
2265
2779
|
kind: "write",
|
|
2266
2780
|
path: "src/App.tsx",
|
|
2267
|
-
contents: appTemplate$3()
|
|
2781
|
+
contents: appTemplate$3(ctx)
|
|
2268
2782
|
},
|
|
2269
2783
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
2270
2784
|
{
|
|
@@ -2309,12 +2823,25 @@ var ReactPatcher = class extends AbstractRulePatcher {
|
|
|
2309
2823
|
* The project id comes from `VITE_ZITADEL_PROJECT_ID` (Vite only exposes
|
|
2310
2824
|
* `VITE_`-prefixed env to the client). No secret reaches the browser: the dev
|
|
2311
2825
|
* proxy in `vite.config.*` attaches the project service-key secret (from
|
|
2312
|
-
* `ZITADEL_PROJECT_SECRET`) server-side
|
|
2313
|
-
|
|
2314
|
-
|
|
2826
|
+
* `ZITADEL_PROJECT_SECRET`) server-side only to `POST /sessions/exchange`.
|
|
2827
|
+
*
|
|
2828
|
+
* Projects set up with the business use case additionally pass the SDK's
|
|
2829
|
+
* `businessLocales` overlay to the login widgets, restoring work-email copy on
|
|
2830
|
+
* top of the widget's neutral built-in dictionaries. A plain `locales` prop
|
|
2831
|
+
* suffices here: the wrapper component assigns it as a DOM property internally.
|
|
2832
|
+
*/
|
|
2833
|
+
function appTemplate$2(ctx) {
|
|
2834
|
+
const business = ctx.useCase === "business";
|
|
2835
|
+
const importNames = business ? "ZitadelLogin, ZitadelSession, businessLocales, configureZitadel" : "ZitadelLogin, ZitadelSession, configureZitadel";
|
|
2836
|
+
const localesComment = business ? `
|
|
2837
|
+
|
|
2838
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
2839
|
+
// the login widget's neutral built-in dictionaries. Remove the locales prop to
|
|
2840
|
+
// fall back to the neutral wording.` : "";
|
|
2841
|
+
const localesAttr = business ? " locales={businessLocales}" : "";
|
|
2315
2842
|
return `${MANAGED_MARKER}
|
|
2316
2843
|
import { onMount } from "solid-js";
|
|
2317
|
-
import {
|
|
2844
|
+
import { ${importNames} } from "@zitadel/sdk-solid";${localesComment}
|
|
2318
2845
|
|
|
2319
2846
|
const project = configureZitadel({
|
|
2320
2847
|
projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
|
|
@@ -2343,13 +2870,13 @@ export default function App() {
|
|
|
2343
2870
|
if (path.startsWith("/register")) {
|
|
2344
2871
|
return (
|
|
2345
2872
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
2346
|
-
<ZitadelLogin project={project} purpose="register" postSignInUrl="/profile" />
|
|
2873
|
+
<ZitadelLogin project={project}${localesAttr} purpose="register" postSignInUrl="/profile" />
|
|
2347
2874
|
</div>
|
|
2348
2875
|
);
|
|
2349
2876
|
}
|
|
2350
2877
|
return (
|
|
2351
2878
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
2352
|
-
<ZitadelLogin project={project} purpose="login" postSignInUrl="/profile" />
|
|
2879
|
+
<ZitadelLogin project={project}${localesAttr} purpose="login" postSignInUrl="/profile" />
|
|
2353
2880
|
</div>
|
|
2354
2881
|
);
|
|
2355
2882
|
}
|
|
@@ -2363,12 +2890,14 @@ const SDK_DEPENDENCY$2 = "@zitadel/sdk-solid";
|
|
|
2363
2890
|
* `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
|
|
2364
2891
|
* managed `src/App.tsx` auth entry, a non-destructive `vite.config.*` merge
|
|
2365
2892
|
* that adds the `/__nextgen` dev proxy (attaching the project secret from
|
|
2366
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
2893
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`), the `VITE_`-prefixed
|
|
2367
2894
|
* project id, and the SDK dep.
|
|
2368
2895
|
*
|
|
2369
2896
|
* Unlike Next.js — whose middleware runs the proxy and token exchange
|
|
2370
|
-
* server-side — a SPA has no server, so the dev proxy
|
|
2371
|
-
*
|
|
2897
|
+
* server-side — a SPA has no server, so the dev proxy provides the same-origin
|
|
2898
|
+
* `/__nextgen` path locally. In production that path comes from a platform
|
|
2899
|
+
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
|
|
2900
|
+
* issue #560.
|
|
2372
2901
|
*/
|
|
2373
2902
|
var SolidPatcher = class extends AbstractRulePatcher {
|
|
2374
2903
|
canPatch(framework) {
|
|
@@ -2382,7 +2911,7 @@ var SolidPatcher = class extends AbstractRulePatcher {
|
|
|
2382
2911
|
{
|
|
2383
2912
|
kind: "write",
|
|
2384
2913
|
path: "src/App.tsx",
|
|
2385
|
-
contents: appTemplate$2()
|
|
2914
|
+
contents: appTemplate$2(ctx)
|
|
2386
2915
|
},
|
|
2387
2916
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
2388
2917
|
{
|
|
@@ -2428,13 +2957,26 @@ var SolidPatcher = class extends AbstractRulePatcher {
|
|
|
2428
2957
|
* eject/doctor stay marker-aware. The project id comes from
|
|
2429
2958
|
* `VITE_ZITADEL_PROJECT_ID`. No secret reaches the browser: the dev proxy in
|
|
2430
2959
|
* `vite.config.*` attaches the project service-key secret (from
|
|
2431
|
-
* `ZITADEL_PROJECT_SECRET`) server-side
|
|
2432
|
-
|
|
2433
|
-
|
|
2960
|
+
* `ZITADEL_PROJECT_SECRET`) server-side only to `POST /sessions/exchange`.
|
|
2961
|
+
*
|
|
2962
|
+
* Projects set up with the business use case additionally pass the SDK's
|
|
2963
|
+
* `businessLocales` overlay to the login widgets, restoring work-email copy on
|
|
2964
|
+
* top of the widget's neutral built-in dictionaries. A plain `locales` prop
|
|
2965
|
+
* suffices here: the wrapper component assigns it as a DOM property internally.
|
|
2966
|
+
*/
|
|
2967
|
+
function appTemplate$1(ctx) {
|
|
2968
|
+
const business = ctx.useCase === "business";
|
|
2969
|
+
const importNames = business ? "ZitadelLogin, ZitadelSession, businessLocales, configureZitadel" : "ZitadelLogin, ZitadelSession, configureZitadel";
|
|
2970
|
+
const localesComment = business ? `
|
|
2971
|
+
|
|
2972
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
2973
|
+
// the login widget's neutral built-in dictionaries. Remove the locales prop to
|
|
2974
|
+
// fall back to the neutral wording.` : "";
|
|
2975
|
+
const localesAttr = business ? " locales={businessLocales}" : "";
|
|
2434
2976
|
return `<script lang="ts">
|
|
2435
2977
|
${MANAGED_MARKER}
|
|
2436
2978
|
import { onMount } from "svelte";
|
|
2437
|
-
import {
|
|
2979
|
+
import { ${importNames} } from "@zitadel/sdk-svelte";${localesComment}
|
|
2438
2980
|
|
|
2439
2981
|
const project = configureZitadel({
|
|
2440
2982
|
projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
|
|
@@ -2456,11 +2998,11 @@ onMount(() => {
|
|
|
2456
2998
|
</div>
|
|
2457
2999
|
{:else if path.startsWith("/register")}
|
|
2458
3000
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
2459
|
-
<ZitadelLogin {project} purpose="register" postSignInUrl="/profile" />
|
|
3001
|
+
<ZitadelLogin {project}${localesAttr} purpose="register" postSignInUrl="/profile" />
|
|
2460
3002
|
</div>
|
|
2461
3003
|
{:else if path !== "/"}
|
|
2462
3004
|
<div style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
2463
|
-
<ZitadelLogin {project} purpose="login" postSignInUrl="/profile" />
|
|
3005
|
+
<ZitadelLogin {project}${localesAttr} purpose="login" postSignInUrl="/profile" />
|
|
2464
3006
|
</div>
|
|
2465
3007
|
{/if}
|
|
2466
3008
|
`;
|
|
@@ -2473,12 +3015,14 @@ const SDK_DEPENDENCY$1 = "@zitadel/sdk-svelte";
|
|
|
2473
3015
|
* `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
|
|
2474
3016
|
* managed `src/App.svelte` auth entry, a non-destructive `vite.config.*` merge
|
|
2475
3017
|
* that adds the `/__nextgen` dev proxy (attaching the project secret from
|
|
2476
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
3018
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`), the `VITE_`-prefixed
|
|
2477
3019
|
* project id, and the SDK dep.
|
|
2478
3020
|
*
|
|
2479
3021
|
* Unlike Next.js — whose middleware runs the proxy and token exchange
|
|
2480
|
-
* server-side — a SPA has no server, so the dev proxy
|
|
2481
|
-
*
|
|
3022
|
+
* server-side — a SPA has no server, so the dev proxy provides the same-origin
|
|
3023
|
+
* `/__nextgen` path locally. In production that path comes from a platform
|
|
3024
|
+
* rewrite or minimal worker (ADR 036); CLI scaffolding for it is tracked in
|
|
3025
|
+
* issue #560.
|
|
2482
3026
|
*/
|
|
2483
3027
|
var SveltePatcher = class extends AbstractRulePatcher {
|
|
2484
3028
|
canPatch(framework) {
|
|
@@ -2492,7 +3036,7 @@ var SveltePatcher = class extends AbstractRulePatcher {
|
|
|
2492
3036
|
{
|
|
2493
3037
|
kind: "write",
|
|
2494
3038
|
path: "src/App.svelte",
|
|
2495
|
-
contents: appTemplate$1()
|
|
3039
|
+
contents: appTemplate$1(ctx)
|
|
2496
3040
|
},
|
|
2497
3041
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
2498
3042
|
{
|
|
@@ -2538,13 +3082,26 @@ var SveltePatcher = class extends AbstractRulePatcher {
|
|
|
2538
3082
|
* eject/doctor stay marker-aware. The project id comes from
|
|
2539
3083
|
* `VITE_ZITADEL_PROJECT_ID`. No secret reaches the browser: the dev proxy in
|
|
2540
3084
|
* `vite.config.*` attaches the project service-key secret (from
|
|
2541
|
-
* `ZITADEL_PROJECT_SECRET`) server-side
|
|
2542
|
-
|
|
2543
|
-
|
|
3085
|
+
* `ZITADEL_PROJECT_SECRET`) server-side only to `POST /sessions/exchange`.
|
|
3086
|
+
*
|
|
3087
|
+
* Projects set up with the business use case additionally pass the SDK's
|
|
3088
|
+
* `businessLocales` overlay to the login widgets, restoring work-email copy on
|
|
3089
|
+
* top of the widget's neutral built-in dictionaries. A plain `:locales` prop
|
|
3090
|
+
* suffices here: the wrapper component assigns it as a DOM property internally.
|
|
3091
|
+
*/
|
|
3092
|
+
function appTemplate(ctx) {
|
|
3093
|
+
const business = ctx.useCase === "business";
|
|
3094
|
+
const importNames = business ? "ZitadelLogin, ZitadelSession, businessLocales, configureZitadel" : "ZitadelLogin, ZitadelSession, configureZitadel";
|
|
3095
|
+
const localesComment = business ? `
|
|
3096
|
+
|
|
3097
|
+
// Set up for a business audience: businessLocales overlays work-email copy on
|
|
3098
|
+
// the login widget's neutral built-in dictionaries. Remove the :locales prop
|
|
3099
|
+
// to fall back to the neutral wording.` : "";
|
|
3100
|
+
const localesAttr = business ? " :locales=\"businessLocales\"" : "";
|
|
2544
3101
|
return `<script setup lang="ts">
|
|
2545
3102
|
${MANAGED_MARKER}
|
|
2546
3103
|
import { onMounted } from "vue";
|
|
2547
|
-
import {
|
|
3104
|
+
import { ${importNames} } from "@zitadel/sdk-vue";${localesComment}
|
|
2548
3105
|
|
|
2549
3106
|
const project = configureZitadel({
|
|
2550
3107
|
projectId: import.meta.env.VITE_ZITADEL_PROJECT_ID,
|
|
@@ -2571,10 +3128,10 @@ onMounted(() => {
|
|
|
2571
3128
|
v-else-if="path.startsWith('/register')"
|
|
2572
3129
|
style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark"
|
|
2573
3130
|
>
|
|
2574
|
-
<ZitadelLogin :project="project" purpose="register" postSignInUrl="/profile" />
|
|
3131
|
+
<ZitadelLogin :project="project"${localesAttr} purpose="register" postSignInUrl="/profile" />
|
|
2575
3132
|
</div>
|
|
2576
3133
|
<div v-else-if="path !== '/'" style="position:fixed;inset:0;overflow:auto;background:#0f0f11;color-scheme:dark">
|
|
2577
|
-
<ZitadelLogin :project="project" purpose="login" postSignInUrl="/profile" />
|
|
3134
|
+
<ZitadelLogin :project="project"${localesAttr} purpose="login" postSignInUrl="/profile" />
|
|
2578
3135
|
</div>
|
|
2579
3136
|
</template>
|
|
2580
3137
|
`;
|
|
@@ -2587,7 +3144,7 @@ const SDK_DEPENDENCY = "@zitadel/sdk-vue";
|
|
|
2587
3144
|
* `.zitadel/` base files from {@link AbstractRulePatcher} and contributes the
|
|
2588
3145
|
* managed `src/App.vue` auth entry, a non-destructive `vite.config.*` merge
|
|
2589
3146
|
* that adds the `/__nextgen` dev proxy (attaching the project secret from
|
|
2590
|
-
* `ZITADEL_PROJECT_SECRET` to
|
|
3147
|
+
* `ZITADEL_PROJECT_SECRET` only to `POST /sessions/exchange`), the `VITE_`-prefixed
|
|
2591
3148
|
* project id, and the SDK dep.
|
|
2592
3149
|
*/
|
|
2593
3150
|
var VuePatcher = class extends AbstractRulePatcher {
|
|
@@ -2602,7 +3159,7 @@ var VuePatcher = class extends AbstractRulePatcher {
|
|
|
2602
3159
|
{
|
|
2603
3160
|
kind: "write",
|
|
2604
3161
|
path: "src/App.vue",
|
|
2605
|
-
contents: appTemplate()
|
|
3162
|
+
contents: appTemplate(ctx)
|
|
2606
3163
|
},
|
|
2607
3164
|
this.viteProxyOp(ctx.framework.devPort, ctx.server),
|
|
2608
3165
|
{
|
|
@@ -3214,6 +3771,6 @@ function isErrno(error, code) {
|
|
|
3214
3771
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
3215
3772
|
}
|
|
3216
3773
|
//#endregion
|
|
3217
|
-
export {
|
|
3774
|
+
export { AGENTS_HEADER as a, RENDERER_IDS as i, inspectScaffoldTarget as n, removeGuidanceSection as o, AVAILABLE_RENDERER_IDS as r, issuerFromPort as s, createOrca as t };
|
|
3218
3775
|
|
|
3219
|
-
//# sourceMappingURL=orca-
|
|
3776
|
+
//# sourceMappingURL=orca-BWB4zOdw.mjs.map
|