@zitadel/testing 0.0.0

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.
@@ -0,0 +1,515 @@
1
+ import { a as nextAppEnv, i as applyAppEnvTemplate } from "./handshake-BPtWruO8.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { createZitadelClient } from "@zitadel/api/client";
4
+ import { DEFAULT_FLOW_SCHEMA_URI, getDefaultHumanUserSchema, getDefaultLoginFlow } from "@zitadel/config/defaults";
5
+ import { mkdtemp, rm } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
+ import { dirname, join } from "node:path";
8
+ import { spawn } from "node:child_process";
9
+ import { createServer } from "node:net";
10
+ import { randomUUID } from "node:crypto";
11
+ //#region src/bootstrap.ts
12
+ const DEFAULT_PROJECT_NAME = "zitadel-testing";
13
+ /**
14
+ * Server-side half of `zitadel setup`, without any file scaffolding:
15
+ * `POST /projects` is unauthenticated and mints the projectSecret used as the
16
+ * bearer for everything else; the schema is uploaded without `$id` so the
17
+ * server assigns an opaque id, which the flow must then reference.
18
+ */
19
+ async function bootstrapProject(options) {
20
+ const { baseUrl } = options;
21
+ const project = await createZitadelClient({ baseUrl }).createProject({
22
+ name: options.projectName ?? DEFAULT_PROJECT_NAME,
23
+ previewOrigins: options.appOrigins ?? [],
24
+ seedDefaults: false
25
+ });
26
+ const projectId = requireString(project.id, "project id");
27
+ const projectSecret = requireString(project.projectSecret, "project secret");
28
+ const previewSecret = typeof project.previewSecret === "string" ? project.previewSecret : void 0;
29
+ const client = createZitadelClient({
30
+ baseUrl,
31
+ token: projectSecret
32
+ });
33
+ const { $id: _templateId, ...schemaBody } = getDefaultHumanUserSchema({
34
+ preset: options.preset,
35
+ useCase: options.useCase
36
+ });
37
+ const schemaId = requireString((await client.createSchema(schemaBody, { project_id: projectId })).id, "schema id");
38
+ const flowBody = getDefaultLoginFlow({
39
+ userSchemaUrl: schemaId,
40
+ preset: options.preset,
41
+ useCase: options.useCase
42
+ });
43
+ return {
44
+ projectId,
45
+ projectSecret,
46
+ previewSecret,
47
+ schemaId,
48
+ flowId: requireString((await client.createFlowDefinition({
49
+ project_id: projectId,
50
+ schema_uri: DEFAULT_FLOW_SCHEMA_URI,
51
+ flow_definition: flowBody
52
+ })).id, "flow definition id")
53
+ };
54
+ }
55
+ function requireString(value, label) {
56
+ if (typeof value === "string" && value.length > 0) return value;
57
+ throw new Error(`Missing ${label} in server response.`);
58
+ }
59
+ //#endregion
60
+ //#region src/cli.ts
61
+ const DEFAULT_TIMEOUT_MS = 12e4;
62
+ function resolveCliBin() {
63
+ const require = createRequire(import.meta.url);
64
+ const pkgPath = require.resolve("@zitadel/cli/package.json");
65
+ const rel = require(pkgPath).bin?.zitadel;
66
+ if (!rel) throw new Error("@zitadel/cli does not declare a `zitadel` bin entry");
67
+ return join(dirname(pkgPath), rel);
68
+ }
69
+ function runCli(options) {
70
+ const bin = options.bin ?? resolveCliBin();
71
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
72
+ return new Promise((resolve, reject) => {
73
+ const child = spawn(process.execPath, [bin, ...options.args], {
74
+ env: {
75
+ ...process.env,
76
+ ...options.env
77
+ },
78
+ stdio: [
79
+ "ignore",
80
+ "pipe",
81
+ "pipe"
82
+ ]
83
+ });
84
+ let stdout = "";
85
+ let stderr = "";
86
+ child.stdout.setEncoding("utf8");
87
+ child.stdout.on("data", (chunk) => {
88
+ stdout += chunk;
89
+ });
90
+ child.stderr.setEncoding("utf8");
91
+ child.stderr.on("data", (chunk) => {
92
+ stderr += chunk;
93
+ });
94
+ const timer = setTimeout(() => {
95
+ child.kill("SIGKILL");
96
+ reject(/* @__PURE__ */ new Error(`zitadel ${options.args[0] ?? ""} timed out after ${timeoutMs}ms\n${tail(stderr)}`));
97
+ }, timeoutMs);
98
+ timer.unref();
99
+ child.on("error", (error) => {
100
+ clearTimeout(timer);
101
+ reject(error);
102
+ });
103
+ child.on("close", (code) => {
104
+ clearTimeout(timer);
105
+ resolve({
106
+ exitCode: code ?? -1,
107
+ stdout,
108
+ stderr
109
+ });
110
+ });
111
+ });
112
+ }
113
+ function tail(text, lines = 20) {
114
+ return text.split("\n").slice(-lines).join("\n").trim();
115
+ }
116
+ //#endregion
117
+ //#region src/envelope.ts
118
+ /**
119
+ * Render an error envelope's remediation fields for humans — the CLI's
120
+ * `hint`/`next_commands` are the actionable part of a failure (e.g. "Reinstall
121
+ * @zitadel/cli so npm can install @zitadel/server"), so surface them instead
122
+ * of a raw stdout dump. Returns undefined when the envelope has no message.
123
+ */
124
+ function describeEnvelopeError(envelope) {
125
+ if (typeof envelope.message !== "string" || envelope.message.length === 0) return;
126
+ const lines = [envelope.code ? `${envelope.code}: ${envelope.message}` : envelope.message];
127
+ if (envelope.hint) lines.push(`hint: ${envelope.hint}`);
128
+ if (envelope.next_commands && envelope.next_commands.length > 0) lines.push(`next: ${envelope.next_commands.join(" | ")}`);
129
+ return lines.join("\n");
130
+ }
131
+ function parseCliEnvelope(stdout, context) {
132
+ const start = stdout.indexOf("{");
133
+ const end = stdout.lastIndexOf("}");
134
+ if (start === -1 || end <= start) throw new Error(`${context}: expected a JSON envelope on stdout, got:\n${stdout.trim() || "(empty)"}`);
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(stdout.slice(start, end + 1));
138
+ } catch (error) {
139
+ throw new Error(`${context}: failed to parse JSON envelope: ${error.message}\n${stdout.trim()}`, { cause: error });
140
+ }
141
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.status !== "string") throw new Error(`${context}: stdout JSON is not a CLI envelope:\n${stdout.trim()}`);
142
+ return parsed;
143
+ }
144
+ //#endregion
145
+ //#region src/ports.ts
146
+ /**
147
+ * Ask the OS for a free TCP port. The port is released before returning, so a
148
+ * racing process could grab it; the CLI's own preflight surfaces that as
149
+ * E_PORT_IN_USE, which is loud rather than corrupting.
150
+ */
151
+ function getFreePort() {
152
+ return new Promise((resolve, reject) => {
153
+ const server = createServer();
154
+ server.unref();
155
+ server.on("error", reject);
156
+ server.listen(0, "127.0.0.1", () => {
157
+ const address = server.address();
158
+ if (address === null || typeof address === "string") {
159
+ server.close();
160
+ reject(/* @__PURE__ */ new Error("could not determine a free port"));
161
+ return;
162
+ }
163
+ const { port } = address;
164
+ server.close((err) => {
165
+ if (err) {
166
+ reject(err);
167
+ return;
168
+ }
169
+ resolve(port);
170
+ });
171
+ });
172
+ });
173
+ }
174
+ //#endregion
175
+ //#region src/lifecycle.ts
176
+ /**
177
+ * Boot an ephemeral local server by shelling out to `zitadel start` and parse
178
+ * its JSON envelope. The CLI owns the subtle parts (port preflight, health
179
+ * wait, process-group stop, embedded-Postgres reaping), so this module stays a
180
+ * thin adapter; swapping it for direct library calls later must not change the
181
+ * shape returned here.
182
+ */
183
+ async function bootLocalServer(options = {}) {
184
+ const ownsDir = options.dir === void 0;
185
+ const dir = options.dir ?? await mkdtemp(join(tmpdir(), "zitadel-testing-"));
186
+ const port = options.port ?? await getFreePort();
187
+ const env = {};
188
+ if (options.serverBinary) env.ZITADEL_SERVER_BINARY = options.serverBinary;
189
+ const result = await runCli({
190
+ args: [
191
+ "start",
192
+ "--port",
193
+ String(port),
194
+ "--non-interactive",
195
+ "--json",
196
+ "-c",
197
+ dir
198
+ ],
199
+ bin: options.cliBin,
200
+ env,
201
+ timeoutMs: options.timeoutMs
202
+ });
203
+ if (result.exitCode !== 0) throw new Error(`zitadel start exited with code ${result.exitCode}.\n${failureDetail(result)}\nstate dir kept for inspection: ${dir}`);
204
+ const stopViaCli = async () => {
205
+ const stopResult = await runCli({
206
+ args: [
207
+ "stop",
208
+ "--non-interactive",
209
+ "--json",
210
+ "-c",
211
+ dir
212
+ ],
213
+ bin: options.cliBin,
214
+ env,
215
+ timeoutMs: options.timeoutMs
216
+ });
217
+ if (stopResult.exitCode !== 0) throw new Error(`zitadel stop exited with code ${stopResult.exitCode}.\n${failureDetail(stopResult)}\nstate dir kept for inspection: ${dir}`);
218
+ };
219
+ let envelope;
220
+ try {
221
+ envelope = parseCliEnvelope(result.stdout, "zitadel start");
222
+ if (envelope.status !== "ok") throw new Error(`zitadel start reported status "${envelope.status}":\n${describeEnvelopeError(envelope) ?? tail(result.stdout)}`);
223
+ } catch (error) {
224
+ const startError = new Error(`zitadel start produced unusable output.\nreason: ${error instanceof Error ? error.message : String(error)}\nstdout: ${tail(result.stdout) || "(empty)"}\nstderr: ${tail(result.stderr) || "(empty)"}\nstate dir kept for inspection: ${dir}`, { cause: error });
225
+ try {
226
+ await stopViaCli();
227
+ } catch (stopError) {
228
+ throw new AggregateError([startError, stopError], `${startError.message}\nStopping the possibly-running instance also failed: ${stopError instanceof Error ? stopError.message : String(stopError)}`);
229
+ }
230
+ throw startError;
231
+ }
232
+ const { runtime, urls } = envelope.data;
233
+ const runStop = async () => {
234
+ await stopViaCli();
235
+ if (ownsDir && !options.keep) await rm(dir, {
236
+ recursive: true,
237
+ force: true
238
+ });
239
+ };
240
+ let stopPromise;
241
+ const stop = () => {
242
+ stopPromise ??= runStop().catch((error) => {
243
+ stopPromise = void 0;
244
+ throw error;
245
+ });
246
+ return stopPromise;
247
+ };
248
+ return {
249
+ baseUrl: urls.api,
250
+ runtime: {
251
+ port: runtime.port,
252
+ pid: runtime.pid,
253
+ dir,
254
+ logPath: runtime.log_path
255
+ },
256
+ stop
257
+ };
258
+ }
259
+ /**
260
+ * A failed CLI run usually still prints an error envelope; its
261
+ * message/hint/next_commands beat raw output tails (e.g. a fresh install
262
+ * missing @zitadel/server gets "Reinstall @zitadel/cli" instead of a stack).
263
+ */
264
+ function failureDetail(result) {
265
+ try {
266
+ const described = describeEnvelopeError(parseCliEnvelope(result.stdout, "zitadel"));
267
+ if (described) return described;
268
+ } catch {}
269
+ return `stdout: ${tail(result.stdout) || "(empty)"}\nstderr: ${tail(result.stderr) || "(empty)"}`;
270
+ }
271
+ //#endregion
272
+ //#region src/seed.ts
273
+ /**
274
+ * A unique unused email + password. Nothing is created on the instance —
275
+ * this is the input for registration-flow specs, which must prove the flow
276
+ * creates the user.
277
+ */
278
+ function identity() {
279
+ return {
280
+ email: `e2e-${randomUUID().slice(0, 8)}@example.com`,
281
+ password: `Pw!${randomUUID()}`
282
+ };
283
+ }
284
+ /**
285
+ * Create a user that can immediately complete the password login flow:
286
+ * `POST /users` (the body must carry `$schema: <schema id>`) followed by
287
+ * `PUT /users/{id}/password` with `isChangeRequired: false`.
288
+ *
289
+ * Defaults mint a unique email per call (email is x-unique per project), which
290
+ * is what makes per-test seeding parallel-safe on a shared instance.
291
+ */
292
+ async function seedUser(client, context, input = {}) {
293
+ const fresh = identity();
294
+ const email = input.email ?? fresh.email;
295
+ const password = input.password ?? fresh.password;
296
+ const id = requireString((await client.createUser({
297
+ ...input.attributes,
298
+ $schema: context.schemaId,
299
+ email
300
+ }, { project_id: context.projectId })).id, "user id");
301
+ await client.setUserPassword(id, {
302
+ password,
303
+ isChangeRequired: false
304
+ }, { project_id: context.projectId });
305
+ return {
306
+ id,
307
+ email,
308
+ password
309
+ };
310
+ }
311
+ /**
312
+ * Seed `count` users sequentially. The template makes fixture data
313
+ * deterministic per index (stable emails/names keep screenshot diffs about
314
+ * code, not reshuffled data — the `console:dev-real` pattern); untemplated
315
+ * fields fall back to the unique defaults. Name-like attributes need a
316
+ * schema that declares them (`useCase: "consumer"` or wider).
317
+ */
318
+ async function seedUsers(client, context, count, template = {}) {
319
+ const users = [];
320
+ for (let index = 0; index < count; index += 1) users.push(await seedUser(client, context, {
321
+ email: template.email?.(index),
322
+ password: template.password?.(index),
323
+ attributes: template.attributes?.(index)
324
+ }));
325
+ return users;
326
+ }
327
+ //#endregion
328
+ //#region src/session.ts
329
+ /** Mirrors the server's session cookie (internal/api/session.go). */
330
+ const SESSION_COOKIE_NAME = "__nextgen_session";
331
+ const MAX_FLOW_STEPS = 6;
332
+ /**
333
+ * Drive the real login flow headlessly for a seeded password user and
334
+ * exchange the terminal handoff for a session: exactly what `<zitadel-login>`
335
+ * does, minus the rendering. Supports flows whose steps only ask for the
336
+ * user's email and password (the shipped `password-first` presets); any step
337
+ * demanding more — a challenge, an unknown field — fails loudly by design.
338
+ *
339
+ * Flow calls use raw fetch instead of the typed client because the flow is
340
+ * stateless through the sealed `_zflow` cookie (internal/api/flow.go): every
341
+ * response re-seals the flow state into Set-Cookie, and submits are rejected
342
+ * without it. Browsers round-trip it implicitly; here a one-cookie jar does.
343
+ */
344
+ async function mintSession(client, handle, context, user, options = {}) {
345
+ const values = {
346
+ email: user.email,
347
+ password: user.password
348
+ };
349
+ const jar = new FlowCookieJar();
350
+ const origin = options.origin;
351
+ let response = await flowFetch(handle, jar, origin, "/flow", {
352
+ project_id: context.projectId,
353
+ purpose: "login",
354
+ ...options.flowDefinitionName ? { flow_definition_name: options.flowDefinitionName } : {}
355
+ });
356
+ for (let hop = 0; hop < MAX_FLOW_STEPS; hop += 1) {
357
+ if (response.handoff_token) {
358
+ const exchanged = await client.exchangeHandoff({ handoff_token: response.handoff_token }, { project_id: context.projectId });
359
+ return {
360
+ user,
361
+ sessionToken: exchanged.session_token,
362
+ expiresAt: exchanged.session.expires_at,
363
+ cookie: {
364
+ name: SESSION_COOKIE_NAME,
365
+ value: exchanged.session_token,
366
+ httpOnly: true,
367
+ secure: true,
368
+ sameSite: "Lax",
369
+ path: "/"
370
+ }
371
+ };
372
+ }
373
+ response = await flowFetch(handle, jar, origin, `/flow/${encodeURIComponent(response.id)}/submit`, {
374
+ session_token: response.session_token,
375
+ action: "submit",
376
+ fields: collectFields(response, values)
377
+ });
378
+ }
379
+ throw new Error(`seed.session: flow did not complete within ${MAX_FLOW_STEPS} steps (last step: ${describeStep(response)}).`);
380
+ }
381
+ /** One-cookie jar for the sealed `_zflow` flow-state cookie. */
382
+ var FlowCookieJar = class {
383
+ cookie;
384
+ absorb(response) {
385
+ for (const raw of response.headers.getSetCookie()) {
386
+ const [pair] = raw.split(";", 1);
387
+ if (pair?.startsWith("_zflow=")) this.cookie = pair;
388
+ }
389
+ }
390
+ header() {
391
+ return this.cookie ? { cookie: this.cookie } : {};
392
+ }
393
+ };
394
+ async function flowFetch(handle, jar, origin, path, body) {
395
+ const response = await fetch(`${handle.baseUrl}${path}`, {
396
+ method: "POST",
397
+ headers: {
398
+ "content-type": "application/json",
399
+ authorization: `Bearer ${handle.projectSecret}`,
400
+ ...origin ? { origin } : {},
401
+ ...jar.header()
402
+ },
403
+ body: JSON.stringify(body)
404
+ });
405
+ jar.absorb(response);
406
+ const parsed = await response.json().catch(() => void 0);
407
+ if (!response.ok || !parsed) {
408
+ const detail = parsed && typeof parsed === "object" ? ` — ${JSON.stringify(parsed)}` : "";
409
+ const hint = !origin && /origin/i.test(detail) ? "\nNo Origin header was sent: pass `origin` to seedSession() (the Playwright fixtures pass the suite's baseURL) or `appOrigins` to startLocalZitadel()." : "";
410
+ throw new Error(`seed.session: POST ${path} returned ${response.status}${detail}${hint}`);
411
+ }
412
+ return parsed;
413
+ }
414
+ /**
415
+ * Fill exactly the fields the current step declares — the orchestrator's
416
+ * convention — from the known email/password values. An unknown required
417
+ * field means this flow needs more than a password login can provide.
418
+ */
419
+ function collectFields(response, values) {
420
+ const fields = {};
421
+ for (const field of response.step.fields ?? []) {
422
+ const value = values[fieldKey(field)];
423
+ if (value === void 0) throw new Error(`seed.session supports password flows only; step ${describeStep(response)} declares field "${field.name}", which the kit cannot fill. Log in through the UI for flows with additional factors.`);
424
+ fields[field.name] = value;
425
+ }
426
+ return fields;
427
+ }
428
+ /**
429
+ * Steps name credential fields with schema pointers (e.g.
430
+ * `x-auth-methods#password`); match on the trailing segment so the value map
431
+ * stays the plain `{ email, password }` a caller thinks in.
432
+ */
433
+ function fieldKey(field) {
434
+ const name = field.name;
435
+ return (name.split(/[#/.]/).at(-1) ?? name).toLowerCase();
436
+ }
437
+ function describeStep(response) {
438
+ const name = response.step.name ?? "(unnamed)";
439
+ const declared = (response.step.fields ?? []).map((field) => field.name).join(", ");
440
+ return `"${name}"${declared ? ` [fields: ${declared}]` : ""}`;
441
+ }
442
+ //#endregion
443
+ //#region src/index.ts
444
+ /**
445
+ * Attach to an already-bootstrapped instance/project. Lifecycle-free on
446
+ * purpose: this is the entry point for Playwright workers (via the handshake
447
+ * file) and, later, for seeding remote instances.
448
+ */
449
+ function connectZitadel(handle) {
450
+ const api = createZitadelClient({
451
+ baseUrl: handle.baseUrl,
452
+ token: handle.projectSecret
453
+ });
454
+ const context = {
455
+ projectId: handle.projectId,
456
+ schemaId: handle.schemaId
457
+ };
458
+ return {
459
+ handle,
460
+ api,
461
+ appEnv: applyAppEnvTemplate(nextAppEnv, handle),
462
+ seedUser: (input) => seedUser(api, context, input),
463
+ seedUsers: (count, template) => seedUsers(api, context, count, template),
464
+ identity,
465
+ seedSession: async (input = {}) => {
466
+ const { user: existing, flowDefinitionName, origin, ...userInput } = input;
467
+ return mintSession(api, handle, context, existing ?? await seedUser(api, context, userInput), {
468
+ flowDefinitionName,
469
+ origin: origin ?? handle.appOrigin
470
+ });
471
+ }
472
+ };
473
+ }
474
+ /**
475
+ * Boot an ephemeral local instance (binary runtime + embedded Postgres, no
476
+ * Docker) and bootstrap a project + default schema + login flow on it. The
477
+ * result can seed loginable password users immediately.
478
+ */
479
+ async function startLocalZitadel(options = {}) {
480
+ const server = await bootLocalServer(options);
481
+ let bootstrapped;
482
+ try {
483
+ bootstrapped = await bootstrapProject({
484
+ baseUrl: server.baseUrl,
485
+ projectName: options.projectName,
486
+ appOrigins: options.appOrigins,
487
+ preset: options.preset,
488
+ useCase: options.useCase
489
+ });
490
+ } catch (error) {
491
+ try {
492
+ await server.stop();
493
+ } catch (stopError) {
494
+ throw new AggregateError([error, stopError], "bootstrap failed, and stopping the booted instance also failed");
495
+ }
496
+ throw error;
497
+ }
498
+ return {
499
+ ...connectZitadel({
500
+ baseUrl: server.baseUrl,
501
+ projectId: bootstrapped.projectId,
502
+ projectSecret: bootstrapped.projectSecret,
503
+ schemaId: bootstrapped.schemaId,
504
+ previewSecret: bootstrapped.previewSecret,
505
+ appOrigin: options.appOrigins?.[0]
506
+ }),
507
+ runtime: server.runtime,
508
+ stop: server.stop,
509
+ [Symbol.asyncDispose]: server.stop
510
+ };
511
+ }
512
+ //#endregion
513
+ export { bootstrapProject as a, bootLocalServer as i, startLocalZitadel as n, SESSION_COOKIE_NAME as r, connectZitadel as t };
514
+
515
+ //# sourceMappingURL=src-DkG0V4A6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-DkG0V4A6.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 previewOrigins: options.appOrigins ?? [],\n seedDefaults: false,\n } as Parameters<ZitadelClient[\"createProject\"]>[0])) as Record<string, unknown>;\n const projectId = requireString(project.id, \"project id\");\n const projectSecret = requireString(project.projectSecret, \"project secret\");\n const previewSecret =\n typeof project.previewSecret === \"string\" ? project.previewSecret : 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, embedded-Postgres reaping), so this module stays a\n * thin adapter; swapping it for direct library calls later must not change the\n * 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 and its embedded Postgres.\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 and embedded Postgres 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 must carry `$schema: <schema id>`) followed by\n * `PUT /users/{id}/password` with `isChangeRequired: 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 // Reserved fields win over attributes: the returned SeededUser must never\n // disagree with what was actually created (a silently overridden email or\n // $schema would yield credentials that cannot log in).\n const user = (await client.createUser(\n {\n ...input.attributes,\n $schema: context.schemaId,\n email,\n } as Parameters<ZitadelClient[\"createUser\"]>[0],\n { project_id: context.projectId },\n )) as Record<string, unknown>;\n const id = requireString(user.id, \"user id\");\n await client.setUserPassword(\n id,\n { password, isChangeRequired: false },\n { project_id: context.projectId },\n );\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 + embedded Postgres, 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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"}
@@ -0,0 +1,76 @@
1
+ const require_src = require("./src-BIL0PdSd.cjs");
2
+ const require_handshake = require("./handshake-CggSIoxF.cjs");
3
+ const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
4
+ let node_fs_promises = require("node:fs/promises");
5
+ //#region src/supervisor.ts
6
+ /**
7
+ * Playwright webServer entry generated by `withZitadel()`: boots an ephemeral
8
+ * seeded Zitadel and writes the handshake file that the app runner and the
9
+ * test fixtures read. Stays in the foreground; SIGTERM/SIGINT stop the
10
+ * instance. Configuration arrives as JSON in ZITADEL_TESTING_SUPERVISOR.
11
+ */
12
+ const LOG = "[zitadel-testing]";
13
+ async function main() {
14
+ const config = require_orchestration.parseSupervisorConfig(process.env[require_orchestration.SUPERVISOR_CONFIG_ENV]);
15
+ const handshakePath = require_orchestration.requireHandshakePath(process.env);
16
+ if (config.serverBinary) await (0, node_fs_promises.access)(config.serverBinary).catch(() => {
17
+ console.error(`${LOG} server binary not found at ${config.serverBinary}` + (config.serverBinaryHint ? ` — ${config.serverBinaryHint}` : ""));
18
+ process.exit(1);
19
+ });
20
+ let zitadel;
21
+ let signalled = false;
22
+ let finishing;
23
+ const finish = (code) => {
24
+ finishing ??= (async () => {
25
+ let exitCode = code;
26
+ try {
27
+ await zitadel?.stop();
28
+ } catch (error) {
29
+ console.error(`${LOG} stop failed: ${error.message}`);
30
+ exitCode = 1;
31
+ }
32
+ await (0, node_fs_promises.rm)(handshakePath, { force: true }).catch(() => void 0);
33
+ process.exit(exitCode);
34
+ })();
35
+ return finishing;
36
+ };
37
+ const onSignal = (signal) => {
38
+ signalled = true;
39
+ console.log(`${LOG} ${signal} received${zitadel ? ", stopping instance" : " during boot; stopping once ready"}`);
40
+ if (zitadel) finish(0);
41
+ };
42
+ process.on("SIGTERM", onSignal);
43
+ process.on("SIGINT", onSignal);
44
+ await (0, node_fs_promises.rm)(handshakePath, { force: true });
45
+ zitadel = await require_src.startLocalZitadel({
46
+ port: config.port,
47
+ appOrigins: config.appOrigins,
48
+ serverBinary: config.serverBinary,
49
+ dir: config.dir,
50
+ keep: config.keep,
51
+ projectName: config.projectName,
52
+ preset: config.preset,
53
+ useCase: config.useCase,
54
+ timeoutMs: config.timeoutMs
55
+ });
56
+ if (signalled) {
57
+ await finish(0);
58
+ return;
59
+ }
60
+ try {
61
+ await require_handshake.writeHandshake(handshakePath, zitadel.handle);
62
+ } catch (error) {
63
+ console.error(`${LOG} failed to write handshake: ${error.message}`);
64
+ await finish(1);
65
+ return;
66
+ }
67
+ console.log(`${LOG} instance ready at ${zitadel.handle.baseUrl} (project ${zitadel.handle.projectId})`);
68
+ setInterval(() => {}, 6e4);
69
+ }
70
+ main().catch((error) => {
71
+ console.error(`${LOG} ${error instanceof Error ? error.message : String(error)}`);
72
+ process.exit(1);
73
+ });
74
+ //#endregion
75
+
76
+ //# sourceMappingURL=supervisor.cjs.map