@zitadel/testing 0.1.0-alpha.19 → 1.0.0-alpha.20

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 CHANGED
@@ -203,7 +203,7 @@ const z = await startLocalZitadel({
203
203
  keep, // keep the temp dir for debugging
204
204
  });
205
205
 
206
- z.handle; // serializable: { baseUrl, projectId, projectSecret, schemaId, previewSecret? }
206
+ z.handle; // serializable: { baseUrl, projectId, projectSecret, schemaId, previewSecret?, platform? }
207
207
  z.api; // authenticated @zitadel/api client (bearer = projectSecret)
208
208
  z.appEnv; // { ZITADEL_URL, NEXT_PUBLIC_ZITADEL_PROJECT_ID, ZITADEL_PROJECT_SECRET }
209
209
  await z.seedUser({ email?, password?, attributes? }); // → { id, email, password }
@@ -241,6 +241,44 @@ above.
241
241
  the schema-defined content under `attributes`) +
242
242
  `PUT /users/{id}/password` with `is_change_required: false`.
243
243
 
244
+ ## Credentials: the boot contract
245
+
246
+ The kit's boot contract is the sanctioned way tests and dev loops obtain
247
+ credentials: they come from the boot contract, never from a seed default —
248
+ the rule root ADR 053 §9 sets. The kit owns the server process and its
249
+ database, captures each credential from provisioning
250
+ output at the moment the server mints it, and exposes it predictably on
251
+ `handle`:
252
+
253
+ - `handle.projectSecret` — the seeded customer project's operator credential,
254
+ captured from `POST /projects` (the server returns it exactly once, at
255
+ creation). Bearer behind `z.api` and every seed op.
256
+ - `handle.previewSecret` — the same project's browser-plane credential (the
257
+ publishable-key predecessor from root ADR 036).
258
+ - `handle.platform` — the platform-plane slot (`PlatformCredentials`): the
259
+ reserved platform project's id and publishable key. **Stub today**: the
260
+ server's platform-project provisioner (Console ADR 0004 §2) has not
261
+ landed, so `startLocalZitadel` never populates it yet. Only the fields the
262
+ contract already guarantees are declared; credentials whose design is
263
+ still open — the platform automation principal (deferred to a future PAT /
264
+ service-user decision) and a boot-minted operator session — join as
265
+ optional fields once they exist, which is additive and churn-free.
266
+
267
+ What this rules out, deliberately: there is no server `--test-mode`, no
268
+ seed-document credential flag, and no other server-side door that mints
269
+ deterministic credentials — and none may be added. The production seed
270
+ contract stays credential-free; predictable test access is this kit's job,
271
+ done entirely with what provisioning already returns. If a credential the
272
+ kit needs is not capturable at provisioning time, the fix is to extend the
273
+ provisioning contract the boot path drives — never a server flag, and never
274
+ direct database writes, which would bypass the provisioner and break seed
275
+ ops for `connectZitadel` targets.
276
+
277
+ The in-repo dev loop follows the same contract: `moon run console:dev-real`
278
+ boots, seeds, and threads `handle.projectSecret` into the console dev proxy's
279
+ `CONSOLE_PROJECT_SECRET` itself; `--seed-only` prints the same variables for
280
+ a separately-started dev server. Nothing to remember or export.
281
+
244
282
  ## Parallelism model
245
283
 
246
284
  **One instance per suite, one fresh user per test.** Emails are unique per
@@ -1,13 +1,48 @@
1
1
  import { ZitadelClient } from "@zitadel/api/client";
2
2
 
3
3
  //#region src/types.d.ts
4
+ /**
5
+ * Credentials of the reserved platform project, captured by the kit at
6
+ * provisioning time. The shape follows root ADR 053 §9 and Console ADR 0004
7
+ * §2 (both Proposed): bootstrap mints **no platform project secret** — the
8
+ * publishable key is the only default credential, and test infrastructure
9
+ * gets its credentials from the testkit's boot contract, not from seed
10
+ * defaults.
11
+ *
12
+ * Stub today: the server's platform-project provisioner does not exist yet,
13
+ * so `startLocalZitadel` never populates this field. Only the fields §9
14
+ * itself guarantees are declared. Credentials whose design is still open —
15
+ * the platform automation principal (deferred to a future PAT /
16
+ * service-user decision) and a boot-minted operator session — join as
17
+ * optional fields once they exist: additions are churn-free, removals are
18
+ * not, so nothing is pre-declared here.
19
+ */
20
+ interface PlatformCredentials {
21
+ /** Reserved platform project id — the Console sign-in target. */
22
+ projectId: string;
23
+ /**
24
+ * Browser-safe publishable key. Required: it is the one credential
25
+ * bootstrap always provisions (sign-in needs it), so a platform block
26
+ * without it is malformed.
27
+ */
28
+ publishableKey: string;
29
+ }
4
30
  /**
5
31
  * Serializable description of a bootstrapped instance + project. This is the
6
- * contract that crosses process boundaries (boot script -> Playwright workers).
32
+ * contract that crosses process boundaries (boot script -> Playwright workers)
33
+ * — and the credential surface of the kit: every credential the server mints
34
+ * during provisioning is captured here, so tests and dev loops read them from
35
+ * the handle instead of hand-set environment or a server-side test door (a
36
+ * server flag that mints deterministic credentials must not exist).
7
37
  */
8
38
  interface InstanceHandle {
9
39
  baseUrl: string;
10
40
  projectId: string;
41
+ /**
42
+ * The seeded customer project's operator credential, captured from
43
+ * `POST /projects` — the server returns it exactly once, at creation.
44
+ * Bearer for the management API (`api`) and every seed op.
45
+ */
11
46
  projectSecret: string;
12
47
  /**
13
48
  * First registered app origin. Flow submissions enforce the project's
@@ -21,7 +56,18 @@ interface InstanceHandle {
21
56
  * it alongside the project credential.
22
57
  */
23
58
  schemaId: string;
59
+ /**
60
+ * The same project's browser-plane credential (the publishable-key
61
+ * predecessor from root ADR 036), also captured at creation.
62
+ */
24
63
  previewSecret?: string;
64
+ /**
65
+ * Platform-plane credential slot. Unpopulated until the platform-project
66
+ * provisioner (Console ADR 0004 §2) lands server-side; see
67
+ * `PlatformCredentials` for what it carries, why it stays empty today, and
68
+ * why the still-undecided credentials are left out of it.
69
+ */
70
+ platform?: PlatformCredentials;
25
71
  }
