@zitadel/testing 0.1.0-alpha.18 → 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 +66 -7
- package/dist/{app-env-D3W0GYhA.d.mts → app-env-DlrV_ePR.d.mts} +59 -7
- package/dist/app-env-DlrV_ePR.d.mts.map +1 -0
- package/dist/app-runner.cjs +1 -1
- package/dist/app-runner.mjs +1 -1
- package/dist/{handshake-CRKcgkfN.cjs → handshake-BOsVBPtn.cjs} +14 -1
- package/dist/handshake-BOsVBPtn.cjs.map +1 -0
- package/dist/{handshake-ClzWvG8z.mjs → handshake-BnAPXC6s.mjs} +14 -1
- package/dist/handshake-BnAPXC6s.mjs.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/playwright.cjs +33 -15
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.d.mts +19 -3
- package/dist/playwright.d.mts.map +1 -1
- package/dist/playwright.mjs +33 -15
- package/dist/playwright.mjs.map +1 -1
- package/dist/{src-eTcdx-ZS.mjs → src-Dkt0HHu8.mjs} +11 -8
- package/dist/src-Dkt0HHu8.mjs.map +1 -0
- package/dist/{src-DirKLqi4.cjs → src-DtCJTEId.cjs} +541 -195
- package/dist/src-DtCJTEId.cjs.map +1 -0
- package/dist/supervisor.cjs +2 -2
- package/dist/supervisor.mjs +2 -2
- package/package.json +4 -4
- package/dist/app-env-D3W0GYhA.d.mts.map +0 -1
- package/dist/handshake-CRKcgkfN.cjs.map +0 -1
- package/dist/handshake-ClzWvG8z.mjs.map +0 -1
- package/dist/src-DirKLqi4.cjs.map +0 -1
- package/dist/src-eTcdx-ZS.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
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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"}
|