@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
package/README.md
CHANGED
|
@@ -70,6 +70,24 @@ apps; the console maps the same fields to `VITE_*`/`CONSOLE_*` names instead.
|
|
|
70
70
|
The fixtures find the instance through `ZITADEL_TESTING_HANDSHAKE`, which
|
|
71
71
|
`withZitadel()` points at its handshake file.
|
|
72
72
|
|
|
73
|
+
`app` is optional. Omit it when the instance itself serves the app — the
|
|
74
|
+
Zitadel binary embeds the console and hosted sign-in at `/ui/console/` and
|
|
75
|
+
`/ui/login/`, so a suite testing those surfaces has no second server to boot.
|
|
76
|
+
Only the instance entry is generated, and `appOrigin` must then be the
|
|
77
|
+
instance's own local origin:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
testDir: "./e2e",
|
|
82
|
+
use: { baseURL: "http://localhost:8092" },
|
|
83
|
+
...withZitadel({
|
|
84
|
+
configDir: import.meta.dirname,
|
|
85
|
+
port: 8092,
|
|
86
|
+
appOrigin: "http://localhost:8092", // the instance is the app server
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
73
91
|
### Start tests authenticated
|
|
74
92
|
|
|
75
93
|
Most app tests don't want to re-test login. `authenticatedPage` seeds a user,
|
|
@@ -181,11 +199,11 @@ const z = await startLocalZitadel({
|
|
|
181
199
|
appOrigins, // registered as the project's preview_origins
|
|
182
200
|
useCase, // "minimal" (default) | "consumer" | "business"
|
|
183
201
|
preset, // "password-first" (default)
|
|
184
|
-
serverBinary, // ZITADEL_SERVER_BINARY override (
|
|
202
|
+
serverBinary, // ZITADEL_SERVER_BINARY override (path to a built server binary)
|
|
185
203
|
keep, // keep the temp dir for debugging
|
|
186
204
|
});
|
|
187
205
|
|
|
188
|
-
z.handle; // serializable: { baseUrl, projectId, projectSecret, schemaId, previewSecret? }
|
|
206
|
+
z.handle; // serializable: { baseUrl, projectId, projectSecret, schemaId, previewSecret?, platform? }
|
|
189
207
|
z.api; // authenticated @zitadel/api client (bearer = projectSecret)
|
|
190
208
|
z.appEnv; // { ZITADEL_URL, NEXT_PUBLIC_ZITADEL_PROJECT_ID, ZITADEL_PROJECT_SECRET }
|
|
191
209
|
await z.seedUser({ email?, password?, attributes? }); // → { id, email, password }
|
|
@@ -219,9 +237,48 @@ above.
|
|
|
219
237
|
bearer for everything else) → `POST /schemas` (server assigns the schema id)
|
|
220
238
|
→ `POST /flow_definitions` (default login flow pinned to that schema id).
|
|
221
239
|
Templates come from `@zitadel/config/defaults`.
|
|
222
|
-
- **Seeding** is `POST /users` (the body
|
|
240
|
+
- **Seeding** is `POST /users` (the body names the schema in `schema` and puts
|
|
241
|
+
the schema-defined content under `attributes`) +
|
|
223
242
|
`PUT /users/{id}/password` with `is_change_required: false`.
|
|
224
243
|
|
|
244
|
+
## Credentials: the boot contract
|
|
245
|
+
|
|
246
|
+
The kit's boot contract is the sanctioned way tests and dev loops obtain
|
|
247
|
+
credentials: they come from the boot contract, never from a seed default —
|
|
248
|
+
the rule root ADR 053 §9 sets. The kit owns the server process and its
|
|
249
|
+
database, captures each credential from provisioning
|
|
250
|
+
output at the moment the server mints it, and exposes it predictably on
|
|
251
|
+
`handle`:
|
|
252
|
+
|
|
253
|
+
- `handle.projectSecret` — the seeded customer project's operator credential,
|
|
254
|
+
captured from `POST /projects` (the server returns it exactly once, at
|
|
255
|
+
creation). Bearer behind `z.api` and every seed op.
|
|
256
|
+
- `handle.previewSecret` — the same project's browser-plane credential (the
|
|
257
|
+
publishable-key predecessor from root ADR 036).
|
|
258
|
+
- `handle.platform` — the platform-plane slot (`PlatformCredentials`): the
|
|
259
|
+
reserved platform project's id and publishable key. **Stub today**: the
|
|
260
|
+
server's platform-project provisioner (Console ADR 0004 §2) has not
|
|
261
|
+
landed, so `startLocalZitadel` never populates it yet. Only the fields the
|
|
262
|
+
contract already guarantees are declared; credentials whose design is
|
|
263
|
+
still open — the platform automation principal (deferred to a future PAT /
|
|
264
|
+
service-user decision) and a boot-minted operator session — join as
|
|
265
|
+
optional fields once they exist, which is additive and churn-free.
|
|
266
|
+
|
|
267
|
+
What this rules out, deliberately: there is no server `--test-mode`, no
|
|
268
|
+
seed-document credential flag, and no other server-side door that mints
|
|
269
|
+
deterministic credentials — and none may be added. The production seed
|
|
270
|
+
contract stays credential-free; predictable test access is this kit's job,
|
|
271
|
+
done entirely with what provisioning already returns. If a credential the
|
|
272
|
+
kit needs is not capturable at provisioning time, the fix is to extend the
|
|
273
|
+
provisioning contract the boot path drives — never a server flag, and never
|
|
274
|
+
direct database writes, which would bypass the provisioner and break seed
|
|
275
|
+
ops for `connectZitadel` targets.
|
|
276
|
+
|
|
277
|
+
The in-repo dev loop follows the same contract: `moon run console:dev-real`
|
|
278
|
+
boots, seeds, and threads `handle.projectSecret` into the console dev proxy's
|
|
279
|
+
`CONSOLE_PROJECT_SECRET` itself; `--seed-only` prints the same variables for
|
|
280
|
+
a separately-started dev server. Nothing to remember or export.
|
|
281
|
+
|
|
225
282
|
## Parallelism model
|
|
226
283
|
|
|
227
284
|
**One instance per suite, one fresh user per test.** Emails are unique per
|
|
@@ -229,7 +286,7 @@ project, so per-test `seed.user()` calls are isolation enough for login-flow
|
|
|
229
286
|
tests, and tests run fully parallel against the shared instance
|
|
230
287
|
(demonstrated by `demo-next-e2e:e2e-real`, 2 workers).
|
|
231
288
|
|
|
232
|
-
Typical timings with the SQLite local default (
|
|
289
|
+
Typical timings with the SQLite local default (dated estimates from a July 2026 dev build — orders of magnitude, not a benchmark; remeasure locally before relying on them):
|
|
233
290
|
|
|
234
291
|
| Operation | Time |
|
|
235
292
|
| --- | --- |
|
|
@@ -259,10 +316,12 @@ Customer installs get the published server binary through `@zitadel/server`'s
|
|
|
259
316
|
platform packages; the in-repo workspace carries no such binary, so the repo's
|
|
260
317
|
own suites run `moon run server:build` and point the kit at the result via
|
|
261
318
|
`ZITADEL_SERVER_BINARY` (the `withZitadel` option `zitadel.serverBinary` /
|
|
262
|
-
`serverBinaryHint` exists for this).
|
|
319
|
+
`serverBinaryHint` exists for this). Most in-repo moon tasks set
|
|
263
320
|
`NEXTGEN_SERVER_LOGIN_ENABLED=false` / `NEXTGEN_SERVER_CONSOLE_ENABLED=false`
|
|
264
|
-
|
|
265
|
-
|
|
321
|
+
because they drive the app-embedded login, not the server-hosted `/ui/*`; the
|
|
322
|
+
exception is `console-e2e:e2e-embedded`, which keeps both surfaces on and
|
|
323
|
+
omits `app` — the binary-served `/ui/*` pages are its subject. Customer
|
|
324
|
+
installs need none of this.
|
|
266
325
|
|
|
267
326
|
## Known limitations
|
|
268
327
|
|
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import { ZitadelClient } from "@zitadel/api/client";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Credentials of the reserved platform project, captured by the kit at
|
|
6
|
+
* provisioning time. The shape follows root ADR 053 §9 and Console ADR 0004
|
|
7
|
+
* §2 (both Proposed): bootstrap mints **no platform project secret** — the
|
|
8
|
+
* publishable key is the only default credential, and test infrastructure
|
|
9
|
+
* gets its credentials from the testkit's boot contract, not from seed
|
|
10
|
+
* defaults.
|
|
11
|
+
*
|
|
12
|
+
* Stub today: the server's platform-project provisioner does not exist yet,
|
|
13
|
+
* so `startLocalZitadel` never populates this field. Only the fields §9
|
|
14
|
+
* itself guarantees are declared. Credentials whose design is still open —
|
|
15
|
+
* the platform automation principal (deferred to a future PAT /
|
|
16
|
+
* service-user decision) and a boot-minted operator session — join as
|
|
17
|
+
* optional fields once they exist: additions are churn-free, removals are
|
|
18
|
+
* not, so nothing is pre-declared here.
|
|
19
|
+
*/
|
|
20
|
+
interface PlatformCredentials {
|
|
21
|
+
/** Reserved platform project id — the Console sign-in target. */
|
|
22
|
+
projectId: string;
|
|
23
|
+
/**
|
|
24
|
+
* Browser-safe publishable key. Required: it is the one credential
|
|
25
|
+
* bootstrap always provisions (sign-in needs it), so a platform block
|
|
26
|
+
* without it is malformed.
|
|
27
|
+
*/
|
|
28
|
+
publishableKey: string;
|
|
29
|
+
}
|
|
4
30
|
/**
|
|
5
31
|
* Serializable description of a bootstrapped instance + project. This is the
|
|
6
|
-
* contract that crosses process boundaries (boot script -> Playwright workers)
|
|
32
|
+
* contract that crosses process boundaries (boot script -> Playwright workers)
|
|
33
|
+
* — and the credential surface of the kit: every credential the server mints
|
|
34
|
+
* during provisioning is captured here, so tests and dev loops read them from
|
|
35
|
+
* the handle instead of hand-set environment or a server-side test door (a
|
|
36
|
+
* server flag that mints deterministic credentials must not exist).
|
|
7
37
|
*/
|
|
8
38
|
interface InstanceHandle {
|
|
9
39
|
baseUrl: string;
|
|
10
40
|
projectId: string;
|
|
41
|
+
/**
|
|
42
|
+
* The seeded customer project's operator credential, captured from
|
|
43
|
+
* `POST /projects` — the server returns it exactly once, at creation.
|
|
44
|
+
* Bearer for the management API (`api`) and every seed op.
|
|
45
|
+
*/
|
|
11
46
|
projectSecret: string;
|
|
12
47
|
/**
|
|
13
48
|
* First registered app origin. Flow submissions enforce the project's
|
|
@@ -16,12 +51,23 @@ interface InstanceHandle {
|
|
|
16
51
|
*/
|
|
17
52
|
appOrigin?: string;
|
|
18
53
|
/**
|
|
19
|
-
* Server-assigned id of the seeded user schema.
|
|
20
|
-
*
|
|
21
|
-
* project credential.
|
|
54
|
+
* Server-assigned id of the seeded user schema. A user names it in `schema`
|
|
55
|
+
* and puts the schema-defined content under `attributes`, so seeding needs
|
|
56
|
+
* it alongside the project credential.
|
|
22
57
|
*/
|
|
23
58
|
schemaId: string;
|
|
59
|
+
/**
|
|
60
|
+
* The same project's browser-plane credential (the publishable-key
|
|
61
|
+
* predecessor from root ADR 036), also captured at creation.
|
|
62
|
+
*/
|
|
24
63
|
previewSecret?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Platform-plane credential slot. Unpopulated until the platform-project
|
|
66
|
+
* provisioner (Console ADR 0004 §2) lands server-side; see
|
|
67
|
+
* `PlatformCredentials` for what it carries, why it stays empty today, and
|
|
68
|
+
* why the still-undecided credentials are left out of it.
|
|
69
|
+
*/
|
|
70
|
+
platform?: PlatformCredentials;
|
|
25
71
|
}
|
|
26
72
|
interface SeedUserInput {
|
|
27
73
|
email?: string;
|
|
@@ -103,13 +149,19 @@ interface LocalZitadel extends ConnectedZitadel, AsyncDisposable {
|
|
|
103
149
|
}
|
|
104
150
|
//#endregion
|
|
105
151
|
//#region src/app-env.d.ts
|
|
152
|
+
/**
|
|
153
|
+
* Handle fields a template may reference: the flat string facts. Structured
|
|
154
|
+
* fields (e.g. `platform`) are not env-var material — consumers read those
|
|
155
|
+
* from the handle directly.
|
|
156
|
+
*/
|
|
157
|
+
type StringHandleField = { [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never }[keyof InstanceHandle];
|
|
106
158
|
/**
|
|
107
159
|
* Declarative mapping from an app's env var names to InstanceHandle fields.
|
|
108
160
|
* A template (not a function) so it can cross process boundaries: the
|
|
109
161
|
* Playwright config serializes it into the app-runner's environment, where it
|
|
110
162
|
* is applied to the handle read from the handshake file.
|
|
111
163
|
*/
|
|
112
|
-
type AppEnvTemplate = Record<string,
|
|
164
|
+
type AppEnvTemplate = Record<string, StringHandleField>;
|
|
113
165
|
/**
|
|
114
166
|
* The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own
|
|
115
167
|
* template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`
|
|
@@ -118,5 +170,5 @@ type AppEnvTemplate = Record<string, keyof InstanceHandle>;
|
|
|
118
170
|
declare const nextAppEnv: AppEnvTemplate;
|
|
119
171
|
declare function applyAppEnvTemplate(template: AppEnvTemplate, handle: InstanceHandle): Record<string, string>;
|
|
120
172
|
//#endregion
|
|
121
|
-
export { Identity as a, LocalZitadelRuntime as c,
|
|
122
|
-
//# sourceMappingURL=app-env-
|
|
173
|
+
export { Identity as a, LocalZitadelRuntime as c, SeedSessionInput as d, SeedUserInput as f, SessionCookie as h, ConnectedZitadel as i, MintedSession as l, SeededUser as m, applyAppEnvTemplate as n, InstanceHandle as o, SeedUsersTemplate as p, nextAppEnv as r, LocalZitadel as s, AppEnvTemplate as t, PlatformCredentials as u };
|
|
174
|
+
//# sourceMappingURL=app-env-DlrV_ePR.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-env-DlrV_ePR.d.mts","names":[],"sources":["../src/types.ts","../src/app-env.ts"],"mappings":";;;;;AAkBA;;;;;AAmBA;;;;;;;;;UAnBiB,mBAAA;EAmDf;EAjDA,SAAA;EAiD8B;;AAGhC;;;EA9CE,cAAA;AAAA;;;;;;AAqDF;;;UA1CiB,cAAA;EACf,OAAA;EACA,SAAA;EA2CA;;;AAIF;;EAzCE,aAAA;EA0CA;;AAKF;;;EAzCE,SAAA;EA0CA;;;;;EApCA,QAAA;EAyCI;;AAGN;;EAvCE,aAAA;EA4CqB;;;;;;EArCrB,QAAA,GAAW,mBAAA;AAAA;AAAA,UAGI,aAAA;EACf,KAAA;EACA,QAAA;;EAEA,UAAA,GAAa,MAAA;AAAA;AAAA,UAGE,UAAA;EACf,EAAA;EACA,KAAA;EACA,QAAA;AAAA;;UAIe,QAAA;EACf,KAAA;EACA,QAAA;AAAA;;UAIe,aAAA;EACf,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;AAAA;AAAA,UAGe,aAAA;EACf,IAAA,EAAM,UAAA;;EAEN,YAAA;EACA,SAAA;EACA,MAAA,EAAQ,aAAA;AAAA;AAAA,UAGO,gBAAA,SAAyB,aAAA;EAyBP;EAvBjC,IAAA,GAAO,UAAA;EAyByD;EAvBhE,kBAAA;EAyBY;;;;;EAnBZ,MAAA;AAAA;AAAA,UAGe,iBAAA;EACf,KAAA,IAAS,KAAA;EACT,QAAA,IAAY,KAAA;EACZ,UAAA,IAAc,KAAA,aAAkB,MAAA;AAAA;AAAA,UAGjB,gBAAA;EACf,MAAA,EAAQ,cAAA;EAKC;EAHT,GAAA,EAAK,aAAA;EAGoC;EADzC,MAAA,EAAQ,MAAA;EACR,QAAA,CAAS,KAAA,GAAQ,aAAA,GAAgB,OAAA,CAAQ,UAAA;EAEL;EAApC,SAAA,CAAU,KAAA,UAAe,QAAA,GAAW,iBAAA,GAAoB,OAAA,CAAQ,UAAA;EAAR;EAExD,QAAA,IAAY,QAAA;EAAZ;;;;;EAMA,WAAA,CAAY,KAAA,GAAQ,gBAAA,GAAmB,OAAA,CAAQ,aAAA;AAAA;AAAA,UAGhC,mBAAA;EACf,IAAA;EACA,GAAA;EAFkC;EAIlC,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,gBAAA,EAAkB,eAAA;EACtD,OAAA,EAAS,mBAAA;EACT,IAAA,IAAQ,OAAA;AAAA;;;;;AA3IV;;;KCXK,iBAAA,iBACS,cAAA,KAAmB,cAAA,CAAe,CAAA,+BAAgC,CAAA,iBACxE,cAAA;;;;;;;KAQI,cAAA,GAAiB,MAAA,SAAe,iBAAA;;;;;;cAO/B,UAAA,EAAY,cAAA;AAAA,iBAMT,mBAAA,CACd,QAAA,EAAU,cAAA,EACV,MAAA,EAAQ,cAAA,GACP,MAAA"}
|
package/dist/app-runner.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_handshake = require("./handshake-
|
|
1
|
+
const require_handshake = require("./handshake-BOsVBPtn.cjs");
|
|
2
2
|
const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
|
|
3
3
|
let node_child_process = require("node:child_process");
|
|
4
4
|
//#region src/app-runner.ts
|
package/dist/app-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-
|
|
1
|
+
import { i as applyAppEnvTemplate, n as waitForHandshake } from "./handshake-BnAPXC6s.mjs";
|
|
2
2
|
import { i as parseAppRunnerConfig, o as requireHandshakePath, t as APP_RUNNER_CONFIG_ENV } from "./orchestration-C640_1Lg.mjs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
//#region src/app-runner.ts
|
|
@@ -62,8 +62,21 @@ function validateHandle(value, source) {
|
|
|
62
62
|
"schemaId"
|
|
63
63
|
]) if (typeof handle[field] !== "string" || handle[field].length === 0) throw new Error(`handshake file ${source} is missing "${field}"`);
|
|
64
64
|
if (!URL.canParse(handle.baseUrl)) throw new Error(`handshake file ${source} has a malformed "baseUrl": ${handle.baseUrl}`);
|
|
65
|
+
if (handle.platform !== void 0) validatePlatform(handle.platform, source);
|
|
65
66
|
return handle;
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The handshake is the cross-process contract, so a malformed platform block
|
|
70
|
+
* must fail here with the field name, not later with a less actionable error.
|
|
71
|
+
*/
|
|
72
|
+
function validatePlatform(value, source) {
|
|
73
|
+
const fail = (detail) => {
|
|
74
|
+
throw new Error(`handshake file ${source} has a malformed "platform" block: ${detail}`);
|
|
75
|
+
};
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("not an object");
|
|
77
|
+
const platform = value;
|
|
78
|
+
for (const field of ["projectId", "publishableKey"]) if (typeof platform[field] !== "string" || platform[field].length === 0) fail(`"${field}" is required`);
|
|
79
|
+
}
|
|
67
80
|
//#endregion
|
|
68
81
|
Object.defineProperty(exports, "applyAppEnvTemplate", {
|
|
69
82
|
enumerable: true,
|
|
@@ -96,4 +109,4 @@ Object.defineProperty(exports, "writeHandshake", {
|
|
|
96
109
|
}
|
|
97
110
|
});
|
|
98
111
|
|
|
99
|
-
//# sourceMappingURL=handshake-
|
|
112
|
+
//# sourceMappingURL=handshake-BOsVBPtn.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake-BOsVBPtn.cjs","names":[],"sources":["../src/app-env.ts","../src/handshake.ts"],"sourcesContent":["import type { InstanceHandle } from \"./types\";\n\n/**\n * Handle fields a template may reference: the flat string facts. Structured\n * fields (e.g. `platform`) are not env-var material — consumers read those\n * from the handle directly.\n */\ntype StringHandleField = {\n [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never;\n}[keyof InstanceHandle];\n\n/**\n * Declarative mapping from an app's env var names to InstanceHandle fields.\n * A template (not a function) so it can cross process boundaries: the\n * Playwright config serializes it into the app-runner's environment, where it\n * is applied to the handle read from the handshake file.\n */\nexport type AppEnvTemplate = Record<string, StringHandleField>;\n\n/**\n * The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own\n * template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`\n * names, for example.\n */\nexport const nextAppEnv: AppEnvTemplate = {\n ZITADEL_URL: \"baseUrl\",\n NEXT_PUBLIC_ZITADEL_PROJECT_ID: \"projectId\",\n ZITADEL_PROJECT_SECRET: \"projectSecret\",\n};\n\nexport function applyAppEnvTemplate(\n template: AppEnvTemplate,\n handle: InstanceHandle,\n): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [name, field] of Object.entries(template)) {\n const value = handle[field];\n if (typeof value !== \"string\" || value.length === 0) {\n // Fail instead of silently dropping the var: an app booted without one\n // of its env vars produces a much harder-to-read failure downstream.\n throw new Error(\n `app env template maps \"${name}\" to handle field \"${field}\", which the instance handle does not carry`,\n );\n }\n env[name] = value;\n }\n return env;\n}\n","import { readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nimport type { InstanceHandle, PlatformCredentials } from \"./types\";\n\n/**\n * The handshake file carries an InstanceHandle across process boundaries:\n * written by the script that boots + bootstraps the instance, read by\n * Playwright workers (fixtures) and the app dev-server wrapper.\n */\nexport async function writeHandshake(path: string, handle: InstanceHandle): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(handle, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function readHandshakeSync(path: string): InstanceHandle {\n const contents = readFileSync(path, \"utf8\");\n let value: unknown;\n try {\n value = JSON.parse(contents);\n } catch (error) {\n throw new Error(`handshake file ${path} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n return validateHandle(value, path);\n}\n\nexport async function waitForHandshake(path: string, timeoutMs = 60_000): Promise<InstanceHandle> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n return validateHandle(JSON.parse(await readFile(path, \"utf8\")), path);\n } catch (error) {\n if (Date.now() >= deadline) {\n throw new Error(\n `handshake file not readable within ${timeoutMs}ms: ${path} (${(error as Error).message})`,\n { cause: error },\n );\n }\n await sleep(250);\n }\n }\n}\n\nfunction validateHandle(value: unknown, source: string): InstanceHandle {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`handshake file ${source} is not an object`);\n }\n const handle = value as Partial<InstanceHandle>;\n for (const field of [\"baseUrl\", \"projectId\", \"projectSecret\", \"schemaId\"] as const) {\n if (typeof handle[field] !== \"string\" || handle[field].length === 0) {\n throw new Error(`handshake file ${source} is missing \"${field}\"`);\n }\n }\n if (!URL.canParse(handle.baseUrl as string)) {\n throw new Error(`handshake file ${source} has a malformed \"baseUrl\": ${handle.baseUrl}`);\n }\n if (handle.platform !== undefined) {\n validatePlatform(handle.platform, source);\n }\n return handle as InstanceHandle;\n}\n\n/**\n * The handshake is the cross-process contract, so a malformed platform block\n * must fail here with the field name, not later with a less actionable error.\n */\nfunction validatePlatform(value: unknown, source: string): void {\n const fail = (detail: string): never => {\n throw new Error(`handshake file ${source} has a malformed \"platform\" block: ${detail}`);\n };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(\"not an object\");\n }\n const platform = value as Partial<PlatformCredentials>;\n for (const field of [\"projectId\", \"publishableKey\"] as const) {\n if (typeof platform[field] !== \"string\" || platform[field].length === 0) {\n fail(`\"${field}\" is required`);\n }\n }\n // Unknown extra fields pass through on purpose: future platform\n // credentials join additively, and an older reader must not reject a\n // newer producer's handle.\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,aAA6B;CACxC,aAAa;CACb,gCAAgC;CAChC,wBAAwB;CACzB;AAED,SAAgB,oBACd,UACA,QACwB;CACxB,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,EAAE;EACpD,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAGhD,OAAM,IAAI,MACR,0BAA0B,KAAK,qBAAqB,MAAM,6CAC3D;AAEH,MAAI,QAAQ;;AAEd,QAAO;;;;;;;;;AClCT,eAAsB,eAAe,MAAc,QAAuC;AACxF,QAAA,GAAA,iBAAA,QAAA,GAAA,UAAA,SAAoB,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,QAAA,GAAA,iBAAA,WAAgB,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAO,CAAC;;AAGhF,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,YAAA,GAAA,QAAA,cAAwB,MAAM,OAAO;CAC3C,IAAI;AACJ,KAAI;AACF,UAAQ,KAAK,MAAM,SAAS;UACrB,OAAO;AACd,QAAM,IAAI,MAAM,kBAAkB,KAAK,0BAA2B,MAAgB,WAAW,EAC3F,OAAO,OACR,CAAC;;AAEJ,QAAO,eAAe,OAAO,KAAK;;AAGpC,eAAsB,iBAAiB,MAAc,YAAY,KAAiC;CAChG,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SACE,KAAI;AACF,SAAO,eAAe,KAAK,MAAM,OAAA,GAAA,iBAAA,UAAe,MAAM,OAAO,CAAC,EAAE,KAAK;UAC9D,OAAO;AACd,MAAI,KAAK,KAAK,IAAI,SAChB,OAAM,IAAI,MACR,sCAAsC,UAAU,MAAM,KAAK,IAAK,MAAgB,QAAQ,IACxF,EAAE,OAAO,OAAO,CACjB;AAEH,SAAA,GAAA,qBAAA,YAAY,IAAI;;;AAKtB,SAAS,eAAe,OAAgB,QAAgC;AACtE,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MAAM,kBAAkB,OAAO,mBAAmB;CAE9D,MAAM,SAAS;AACf,MAAK,MAAM,SAAS;EAAC;EAAW;EAAa;EAAiB;EAAW,CACvE,KAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,WAAW,EAChE,OAAM,IAAI,MAAM,kBAAkB,OAAO,eAAe,MAAM,GAAG;AAGrE,KAAI,CAAC,IAAI,SAAS,OAAO,QAAkB,CACzC,OAAM,IAAI,MAAM,kBAAkB,OAAO,8BAA8B,OAAO,UAAU;AAE1F,KAAI,OAAO,aAAa,KAAA,EACtB,kBAAiB,OAAO,UAAU,OAAO;AAE3C,QAAO;;;;;;AAOT,SAAS,iBAAiB,OAAgB,QAAsB;CAC9D,MAAM,QAAQ,WAA0B;AACtC,QAAM,IAAI,MAAM,kBAAkB,OAAO,qCAAqC,SAAS;;AAEzF,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,MAAK,gBAAgB;CAEvB,MAAM,WAAW;AACjB,MAAK,MAAM,SAAS,CAAC,aAAa,iBAAiB,CACjD,KAAI,OAAO,SAAS,WAAW,YAAY,SAAS,OAAO,WAAW,EACpE,MAAK,IAAI,MAAM,eAAe"}
|
|
@@ -62,9 +62,22 @@ function validateHandle(value, source) {
|
|
|
62
62
|
"schemaId"
|
|
63
63
|
]) if (typeof handle[field] !== "string" || handle[field].length === 0) throw new Error(`handshake file ${source} is missing "${field}"`);
|
|
64
64
|
if (!URL.canParse(handle.baseUrl)) throw new Error(`handshake file ${source} has a malformed "baseUrl": ${handle.baseUrl}`);
|
|
65
|
+
if (handle.platform !== void 0) validatePlatform(handle.platform, source);
|
|
65
66
|
return handle;
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The handshake is the cross-process contract, so a malformed platform block
|
|
70
|
+
* must fail here with the field name, not later with a less actionable error.
|
|
71
|
+
*/
|
|
72
|
+
function validatePlatform(value, source) {
|
|
73
|
+
const fail = (detail) => {
|
|
74
|
+
throw new Error(`handshake file ${source} has a malformed "platform" block: ${detail}`);
|
|
75
|
+
};
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("not an object");
|
|
77
|
+
const platform = value;
|
|
78
|
+
for (const field of ["projectId", "publishableKey"]) if (typeof platform[field] !== "string" || platform[field].length === 0) fail(`"${field}" is required`);
|
|
79
|
+
}
|
|
67
80
|
//#endregion
|
|
68
81
|
export { nextAppEnv as a, applyAppEnvTemplate as i, waitForHandshake as n, writeHandshake as r, readHandshakeSync as t };
|
|
69
82
|
|
|
70
|
-
//# sourceMappingURL=handshake-
|
|
83
|
+
//# sourceMappingURL=handshake-BnAPXC6s.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake-BnAPXC6s.mjs","names":["sleep"],"sources":["../src/app-env.ts","../src/handshake.ts"],"sourcesContent":["import type { InstanceHandle } from \"./types\";\n\n/**\n * Handle fields a template may reference: the flat string facts. Structured\n * fields (e.g. `platform`) are not env-var material — consumers read those\n * from the handle directly.\n */\ntype StringHandleField = {\n [K in keyof InstanceHandle]-?: InstanceHandle[K] extends string | undefined ? K : never;\n}[keyof InstanceHandle];\n\n/**\n * Declarative mapping from an app's env var names to InstanceHandle fields.\n * A template (not a function) so it can cross process boundaries: the\n * Playwright config serializes it into the app-runner's environment, where it\n * is applied to the handle read from the handshake file.\n */\nexport type AppEnvTemplate = Record<string, StringHandleField>;\n\n/**\n * The env shape `@zitadel/sdk-next` apps read. Other frameworks pass their own\n * template — the console maps the same handle fields to `VITE_*`/`CONSOLE_*`\n * names, for example.\n */\nexport const nextAppEnv: AppEnvTemplate = {\n ZITADEL_URL: \"baseUrl\",\n NEXT_PUBLIC_ZITADEL_PROJECT_ID: \"projectId\",\n ZITADEL_PROJECT_SECRET: \"projectSecret\",\n};\n\nexport function applyAppEnvTemplate(\n template: AppEnvTemplate,\n handle: InstanceHandle,\n): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [name, field] of Object.entries(template)) {\n const value = handle[field];\n if (typeof value !== \"string\" || value.length === 0) {\n // Fail instead of silently dropping the var: an app booted without one\n // of its env vars produces a much harder-to-read failure downstream.\n throw new Error(\n `app env template maps \"${name}\" to handle field \"${field}\", which the instance handle does not carry`,\n );\n }\n env[name] = value;\n }\n return env;\n}\n","import { readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nimport type { InstanceHandle, PlatformCredentials } from \"./types\";\n\n/**\n * The handshake file carries an InstanceHandle across process boundaries:\n * written by the script that boots + bootstraps the instance, read by\n * Playwright workers (fixtures) and the app dev-server wrapper.\n */\nexport async function writeHandshake(path: string, handle: InstanceHandle): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(handle, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function readHandshakeSync(path: string): InstanceHandle {\n const contents = readFileSync(path, \"utf8\");\n let value: unknown;\n try {\n value = JSON.parse(contents);\n } catch (error) {\n throw new Error(`handshake file ${path} contains invalid JSON: ${(error as Error).message}`, {\n cause: error,\n });\n }\n return validateHandle(value, path);\n}\n\nexport async function waitForHandshake(path: string, timeoutMs = 60_000): Promise<InstanceHandle> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n return validateHandle(JSON.parse(await readFile(path, \"utf8\")), path);\n } catch (error) {\n if (Date.now() >= deadline) {\n throw new Error(\n `handshake file not readable within ${timeoutMs}ms: ${path} (${(error as Error).message})`,\n { cause: error },\n );\n }\n await sleep(250);\n }\n }\n}\n\nfunction validateHandle(value: unknown, source: string): InstanceHandle {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`handshake file ${source} is not an object`);\n }\n const handle = value as Partial<InstanceHandle>;\n for (const field of [\"baseUrl\", \"projectId\", \"projectSecret\", \"schemaId\"] as const) {\n if (typeof handle[field] !== \"string\" || handle[field].length === 0) {\n throw new Error(`handshake file ${source} is missing \"${field}\"`);\n }\n }\n if (!URL.canParse(handle.baseUrl as string)) {\n throw new Error(`handshake file ${source} has a malformed \"baseUrl\": ${handle.baseUrl}`);\n }\n if (handle.platform !== undefined) {\n validatePlatform(handle.platform, source);\n }\n return handle as InstanceHandle;\n}\n\n/**\n * The handshake is the cross-process contract, so a malformed platform block\n * must fail here with the field name, not later with a less actionable error.\n */\nfunction validatePlatform(value: unknown, source: string): void {\n const fail = (detail: string): never => {\n throw new Error(`handshake file ${source} has a malformed \"platform\" block: ${detail}`);\n };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n fail(\"not an object\");\n }\n const platform = value as Partial<PlatformCredentials>;\n for (const field of [\"projectId\", \"publishableKey\"] as const) {\n if (typeof platform[field] !== \"string\" || platform[field].length === 0) {\n fail(`\"${field}\" is required`);\n }\n }\n // Unknown extra fields pass through on purpose: future platform\n // credentials join additively, and an older reader must not reject a\n // newer producer's handle.\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,aAA6B;CACxC,aAAa;CACb,gCAAgC;CAChC,wBAAwB;CACzB;AAED,SAAgB,oBACd,UACA,QACwB;CACxB,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,EAAE;EACpD,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAGhD,OAAM,IAAI,MACR,0BAA0B,KAAK,qBAAqB,MAAM,6CAC3D;AAEH,MAAI,QAAQ;;AAEd,QAAO;;;;;;;;;AClCT,eAAsB,eAAe,MAAc,QAAuC;AACxF,OAAM,MAAM,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,OAAM,UAAU,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAO,CAAC;;AAGhF,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,WAAW,aAAa,MAAM,OAAO;CAC3C,IAAI;AACJ,KAAI;AACF,UAAQ,KAAK,MAAM,SAAS;UACrB,OAAO;AACd,QAAM,IAAI,MAAM,kBAAkB,KAAK,0BAA2B,MAAgB,WAAW,EAC3F,OAAO,OACR,CAAC;;AAEJ,QAAO,eAAe,OAAO,KAAK;;AAGpC,eAAsB,iBAAiB,MAAc,YAAY,KAAiC;CAChG,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SACE,KAAI;AACF,SAAO,eAAe,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC,EAAE,KAAK;UAC9D,OAAO;AACd,MAAI,KAAK,KAAK,IAAI,SAChB,OAAM,IAAI,MACR,sCAAsC,UAAU,MAAM,KAAK,IAAK,MAAgB,QAAQ,IACxF,EAAE,OAAO,OAAO,CACjB;AAEH,QAAMA,WAAM,IAAI;;;AAKtB,SAAS,eAAe,OAAgB,QAAgC;AACtE,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MAAM,kBAAkB,OAAO,mBAAmB;CAE9D,MAAM,SAAS;AACf,MAAK,MAAM,SAAS;EAAC;EAAW;EAAa;EAAiB;EAAW,CACvE,KAAI,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,WAAW,EAChE,OAAM,IAAI,MAAM,kBAAkB,OAAO,eAAe,MAAM,GAAG;AAGrE,KAAI,CAAC,IAAI,SAAS,OAAO,QAAkB,CACzC,OAAM,IAAI,MAAM,kBAAkB,OAAO,8BAA8B,OAAO,UAAU;AAE1F,KAAI,OAAO,aAAa,KAAA,EACtB,kBAAiB,OAAO,UAAU,OAAO;AAE3C,QAAO;;;;;;AAOT,SAAS,iBAAiB,OAAgB,QAAsB;CAC9D,MAAM,QAAQ,WAA0B;AACtC,QAAM,IAAI,MAAM,kBAAkB,OAAO,qCAAqC,SAAS;;AAEzF,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,MAAK,gBAAgB;CAEvB,MAAM,WAAW;AACjB,MAAK,MAAM,SAAS,CAAC,aAAa,iBAAiB,CACjD,KAAI,OAAO,SAAS,WAAW,YAAY,SAAS,OAAO,WAAW,EACpE,MAAK,IAAI,MAAM,eAAe"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("./src-
|
|
3
|
-
const require_handshake = require("./handshake-
|
|
2
|
+
const require_src = require("./src-DtCJTEId.cjs");
|
|
3
|
+
const require_handshake = require("./handshake-BOsVBPtn.cjs");
|
|
4
4
|
exports.SESSION_COOKIE_NAME = require_src.SESSION_COOKIE_NAME;
|
|
5
5
|
exports.applyAppEnvTemplate = require_handshake.applyAppEnvTemplate;
|
|
6
6
|
exports.bootLocalServer = require_src.bootLocalServer;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Identity, c as LocalZitadelRuntime, d as
|
|
1
|
+
import { a as Identity, c as LocalZitadelRuntime, d as SeedSessionInput, f as SeedUserInput, h as SessionCookie, i as ConnectedZitadel, l as MintedSession, m as SeededUser, n as applyAppEnvTemplate, o as InstanceHandle, p as SeedUsersTemplate, r as nextAppEnv, s as LocalZitadel, t as AppEnvTemplate, u as PlatformCredentials } from "./app-env-DlrV_ePR.mjs";
|
|
2
2
|
import { ZitadelClient } from "@zitadel/api/client";
|
|
3
3
|
import { SetupPreset, SetupUseCase } from "@zitadel/config/defaults";
|
|
4
4
|
|
|
@@ -88,5 +88,5 @@ declare function connectZitadel(handle: InstanceHandle): ConnectedZitadel;
|
|
|
88
88
|
*/
|
|
89
89
|
declare function startLocalZitadel(options?: StartLocalZitadelOptions): Promise<LocalZitadel>;
|
|
90
90
|
//#endregion
|
|
91
|
-
export { type AppEnvTemplate, type BootServerOptions, type BootedServer, type BootstrapProjectOptions, type BootstrappedProject, type ConnectedZitadel, type Identity, type InstanceHandle, type LocalZitadel, type LocalZitadelRuntime, type MintedSession, SESSION_COOKIE_NAME, type SeedSessionInput, type SeedUserInput, type SeedUsersTemplate, type SeededUser, type SessionCookie, StartLocalZitadelOptions, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
|
|
91
|
+
export { type AppEnvTemplate, type BootServerOptions, type BootedServer, type BootstrapProjectOptions, type BootstrappedProject, type ConnectedZitadel, type Identity, type InstanceHandle, type LocalZitadel, type LocalZitadelRuntime, type MintedSession, type PlatformCredentials, SESSION_COOKIE_NAME, type SeedSessionInput, type SeedUserInput, type SeedUsersTemplate, type SeededUser, type SessionCookie, StartLocalZitadelOptions, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
|
|
92
92
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake, r as writeHandshake, t as readHandshakeSync } from "./handshake-
|
|
2
|
-
import { a as bootstrapProject, i as bootLocalServer, n as startLocalZitadel, r as SESSION_COOKIE_NAME, t as connectZitadel } from "./src-
|
|
1
|
+
import { a as nextAppEnv, i as applyAppEnvTemplate, n as waitForHandshake, r as writeHandshake, t as readHandshakeSync } from "./handshake-BnAPXC6s.mjs";
|
|
2
|
+
import { a as bootstrapProject, i as bootLocalServer, n as startLocalZitadel, r as SESSION_COOKIE_NAME, t as connectZitadel } from "./src-Dkt0HHu8.mjs";
|
|
3
3
|
export { SESSION_COOKIE_NAME, applyAppEnvTemplate, bootLocalServer, bootstrapProject, connectZitadel, nextAppEnv, readHandshakeSync, startLocalZitadel, waitForHandshake, writeHandshake };
|
package/dist/playwright.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("./src-
|
|
3
|
-
const require_handshake = require("./handshake-
|
|
2
|
+
const require_src = require("./src-DtCJTEId.cjs");
|
|
3
|
+
const require_handshake = require("./handshake-BOsVBPtn.cjs");
|
|
4
4
|
const require_orchestration = require("./orchestration-D7QBgQ0l.cjs");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
let node_url = require("node:url");
|
|
@@ -245,6 +245,10 @@ async function fillIfVisible(field, value) {
|
|
|
245
245
|
* });
|
|
246
246
|
* ```
|
|
247
247
|
*
|
|
248
|
+
* Omit `app` when the instance serves the app itself (the binary's embedded
|
|
249
|
+
* `/ui/console/` and `/ui/login/` surfaces): only the boot entry is
|
|
250
|
+
* generated, and `appOrigin` must be the instance's own origin.
|
|
251
|
+
*
|
|
248
252
|
* Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the
|
|
249
253
|
* `@zitadel/testing/playwright` fixtures resolve the instance — Playwright
|
|
250
254
|
* workers re-evaluate the config, which re-applies this for every process
|
|
@@ -257,9 +261,18 @@ function withZitadel(options, resolveEntry = entryPoint) {
|
|
|
257
261
|
if (!Number.isInteger(port) || port <= 0) throw new Error(`withZitadel: port must be a positive integer, got ${port}`);
|
|
258
262
|
const origin = URL.canParse(appOrigin) ? new URL(appOrigin) : void 0;
|
|
259
263
|
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}"`);
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
if (app === void 0) {
|
|
265
|
+
const loopbackHosts = new Set([
|
|
266
|
+
"localhost",
|
|
267
|
+
"127.0.0.1",
|
|
268
|
+
"[::1]"
|
|
269
|
+
]);
|
|
270
|
+
if (origin.protocol !== "http:" || !loopbackHosts.has(origin.hostname) || (origin.port || "80") !== String(port)) throw new Error(`withZitadel: with no \`app\`, the instance itself serves the app, so appOrigin must be its local origin (http://localhost:${port}); got "${appOrigin}".`);
|
|
271
|
+
} else {
|
|
272
|
+
if (!app.readyPath.startsWith("/")) throw new Error(`withZitadel: app.readyPath must start with "/", got "${app.readyPath}"`);
|
|
273
|
+
if (app.command.length === 0) throw new Error("withZitadel: app.command must not be empty");
|
|
274
|
+
if (!(0, node_path.isAbsolute)(app.cwd)) throw new Error(`withZitadel: app.cwd must be absolute, got "${app.cwd}"`);
|
|
275
|
+
}
|
|
263
276
|
for (const [label, value] of [
|
|
264
277
|
["zitadel.serverBinary", options.zitadel?.serverBinary],
|
|
265
278
|
["zitadel.dir", options.zitadel?.dir],
|
|
@@ -278,13 +291,7 @@ function withZitadel(options, resolveEntry = entryPoint) {
|
|
|
278
291
|
preset: options.zitadel?.preset,
|
|
279
292
|
useCase: options.zitadel?.useCase
|
|
280
293
|
};
|
|
281
|
-
const
|
|
282
|
-
command: app.command,
|
|
283
|
-
cwd: app.cwd,
|
|
284
|
-
env: app.env,
|
|
285
|
-
handshakeTimeoutMs: app.readyTimeoutMs ?? 18e4
|
|
286
|
-
};
|
|
287
|
-
return { webServer: [{
|
|
294
|
+
const supervisorEntry = {
|
|
288
295
|
command: `node ${JSON.stringify(resolveEntry("supervisor"))}`,
|
|
289
296
|
url: `http://localhost:${port}/healthz`,
|
|
290
297
|
reuseExistingServer: false,
|
|
@@ -300,7 +307,15 @@ function withZitadel(options, resolveEntry = entryPoint) {
|
|
|
300
307
|
signal: "SIGTERM",
|
|
301
308
|
timeout: 3e4
|
|
302
309
|
}
|
|
303
|
-
}
|
|
310
|
+
};
|
|
311
|
+
if (app === void 0) return { webServer: [supervisorEntry] };
|
|
312
|
+
const appRunnerConfig = {
|
|
313
|
+
command: app.command,
|
|
314
|
+
cwd: app.cwd,
|
|
315
|
+
env: app.env,
|
|
316
|
+
handshakeTimeoutMs: app.readyTimeoutMs ?? 18e4
|
|
317
|
+
};
|
|
318
|
+
return { webServer: [supervisorEntry, {
|
|
304
319
|
command: `node ${JSON.stringify(resolveEntry("app-runner"))}`,
|
|
305
320
|
url: new URL(app.readyPath, appOrigin).toString(),
|
|
306
321
|
reuseExistingServer: false,
|
|
@@ -337,8 +352,11 @@ const test = _playwright_test.test.extend({
|
|
|
337
352
|
zitadel: [async ({}, use) => {
|
|
338
353
|
const handshakePath = process.env.ZITADEL_TESTING_HANDSHAKE;
|
|
339
354
|
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).");
|
|
340
|
-
await use(require_src.connectZitadel(require_handshake.
|
|
341
|
-
}, {
|
|
355
|
+
await use(require_src.connectZitadel(await require_handshake.waitForHandshake(handshakePath)));
|
|
356
|
+
}, {
|
|
357
|
+
scope: "worker",
|
|
358
|
+
auto: true
|
|
359
|
+
}],
|
|
342
360
|
seed: async ({ zitadel, baseURL }, use) => {
|
|
343
361
|
await use({
|
|
344
362
|
user: (input) => zitadel.seedUser(input),
|