26
72
  interface SeedUserInput {
27
73
  email?: string;
@@ -103,13 +149,19 @@ interface LocalZitadel extends ConnectedZitadel, AsyncDisposable {
103
149
  }
104
150
  //#endregion
105
151
  //#region src/app-env.d.ts
152
+ /**
153
+ * Handle fields a template may reference: the flat string facts. Structured
154
+ * fields (e.g. `platform`) are not env-var material — consumers read those
155
+ * from the handle directly.
156
+ */
157
+ type StringHandleField = { [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never }[keyof InstanceHandle];
106
158
  /**
107
159
  * Declarative mapping from an app's env var names to InstanceHandle fields.
108
160
  * A template (not a function) so it can cross process boundaries: the
109
161
  * Playwright config serializes it into the app-runner's environment, where it
110
162
  * is applied to the handle read from the handshake file.
111
163
  */
112
- type AppEnvTemplate = Record<string, keyof InstanceHandle>;
164
+ type AppEnvTemplate = Record<string, StringHandleField>;
113
165
  /**
114
166
  * The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own
115
167
  * template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`
@@ -118,5 +170,5 @@ type AppEnvTemplate = Record<string, keyof InstanceHandle>;
118
170
  declare const nextAppEnv: AppEnvTemplate;
119
171
  declare function applyAppEnvTemplate(template: AppEnvTemplate, handle: InstanceHandle): Record<string, string>;
120
172
  //#endregion
121
- export { Identity as a, LocalZitadelRuntime as c, SeedUserInput as d, SeedUsersTemplate as f, ConnectedZitadel as i, MintedSession as l, SessionCookie as m, applyAppEnvTemplate as n, InstanceHandle as o, SeededUser as p, nextAppEnv as r, LocalZitadel as s, AppEnvTemplate as t, SeedSessionInput as u };
122
- //# sourceMappingURL=app-env-Btmfcflb.d.mts.map
173
+ export { Identity as a, LocalZitadelRuntime as c, SeedSessionInput as d, SeedUserInput as f, SessionCookie as h, ConnectedZitadel as i, MintedSession as l, SeededUser as m, applyAppEnvTemplate as n, InstanceHandle as o, SeedUsersTemplate as p, nextAppEnv as r, LocalZitadel as s, AppEnvTemplate as t, PlatformCredentials as u };
174
+ //# sourceMappingURL=app-env-DlrV_ePR.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-env-DlrV_ePR.d.mts","names":[],"sources":["../src/types.ts","../src/app-env.ts"],"mappings":";;;;;AAkBA;;;;;AAmBA;;;;;;;;;UAnBiB,mBAAA;EAmDf;EAjDA,SAAA;EAiD8B;;AAGhC;;;EA9CE,cAAA;AAAA;;;;;;AAqDF;;;UA1CiB,cAAA;EACf,OAAA;EACA,SAAA;EA2CA;;;AAIF;;EAzCE,aAAA;EA0CA;;AAKF;;;EAzCE,SAAA;EA0CA;;;;;EApCA,QAAA;EAyCI;;AAGN;;EAvCE,aAAA;EA4CqB;;;;;;EArCrB,QAAA,GAAW,mBAAA;AAAA;AAAA,UAGI,aAAA;EACf,KAAA;EACA,QAAA;;EAEA,UAAA,GAAa,MAAA;AAAA;AAAA,UAGE,UAAA;EACf,EAAA;EACA,KAAA;EACA,QAAA;AAAA;;UAIe,QAAA;EACf,KAAA;EACA,QAAA;AAAA;;UAIe,aAAA;EACf,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;AAAA;AAAA,UAGe,aAAA;EACf,IAAA,EAAM,UAAA;;EAEN,YAAA;EACA,SAAA;EACA,MAAA,EAAQ,aAAA;AAAA;AAAA,UAGO,gBAAA,SAAyB,aAAA;EAyBP;EAvBjC,IAAA,GAAO,UAAA;EAyByD;EAvBhE,kBAAA;EAyBY;;;;;EAnBZ,MAAA;AAAA;AAAA,UAGe,iBAAA;EACf,KAAA,IAAS,KAAA;EACT,QAAA,IAAY,KAAA;EACZ,UAAA,IAAc,KAAA,aAAkB,MAAA;AAAA;AAAA,UAGjB,gBAAA;EACf,MAAA,EAAQ,cAAA;EAKC;EAHT,GAAA,EAAK,aAAA;EAGoC;EADzC,MAAA,EAAQ,MAAA;EACR,QAAA,CAAS,KAAA,GAAQ,aAAA,GAAgB,OAAA,CAAQ,UAAA;EAEL;EAApC,SAAA,CAAU,KAAA,UAAe,QAAA,GAAW,iBAAA,GAAoB,OAAA,CAAQ,UAAA;EAAR;EAExD,QAAA,IAAY,QAAA;EAAZ;;;;;EAMA,WAAA,CAAY,KAAA,GAAQ,gBAAA,GAAmB,OAAA,CAAQ,aAAA;AAAA;AAAA,UAGhC,mBAAA;EACf,IAAA;EACA,GAAA;EAFkC;EAIlC,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,gBAAA,EAAkB,eAAA;EACtD,OAAA,EAAS,mBAAA;EACT,IAAA,IAAQ,OAAA;AAAA;;;;;AA3IV;;;KCXK,iBAAA,iBACS,cAAA,KAAmB,cAAA,CAAe,CAAA,+BAAgC,CAAA,iBACxE,cAAA;;;;;;;KAQI,cAAA,GAAiB,MAAA,SAAe,iBAAA;;;;;;cAO/B,UAAA,EAAY,cAAA;AAAA,iBAMT,mBAAA,CACd,QAAA,EAAU,cAAA,EACV,MAAA,EAAQ,cAAA,GACP,MAAA"}
@@ -1,4 +1,4 @@
1
- const require_handshake = require("./handshake-CRKcgkfN.cjs");
1
+ const require_handshake = require("./handshake-BOsVBPtn.cjs");
2
2
  const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
3
3
  let node_child_process = require("node:child_process");
4
4
  //#region src/app-runner.ts
@@ -1,4 +1,4 @@
1
- import { i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-ClzWvG8z.mjs";
1
+ import { i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-BnAPXC6s.mjs";
2
2
  import { i as parseAppRunnerConfig, o as requireHandshakePath, t as APP_RUNNER_CONFIG_ENV } from "./orchestration-C640_1Lg.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region src/app-runner.ts
@@ -62,8 +62,21 @@ function validateHandle(value, source) {
62
62
  "schemaId"
63
63
  ]) if (typeof handle[field] !== "string" || handle[field].length === 0) throw new Error(`handshake file ${source} is missing "${field}"`);
64
64
  if (!URL.canParse(handle.baseUrl)) throw new Error(`handshake file ${source} has a malformed "baseUrl": ${handle.baseUrl}`);
65
+ if (handle.platform !== void 0) validatePlatform(handle.platform, source);
65
66
  return handle;
66
67
  }
68
+ /**
69
+ * The handshake is the cross-process contract, so a malformed platform block
70
+ * must fail here with the field name, not later with a less actionable error.
71
+ */
72
+ function validatePlatform(value, source) {
73
+ const fail = (detail) => {
74
+ throw new Error(`handshake file ${source} has a malformed "platform" block: ${detail}`);
75
+ };
76
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail("not an object");
77
+ const platform = value;
78
+ for (const field of ["projectId", "publishableKey"]) if (typeof platform[field] !== "string" || platform[field].length === 0) fail(`"${field}" is required`);
79
+ }
67
80
  //#endregion
68
81
  Object.defineProperty(exports, "applyAppEnvTemplate", {
69
82
  enumerable: true,
@@ -96,4 +109,4 @@ Object.defineProperty(exports, "writeHandshake", {
96
109
  }
97
110
  });
98
111
 
99
- //# sourceMappingURL=handshake-CRKcgkfN.cjs.map
112
+ //# sourceMappingURL=handshake-BOsVBPtn.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake-BOsVBPtn.cjs","names":[],"sources":["../src/app-env.ts","../src/handshake.ts"],"sourcesContent":["import type { InstanceHandle } from \"./types\";\n\n/**\n * Handle fields a template may reference: the flat string facts. Structured\n * fields (e.g. `platform`) are not env-var material — consumers read those\n * from the handle directly.\n */\ntype StringHandleField = {\n [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never;\n}[keyof InstanceHandle];\n\n/**\n * Declarative mapping from an app's env var names to InstanceHandle fields.\n * A template (not a function) so it can cross process boundaries: the\n * Playwright config serializes it into the app-runner's environment, where it\n * is applied to the handle read from the handshake file.\n */\nexport type AppEnvTemplate = Record<string, StringHandleField>;\n\n/**\n * The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own\n * template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`\n * names, for example.\n */\nexport const nextAppEnv: AppEnvTemplate = {\n ZITADEL_URL: \"baseUrl\",\n NEXT_PUBLIC_ZITADEL_PROJECT_ID: \"projectId\",\n ZITADEL_PROJECT_SECRET: \"projectSecret\",\n};\n\nexport function applyAppEnvTemplate(\n template: AppEnvTemplate,\n handle: InstanceHandle,\n): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [name, field] of Object.entries(template)) {\n const value = handle[field];\n if (typeof value !== \"string\" || value.length === 0) {\n // Fail instead of silently dropping the var: an app booted without one\n // of its env vars produces a much harder-to-read failure downstream.\n throw new Error(\n `app env template maps \"${name}\" to handle field \"${field}\", which the instance handle does not carry`,\n );\n }\n env[name] = value;\n }\n return env;\n}\n","import { readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nimport type { InstanceHandle, PlatformCredentials } from \"./types\";\n\n/**\n * The handshake file carries an InstanceHandle across process boundaries:\n * written by the script that boots + bootstraps the instance, read by\n * Playwright workers (fixtures) and the app dev-server wrapper.\n */\nexport async function writeHandshake(path: string, handle: InstanceHandle): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(handle, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function readHandshakeSync(path: string): InstanceHandle {\n const contents = readFileSync(path, \"utf8\");\n let value: unknown;\n try {\n value = JSON.parse(contents);\n } catch (error) {\n throw new Error(`handshake file ${path} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n return validateHandle(value, path);\n}\n\nexport async function waitForHandshake(path: string, timeoutMs = 60_000): Promise<InstanceHandle> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n return validateHandle(JSON.parse(await readFile(path, \"utf8\")), path);\n } catch (error) {\n if (Date.now() >= deadline) {\n throw new Error(\n `handshake file not readable within ${timeoutMs}ms: ${path} (${(error as Error).message})`,\n { cause: error },\n );\n }\n await sleep(250);\n }\n }\n}\n\nfunction validateHandle(value: unknown, source: string): InstanceHandle {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`handshake file ${source} is not an object`);\n }\n const handle = value as Partial<InstanceHandle>;\n for (const field of [\"baseUrl\", \"projectId\", \"projectSecret\", \"schemaId\"] as const) {\n if (typeof handle[field] !== \"string\" || handle[field].length === 0) {\n throw new Error(`handshake file ${source} is missing \"${field}\"`);\n }\n }\n if (!URL.canParse(handle.baseUrl as string)) {\n throw new Error(`handshake file ${source} has a malformed \"baseUrl\": ${handle.baseUrl}`);\n }\n if (handle.platform !== undefined) {\n validatePlatform(handle.platform, source);\n }\n return handle as InstanceHandle;\n}\n\n/**\n * The handshake is the cross-process contract, so a malformed platform block\n * must fail here with the field name, not later with a less actionable error.\n */\nfunction validatePlatform(value: unknown, source: string): void {\n const fail = (detail: string): never => {\n throw new Error(`handshake file ${source} has a malformed \"platform\" block: ${detail}`);\n };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(\"not an object\");\n }\n const platform = value as Partial<PlatformCredentials>;\n for (const field of [\"projectId\", \"publishableKey\"] as const) {\n if (typeof platform[field] !== \"string\" || platform[field].length === 0) {\n fail(`\"${field}\" is required`);\n }\n }\n // Unknown extra fields pass through on purpose: future platform\n // credentials join additively, and an older reader must not reject a\n // newer producer's handle.\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,aAA6B;CACxC,aAAa;CACb,gCAAgC;CAChC,wBAAwB;CACzB;AAED,SAAgB,oBACd,UACA,QACwB;CACxB,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,EAAE;EACpD,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAGhD,OAAM,IAAI,MACR,0BAA0B,KAAK,qBAAqB,MAAM,6CAC3D;AAEH,MAAI,QAAQ;;AAEd,QAAO;;;;;;;;;AClCT,eAAsB,eAAe,MAAc,QAAuC;AACxF,QAAA,GAAA,iBAAA,QAAA,GAAA,UAAA,SAAoB,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,QAAA,GAAA,iBAAA,WAAgB,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAO,CAAC;;AAGhF,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,YAAA,GAAA,QAAA,cAAwB,MAAM,OAAO;CAC3C,IAAI;AACJ,KAAI;AACF,UAAQ,KAAK,MAAM,SAAS;UACrB,OAAO;AACd,QAAM,IAAI,MAAM,kBAAkB,KAAK,0BAA2B,MAAgB,WAAW,EAC3F,OAAO,OACR,CAAC;;AAEJ,QAAO,eAAe,OAAO,KAAK;;AAGpC,eAAsB,iBAAiB,MAAc,YAAY,KAAiC;CAChG,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SACE,KAAI;AACF,SAAO,eAAe,KAAK,MAAM,OAAA,GAAA,iBAAA,UAAe,MAAM,OAAO,CAAC,EAAE,KAAK;UAC9D,OAAO;AACd,MAAI,KAAK,KAAK,IAAI,SAChB,OAAM,IAAI,MACR,sCAAsC,UAAU,MAAM,KAAK,IAAK,MAAgB,QAAQ,IACxF,EAAE,OAAO,OAAO,CACjB;AAEH,SAAA,GAAA,qBAAA,YAAY,IAAI;;;AAKtB,SAAS,eAAe,OAAgB,QAAgC;AACtE,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MAAM,kBAAkB,OAAO,mBAAmB;CAE9D,MAAM,SAAS;AACf,MAAK,MAAM,SAAS;EAAC;EAAW;EAAa;EAAiB;EAAW,CACvE,KAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,WAAW,EAChE,OAAM,IAAI,MAAM,kBAAkB,OAAO,eAAe,MAAM,GAAG;AAGrE,KAAI,CAAC,IAAI,SAAS,OAAO,QAAkB,CACzC,OAAM,IAAI,MAAM,kBAAkB,OAAO,8BAA8B,OAAO,UAAU;AAE1F,KAAI,OAAO,aAAa,KAAA,EACtB,kBAAiB,OAAO,UAAU,OAAO;AAE3C,QAAO;;;;;;AAOT,SAAS,iBAAiB,OAAgB,QAAsB;CAC9D,MAAM,QAAQ,WAA0B;AACtC,QAAM,IAAI,MAAM,kBAAkB,OAAO,qCAAqC,SAAS;;AAEzF,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,MAAK,gBAAgB;CAEvB,MAAM,WAAW;AACjB,MAAK,MAAM,SAAS,CAAC,aAAa,iBAAiB,CACjD,KAAI,OAAO,SAAS,WAAW,YAAY,SAAS,OAAO,WAAW,EACpE,MAAK,IAAI,MAAM,eAAe"}
@@ -62,9 +62,22 @@ function validateHandle(value, source) {
62
62
  "schemaId"
63
63
  ]) if (typeof handle[field] !== "string" || handle[field].length === 0) throw new Error(`handshake file ${source} is missing "${field}"`);
64
64
  if (!URL.canParse(handle.baseUrl)) throw new Error(`handshake file ${source} has a malformed "baseUrl": ${handle.baseUrl}`);
65
+ if (handle.platform !== void 0) validatePlatform(handle.platform, source);
65
66
  return handle;
66
67
  }
68
+ /**
69
+ * The handshake is the cross-process contract, so a malformed platform block
70
+ * must fail here with the field name, not later with a less actionable error.
71
+ */
72
+ function validatePlatform(value, source) {
73
+ const fail = (detail) => {
74
+ throw new Error(`handshake file ${source} has a malformed "platform" block: ${detail}`);
75
+ };
76
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail("not an object");
77
+ const platform = value;
78
+ for (const field of ["projectId", "publishableKey"]) if (typeof platform[field] !== "string" || platform[field].length === 0) fail(`"${field}" is required`);
79
+ }
67
80
  //#endregion
68
81
  export { nextAppEnv as a, applyAppEnvTemplate as i, waitForHandshake as n, writeHandshake as r, readHandshakeSync as t };
69
82
 
70
- //# sourceMappingURL=handshake-ClzWvG8z.mjs.map
83
+ //# sourceMappingURL=handshake-BnAPXC6s.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake-BnAPXC6s.mjs","names":["sleep"],"sources":["../src/app-env.ts","../src/handshake.ts"],"sourcesContent":["import type { InstanceHandle } from \"./types\";\n\n/**\n * Handle fields a template may reference: the flat string facts. Structured\n * fields (e.g. `platform`) are not env-var material — consumers read those\n * from the handle directly.\n */\ntype StringHandleField = {\n [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never;\n}[keyof InstanceHandle];\n\n/**\n * Declarative mapping from an app's env var names to InstanceHandle fields.\n * A template (not a function) so it can cross process boundaries: the\n * Playwright config serializes it into the app-runner's environment, where it\n * is applied to the handle read from the handshake file.\n */\nexport type AppEnvTemplate = Record<string, StringHandleField>;\n\n/**\n * The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own\n * template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`\n * names, for example.\n */\nexport const nextAppEnv: AppEnvTemplate = {\n ZITADEL_URL: \"baseUrl\",\n NEXT_PUBLIC_ZITADEL_PROJECT_ID: \"projectId\",\n ZITADEL_PROJECT_SECRET: \"projectSecret\",\n};\n\nexport function applyAppEnvTemplate(\n template: AppEnvTemplate,\n handle: InstanceHandle,\n): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [name, field] of Object.entries(template)) {\n const value = handle[field];\n if (typeof value !== \"string\" || value.length === 0) {\n // Fail instead of silently dropping the var: an app booted without one\n // of its env vars produces a much harder-to-read failure downstream.\n throw new Error(\n `app env template maps \"${name}\" to handle field \"${field}\", which the instance handle does not carry`,\n );\n }\n env[name] = value;\n }\n return env;\n}\n","import { readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nimport type { InstanceHandle, PlatformCredentials } from \"./types\";\n\n/**\n * The handshake file carries an InstanceHandle across process boundaries:\n * written by the script that boots + bootstraps the instance, read by\n * Playwright workers (fixtures) and the app dev-server wrapper.\n */\nexport async function writeHandshake(path: string, handle: InstanceHandle): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(handle, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function readHandshakeSync(path: string): InstanceHandle {\n const contents = readFileSync(path, \"utf8\");\n let value: unknown;\n try {\n value = JSON.parse(contents);\n } catch (error) {\n throw new Error(`handshake file ${path} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n return validateHandle(value, path);\n}\n\nexport async function waitForHandshake(path: string, timeoutMs = 60_000): Promise<InstanceHandle> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n return validateHandle(JSON.parse(await readFile(path, \"utf8\")), path);\n } catch (error) {\n if (Date.now() >= deadline) {\n throw new Error(\n `handshake file not readable within ${timeoutMs}ms: ${path} (${(error as Error).message})`,\n { cause: error },\n );\n }\n await sleep(250);\n }\n }\n}\n\nfunction validateHandle(value: unknown, source: string): InstanceHandle {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`handshake file ${source} is not an object`);\n }\n const handle = value as Partial<InstanceHandle>;\n for (const field of [\"baseUrl\", \"projectId\", \"projectSecret\", \"schemaId\"] as const) {\n if (typeof handle[field] !== \"string\" || handle[field].length === 0) {\n throw new Error(`handshake file ${source} is missing \"${field}\"`);\n }\n }\n if (!URL.canParse(handle.baseUrl as string)) {\n throw new Error(`handshake file ${source} has a malformed \"baseUrl\": ${handle.baseUrl}`);\n }\n if (handle.platform !== undefined) {\n validatePlatform(handle.platform, source);\n }\n return handle as InstanceHandle;\n}\n\n/**\n * The handshake is the cross-process contract, so a malformed platform block\n * must fail here with the field name, not later with a less actionable error.\n */\nfunction validatePlatform(value: unknown, source: string): void {\n const fail = (detail: string): never => {\n throw new Error(`handshake file ${source} has a malformed \"platform\" block: ${detail}`);\n };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(\"not an object\");\n }\n const platform = value as Partial<PlatformCredentials>;\n for (const field of [\"projectId\", \"publishableKey\"] as const) {\n if (typeof platform[field] !== \"string\" || platform[field].length === 0) {\n fail(`\"${field}\" is required`);\n }\n }\n // Unknown extra fields pass through on purpose: future platform\n // credentials join additively, and an older reader must not reject a\n // newer producer's handle.\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,aAA6B;CACxC,aAAa;CACb,gCAAgC;CAChC,wBAAwB;CACzB;AAED,SAAgB,oBACd,UACA,QACwB;CACxB,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,EAAE;EACpD,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAGhD,OAAM,IAAI,MACR,0BAA0B,KAAK,qBAAqB,MAAM,6CAC3D;AAEH,MAAI,QAAQ;;AAEd,QAAO;;;;;;;;;AClCT,eAAsB,eAAe,MAAc,QAAuC;AACxF,OAAM,MAAM,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,OAAM,UAAU,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAO,CAAC;;AAGhF,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,WAAW,aAAa,MAAM,OAAO;CAC3C,IAAI;AACJ,KAAI;AACF,UAAQ,KAAK,MAAM,SAAS;UACrB,OAAO;AACd,QAAM,IAAI,MAAM,kBAAkB,KAAK,0BAA2B,MAAgB,WAAW,EAC3F,OAAO,OACR,CAAC;;AAEJ,QAAO,eAAe,OAAO,KAAK;;AAGpC,eAAsB,iBAAiB,MAAc,YAAY,KAAiC;CAChG,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SACE,KAAI;AACF,SAAO,eAAe,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC,EAAE,KAAK;UAC9D,OAAO;AACd,MAAI,KAAK,KAAK,IAAI,SAChB,OAAM,IAAI,MACR,sCAAsC,UAAU,MAAM,KAAK,IAAK,MAAgB,QAAQ,IACxF,EAAE,OAAO,OAAO,CACjB;AAEH,QAAMA,WAAM,IAAI;;;AAKtB,SAAS,eAAe,OAAgB,QAAgC;AACtE,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MAAM,kBAAkB,OAAO,mBAAmB;CAE9D,MAAM,SAAS;AACf,MAAK,MAAM,SAAS;EAAC;EAAW;EAAa;EAAiB;EAAW,CACvE,KAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,WAAW,EAChE,OAAM,IAAI,MAAM,kBAAkB,OAAO,eAAe,MAAM,GAAG;AAGrE,KAAI,CAAC,IAAI,SAAS,OAAO,QAAkB,CACzC,OAAM,IAAI,MAAM,kBAAkB,OAAO,8BAA8B,OAAO,UAAU;AAE1F,KAAI,OAAO,aAAa,KAAA,EACtB,kBAAiB,OAAO,UAAU,OAAO;AAE3C,QAAO;;;;;;AAOT,SAAS,iBAAiB,OAAgB,QAAsB;CAC9D,MAAM,QAAQ,WAA0B;AACtC,QAAM,IAAI,MAAM,kBAAkB,OAAO,qCAAqC,SAAS;;AAEzF,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,MAAK,gBAAgB;CAEvB,MAAM,WAAW;AACjB,MAAK,MAAM,SAAS,CAAC,aAAa,iBAAiB,CACjD,KAAI,OAAO,SAAS,WAAW,YAAY,SAAS,OAAO,WAAW,EACpE,MAAK,IAAI,MAAM,eAAe"}
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_src = require("./src-I0KAh1zU.cjs");
3
- const require_handshake = require("./handshake-CRKcgkfN.cjs");
2
+ const require_src = require("./src-DtCJTEId.cjs");
3
+ const require_handshake = require("./handshake-BOsVBPtn.cjs");
4
4
  exports.SESSION_COOKIE_NAME = require_src.SESSION_COOKIE_NAME;
5
5
  exports.applyAppEnvTemplate = require_handshake.applyAppEnvTemplate;
6
6
  exports.bootLocalServer = require_src.bootLocalServer;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Identity, c as LocalZitadelRuntime, d as SeedUserInput, f as SeedUsersTemplate, i as ConnectedZitadel, l as MintedSession, m as SessionCookie, n as applyAppEnvTemplate, o as InstanceHandle, p as SeededUser, r as nextAppEnv, s as LocalZitadel, t as AppEnvTemplate, u as SeedSessionInput } from "./app-env-Btmfcflb.mjs";
