@velarscript/cli 0.10.1

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 (228) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +117 -0
  3. package/dist/application-package-host.d.ts +5 -0
  4. package/dist/application-package-host.d.ts.map +1 -0
  5. package/dist/application-package-host.js +67 -0
  6. package/dist/application-package-host.js.map +1 -0
  7. package/dist/bounded-text.d.ts +2 -0
  8. package/dist/bounded-text.d.ts.map +1 -0
  9. package/dist/bounded-text.js +13 -0
  10. package/dist/bounded-text.js.map +1 -0
  11. package/dist/browser-process-owner.d.ts +15 -0
  12. package/dist/browser-process-owner.d.ts.map +1 -0
  13. package/dist/browser-process-owner.js +151 -0
  14. package/dist/browser-process-owner.js.map +1 -0
  15. package/dist/browser-test-runner.d.ts +11 -0
  16. package/dist/browser-test-runner.d.ts.map +1 -0
  17. package/dist/browser-test-runner.js +1022 -0
  18. package/dist/browser-test-runner.js.map +1 -0
  19. package/dist/build-engine-tool.d.ts +3 -0
  20. package/dist/build-engine-tool.d.ts.map +1 -0
  21. package/dist/build-engine-tool.js +34 -0
  22. package/dist/build-engine-tool.js.map +1 -0
  23. package/dist/build-staging.d.ts +3 -0
  24. package/dist/build-staging.d.ts.map +1 -0
  25. package/dist/build-staging.js +3 -0
  26. package/dist/build-staging.js.map +1 -0
  27. package/dist/bundled-application-package-host-registry.d.ts +10 -0
  28. package/dist/bundled-application-package-host-registry.d.ts.map +1 -0
  29. package/dist/bundled-application-package-host-registry.js +16 -0
  30. package/dist/bundled-application-package-host-registry.js.map +1 -0
  31. package/dist/bundled-extension-registry.d.ts +22 -0
  32. package/dist/bundled-extension-registry.d.ts.map +1 -0
  33. package/dist/bundled-extension-registry.js +10 -0
  34. package/dist/bundled-extension-registry.js.map +1 -0
  35. package/dist/canonical-path.d.ts +3 -0
  36. package/dist/canonical-path.d.ts.map +1 -0
  37. package/dist/canonical-path.js +30 -0
  38. package/dist/canonical-path.js.map +1 -0
  39. package/dist/cli.d.ts +3 -0
  40. package/dist/cli.d.ts.map +1 -0
  41. package/dist/cli.js +1274 -0
  42. package/dist/cli.js.map +1 -0
  43. package/dist/config.d.ts +26 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +403 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/deployment-verifier.d.ts +34 -0
  48. package/dist/deployment-verifier.d.ts.map +1 -0
  49. package/dist/deployment-verifier.js +260 -0
  50. package/dist/deployment-verifier.js.map +1 -0
  51. package/dist/dev-server.d.ts +3 -0
  52. package/dist/dev-server.d.ts.map +1 -0
  53. package/dist/dev-server.js +464 -0
  54. package/dist/dev-server.js.map +1 -0
  55. package/dist/embedded-modules.d.ts +17 -0
  56. package/dist/embedded-modules.d.ts.map +1 -0
  57. package/dist/embedded-modules.js +33 -0
  58. package/dist/embedded-modules.js.map +1 -0
  59. package/dist/extension-metadata.d.ts +23 -0
  60. package/dist/extension-metadata.d.ts.map +1 -0
  61. package/dist/extension-metadata.js +254 -0
  62. package/dist/extension-metadata.js.map +1 -0
  63. package/dist/file-integrity.d.ts +7 -0
  64. package/dist/file-integrity.d.ts.map +1 -0
  65. package/dist/file-integrity.js +14 -0
  66. package/dist/file-integrity.js.map +1 -0
  67. package/dist/framework-host.d.ts +12 -0
  68. package/dist/framework-host.d.ts.map +1 -0
  69. package/dist/framework-host.js +46 -0
  70. package/dist/framework-host.js.map +1 -0
  71. package/dist/host-error.d.ts +6 -0
  72. package/dist/host-error.d.ts.map +1 -0
  73. package/dist/host-error.js +54 -0
  74. package/dist/host-error.js.map +1 -0
  75. package/dist/installed-package.d.ts +9 -0
  76. package/dist/installed-package.d.ts.map +1 -0
  77. package/dist/installed-package.js +23 -0
  78. package/dist/installed-package.js.map +1 -0
  79. package/dist/language-server-bundle-entry.d.ts +2 -0
  80. package/dist/language-server-bundle-entry.d.ts.map +1 -0
  81. package/dist/language-server-bundle-entry.js +7 -0
  82. package/dist/language-server-bundle-entry.js.map +1 -0
  83. package/dist/language-server-tool.d.ts +3 -0
  84. package/dist/language-server-tool.d.ts.map +1 -0
  85. package/dist/language-server-tool.js +30 -0
  86. package/dist/language-server-tool.js.map +1 -0
  87. package/dist/language-server.d.ts +9 -0
  88. package/dist/language-server.d.ts.map +1 -0
  89. package/dist/language-server.js +1733 -0
  90. package/dist/language-server.js.map +1 -0
  91. package/dist/mechanical-fixer.d.ts +30 -0
  92. package/dist/mechanical-fixer.d.ts.map +1 -0
  93. package/dist/mechanical-fixer.js +103 -0
  94. package/dist/mechanical-fixer.js.map +1 -0
  95. package/dist/module-assets.d.ts +11 -0
  96. package/dist/module-assets.d.ts.map +1 -0
  97. package/dist/module-assets.js +90 -0
  98. package/dist/module-assets.js.map +1 -0
  99. package/dist/npm.d.ts +23 -0
  100. package/dist/npm.d.ts.map +1 -0
  101. package/dist/npm.js +456 -0
  102. package/dist/npm.js.map +1 -0
  103. package/dist/official-language-server-extensions.d.ts +2 -0
  104. package/dist/official-language-server-extensions.d.ts.map +1 -0
  105. package/dist/official-language-server-extensions.js +38 -0
  106. package/dist/official-language-server-extensions.js.map +1 -0
  107. package/dist/official-script-language-service.d.ts +2 -0
  108. package/dist/official-script-language-service.d.ts.map +1 -0
  109. package/dist/official-script-language-service.js +10 -0
  110. package/dist/official-script-language-service.js.map +1 -0
  111. package/dist/official-tool-assets.d.ts +3 -0
  112. package/dist/official-tool-assets.d.ts.map +1 -0
  113. package/dist/official-tool-assets.js +41 -0
  114. package/dist/official-tool-assets.js.map +1 -0
  115. package/dist/ownership-graph.d.ts +46 -0
  116. package/dist/ownership-graph.d.ts.map +1 -0
  117. package/dist/ownership-graph.js +255 -0
  118. package/dist/ownership-graph.js.map +1 -0
  119. package/dist/package-manager.d.ts +20 -0
  120. package/dist/package-manager.d.ts.map +1 -0
  121. package/dist/package-manager.js +345 -0
  122. package/dist/package-manager.js.map +1 -0
  123. package/dist/packaged-official-tool.d.ts +8 -0
  124. package/dist/packaged-official-tool.d.ts.map +1 -0
  125. package/dist/packaged-official-tool.js +32 -0
  126. package/dist/packaged-official-tool.js.map +1 -0
  127. package/dist/preview-server.d.ts +11 -0
  128. package/dist/preview-server.d.ts.map +1 -0
  129. package/dist/preview-server.js +163 -0
  130. package/dist/preview-server.js.map +1 -0
  131. package/dist/production-build.d.ts +57 -0
  132. package/dist/production-build.d.ts.map +1 -0
  133. package/dist/production-build.js +332 -0
  134. package/dist/production-build.js.map +1 -0
  135. package/dist/production-verifier.d.ts +9 -0
  136. package/dist/production-verifier.d.ts.map +1 -0
  137. package/dist/production-verifier.js +268 -0
  138. package/dist/production-verifier.js.map +1 -0
  139. package/dist/program-runner.d.ts +7 -0
  140. package/dist/program-runner.d.ts.map +1 -0
  141. package/dist/program-runner.js +98 -0
  142. package/dist/program-runner.js.map +1 -0
  143. package/dist/project-check.d.ts +31 -0
  144. package/dist/project-check.d.ts.map +1 -0
  145. package/dist/project-check.js +95 -0
  146. package/dist/project-check.js.map +1 -0
  147. package/dist/project-format.d.ts +6 -0
  148. package/dist/project-format.d.ts.map +1 -0
  149. package/dist/project-format.js +21 -0
  150. package/dist/project-format.js.map +1 -0
  151. package/dist/project-semantic.d.ts +62 -0
  152. package/dist/project-semantic.d.ts.map +1 -0
  153. package/dist/project-semantic.js +1079 -0
  154. package/dist/project-semantic.js.map +1 -0
  155. package/dist/project-session.d.ts +36 -0
  156. package/dist/project-session.d.ts.map +1 -0
  157. package/dist/project-session.js +339 -0
  158. package/dist/project-session.js.map +1 -0
  159. package/dist/project-task-bundle-entry.d.ts +2 -0
  160. package/dist/project-task-bundle-entry.d.ts.map +1 -0
  161. package/dist/project-task-bundle-entry.js +18 -0
  162. package/dist/project-task-bundle-entry.js.map +1 -0
  163. package/dist/project-task-invocation.d.ts +16 -0
  164. package/dist/project-task-invocation.d.ts.map +1 -0
  165. package/dist/project-task-invocation.js +72 -0
  166. package/dist/project-task-invocation.js.map +1 -0
  167. package/dist/project-task-tool.d.ts +3 -0
  168. package/dist/project-task-tool.d.ts.map +1 -0
  169. package/dist/project-task-tool.js +61 -0
  170. package/dist/project-task-tool.js.map +1 -0
  171. package/dist/project.d.ts +71 -0
  172. package/dist/project.d.ts.map +1 -0
  173. package/dist/project.js +1750 -0
  174. package/dist/project.js.map +1 -0
  175. package/dist/reproduction.d.ts +50 -0
  176. package/dist/reproduction.d.ts.map +1 -0
  177. package/dist/reproduction.js +321 -0
  178. package/dist/reproduction.js.map +1 -0
  179. package/dist/script-language-service.d.ts +82 -0
  180. package/dist/script-language-service.d.ts.map +1 -0
  181. package/dist/script-language-service.js +18 -0
  182. package/dist/script-language-service.js.map +1 -0
  183. package/dist/source-limits.d.ts +5 -0
  184. package/dist/source-limits.d.ts.map +1 -0
  185. package/dist/source-limits.js +19 -0
  186. package/dist/source-limits.js.map +1 -0
  187. package/dist/standalone-build.d.ts +15 -0
  188. package/dist/standalone-build.d.ts.map +1 -0
  189. package/dist/standalone-build.js +76 -0
  190. package/dist/standalone-build.js.map +1 -0
  191. package/dist/standard-modules.d.ts +22 -0
  192. package/dist/standard-modules.d.ts.map +1 -0
  193. package/dist/standard-modules.js +3066 -0
  194. package/dist/standard-modules.js.map +1 -0
  195. package/dist/static-deployment.d.ts +48 -0
  196. package/dist/static-deployment.d.ts.map +1 -0
  197. package/dist/static-deployment.js +142 -0
  198. package/dist/static-deployment.js.map +1 -0
  199. package/dist/test-output.d.ts +20 -0
  200. package/dist/test-output.d.ts.map +1 -0
  201. package/dist/test-output.js +119 -0
  202. package/dist/test-output.js.map +1 -0
  203. package/dist/test-runner.d.ts +13 -0
  204. package/dist/test-runner.d.ts.map +1 -0
  205. package/dist/test-runner.js +226 -0
  206. package/dist/test-runner.js.map +1 -0
  207. package/dist/typescript-declarations.d.ts +20 -0
  208. package/dist/typescript-declarations.d.ts.map +1 -0
  209. package/dist/typescript-declarations.js +1609 -0
  210. package/dist/typescript-declarations.js.map +1 -0
  211. package/dist/uncaught-program-error.d.ts +24 -0
  212. package/dist/uncaught-program-error.d.ts.map +1 -0
  213. package/dist/uncaught-program-error.js +105 -0
  214. package/dist/uncaught-program-error.js.map +1 -0
  215. package/dist/unowned-errors.d.ts +56 -0
  216. package/dist/unowned-errors.d.ts.map +1 -0
  217. package/dist/unowned-errors.js +82 -0
  218. package/dist/unowned-errors.js.map +1 -0
  219. package/dist/version.d.ts +3 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +3 -0
  222. package/dist/version.js.map +1 -0
  223. package/dist/workspace-index.d.ts +78 -0
  224. package/dist/workspace-index.d.ts.map +1 -0
  225. package/dist/workspace-index.js +493 -0
  226. package/dist/workspace-index.js.map +1 -0
  227. package/package.json +45 -0
  228. package/skill/ai-skill.md +749 -0
