@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.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/app-env-D3W0GYhA.d.mts +122 -0
- package/dist/app-env-D3W0GYhA.d.mts.map +1 -0
- package/dist/app-runner.cjs +36 -0
- package/dist/app-runner.cjs.map +1 -0
- package/dist/app-runner.d.mts +1 -0
- package/dist/app-runner.mjs +37 -0
- package/dist/app-runner.mjs.map +1 -0
- package/dist/handshake-BPtWruO8.mjs +69 -0
- package/dist/handshake-BPtWruO8.mjs.map +1 -0
- package/dist/handshake-CggSIoxF.cjs +98 -0
- package/dist/handshake-CggSIoxF.cjs.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/orchestration-C640_1Lg.mjs +38 -0
- package/dist/orchestration-C640_1Lg.mjs.map +1 -0
- package/dist/orchestration-D7QBgQ0l.cjs +73 -0
- package/dist/orchestration-D7QBgQ0l.cjs.map +1 -0
- package/dist/playwright.cjs +156 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.d.mts +124 -0
- package/dist/playwright.d.mts.map +1 -0
- package/dist/playwright.mjs +146 -0
- package/dist/playwright.mjs.map +1 -0
- package/dist/src-BIL0PdSd.cjs +2356 -0
- package/dist/src-BIL0PdSd.cjs.map +1 -0
- package/dist/src-DkG0V4A6.mjs +515 -0
- package/dist/src-DkG0V4A6.mjs.map +1 -0
- package/dist/supervisor.cjs +76 -0
- package/dist/supervisor.cjs.map +1 -0
- package/dist/supervisor.d.mts +1 -0
- package/dist/supervisor.mjs +77 -0
- package/dist/supervisor.mjs.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
let node_fs = require("node:fs");
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_timers_promises = require("node:timers/promises");
|
|
5
|
+
//#region src/app-env.ts
|
|
6
|
+
/**
|
|
7
|
+
* The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own
|
|
8
|
+
* template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`
|
|
9
|
+
* names, for example.
|
|
10
|
+
*/
|
|
11
|
+
const nextAppEnv = {
|
|
12
|
+
ZITADEL_URL: "baseUrl",
|
|
13
|
+
NEXT_PUBLIC_ZITADEL_PROJECT_ID: "projectId",
|
|
14
|
+
ZITADEL_PROJECT_SECRET: "projectSecret"
|
|
15
|
+
};
|
|
16
|
+
function applyAppEnvTemplate(template, handle) {
|
|
17
|
+
const env = {};
|
|
18
|
+
for (const [name, field] of Object.entries(template)) {
|
|
19
|
+
const value = handle[field];
|
|
20
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`app env template maps "${name}" to handle field "${field}", which the instance handle does not carry`);
|
|
21
|
+
env[name] = value;
|
|
22
|
+
}
|
|
23
|
+
return env;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/handshake.ts
|
|
27
|
+
/**
|
|
28
|
+
* The handshake file carries an InstanceHandle across process boundaries:
|
|
29
|
+
* written by the script that boots + bootstraps the instance, read by
|
|
30
|
+
* Playwright workers (fixtures) and the app dev-server wrapper.
|
|
31
|
+
*/
|
|
32
|
+
async function writeHandshake(path, handle) {
|
|
33
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
34
|
+
await (0, node_fs_promises.writeFile)(path, `${JSON.stringify(handle, null, 2)}\n`, { mode: 384 });
|
|
35
|
+
}
|
|
36
|
+
function readHandshakeSync(path) {
|
|
37
|
+
const contents = (0, node_fs.readFileSync)(path, "utf8");
|
|
38
|
+
let value;
|
|
39
|
+
try {
|
|
40
|
+
value = JSON.parse(contents);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`handshake file ${path} contains invalid JSON: ${error.message}`, { cause: error });
|
|
43
|
+
}
|
|
44
|
+
return validateHandle(value, path);
|
|
45
|
+
}
|
|
46
|
+
async function waitForHandshake(path, timeoutMs = 6e4) {
|
|
47
|
+
const deadline = Date.now() + timeoutMs;
|
|
48
|
+
for (;;) try {
|
|
49
|
+
return validateHandle(JSON.parse(await (0, node_fs_promises.readFile)(path, "utf8")), path);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (Date.now() >= deadline) throw new Error(`handshake file not readable within ${timeoutMs}ms: ${path} (${error.message})`, { cause: error });
|
|
52
|
+
await (0, node_timers_promises.setTimeout)(250);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function validateHandle(value, source) {
|
|
56
|
+
const handle = value;
|
|
57
|
+
for (const field of [
|
|
58
|
+
"baseUrl",
|
|
59
|
+
"projectId",
|
|
60
|
+
"projectSecret",
|
|
61
|
+
"schemaId"
|
|
62
|
+
]) if (typeof handle?.[field] !== "string" || handle[field].length === 0) throw new Error(`handshake file ${source} is missing "${field}"`);
|
|
63
|
+
if (!URL.canParse(handle.baseUrl)) throw new Error(`handshake file ${source} has a malformed "baseUrl": ${handle.baseUrl}`);
|
|
64
|
+
return handle;
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
Object.defineProperty(exports, "applyAppEnvTemplate", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
get: function() {
|
|
70
|
+
return applyAppEnvTemplate;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(exports, "nextAppEnv", {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
get: function() {
|
|
76
|
+
return nextAppEnv;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
Object.defineProperty(exports, "readHandshakeSync", {
|
|
80
|
+
enumerable: true,
|
|
81
|
+
get: function() {
|
|
82
|
+
return readHandshakeSync;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
Object.defineProperty(exports, "waitForHandshake", {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
get: function() {
|
|
88
|
+
return waitForHandshake;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
Object.defineProperty(exports, "writeHandshake", {
|
|
92
|
+
enumerable: true,
|
|
93
|
+
get: function() {
|
|
94
|
+
return writeHandshake;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=handshake-CggSIoxF.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake-CggSIoxF.cjs","names":[],"sources":["../src/app-env.ts","../src/handshake.ts"],"sourcesContent":["import type { InstanceHandle } from \"./types\";\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, keyof InstanceHandle>;\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 } 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 const handle = value as Partial<InstanceHandle> | null;\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 return handle as InstanceHandle;\n}\n"],"mappings":";;;;;;;;;;AAeA,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;;;;;;;;;ACzBT,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;CACtE,MAAM,SAAS;AACf,MAAK,MAAM,SAAS;EAAC;EAAW;EAAa;EAAiB;EAAW,CACvE,KAAI,OAAO,SAAS,WAAW,YAAY,OAAO,OAAO,WAAW,EAClE,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,QAAO"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_src = require("./src-BIL0PdSd.cjs");
|
|
3
|
+
const require_handshake = require("./handshake-CggSIoxF.cjs");
|
|
4
|
+
exports.SESSION_COOKIE_NAME = require_src.SESSION_COOKIE_NAME;
|
|
5
|
+
exports.applyAppEnvTemplate = require_handshake.applyAppEnvTemplate;
|
|
6
|
+
exports.bootLocalServer = require_src.bootLocalServer;
|
|
7
|
+
exports.bootstrapProject = require_src.bootstrapProject;
|
|
8
|
+
exports.connectZitadel = require_src.connectZitadel;
|
|
9
|
+
exports.nextAppEnv = require_handshake.nextAppEnv;
|
|
10
|
+
exports.readHandshakeSync = require_handshake.readHandshakeSync;
|
|
11
|
+
exports.startLocalZitadel = require_src.startLocalZitadel;
|
|
12
|
+
exports.waitForHandshake = require_handshake.waitForHandshake;
|
|
13
|
+
exports.writeHandshake = require_handshake.writeHandshake;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
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-D3W0GYhA.mjs";
|
|
2
|
+
import { ZitadelClient } from "@zitadel/api/client";
|
|
3
|
+
import { SetupPreset, SetupUseCase } from "@zitadel/config/defaults";
|
|
4
|
+
|
|
5
|
+
//#region src/bootstrap.d.ts
|
|
6
|
+
interface BootstrapProjectOptions {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
projectName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Origins of the apps that will proxy to this instance. The backend's
|
|
11
|
+
* origin check rejects forwarded requests from unregistered origins.
|
|
12
|
+
*/
|
|
13
|
+
appOrigins?: string[];
|
|
14
|
+
preset?: SetupPreset;
|
|
15
|
+
useCase?: SetupUseCase;
|
|
16
|
+
}
|
|
17
|
+
interface BootstrappedProject {
|
|
18
|
+
projectId: string;
|
|
19
|
+
projectSecret: string;
|
|
20
|
+
previewSecret?: string;
|
|
21
|
+
schemaId: string;
|
|
22
|
+
flowId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Server-side half of `zitadel setup`, without any file scaffolding:
|
|
26
|
+
* `POST /projects` is unauthenticated and mints the projectSecret used as the
|
|
27
|
+
* bearer for everything else; the schema is uploaded without `$id` so the
|
|
28
|
+
* server assigns an opaque id, which the flow must then reference.
|
|
29
|
+
*/
|
|
30
|
+
declare function bootstrapProject(options: BootstrapProjectOptions): Promise<BootstrappedProject>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/lifecycle.d.ts
|
|
33
|
+
interface BootServerOptions {
|
|
34
|
+
/** TCP port for the instance; defaults to an OS-assigned free port. */
|
|
35
|
+
port?: number;
|
|
36
|
+
/**
|
|
37
|
+
* State directory. Defaults to a fresh temp dir that is removed on stop;
|
|
38
|
+
* a caller-provided dir is never removed.
|
|
39
|
+
*/
|
|
40
|
+
dir?: string;
|
|
41
|
+
/** Forwarded as ZITADEL_SERVER_BINARY (in-repo runs use dist/server/nextgen). */
|
|
42
|
+
serverBinary?: string;
|
|
43
|
+
/** Keep the owned temp dir after stop (debugging). */
|
|
44
|
+
keep?: boolean;
|
|
45
|
+
/** Test seam: alternative CLI entry script. */
|
|
46
|
+
cliBin?: string;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}
|
|
49
|
+
interface BootedServer {
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
runtime: LocalZitadelRuntime;
|
|
52
|
+
stop(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Boot an ephemeral local server by shelling out to `zitadel start` and parse
|
|
56
|
+
* its JSON envelope. The CLI owns the subtle parts (port preflight, health
|
|
57
|
+
* wait, process-group stop, embedded-Postgres reaping), so this module stays a
|
|
58
|
+
* thin adapter; swapping it for direct library calls later must not change the
|
|
59
|
+
* shape returned here.
|
|
60
|
+
*/
|
|
61
|
+
declare function bootLocalServer(options?: BootServerOptions): Promise<BootedServer>;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/handshake.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* The handshake file carries an InstanceHandle across process boundaries:
|
|
66
|
+
* written by the script that boots + bootstraps the instance, read by
|
|
67
|
+
* Playwright workers (fixtures) and the app dev-server wrapper.
|
|
68
|
+
*/
|
|
69
|
+
declare function writeHandshake(path: string, handle: InstanceHandle): Promise<void>;
|
|
70
|
+
declare function readHandshakeSync(path: string): InstanceHandle;
|
|
71
|
+
declare function waitForHandshake(path: string, timeoutMs?: number): Promise<InstanceHandle>;
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/session.d.ts
|
|
74
|
+
/** Mirrors the server's session cookie (internal/api/session.go). */
|
|
75
|
+
declare const SESSION_COOKIE_NAME = "__nextgen_session";
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/index.d.ts
|
|
78
|
+
type StartLocalZitadelOptions = BootServerOptions & Omit<BootstrapProjectOptions, "baseUrl">;
|
|
79
|
+
/**
|
|
80
|
+
* Attach to an already-bootstrapped instance/project. Lifecycle-free on
|
|
81
|
+
* purpose: this is the entry point for Playwright workers (via the handshake
|
|
82
|
+
* file) and, later, for seeding remote instances.
|
|
83
|
+
*/
|
|
84
|
+
declare function connectZitadel(handle: InstanceHandle): ConnectedZitadel;
|
|
85
|
+
/**
|
|
86
|
+
* Boot an ephemeral local instance (binary runtime + embedded Postgres, no
|
|
87
|
+
* Docker) and bootstrap a project + default schema + login flow on it. The
|
|
88
|
+
* result can seed loginable password users immediately.
|
|
89
|
+
*/
|
|
90
|
+
declare function startLocalZitadel(options?: StartLocalZitadelOptions): Promise<LocalZitadel>;
|
|
91
|
+
//#endregion
|
|
92
|
+
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 };
|
|
93
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/bootstrap.ts","../src/lifecycle.ts","../src/handshake.ts","../src/session.ts","../src/index.ts"],"mappings":";;;;;UASiB,uBAAA;EACf,OAAA;EACA,WAAA;;AAFF;;;EAOE,UAAA;EACA,MAAA,GAAS,WAAA;EACT,OAAA,GAAU,YAAA;AAAA;AAAA,UAGK,mBAAA;EACf,SAAA;EACA,aAAA;EACA,aAAA;EACA,QAAA;EACA,MAAA;AAAA;;;;;;;iBAWoB,gBAAA,CACpB,OAAA,EAAS,uBAAA,GACR,OAAA,CAAQ,mBAAA;;;UCzBM,iBAAA;;EAEf,IAAA;;ADPF;;;ECYE,GAAA;EDXA;ECaA,YAAA;EDPA;ECSA,IAAA;EDRS;ECUT,MAAA;EACA,SAAA;AAAA;AAAA,UAGe,YAAA;EACf,OAAA;EACA,OAAA,EAAS,mBAAA;EACT,IAAA,IAAQ,OAAA;AAAA;;;;;;;;iBAUY,eAAA,CAAgB,OAAA,GAAS,iBAAA,GAAyB,OAAA,CAAQ,YAAA;;;;;;;ADnChF;iBEGsB,cAAA,CAAe,IAAA,UAAc,MAAA,EAAQ,cAAA,GAAiB,OAAA;AAAA,iBAK5D,iBAAA,CAAkB,IAAA,WAAe,cAAA;AAAA,iBAa3B,gBAAA,CAAiB,IAAA,UAAc,SAAA,YAAqB,OAAA,CAAQ,cAAA;;;;cCvBrE,mBAAA;;;KCED,wBAAA,GAA2B,iBAAA,GACrC,IAAA,CAAK,uBAAA;;AJDP;;;;iBIQgB,cAAA,CAAe,MAAA,EAAQ,cAAA,GAAiB,gBAAA;;;;;;iBA6BlC,iBAAA,CACpB,OAAA,GAAS,wBAAA,GACR,OAAA,CAAQ,YAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake, r as writeHandshake, t as readHandshakeSync } from "./handshake-BPtWruO8.mjs";
|
|
2
|
+
import { a as bootstrapProject, i as bootLocalServer, n as startLocalZitadel, r as SESSION_COOKIE_NAME, t as connectZitadel } from "./src-DkG0V4A6.mjs";
|
|
3
|
+
export { SESSION_COOKIE_NAME, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/orchestration.ts
|
|
2
|
+
/**
|
|
3
|
+
* The contract between `withZitadel()` (evaluated in the Playwright config
|
|
4
|
+
* process) and the two executables it points webServer entries at. Options
|
|
5
|
+
* travel as JSON in these env vars because a webServer command is a plain
|
|
6
|
+
* string — there is no richer channel.
|
|
7
|
+
*/
|
|
8
|
+
const SUPERVISOR_CONFIG_ENV = "ZITADEL_TESTING_SUPERVISOR";
|
|
9
|
+
const APP_RUNNER_CONFIG_ENV = "ZITADEL_TESTING_APP_RUNNER";
|
|
10
|
+
/** Read by the executables and the Playwright fixtures alike. */
|
|
11
|
+
const HANDSHAKE_ENV = "ZITADEL_TESTING_HANDSHAKE";
|
|
12
|
+
function parseSupervisorConfig(raw) {
|
|
13
|
+
const value = parseJsonEnv(raw, SUPERVISOR_CONFIG_ENV);
|
|
14
|
+
if (typeof value.port !== "number" || !Number.isInteger(value.port) || value.port <= 0) throw new Error(`${SUPERVISOR_CONFIG_ENV} must carry a positive integer "port"`);
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function parseAppRunnerConfig(raw) {
|
|
18
|
+
const value = parseJsonEnv(raw, APP_RUNNER_CONFIG_ENV);
|
|
19
|
+
if (!Array.isArray(value.command) || value.command.length === 0 || value.command.some((part) => typeof part !== "string" || part.length === 0)) throw new Error(`${APP_RUNNER_CONFIG_ENV} must carry a non-empty string[] "command"`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function requireHandshakePath(env) {
|
|
23
|
+
const path = env[HANDSHAKE_ENV];
|
|
24
|
+
if (!path) throw new Error(`${HANDSHAKE_ENV} is not set. These entry points are meant to be launched by the webServer entries that withZitadel() generates.`);
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
function parseJsonEnv(raw, name) {
|
|
28
|
+
if (!raw) throw new Error(`${name} is not set. This entry point is meant to be launched by the webServer entries that withZitadel() generates.`);
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`${name} contains invalid JSON: ${error.message}`, { cause: error });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { parseSupervisorConfig as a, parseAppRunnerConfig as i, HANDSHAKE_ENV as n, requireHandshakePath as o, SUPERVISOR_CONFIG_ENV as r, APP_RUNNER_CONFIG_ENV as t };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=orchestration-C640_1Lg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestration-C640_1Lg.mjs","names":[],"sources":["../src/orchestration.ts"],"sourcesContent":["import type { SetupPreset, SetupUseCase } from \"@zitadel/config/defaults\";\n\nimport type { AppEnvTemplate } from \"./app-env\";\n\n/**\n * The contract between `withZitadel()` (evaluated in the Playwright config\n * process) and the two executables it points webServer entries at. Options\n * travel as JSON in these env vars because a webServer command is a plain\n * string — there is no richer channel.\n */\nexport const SUPERVISOR_CONFIG_ENV = \"ZITADEL_TESTING_SUPERVISOR\";\nexport const APP_RUNNER_CONFIG_ENV = \"ZITADEL_TESTING_APP_RUNNER\";\n/** Read by the executables and the Playwright fixtures alike. */\nexport const HANDSHAKE_ENV = \"ZITADEL_TESTING_HANDSHAKE\";\n\nexport interface SupervisorConfig {\n /** Fixed port: the config process must know the health URL up front. */\n port: number;\n appOrigins?: string[];\n serverBinary?: string;\n /** Appended to the missing-binary error, e.g. how to build it. */\n serverBinaryHint?: string;\n dir?: string;\n keep?: boolean;\n projectName?: string;\n preset?: SetupPreset;\n useCase?: SetupUseCase;\n timeoutMs?: number;\n}\n\nexport interface AppRunnerConfig {\n /** Spawn argv; executed without a shell. */\n command: string[];\n cwd?: string;\n env?: AppEnvTemplate;\n handshakeTimeoutMs?: number;\n}\n\nexport function parseSupervisorConfig(raw: string | undefined): SupervisorConfig {\n const value = parseJsonEnv(raw, SUPERVISOR_CONFIG_ENV) as Partial<SupervisorConfig>;\n if (typeof value.port !== \"number\" || !Number.isInteger(value.port) || value.port <= 0) {\n throw new Error(`${SUPERVISOR_CONFIG_ENV} must carry a positive integer \"port\"`);\n }\n return value as SupervisorConfig;\n}\n\nexport function parseAppRunnerConfig(raw: string | undefined): AppRunnerConfig {\n const value = parseJsonEnv(raw, APP_RUNNER_CONFIG_ENV) as Partial<AppRunnerConfig>;\n if (\n !Array.isArray(value.command) ||\n value.command.length === 0 ||\n value.command.some((part) => typeof part !== \"string\" || part.length === 0)\n ) {\n throw new Error(`${APP_RUNNER_CONFIG_ENV} must carry a non-empty string[] \"command\"`);\n }\n return value as AppRunnerConfig;\n}\n\nexport function requireHandshakePath(env: NodeJS.ProcessEnv): string {\n const path = env[HANDSHAKE_ENV];\n if (!path) {\n throw new Error(\n `${HANDSHAKE_ENV} is not set. These entry points are meant to be launched ` +\n `by the webServer entries that withZitadel() generates.`,\n );\n }\n return path;\n}\n\nfunction parseJsonEnv(raw: string | undefined, name: string): unknown {\n if (!raw) {\n throw new Error(\n `${name} is not set. This entry point is meant to be launched by the ` +\n `webServer entries that withZitadel() generates.`,\n );\n }\n try {\n return JSON.parse(raw);\n } catch (error) {\n throw new Error(`${name} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAa,wBAAwB;AACrC,MAAa,wBAAwB;;AAErC,MAAa,gBAAgB;AAyB7B,SAAgB,sBAAsB,KAA2C;CAC/E,MAAM,QAAQ,aAAa,KAAK,sBAAsB;AACtD,KAAI,OAAO,MAAM,SAAS,YAAY,CAAC,OAAO,UAAU,MAAM,KAAK,IAAI,MAAM,QAAQ,EACnF,OAAM,IAAI,MAAM,GAAG,sBAAsB,uCAAuC;AAElF,QAAO;;AAGT,SAAgB,qBAAqB,KAA0C;CAC7E,MAAM,QAAQ,aAAa,KAAK,sBAAsB;AACtD,KACE,CAAC,MAAM,QAAQ,MAAM,QAAQ,IAC7B,MAAM,QAAQ,WAAW,KACzB,MAAM,QAAQ,MAAM,SAAS,OAAO,SAAS,YAAY,KAAK,WAAW,EAAE,CAE3E,OAAM,IAAI,MAAM,GAAG,sBAAsB,4CAA4C;AAEvF,QAAO;;AAGT,SAAgB,qBAAqB,KAAgC;CACnE,MAAM,OAAO,IAAI;AACjB,KAAI,CAAC,KACH,OAAM,IAAI,MACR,GAAG,cAAc,iHAElB;AAEH,QAAO;;AAGT,SAAS,aAAa,KAAyB,MAAuB;AACpE,KAAI,CAAC,IACH,OAAM,IAAI,MACR,GAAG,KAAK,8GAET;AAEH,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,OAAO;AACd,QAAM,IAAI,MAAM,GAAG,KAAK,0BAA2B,MAAgB,WAAW,EAC5E,OAAO,OACR,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/orchestration.ts
|
|
2
|
+
/**
|
|
3
|
+
* The contract between `withZitadel()` (evaluated in the Playwright config
|
|
4
|
+
* process) and the two executables it points webServer entries at. Options
|
|
5
|
+
* travel as JSON in these env vars because a webServer command is a plain
|
|
6
|
+
* string — there is no richer channel.
|
|
7
|
+
*/
|
|
8
|
+
const SUPERVISOR_CONFIG_ENV = "ZITADEL_TESTING_SUPERVISOR";
|
|
9
|
+
const APP_RUNNER_CONFIG_ENV = "ZITADEL_TESTING_APP_RUNNER";
|
|
10
|
+
/** Read by the executables and the Playwright fixtures alike. */
|
|
11
|
+
const HANDSHAKE_ENV = "ZITADEL_TESTING_HANDSHAKE";
|
|
12
|
+
function parseSupervisorConfig(raw) {
|
|
13
|
+
const value = parseJsonEnv(raw, SUPERVISOR_CONFIG_ENV);
|
|
14
|
+
if (typeof value.port !== "number" || !Number.isInteger(value.port) || value.port <= 0) throw new Error(`${SUPERVISOR_CONFIG_ENV} must carry a positive integer "port"`);
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function parseAppRunnerConfig(raw) {
|
|
18
|
+
const value = parseJsonEnv(raw, APP_RUNNER_CONFIG_ENV);
|
|
19
|
+
if (!Array.isArray(value.command) || value.command.length === 0 || value.command.some((part) => typeof part !== "string" || part.length === 0)) throw new Error(`${APP_RUNNER_CONFIG_ENV} must carry a non-empty string[] "command"`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function requireHandshakePath(env) {
|
|
23
|
+
const path = env[HANDSHAKE_ENV];
|
|
24
|
+
if (!path) throw new Error(`${HANDSHAKE_ENV} is not set. These entry points are meant to be launched by the webServer entries that withZitadel() generates.`);
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
function parseJsonEnv(raw, name) {
|
|
28
|
+
if (!raw) throw new Error(`${name} is not set. This entry point is meant to be launched by the webServer entries that withZitadel() generates.`);
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`${name} contains invalid JSON: ${error.message}`, { cause: error });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
Object.defineProperty(exports, "APP_RUNNER_CONFIG_ENV", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function() {
|
|
39
|
+
return APP_RUNNER_CONFIG_ENV;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, "HANDSHAKE_ENV", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function() {
|
|
45
|
+
return HANDSHAKE_ENV;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "SUPERVISOR_CONFIG_ENV", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function() {
|
|
51
|
+
return SUPERVISOR_CONFIG_ENV;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "parseAppRunnerConfig", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function() {
|
|
57
|
+
return parseAppRunnerConfig;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(exports, "parseSupervisorConfig", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function() {
|
|
63
|
+
return parseSupervisorConfig;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
Object.defineProperty(exports, "requireHandshakePath", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
get: function() {
|
|
69
|
+
return requireHandshakePath;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=orchestration-D7QBgQ0l.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestration-D7QBgQ0l.cjs","names":[],"sources":["../src/orchestration.ts"],"sourcesContent":["import type { SetupPreset, SetupUseCase } from \"@zitadel/config/defaults\";\n\nimport type { AppEnvTemplate } from \"./app-env\";\n\n/**\n * The contract between `withZitadel()` (evaluated in the Playwright config\n * process) and the two executables it points webServer entries at. Options\n * travel as JSON in these env vars because a webServer command is a plain\n * string — there is no richer channel.\n */\nexport const SUPERVISOR_CONFIG_ENV = \"ZITADEL_TESTING_SUPERVISOR\";\nexport const APP_RUNNER_CONFIG_ENV = \"ZITADEL_TESTING_APP_RUNNER\";\n/** Read by the executables and the Playwright fixtures alike. */\nexport const HANDSHAKE_ENV = \"ZITADEL_TESTING_HANDSHAKE\";\n\nexport interface SupervisorConfig {\n /** Fixed port: the config process must know the health URL up front. */\n port: number;\n appOrigins?: string[];\n serverBinary?: string;\n /** Appended to the missing-binary error, e.g. how to build it. */\n serverBinaryHint?: string;\n dir?: string;\n keep?: boolean;\n projectName?: string;\n preset?: SetupPreset;\n useCase?: SetupUseCase;\n timeoutMs?: number;\n}\n\nexport interface AppRunnerConfig {\n /** Spawn argv; executed without a shell. */\n command: string[];\n cwd?: string;\n env?: AppEnvTemplate;\n handshakeTimeoutMs?: number;\n}\n\nexport function parseSupervisorConfig(raw: string | undefined): SupervisorConfig {\n const value = parseJsonEnv(raw, SUPERVISOR_CONFIG_ENV) as Partial<SupervisorConfig>;\n if (typeof value.port !== \"number\" || !Number.isInteger(value.port) || value.port <= 0) {\n throw new Error(`${SUPERVISOR_CONFIG_ENV} must carry a positive integer \"port\"`);\n }\n return value as SupervisorConfig;\n}\n\nexport function parseAppRunnerConfig(raw: string | undefined): AppRunnerConfig {\n const value = parseJsonEnv(raw, APP_RUNNER_CONFIG_ENV) as Partial<AppRunnerConfig>;\n if (\n !Array.isArray(value.command) ||\n value.command.length === 0 ||\n value.command.some((part) => typeof part !== \"string\" || part.length === 0)\n ) {\n throw new Error(`${APP_RUNNER_CONFIG_ENV} must carry a non-empty string[] \"command\"`);\n }\n return value as AppRunnerConfig;\n}\n\nexport function requireHandshakePath(env: NodeJS.ProcessEnv): string {\n const path = env[HANDSHAKE_ENV];\n if (!path) {\n throw new Error(\n `${HANDSHAKE_ENV} is not set. These entry points are meant to be launched ` +\n `by the webServer entries that withZitadel() generates.`,\n );\n }\n return path;\n}\n\nfunction parseJsonEnv(raw: string | undefined, name: string): unknown {\n if (!raw) {\n throw new Error(\n `${name} is not set. This entry point is meant to be launched by the ` +\n `webServer entries that withZitadel() generates.`,\n );\n }\n try {\n return JSON.parse(raw);\n } catch (error) {\n throw new Error(`${name} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAa,wBAAwB;AACrC,MAAa,wBAAwB;;AAErC,MAAa,gBAAgB;AAyB7B,SAAgB,sBAAsB,KAA2C;CAC/E,MAAM,QAAQ,aAAa,KAAK,sBAAsB;AACtD,KAAI,OAAO,MAAM,SAAS,YAAY,CAAC,OAAO,UAAU,MAAM,KAAK,IAAI,MAAM,QAAQ,EACnF,OAAM,IAAI,MAAM,GAAG,sBAAsB,uCAAuC;AAElF,QAAO;;AAGT,SAAgB,qBAAqB,KAA0C;CAC7E,MAAM,QAAQ,aAAa,KAAK,sBAAsB;AACtD,KACE,CAAC,MAAM,QAAQ,MAAM,QAAQ,IAC7B,MAAM,QAAQ,WAAW,KACzB,MAAM,QAAQ,MAAM,SAAS,OAAO,SAAS,YAAY,KAAK,WAAW,EAAE,CAE3E,OAAM,IAAI,MAAM,GAAG,sBAAsB,4CAA4C;AAEvF,QAAO;;AAGT,SAAgB,qBAAqB,KAAgC;CACnE,MAAM,OAAO,IAAI;AACjB,KAAI,CAAC,KACH,OAAM,IAAI,MACR,GAAG,cAAc,iHAElB;AAEH,QAAO;;AAGT,SAAS,aAAa,KAAyB,MAAuB;AACpE,KAAI,CAAC,IACH,OAAM,IAAI,MACR,GAAG,KAAK,8GAET;AAEH,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,OAAO;AACd,QAAM,IAAI,MAAM,GAAG,KAAK,0BAA2B,MAAgB,WAAW,EAC5E,OAAO,OACR,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_src = require("./src-BIL0PdSd.cjs");
|
|
3
|
+
const require_handshake = require("./handshake-CggSIoxF.cjs");
|
|
4
|
+
const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
let node_url = require("node:url");
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
let _playwright_test = require("@playwright/test");
|
|
9
|
+
//#region src/playwright-config.ts
|
|
10
|
+
/**
|
|
11
|
+
* Generate the Playwright `webServer` entries that boot an ephemeral seeded
|
|
12
|
+
* Zitadel and run the app against it, replacing the per-suite wrapper
|
|
13
|
+
* scripts. Spread the result into `defineConfig`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* ...withZitadel({ configDir: import.meta.dirname, port: 8092, ... }),
|
|
18
|
+
* testDir: "./src-real",
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the
|
|
23
|
+
* `@zitadel/testing/playwright` fixtures resolve the instance — Playwright
|
|
24
|
+
* workers re-evaluate the config, which re-applies this for every process
|
|
25
|
+
* that needs it. The returned value is plain data; append your own entries
|
|
26
|
+
* to `webServer` if the suite needs additional servers.
|
|
27
|
+
*/
|
|
28
|
+
function withZitadel(options, resolveEntry = entryPoint) {
|
|
29
|
+
const { configDir, port, appOrigin, app } = options;
|
|
30
|
+
if (!(0, node_path.isAbsolute)(configDir)) throw new Error(`withZitadel: configDir must be absolute, got "${configDir}"`);
|
|
31
|
+
if (!Number.isInteger(port) || port <= 0) throw new Error(`withZitadel: port must be a positive integer, got ${port}`);
|
|
32
|
+
const origin = URL.canParse(appOrigin) ? new URL(appOrigin) : void 0;
|
|
33
|
+
if (!origin || origin.protocol !== "http:" && origin.protocol !== "https:" || origin.pathname !== "/" || origin.search !== "" || origin.hash !== "") throw new Error(`withZitadel: appOrigin must be an origin like "http://localhost:3002", got "${appOrigin}"`);
|
|
34
|
+
if (!app.readyPath.startsWith("/")) throw new Error(`withZitadel: app.readyPath must start with "/", got "${app.readyPath}"`);
|
|
35
|
+
if (app.command.length === 0) throw new Error("withZitadel: app.command must not be empty");
|
|
36
|
+
if (!(0, node_path.isAbsolute)(app.cwd)) throw new Error(`withZitadel: app.cwd must be absolute, got "${app.cwd}"`);
|
|
37
|
+
for (const [label, value] of [
|
|
38
|
+
["zitadel.serverBinary", options.zitadel?.serverBinary],
|
|
39
|
+
["zitadel.dir", options.zitadel?.dir],
|
|
40
|
+
["handshakePath", options.handshakePath]
|
|
41
|
+
]) if (value !== void 0 && !(0, node_path.isAbsolute)(value)) throw new Error(`withZitadel: ${label} must be an absolute path, got "${value}"`);
|
|
42
|
+
const handshakePath = options.handshakePath ?? (0, node_path.join)(configDir, ".zitadel-testing", "handshake.json");
|
|
43
|
+
process.env[require_orchestration.HANDSHAKE_ENV] = handshakePath;
|
|
44
|
+
const supervisorConfig = {
|
|
45
|
+
port,
|
|
46
|
+
appOrigins: [appOrigin],
|
|
47
|
+
serverBinary: options.zitadel?.serverBinary,
|
|
48
|
+
serverBinaryHint: options.zitadel?.serverBinaryHint,
|
|
49
|
+
dir: options.zitadel?.dir,
|
|
50
|
+
keep: options.zitadel?.keep,
|
|
51
|
+
projectName: options.zitadel?.projectName,
|
|
52
|
+
preset: options.zitadel?.preset,
|
|
53
|
+
useCase: options.zitadel?.useCase
|
|
54
|
+
};
|
|
55
|
+
const appRunnerConfig = {
|
|
56
|
+
command: app.command,
|
|
57
|
+
cwd: app.cwd,
|
|
58
|
+
env: app.env,
|
|
59
|
+
handshakeTimeoutMs: app.readyTimeoutMs ?? 18e4
|
|
60
|
+
};
|
|
61
|
+
return { webServer: [{
|
|
62
|
+
command: `node ${JSON.stringify(resolveEntry("supervisor"))}`,
|
|
63
|
+
url: `http://localhost:${port}/healthz`,
|
|
64
|
+
reuseExistingServer: false,
|
|
65
|
+
cwd: configDir,
|
|
66
|
+
stdout: "pipe",
|
|
67
|
+
stderr: "pipe",
|
|
68
|
+
timeout: options.zitadel?.bootTimeoutMs ?? 12e4,
|
|
69
|
+
env: {
|
|
70
|
+
[require_orchestration.HANDSHAKE_ENV]: handshakePath,
|
|
71
|
+
[require_orchestration.SUPERVISOR_CONFIG_ENV]: JSON.stringify(supervisorConfig)
|
|
72
|
+
},
|
|
73
|
+
gracefulShutdown: {
|
|
74
|
+
signal: "SIGTERM",
|
|
75
|
+
timeout: 3e4
|
|
76
|
+
}
|
|
77
|
+
}, {
|
|
78
|
+
command: `node ${JSON.stringify(resolveEntry("app-runner"))}`,
|
|
79
|
+
url: new URL(app.readyPath, appOrigin).toString(),
|
|
80
|
+
reuseExistingServer: false,
|
|
81
|
+
cwd: configDir,
|
|
82
|
+
stdout: "pipe",
|
|
83
|
+
stderr: "pipe",
|
|
84
|
+
timeout: app.readyTimeoutMs ?? 18e4,
|
|
85
|
+
env: {
|
|
86
|
+
[require_orchestration.HANDSHAKE_ENV]: handshakePath,
|
|
87
|
+
[require_orchestration.APP_RUNNER_CONFIG_ENV]: JSON.stringify(appRunnerConfig)
|
|
88
|
+
},
|
|
89
|
+
gracefulShutdown: {
|
|
90
|
+
signal: "SIGTERM",
|
|
91
|
+
timeout: app.gracefulShutdownMs ?? 15e3
|
|
92
|
+
}
|
|
93
|
+
}] };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a sibling dist entry in the same module format this file was loaded
|
|
97
|
+
* as (dist/supervisor.mjs next to dist/playwright.mjs, .cjs next to .cjs), so
|
|
98
|
+
* the spawned process needs no package-manager bin plumbing.
|
|
99
|
+
*/
|
|
100
|
+
function entryPoint(name) {
|
|
101
|
+
const self = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
102
|
+
const ext = (0, node_path.extname)(self);
|
|
103
|
+
if (ext !== ".mjs" && ext !== ".cjs") throw new Error(`withZitadel: expected to run from the built package (got ${self}); build @zitadel/testing first (in-repo: \`moon run testing:build\`).`);
|
|
104
|
+
const path = (0, node_url.fileURLToPath)(new URL(`./${name}${ext}`, require("url").pathToFileURL(__filename).href));
|
|
105
|
+
if (!(0, node_fs.existsSync)(path)) throw new Error(`withZitadel: missing ${path}; rebuild @zitadel/testing (in-repo: \`moon run testing:build\`).`);
|
|
106
|
+
return path;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/playwright.ts
|
|
110
|
+
const test = _playwright_test.test.extend({
|
|
111
|
+
zitadel: [async ({}, use) => {
|
|
112
|
+
const handshakePath = process.env.ZITADEL_TESTING_HANDSHAKE;
|
|
113
|
+
if (!handshakePath) throw new Error("ZITADEL_TESTING_HANDSHAKE is not set. Point it at the handshake file written by the script that boots the instance (see @zitadel/testing docs).");
|
|
114
|
+
await use(require_src.connectZitadel(require_handshake.readHandshakeSync(handshakePath)));
|
|
115
|
+
}, { scope: "worker" }],
|
|
116
|
+
seed: async ({ zitadel, baseURL }, use) => {
|
|
117
|
+
await use({
|
|
118
|
+
user: (input) => zitadel.seedUser(input),
|
|
119
|
+
users: (count, template) => zitadel.seedUsers(count, template),
|
|
120
|
+
identity: () => zitadel.identity(),
|
|
121
|
+
session: (input) => zitadel.seedSession({
|
|
122
|
+
origin: baseURL,
|
|
123
|
+
...input
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
authenticatedPage: async ({ browser, zitadel, baseURL }, use) => {
|
|
128
|
+
if (!baseURL) throw new Error("authenticatedPage requires `use.baseURL` so the session cookie can be scoped to the app under test.");
|
|
129
|
+
const session = await zitadel.seedSession({ origin: baseURL });
|
|
130
|
+
const context = await browser.newContext({ baseURL });
|
|
131
|
+
const { path: _path, ...cookie } = session.cookie;
|
|
132
|
+
await context.addCookies([{
|
|
133
|
+
...cookie,
|
|
134
|
+
url: baseURL
|
|
135
|
+
}]);
|
|
136
|
+
await use({
|
|
137
|
+
page: await context.newPage(),
|
|
138
|
+
user: session.user,
|
|
139
|
+
session
|
|
140
|
+
});
|
|
141
|
+
await context.close();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
//#endregion
|
|
145
|
+
exports.applyAppEnvTemplate = require_handshake.applyAppEnvTemplate;
|
|
146
|
+
Object.defineProperty(exports, "expect", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
get: function() {
|
|
149
|
+
return _playwright_test.expect;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
exports.nextAppEnv = require_handshake.nextAppEnv;
|
|
153
|
+
exports.test = test;
|
|
154
|
+
exports.withZitadel = withZitadel;
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=playwright.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright.cjs","names":["HANDSHAKE_ENV","SUPERVISOR_CONFIG_ENV","APP_RUNNER_CONFIG_ENV","base","connectZitadel","readHandshakeSync"],"sources":["../src/playwright-config.ts","../src/playwright.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { extname, isAbsolute, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { PlaywrightTestConfig } from \"@playwright/test\";\n\nimport type { AppEnvTemplate } from \"./app-env\";\nimport {\n APP_RUNNER_CONFIG_ENV,\n HANDSHAKE_ENV,\n SUPERVISOR_CONFIG_ENV,\n type AppRunnerConfig,\n type SupervisorConfig,\n} from \"./orchestration\";\n\ntype WebServerEntry = Extract<\n NonNullable<PlaywrightTestConfig[\"webServer\"]>,\n readonly unknown[]\n>[number];\n\nexport interface WithZitadelOptions {\n /**\n * The Playwright config's directory (`import.meta.dirname`). Anchors the\n * default handshake location and the working directory of the generated\n * webServer entries.\n */\n configDir: string;\n /**\n * Fixed TCP port for the instance. Required (unlike `startLocalZitadel`,\n * which defaults to a free port) because Playwright's readiness URL must be\n * known while the config is evaluated, before anything boots.\n */\n port: number;\n /**\n * Origin the browser will use for the app under test. Registered as the\n * project's preview origin (the backend's origin check rejects forwarded\n * requests from unregistered origins) and the base of `app.readyPath`.\n */\n appOrigin: string;\n /** Boot/bootstrap options forwarded to the instance supervisor. */\n zitadel?: {\n /** Absolute path to the server binary. */\n serverBinary?: string;\n /** Appended to the missing-binary error, e.g. \"run `moon run server:build` first.\" */\n serverBinaryHint?: string;\n projectName?: string;\n preset?: SupervisorConfig[\"preset\"];\n useCase?: SupervisorConfig[\"useCase\"];\n /** Absolute state directory; defaults to a fresh temp dir removed on stop. */\n dir?: string;\n /** Keep the owned temp dir after stop (debugging). */\n keep?: boolean;\n /** webServer readiness timeout for the boot; cold boot dominates it. */\n bootTimeoutMs?: number;\n };\n /** The app dev server to run against the instance. */\n app: {\n /** Spawn argv (no shell), e.g. [\"corepack\", \"pnpm\", \"--filter\", \"my-app\", \"dev\"]. */\n command: string[];\n /** Working directory for the app command. */\n cwd: string;\n /** Path on `appOrigin` Playwright polls for readiness, e.g. \"/login\". */\n readyPath: string;\n /**\n * Env vars the app needs, as a template mapping env names to\n * InstanceHandle fields — see `nextAppEnv` for the `@zitadel/sdk-next`\n * shape. A template (not a callback) because it crosses into the app\n * runner process.\n */\n env: AppEnvTemplate;\n readyTimeoutMs?: number;\n /** How long SIGTERM gets before the app is killed on teardown. */\n gracefulShutdownMs?: number;\n };\n /** Absolute path; defaults to `<configDir>/.zitadel-testing/handshake.json`. */\n handshakePath?: string;\n}\n\n/**\n * Generate the Playwright `webServer` entries that boot an ephemeral seeded\n * Zitadel and run the app against it, replacing the per-suite wrapper\n * scripts. Spread the result into `defineConfig`:\n *\n * ```ts\n * export default defineConfig({\n * ...withZitadel({ configDir: import.meta.dirname, port: 8092, ... }),\n * testDir: \"./src-real\",\n * });\n * ```\n *\n * Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the\n * `@zitadel/testing/playwright` fixtures resolve the instance — Playwright\n * workers re-evaluate the config, which re-applies this for every process\n * that needs it. The returned value is plain data; append your own entries\n * to `webServer` if the suite needs additional servers.\n */\nexport function withZitadel(\n options: WithZitadelOptions,\n /** Test seam: alternative executable resolution. */\n resolveEntry: (name: \"supervisor\" | \"app-runner\") => string = entryPoint,\n): { webServer: WebServerEntry[] } {\n const { configDir, port, appOrigin, app } = options;\n if (!isAbsolute(configDir)) {\n throw new Error(`withZitadel: configDir must be absolute, got \"${configDir}\"`);\n }\n if (!Number.isInteger(port) || port <= 0) {\n throw new Error(`withZitadel: port must be a positive integer, got ${port}`);\n }\n const origin = URL.canParse(appOrigin) ? new URL(appOrigin) : undefined;\n if (\n !origin ||\n (origin.protocol !== \"http:\" && origin.protocol !== \"https:\") ||\n origin.pathname !== \"/\" ||\n origin.search !== \"\" ||\n origin.hash !== \"\"\n ) {\n throw new Error(\n `withZitadel: appOrigin must be an origin like \"http://localhost:3002\", got \"${appOrigin}\"`,\n );\n }\n if (!app.readyPath.startsWith(\"/\")) {\n throw new Error(`withZitadel: app.readyPath must start with \"/\", got \"${app.readyPath}\"`);\n }\n if (app.command.length === 0) {\n throw new Error(\"withZitadel: app.command must not be empty\");\n }\n if (!isAbsolute(app.cwd)) {\n throw new Error(`withZitadel: app.cwd must be absolute, got \"${app.cwd}\"`);\n }\n // Path options are consumed by the executables, whose cwd is configDir —\n // a relative path would silently resolve against that, not the project.\n for (const [label, value] of [\n [\"zitadel.serverBinary\", options.zitadel?.serverBinary],\n [\"zitadel.dir\", options.zitadel?.dir],\n [\"handshakePath\", options.handshakePath],\n ] as const) {\n if (value !== undefined && !isAbsolute(value)) {\n throw new Error(`withZitadel: ${label} must be an absolute path, got \"${value}\"`);\n }\n }\n\n const handshakePath =\n options.handshakePath ?? join(configDir, \".zitadel-testing\", \"handshake.json\");\n // Workers inherit the runner's env; the fixtures resolve the instance from it.\n process.env[HANDSHAKE_ENV] = handshakePath;\n\n const supervisorConfig: SupervisorConfig = {\n port,\n appOrigins: [appOrigin],\n serverBinary: options.zitadel?.serverBinary,\n serverBinaryHint: options.zitadel?.serverBinaryHint,\n dir: options.zitadel?.dir,\n keep: options.zitadel?.keep,\n projectName: options.zitadel?.projectName,\n preset: options.zitadel?.preset,\n useCase: options.zitadel?.useCase,\n };\n const appRunnerConfig: AppRunnerConfig = {\n command: app.command,\n cwd: app.cwd,\n env: app.env,\n handshakeTimeoutMs: app.readyTimeoutMs ?? 180_000,\n };\n\n return {\n webServer: [\n {\n command: `node ${JSON.stringify(resolveEntry(\"supervisor\"))}`,\n url: `http://localhost:${port}/healthz`,\n reuseExistingServer: false,\n cwd: configDir,\n stdout: \"pipe\",\n stderr: \"pipe\",\n // Cold boot (fresh data dir: migrations + health wait) dominates.\n timeout: options.zitadel?.bootTimeoutMs ?? 120_000,\n env: {\n [HANDSHAKE_ENV]: handshakePath,\n [SUPERVISOR_CONFIG_ENV]: JSON.stringify(supervisorConfig),\n },\n // SIGTERM first so the supervisor can stop the instance; the default\n // hard kill would orphan the server process group.\n gracefulShutdown: { signal: \"SIGTERM\", timeout: 30_000 },\n },\n {\n command: `node ${JSON.stringify(resolveEntry(\"app-runner\"))}`,\n url: new URL(app.readyPath, appOrigin).toString(),\n reuseExistingServer: false,\n cwd: configDir,\n stdout: \"pipe\",\n stderr: \"pipe\",\n timeout: app.readyTimeoutMs ?? 180_000,\n env: {\n [HANDSHAKE_ENV]: handshakePath,\n [APP_RUNNER_CONFIG_ENV]: JSON.stringify(appRunnerConfig),\n },\n gracefulShutdown: {\n signal: \"SIGTERM\",\n timeout: app.gracefulShutdownMs ?? 15_000,\n },\n },\n ],\n };\n}\n\n/**\n * Resolve a sibling dist entry in the same module format this file was loaded\n * as (dist/supervisor.mjs next to dist/playwright.mjs, .cjs next to .cjs), so\n * the spawned process needs no package-manager bin plumbing.\n */\nfunction entryPoint(name: \"supervisor\" | \"app-runner\"): string {\n const self = fileURLToPath(import.meta.url);\n const ext = extname(self);\n if (ext !== \".mjs\" && ext !== \".cjs\") {\n throw new Error(\n `withZitadel: expected to run from the built package (got ${self}); ` +\n \"build @zitadel/testing first (in-repo: `moon run testing:build`).\",\n );\n }\n const path = fileURLToPath(new URL(`./${name}${ext}`, import.meta.url));\n if (!existsSync(path)) {\n throw new Error(\n `withZitadel: missing ${path}; rebuild @zitadel/testing (in-repo: \\`moon run testing:build\\`).`,\n );\n }\n return path;\n}\n","import { test as base, type Page } from \"@playwright/test\";\n\nimport { readHandshakeSync } from \"./handshake\";\nimport { connectZitadel } from \"./index\";\nimport type {\n ConnectedZitadel,\n Identity,\n MintedSession,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n} from \"./types\";\n\nexport interface AuthenticatedPage {\n /** A page in its own context, already carrying the session cookie. */\n page: Page;\n user: SeededUser;\n session: MintedSession;\n}\n\nexport interface ZitadelTestFixtures {\n /** Per-test seeding; each call mints unique data on the shared instance. */\n seed: {\n user(input?: SeedUserInput): Promise<SeededUser>;\n users(count: number, template?: SeedUsersTemplate): Promise<SeededUser[]>;\n /** Unused email+password for registration flows — creates nothing. */\n identity(): Identity;\n /** Seeded user + headless real-flow login; password flows only. */\n session(input?: SeedSessionInput): Promise<MintedSession>;\n };\n /**\n * Start the test authenticated: a fresh user, a real session minted through\n * the flow API, and the cookie injected into a dedicated browser context —\n * the default `page` stays signed out for login-flow tests. Requires\n * `use.baseURL` (every withZitadel consumer sets it).\n */\n authenticatedPage: AuthenticatedPage;\n}\n\nexport interface ZitadelWorkerFixtures {\n /** Connection to the suite's instance, resolved once per worker. */\n zitadel: ConnectedZitadel;\n}\n\nexport const test = base.extend<ZitadelTestFixtures, ZitadelWorkerFixtures>({\n zitadel: [\n // Playwright derives fixture dependencies from the destructuring pattern,\n // so the empty pattern is required here.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n const handshakePath = process.env.ZITADEL_TESTING_HANDSHAKE;\n if (!handshakePath) {\n throw new Error(\n \"ZITADEL_TESTING_HANDSHAKE is not set. Point it at the handshake file \" +\n \"written by the script that boots the instance (see @zitadel/testing docs).\",\n );\n }\n await use(connectZitadel(readHandshakeSync(handshakePath)));\n },\n { scope: \"worker\" },\n ],\n seed: async ({ zitadel, baseURL }, use) => {\n await use({\n user: (input) => zitadel.seedUser(input),\n users: (count, template) => zitadel.seedUsers(count, template),\n identity: () => zitadel.identity(),\n // The suite's baseURL is the app origin the project allowlists.\n session: (input) => zitadel.seedSession({ origin: baseURL, ...input }),\n });\n },\n authenticatedPage: async ({ browser, zitadel, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n \"authenticatedPage requires `use.baseURL` so the session cookie can be \" +\n \"scoped to the app under test.\",\n );\n }\n const session = await zitadel.seedSession({ origin: baseURL });\n const context = await browser.newContext({ baseURL });\n // `addCookies` takes either url or domain/path; url derives the rest.\n const { path: _path, ...cookie } = session.cookie;\n await context.addCookies([{ ...cookie, url: baseURL }]);\n const page = await context.newPage();\n await use({ page, user: session.user, session });\n await context.close();\n },\n});\n\nexport { expect } from \"@playwright/test\";\nexport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nexport type { AppEnvTemplate } from \"./app-env\";\nexport { withZitadel } from \"./playwright-config\";\nexport type { WithZitadelOptions } from \"./playwright-config\";\nexport type {\n ConnectedZitadel,\n Identity,\n InstanceHandle,\n MintedSession,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n} from \"./types\";\n"],"mappings":"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"}
|