1
+ import { a as Identity, c as LocalZitadelRuntime, d as SeedSessionInput, f as SeedUserInput, h as SessionCookie, i as ConnectedZitadel, l as MintedSession, m as SeededUser, n as applyAppEnvTemplate, o as InstanceHandle, p as SeedUsersTemplate, r as nextAppEnv, s as LocalZitadel, t as AppEnvTemplate, u as PlatformCredentials } from "./app-env-DlrV_ePR.mjs";
2
2
  import { ZitadelClient } from "@zitadel/api/client";
3
3
  import { SetupPreset, SetupUseCase } from "@zitadel/config/defaults";
4
4
 
@@ -88,5 +88,5 @@ declare function connectZitadel(handle: InstanceHandle): ConnectedZitadel;
88
88
  */
89
89
  declare function startLocalZitadel(options?: StartLocalZitadelOptions): Promise<LocalZitadel>;
90
90
  //#endregion
91
- export { type AppEnvTemplate, type BootServerOptions, type BootedServer, type BootstrapProjectOptions, type BootstrappedProject, type ConnectedZitadel, type Identity, type InstanceHandle, type LocalZitadel, type LocalZitadelRuntime, type MintedSession, SESSION_COOKIE_NAME, type SeedSessionInput, type SeedUserInput, type SeedUsersTemplate, type SeededUser, type SessionCookie, StartLocalZitadelOptions, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
91
+ export { type AppEnvTemplate, type BootServerOptions, type BootedServer, type BootstrapProjectOptions, type BootstrappedProject, type ConnectedZitadel, type Identity, type InstanceHandle, type LocalZitadel, type LocalZitadelRuntime, type MintedSession, type PlatformCredentials, SESSION_COOKIE_NAME, type SeedSessionInput, type SeedUserInput, type SeedUsersTemplate, type SeededUser, type SessionCookie, StartLocalZitadelOptions, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
92
92
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake, r as writeHandshake, t as readHandshakeSync } from "./handshake-ClzWvG8z.mjs";
2
- import { a as bootstrapProject, i as bootLocalServer, n as startLocalZitadel, r as SESSION_COOKIE_NAME, t as connectZitadel } from "./src-9dFjTIAw.mjs";
1
+ import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake, r as writeHandshake, t as readHandshakeSync } from "./handshake-BnAPXC6s.mjs";
2
+ import { a as bootstrapProject, i as bootLocalServer, n as startLocalZitadel, r as SESSION_COOKIE_NAME, t as connectZitadel } from "./src-Dkt0HHu8.mjs";
3
3
  export { SESSION_COOKIE_NAME, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_src = require("./src-I0KAh1zU.cjs");
3
- const require_handshake = require("./handshake-CRKcgkfN.cjs");
2
+ const require_src = require("./src-DtCJTEId.cjs");
3
+ const require_handshake = require("./handshake-BOsVBPtn.cjs");
4
4
  const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
5
5
  let node_fs = require("node:fs");
6
6
  let node_url = require("node:url");
@@ -1,4 +1,4 @@
1
- import { a as Identity, d as SeedUserInput, f as SeedUsersTemplate, i as ConnectedZitadel, l as MintedSession, n as applyAppEnvTemplate, o as InstanceHandle, p as SeededUser, r as nextAppEnv, t as AppEnvTemplate, u as SeedSessionInput } from "./app-env-Btmfcflb.mjs";
1
+ import { a as Identity, d as SeedSessionInput, f as SeedUserInput, i as ConnectedZitadel, l as MintedSession, m as SeededUser, n as applyAppEnvTemplate, o as InstanceHandle, p as SeedUsersTemplate, r as nextAppEnv, t as AppEnvTemplate } from "./app-env-DlrV_ePR.mjs";
2
2
  import { SetupPreset, SetupUseCase } from "@zitadel/config/defaults";
3
3
  import * as _$_playwright_test0 from "@playwright/test";
4
4
  import { Locator, Page, PlaywrightTestConfig, expect } from "@playwright/test";
@@ -1,5 +1,5 @@
1
- import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-ClzWvG8z.mjs";
2
- import { t as connectZitadel } from "./src-9dFjTIAw.mjs";
1
+ import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-BnAPXC6s.mjs";
2
+ import { t as connectZitadel } from "./src-Dkt0HHu8.mjs";
3
3
  import { n as HANDSHAKE_ENV, r as SUPERVISOR_CONFIG_ENV, t as APP_RUNNER_CONFIG_ENV } from "./orchestration-C640_1Lg.mjs";
4
4
  import { extname, isAbsolute, join } from "node:path";
5
5
  import { existsSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { a as nextAppEnv, i as applyAppEnvTemplate } from "./handshake-ClzWvG8z.mjs";
1
+ import { a as nextAppEnv, i as applyAppEnvTemplate } from "./handshake-BnAPXC6s.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { createZitadelClient } from "@zitadel/api/client";
4
4
  import { DEFAULT_FLOW_SCHEMA_URI, getDefaultHumanUserSchema, getDefaultLoginFlow } from "@zitadel/config/defaults";
@@ -514,4 +514,4 @@ async function startLocalZitadel(options = {}) {
514
514
  //#endregion
515
515
  export { bootstrapProject as a, bootLocalServer as i, startLocalZitadel as n, SESSION_COOKIE_NAME as r, connectZitadel as t };
516
516
 
517
- //# sourceMappingURL=src-9dFjTIAw.mjs.map
517
+ //# sourceMappingURL=src-Dkt0HHu8.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"src-9dFjTIAw.mjs","names":[],"sources":["../src/bootstrap.ts","../src/cli.ts","../src/envelope.ts","../src/ports.ts","../src/lifecycle.ts","../src/seed.ts","../src/session.ts","../src/index.ts"],"sourcesContent":["import { createZitadelClient, type ZitadelClient } from \"@zitadel/api/client\";\nimport {\n DEFAULT_FLOW_SCHEMA_URI,\n getDefaultHumanUserSchema,\n getDefaultLoginFlow,\n type SetupPreset,\n type SetupUseCase,\n} from \"@zitadel/config/defaults\";\n\nexport interface BootstrapProjectOptions {\n baseUrl: string;\n projectName?: string;\n /**\n * Origins of the apps that will proxy to this instance. The backend's\n * origin check rejects forwarded requests from unregistered origins.\n */\n appOrigins?: string[];\n preset?: SetupPreset;\n useCase?: SetupUseCase;\n}\n\nexport interface BootstrappedProject {\n projectId: string;\n projectSecret: string;\n previewSecret?: string;\n schemaId: string;\n flowId: string;\n}\n\nconst DEFAULT_PROJECT_NAME = \"zitadel-testing\";\n\n/**\n * Server-side half of `zitadel setup`, without any file scaffolding:\n * `POST /projects` is unauthenticated and mints the projectSecret used as the\n * bearer for everything else; the schema is uploaded without `$id` so the\n * server assigns an opaque id, which the flow must then reference.\n */\nexport async function bootstrapProject(\n options: BootstrapProjectOptions,\n): Promise<BootstrappedProject> {\n const { baseUrl } = options;\n const unauthenticated = createZitadelClient({ baseUrl });\n const project = (await unauthenticated.createProject({\n name: options.projectName ?? DEFAULT_PROJECT_NAME,\n preview_origins: options.appOrigins ?? [],\n seed_defaults: false,\n } as Parameters<ZitadelClient[\"createProject\"]>[0])) as Record<string, unknown>;\n const projectId = requireString(project.id, \"project id\");\n const projectSecret = requireString(project.project_secret, \"project secret\");\n const previewSecret =\n typeof project.preview_secret === \"string\" ? project.preview_secret : undefined;\n\n const client = createZitadelClient({ baseUrl, token: projectSecret });\n\n const { $id: _templateId, ...schemaBody } = getDefaultHumanUserSchema({\n preset: options.preset,\n useCase: options.useCase,\n }) as { $id?: string } & Record<string, unknown>;\n void _templateId;\n const schema = (await client.createSchema(\n schemaBody as Parameters<ZitadelClient[\"createSchema\"]>[0],\n { project_id: projectId },\n )) as Record<string, unknown>;\n const schemaId = requireString(schema.id, \"schema id\");\n\n const flowBody = getDefaultLoginFlow({\n userSchemaUrl: schemaId,\n preset: options.preset,\n useCase: options.useCase,\n });\n const flow = (await client.createFlowDefinition({\n project_id: projectId,\n schema_uri: DEFAULT_FLOW_SCHEMA_URI,\n flow_definition: flowBody,\n } as Parameters<ZitadelClient[\"createFlowDefinition\"]>[0])) as Record<string, unknown>;\n const flowId = requireString(flow.id, \"flow definition id\");\n\n return { projectId, projectSecret, previewSecret, schemaId, flowId };\n}\n\nexport function requireString(value: unknown, label: string): string {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n throw new Error(`Missing ${label} in server response.`);\n}\n","import { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\nexport interface RunCliOptions {\n args: string[];\n env?: NodeJS.ProcessEnv;\n /** Test seam / escape hatch: alternative CLI entry script. */\n bin?: string;\n timeoutMs?: number;\n}\n\nexport interface RunCliResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\nexport function resolveCliBin(): string {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve(\"@zitadel/cli/package.json\");\n const pkg = require(pkgPath) as { bin?: Record<string, string> };\n const rel = pkg.bin?.zitadel;\n if (!rel) {\n throw new Error(\"@zitadel/cli does not declare a `zitadel` bin entry\");\n }\n return join(dirname(pkgPath), rel);\n}\n\nexport function runCli(options: RunCliOptions): Promise<RunCliResult> {\n const bin = options.bin ?? resolveCliBin();\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [bin, ...options.args], {\n env: { ...process.env, ...options.env },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.setEncoding(\"utf8\");\n child.stderr.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n const timer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n reject(\n new Error(\n `zitadel ${options.args[0] ?? \"\"} timed out after ${timeoutMs}ms\\n${tail(stderr)}`,\n ),\n );\n }, timeoutMs);\n timer.unref();\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n reject(error);\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n resolve({ exitCode: code ?? -1, stdout, stderr });\n });\n });\n}\n\nexport function tail(text: string, lines = 20): string {\n return text.split(\"\\n\").slice(-lines).join(\"\\n\").trim();\n}\n","export interface CliEnvelope<TData> {\n cli_version?: string;\n command?: string;\n source?: string;\n status: string;\n data: TData;\n warnings?: string[];\n /** Error envelopes (`status: \"error\"`) carry remediation guidance. */\n code?: string;\n message?: string;\n hint?: string;\n next_commands?: string[];\n}\n\n/**\n * Render an error envelope's remediation fields for humans — the CLI's\n * `hint`/`next_commands` are the actionable part of a failure (e.g. \"Reinstall\n * @zitadel/cli so npm can install @zitadel/server\"), so surface them instead\n * of a raw stdout dump. Returns undefined when the envelope has no message.\n */\nexport function describeEnvelopeError(envelope: CliEnvelope<unknown>): string | undefined {\n if (typeof envelope.message !== \"string\" || envelope.message.length === 0) {\n return undefined;\n }\n const lines = [envelope.code ? `${envelope.code}: ${envelope.message}` : envelope.message];\n if (envelope.hint) {\n lines.push(`hint: ${envelope.hint}`);\n }\n if (envelope.next_commands && envelope.next_commands.length > 0) {\n lines.push(`next: ${envelope.next_commands.join(\" | \")}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport interface StartEnvelopeData {\n runtime: {\n backend: string;\n pid: number;\n port: number;\n data_dir: string;\n log_path: string;\n };\n urls: {\n api: string;\n console: string;\n login: string;\n };\n}\n\nexport function parseCliEnvelope<TData>(stdout: string, context: string): CliEnvelope<TData> {\n const start = stdout.indexOf(\"{\");\n const end = stdout.lastIndexOf(\"}\");\n if (start === -1 || end <= start) {\n throw new Error(\n `${context}: expected a JSON envelope on stdout, got:\\n${stdout.trim() || \"(empty)\"}`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout.slice(start, end + 1));\n } catch (error) {\n throw new Error(\n `${context}: failed to parse JSON envelope: ${(error as Error).message}\\n${stdout.trim()}`,\n { cause: error },\n );\n }\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n typeof (parsed as { status?: unknown }).status !== \"string\"\n ) {\n throw new Error(`${context}: stdout JSON is not a CLI envelope:\\n${stdout.trim()}`);\n }\n return parsed as CliEnvelope<TData>;\n}\n","import { createServer } from \"node:net\";\n\n/**\n * Ask the OS for a free TCP port. The port is released before returning, so a\n * racing process could grab it; the CLI's own preflight surfaces that as\n * E_PORT_IN_USE, which is loud rather than corrupting.\n */\nexport function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.unref();\n server.on(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n server.close();\n reject(new Error(\"could not determine a free port\"));\n return;\n }\n const { port } = address;\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(port);\n });\n });\n });\n}\n","import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { runCli, tail, type RunCliResult } from \"./cli\";\nimport {\n describeEnvelopeError,\n parseCliEnvelope,\n type CliEnvelope,\n type StartEnvelopeData,\n} from \"./envelope\";\nimport { getFreePort } from \"./ports\";\nimport type { LocalZitadelRuntime } from \"./types\";\n\nexport interface BootServerOptions {\n /** TCP port for the instance; defaults to an OS-assigned free port. */\n port?: number;\n /**\n * State directory. Defaults to a fresh temp dir that is removed on stop;\n * a caller-provided dir is never removed.\n */\n dir?: string;\n /** Forwarded as ZITADEL_SERVER_BINARY (in-repo runs use dist/server/nextgen). */\n serverBinary?: string;\n /** Keep the owned temp dir after stop (debugging). */\n keep?: boolean;\n /** Test seam: alternative CLI entry script. */\n cliBin?: string;\n timeoutMs?: number;\n}\n\nexport interface BootedServer {\n baseUrl: string;\n runtime: LocalZitadelRuntime;\n stop(): Promise<void>;\n}\n\n/**\n * Boot an ephemeral local server by shelling out to `zitadel start` and parse\n * its JSON envelope. The CLI owns the subtle parts (port preflight, health\n * wait, process-group stop), so this module stays a thin adapter; swapping it\n * for direct library calls later must not change the shape returned here.\n */\nexport async function bootLocalServer(options: BootServerOptions = {}): Promise<BootedServer> {\n const ownsDir = options.dir === undefined;\n const dir = options.dir ?? (await mkdtemp(join(tmpdir(), \"zitadel-testing-\")));\n const port = options.port ?? (await getFreePort());\n const env: NodeJS.ProcessEnv = {};\n if (options.serverBinary) {\n env.ZITADEL_SERVER_BINARY = options.serverBinary;\n }\n\n const result = await runCli({\n args: [\"start\", \"--port\", String(port), \"--non-interactive\", \"--json\", \"-c\", dir],\n bin: options.cliBin,\n env,\n timeoutMs: options.timeoutMs,\n });\n if (result.exitCode !== 0) {\n // Keep the dir on failure: server.log inside it is the diagnostic.\n throw new Error(\n `zitadel start exited with code ${result.exitCode}.\\n` +\n `${failureDetail(result)}\\n` +\n `state dir kept for inspection: ${dir}`,\n );\n }\n const stopViaCli = async (): Promise<void> => {\n const stopResult = await runCli({\n args: [\"stop\", \"--non-interactive\", \"--json\", \"-c\", dir],\n bin: options.cliBin,\n env,\n timeoutMs: options.timeoutMs,\n });\n if (stopResult.exitCode !== 0) {\n throw new Error(\n `zitadel stop exited with code ${stopResult.exitCode}.\\n` +\n `${failureDetail(stopResult)}\\n` +\n `state dir kept for inspection: ${dir}`,\n );\n }\n };\n\n let envelope: CliEnvelope<StartEnvelopeData>;\n try {\n envelope = parseCliEnvelope<StartEnvelopeData>(result.stdout, \"zitadel start\");\n if (envelope.status !== \"ok\") {\n throw new Error(\n `zitadel start reported status \"${envelope.status}\":\\n` +\n `${describeEnvelopeError(envelope) ?? tail(result.stdout)}`,\n );\n }\n } catch (error) {\n const startError = new Error(\n `zitadel start produced unusable output.\\n` +\n `reason: ${error instanceof Error ? error.message : String(error)}\\n` +\n `stdout: ${tail(result.stdout) || \"(empty)\"}\\n` +\n `stderr: ${tail(result.stderr) || \"(empty)\"}\\n` +\n `state dir kept for inspection: ${dir}`,\n { cause: error },\n );\n // start exited 0, so a server may well be running despite the unusable\n // output — stop it instead of orphaning it.\n try {\n await stopViaCli();\n } catch (stopError) {\n // Both errors are preserved in AggregateError.errors, which the rule\n // below cannot model.\n // oxlint-disable-next-line preserve-caught-error\n throw new AggregateError(\n [startError, stopError],\n `${startError.message}\\nStopping the possibly-running instance also failed: ${\n stopError instanceof Error ? stopError.message : String(stopError)\n }`,\n );\n }\n throw startError;\n }\n\n const { runtime, urls } = envelope.data;\n const runStop = async (): Promise<void> => {\n await stopViaCli();\n if (ownsDir && !options.keep) {\n await rm(dir, { recursive: true, force: true });\n }\n };\n // Memoize the in-flight stop so concurrent callers await the same cleanup,\n // and reset on failure so a failed stop can be retried instead of silently\n // leaving the server behind.\n let stopPromise: Promise<void> | undefined;\n const stop = (): Promise<void> => {\n stopPromise ??= runStop().catch((error: unknown) => {\n stopPromise = undefined;\n throw error;\n });\n return stopPromise;\n };\n\n return {\n baseUrl: urls.api,\n runtime: {\n port: runtime.port,\n pid: runtime.pid,\n dir,\n logPath: runtime.log_path,\n },\n stop,\n };\n}\n\n/**\n * A failed CLI run usually still prints an error envelope; its\n * message/hint/next_commands beat raw output tails (e.g. a fresh install\n * missing @zitadel/server gets \"Reinstall @zitadel/cli\" instead of a stack).\n */\nfunction failureDetail(result: RunCliResult): string {\n try {\n const described = describeEnvelopeError(parseCliEnvelope<unknown>(result.stdout, \"zitadel\"));\n if (described) {\n return described;\n }\n } catch {\n // stdout carried no envelope; fall back to the raw tails.\n }\n return `stdout: ${tail(result.stdout) || \"(empty)\"}\\nstderr: ${tail(result.stderr) || \"(empty)\"}`;\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport type { ZitadelClient } from \"@zitadel/api/client\";\n\nimport { requireString } from \"./bootstrap\";\nimport type { Identity, SeededUser, SeedUserInput, SeedUsersTemplate } from \"./types\";\n\nexport interface SeedContext {\n projectId: string;\n schemaId: string;\n}\n\n/**\n * A unique unused email + password. Nothing is created on the instance —\n * this is the input for registration-flow specs, which must prove the flow\n * creates the user.\n */\nexport function identity(): Identity {\n return {\n email: `e2e-${randomUUID().slice(0, 8)}@example.com`,\n password: `Pw!${randomUUID()}`,\n };\n}\n\n/**\n * Create a user that can immediately complete the password login flow:\n * `POST /users` (the body carries `schema: <schema id>` and the schema-defined\n * content under `attributes`) followed by `PUT /users/{id}/password` with\n * `is_change_required: false`.\n *\n * Defaults mint a unique email per call (email is x-unique per project), which\n * is what makes per-test seeding parallel-safe on a shared instance.\n */\nexport async function seedUser(\n client: ZitadelClient,\n context: SeedContext,\n input: SeedUserInput = {},\n): Promise<SeededUser> {\n const fresh = identity();\n const email = input.email ?? fresh.email;\n const password = input.password ?? fresh.password;\n // `email` wins over the templated attributes: the returned SeededUser must\n // never disagree with what was actually created, since a silently overridden\n // email would yield credentials that cannot log in.\n const user = (await client.createUser(\n {\n schema: context.schemaId,\n attributes: { ...input.attributes, email },\n },\n { project_id: context.projectId },\n )) as Record<string, unknown>;\n const id = requireString(user.id, \"user id\");\n await client.setUserPassword(id, { password, is_change_required: false });\n return { id, email, password };\n}\n\n/**\n * Seed `count` users sequentially. The template makes fixture data\n * deterministic per index (stable emails/names keep screenshot diffs about\n * code, not reshuffled data — the `console:dev-real` pattern); untemplated\n * fields fall back to the unique defaults. Name-like attributes need a\n * schema that declares them (`useCase: \"consumer\"` or wider).\n */\nexport async function seedUsers(\n client: ZitadelClient,\n context: SeedContext,\n count: number,\n template: SeedUsersTemplate = {},\n): Promise<SeededUser[]> {\n const users: SeededUser[] = [];\n for (let index = 0; index < count; index += 1) {\n users.push(\n await seedUser(client, context, {\n email: template.email?.(index),\n password: template.password?.(index),\n attributes: template.attributes?.(index),\n }),\n );\n }\n return users;\n}\n","import type { ZitadelClient } from \"@zitadel/api/client\";\nimport type { CreateFlow201, CreateFlow201StepFieldsItem } from \"@zitadel/api/generated/model\";\n\nimport type { SeedContext } from \"./seed\";\nimport type { InstanceHandle, MintedSession, SeededUser } from \"./types\";\n\n/** Mirrors the server's session cookie (internal/api/session.go). */\nexport const SESSION_COOKIE_NAME = \"__nextgen_session\";\n\nconst MAX_FLOW_STEPS = 6;\n\nexport interface MintSessionOptions {\n /** Forwarded to `POST /flow`; the project's default flow when omitted. */\n flowDefinitionName?: string;\n /** Origin header for flow calls (the project's origin check enforces it). */\n origin?: string;\n}\n\n/**\n * Drive the real login flow headlessly for a seeded password user and\n * exchange the terminal handoff for a session: exactly what `<zitadel-login>`\n * does, minus the rendering. Supports flows whose steps only ask for the\n * user's email and password (the shipped `password-first` presets); any step\n * demanding more — a challenge, an unknown field — fails loudly by design.\n *\n * Flow calls use raw fetch instead of the typed client because the flow is\n * stateless through the sealed `_zflow` cookie (internal/api/flow.go): every\n * response re-seals the flow state into Set-Cookie, and submits are rejected\n * without it. Browsers round-trip it implicitly; here a one-cookie jar does.\n */\nexport async function mintSession(\n client: ZitadelClient,\n handle: Pick<InstanceHandle, \"baseUrl\" | \"projectSecret\">,\n context: SeedContext,\n user: SeededUser,\n options: MintSessionOptions = {},\n): Promise<MintedSession> {\n const values: Record<string, string> = { email: user.email, password: user.password };\n const jar = new FlowCookieJar();\n const origin = options.origin;\n\n let response = await flowFetch(handle, jar, origin, \"/flow\", {\n project_id: context.projectId,\n purpose: \"login\",\n ...(options.flowDefinitionName ? { flow_definition_name: options.flowDefinitionName } : {}),\n });\n\n for (let hop = 0; hop < MAX_FLOW_STEPS; hop += 1) {\n if (response.handoff_token) {\n const exchanged = await client.exchangeHandoff(\n { handoff_token: response.handoff_token },\n { project_id: context.projectId },\n );\n return {\n user,\n sessionToken: exchanged.session_token,\n expiresAt: exchanged.session.expires_at,\n cookie: {\n name: SESSION_COOKIE_NAME,\n value: exchanged.session_token,\n httpOnly: true,\n secure: true,\n sameSite: \"Lax\",\n path: \"/\",\n },\n };\n }\n response = await flowFetch(handle, jar, origin, `/flow/${encodeURIComponent(response.id)}/submit`, {\n session_token: response.session_token,\n action: \"submit\",\n fields: collectFields(response, values),\n });\n }\n\n throw new Error(\n `seed.session: flow did not complete within ${MAX_FLOW_STEPS} steps ` +\n `(last step: ${describeStep(response)}).`,\n );\n}\n\n/** One-cookie jar for the sealed `_zflow` flow-state cookie. */\nclass FlowCookieJar {\n private cookie: string | undefined;\n\n absorb(response: Response): void {\n for (const raw of response.headers.getSetCookie()) {\n const [pair] = raw.split(\";\", 1);\n if (pair?.startsWith(\"_zflow=\")) {\n this.cookie = pair;\n }\n }\n }\n\n header(): Record<string, string> {\n return this.cookie ? { cookie: this.cookie } : {};\n }\n}\n\nasync function flowFetch(\n handle: Pick<InstanceHandle, \"baseUrl\" | \"projectSecret\">,\n jar: FlowCookieJar,\n origin: string | undefined,\n path: string,\n body: Record<string, unknown>,\n): Promise<CreateFlow201> {\n const response = await fetch(`${handle.baseUrl}${path}`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${handle.projectSecret}`,\n // The project's origin allowlist applies to flow calls; send the app\n // origin the way a browser request through the app would carry it.\n ...(origin ? { origin } : {}),\n ...jar.header(),\n },\n body: JSON.stringify(body),\n });\n jar.absorb(response);\n const parsed = (await response.json().catch(() => undefined)) as CreateFlow201 | undefined;\n if (!response.ok || !parsed) {\n const detail =\n parsed && typeof parsed === \"object\" ? ` — ${JSON.stringify(parsed)}` : \"\";\n // An origin-allowlist rejection without an Origin header is a\n // configuration gap, not a flow problem — say how to close it. (No eager\n // check: a project with an empty allowlist may accept originless calls.)\n const hint =\n !origin && /origin/i.test(detail)\n ? \"\\nNo Origin header was sent: pass `origin` to seedSession() (the Playwright \" +\n \"fixtures pass the suite's baseURL) or `appOrigins` to startLocalZitadel().\"\n : \"\";\n throw new Error(`seed.session: POST ${path} returned ${response.status}${detail}${hint}`);\n }\n return parsed;\n}\n\n/**\n * Fill exactly the fields the current step declares — the orchestrator's\n * convention — from the known email/password values. An unknown required\n * field means this flow needs more than a password login can provide.\n */\nfunction collectFields(response: CreateFlow201, values: Record<string, string>): Record<string, string> {\n const fields: Record<string, string> = {};\n for (const field of response.step.fields ?? []) {\n const value = values[fieldKey(field)];\n if (value === undefined) {\n throw new Error(\n `seed.session supports password flows only; step ${describeStep(response)} ` +\n `declares field \"${field.name}\", which the kit cannot fill. ` +\n `Log in through the UI for flows with additional factors.`,\n );\n }\n fields[field.name] = value;\n }\n return fields;\n}\n\n/**\n * Steps name credential fields with schema pointers (e.g.\n * `x-auth-methods#password`); match on the trailing segment so the value map\n * stays the plain `{ email, password }` a caller thinks in.\n */\nfunction fieldKey(field: CreateFlow201StepFieldsItem): string {\n const name = field.name;\n const tail = name.split(/[#/.]/).at(-1) ?? name;\n return tail.toLowerCase();\n}\n\nfunction describeStep(response: CreateFlow201): string {\n const name = response.step.name ?? \"(unnamed)\";\n const declared = (response.step.fields ?? []).map((field) => field.name).join(\", \");\n return `\"${name}\"${declared ? ` [fields: ${declared}]` : \"\"}`;\n}\n","import { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nimport { bootstrapProject, type BootstrapProjectOptions } from \"./bootstrap\";\nimport { bootLocalServer, type BootServerOptions } from \"./lifecycle\";\nimport { identity, seedUser, seedUsers } from \"./seed\";\nimport { mintSession } from \"./session\";\nimport type { ConnectedZitadel, InstanceHandle, LocalZitadel } from \"./types\";\n\nexport type StartLocalZitadelOptions = BootServerOptions &\n Omit<BootstrapProjectOptions, \"baseUrl\">;\n\n/**\n * Attach to an already-bootstrapped instance/project. Lifecycle-free on\n * purpose: this is the entry point for Playwright workers (via the handshake\n * file) and, later, for seeding remote instances.\n */\nexport function connectZitadel(handle: InstanceHandle): ConnectedZitadel {\n const api = createZitadelClient({ baseUrl: handle.baseUrl, token: handle.projectSecret });\n const context = { projectId: handle.projectId, schemaId: handle.schemaId };\n const connected: ConnectedZitadel = {\n handle,\n api,\n // The Next-shaped convenience view; other frameworks apply their own\n // template to `handle` (see AppEnvTemplate).\n appEnv: applyAppEnvTemplate(nextAppEnv, handle),\n seedUser: (input) => seedUser(api, context, input),\n seedUsers: (count, template) => seedUsers(api, context, count, template),\n identity,\n seedSession: async (input = {}) => {\n const { user: existing, flowDefinitionName, origin, ...userInput } = input;\n const user = existing ?? (await seedUser(api, context, userInput));\n return mintSession(api, handle, context, user, {\n flowDefinitionName,\n origin: origin ?? handle.appOrigin,\n });\n },\n };\n return connected;\n}\n\n/**\n * Boot an ephemeral local instance (binary runtime + SQLite by default, no\n * Docker) and bootstrap a project + default schema + login flow on it. The\n * result can seed loginable password users immediately.\n */\nexport async function startLocalZitadel(\n options: StartLocalZitadelOptions = {},\n): Promise<LocalZitadel> {\n const server = await bootLocalServer(options);\n let bootstrapped;\n try {\n bootstrapped = await bootstrapProject({\n baseUrl: server.baseUrl,\n projectName: options.projectName,\n appOrigins: options.appOrigins,\n preset: options.preset,\n useCase: options.useCase,\n });\n } catch (error) {\n try {\n await server.stop();\n } catch (stopError) {\n // Both errors are preserved in AggregateError.errors, which the rule\n // below cannot model.\n // oxlint-disable-next-line preserve-caught-error\n throw new AggregateError(\n [error, stopError],\n \"bootstrap failed, and stopping the booted instance also failed\",\n );\n }\n throw error;\n }\n const handle: InstanceHandle = {\n baseUrl: server.baseUrl,\n projectId: bootstrapped.projectId,\n projectSecret: bootstrapped.projectSecret,\n schemaId: bootstrapped.schemaId,\n previewSecret: bootstrapped.previewSecret,\n appOrigin: options.appOrigins?.[0],\n };\n return {\n ...connectZitadel(handle),\n runtime: server.runtime,\n stop: server.stop,\n [Symbol.asyncDispose]: server.stop,\n };\n}\n\nexport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nexport type { AppEnvTemplate } from \"./app-env\";\nexport { bootstrapProject } from \"./bootstrap\";\nexport type { BootstrapProjectOptions, BootstrappedProject } from \"./bootstrap\";\nexport { readHandshakeSync, waitForHandshake, writeHandshake } from \"./handshake\";\nexport { bootLocalServer } from \"./lifecycle\";\nexport type { BootedServer, BootServerOptions } from \"./lifecycle\";\nexport { SESSION_COOKIE_NAME } from \"./session\";\nexport type {\n ConnectedZitadel,\n Identity,\n InstanceHandle,\n LocalZitadel,\n LocalZitadelRuntime,\n MintedSession,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n SessionCookie,\n} from \"./types\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"src-Dkt0HHu8.mjs","names":[],"sources":["../src/bootstrap.ts","../src/cli.ts","../src/envelope.ts","../src/ports.ts","../src/lifecycle.ts","../src/seed.ts","../src/session.ts","../src/index.ts"],"sourcesContent":["import { createZitadelClient, type ZitadelClient } from \"@zitadel/api/client\";\nimport {\n DEFAULT_FLOW_SCHEMA_URI,\n getDefaultHumanUserSchema,\n getDefaultLoginFlow,\n type SetupPreset,\n type SetupUseCase,\n} from \"@zitadel/config/defaults\";\n\nexport interface BootstrapProjectOptions {\n baseUrl: string;\n projectName?: string;\n /**\n * Origins of the apps that will proxy to this instance. The backend's\n * origin check rejects forwarded requests from unregistered origins.\n */\n appOrigins?: string[];\n preset?: SetupPreset;\n useCase?: SetupUseCase;\n}\n\nexport interface BootstrappedProject {\n projectId: string;\n projectSecret: string;\n previewSecret?: string;\n schemaId: string;\n flowId: string;\n}\n\nconst DEFAULT_PROJECT_NAME = \"zitadel-testing\";\n\n/**\n * Server-side half of `zitadel setup`, without any file scaffolding:\n * `POST /projects` is unauthenticated and mints the projectSecret used as the\n * bearer for everything else; the schema is uploaded without `$id` so the\n * server assigns an opaque id, which the flow must then reference.\n */\nexport async function bootstrapProject(\n options: BootstrapProjectOptions,\n): Promise<BootstrappedProject> {\n const { baseUrl } = options;\n const unauthenticated = createZitadelClient({ baseUrl });\n const project = (await unauthenticated.createProject({\n name: options.projectName ?? DEFAULT_PROJECT_NAME,\n preview_origins: options.appOrigins ?? [],\n seed_defaults: false,\n } as Parameters<ZitadelClient[\"createProject\"]>[0])) as Record<string, unknown>;\n const projectId = requireString(project.id, \"project id\");\n const projectSecret = requireString(project.project_secret, \"project secret\");\n const previewSecret =\n typeof project.preview_secret === \"string\" ? project.preview_secret : undefined;\n\n const client = createZitadelClient({ baseUrl, token: projectSecret });\n\n const { $id: _templateId, ...schemaBody } = getDefaultHumanUserSchema({\n preset: options.preset,\n useCase: options.useCase,\n }) as { $id?: string } & Record<string, unknown>;\n void _templateId;\n const schema = (await client.createSchema(\n schemaBody as Parameters<ZitadelClient[\"createSchema\"]>[0],\n { project_id: projectId },\n )) as Record<string, unknown>;\n const schemaId = requireString(schema.id, \"schema id\");\n\n const flowBody = getDefaultLoginFlow({\n userSchemaUrl: schemaId,\n preset: options.preset,\n useCase: options.useCase,\n });\n const flow = (await client.createFlowDefinition({\n project_id: projectId,\n schema_uri: DEFAULT_FLOW_SCHEMA_URI,\n flow_definition: flowBody,\n } as Parameters<ZitadelClient[\"createFlowDefinition\"]>[0])) as Record<string, unknown>;\n const flowId = requireString(flow.id, \"flow definition id\");\n\n return { projectId, projectSecret, previewSecret, schemaId, flowId };\n}\n\nexport function requireString(value: unknown, label: string): string {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n throw new Error(`Missing ${label} in server response.`);\n}\n","import { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\nexport interface RunCliOptions {\n args: string[];\n env?: NodeJS.ProcessEnv;\n /** Test seam / escape hatch: alternative CLI entry script. */\n bin?: string;\n timeoutMs?: number;\n}\n\nexport interface RunCliResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\nexport function resolveCliBin(): string {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve(\"@zitadel/cli/package.json\");\n const pkg = require(pkgPath) as { bin?: Record<string, string> };\n const rel = pkg.bin?.zitadel;\n if (!rel) {\n throw new Error(\"@zitadel/cli does not declare a `zitadel` bin entry\");\n }\n return join(dirname(pkgPath), rel);\n}\n\nexport function runCli(options: RunCliOptions): Promise<RunCliResult> {\n const bin = options.bin ?? resolveCliBin();\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [bin, ...options.args], {\n env: { ...process.env, ...options.env },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.setEncoding(\"utf8\");\n child.stderr.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n const timer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n reject(\n new Error(\n `zitadel ${options.args[0] ?? \"\"} timed out after ${timeoutMs}ms\\n${tail(stderr)}`,\n ),\n );\n }, timeoutMs);\n timer.unref();\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n reject(error);\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n resolve({ exitCode: code ?? -1, stdout, stderr });\n });\n });\n}\n\nexport function tail(text: string, lines = 20): string {\n return text.split(\"\\n\").slice(-lines).join(\"\\n\").trim();\n}\n","export interface CliEnvelope<TData> {\n cli_version?: string;\n command?: string;\n source?: string;\n status: string;\n data: TData;\n warnings?: string[];\n /** Error envelopes (`status: \"error\"`) carry remediation guidance. */\n code?: string;\n message?: string;\n hint?: string;\n next_commands?: string[];\n}\n\n/**\n * Render an error envelope's remediation fields for humans — the CLI's\n * `hint`/`next_commands` are the actionable part of a failure (e.g. \"Reinstall\n * @zitadel/cli so npm can install @zitadel/server\"), so surface them instead\n * of a raw stdout dump. Returns undefined when the envelope has no message.\n */\nexport function describeEnvelopeError(envelope: CliEnvelope<unknown>): string | undefined {\n if (typeof envelope.message !== \"string\" || envelope.message.length === 0) {\n return undefined;\n }\n const lines = [envelope.code ? `${envelope.code}: ${envelope.message}` : envelope.message];\n if (envelope.hint) {\n lines.push(`hint: ${envelope.hint}`);\n }\n if (envelope.next_commands && envelope.next_commands.length > 0) {\n lines.push(`next: ${envelope.next_commands.join(\" | \")}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport interface StartEnvelopeData {\n runtime: {\n backend: string;\n pid: number;\n port: number;\n data_dir: string;\n log_path: string;\n };\n urls: {\n api: string;\n console: string;\n login: string;\n };\n}\n\nexport function parseCliEnvelope<TData>(stdout: string, context: string): CliEnvelope<TData> {\n const start = stdout.indexOf(\"{\");\n const end = stdout.lastIndexOf(\"}\");\n if (start === -1 || end <= start) {\n throw new Error(\n `${context}: expected a JSON envelope on stdout, got:\\n${stdout.trim() || \"(empty)\"}`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout.slice(start, end + 1));\n } catch (error) {\n throw new Error(\n `${context}: failed to parse JSON envelope: ${(error as Error).message}\\n${stdout.trim()}`,\n { cause: error },\n );\n }\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n typeof (parsed as { status?: unknown }).status !== \"string\"\n ) {\n throw new Error(`${context}: stdout JSON is not a CLI envelope:\\n${stdout.trim()}`);\n }\n return parsed as CliEnvelope<TData>;\n}\n","import { createServer } from \"node:net\";\n\n/**\n * Ask the OS for a free TCP port. The port is released before returning, so a\n * racing process could grab it; the CLI's own preflight surfaces that as\n * E_PORT_IN_USE, which is loud rather than corrupting.\n */\nexport function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.unref();\n server.on(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n server.close();\n reject(new Error(\"could not determine a free port\"));\n return;\n }\n const { port } = address;\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(port);\n });\n });\n });\n}\n","import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { runCli, tail, type RunCliResult } from \"./cli\";\nimport {\n describeEnvelopeError,\n parseCliEnvelope,\n type CliEnvelope,\n type StartEnvelopeData,\n} from \"./envelope\";\nimport { getFreePort } from \"./ports\";\nimport type { LocalZitadelRuntime } from \"./types\";\n\nexport interface BootServerOptions {\n /** TCP port for the instance; defaults to an OS-assigned free port. */\n port?: number;\n /**\n * State directory. Defaults to a fresh temp dir that is removed on stop;\n * a caller-provided dir is never removed.\n */\n dir?: string;\n /** Forwarded as ZITADEL_SERVER_BINARY (in-repo runs use dist/server/nextgen). */\n serverBinary?: string;\n /** Keep the owned temp dir after stop (debugging). */\n keep?: boolean;\n /** Test seam: alternative CLI entry script. */\n cliBin?: string;\n timeoutMs?: number;\n}\n\nexport interface BootedServer {\n baseUrl: string;\n runtime: LocalZitadelRuntime;\n stop(): Promise<void>;\n}\n\n/**\n * Boot an ephemeral local server by shelling out to `zitadel start` and parse\n * its JSON envelope. The CLI owns the subtle parts (port preflight, health\n * wait, process-group stop), so this module stays a thin adapter; swapping it\n * for direct library calls later must not change the shape returned here.\n */\nexport async function bootLocalServer(options: BootServerOptions = {}): Promise<BootedServer> {\n const ownsDir = options.dir === undefined;\n const dir = options.dir ?? (await mkdtemp(join(tmpdir(), \"zitadel-testing-\")));\n const port = options.port ?? (await getFreePort());\n const env: NodeJS.ProcessEnv = {};\n if (options.serverBinary) {\n env.ZITADEL_SERVER_BINARY = options.serverBinary;\n }\n\n const result = await runCli({\n args: [\"start\", \"--port\", String(port), \"--non-interactive\", \"--json\", \"-c\", dir],\n bin: options.cliBin,\n env,\n timeoutMs: options.timeoutMs,\n });\n if (result.exitCode !== 0) {\n // Keep the dir on failure: server.log inside it is the diagnostic.\n throw new Error(\n `zitadel start exited with code ${result.exitCode}.\\n` +\n `${failureDetail(result)}\\n` +\n `state dir kept for inspection: ${dir}`,\n );\n }\n const stopViaCli = async (): Promise<void> => {\n const stopResult = await runCli({\n args: [\"stop\", \"--non-interactive\", \"--json\", \"-c\", dir],\n bin: options.cliBin,\n env,\n timeoutMs: options.timeoutMs,\n });\n if (stopResult.exitCode !== 0) {\n throw new Error(\n `zitadel stop exited with code ${stopResult.exitCode}.\\n` +\n `${failureDetail(stopResult)}\\n` +\n `state dir kept for inspection: ${dir}`,\n );\n }\n };\n\n let envelope: CliEnvelope<StartEnvelopeData>;\n try {\n envelope = parseCliEnvelope<StartEnvelopeData>(result.stdout, \"zitadel start\");\n if (envelope.status !== \"ok\") {\n throw new Error(\n `zitadel start reported status \"${envelope.status}\":\\n` +\n `${describeEnvelopeError(envelope) ?? tail(result.stdout)}`,\n );\n }\n } catch (error) {\n const startError = new Error(\n `zitadel start produced unusable output.\\n` +\n `reason: ${error instanceof Error ? error.message : String(error)}\\n` +\n `stdout: ${tail(result.stdout) || \"(empty)\"}\\n` +\n `stderr: ${tail(result.stderr) || \"(empty)\"}\\n` +\n `state dir kept for inspection: ${dir}`,\n { cause: error },\n );\n // start exited 0, so a server may well be running despite the unusable\n // output — stop it instead of orphaning it.\n try {\n await stopViaCli();\n } catch (stopError) {\n // Both errors are preserved in AggregateError.errors, which the rule\n // below cannot model.\n // oxlint-disable-next-line preserve-caught-error\n throw new AggregateError(\n [startError, stopError],\n `${startError.message}\\nStopping the possibly-running instance also failed: ${\n stopError instanceof Error ? stopError.message : String(stopError)\n }`,\n );\n }\n throw startError;\n }\n\n const { runtime, urls } = envelope.data;\n const runStop = async (): Promise<void> => {\n await stopViaCli();\n if (ownsDir && !options.keep) {\n await rm(dir, { recursive: true, force: true });\n }\n };\n // Memoize the in-flight stop so concurrent callers await the same cleanup,\n // and reset on failure so a failed stop can be retried instead of silently\n // leaving the server behind.\n let stopPromise: Promise<void> | undefined;\n const stop = (): Promise<void> => {\n stopPromise ??= runStop().catch((error: unknown) => {\n stopPromise = undefined;\n throw error;\n });\n return stopPromise;\n };\n\n return {\n baseUrl: urls.api,\n runtime: {\n port: runtime.port,\n pid: runtime.pid,\n dir,\n logPath: runtime.log_path,\n },\n stop,\n };\n}\n\n/**\n * A failed CLI run usually still prints an error envelope; its\n * message/hint/next_commands beat raw output tails (e.g. a fresh install\n * missing @zitadel/server gets \"Reinstall @zitadel/cli\" instead of a stack).\n */\nfunction failureDetail(result: RunCliResult): string {\n try {\n const described = describeEnvelopeError(parseCliEnvelope<unknown>(result.stdout, \"zitadel\"));\n if (described) {\n return described;\n }\n } catch {\n // stdout carried no envelope; fall back to the raw tails.\n }\n return `stdout: ${tail(result.stdout) || \"(empty)\"}\\nstderr: ${tail(result.stderr) || \"(empty)\"}`;\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport type { ZitadelClient } from \"@zitadel/api/client\";\n\nimport { requireString } from \"./bootstrap\";\nimport type { Identity, SeededUser, SeedUserInput, SeedUsersTemplate } from \"./types\";\n\nexport interface SeedContext {\n projectId: string;\n schemaId: string;\n}\n\n/**\n * A unique unused email + password. Nothing is created on the instance —\n * this is the input for registration-flow specs, which must prove the flow\n * creates the user.\n */\nexport function identity(): Identity {\n return {\n email: `e2e-${randomUUID().slice(0, 8)}@example.com`,\n password: `Pw!${randomUUID()}`,\n };\n}\n\n/**\n * Create a user that can immediately complete the password login flow:\n * `POST /users` (the body carries `schema: <schema id>` and the schema-defined\n * content under `attributes`) followed by `PUT /users/{id}/password` with\n * `is_change_required: false`.\n *\n * Defaults mint a unique email per call (email is x-unique per project), which\n * is what makes per-test seeding parallel-safe on a shared instance.\n */\nexport async function seedUser(\n client: ZitadelClient,\n context: SeedContext,\n input: SeedUserInput = {},\n): Promise<SeededUser> {\n const fresh = identity();\n const email = input.email ?? fresh.email;\n const password = input.password ?? fresh.password;\n // `email` wins over the templated attributes: the returned SeededUser must\n // never disagree with what was actually created, since a silently overridden\n // email would yield credentials that cannot log in.\n const user = (await client.createUser(\n {\n schema: context.schemaId,\n attributes: { ...input.attributes, email },\n },\n { project_id: context.projectId },\n )) as Record<string, unknown>;\n const id = requireString(user.id, \"user id\");\n await client.setUserPassword(id, { password, is_change_required: false });\n return { id, email, password };\n}\n\n/**\n * Seed `count` users sequentially. The template makes fixture data\n * deterministic per index (stable emails/names keep screenshot diffs about\n * code, not reshuffled data — the `console:dev-real` pattern); untemplated\n * fields fall back to the unique defaults. Name-like attributes need a\n * schema that declares them (`useCase: \"consumer\"` or wider).\n */\nexport async function seedUsers(\n client: ZitadelClient,\n context: SeedContext,\n count: number,\n template: SeedUsersTemplate = {},\n): Promise<SeededUser[]> {\n const users: SeededUser[] = [];\n for (let index = 0; index < count; index += 1) {\n users.push(\n await seedUser(client, context, {\n email: template.email?.(index),\n password: template.password?.(index),\n attributes: template.attributes?.(index),\n }),\n );\n }\n return users;\n}\n","import type { ZitadelClient } from \"@zitadel/api/client\";\nimport type { CreateFlow201, CreateFlow201StepFieldsItem } from \"@zitadel/api/generated/model\";\n\nimport type { SeedContext } from \"./seed\";\nimport type { InstanceHandle, MintedSession, SeededUser } from \"./types\";\n\n/** Mirrors the server's session cookie (internal/api/session.go). */\nexport const SESSION_COOKIE_NAME = \"__nextgen_session\";\n\nconst MAX_FLOW_STEPS = 6;\n\nexport interface MintSessionOptions {\n /** Forwarded to `POST /flow`; the project's default flow when omitted. */\n flowDefinitionName?: string;\n /** Origin header for flow calls (the project's origin check enforces it). */\n origin?: string;\n}\n\n/**\n * Drive the real login flow headlessly for a seeded password user and\n * exchange the terminal handoff for a session: exactly what `<zitadel-login>`\n * does, minus the rendering. Supports flows whose steps only ask for the\n * user's email and password (the shipped `password-first` presets); any step\n * demanding more — a challenge, an unknown field — fails loudly by design.\n *\n * Flow calls use raw fetch instead of the typed client because the flow is\n * stateless through the sealed `_zflow` cookie (internal/api/flow.go): every\n * response re-seals the flow state into Set-Cookie, and submits are rejected\n * without it. Browsers round-trip it implicitly; here a one-cookie jar does.\n */\nexport async function mintSession(\n client: ZitadelClient,\n handle: Pick<InstanceHandle, \"baseUrl\" | \"projectSecret\">,\n context: SeedContext,\n user: SeededUser,\n options: MintSessionOptions = {},\n): Promise<MintedSession> {\n const values: Record<string, string> = { email: user.email, password: user.password };\n const jar = new FlowCookieJar();\n const origin = options.origin;\n\n let response = await flowFetch(handle, jar, origin, \"/flow\", {\n project_id: context.projectId,\n purpose: \"login\",\n ...(options.flowDefinitionName ? { flow_definition_name: options.flowDefinitionName } : {}),\n });\n\n for (let hop = 0; hop < MAX_FLOW_STEPS; hop += 1) {\n if (response.handoff_token) {\n const exchanged = await client.exchangeHandoff(\n { handoff_token: response.handoff_token },\n { project_id: context.projectId },\n );\n return {\n user,\n sessionToken: exchanged.session_token,\n expiresAt: exchanged.session.expires_at,\n cookie: {\n name: SESSION_COOKIE_NAME,\n value: exchanged.session_token,\n httpOnly: true,\n secure: true,\n sameSite: \"Lax\",\n path: \"/\",\n },\n };\n }\n response = await flowFetch(handle, jar, origin, `/flow/${encodeURIComponent(response.id)}/submit`, {\n session_token: response.session_token,\n action: \"submit\",\n fields: collectFields(response, values),\n });\n }\n\n throw new Error(\n `seed.session: flow did not complete within ${MAX_FLOW_STEPS} steps ` +\n `(last step: ${describeStep(response)}).`,\n );\n}\n\n/** One-cookie jar for the sealed `_zflow` flow-state cookie. */\nclass FlowCookieJar {\n private cookie: string | undefined;\n\n absorb(response: Response): void {\n for (const raw of response.headers.getSetCookie()) {\n const [pair] = raw.split(\";\", 1);\n if (pair?.startsWith(\"_zflow=\")) {\n this.cookie = pair;\n }\n }\n }\n\n header(): Record<string, string> {\n return this.cookie ? { cookie: this.cookie } : {};\n }\n}\n\nasync function flowFetch(\n handle: Pick<InstanceHandle, \"baseUrl\" | \"projectSecret\">,\n jar: FlowCookieJar,\n origin: string | undefined,\n path: string,\n body: Record<string, unknown>,\n): Promise<CreateFlow201> {\n const response = await fetch(`${handle.baseUrl}${path}`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${handle.projectSecret}`,\n // The project's origin allowlist applies to flow calls; send the app\n // origin the way a browser request through the app would carry it.\n ...(origin ? { origin } : {}),\n ...jar.header(),\n },\n body: JSON.stringify(body),\n });\n jar.absorb(response);\n const parsed = (await response.json().catch(() => undefined)) as CreateFlow201 | undefined;\n if (!response.ok || !parsed) {\n const detail =\n parsed && typeof parsed === \"object\" ? ` — ${JSON.stringify(parsed)}` : \"\";\n // An origin-allowlist rejection without an Origin header is a\n // configuration gap, not a flow problem — say how to close it. (No eager\n // check: a project with an empty allowlist may accept originless calls.)\n const hint =\n !origin && /origin/i.test(detail)\n ? \"\\nNo Origin header was sent: pass `origin` to seedSession() (the Playwright \" +\n \"fixtures pass the suite's baseURL) or `appOrigins` to startLocalZitadel().\"\n : \"\";\n throw new Error(`seed.session: POST ${path} returned ${response.status}${detail}${hint}`);\n }\n return parsed;\n}\n\n/**\n * Fill exactly the fields the current step declares — the orchestrator's\n * convention — from the known email/password values. An unknown required\n * field means this flow needs more than a password login can provide.\n */\nfunction collectFields(response: CreateFlow201, values: Record<string, string>): Record<string, string> {\n const fields: Record<string, string> = {};\n for (const field of response.step.fields ?? []) {\n const value = values[fieldKey(field)];\n if (value === undefined) {\n throw new Error(\n `seed.session supports password flows only; step ${describeStep(response)} ` +\n `declares field \"${field.name}\", which the kit cannot fill. ` +\n `Log in through the UI for flows with additional factors.`,\n );\n }\n fields[field.name] = value;\n }\n return fields;\n}\n\n/**\n * Steps name credential fields with schema pointers (e.g.\n * `x-auth-methods#password`); match on the trailing segment so the value map\n * stays the plain `{ email, password }` a caller thinks in.\n */\nfunction fieldKey(field: CreateFlow201StepFieldsItem): string {\n const name = field.name;\n const tail = name.split(/[#/.]/).at(-1) ?? name;\n return tail.toLowerCase();\n}\n\nfunction describeStep(response: CreateFlow201): string {\n const name = response.step.name ?? \"(unnamed)\";\n const declared = (response.step.fields ?? []).map((field) => field.name).join(\", \");\n return `\"${name}\"${declared ? ` [fields: ${declared}]` : \"\"}`;\n}\n","import { createZitadelClient } from \"@zitadel/api/client\";\n\nimport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nimport { bootstrapProject, type BootstrapProjectOptions } from \"./bootstrap\";\nimport { bootLocalServer, type BootServerOptions } from \"./lifecycle\";\nimport { identity, seedUser, seedUsers } from \"./seed\";\nimport { mintSession } from \"./session\";\nimport type { ConnectedZitadel, InstanceHandle, LocalZitadel } from \"./types\";\n\nexport type StartLocalZitadelOptions = BootServerOptions &\n Omit<BootstrapProjectOptions, \"baseUrl\">;\n\n/**\n * Attach to an already-bootstrapped instance/project. Lifecycle-free on\n * purpose: this is the entry point for Playwright workers (via the handshake\n * file) and, later, for seeding remote instances.\n */\nexport function connectZitadel(handle: InstanceHandle): ConnectedZitadel {\n const api = createZitadelClient({ baseUrl: handle.baseUrl, token: handle.projectSecret });\n const context = { projectId: handle.projectId, schemaId: handle.schemaId };\n const connected: ConnectedZitadel = {\n handle,\n api,\n // The Next-shaped convenience view; other frameworks apply their own\n // template to `handle` (see AppEnvTemplate).\n appEnv: applyAppEnvTemplate(nextAppEnv, handle),\n seedUser: (input) => seedUser(api, context, input),\n seedUsers: (count, template) => seedUsers(api, context, count, template),\n identity,\n seedSession: async (input = {}) => {\n const { user: existing, flowDefinitionName, origin, ...userInput } = input;\n const user = existing ?? (await seedUser(api, context, userInput));\n return mintSession(api, handle, context, user, {\n flowDefinitionName,\n origin: origin ?? handle.appOrigin,\n });\n },\n };\n return connected;\n}\n\n/**\n * Boot an ephemeral local instance (binary runtime + SQLite by default, no\n * Docker) and bootstrap a project + default schema + login flow on it. The\n * result can seed loginable password users immediately.\n */\nexport async function startLocalZitadel(\n options: StartLocalZitadelOptions = {},\n): Promise<LocalZitadel> {\n const server = await bootLocalServer(options);\n let bootstrapped;\n try {\n bootstrapped = await bootstrapProject({\n baseUrl: server.baseUrl,\n projectName: options.projectName,\n appOrigins: options.appOrigins,\n preset: options.preset,\n useCase: options.useCase,\n });\n } catch (error) {\n try {\n await server.stop();\n } catch (stopError) {\n // Both errors are preserved in AggregateError.errors, which the rule\n // below cannot model.\n // oxlint-disable-next-line preserve-caught-error\n throw new AggregateError(\n [error, stopError],\n \"bootstrap failed, and stopping the booted instance also failed\",\n );\n }\n throw error;\n }\n const handle: InstanceHandle = {\n baseUrl: server.baseUrl,\n projectId: bootstrapped.projectId,\n projectSecret: bootstrapped.projectSecret,\n schemaId: bootstrapped.schemaId,\n previewSecret: bootstrapped.previewSecret,\n appOrigin: options.appOrigins?.[0],\n };\n return {\n ...connectZitadel(handle),\n runtime: server.runtime,\n stop: server.stop,\n [Symbol.asyncDispose]: server.stop,\n };\n}\n\nexport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nexport type { AppEnvTemplate } from \"./app-env\";\nexport { bootstrapProject } from \"./bootstrap\";\nexport type { BootstrapProjectOptions, BootstrappedProject } from \"./bootstrap\";\nexport { readHandshakeSync, waitForHandshake, writeHandshake } from \"./handshake\";\nexport { bootLocalServer } from \"./lifecycle\";\nexport type { BootedServer, BootServerOptions } from \"./lifecycle\";\nexport { SESSION_COOKIE_NAME } from \"./session\";\nexport type {\n ConnectedZitadel,\n Identity,\n InstanceHandle,\n LocalZitadel,\n LocalZitadelRuntime,\n MintedSession,\n PlatformCredentials,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n SessionCookie,\n} from \"./types\";\n"],"mappings":"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"}