@warlock.js/core 5.32.0 → 5.34.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.
Files changed (173) hide show
  1. package/CHANGELOG.md +64 -46
  2. package/esm/application/application-config-types.d.mts +8 -0
  3. package/esm/application/application-config-types.d.mts.map +1 -1
  4. package/esm/application/boot-signal.d.mts +8 -8
  5. package/esm/application/boot-signal.d.mts.map +1 -1
  6. package/esm/application/boot-signal.mjs +15 -9
  7. package/esm/application/boot-signal.mjs.map +1 -1
  8. package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
  9. package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
  10. package/esm/cli/commands/ai.command.mjs +50 -0
  11. package/esm/cli/commands/ai.command.mjs.map +1 -0
  12. package/esm/cli/commands/check/check-command.action.mjs +4 -0
  13. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/check.command.mjs +6 -1
  15. package/esm/cli/commands/check.command.mjs.map +1 -1
  16. package/esm/cli/commands/e2e.command.mjs +63 -0
  17. package/esm/cli/commands/e2e.command.mjs.map +1 -0
  18. package/esm/cli/commands/load-e2e-spec.mjs +47 -0
  19. package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
  20. package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
  21. package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
  22. package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
  23. package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
  24. package/esm/cli/commands/pm2.command.mjs +53 -0
  25. package/esm/cli/commands/pm2.command.mjs.map +1 -0
  26. package/esm/cli/framework-cli-commands.mjs +6 -0
  27. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  28. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  29. package/esm/connectors/connectors-manager.mjs +1 -4
  30. package/esm/connectors/connectors-manager.mjs.map +1 -1
  31. package/esm/connectors/shutdown-timeout.mjs +7 -0
  32. package/esm/connectors/shutdown-timeout.mjs.map +1 -0
  33. package/esm/connectors/socket-connector.d.mts.map +1 -1
  34. package/esm/connectors/socket-connector.mjs +8 -1
  35. package/esm/connectors/socket-connector.mjs.map +1 -1
  36. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
  37. package/esm/dev-server/health-checker/default-health-checkers.mjs +2 -16
  38. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
  39. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -2
  40. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  41. package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
  42. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  43. package/esm/e2e/browser.d.mts +4 -0
  44. package/esm/e2e/browser.d.mts.map +1 -0
  45. package/esm/e2e/browser.mjs +88 -0
  46. package/esm/e2e/browser.mjs.map +1 -0
  47. package/esm/e2e/cdp.d.mts +152 -0
  48. package/esm/e2e/cdp.d.mts.map +1 -0
  49. package/esm/e2e/cdp.mjs +106 -0
  50. package/esm/e2e/cdp.mjs.map +1 -0
  51. package/esm/e2e/fixture.mjs +195 -0
  52. package/esm/e2e/fixture.mjs.map +1 -0
  53. package/esm/e2e/index.d.mts +5 -0
  54. package/esm/e2e/index.mjs +6 -0
  55. package/esm/e2e/owned-process.mjs +193 -0
  56. package/esm/e2e/owned-process.mjs.map +1 -0
  57. package/esm/e2e/page.d.mts +64 -0
  58. package/esm/e2e/page.d.mts.map +1 -0
  59. package/esm/e2e/page.mjs +376 -0
  60. package/esm/e2e/page.mjs.map +1 -0
  61. package/esm/e2e/run-e2e.d.mts +17 -0
  62. package/esm/e2e/run-e2e.d.mts.map +1 -0
  63. package/esm/e2e/run-e2e.mjs +143 -0
  64. package/esm/e2e/run-e2e.mjs.map +1 -0
  65. package/esm/e2e/types.d.mts +123 -0
  66. package/esm/e2e/types.d.mts.map +1 -0
  67. package/esm/e2e/types.mjs +24 -0
  68. package/esm/e2e/types.mjs.map +1 -0
  69. package/esm/generations/features/index.mjs +2 -0
  70. package/esm/generations/features/index.mjs.map +1 -1
  71. package/esm/generations/features/process.feature.mjs +65 -0
  72. package/esm/generations/features/process.feature.mjs.map +1 -0
  73. package/esm/http/port-preflight.d.mts.map +1 -1
  74. package/esm/http/port-preflight.mjs +2 -0
  75. package/esm/http/port-preflight.mjs.map +1 -1
  76. package/esm/index.d.mts +7 -2
  77. package/esm/index.mjs +7 -2
  78. package/esm/secrets/errors.d.mts +9 -0
  79. package/esm/secrets/errors.d.mts.map +1 -0
  80. package/esm/secrets/errors.mjs +24 -0
  81. package/esm/secrets/errors.mjs.map +1 -0
  82. package/esm/secrets/file-backend.mjs +167 -0
  83. package/esm/secrets/file-backend.mjs.map +1 -0
  84. package/esm/secrets/index.d.mts +6 -0
  85. package/esm/secrets/index.mjs +6 -0
  86. package/esm/secrets/keyring-backend.mjs +50 -0
  87. package/esm/secrets/keyring-backend.mjs.map +1 -0
  88. package/esm/secrets/memory-backend.d.mts +12 -0
  89. package/esm/secrets/memory-backend.d.mts.map +1 -0
  90. package/esm/secrets/memory-backend.mjs +28 -0
  91. package/esm/secrets/memory-backend.mjs.map +1 -0
  92. package/esm/secrets/resolve-driver.mjs +14 -0
  93. package/esm/secrets/resolve-driver.mjs.map +1 -0
  94. package/esm/secrets/secret-store.d.mts +25 -0
  95. package/esm/secrets/secret-store.d.mts.map +1 -0
  96. package/esm/secrets/secret-store.mjs +120 -0
  97. package/esm/secrets/secret-store.mjs.map +1 -0
  98. package/esm/secrets/secrets.d.mts +11 -0
  99. package/esm/secrets/secrets.d.mts.map +1 -0
  100. package/esm/secrets/secrets.mjs +72 -0
  101. package/esm/secrets/secrets.mjs.map +1 -0
  102. package/esm/secrets/types.d.mts +26 -0
  103. package/esm/secrets/types.d.mts.map +1 -0
  104. package/esm/socket/index.d.mts +2 -2
  105. package/esm/socket/index.mjs +2 -2
  106. package/esm/socket/utils.d.mts +7 -0
  107. package/esm/socket/utils.d.mts.map +1 -1
  108. package/esm/socket/utils.mjs +12 -1
  109. package/esm/socket/utils.mjs.map +1 -1
  110. package/esm/tests/fakes/index.d.mts +2 -1
  111. package/esm/tests/fakes/index.mjs +2 -1
  112. package/esm/tests/fakes/secrets-fake.d.mts +13 -0
  113. package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
  114. package/esm/tests/fakes/secrets-fake.mjs +50 -0
  115. package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
  116. package/esm/tests/fakes/test-fakes.d.mts +2 -2
  117. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  118. package/esm/tests/fakes/test-fakes.mjs +5 -2
  119. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  120. package/esm/tests/index.d.mts +2 -1
  121. package/esm/tests/index.mjs +2 -1
  122. package/esm/tests/vitest-setup.d.mts +1 -1
  123. package/esm/tests/vitest-setup.mjs.map +1 -1
  124. package/esm/tooling/check/lint-step.d.mts +2 -8
  125. package/esm/tooling/check/lint-step.d.mts.map +1 -1
  126. package/esm/tooling/check/lint-step.mjs +1 -42
  127. package/esm/tooling/check/lint-step.mjs.map +1 -1
  128. package/esm/tooling/check/run-check.d.mts +2 -0
  129. package/esm/tooling/check/run-check.d.mts.map +1 -1
  130. package/esm/tooling/check/run-check.mjs +3 -1
  131. package/esm/tooling/check/run-check.mjs.map +1 -1
  132. package/esm/tooling/problem.d.mts +1 -1
  133. package/esm/tooling/problem.d.mts.map +1 -1
  134. package/esm/tooling/problem.mjs.map +1 -1
  135. package/esm/updater/package-manager.mjs +5 -1
  136. package/esm/updater/package-manager.mjs.map +1 -1
  137. package/esm/upgrade/codemods/index.mjs +3 -0
  138. package/esm/upgrade/codemods/index.mjs.map +1 -1
  139. package/esm/upgrade/codemods/oxc-toolchain.mjs +191 -0
  140. package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
  141. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
  142. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
  143. package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
  144. package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
  145. package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
  146. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
  147. package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
  148. package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
  149. package/esm/upgrade/notes.mjs +14 -7
  150. package/esm/upgrade/notes.mjs.map +1 -1
  151. package/esm/upgrade/run-upgrade.mjs +25 -6
  152. package/esm/upgrade/run-upgrade.mjs.map +1 -1
  153. package/esm/warlock-config/types.d.mts +1 -1
  154. package/llms-full.txt +538 -29
  155. package/llms.txt +5 -3
  156. package/package.json +29 -20
  157. package/skills/README.md +2 -1
  158. package/skills/create-module/SKILL.md +2 -2
  159. package/skills/index.md +1 -1
  160. package/skills/run-app/SKILL.md +115 -6
  161. package/skills/store-secrets/SKILL.md +141 -0
  162. package/skills/test-browser/SKILL.md +119 -0
  163. package/skills/test-fakes/SKILL.md +13 -11
  164. package/skills/upgrade-from-5-29/SKILL.md +85 -7
  165. package/skills/warlock-check/SKILL.md +7 -3
  166. package/skills/warlock-doctor/SKILL.md +8 -0
  167. package/skills/wire-socket/SKILL.md +19 -0
  168. package/skills/write-cli-command/SKILL.md +21 -0
  169. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -154
  170. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
  171. package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
  172. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -192
  173. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