@@ -0,0 +1,1022 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { spawn } from "node:child_process";
10
+ import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join, relative, resolve } from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+ import { formatDiagnostic } from "@velarscript/compiler";
15
+ import { chromium, firefox, webkit, } from "playwright";
16
+ import { compileProject } from "./project.js";
17
+ import { standardModuleSource, standardModuleSources } from "./standard-modules.js";
18
+ import { compiledTestModulePath, portablePath, quoteReportedText, writeCompiledTestProject } from "./test-output.js";
19
+ import { verifyProductionBuild } from "./production-verifier.js";
20
+ import { startProductionPreview } from "./preview-server.js";
21
+ import { hostErrorStack } from "./host-error.js";
22
+ import { captureUnownedErrors, mapCompiledStacksToSources } from "./unowned-errors.js";
23
+ import { boundedBrowserOperation, exitBrowserWorker, observeBrowserWorkerParent, superviseBrowserWorker, terminateBrowserServer, } from "./browser-process-owner.js";
24
+ /**
25
+ * The one thing an author who reached this failure did not know. A
26
+ * `.browser.test.vel` body runs in the test process and drives a page that is
27
+ * already running the built application, so a page API called from the test
28
+ * body fails on a host that has no DOM and no storage. The compile-time
29
+ * guidance for `document` says the same thing at the other end.
30
+ */
31
+ const browserTestHostGuidance = [
32
+ "A .browser.test.vel body runs in the test process, not in the page — the page already runs the built application.",
33
+ 'Drive it through `import {browser} from "velar/web-test"`: browser.open("/"), browser.fill(selector, text),',
34
+ "browser.click(selector), browser.waitForText(selector, text), browser.text(selector).",
35
+ "mount, JSX, document, and velar/storage are page APIs and are unavailable in the test process.",
36
+ ].join("\n");
37
+ const browserTypes = { chromium, firefox, webkit };
38
+ const defaultBrowserTestTimeoutMs = 120_000;
39
+ const defaultBrowserRunTimeoutMs = 20 * 60_000;
40
+ const defaultBrowserCleanupTimeoutMs = 10_000;
41
+ const browserTestWorkerEnvironment = "VELAR_BROWSER_TEST_WORKER_V1";
42
+ const browserPerformanceRuntimeKey = "velar.browser.test.performance.v1";
43
+ const browserPerformanceInitScript = String.raw `
44
+ (() => {
45
+ "use strict";
46
+ const key = Symbol.for(${JSON.stringify(browserPerformanceRuntimeKey)});
47
+ const existing = Object.getOwnPropertyDescriptor(globalThis, key);
48
+ if (existing) {
49
+ if (!("value" in existing) || !existing.value || typeof existing.value !== "object") {
50
+ throw new TypeError("Browser performance test runtime identity is invalid");
51
+ }
52
+ return;
53
+ }
54
+ const nativeObject = globalThis.Object;
55
+ const nativeNumber = globalThis.Number;
56
+ const nativeArray = globalThis.Array;
57
+ const nativeMath = globalThis.Math;
58
+ const nativeMap = globalThis.Map;
59
+ const nativePromise = globalThis.Promise;
60
+ const nativeReflect = globalThis.Reflect;
61
+ const nativePerformance = globalThis.performance;
62
+ const nativeDocument = globalThis.document;
63
+ const nativeEventTarget = globalThis.EventTarget;
64
+ const nativeEvent = globalThis.Event;
65
+ const nativeElement = globalThis.Element;
66
+ const nativeDOMRectReadOnly = globalThis.DOMRectReadOnly;
67
+ const nativeCSSStyleDeclaration = globalThis.CSSStyleDeclaration;
68
+ const getComputedStyle_ = globalThis.getComputedStyle;
69
+ const getOwnPropertyDescriptor = nativeObject.getOwnPropertyDescriptor;
70
+ const defineProperty = nativeObject.defineProperty;
71
+ const freeze = nativeObject.freeze;
72
+ const getPrototypeOf = nativeObject.getPrototypeOf;
73
+ const reflectApply = getOwnPropertyDescriptor(nativeReflect, "apply")?.value;
74
+ const numberFinite = getOwnPropertyDescriptor(nativeNumber, "isFinite")?.value;
75
+ const arrayIsArray = getOwnPropertyDescriptor(nativeArray, "isArray")?.value;
76
+ const mathMax = getOwnPropertyDescriptor(nativeMath, "max")?.value;
77
+ const mapPrototype = getOwnPropertyDescriptor(nativeMap, "prototype")?.value;
78
+ const mapGet = getOwnPropertyDescriptor(mapPrototype, "get")?.value;
79
+ const mapSet = getOwnPropertyDescriptor(mapPrototype, "set")?.value;
80
+ const mapDelete = getOwnPropertyDescriptor(mapPrototype, "delete")?.value;
81
+ const mapSize = getOwnPropertyDescriptor(mapPrototype, "size")?.get;
82
+ const eventAdd = getOwnPropertyDescriptor(nativeEventTarget.prototype, "addEventListener")?.value;
83
+ const eventRemove = getOwnPropertyDescriptor(nativeEventTarget.prototype, "removeEventListener")?.value;
84
+ const eventTarget = getOwnPropertyDescriptor(nativeEvent.prototype, "target")?.get;
85
+ const eventTimeStamp = getOwnPropertyDescriptor(nativeEvent.prototype, "timeStamp")?.get;
86
+ const elementScrollTo = getOwnPropertyDescriptor(nativeElement.prototype, "scrollTo")?.value;
87
+ const elementBoundingClientRect = getOwnPropertyDescriptor(nativeElement.prototype, "getBoundingClientRect")?.value;
88
+ const domRectPrototype = getOwnPropertyDescriptor(nativeDOMRectReadOnly, "prototype")?.value;
89
+ const rectX = getOwnPropertyDescriptor(domRectPrototype, "x")?.get;
90
+ const rectY = getOwnPropertyDescriptor(domRectPrototype, "y")?.get;
91
+ const rectWidth = getOwnPropertyDescriptor(domRectPrototype, "width")?.get;
92
+ const rectHeight = getOwnPropertyDescriptor(domRectPrototype, "height")?.get;
93
+ const rectTop = getOwnPropertyDescriptor(domRectPrototype, "top")?.get;
94
+ const rectRight = getOwnPropertyDescriptor(domRectPrototype, "right")?.get;
95
+ const rectBottom = getOwnPropertyDescriptor(domRectPrototype, "bottom")?.get;
96
+ const rectLeft = getOwnPropertyDescriptor(domRectPrototype, "left")?.get;
97
+ const cssStylePrototype = getOwnPropertyDescriptor(nativeCSSStyleDeclaration, "prototype")?.value;
98
+ const cssGetPropertyValue = getOwnPropertyDescriptor(cssStylePrototype, "getPropertyValue")?.value;
99
+ const performancePrototype = callPrototype(nativePerformance);
100
+ const performanceNow = getOwnPropertyDescriptor(performancePrototype, "now")?.value;
101
+ const performanceEntriesByType = getOwnPropertyDescriptor(performancePrototype, "getEntriesByType")?.value;
102
+ const performanceEntriesByName = getOwnPropertyDescriptor(performancePrototype, "getEntriesByName")?.value;
103
+ const performanceTimeOrigin = nativePerformance.timeOrigin;
104
+ const queueMicrotask_ = getOwnPropertyDescriptor(globalThis, "queueMicrotask")?.value;
105
+ const requestAnimationFrame_ = getOwnPropertyDescriptor(globalThis, "requestAnimationFrame")?.value;
106
+ const setTimeout_ = getOwnPropertyDescriptor(globalThis, "setTimeout")?.value;
107
+ const clearTimeout_ = getOwnPropertyDescriptor(globalThis, "clearTimeout")?.value;
108
+ const measurements = new nativeMap();
109
+ let nextMeasurement = 1;
110
+
111
+ function callPrototype(value) {
112
+ if (typeof getPrototypeOf !== "function") throw new TypeError("Object.getPrototypeOf is unavailable");
113
+ return getPrototypeOf(value);
114
+ }
115
+ function call(operation, receiver, args, name) {
116
+ if (typeof operation !== "function" || typeof reflectApply !== "function") throw new TypeError(name + " is unavailable");
117
+ return reflectApply(operation, receiver, args);
118
+ }
119
+ function finite(value, name) {
120
+ if (!call(numberFinite, nativeNumber, [value], "Number.isFinite") || value < 0 || value > 600000) {
121
+ throw new RangeError(name + " is outside the browser performance test bound");
122
+ }
123
+ return value;
124
+ }
125
+ function now() { return finite(call(performanceNow, nativePerformance, [], "performance.now"), "Browser monotonic time"); }
126
+ function entries(operation, value) {
127
+ const result = call(operation, nativePerformance, [value], "Performance entry lookup");
128
+ if (!call(arrayIsArray, nativeArray, [result], "Array.isArray") || result.length > 10000) throw new TypeError("Browser performance entries are invalid");
129
+ return result;
130
+ }
131
+ function field(value, name) {
132
+ const descriptor = value && getOwnPropertyDescriptor(value, name);
133
+ if (descriptor && "value" in descriptor) return descriptor.value;
134
+ let prototype = value && callPrototype(value);
135
+ for (let depth = 0; prototype && depth < 8; depth += 1) {
136
+ const getter = getOwnPropertyDescriptor(prototype, name)?.get;
137
+ if (typeof getter === "function") return call(getter, value, [], "Performance entry " + name);
138
+ prototype = callPrototype(prototype);
139
+ }
140
+ throw new TypeError("Performance entry " + name + " is unavailable");
141
+ }
142
+ function timings() {
143
+ const navigation = entries(performanceEntriesByType, "navigation")[0];
144
+ if (!navigation) throw new Error("The browser did not expose navigation timing");
145
+ const paints = entries(performanceEntriesByName, "first-contentful-paint");
146
+ const paint = paints.length === 0 ? null : finite(field(paints[0], "startTime"), "First contentful paint");
147
+ return freeze({
148
+ firstContentfulPaintMs: paint,
149
+ domContentLoadedMs: finite(field(navigation, "domContentLoadedEventEnd"), "DOMContentLoaded timing"),
150
+ loadMs: finite(field(navigation, "loadEventEnd"), "Load timing"),
151
+ });
152
+ }
153
+ function scroll(element, x, y) {
154
+ if (!(element instanceof nativeElement)) throw new TypeError("Browser test scroll target must be an Element");
155
+ if (typeof x !== "number" || !call(numberFinite, nativeNumber, [x], "Number.isFinite")
156
+ || typeof y !== "number" || !call(numberFinite, nativeNumber, [y], "Number.isFinite")
157
+ || call(mathMax, nativeMath, [x, -x, y, -y], "Math.max") > 100000000) {
158
+ throw new RangeError("Browser test scroll coordinates are outside the supported bound");
159
+ }
160
+ call(elementScrollTo, element, [{ left: x, top: y, behavior: "auto" }], "Element.scrollTo");
161
+ return null;
162
+ }
163
+ function rectangleNumber(rectangle, getter, name) {
164
+ const value = call(getter, rectangle, [], "DOMRect." + name);
165
+ if (typeof value !== "number" || !call(numberFinite, nativeNumber, [value], "Number.isFinite")) {
166
+ throw new TypeError("Browser test rectangle " + name + " is not finite");
167
+ }
168
+ return value;
169
+ }
170
+ function box(element) {
171
+ if (!(element instanceof nativeElement)) throw new TypeError("browser.box target must be an Element");
172
+ const rectangle = call(elementBoundingClientRect, element, [], "Element.getBoundingClientRect");
173
+ return freeze({
174
+ x: rectangleNumber(rectangle, rectX, "x"),
175
+ y: rectangleNumber(rectangle, rectY, "y"),
176
+ width: rectangleNumber(rectangle, rectWidth, "width"),
177
+ height: rectangleNumber(rectangle, rectHeight, "height"),
178
+ top: rectangleNumber(rectangle, rectTop, "top"),
179
+ right: rectangleNumber(rectangle, rectRight, "right"),
180
+ bottom: rectangleNumber(rectangle, rectBottom, "bottom"),
181
+ left: rectangleNumber(rectangle, rectLeft, "left"),
182
+ });
183
+ }
184
+ function style(element, property) {
185
+ if (!(element instanceof nativeElement)) throw new TypeError("browser.style target must be an Element");
186
+ if (typeof property !== "string") throw new TypeError("browser.style property must be text");
187
+ const declaration = call(getComputedStyle_, globalThis, [element], "getComputedStyle");
188
+ const value = call(cssGetPropertyValue, declaration, [property], "CSSStyleDeclaration.getPropertyValue");
189
+ if (typeof value !== "string") throw new TypeError("browser.style result must be text");
190
+ return value;
191
+ }
192
+ function prepare(element, eventName) {
193
+ if (eventName !== "click" && eventName !== "input" && eventName !== "beforeinput-or-input") {
194
+ throw new TypeError("Measured browser event must be click, input, or beforeinput-or-input");
195
+ }
196
+ if (!(element instanceof nativeEventTarget)) throw new TypeError("Measured browser target must be an EventTarget");
197
+ if (call(mapSize, measurements, [], "Map.size") >= 32) throw new RangeError("Too many pending browser performance measurements");
198
+ const eventNames = eventName === "beforeinput-or-input" ? ["beforeinput", "input"] : [eventName];
199
+ const id = nextMeasurement++;
200
+ let finish;
201
+ let fail;
202
+ const promise = new nativePromise((resolve, reject) => { finish = resolve; fail = reject; });
203
+ let timer = null;
204
+ let listening = true;
205
+ const cleanup = () => {
206
+ if (listening) {
207
+ listening = false;
208
+ for (const name of eventNames) call(eventRemove, nativeDocument, [name, listener, true], "Document.removeEventListener");
209
+ }
210
+ if (timer !== null) {
211
+ call(clearTimeout_, globalThis, [timer], "clearTimeout");
212
+ timer = null;
213
+ }
214
+ };
215
+ const listener = (event) => {
216
+ if (call(eventTarget, event, [], "Event.target") !== element) return;
217
+ cleanup();
218
+ try {
219
+ const listenerStart = now();
220
+ let eventTime = call(eventTimeStamp, event, [], "Event.timeStamp");
221
+ if (typeof eventTime !== "number" || !call(numberFinite, nativeNumber, [eventTime], "Number.isFinite")) eventTime = listenerStart;
222
+ if (eventTime > listenerStart + 1000 && typeof performanceTimeOrigin === "number") eventTime -= performanceTimeOrigin;
223
+ if (eventTime < 0 || eventTime > listenerStart + 1000) eventTime = listenerStart;
224
+ const inputDelayMs = finite(call(mathMax, nativeMath, [0, listenerStart - eventTime], "Math.max"), "Input delay");
225
+ call(queueMicrotask_, globalThis, [() => {
226
+ try {
227
+ const processingDurationMs = finite(call(mathMax, nativeMath, [0, now() - listenerStart], "Math.max"), "Input processing duration");
228
+ call(requestAnimationFrame_, globalThis, [(frameTime) => {
229
+ try {
230
+ const nextFrameMs = finite(call(mathMax, nativeMath, [inputDelayMs + processingDurationMs, frameTime - eventTime], "Math.max"), "Next frame timing");
231
+ finish(freeze({ inputDelayMs, processingDurationMs, nextFrameMs }));
232
+ } catch (error) { fail(error); }
233
+ }], "requestAnimationFrame");
234
+ } catch (error) { fail(error); }
235
+ }], "queueMicrotask");
236
+ } catch (error) { fail(error); }
237
+ };
238
+ for (const name of eventNames) call(eventAdd, nativeDocument, [name, listener, true], "Document.addEventListener");
239
+ timer = call(setTimeout_, globalThis, [() => { cleanup(); fail(new Error("Measured browser interaction did not dispatch its expected event")); }, 5000], "setTimeout");
240
+ call(mapSet, measurements, [id, freeze({ promise, cleanup })], "Map.set");
241
+ return id;
242
+ }
243
+ async function finishMeasurement(id) {
244
+ const measurement = call(mapGet, measurements, [id], "Map.get");
245
+ if (!measurement) throw new Error("Browser performance measurement is unknown");
246
+ try { return await measurement.promise; }
247
+ finally { measurement.cleanup(); call(mapDelete, measurements, [id], "Map.delete"); }
248
+ }
249
+ function cancel(id) {
250
+ const measurement = call(mapGet, measurements, [id], "Map.get");
251
+ if (!measurement) return null;
252
+ measurement.cleanup();
253
+ call(mapDelete, measurements, [id], "Map.delete");
254
+ return null;
255
+ }
256
+ defineProperty(globalThis, key, { value: freeze({ timings, scroll, box, style, prepare, finish: finishMeasurement, cancel }), enumerable: false, configurable: false, writable: false });
257
+ })();
258
+ `;
259
+ class BrowserTestInterrupted extends Error {
260
+ exitCode;
261
+ constructor(signal, exitCode) {
262
+ super(`Browser test run interrupted by ${signal}`);
263
+ this.name = "BrowserTestInterrupted";
264
+ this.exitCode = exitCode;
265
+ }
266
+ }
267
+ class BrowserTestRunTimedOut extends Error {
268
+ constructor(timeoutMs) {
269
+ super(`Browser test run exceeded its ${timeoutMs} millisecond aggregate deadline`);
270
+ this.name = "BrowserTestRunTimedOut";
271
+ }
272
+ }
273
+ function browserTestLimits(options) {
274
+ const bounded = (value, fallback, name, maximum) => {
275
+ const resolved = value ?? fallback;
276
+ if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > maximum) {
277
+ throw new RangeError(`${name} must be an integer from 1 through ${maximum}`);
278
+ }
279
+ return resolved;
280
+ };
281
+ return {
282
+ testTimeoutMs: bounded(options.testTimeoutMs, defaultBrowserTestTimeoutMs, "Browser test timeout", 10 * 60_000),
283
+ runTimeoutMs: bounded(options.runTimeoutMs, defaultBrowserRunTimeoutMs, "Browser test run timeout", 60 * 60_000),
284
+ cleanupTimeoutMs: bounded(options.cleanupTimeoutMs, defaultBrowserCleanupTimeoutMs, "Browser cleanup timeout", 60_000),
285
+ };
286
+ }
287
+ export async function runBrowserTests(config, explicitInput, selection, options = {}) {
288
+ const workerOptions = process.env[browserTestWorkerEnvironment];
289
+ if (workerOptions !== undefined && typeof process.send === "function") {
290
+ const resolvedOptions = browserTestWorkerOptions(workerOptions);
291
+ const stopObservingParent = observeBrowserWorkerParent();
292
+ // The worker owns its process and ends it with process.exit, so the exit
293
+ // net is this runner's last guarantee that a report arriving after the
294
+ // verdict cannot leave the run green.
295
+ const channel = captureUnownedErrors({ exitNet: true });
296
+ mapCompiledStacksToSources();
297
+ let code = 1;
298
+ try {
299
+ code = await runBrowserTestsInWorker(config, explicitInput, selection, resolvedOptions, channel);
300
+ }
301
+ catch (error) {
302
+ process.stderr.write(`${stackOf(error)}\n`);
303
+ }
304
+ const trailing = await channel.drain();
305
+ if (trailing.length > 0) {
306
+ process.stderr.write(`✗ an unowned error was reported after the last browser test\n${trailing.join("\n")}\n${browserTestHostGuidance}\n`);
307
+ code = code === 0 ? 1 : code;
308
+ }
309
+ stopObservingParent();
310
+ await exitBrowserWorker(code);
311
+ }
312
+ return superviseBrowserTests(config, explicitInput, selection, options);
313
+ }
314
+ async function superviseBrowserTests(config, explicitInput, selection, options) {
315
+ const limits = browserTestLimits(options);
316
+ const executable = resolve(options.executable ?? process.argv[1]);
317
+ const input = explicitInput === null ? config.root : resolve(explicitInput);
318
+ return superviseBrowserWorker({
319
+ executable: process.execPath,
320
+ arguments: [executable, "test", input, `--browser=${selection}`],
321
+ cwd: config.root,
322
+ environment: { ...process.env, [browserTestWorkerEnvironment]: JSON.stringify(limits) },
323
+ deadlineMs: limits.runTimeoutMs,
324
+ cleanupTimeoutMs: limits.cleanupTimeoutMs,
325
+ });
326
+ }
327
+ function browserTestWorkerOptions(value) {
328
+ let parsed;
329
+ try {
330
+ parsed = JSON.parse(value);
331
+ }
332
+ catch {
333
+ throw new TypeError("Browser test worker options are invalid");
334
+ }
335
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)
336
+ || Object.keys(parsed).some((name) => name !== "testTimeoutMs" && name !== "runTimeoutMs" && name !== "cleanupTimeoutMs")) {
337
+ throw new TypeError("Browser test worker options are invalid");
338
+ }
339
+ const record = parsed;
340
+ for (const name of ["testTimeoutMs", "runTimeoutMs", "cleanupTimeoutMs"]) {
341
+ const descriptor = Object.getOwnPropertyDescriptor(record, name);
342
+ if (!descriptor?.enumerable || !("value" in descriptor) || typeof descriptor.value !== "number") {
343
+ throw new TypeError("Browser test worker options are invalid");
344
+ }
345
+ }
346
+ return browserTestLimits(record);
347
+ }
348
+ async function runBrowserTestsInWorker(config, explicitInput, selection, options, channel) {
349
+ const limits = browserTestLimits(options);
350
+ const contract = config.framework?.host.browserTests;
351
+ if (!config.framework || !contract) {
352
+ process.stderr.write("The project framework does not provide browser-test hosting\n");
353
+ return 1;
354
+ }
355
+ const runtimeKey = Symbol.for(contract.runtimeKey);
356
+ const files = explicitInput?.endsWith(contract.sourceSuffix)
357
+ ? [resolve(explicitInput)]
358
+ : await discoverBrowserTestFiles(config.root, new Set([config.outDir, config.publicDir]), contract.sourceSuffix);
359
+ if (files.length === 0) {
360
+ process.stderr.write("No .browser.test.vel files were found\n");
361
+ return 1;
362
+ }
363
+ const temporary = await mkdtemp(join(tmpdir(), "velar-browser-tests-"));
364
+ const site = join(temporary, "site");
365
+ const compiled = join(temporary, "tests");
366
+ let server = null;
367
+ let activeBrowser = null;
368
+ let activeBrowserServer = null;
369
+ let passed = 0;
370
+ let failed = 0;
371
+ try {
372
+ const build = await buildProject(config, site, options.executable);
373
+ if (!build.ok) {
374
+ process.stderr.write(build.output);
375
+ return 1;
376
+ }
377
+ const verified = await verifyProductionBuild(site);
378
+ await prepareStandardModules(compiled, config);
379
+ const entries = [];
380
+ for (const file of files) {
381
+ const entry = await compileBrowserTest(file, compiled, config);
382
+ if (!entry) {
383
+ failed += 1;
384
+ continue;
385
+ }
386
+ entries.push(entry);
387
+ }
388
+ if (entries.length === 0) {
389
+ process.stdout.write(`\n${passed} passed, ${failed} failed\n`);
390
+ return 1;
391
+ }
392
+ server = await startProductionPreview(verified, 0);
393
+ const origin = server.origin;
394
+ const engines = selection === "all"
395
+ ? ["chromium", "firefox", "webkit"]
396
+ : [selection];
397
+ let lifecycleFailure = null;
398
+ let rejectLifecycle;
399
+ const lifecycle = new Promise((_resolve, reject) => { rejectLifecycle = reject; });
400
+ void lifecycle.catch(() => { });
401
+ const stop = (error) => {
402
+ if (lifecycleFailure !== null)
403
+ return;
404
+ lifecycleFailure = error;
405
+ rejectLifecycle(error);
406
+ };
407
+ const onHangup = () => stop(new BrowserTestInterrupted("SIGHUP", 129));
408
+ const onInterrupt = () => stop(new BrowserTestInterrupted("SIGINT", 130));
409
+ const onTerminate = () => stop(new BrowserTestInterrupted("SIGTERM", 143));
410
+ process.once("SIGHUP", onHangup);
411
+ process.once("SIGINT", onInterrupt);
412
+ process.once("SIGTERM", onTerminate);
413
+ const runTimer = setTimeout(() => stop(new BrowserTestRunTimedOut(limits.runTimeoutMs)), limits.runTimeoutMs);
414
+ try {
415
+ for (const engine of engines) {
416
+ if (lifecycleFailure !== null)
417
+ throw lifecycleFailure;
418
+ let engineStarted = false;
419
+ let engineUsable = true;
420
+ try {
421
+ activeBrowserServer = await browserTypes[engine].launchServer({ headless: true, timeout: 30_000 });
422
+ engineStarted = true;
423
+ if (lifecycleFailure !== null)
424
+ throw lifecycleFailure;
425
+ activeBrowser = await browserTypes[engine].connect(activeBrowserServer.wsEndpoint(), { timeout: 30_000 });
426
+ engineEntries: for (const entry of entries) {
427
+ let namespace;
428
+ try {
429
+ namespace = await import(__rewriteRelativeImportExtension(`${pathToFileURL(entry.output).href}?engine=${engine}&run=${Date.now()}`));
430
+ }
431
+ catch (error) {
432
+ if (lifecycleFailure !== null)
433
+ throw lifecycleFailure;
434
+ failed += entry.tests.length;
435
+ process.stderr.write(`✗ ${engine} :: ${portablePath(relative(config.root, entry.file))} failed to load\n${stackOf(error)}\n${browserTestHostGuidance}\n`);
436
+ await channel.drain();
437
+ continue;
438
+ }
439
+ // A module initialization error that surfaced on the host channel
440
+ // instead of the import's own await — the shape a mounted entry
441
+ // takes when a browser test imports it — fails the file's tests
442
+ // before any of them can run green.
443
+ const loadTimeErrors = await channel.drain();
444
+ if (loadTimeErrors.length > 0) {
445
+ failed += entry.tests.length;
446
+ process.stderr.write(`✗ ${engine} :: ${portablePath(relative(config.root, entry.file))} reported an unowned error while loading\n${loadTimeErrors.join("\n")}\n${browserTestHostGuidance}\n`);
447
+ continue;
448
+ }
449
+ for (const declared of entry.tests) {
450
+ if (lifecycleFailure !== null)
451
+ throw lifecycleFailure;
452
+ // D39 item 53: the reporter quotes the author's name for the
453
+ // test, which is the specification a person reads.
454
+ // D51 rule 105: the browser verdict line escapes author text too.
455
+ const name = quoteReportedText(declared.title);
456
+ const test = namespace[declared.name];
457
+ const context = await activeBrowser.newContext();
458
+ const page = await context.newPage();
459
+ page.setDefaultTimeout(30_000);
460
+ page.setDefaultNavigationTimeout(30_000);
461
+ const runtimeFailures = [];
462
+ let testFailure = null;
463
+ page.on("pageerror", (error) => runtimeFailures.push(error.stack ?? error.message));
464
+ page.on("console", (message) => {
465
+ if (message.type() === "error" || message.type() === "warning") {
466
+ runtimeFailures.push(`${message.type()}: ${message.text()}`);
467
+ }
468
+ });
469
+ try {
470
+ await installBrowserPerformanceRuntime(page);
471
+ const frameworkConfig = config.framework.config;
472
+ const frameworkController = contract.createController?.(frameworkConfig);
473
+ if (frameworkController !== undefined
474
+ && (typeof frameworkController !== "object" || frameworkController === null
475
+ || typeof frameworkController.initScript !== "function"
476
+ || typeof frameworkController.invoke !== "function")) {
477
+ throw new Error("Framework browser-test controller is invalid");
478
+ }
479
+ installBrowserRuntime(page, origin, verified.deployment.base, runtimeKey, frameworkController, contract.initScript === undefined ? undefined : () => contract.initScript(frameworkConfig));
480
+ if (typeof test !== "function")
481
+ throw new Error(`Test ${name} was not emitted`);
482
+ if (test.length !== 0)
483
+ throw new Error(`Browser test ${name} cannot declare parameters`);
484
+ await boundedBrowserOperation(Promise.resolve().then(() => test()), limits.testTimeoutMs, `Browser test ${quoteReportedText(portablePath(relative(config.root, entry.file)))} :: ${name}`, lifecycle);
485
+ if (runtimeFailures.length > 0)
486
+ throw new Error(`Browser runtime failures:\n${runtimeFailures.join("\n")}`);
487
+ }
488
+ catch (error) {
489
+ testFailure = error;
490
+ }
491
+ finally {
492
+ removeBrowserRuntime(runtimeKey);
493
+ try {
494
+ await boundedBrowserOperation(context.close(), limits.cleanupTimeoutMs, "Browser context cleanup");
495
+ }
496
+ catch (cleanupError) {
497
+ engineUsable = false;
498
+ testFailure = new Error(`${testFailure === null ? "Browser test completed but its context leaked" : stackOf(testFailure)}\n${stackOf(cleanupError)}`);
499
+ }
500
+ }
501
+ // The host error channel is the third way a failure reaches a
502
+ // human here, and the one the runner used to ignore: the test
503
+ // body runs in this process, so a page API it calls, a detached
504
+ // task it starts, and a module it imports all report through
505
+ // console.error, uncaughtException, or unhandledRejection rather
506
+ // than through the page. Draining after the verdict also catches
507
+ // page reports queued while the context closed.
508
+ const hostReports = await channel.drain();
509
+ if (testFailure === null && (hostReports.length > 0 || runtimeFailures.length > 0)) {
510
+ const reported = [...runtimeFailures, ...hostReports].join("\n");
511
+ testFailure = new Error(hostReports.length > 0
512
+ ? `Browser runtime failures:\n${reported}\n${browserTestHostGuidance}`
513
+ : `Browser runtime failures:\n${reported}`);
514
+ }
515
+ if (testFailure instanceof BrowserTestInterrupted || testFailure instanceof BrowserTestRunTimedOut)
516
+ throw testFailure;
517
+ if (testFailure === null) {
518
+ passed += 1;
519
+ process.stdout.write(`✓ ${engine} :: ${quoteReportedText(portablePath(relative(config.root, entry.file)))} :: ${name}\n`);
520
+ }
521
+ else {
522
+ failed += 1;
523
+ process.stderr.write(`✗ ${engine} :: ${quoteReportedText(portablePath(relative(config.root, entry.file)))} :: ${name}\n${stackOf(testFailure)}\n`);
524
+ }
525
+ if (!engineUsable) {
526
+ process.stderr.write(`✗ ${engine} was retired after context cleanup failed\n`);
527
+ break engineEntries;
528
+ }
529
+ }
530
+ }
531
+ }
532
+ catch (error) {
533
+ if (lifecycleFailure !== null)
534
+ throw lifecycleFailure;
535
+ if (error instanceof BrowserTestInterrupted || error instanceof BrowserTestRunTimedOut)
536
+ throw error;
537
+ if (!engineStarted) {
538
+ failed += entries.reduce((count, entry) => count + entry.tests.length, 0);
539
+ process.stderr.write(`✗ ${engine} could not start\n${stackOf(error)}\nInstall it with: npx playwright install ${engine}\n`);
540
+ }
541
+ else {
542
+ failed += 1;
543
+ process.stderr.write(`✗ ${engine} browser-test owner failed\n${stackOf(error)}\n`);
544
+ }
545
+ }
546
+ finally {
547
+ if (activeBrowserServer !== null) {
548
+ const owned = activeBrowserServer;
549
+ const connection = activeBrowser;
550
+ activeBrowser = null;
551
+ activeBrowserServer = null;
552
+ await terminateBrowserServer(connection, owned, limits.cleanupTimeoutMs);
553
+ }
554
+ }
555
+ }
556
+ }
557
+ catch (error) {
558
+ if (error instanceof BrowserTestInterrupted) {
559
+ process.stderr.write(`${error.message}\n`);
560
+ return error.exitCode;
561
+ }
562
+ if (error instanceof BrowserTestRunTimedOut) {
563
+ failed += 1;
564
+ process.stderr.write(`✗ ${error.message}\n`);
565
+ }
566
+ else {
567
+ throw error;
568
+ }
569
+ }
570
+ finally {
571
+ clearTimeout(runTimer);
572
+ process.off("SIGHUP", onHangup);
573
+ process.off("SIGINT", onInterrupt);
574
+ process.off("SIGTERM", onTerminate);
575
+ }
576
+ }
577
+ finally {
578
+ removeBrowserRuntime(runtimeKey);
579
+ let cleanupFailure = null;
580
+ if (activeBrowserServer !== null) {
581
+ try {
582
+ await terminateBrowserServer(activeBrowser, activeBrowserServer, limits.cleanupTimeoutMs);
583
+ }
584
+ catch (error) {
585
+ cleanupFailure = error;
586
+ }
587
+ activeBrowser = null;
588
+ activeBrowserServer = null;
589
+ }
590
+ if (server) {
591
+ try {
592
+ await boundedBrowserOperation(server.close(), limits.cleanupTimeoutMs, "Browser preview cleanup");
593
+ }
594
+ catch (error) {
595
+ cleanupFailure ??= error;
596
+ }
597
+ }
598
+ try {
599
+ await rm(temporary, { recursive: true, force: true });
600
+ }
601
+ catch (error) {
602
+ cleanupFailure ??= error;
603
+ }
604
+ if (cleanupFailure !== null)
605
+ throw cleanupFailure;
606
+ }
607
+ // The cleanup above released everything this runner owns, so whatever still
608
+ // holds the loop is work a test started. Waiting for it before the verdict is
609
+ // what keeps a late failure from being dropped — and keeps the printed count
610
+ // honest about it.
611
+ if (!await settleWorkerWork(channel, limits.cleanupTimeoutMs)) {
612
+ failed += 1;
613
+ process.stderr.write("✗ work a browser test started was still running when the run ended; a test owns the work it starts, and a failure from work that never finishes can never be reported\n");
614
+ }
615
+ const trailing = await channel.drain();
616
+ if (trailing.length > 0) {
617
+ failed += 1;
618
+ process.stderr.write(`✗ an unowned error was reported after the last browser test\n${trailing.join("\n")}\n${browserTestHostGuidance}\n`);
619
+ }
620
+ process.stdout.write(`\n${passed} passed, ${failed} failed\n`);
621
+ return failed === 0 ? 0 : 1;
622
+ }
623
+ /**
624
+ * Work a browser test started outlives the last test exactly as it does in the
625
+ * Node runner, and the worker ends with process.exit, so the run must wait for
626
+ * the process to run out of work before it takes its verdict. The one handle
627
+ * that would make quiescence unobservable is the worker's own IPC channel to
628
+ * its supervisor; unreferencing it for the settle window does not close it —
629
+ * the disconnect that reports a dead parent still arrives.
630
+ */
631
+ async function settleWorkerWork(channel, timeoutMs) {
632
+ const supervisorChannel = process.channel;
633
+ supervisorChannel?.unref?.();
634
+ try {
635
+ return await channel.settle(timeoutMs);
636
+ }
637
+ finally {
638
+ supervisorChannel?.ref?.();
639
+ }
640
+ }
641
+ async function installFrameworkRuntime(page, source) {
642
+ if (source === undefined)
643
+ return;
644
+ if (typeof source !== "string" || source.length === 0 || Buffer.byteLength(source, "utf8") > 1024 * 1024) {
645
+ throw new Error("Framework browser-test init script must contain 1 byte through 1 MiB of text");
646
+ }
647
+ await page.addInitScript({ content: source });
648
+ }
649
+ async function installBrowserPerformanceRuntime(page) {
650
+ await page.addInitScript({ content: browserPerformanceInitScript });
651
+ }
652
+ async function compileBrowserTest(file, outputRoot, config) {
653
+ const project = await compileProject(file, new Map(), {
654
+ sourceRoot: config.root,
655
+ projectRoot: config.root,
656
+ publicRoot: config.publicDir,
657
+ extensions: config.compilerExtensions,
658
+ extensionConfig: config.extensionConfig,
659
+ framework: config.framework,
660
+ exportTestFunctions: true,
661
+ });
662
+ const errors = [
663
+ ...project.failures.map((failure) => `${failure.path}: ${failure.message}`),
664
+ ...project.modules.flatMap((module) => module.result.diagnostics.map((diagnostic) => formatDiagnostic(module.result.source, diagnostic))),
665
+ ];
666
+ if (errors.length > 0) {
667
+ process.stderr.write(`✗ ${portablePath(relative(config.root, file))}\n${errors.join("\n\n")}\n`);
668
+ return null;
669
+ }
670
+ await writeCompiledTestProject(project, outputRoot);
671
+ const entry = project.modules.find((module) => module.inputPath === file);
672
+ const tests = entry?.result.moduleInterface.tests ?? [];
673
+ if (tests.length === 0) {
674
+ process.stderr.write(`✗ ${portablePath(relative(config.root, file))} declares no tests\n`);
675
+ return null;
676
+ }
677
+ return { file, output: entry ? compiledTestModulePath(project, entry, outputRoot) : join(outputRoot, relative(config.root, file).replace(/\.vel$/u, ".js")), tests };
678
+ }
679
+ function installBrowserRuntime(page, origin, base, runtimeKey, frameworkController = undefined, frameworkInitScript = undefined) {
680
+ const locator = (selector) => page.locator(String(selector));
681
+ const storageArea = (area) => {
682
+ const value = String(area);
683
+ if (value !== "local" && value !== "session")
684
+ throw new Error("Browser test storage area must be local or session");
685
+ return value;
686
+ };
687
+ const mockedRoutes = new Set();
688
+ let frameworkRuntime = null;
689
+ const installFrameworkBeforeOpen = () => {
690
+ frameworkRuntime ??= installFrameworkRuntime(page, frameworkController?.initScript() ?? frameworkInitScript?.());
691
+ return frameworkRuntime;
692
+ };
693
+ const runtime = Object.freeze({
694
+ async open(path = "/") {
695
+ const value = String(path);
696
+ if (!value.startsWith("/"))
697
+ throw new Error("browser.open requires an application-relative path starting with '/'");
698
+ const target = base === "/" ? value : `${base.slice(0, -1)}${value}`;
699
+ await installFrameworkBeforeOpen();
700
+ // Navigation owns document loading, not application network quiescence.
701
+ // A product may poll or stream forever; tests establish readiness with
702
+ // the web-first waitFor/waitForText assertions exposed beside open().
703
+ await page.goto(new URL(target, origin).href, { waitUntil: "load" });
704
+ return null;
705
+ },
706
+ async reload() { await page.reload({ waitUntil: "load" }); return null; },
707
+ async click(selector) { await locator(selector).click(); return null; },
708
+ async fill(selector, value) { await locator(selector).fill(String(value)); return null; },
709
+ async select(selector, value) { await locator(selector).selectOption(String(value)); return null; },
710
+ async press(selector, key) { await locator(selector).press(String(key)); return null; },
711
+ async scroll(selector, x, y) {
712
+ if (typeof x !== "number" || !Number.isFinite(x) || typeof y !== "number" || !Number.isFinite(y)
713
+ || Math.abs(x) > 100_000_000 || Math.abs(y) > 100_000_000) {
714
+ throw new RangeError("browser.scroll requires finite coordinates within 100000000 pixels");
715
+ }
716
+ await locator(selector).evaluate((element, position) => {
717
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(position.key))?.value;
718
+ if (!runtime || typeof runtime.scroll !== "function")
719
+ throw new Error("Browser test scroll runtime is unavailable");
720
+ runtime.scroll(element, position.x, position.y);
721
+ }, { key: browserPerformanceRuntimeKey, x, y });
722
+ return null;
723
+ },
724
+ async text(selector) { return await locator(selector).textContent() ?? ""; },
725
+ async attribute(selector, name) { return locator(selector).getAttribute(String(name)); },
726
+ async box(selector) {
727
+ return locator(selector).evaluate((element, key) => {
728
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(key))?.value;
729
+ if (!runtime || typeof runtime.box !== "function")
730
+ throw new Error("Browser test geometry runtime is unavailable");
731
+ return runtime.box(element);
732
+ }, browserPerformanceRuntimeKey);
733
+ },
734
+ async style(selector, property) {
735
+ return locator(selector).evaluate((element, input) => {
736
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(input.key))?.value;
737
+ if (!runtime || typeof runtime.style !== "function")
738
+ throw new Error("Browser test computed-style runtime is unavailable");
739
+ return runtime.style(element, input.property);
740
+ }, { key: browserPerformanceRuntimeKey, property: String(property) });
741
+ },
742
+ async namespace(selector) {
743
+ return locator(selector).evaluate((element) => element.namespaceURI ?? "");
744
+ },
745
+ async count(selector) { return locator(selector).count(); },
746
+ async visible(selector) { return locator(selector).isVisible(); },
747
+ async waitFor(selector, state = "visible") {
748
+ const value = String(state);
749
+ if (value !== "visible" && value !== "hidden" && value !== "attached" && value !== "detached") {
750
+ throw new Error("browser.waitFor state must be visible, hidden, attached, or detached");
751
+ }
752
+ await locator(selector).waitFor({ state: value });
753
+ return null;
754
+ },
755
+ async waitForText(selector, text) {
756
+ await locator(selector).filter({ hasText: String(text) }).waitFor({ state: "visible" });
757
+ return null;
758
+ },
759
+ async currentPath() {
760
+ const url = new URL(page.url());
761
+ const path = base === "/" ? url.pathname : `/${url.pathname.slice(base.length)}`;
762
+ return `${path || "/"}${url.search}${url.hash}`;
763
+ },
764
+ async viewport(width, height) {
765
+ const next = { width: Number(width), height: Number(height) };
766
+ if (!Number.isInteger(next.width) || next.width < 1 || !Number.isInteger(next.height) || next.height < 1) {
767
+ throw new Error("browser.viewport requires positive integer dimensions");
768
+ }
769
+ await page.setViewportSize(next);
770
+ return null;
771
+ },
772
+ async timings() {
773
+ const value = await page.evaluate((key) => {
774
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(key))?.value;
775
+ if (!runtime || typeof runtime.timings !== "function")
776
+ throw new Error("Browser performance test runtime is unavailable");
777
+ return runtime.timings();
778
+ }, browserPerformanceRuntimeKey);
779
+ return navigationTiming(value);
780
+ },
781
+ async animation(selector) {
782
+ return locator(selector).evaluate(async (element) => {
783
+ const animations = element.getAnimations();
784
+ const first = animations[0];
785
+ if (!first)
786
+ return { count: animations.length, name: "", rotating: false };
787
+ const beforeTime = Number(first.currentTime ?? 0);
788
+ const before = getComputedStyle(element).rotate;
789
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
790
+ const afterTime = Number(first.currentTime ?? 0);
791
+ const after = getComputedStyle(element).rotate;
792
+ return {
793
+ count: animations.length,
794
+ name: typeof first.animationName === "string" ? first.animationName : "",
795
+ rotating: afterTime > beforeTime && after !== before,
796
+ };
797
+ });
798
+ },
799
+ async measureClick(selector) {
800
+ const target = locator(selector);
801
+ return measureBrowserInteraction(page, target, "click", async () => { await target.click(); });
802
+ },
803
+ async measureFill(selector, value) {
804
+ const target = locator(selector);
805
+ const text = String(value);
806
+ return measureBrowserInteraction(page, target, "beforeinput-or-input", async () => { await target.fill(text); });
807
+ },
808
+ async measurePress(selector, key) {
809
+ const target = locator(selector);
810
+ const value = String(key);
811
+ return measureBrowserInteraction(page, target, "beforeinput-or-input", async () => { await target.press(value); });
812
+ },
813
+ async storageGet(area, key) {
814
+ const input = { area: storageArea(area), key: String(key) };
815
+ return page.evaluate(({ area: name, key: itemKey }) => {
816
+ const target = name === "local" ? globalThis.localStorage : globalThis.sessionStorage;
817
+ return target.getItem(itemKey);
818
+ }, input);
819
+ },
820
+ async storageSet(area, key, value) {
821
+ const input = { area: storageArea(area), key: String(key), value: String(value) };
822
+ await page.evaluate(({ area: name, key: itemKey, value: itemValue }) => {
823
+ const target = name === "local" ? globalThis.localStorage : globalThis.sessionStorage;
824
+ target.setItem(itemKey, itemValue);
825
+ }, input);
826
+ return null;
827
+ },
828
+ async storageRemove(area, key) {
829
+ const input = { area: storageArea(area), key: String(key) };
830
+ await page.evaluate(({ area: name, key: itemKey }) => {
831
+ const target = name === "local" ? globalThis.localStorage : globalThis.sessionStorage;
832
+ target.removeItem(itemKey);
833
+ }, input);
834
+ return null;
835
+ },
836
+ async storageClear(area) {
837
+ const name = storageArea(area);
838
+ await page.evaluate((storageName) => {
839
+ const target = storageName === "local" ? globalThis.localStorage : globalThis.sessionStorage;
840
+ target.clear();
841
+ }, name);
842
+ return null;
843
+ },
844
+ async networkRespond(path, body, status, contentType, delayMs) {
845
+ const pathname = String(path);
846
+ const responseBody = String(body);
847
+ const responseStatus = Number(status);
848
+ const responseType = String(contentType);
849
+ const delay = Number(delayMs);
850
+ if (!pathname.startsWith("/") || pathname.includes("\0"))
851
+ throw new Error("network.respond path must be application-relative and start with '/'");
852
+ if (responseBody.length > 16 * 1024 * 1024)
853
+ throw new Error("network.respond body cannot exceed 16 MiB");
854
+ if (!Number.isInteger(responseStatus) || responseStatus < 100 || responseStatus > 599)
855
+ throw new Error("network.respond status must be an HTTP status integer");
856
+ if (!responseType || responseType.length > 1024 || /[\r\n]/u.test(responseType))
857
+ throw new Error("network.respond contentType must be bounded single-line text");
858
+ if (!Number.isInteger(delay) || delay < 0 || delay > 30000)
859
+ throw new Error("network.respond delayMs must be an integer from 0 through 30000");
860
+ const target = new URL(base === "/" ? pathname : `${base.slice(0, -1)}${pathname}`, origin).href;
861
+ if (mockedRoutes.has(target))
862
+ await page.unroute(target);
863
+ mockedRoutes.add(target);
864
+ await page.route(target, async (route) => {
865
+ if (delay > 0)
866
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, delay));
867
+ await route.fulfill({ status: responseStatus, contentType: responseType, body: responseBody });
868
+ });
869
+ return null;
870
+ },
871
+ async networkClear() {
872
+ for (const target of mockedRoutes)
873
+ await page.unroute(target);
874
+ mockedRoutes.clear();
875
+ return null;
876
+ },
877
+ async frameworkInvoke(capability, operation, args, timeout) {
878
+ const input = { capability: String(capability), operation: String(operation), args, timeout: Number(timeout) };
879
+ if (!input.capability || input.capability.length > 128 || !input.operation || input.operation.length > 128 || !Array.isArray(input.args)) {
880
+ throw new TypeError("Framework test invoke requires bounded capability, operation, and argument values");
881
+ }
882
+ if (!Number.isSafeInteger(input.timeout) || input.timeout < 0 || input.timeout > 600000) {
883
+ throw new RangeError("Framework test invoke timeout is outside its supported bounds");
884
+ }
885
+ if (frameworkController !== undefined) {
886
+ const result = await frameworkController.invoke(input.capability, input.operation, input.args, input.timeout);
887
+ if (typeof result !== "object" || result === null || typeof result.handled !== "boolean") {
888
+ throw new TypeError("Framework browser-test controller returned an invalid result");
889
+ }
890
+ if (result.handled)
891
+ return result.value;
892
+ }
893
+ return page.evaluate(async (request) => {
894
+ const bridge = Object.getOwnPropertyDescriptor(globalThis, Symbol.for("velar.desktop.bridge.v1"))?.value;
895
+ if (!bridge || typeof bridge.invoke !== "function")
896
+ throw new Error("Desktop application test bridge is unavailable");
897
+ return bridge.invoke(request.capability, request.operation, request.args, request.timeout);
898
+ }, input);
899
+ },
900
+ });
901
+ globalThis[runtimeKey] = runtime;
902
+ }
903
+ function removeBrowserRuntime(runtimeKey) {
904
+ delete globalThis[runtimeKey];
905
+ }
906
+ async function measureBrowserInteraction(page, target, eventName, action) {
907
+ const measurement = await target.evaluate((element, input) => {
908
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(input.key))?.value;
909
+ if (!runtime || typeof runtime.prepare !== "function")
910
+ throw new Error("Browser performance test runtime is unavailable");
911
+ return runtime.prepare(element, input.eventName);
912
+ }, { key: browserPerformanceRuntimeKey, eventName });
913
+ if (!Number.isSafeInteger(measurement) || measurement < 1)
914
+ throw new TypeError("Browser performance runtime returned an invalid measurement identity");
915
+ try {
916
+ await action();
917
+ const value = await page.evaluate(async (input) => {
918
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(input.key))?.value;
919
+ if (!runtime || typeof runtime.finish !== "function")
920
+ throw new Error("Browser performance test runtime is unavailable");
921
+ return runtime.finish(input.identity);
922
+ }, { key: browserPerformanceRuntimeKey, identity: measurement });
923
+ return interactionTiming(value);
924
+ }
925
+ catch (error) {
926
+ await page.evaluate((input) => {
927
+ const runtime = Object.getOwnPropertyDescriptor(globalThis, Symbol.for(input.key))?.value;
928
+ if (runtime && typeof runtime.cancel === "function")
929
+ runtime.cancel(input.identity);
930
+ }, { key: browserPerformanceRuntimeKey, identity: measurement }).catch(() => { });
931
+ throw error;
932
+ }
933
+ }
934
+ function navigationTiming(value) {
935
+ const record = timingRecord(value, ["firstContentfulPaintMs", "domContentLoadedMs", "loadMs"]);
936
+ const firstContentfulPaintMs = record.firstContentfulPaintMs;
937
+ if (firstContentfulPaintMs !== null)
938
+ boundedTiming(firstContentfulPaintMs, "firstContentfulPaintMs");
939
+ return Object.freeze({
940
+ firstContentfulPaintMs: firstContentfulPaintMs,
941
+ domContentLoadedMs: boundedTiming(record.domContentLoadedMs, "domContentLoadedMs"),
942
+ loadMs: boundedTiming(record.loadMs, "loadMs"),
943
+ });
944
+ }
945
+ function interactionTiming(value) {
946
+ const record = timingRecord(value, ["inputDelayMs", "processingDurationMs", "nextFrameMs"]);
947
+ const inputDelayMs = boundedTiming(record.inputDelayMs, "inputDelayMs");
948
+ const processingDurationMs = boundedTiming(record.processingDurationMs, "processingDurationMs");
949
+ const nextFrameMs = boundedTiming(record.nextFrameMs, "nextFrameMs");
950
+ if (nextFrameMs + Number.EPSILON < inputDelayMs + processingDurationMs) {
951
+ throw new TypeError("Browser interaction timing ends before processing completes");
952
+ }
953
+ return Object.freeze({ inputDelayMs, processingDurationMs, nextFrameMs });
954
+ }
955
+ function timingRecord(value, fields) {
956
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
957
+ throw new TypeError("Browser performance runtime returned an invalid record");
958
+ }
959
+ const names = Object.keys(value);
960
+ if (names.length !== fields.length || names.some((name) => !fields.includes(name))) {
961
+ throw new TypeError("Browser performance runtime returned unexpected fields");
962
+ }
963
+ for (const name of fields) {
964
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
965
+ if (!descriptor?.enumerable || !("value" in descriptor))
966
+ throw new TypeError("Browser performance runtime returned an accessor field");
967
+ }
968
+ return value;
969
+ }
970
+ function boundedTiming(value, name) {
971
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 600_000) {
972
+ throw new RangeError(`Browser performance ${name} is outside its supported bound`);
973
+ }
974
+ return value;
975
+ }
976
+ async function buildProject(config, outputDirectory, executableOverride) {
977
+ const executable = resolve(executableOverride ?? process.argv[1]);
978
+ const child = spawn(process.execPath, [executable, "build", config.root, "--out-dir", outputDirectory], {
979
+ cwd: config.root,
980
+ stdio: ["ignore", "pipe", "pipe"],
981
+ });
982
+ let output = "";
983
+ child.stdout.on("data", (chunk) => { output += chunk.toString("utf8"); });
984
+ child.stderr.on("data", (chunk) => { output += chunk.toString("utf8"); });
985
+ const code = await new Promise((resolvePromise, reject) => {
986
+ child.once("error", reject);
987
+ child.once("exit", resolvePromise);
988
+ });
989
+ return { ok: code === 0, output };
990
+ }
991
+ async function prepareStandardModules(root, config) {
992
+ const packageRoot = join(root, "node_modules", "velar");
993
+ await mkdir(packageRoot, { recursive: true });
994
+ const exports = {};
995
+ for (const [source, fallback] of standardModuleSources(config.compilerExtensions)) {
996
+ const name = source.slice("velar/".length);
997
+ exports[`./${name}`] = `./${name}.js`;
998
+ await writeFile(join(packageRoot, `${name}.js`), standardModuleSource(source, config.extensionConfig, config.compilerExtensions) ?? fallback, "utf8");
999
+ }
1000
+ await writeFile(join(packageRoot, "package.json"), JSON.stringify({ name: "velar", private: true, type: "module", exports }), "utf8");
1001
+ }
1002
+ async function discoverBrowserTestFiles(root, excluded, sourceSuffix) {
1003
+ const output = [];
1004
+ const visit = async (directory) => {
1005
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
1006
+ const path = join(directory, entry.name);
1007
+ if (entry.isDirectory()) {
1008
+ if (entry.name === "node_modules" || entry.name === ".git" || excluded.has(path))
1009
+ continue;
1010
+ await visit(path);
1011
+ }
1012
+ else if (entry.isFile() && entry.name.endsWith(sourceSuffix))
1013
+ output.push(path);
1014
+ }
1015
+ };
1016
+ await visit(root);
1017
+ return output.sort();
1018
+ }
1019
+ function stackOf(error) {
1020
+ return hostErrorStack(error);
1021
+ }
1022
+ //# sourceMappingURL=browser-test-runner.js.map