@@ -0,0 +1,195 @@
1
+ import { OwnedProcess } from "./owned-process.mjs";
2
+ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { createServer } from "node:net";
4
+ import { cp, lstat, mkdir, mkdtemp, readFile, realpath, rm, stat, symlink, writeFile } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { setTimeout } from "node:timers/promises";
7
+
8
+ //#region ../../packages/core/src/e2e/fixture.ts
9
+ /** Deliberately does not inherit app secrets, NODE_OPTIONS, or service endpoints. */
10
+ function isolatedEnvironment(directory, additions, paths = []) {
11
+ const env = {};
12
+ for (const key of [
13
+ "PATH",
14
+ "SystemRoot",
15
+ "WINDIR",
16
+ "COMSPEC",
17
+ "PATHEXT",
18
+ "LANG",
19
+ "LC_ALL",
20
+ "TZ"
21
+ ]) if (process.env[key]) env[key] = process.env[key];
22
+ return {
23
+ ...env,
24
+ ...additions,
25
+ HOME: join(directory, "home"),
26
+ USERPROFILE: join(directory, "home"),
27
+ XDG_CONFIG_HOME: join(directory, "config"),
28
+ XDG_CACHE_HOME: join(directory, "cache"),
29
+ TMPDIR: join(directory, "tmp"),
30
+ TEMP: join(directory, "tmp"),
31
+ TMP: join(directory, "tmp"),
32
+ PATH: [...paths, env.PATH ?? ""].join(delimiter)
33
+ };
34
+ }
35
+ async function reservePort() {
36
+ const server = createServer();
37
+ await new Promise((accept, reject) => {
38
+ server.once("error", reject);
39
+ server.listen(0, "127.0.0.1", accept);
40
+ });
41
+ const address = server.address();
42
+ if (!address || typeof address === "string") throw new Error("Could not reserve an ephemeral fixture port");
43
+ return {
44
+ port: address.port,
45
+ release: () => new Promise((accept, reject) => server.close((error) => error ? reject(error) : accept()))
46
+ };
47
+ }
48
+ async function linkDependencies(source, target) {
49
+ let current = source;
50
+ while (true) {
51
+ const modules = join(current, "node_modules");
52
+ try {
53
+ if ((await stat(modules)).isDirectory()) {
54
+ await symlink(await realpath(modules), join(target, "node_modules"), process.platform === "win32" ? "junction" : "dir");
55
+ return;
56
+ }
57
+ } catch (error) {
58
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
59
+ }
60
+ const parent = dirname(current);
61
+ if (parent === current) return;
62
+ current = parent;
63
+ }
64
+ }
65
+ var AppFixture = class AppFixture {
66
+ constructor(root, evidence) {
67
+ this.root = root;
68
+ this.evidence = evidence;
69
+ this.processes = [];
70
+ }
71
+ static async create(evidence) {
72
+ return new AppFixture(await mkdtemp(join(tmpdir(), "warlock-e2e-")), evidence);
73
+ }
74
+ async start(options, runOptions) {
75
+ runOptions.signal?.throwIfAborted();
76
+ const source = await realpath(resolve(options.directory ?? process.cwd()));
77
+ const directory = join(this.root, "app");
78
+ const dataRoot = join(this.root, "data");
79
+ const portFile = join(this.root, "port");
80
+ const output = relative(source, resolve(runOptions.outputDirectory ?? ".warlock/e2e"));
81
+ const excluded = [
82
+ "node_modules",
83
+ ".git",
84
+ ".warlock",
85
+ ...output && !output.startsWith("..") && !isAbsolute(output) ? [output] : [],
86
+ ...options.exclude ?? []
87
+ ];
88
+ if (excluded.some((entry) => isAbsolute(entry) || entry.split(/[\\/]/).includes(".."))) throw new Error("Fixture exclusions must be relative paths without '..'");
89
+ await cp(source, directory, {
90
+ recursive: true,
91
+ dereference: false,
92
+ filter: async (file) => {
93
+ const path = relative(source, file);
94
+ if (!path) return true;
95
+ if (file === this.root || this.root.startsWith(`${file}${sep}`)) return false;
96
+ const segments = path.split(sep);
97
+ if (segments.some((entry) => entry === ".env" || entry.startsWith(".env."))) return false;
98
+ if (excluded.some((entry) => path === entry || path.startsWith(`${entry}${sep}`) || entry === "node_modules" && segments.includes(entry))) return false;
99
+ return !(await lstat(file).catch(() => void 0))?.isSymbolicLink();
100
+ }
101
+ });
102
+ await linkDependencies(source, directory);
103
+ for (const path of [
104
+ dataRoot,
105
+ "home",
106
+ "config",
107
+ "cache",
108
+ "tmp"
109
+ ].map((path) => isAbsolute(path) ? path : join(this.root, path))) await mkdir(path, { recursive: true });
110
+ const reservation = await reservePort();
111
+ const context = {
112
+ directory,
113
+ dataRoot,
114
+ port: reservation.port,
115
+ url: `http://127.0.0.1:${reservation.port}`,
116
+ portFile
117
+ };
118
+ this.context = context;
119
+ try {
120
+ await options.prepare?.(context);
121
+ runOptions.signal?.throwIfAborted();
122
+ const additions = typeof options.env === "function" ? options.env(context) : options.env ?? {};
123
+ const env = isolatedEnvironment(this.root, {
124
+ NODE_ENV: "production",
125
+ ...additions,
126
+ HOST: "127.0.0.1",
127
+ HTTP_HOST: "127.0.0.1",
128
+ PORT: String(options.portFromFile ? 0 : context.port),
129
+ HTTP_PORT: String(options.portFromFile ? 0 : context.port),
130
+ WARLOCK_E2E_DATA_ROOT: dataRoot,
131
+ WARLOCK_E2E_PORT_FILE: portFile
132
+ }, [...runOptions.path ?? [], ...options.path ?? []].map((path) => resolve(directory, path)));
133
+ await writeFile(join(directory, ".env"), Object.entries(env).map(([key, value]) => `${key}=${JSON.stringify(value ?? "")}`).join("\n") + "\n", { mode: 384 });
134
+ const timeoutMs = options.startupTimeoutMs ?? 3e4;
135
+ if (options.build) {
136
+ const command = typeof options.build === "function" ? options.build(context) : options.build;
137
+ const build = await OwnedProcess.start("build", command, directory, env, this.root, this.evidence.processes, (owned) => this.processes.push(owned));
138
+ await build.wait(timeoutMs, runOptions.signal);
139
+ await build.stop();
140
+ }
141
+ await reservation.release();
142
+ runOptions.signal?.throwIfAborted();
143
+ const command = typeof options.start === "function" ? options.start(context) : options.start ?? [process.execPath, "dist/app.js"];
144
+ const app = await OwnedProcess.start("app", command, directory, env, this.root, this.evidence.processes, (owned) => this.processes.push(owned));
145
+ const deadline = Date.now() + timeoutMs;
146
+ let reason = "No successful readiness response";
147
+ while (Date.now() < deadline) {
148
+ runOptions.signal?.throwIfAborted();
149
+ app.assertRunning();
150
+ try {
151
+ if (options.portFromFile) {
152
+ const port = Number((await readFile(portFile, "utf8")).trim());
153
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error("Port file must contain a bound TCP port between 1 and 65535");
154
+ context.port = port;
155
+ context.url = `http://127.0.0.1:${port}`;
156
+ }
157
+ const ready = new URL(options.readyPath ?? "/", context.url);
158
+ if (ready.origin !== context.url) throw new Error("Readiness URL must stay on the fixture origin");
159
+ const response = await fetch(ready, {
160
+ redirect: "manual",
161
+ signal: AbortSignal.timeout(Math.min(1e3, Math.max(1, deadline - Date.now())))
162
+ });
163
+ await response.body?.cancel();
164
+ if (response.status >= 200 && response.status < 400) {
165
+ await setTimeout(100);
166
+ app.assertRunning();
167
+ this.evidence.url = context.url;
168
+ return context;
169
+ }
170
+ reason = `Readiness HTTP ${response.status}`;
171
+ } catch (error) {
172
+ reason = String(error);
173
+ }
174
+ await setTimeout(50);
175
+ }
176
+ throw new Error(`App readiness timed out: ${reason}\n${app.logs().slice(-8e3)}`);
177
+ } finally {
178
+ await reservation.release().catch(() => {});
179
+ }
180
+ }
181
+ async stop() {
182
+ const errors = (await Promise.allSettled(this.processes.map((process) => process.stop()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
183
+ if (errors.length) throw new AggregateError(errors, "Owned fixture teardown failed; disposable files retained for inspection: " + errors.map(String).join("; "));
184
+ await rm(this.root, {
185
+ recursive: true,
186
+ force: true,
187
+ maxRetries: 8,
188
+ retryDelay: 100
189
+ });
190
+ }
191
+ };
192
+
193
+ //#endregion
194
+ export { AppFixture, isolatedEnvironment };
195
+ //# sourceMappingURL=fixture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.mjs","names":["delay"],"sources":["../../../../../../../../packages/core/src/e2e/fixture.ts"],"sourcesContent":["import {\n cp,\n mkdir,\n mkdtemp,\n readFile,\n realpath,\n rm,\n lstat,\n stat,\n symlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { createServer } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { OwnedProcess } from \"./owned-process\";\nimport type {\n AppFixtureOptions,\n Command,\n E2EEvidence,\n FixtureContext,\n RunE2EOptions,\n} from \"./types\";\n\n/** Deliberately does not inherit app secrets, NODE_OPTIONS, or service endpoints. */\nexport function isolatedEnvironment(\n directory: string,\n additions: Record<string, string>,\n paths: string[] = [],\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of [\"PATH\", \"SystemRoot\", \"WINDIR\", \"COMSPEC\", \"PATHEXT\", \"LANG\", \"LC_ALL\", \"TZ\"])\n if (process.env[key]) env[key] = process.env[key];\n return {\n ...env,\n ...additions,\n HOME: join(directory, \"home\"),\n USERPROFILE: join(directory, \"home\"),\n XDG_CONFIG_HOME: join(directory, \"config\"),\n XDG_CACHE_HOME: join(directory, \"cache\"),\n TMPDIR: join(directory, \"tmp\"),\n TEMP: join(directory, \"tmp\"),\n TMP: join(directory, \"tmp\"),\n PATH: [...paths, env.PATH ?? \"\"].join(delimiter),\n };\n}\n\nasync function reservePort(): Promise<{ port: number; release: () => Promise<void> }> {\n const server = createServer();\n await new Promise<void>((accept, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", accept);\n });\n const address = server.address();\n if (!address || typeof address === \"string\")\n throw new Error(\"Could not reserve an ephemeral fixture port\");\n return {\n port: address.port,\n release: () =>\n new Promise<void>((accept, reject) =>\n server.close((error) => (error ? reject(error) : accept())),\n ),\n };\n}\n\nasync function linkDependencies(source: string, target: string): Promise<void> {\n // A workspace app may resolve dependencies from a parent node_modules.\n let current = source;\n while (true) {\n const modules = join(current, \"node_modules\");\n try {\n if ((await stat(modules)).isDirectory()) {\n await symlink(\n await realpath(modules),\n join(target, \"node_modules\"),\n process.platform === \"win32\" ? \"junction\" : \"dir\",\n );\n return;\n }\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n }\n const parent = dirname(current);\n if (parent === current) return;\n current = parent;\n }\n}\n\nexport class AppFixture {\n readonly processes: OwnedProcess[] = [];\n context?: FixtureContext;\n private constructor(\n readonly root: string,\n private evidence: E2EEvidence,\n ) {}\n\n static async create(evidence: E2EEvidence): Promise<AppFixture> {\n return new AppFixture(await mkdtemp(join(tmpdir(), \"warlock-e2e-\")), evidence);\n }\n\n async start(options: AppFixtureOptions, runOptions: RunE2EOptions): Promise<FixtureContext> {\n runOptions.signal?.throwIfAborted();\n const source = await realpath(resolve(options.directory ?? process.cwd()));\n const directory = join(this.root, \"app\");\n const dataRoot = join(this.root, \"data\");\n const portFile = join(this.root, \"port\");\n const output = relative(source, resolve(runOptions.outputDirectory ?? \".warlock/e2e\"));\n const excluded = [\n \"node_modules\",\n \".git\",\n \".warlock\",\n ...(output && !output.startsWith(\"..\") && !isAbsolute(output) ? [output] : []),\n ...(options.exclude ?? []),\n ];\n if (excluded.some((entry) => isAbsolute(entry) || entry.split(/[\\\\/]/).includes(\"..\")))\n throw new Error(\"Fixture exclusions must be relative paths without '..'\");\n await cp(source, directory, {\n recursive: true,\n // Never copy dotenv files or follow symlinks into shared app data.\n dereference: false,\n filter: async (file) => {\n const path = relative(source, file);\n if (!path) return true;\n if (file === this.root || this.root.startsWith(`${file}${sep}`)) return false;\n const segments = path.split(sep);\n if (segments.some((entry) => entry === \".env\" || entry.startsWith(\".env.\"))) return false;\n if (\n excluded.some(\n (entry) =>\n path === entry ||\n path.startsWith(`${entry}${sep}`) ||\n (entry === \"node_modules\" && segments.includes(entry)),\n )\n )\n return false;\n return !(await lstat(file).catch(() => undefined))?.isSymbolicLink();\n },\n });\n // cp preserves links by default; reject app links to keep writes disposable.\n // Dependencies are the sole explicit read-only-by-contract shared link.\n await linkDependencies(source, directory);\n for (const path of [dataRoot, \"home\", \"config\", \"cache\", \"tmp\"].map((path) =>\n isAbsolute(path) ? path : join(this.root, path),\n ))\n await mkdir(path, { recursive: true });\n const reservation = await reservePort();\n const context: FixtureContext = {\n directory,\n dataRoot,\n port: reservation.port,\n url: `http://127.0.0.1:${reservation.port}`,\n portFile,\n };\n this.context = context;\n try {\n await options.prepare?.(context);\n runOptions.signal?.throwIfAborted();\n const additions =\n typeof options.env === \"function\" ? options.env(context) : (options.env ?? {});\n const env = isolatedEnvironment(\n this.root,\n {\n NODE_ENV: \"production\",\n ...additions,\n HOST: \"127.0.0.1\",\n HTTP_HOST: \"127.0.0.1\",\n PORT: String(options.portFromFile ? 0 : context.port),\n HTTP_PORT: String(options.portFromFile ? 0 : context.port),\n WARLOCK_E2E_DATA_ROOT: dataRoot,\n WARLOCK_E2E_PORT_FILE: portFile,\n },\n [...(runOptions.path ?? []), ...(options.path ?? [])].map((path) =>\n resolve(directory, path),\n ),\n );\n // A framework env() consumer sees the same owned variables as process.env.\n await writeFile(\n join(directory, \".env\"),\n Object.entries(env)\n .map(([key, value]) => `${key}=${JSON.stringify(value ?? \"\")}`)\n .join(\"\\n\") + \"\\n\",\n { mode: 0o600 },\n );\n const timeoutMs = options.startupTimeoutMs ?? 30_000;\n if (options.build) {\n const command =\n typeof options.build === \"function\" ? options.build(context) : options.build;\n const build = await OwnedProcess.start(\n \"build\",\n command,\n directory,\n env,\n this.root,\n this.evidence.processes,\n (owned) => this.processes.push(owned),\n );\n await build.wait(timeoutMs, runOptions.signal);\n await build.stop();\n }\n await reservation.release();\n runOptions.signal?.throwIfAborted();\n const command: Command =\n typeof options.start === \"function\"\n ? options.start(context)\n : (options.start ?? [process.execPath, \"dist/app.js\"]);\n const app = await OwnedProcess.start(\n \"app\",\n command,\n directory,\n env,\n this.root,\n this.evidence.processes,\n (owned) => this.processes.push(owned),\n );\n const deadline = Date.now() + timeoutMs;\n let reason = \"No successful readiness response\";\n while (Date.now() < deadline) {\n runOptions.signal?.throwIfAborted();\n app.assertRunning();\n try {\n if (options.portFromFile) {\n const port = Number((await readFile(portFile, \"utf8\")).trim());\n if (!Number.isInteger(port) || port <= 0 || port > 65535)\n throw new Error(\"Port file must contain a bound TCP port between 1 and 65535\");\n context.port = port;\n context.url = `http://127.0.0.1:${port}`;\n }\n const ready = new URL(options.readyPath ?? \"/\", context.url);\n if (ready.origin !== context.url)\n throw new Error(\"Readiness URL must stay on the fixture origin\");\n const response = await fetch(ready, {\n redirect: \"manual\",\n signal: AbortSignal.timeout(Math.min(1000, Math.max(1, deadline - Date.now()))),\n });\n await response.body?.cancel();\n if (response.status >= 200 && response.status < 400) {\n // Give foreground startup failures a chance to arrive over IPC.\n await delay(100);\n app.assertRunning();\n this.evidence.url = context.url;\n return context;\n }\n reason = `Readiness HTTP ${response.status}`;\n } catch (error) {\n reason = String(error);\n }\n await delay(50);\n }\n throw new Error(`App readiness timed out: ${reason}\\n${app.logs().slice(-8000)}`);\n } finally {\n await reservation.release().catch(() => {});\n }\n }\n\n async stop(): Promise<void> {\n const results = await Promise.allSettled(this.processes.map((process) => process.stop()));\n const errors = results.flatMap((result) =>\n result.status === \"rejected\" ? [result.reason] : [],\n );\n if (errors.length)\n throw new AggregateError(\n errors,\n \"Owned fixture teardown failed; disposable files retained for inspection: \" +\n errors.map(String).join(\"; \"),\n );\n await rm(this.root, { recursive: true, force: true, maxRetries: 8, retryDelay: 100 });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { BrowserPage } from "./page.mjs";
2
+ import { AppFixtureOptions, BrowserOptions, Command, ConsoleEntry, E2EContext, E2EEvidence, E2ESpec, FixtureContext, NetworkAllowance, NetworkFailure, Pattern, ProcessEvidence, RunE2EOptions, StepEvidence, Viewport, defaultViewports, defineE2E } from "./types.mjs";
3
+ import { findBrowserBinary } from "./browser.mjs";
4
+ import { E2EResult, E2ERunError, runE2E } from "./run-e2e.mjs";
5
+ export { type AppFixtureOptions, type BrowserOptions, BrowserPage, type Command, type ConsoleEntry, type E2EContext, type E2EEvidence, type E2EResult, E2ERunError, type E2ESpec, type FixtureContext, type NetworkAllowance, type NetworkFailure, type Pattern, type ProcessEvidence, type RunE2EOptions, type StepEvidence, type Viewport, defaultViewports, defineE2E, findBrowserBinary, runE2E };
@@ -0,0 +1,6 @@
1
+ import { findBrowserBinary } from "./browser.mjs";
2
+ import { defaultViewports, defineE2E } from "./types.mjs";
3
+ import { BrowserPage } from "./page.mjs";
4
+ import { E2ERunError, runE2E } from "./run-e2e.mjs";
5
+
6
+ export { BrowserPage, E2ERunError, defaultViewports, defineE2E, findBrowserBinary, runE2E };
@@ -0,0 +1,193 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { execFile, spawn } from "node:child_process";
5
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
6
+
7
+ //#region ../../packages/core/src/e2e/owned-process.ts
8
+ const supervisorSource = `
9
+ import { spawn } from 'node:child_process';
10
+ process.on('SIGTERM', () => {});
11
+ process.on('SIGINT', () => {});
12
+ process.on('message', ({command}) => {
13
+ const child = spawn(command[0], command.slice(1), {cwd:process.cwd(),env:process.env,stdio:'inherit'});
14
+ child.once('spawn', () => process.send?.({type:'started',pid:child.pid}));
15
+ child.once('error', error => process.send?.({type:'failure',message:error.message}));
16
+ child.once('exit', (code,signal) => process.send?.({type:'exit',code,signal}));
17
+ });
18
+ `;
19
+ /** Small OS identity queries; every helper PID is retained as evidence too. */
20
+ function utility(command, args, records) {
21
+ return new Promise((resolve, reject) => {
22
+ const child = execFile(command, args, { encoding: "utf8" }, (error, stdout) => {
23
+ if (record) record.stopped = true;
24
+ if (error) reject(error);
25
+ else resolve(stdout);
26
+ });
27
+ const record = child.pid ? {
28
+ role: "process-identity",
29
+ pid: child.pid,
30
+ command: [command, ...args],
31
+ ownerFile: ""
32
+ } : void 0;
33
+ if (record) records.push(record);
34
+ });
35
+ }
36
+ function commandLineOwns(commandLine, ownerFile) {
37
+ if (commandLine.includes("\0")) return commandLine.split("\0").includes(ownerFile);
38
+ return commandLine.includes(ownerFile);
39
+ }
40
+ var OwnedProcess = class OwnedProcess {
41
+ constructor(child, record, records) {
42
+ this.child = child;
43
+ this.records = records;
44
+ this.output = "";
45
+ this.record = record;
46
+ records.push(record);
47
+ const append = (chunk) => {
48
+ this.output = (this.output + chunk.toString()).slice(-1e6);
49
+ };
50
+ child.stdout?.on("data", append);
51
+ child.stderr?.on("data", append);
52
+ child.on("message", (message) => {
53
+ const data = message;
54
+ if (data.type === "exit") this.finished = {
55
+ code: data.code ?? null,
56
+ signal: data.signal ?? null
57
+ };
58
+ });
59
+ }
60
+ static async start(role, command, directory, env, ownerDirectory, records, onCreated) {
61
+ const ownerFile = join(ownerDirectory, `owner-${randomUUID()}.mjs`);
62
+ await writeFile(ownerFile, supervisorSource, { mode: 384 });
63
+ const child = spawn(process.execPath, [ownerFile], {
64
+ cwd: directory,
65
+ env,
66
+ detached: process.platform !== "win32",
67
+ stdio: [
68
+ "ignore",
69
+ "pipe",
70
+ "pipe",
71
+ "ipc"
72
+ ]
73
+ });
74
+ if (!child.pid) {
75
+ await new Promise((_resolve, reject) => child.once("error", reject));
76
+ throw new Error(`Could not start ${role}`);
77
+ }
78
+ const owned = new OwnedProcess(child, {
79
+ role,
80
+ pid: child.pid,
81
+ command: [...command],
82
+ ownerFile
83
+ }, records);
84
+ onCreated?.(owned);
85
+ try {
86
+ await new Promise((resolve, reject) => {
87
+ const cleanup = () => {
88
+ clearTimeout(timer);
89
+ child.removeListener("message", message);
90
+ child.removeListener("error", failed);
91
+ child.removeListener("exit", exited);
92
+ };
93
+ const failed = (error) => {
94
+ cleanup();
95
+ reject(error);
96
+ };
97
+ const exited = () => failed(/* @__PURE__ */ new Error(`${role} supervisor exited before startup`));
98
+ const message = (value) => {
99
+ const data = value;
100
+ if (data.type === "started" && typeof data.pid === "number") {
101
+ owned.record.childPid = data.pid;
102
+ cleanup();
103
+ resolve();
104
+ }
105
+ if (data.type === "failure") failed(/* @__PURE__ */ new Error(`${role}: ${data.message}`));
106
+ };
107
+ const timer = setTimeout(() => failed(/* @__PURE__ */ new Error(`${role} process startup timed out`)), 1e4);
108
+ child.on("message", message);
109
+ child.once("error", failed);
110
+ child.once("exit", exited);
111
+ child.send({ command }, (error) => {
112
+ if (error) failed(error);
113
+ });
114
+ });
115
+ return owned;
116
+ } catch (error) {
117
+ try {
118
+ await owned.stop();
119
+ } catch (cleanup) {
120
+ throw new AggregateError([error, cleanup], `Process startup and verified cleanup failed: ${String(error)}; ${String(cleanup)}`);
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ logs() {
126
+ return this.output;
127
+ }
128
+ assertRunning() {
129
+ if (this.finished || this.child.exitCode !== null || this.child.signalCode !== null) throw new Error(`${this.record.role} exited: ${JSON.stringify(this.finished)}\n${this.output.slice(-8e3)}`);
130
+ }
131
+ async wait(timeoutMs, signal) {
132
+ const deadline = Date.now() + timeoutMs;
133
+ while (!this.finished && Date.now() < deadline) {
134
+ signal?.throwIfAborted();
135
+ await setTimeout$1(25);
136
+ }
137
+ if (!this.finished) throw new Error(`${this.record.role} timed out after ${timeoutMs}ms\n${this.output.slice(-8e3)}`);
138
+ if (this.finished.code !== 0) throw new Error(`${this.record.role} failed: ${JSON.stringify(this.finished)}\n${this.output.slice(-8e3)}`);
139
+ }
140
+ async verify() {
141
+ if (this.child.exitCode !== null || this.child.signalCode !== null) throw new Error(`Refusing cleanup: ${this.record.role} group anchor ${this.record.pid} exited unexpectedly`);
142
+ let commandLine;
143
+ if (process.platform === "linux") commandLine = await readFile(`/proc/${this.record.pid}/cmdline`, "utf8");
144
+ else if (process.platform === "win32") commandLine = await utility("powershell.exe", [
145
+ "-NoProfile",
146
+ "-NonInteractive",
147
+ "-Command",
148
+ `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${this.record.pid}').CommandLine`
149
+ ], this.records);
150
+ else commandLine = await utility("/bin/ps", [
151
+ "-p",
152
+ String(this.record.pid),
153
+ "-o",
154
+ "command="
155
+ ], this.records);
156
+ if (!commandLineOwns(commandLine, this.record.ownerFile)) throw new Error(`Refusing cleanup: PID ${this.record.pid} command line does not contain its recorded owner file`);
157
+ }
158
+ stop() {
159
+ this.stopping ??= this.stopOwnedGroup().catch((error) => {
160
+ this.stopping = void 0;
161
+ throw error;
162
+ });
163
+ return this.stopping;
164
+ }
165
+ async stopOwnedGroup() {
166
+ await this.verify();
167
+ const exited = new Promise((resolve) => this.child.once("exit", () => resolve()));
168
+ if (process.platform === "win32") await utility("taskkill.exe", [
169
+ "/PID",
170
+ String(this.record.pid),
171
+ "/T",
172
+ "/F"
173
+ ], this.records);
174
+ else {
175
+ process.kill(-this.record.pid, "SIGTERM");
176
+ await setTimeout$1(250);
177
+ await this.verify();
178
+ process.kill(-this.record.pid, "SIGKILL");
179
+ }
180
+ await new Promise((resolve, reject) => {
181
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Owned ${this.record.role} PID ${this.record.pid} did not exit`)), 5e3);
182
+ exited.then(() => {
183
+ clearTimeout(timer);
184
+ resolve();
185
+ });
186
+ });
187
+ this.record.stopped = true;
188
+ }
189
+ };
190
+
191
+ //#endregion
192
+ export { OwnedProcess, commandLineOwns };
193
+ //# sourceMappingURL=owned-process.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owned-process.mjs","names":["delay"],"sources":["../../../../../../../../packages/core/src/e2e/owned-process.ts"],"sourcesContent":["import { execFile, spawn, type ChildProcess } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport type { Command, ProcessEvidence } from \"./types\";\n\n// A group anchor stays alive after the foreground command exits. This lets\n// teardown verify a live, unique command line before BOTH TERM and KILL,\n// including when the app crashed but left descendants behind.\nconst supervisorSource = `\nimport { spawn } from 'node:child_process';\nprocess.on('SIGTERM', () => {});\nprocess.on('SIGINT', () => {});\nprocess.on('message', ({command}) => {\n const child = spawn(command[0], command.slice(1), {cwd:process.cwd(),env:process.env,stdio:'inherit'});\n child.once('spawn', () => process.send?.({type:'started',pid:child.pid}));\n child.once('error', error => process.send?.({type:'failure',message:error.message}));\n child.once('exit', (code,signal) => process.send?.({type:'exit',code,signal}));\n});\n`;\n\n/** Small OS identity queries; every helper PID is retained as evidence too. */\nfunction utility(command: string, args: string[], records: ProcessEvidence[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(command, args, { encoding: \"utf8\" }, (error, stdout) => {\n if (record) record.stopped = true;\n if (error) reject(error);\n else resolve(stdout);\n });\n const record: ProcessEvidence | undefined = child.pid\n ? { role: \"process-identity\", pid: child.pid, command: [command, ...args], ownerFile: \"\" }\n : undefined;\n if (record) records.push(record);\n });\n}\n\nexport function commandLineOwns(commandLine: string, ownerFile: string): boolean {\n if (commandLine.includes(\"\\0\")) return commandLine.split(\"\\0\").includes(ownerFile);\n // ps/CIM expose a rendered command line; the random owner path is unique\n // to this child, never an app name, binary name, PID search, or a port.\n return commandLine.includes(ownerFile);\n}\n\nexport class OwnedProcess {\n readonly record: ProcessEvidence;\n private output = \"\";\n private finished?: { code: number | null; signal: string | null };\n private stopping?: Promise<void>;\n private constructor(\n private child: ChildProcess,\n record: ProcessEvidence,\n private records: ProcessEvidence[],\n ) {\n this.record = record;\n records.push(record);\n const append = (chunk: Buffer) => {\n this.output = (this.output + chunk.toString()).slice(-1_000_000);\n };\n child.stdout?.on(\"data\", append);\n child.stderr?.on(\"data\", append);\n child.on(\"message\", (message: unknown) => {\n const data = message as { type?: string; code?: number | null; signal?: string | null };\n if (data.type === \"exit\")\n this.finished = { code: data.code ?? null, signal: data.signal ?? null };\n });\n }\n\n static async start(\n role: string,\n command: Command,\n directory: string,\n env: NodeJS.ProcessEnv,\n ownerDirectory: string,\n records: ProcessEvidence[],\n onCreated?: (owned: OwnedProcess) => void,\n ): Promise<OwnedProcess> {\n const ownerFile = join(ownerDirectory, `owner-${randomUUID()}.mjs`);\n await writeFile(ownerFile, supervisorSource, { mode: 0o600 });\n // detached makes the recorded supervisor PID its process-group ID on POSIX.\n // https://nodejs.org/download/release/v22.15.0/docs/api/child_process.html#optionsdetached\n const child = spawn(process.execPath, [ownerFile], {\n cwd: directory,\n env,\n detached: process.platform !== \"win32\",\n stdio: [\"ignore\", \"pipe\", \"pipe\", \"ipc\"],\n });\n if (!child.pid) {\n await new Promise<never>((_resolve, reject) => child.once(\"error\", reject));\n throw new Error(`Could not start ${role}`);\n }\n const owned = new OwnedProcess(\n child,\n { role, pid: child.pid, command: [...command], ownerFile },\n records,\n );\n onCreated?.(owned);\n try {\n await new Promise<void>((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timer);\n child.removeListener(\"message\", message);\n child.removeListener(\"error\", failed);\n child.removeListener(\"exit\", exited);\n };\n const failed = (error: Error) => {\n cleanup();\n reject(error);\n };\n const exited = () => failed(new Error(`${role} supervisor exited before startup`));\n const message = (value: unknown) => {\n const data = value as { type?: string; pid?: number; message?: string };\n if (data.type === \"started\" && typeof data.pid === \"number\") {\n owned.record.childPid = data.pid;\n cleanup();\n resolve();\n }\n if (data.type === \"failure\") failed(new Error(`${role}: ${data.message}`));\n };\n const timer = setTimeout(\n () => failed(new Error(`${role} process startup timed out`)),\n 10_000,\n );\n child.on(\"message\", message);\n child.once(\"error\", failed);\n child.once(\"exit\", exited);\n child.send({ command }, (error) => {\n if (error) failed(error);\n });\n });\n return owned;\n } catch (error) {\n try {\n await owned.stop();\n } catch (cleanup) {\n throw new AggregateError(\n [error, cleanup],\n `Process startup and verified cleanup failed: ${String(error)}; ${String(cleanup)}`,\n );\n }\n throw error;\n }\n }\n\n logs(): string {\n return this.output;\n }\n\n assertRunning(): void {\n if (this.finished || this.child.exitCode !== null || this.child.signalCode !== null) {\n throw new Error(\n `${this.record.role} exited: ${JSON.stringify(this.finished)}\\n${this.output.slice(-8000)}`,\n );\n }\n }\n\n async wait(timeoutMs: number, signal?: AbortSignal): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (!this.finished && Date.now() < deadline) {\n signal?.throwIfAborted();\n await delay(25);\n }\n if (!this.finished)\n throw new Error(\n `${this.record.role} timed out after ${timeoutMs}ms\\n${this.output.slice(-8000)}`,\n );\n if (this.finished.code !== 0)\n throw new Error(\n `${this.record.role} failed: ${JSON.stringify(this.finished)}\\n${this.output.slice(-8000)}`,\n );\n }\n\n private async verify(): Promise<void> {\n if (this.child.exitCode !== null || this.child.signalCode !== null)\n throw new Error(\n `Refusing cleanup: ${this.record.role} group anchor ${this.record.pid} exited unexpectedly`,\n );\n let commandLine: string;\n if (process.platform === \"linux\")\n commandLine = await readFile(`/proc/${this.record.pid}/cmdline`, \"utf8\");\n else if (process.platform === \"win32\") {\n // PID is from spawn, interpolated as a number; no user text enters PowerShell.\n // https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process\n commandLine = await utility(\n \"powershell.exe\",\n [\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-Command\",\n `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${this.record.pid}').CommandLine`,\n ],\n this.records,\n );\n } else\n commandLine = await utility(\n \"/bin/ps\",\n [\"-p\", String(this.record.pid), \"-o\", \"command=\"],\n this.records,\n );\n if (!commandLineOwns(commandLine, this.record.ownerFile))\n throw new Error(\n `Refusing cleanup: PID ${this.record.pid} command line does not contain its recorded owner file`,\n );\n }\n\n stop(): Promise<void> {\n this.stopping ??= this.stopOwnedGroup().catch((error: unknown) => {\n this.stopping = undefined;\n throw error;\n });\n return this.stopping;\n }\n\n private async stopOwnedGroup(): Promise<void> {\n await this.verify();\n const exited = new Promise<void>((resolve) => this.child.once(\"exit\", () => resolve()));\n if (process.platform === \"win32\") {\n // Windows has no POSIX process groups. /T targets this verified PID's tree.\n // https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill\n await utility(\"taskkill.exe\", [\"/PID\", String(this.record.pid), \"/T\", \"/F\"], this.records);\n } else {\n process.kill(-this.record.pid, \"SIGTERM\");\n await delay(250);\n await this.verify();\n process.kill(-this.record.pid, \"SIGKILL\");\n }\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`Owned ${this.record.role} PID ${this.record.pid} did not exit`)),\n 5000,\n );\n void exited.then(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n this.record.stopped = true;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { CdpConnection } from "./cdp.mjs";
2
+ import { E2EEvidence, E2ESpec, Viewport } from "./types.mjs";
3
+ //#region ../../packages/core/src/e2e/page.d.ts
4
+ type WaitOptions = {
5
+ selector?: string;
6
+ text?: string;
7
+ url?: string;
8
+ timeoutMs?: number;
9
+ };
10
+ type ViewportReport = {
11
+ requestedWidth: number;
12
+ innerWidth: number;
13
+ documentWidth: number;
14
+ bodyWidth: number;
15
+ offenders: {
16
+ tag: string;
17
+ id: string;
18
+ className: string;
19
+ width: number;
20
+ scrollWidth: number;
21
+ right: number;
22
+ left: number;
23
+ }[];
24
+ };
25
+ export declare class BrowserPage {
26
+ private cdp;
27
+ private origin;
28
+ private outputDirectory;
29
+ private evidence;
30
+ private spec;
31
+ private viewport;
32
+ private loads;
33
+ private pendingRequests;
34
+ private requestUrls;
35
+ private lastNetworkActivity;
36
+ constructor(cdp: CdpConnection, origin: string, outputDirectory: string, evidence: E2EEvidence, spec: E2ESpec);
37
+ private completeRequest;
38
+ initialize(): Promise<void>;
39
+ step<T>(name: string, action: () => Promise<T>): Promise<T>;
40
+ evaluate<T = unknown>(expression: string, timeoutMs?: number): Promise<T>;
41
+ private poll;
42
+ goto(path: string, timeoutMs?: number): Promise<void>;
43
+ waitFor(options: WaitOptions): Promise<void>;
44
+ /** Arms the document/URL comparison BEFORE invoking the navigation action,
45
+ * covering full loads, reloads, hash changes and client-side routing.
46
+ */
47
+ waitForNavigation(action: () => Promise<unknown>, options?: {
48
+ url?: string;
49
+ timeoutMs?: number;
50
+ }): Promise<void>;
51
+ click(selector: string, timeoutMs?: number): Promise<void>;
52
+ fill(selector: string, value: string): Promise<void>;
53
+ /** Appends Unicode text through the browser's text-input path. */
54
+ type(selector: string, text: string): Promise<void>;
55
+ setViewport(viewport: Viewport): Promise<void>;
56
+ assertViewport(): Promise<ViewportReport>;
57
+ screenshot(name: string): Promise<string>;
58
+ captureViewports(name: string, viewports?: readonly Viewport[]): Promise<void>;
59
+ waitForNetworkIdle(timeoutMs?: number): Promise<void>;
60
+ assertHealthy(): void;
61
+ }
62
+ //#endregion
63
+ export {};
64
+ //# sourceMappingURL=page.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/e2e/page.ts"],"mappings":";;;KAaK;EAAgB;EAAmB;EAAe;EAAc;;KAEhE;EACH;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;;qBAuDS;UAQD;UACA;UACA;UACA;UACA;UAXF;UACA;UACA;UACA;UACA;EAER,YACU,KAAK,eACL,gBACA,yBACA,UAAU,aACV,MAAM;UAgDR;EAMF,cAAc;EAQd,KAAK,GAAG,cAAc,cAAc,QAAQ,KAAK,QAAQ;EAqBzD,SAAS,aAAa,oBAAoB,qBAAqB,QAAQ;UAa/D;EAkBR,KAAK,cAAc,qBAAqB;EAkBxC,QAAQ,SAAS,cAAc;;;;EA0B/B,kBACJ,cAAc,kBACd;IAAW;IAAc;MACxB;EAkBG,MAAM,kBAAkB,qBAAqB;EA2E7C,KAAK,kBAAkB,gBAAgB;;EA4BvC,KAAK,kBAAkB,eAAe;EA0BtC,YAAY,UAAU,WAAW;EAuBjC,kBAAkB,QAAQ;EAoB1B,WAAW,eAAe;EAY1B,iBACJ,cACA,qBAAS,aACR;EAuBG,mBAAmB,qBAAqB;EAgB9C"}