@jterrazz/test 16.1.1 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +2 -2980
  3. package/dist/browser/services.js +2 -0
  4. package/dist/browser/stubs.js +2 -0
  5. package/dist/checker.d.ts +6 -1
  6. package/dist/checker.js +4534 -2628
  7. package/dist/config.d.ts +40 -0
  8. package/dist/config.js +309 -0
  9. package/dist/in-place.global-setup.d.ts +11 -0
  10. package/dist/in-place.global-setup.js +21 -0
  11. package/dist/index.d.ts +1653 -3004
  12. package/dist/index.js +5226 -3845
  13. package/dist/mobile-session.global-setup.d.ts +14 -0
  14. package/dist/mobile-session.global-setup.js +31 -0
  15. package/dist/oxlint.cjs +6069 -5634
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6127 -5693
  19. package/dist/route-coverage.global-setup.d.ts +42 -0
  20. package/dist/route-coverage.global-setup.js +2 -0
  21. package/dist/services.d.ts +3 -0
  22. package/dist/services.global-setup.d.ts +43 -0
  23. package/dist/services.global-setup.js +2 -0
  24. package/dist/services.js +597 -0
  25. package/dist/shared-appium.adapter.js +428 -0
  26. package/dist/shared-artifacts.js +32 -0
  27. package/dist/shared-chapter.js +7 -0
  28. package/dist/shared-container.port.d.ts +31 -0
  29. package/dist/shared-derived-projects.js +58 -0
  30. package/dist/shared-device.port.d.ts +189 -0
  31. package/dist/shared-exchange.js +447 -0
  32. package/dist/shared-in-place.js +172 -0
  33. package/dist/shared-index.browser.js +3191 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-literate-plugin.js +183 -0
  40. package/dist/shared-manifest.js +1321 -0
  41. package/dist/shared-pause.js +11 -0
  42. package/dist/shared-peer.js +88 -0
  43. package/dist/shared-playwright.adapter.js +330 -0
  44. package/dist/shared-process.adapter.js +74 -0
  45. package/dist/shared-refusal.js +35 -0
  46. package/dist/shared-resolve.js +24 -0
  47. package/dist/shared-route-coverage.global-setup.js +187 -0
  48. package/dist/shared-scenario-failure.js +198 -0
  49. package/dist/shared-serve.js +365 -0
  50. package/dist/shared-service-images.js +10 -0
  51. package/dist/shared-services.global-setup.js +179 -0
  52. package/dist/shared-session-ledger.js +301 -0
  53. package/dist/shared-setup-entry.js +12 -0
  54. package/dist/shared-source-scan.js +133 -0
  55. package/dist/shared-spec-document.js +365 -0
  56. package/dist/shared-specifications.js +26 -0
  57. package/dist/shared-stream-body.js +130 -0
  58. package/dist/shared-stub-server.js +190 -0
  59. package/dist/shared-temp-root.global-setup.js +80 -0
  60. package/dist/shared-test-baseline.js +26 -0
  61. package/dist/shared-test-section.js +58 -0
  62. package/dist/shared-testcontainers.adapter.js +203 -0
  63. package/dist/shared-tokens.js +55 -0
  64. package/dist/shared-yaml-document.js +119 -0
  65. package/dist/stubs.d.ts +2 -0
  66. package/dist/stubs.js +657 -0
  67. package/dist/temp-root.global-setup.d.ts +7 -0
  68. package/dist/temp-root.global-setup.js +2 -0
  69. package/dist/vitest.d.ts +97 -163
  70. package/dist/vitest.js +373 -525
  71. package/docs/README.md +82 -36
  72. package/docs/concepts/architecture.md +118 -0
  73. package/docs/concepts/conventions.md +333 -0
  74. package/docs/guides/api-specs.md +374 -0
  75. package/docs/guides/cli-specs.md +564 -0
  76. package/docs/guides/component-specs.md +252 -0
  77. package/docs/guides/develop.md +94 -0
  78. package/docs/guides/integration-specs.md +181 -0
  79. package/docs/guides/job-specs.md +180 -0
  80. package/docs/guides/lint-specs.md +174 -0
  81. package/docs/guides/mobile-specs.md +319 -0
  82. package/docs/guides/module-tests.md +149 -0
  83. package/docs/guides/release.md +43 -0
  84. package/docs/guides/spec-documents.md +189 -0
  85. package/docs/guides/website-specs.md +413 -0
  86. package/docs/reference/assertions.md +345 -0
  87. package/docs/reference/glossary.md +20 -0
  88. package/docs/reference/linting.md +483 -0
  89. package/docs/reference/services.md +236 -0
  90. package/docs/reference/stubs.md +498 -0
  91. package/docs/reference/time-and-doubles.md +137 -0
  92. package/docs/reference/tokens.md +258 -0
  93. package/docs/reference/visitor.md +221 -0
  94. package/docs/start/getting-started.md +187 -0
  95. package/docs/start/set-up-a-project.md +214 -0
  96. package/package.json +50 -30
  97. package/schema/spec.schema.json +43 -17
  98. package/dist/appium.adapter.js +0 -489
  99. package/dist/artifacts.js +0 -32
  100. package/dist/fixtures.js +0 -110
  101. package/dist/literate.js +0 -1747
  102. package/dist/match.js +0 -804
  103. package/dist/playwright.adapter.js +0 -438
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -282
  107. package/docs/01-architecture.md +0 -95
  108. package/docs/02-developing.md +0 -351
  109. package/docs/03-testing.md +0 -495
  110. package/docs/04-operating.md +0 -101
  111. package/docs/05-module-tests.md +0 -152
  112. package/docs/06-integration.md +0 -180
  113. package/docs/07-component.md +0 -246
  114. package/docs/08-website.md +0 -404
  115. package/docs/09-mobile.md +0 -270
  116. package/docs/10-api.md +0 -264
  117. package/docs/11-jobs.md +0 -157
  118. package/docs/12-cli.md +0 -677
  119. package/docs/13-elements.md +0 -248
  120. package/docs/14-assertions.md +0 -314
  121. package/docs/15-tokens.md +0 -253
  122. package/docs/16-contracts.md +0 -438
  123. package/docs/17-services.md +0 -188
  124. package/docs/18-conventions.md +0 -332
  125. package/docs/19-linting.md +0 -512
  126. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  127. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  128. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  129. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  130. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  131. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  132. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  133. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  134. package/docs/decisions/_template.md +0 -22
  135. package/docs/reference/classes/Matcher.md +0 -120
  136. package/docs/reference/classes/ProcessHandle.md +0 -368
  137. package/docs/reference/functions/defineContract.md +0 -39
  138. package/docs/reference/functions/defineContracts.md +0 -36
  139. package/docs/reference/functions/disabled.md +0 -28
  140. package/docs/reference/functions/enabled.md +0 -20
  141. package/docs/reference/functions/focused.md +0 -26
  142. package/docs/reference/functions/mockOf.md +0 -79
  143. package/docs/reference/functions/postgres.md +0 -31
  144. package/docs/reference/functions/process.md +0 -33
  145. package/docs/reference/functions/redis.md +0 -28
  146. package/docs/reference/functions/required.md +0 -41
  147. package/docs/reference/functions/selected.md +0 -26
  148. package/docs/reference/functions/sqlite.md +0 -33
  149. package/docs/reference/functions/testId.md +0 -20
  150. package/docs/reference/functions/text.md +0 -40
  151. package/docs/reference/functions/valued.md +0 -28
  152. package/docs/reference/functions/waitUntil.md +0 -46
  153. package/docs/reference/functions/within.md +0 -29
  154. package/docs/reference/index.md +0 -196
  155. package/docs/reference/interfaces/BaseResult.md +0 -87
  156. package/docs/reference/interfaces/CallResult.md +0 -160
  157. package/docs/reference/interfaces/CaptureScope.md +0 -82
  158. package/docs/reference/interfaces/CliResult.md +0 -253
  159. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  160. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  161. package/docs/reference/interfaces/FetchResult.md +0 -180
  162. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  163. package/docs/reference/interfaces/HttpResult.md +0 -126
  164. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  165. package/docs/reference/interfaces/PageResult.md +0 -356
  166. package/docs/reference/interfaces/RenderResult.md +0 -72
  167. package/docs/reference/interfaces/RenderedText.md +0 -110
  168. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  169. package/docs/reference/interfaces/ScreenResult.md +0 -128
  170. package/docs/reference/interfaces/TableAccessor.md +0 -26
  171. package/docs/reference/interfaces/TextAccessor.md +0 -96
  172. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  173. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  174. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  175. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  176. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  177. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  178. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  179. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  180. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  181. package/docs/reference/type-aliases/CliEnv.md +0 -11
  182. package/docs/reference/type-aliases/CliHandle.md +0 -80
  183. package/docs/reference/type-aliases/CliInput.md +0 -36
  184. package/docs/reference/type-aliases/CliOutput.md +0 -46
  185. package/docs/reference/type-aliases/CliPort.md +0 -58
  186. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  187. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  188. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  189. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  190. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  191. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  192. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  193. package/docs/reference/type-aliases/Contract.md +0 -68
  194. package/docs/reference/type-aliases/ContractInput.md +0 -13
  195. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  196. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  197. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  198. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  199. package/docs/reference/type-aliases/Contracts.md +0 -48
  200. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  201. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  202. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  203. package/docs/reference/type-aliases/DevicePort.md +0 -51
  204. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  205. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  206. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  207. package/docs/reference/type-aliases/DomMount.md +0 -21
  208. package/docs/reference/type-aliases/ElementKind.md +0 -27
  209. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  210. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  211. package/docs/reference/type-aliases/ElementRef.md +0 -128
  212. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  213. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  214. package/docs/reference/type-aliases/HonoApp.md +0 -31
  215. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  216. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  217. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  218. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  219. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  220. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  221. package/docs/reference/type-aliases/Intercept.md +0 -19
  222. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  223. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  224. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  225. package/docs/reference/type-aliases/JobHandle.md +0 -34
  226. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  227. package/docs/reference/type-aliases/JobsResult.md +0 -16
  228. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  229. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  230. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  231. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  232. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  233. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  234. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  235. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  236. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  237. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  238. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  239. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  240. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  241. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  242. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  243. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  244. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  245. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  246. package/docs/reference/type-aliases/MockPort.md +0 -10
  247. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  248. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  249. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  250. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  251. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  252. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  253. package/docs/reference/type-aliases/ServerPort.md +0 -36
  254. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  255. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  256. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  257. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  258. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  259. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  260. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  261. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  262. package/docs/reference/type-aliases/SpecKind.md +0 -8
  263. package/docs/reference/type-aliases/SpecRun.md +0 -108
  264. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  265. package/docs/reference/type-aliases/SpecStream.md +0 -32
  266. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  267. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  268. package/docs/reference/type-aliases/SseEvent.md +0 -58
  269. package/docs/reference/type-aliases/StreamBody.md +0 -61
  270. package/docs/reference/type-aliases/TextFilter.md +0 -22
  271. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  272. package/docs/reference/type-aliases/Visitor.md +0 -215
  273. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  274. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  275. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  276. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  277. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  278. package/docs/reference/variables/anthropic.md +0 -19
  279. package/docs/reference/variables/banner.md +0 -10
  280. package/docs/reference/variables/button.md +0 -10
  281. package/docs/reference/variables/clock.md +0 -16
  282. package/docs/reference/variables/complementary.md +0 -10
  283. package/docs/reference/variables/component.md +0 -10
  284. package/docs/reference/variables/content.md +0 -10
  285. package/docs/reference/variables/contentinfo.md +0 -10
  286. package/docs/reference/variables/dialog.md +0 -10
  287. package/docs/reference/variables/field.md +0 -10
  288. package/docs/reference/variables/form.md +0 -10
  289. package/docs/reference/variables/heading.md +0 -10
  290. package/docs/reference/variables/http.md +0 -40
  291. package/docs/reference/variables/intercept.md +0 -11
  292. package/docs/reference/variables/link.md +0 -10
  293. package/docs/reference/variables/listitem.md +0 -10
  294. package/docs/reference/variables/main.md +0 -10
  295. package/docs/reference/variables/match.md +0 -8
  296. package/docs/reference/variables/navigation.md +0 -10
  297. package/docs/reference/variables/openai.md +0 -21
  298. package/docs/reference/variables/option.md +0 -17
  299. package/docs/reference/variables/region.md +0 -10
  300. package/docs/reference/variables/row.md +0 -10
  301. package/docs/reference/variables/search.md +0 -10
  302. package/docs/reference/variables/specification.md +0 -8
  303. package/docs/reference/variables/status.md +0 -10
  304. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,428 @@
1
+ import { n as refusal } from "./shared-refusal.js";
2
+ import { t as pause } from "./shared-pause.js";
3
+ import { u as evidenceDirectory } from "./shared-artifacts.js";
4
+ import { n as SERVER_CALL_TIMEOUT_MS } from "./shared-session-ledger.js";
5
+ import { c as scopeChain } from "./shared-landmarks.js";
6
+ import { a as ambiguityError, c as asWrittenUnscoped, i as shortened, s as asWritten, t as scenarioFailure } from "./shared-scenario-failure.js";
7
+ import { mkdtempSync, writeFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ //#region src/specifications/mobile/session/driver-session.ts
10
+ const IDLE_SESSION_BUDGET_S = 300;
11
+ const IOS_PREFIX = /^ios\s+/iu;
12
+ function platformVersion(os) {
13
+ return os.replace(IOS_PREFIX, "");
14
+ }
15
+ async function loadWebdriverio() {
16
+ try {
17
+ const { attach: attachFn, remote: remoteFn } = await import("webdriverio");
18
+ return {
19
+ attach: attachFn,
20
+ remote: remoteFn
21
+ };
22
+ } catch {
23
+ throw new Error(refusal("seam-is-an-optional-peer", {
24
+ fix: "npm install -D appium webdriverio && npx appium driver install xcuitest",
25
+ found: ".open() requires `webdriverio`, and it is not installed"
26
+ }));
27
+ }
28
+ }
29
+ function headless() {
30
+ return process.env.TEST_MOBILE_HEADED !== "1";
31
+ }
32
+ function capabilitiesOf(request) {
33
+ const { name, os, udid } = request.device;
34
+ return {
35
+ "appium:automationName": "XCUITest",
36
+ "appium:bundleId": request.bundleId,
37
+ "appium:deviceName": name,
38
+ "appium:isHeadless": headless(),
39
+ "appium:newCommandTimeout": IDLE_SESSION_BUDGET_S,
40
+ "appium:noReset": true,
41
+ ...os === void 0 ? {} : { "appium:platformVersion": platformVersion(os) },
42
+ ...udid === void 0 ? {} : { "appium:udid": udid },
43
+ "appium:wdaLaunchTimeout": request.launchTimeoutMs,
44
+ platformName: "iOS"
45
+ };
46
+ }
47
+ async function succeeded(url, method) {
48
+ try {
49
+ const response = await fetch(url, {
50
+ method,
51
+ signal: AbortSignal.timeout(SERVER_CALL_TIMEOUT_MS)
52
+ });
53
+ await response.body?.cancel();
54
+ return response.ok;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ async function firstHeld(serverUrl, ledger, recorded) {
60
+ const [candidate, ...rest] = recorded;
61
+ if (candidate === void 0) return;
62
+ const session = `${serverUrl}/session/${candidate.sessionId}`;
63
+ if (await succeeded(`${session}/timeouts`, "GET")) return candidate;
64
+ await succeeded(session, "DELETE");
65
+ ledger.forget(candidate.sessionId);
66
+ return await firstHeld(serverUrl, ledger, rest);
67
+ }
68
+ /** Attached to the session the run recorded for this server and capabilities, or created and recorded; `shared` sessions are the run's to end. */
69
+ async function openSession(request) {
70
+ const { ledger, serverUrl } = request;
71
+ const url = new URL(serverUrl);
72
+ const server = {
73
+ hostname: url.hostname,
74
+ logLevel: "error",
75
+ port: Number(url.port)
76
+ };
77
+ const capabilities = capabilitiesOf(request);
78
+ const webdriverio = request.webdriverio ?? await loadWebdriverio();
79
+ const held = ledger === void 0 ? void 0 : await firstHeld(serverUrl, ledger, ledger.lookup(serverUrl, capabilities));
80
+ if (held !== void 0) return {
81
+ driver: await webdriverio.attach({
82
+ ...server,
83
+ capabilities: held.capabilities,
84
+ sessionId: held.sessionId
85
+ }),
86
+ shared: true
87
+ };
88
+ const driver = await webdriverio.remote({
89
+ ...server,
90
+ capabilities
91
+ });
92
+ ledger?.record(capabilities, {
93
+ capabilities: { ...driver.capabilities },
94
+ serverUrl,
95
+ sessionId: driver.sessionId
96
+ });
97
+ return {
98
+ driver,
99
+ shared: ledger !== void 0
100
+ };
101
+ }
102
+ //#endregion
103
+ //#region src/specifications/mobile/device/ambiguous.ts
104
+ function formatMatch(match, index) {
105
+ const label = match.label === void 0 ? "" : ` ${JSON.stringify(match.label)}`;
106
+ const value = match.value === void 0 ? "" : ` value=${JSON.stringify(match.value)}`;
107
+ const identifier = match.identifier === void 0 ? "" : ` [testId: ${match.identifier}]`;
108
+ return ` ${index + 1}. ${match.type}${label}${value}${identifier}`;
109
+ }
110
+ function formatFixes(target, matches) {
111
+ const fixes = [];
112
+ if (!target.scope) fixes.push(`scope it within(testId('...'), ${asWrittenUnscoped(target)}): a screen has no landmarks, and any target works as the scope`);
113
+ if (target.exact === false) {
114
+ const remaining = matches.filter((match) => match.label === target.name).length;
115
+ if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${asWrittenUnscoped({
116
+ ...target,
117
+ exact: true
118
+ })} [leaves ${remaining} of ${matches.length}]`);
119
+ }
120
+ const identifiers = [...new Set(matches.map((match) => match.identifier).filter((identifier) => identifier !== void 0))];
121
+ if (identifiers.length > 1 && target.kind !== "testId") fixes.push(`test id testId(${JSON.stringify(identifiers[0])}) [also here: ${identifiers.slice(1).join(", ")}]`);
122
+ fixes.push("other element a button() or field() may name one thing where this does not");
123
+ return fixes;
124
+ }
125
+ function ambiguousScreenElementError(options) {
126
+ const { target, matches } = options;
127
+ return ambiguityError({
128
+ target,
129
+ fixes: formatFixes(target, matches),
130
+ lines: matches.map((match, index) => formatMatch(match, index)),
131
+ where: "the screen"
132
+ });
133
+ }
134
+ //#endregion
135
+ //#region src/specifications/mobile/device/projection.ts
136
+ const TAG_PATTERN = /<(?<openTag>[A-Za-z][\w.]*)(?<rawAttributes>(?:\s+[\w:.-]+="[^"]*")*)\s*(?<selfClosing>\/?)>|<\/[\w.]*\s*>/gu;
137
+ const ATTRIBUTE_PATTERN = /(?<key>[\w:.-]+)="(?<value>[^"]*)"/gu;
138
+ const NAMED_ENTITIES = {
139
+ "&amp;": "&",
140
+ "&apos;": "'",
141
+ "&gt;": ">",
142
+ "&lt;": "<",
143
+ "&quot;": "\""
144
+ };
145
+ function decodeEntities(value) {
146
+ return value.replaceAll(/&(?:#x(?<hex>[\dA-Fa-f]+)|#(?<decimal>\d+)|(?<named>amp|apos|gt|lt|quot));/gu, (entity, hex, decimal) => {
147
+ if (hex !== void 0) return String.fromCodePoint(Number.parseInt(hex, 16));
148
+ if (decimal !== void 0) return String.fromCodePoint(Number(decimal));
149
+ return NAMED_ENTITIES[entity] ?? entity;
150
+ });
151
+ }
152
+ function attributesOf(raw) {
153
+ return Object.fromEntries([...raw.matchAll(ATTRIBUTE_PATTERN)].map(([, key, value]) => [String(key), decodeEntities(String(value))]));
154
+ }
155
+ function parsePageSource(xml) {
156
+ const stack = [];
157
+ let root = null;
158
+ for (const match of xml.matchAll(TAG_PATTERN)) {
159
+ const { openTag, rawAttributes, selfClosing } = match.groups ?? {};
160
+ if (openTag === void 0) {
161
+ stack.pop();
162
+ continue;
163
+ }
164
+ const node = {
165
+ attributes: attributesOf(String(rawAttributes)),
166
+ children: [],
167
+ tag: openTag
168
+ };
169
+ const parent = stack.at(-1);
170
+ if (parent === void 0) root = node;
171
+ else parent.children.push(node);
172
+ if (selfClosing !== "/") stack.push(node);
173
+ }
174
+ if (!root) throw new Error(refusal({
175
+ fix: "restart the Appium server, or upgrade Appium and its XCUITest driver",
176
+ found: "the page source Appium answered holds no XML element",
177
+ read: "docs/guides/mobile-specs.md#requirements-what-the-machine-must-have",
178
+ why: "A screen is read from the XML page source Appium answers."
179
+ }));
180
+ return root;
181
+ }
182
+ const present = (attribute) => attribute === "" ? void 0 : attribute;
183
+ function substance(node) {
184
+ const label = present(node.attributes.label);
185
+ const value = present(node.attributes.value);
186
+ const identifier = present(node.attributes.name);
187
+ return {
188
+ type: node.tag.replace(/^XCUIElementType/u, ""),
189
+ ...label === void 0 ? {} : { label },
190
+ ...value === void 0 || value === label ? {} : { value },
191
+ ...identifier === void 0 || identifier === label ? {} : { identifier }
192
+ };
193
+ }
194
+ function hasSubstance(node) {
195
+ return node.label !== void 0 || node.value !== void 0 || node.identifier !== void 0;
196
+ }
197
+ function repeatsParent(child, parent) {
198
+ return child.children === void 0 && child.type === parent.type && child.label === parent.label && child.value === parent.value && child.identifier === parent.identifier;
199
+ }
200
+ function projectNode(node) {
201
+ const own = substance(node);
202
+ const children = node.children.flatMap((child) => projectNode(child)).filter((child) => !repeatsParent(child, own));
203
+ if (!hasSubstance(own)) return children;
204
+ return [{
205
+ ...own,
206
+ ...children.length > 0 ? { children } : {}
207
+ }];
208
+ }
209
+ function collectTexts(node, texts) {
210
+ if (node.attributes.visible === "true") {
211
+ const text = present(node.attributes.label) ?? present(node.attributes.value);
212
+ if (text !== void 0 && texts.at(-1) !== text) texts.push(text);
213
+ }
214
+ for (const child of node.children) collectTexts(child, texts);
215
+ }
216
+ function projectScreen(xml) {
217
+ let root = parsePageSource(xml);
218
+ const [onlyChild, ...others] = root.children;
219
+ if (root.tag === "AppiumAUT" && onlyChild !== void 0 && others.length === 0) root = onlyChild;
220
+ const texts = [];
221
+ for (const child of root.children) collectTexts(child, texts);
222
+ const own = substance(root);
223
+ const children = root.children.flatMap((child) => projectNode(child)).filter((child) => !repeatsParent(child, own));
224
+ return {
225
+ texts,
226
+ tree: {
227
+ ...own,
228
+ ...children.length > 0 ? { children } : {}
229
+ }
230
+ };
231
+ }
232
+ //#endregion
233
+ //#region src/specifications/mobile/device/xcui-query.ts
234
+ function escapePredicate(value) {
235
+ return value.replaceAll("\\", String.raw`\\`).replaceAll("'", String.raw`\'`);
236
+ }
237
+ const PREDICATES = {
238
+ button: ({ contains, visible }) => `type == 'XCUIElementTypeButton' AND ${contains("label")} AND ${visible}`,
239
+ field: ({ contains, visible }) => `type IN {'XCUIElementTypeTextField','XCUIElementTypeSecureTextField'} AND (${contains("label")} OR ${contains("value")}) AND ${visible}`,
240
+ testId: ({ name, visible }) => `name == '${name}' AND ${visible}`,
241
+ text: ({ contains, visible }) => `(${contains("label")} OR ${contains("value")}) AND ${visible}`
242
+ };
243
+ function isMobileKind(kind) {
244
+ return Object.hasOwn(PREDICATES, kind);
245
+ }
246
+ /** Landmarks have no XCUITest analog: the boundary is refused, not approximated. */
247
+ function compilePredicate(target, options) {
248
+ if (!isMobileKind(target.kind)) throw new Error(refusal("no-landmark-on-mobile", {
249
+ fix: `scope with within(testId('...'), ...): any target works as the scope`,
250
+ found: `${asWritten(target)}, a landmark: an iOS screen has no '${target.kind}' region`
251
+ }));
252
+ const name = escapePredicate(target.name ?? "");
253
+ return PREDICATES[target.kind]({
254
+ contains: (attribute) => target.exact === false ? `${attribute} CONTAINS '${name}'` : `${attribute} == '${name}'`,
255
+ name,
256
+ visible: options?.anyVisibility === true ? "1 == 1" : "visible == 1"
257
+ });
258
+ }
259
+ /** `$$` answers a chainable typed as its array: the one await of it lives here. */
260
+ async function matching(scope, target, options) {
261
+ return await scope.$$(`-ios predicate string:${compilePredicate(target, options)}`);
262
+ }
263
+ async function captureMatch(element) {
264
+ const [type, label, value, identifier] = await Promise.all([
265
+ element.getAttribute("type"),
266
+ element.getAttribute("label"),
267
+ element.getAttribute("value"),
268
+ element.getAttribute("name")
269
+ ]);
270
+ const shortLabel = label === null || label === "" ? void 0 : shortened(label);
271
+ return {
272
+ type: (type ?? "Unknown").replace(/^XCUIElementType/u, ""),
273
+ ...shortLabel === void 0 ? {} : { label: shortLabel },
274
+ ...value === null || value === "" || value === label ? {} : { value },
275
+ ...identifier === null || identifier === "" || identifier === label ? {} : { identifier }
276
+ };
277
+ }
278
+ async function captureMatches(matches) {
279
+ return await Promise.all([...matches].slice(0, 10).map(async (match) => await captureMatch(match)));
280
+ }
281
+ /** invariant: actionability includes scroll-into-view: the next pass re-resolves the element as visible. */
282
+ async function scrollToOffscreen(driver, scope, level) {
283
+ const elementId = (await matching(scope, level, { anyVisibility: true }))[0]?.elementId;
284
+ if (elementId === void 0) return;
285
+ try {
286
+ await driver.executeScript("mobile: scrollToElement", [{ elementId }]);
287
+ } catch {}
288
+ }
289
+ const LABEL_WIDTH = 60;
290
+ const labelShortened = (label) => label.length > LABEL_WIDTH ? `${label.slice(0, 57)}...` : label;
291
+ /** The labels a page source shows, each at most 60 characters, for a timeout's evidence. */
292
+ function onScreenLabels(source) {
293
+ return [...new Set([...source.matchAll(/label="(?<label>[^"]{2,})"/gu)].map(([, label]) => labelShortened(String(label))))].slice(0, 15);
294
+ }
295
+ //#endregion
296
+ //#region src/specifications/mobile/device/appium.adapter.ts
297
+ const DEFAULT_ACTION_TIMEOUT_MS = 3e4;
298
+ const POLL_INTERVAL_MS = 500;
299
+ const DEFAULT_WDA_LAUNCH_TIMEOUT_MS = 24e4;
300
+ /** One driver session, opened lazily and reused: WebDriverAgent startup is the expensive part. Every `open()` relaunches the app. */
301
+ var AppiumAdapter = class {
302
+ actionTimeoutMs;
303
+ opened = null;
304
+ launchTimeoutMs;
305
+ ledger;
306
+ options;
307
+ /** Injected by the unit test; production loads webdriverio lazily. */
308
+ webdriverio;
309
+ constructor(options) {
310
+ this.options = {
311
+ device: options.device,
312
+ serverUrl: options.serverUrl
313
+ };
314
+ this.ledger = options.ledger;
315
+ this.webdriverio = options.webdriverio;
316
+ this.actionTimeoutMs = options.timeouts?.action ?? DEFAULT_ACTION_TIMEOUT_MS;
317
+ this.launchTimeoutMs = options.timeouts?.launch ?? DEFAULT_WDA_LAUNCH_TIMEOUT_MS;
318
+ }
319
+ async close() {
320
+ if (this.opened?.shared === false) await this.opened.driver.deleteSession();
321
+ this.opened = null;
322
+ }
323
+ async open(options) {
324
+ const driver = await this.session(options.bundleId);
325
+ try {
326
+ await driver.executeScript("mobile: terminateApp", [{ bundleId: options.bundleId }]);
327
+ } catch {}
328
+ await (options.deepLink === void 0 ? driver.executeScript("mobile: activateApp", [{ bundleId: options.bundleId }]) : driver.executeScript("mobile: deepLink", [{
329
+ bundleId: options.bundleId,
330
+ url: options.deepLink
331
+ }]));
332
+ if (options.scenario) try {
333
+ await options.scenario(this.createVisitor(driver));
334
+ } catch (error) {
335
+ const evidence = await this.captureEvidence(driver);
336
+ throw scenarioFailure(error, evidence, "open");
337
+ }
338
+ return projectScreen(await driver.getPageSource());
339
+ }
340
+ /** `see()` acts on nothing, so any visible match satisfies it: XCUITest trees duplicate labels across container and child. */
341
+ createVisitor(driver) {
342
+ return {
343
+ fill: async (target, value) => {
344
+ await (await this.resolveOne(driver, target, "fill")).setValue(value);
345
+ },
346
+ see: async (target) => {
347
+ await this.resolveOne(driver, target, "see");
348
+ },
349
+ tap: async (target) => {
350
+ await (await this.resolveOne(driver, target, "tap")).click();
351
+ }
352
+ };
353
+ }
354
+ /** Scopes stay strict in both modes; the chain resolves outside-in, so an ambiguous scope names itself. */
355
+ async resolveOne(driver, target, verb) {
356
+ const chain = scopeChain(target);
357
+ const anyTarget = verb === "see";
358
+ const deadline = Date.now() + this.actionTimeoutMs;
359
+ let tryScroll = true;
360
+ for (;;) {
361
+ const resolved = await this.resolveChain(driver, chain, anyTarget, tryScroll);
362
+ tryScroll = false;
363
+ if (resolved) return resolved;
364
+ if (Date.now() > deadline) throw await this.timeoutError(driver, target, verb);
365
+ await pause(POLL_INTERVAL_MS);
366
+ }
367
+ }
368
+ async resolveChain(driver, chain, anyTarget, tryScroll) {
369
+ let scope = driver;
370
+ let element = null;
371
+ for (const [index, level] of chain.entries()) {
372
+ const matches = await matching(scope, level);
373
+ const first = matches[0];
374
+ if (first === void 0) {
375
+ if (tryScroll) await scrollToOffscreen(driver, scope, level);
376
+ return null;
377
+ }
378
+ const isTarget = index === chain.length - 1;
379
+ if (await matches.length > 1 && (!anyTarget || !isTarget)) throw ambiguousScreenElementError({
380
+ target: level,
381
+ matches: await captureMatches(matches)
382
+ });
383
+ scope = first;
384
+ element = first;
385
+ }
386
+ return element;
387
+ }
388
+ /** Screenshot the failing state into a temp file; never masks the original error. */
389
+ async captureEvidence(driver) {
390
+ try {
391
+ const dir = mkdtempSync(evidenceDirectory("mobile-"));
392
+ const path = resolve(dir, "failure.png");
393
+ writeFileSync(path, Buffer.from(await driver.takeScreenshot(), "base64"));
394
+ return path;
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
399
+ async timeoutError(driver, target, verb) {
400
+ let excerpt = "";
401
+ try {
402
+ const labels = onScreenLabels(await driver.getPageSource());
403
+ if (labels.length > 0) excerpt = `\nCurrently on screen: ${labels.join(" | ")}`;
404
+ if (labels.length === 1) excerpt += "\nThe app is stalled on the splash: nothing but its launch screen is up.";
405
+ } catch {}
406
+ return new Error(refusal({
407
+ fix: "designate what this screen shows, or raise `timeouts.action` for a slow build",
408
+ found: `${verb}(${asWritten(target)}) timed out after ${this.actionTimeoutMs}ms: no visible match.${excerpt}`,
409
+ read: "docs/guides/mobile-specs.md#declared-timeouts",
410
+ why: "A mobile verb waits for one visible match, within the declared action budget."
411
+ }));
412
+ }
413
+ async session(bundleId) {
414
+ this.opened ??= await openSession({
415
+ bundleId,
416
+ device: this.options.device,
417
+ launchTimeoutMs: this.launchTimeoutMs,
418
+ ledger: this.ledger,
419
+ serverUrl: this.options.serverUrl,
420
+ webdriverio: this.webdriverio
421
+ });
422
+ return this.opened.driver;
423
+ }
424
+ };
425
+ //#endregion
426
+ export { AppiumAdapter };
427
+
428
+ //# sourceMappingURL=shared-appium.adapter.js.map
@@ -0,0 +1,32 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ //#region lint/kit/project/artifacts.ts
5
+ /** Every path here is relative to the project root. */
6
+ const ARTIFACTS_DIR = ".artifacts";
7
+ const VITEST_ARTIFACTS_DIR = `${ARTIFACTS_DIR}/vitest`;
8
+ const COVERAGE_DIR = `${VITEST_ARTIFACTS_DIR}/coverage`;
9
+ const SQLITE_TEMPLATE_DIR = `${VITEST_ARTIFACTS_DIR}/sqlite`;
10
+ /** Attachments and Browser Mode failure screenshots, which Vitest 5 would put in `.vitest/`. */
11
+ const ATTACHMENTS_DIR = `${VITEST_ARTIFACTS_DIR}/attachments`;
12
+ /** Under the project: the run's temp root is gone by the time the error naming the file is read. */
13
+ const EVIDENCE_DIR = `${VITEST_ARTIFACTS_DIR}/evidence`;
14
+ /** Filed under the run's temp root name, `<pid>-...`: the next run's start sweeps every run that is no longer alive. */
15
+ function evidenceDirectory(prefix) {
16
+ const parent = resolve(EVIDENCE_DIR, basename(tmpdir()));
17
+ mkdirSync(parent, { recursive: true });
18
+ return join(parent, prefix);
19
+ }
20
+ const SCREENSHOTS_DIR = `${VITEST_ARTIFACTS_DIR}/screenshots`;
21
+ const BLOB_DIR = `${VITEST_ARTIFACTS_DIR}/blob`;
22
+ /** Vitest 5's file-writing reporters, each pinned under the artifacts folder. */
23
+ const REPORTER_OUTPUT_FILES = {
24
+ blob: `${BLOB_DIR}/blob.json`,
25
+ html: `${VITEST_ARTIFACTS_DIR}/html/index.html`,
26
+ json: `${VITEST_ARTIFACTS_DIR}/json/output.json`,
27
+ junit: `${VITEST_ARTIFACTS_DIR}/junit/output.xml`
28
+ };
29
+ //#endregion
30
+ export { EVIDENCE_DIR as a, SQLITE_TEMPLATE_DIR as c, COVERAGE_DIR as i, VITEST_ARTIFACTS_DIR as l, ATTACHMENTS_DIR as n, REPORTER_OUTPUT_FILES as o, BLOB_DIR as r, SCREENSHOTS_DIR as s, ARTIFACTS_DIR as t, evidenceDirectory as u };
31
+
32
+ //# sourceMappingURL=shared-artifacts.js.map
@@ -0,0 +1,7 @@
1
+ import "@jterrazz/typescript/rules";
2
+ //#region lint/kit/catalogue/chapter.ts
3
+ const CATALOGUE_CHAPTER = "docs/reference/linting.md";
4
+ //#endregion
5
+ export { CATALOGUE_CHAPTER as t };
6
+
7
+ //# sourceMappingURL=shared-chapter.js.map
@@ -0,0 +1,31 @@
1
+ //#region src/services/containers/container.port.d.ts
2
+ /** A running service container (database, cache, ...). */
3
+ type ContainerPort = {
4
+ /** Start the container and wait until ready. */
5
+ start: () => Promise<void>;
6
+ /** Stop and remove the container. */
7
+ stop: () => Promise<void>;
8
+ /** Get the mapped host port for a container port. */
9
+ getMappedPort: (containerPort: number) => number;
10
+ /** Get the host to connect to. */
11
+ getHost: () => string;
12
+ /** Get a full connection string for this service. */
13
+ getConnectionString: () => string;
14
+ /** Get container logs (stdout + stderr): all of them, or those logged since a moment. */
15
+ getLogs: (since?: Date) => Promise<string>;
16
+ };
17
+ type ContainerRequest = ContainerOptions & {
18
+ image: string;
19
+ port: number;
20
+ env?: Record<string, string>;
21
+ };
22
+ /** How a service asks its container to run, beyond its image, port and environment. */
23
+ type ContainerOptions = {
24
+ /** The command the image's entrypoint is handed, replacing its default. */
25
+ command?: string[];
26
+ /** Paths mounted as memory disks, each with its mount options. */
27
+ tmpfs?: Record<string, string>;
28
+ };
29
+ //#endregion
30
+ export { ContainerPort as n, ContainerRequest as r, ContainerOptions as t };
31
+ //# sourceMappingURL=shared-container.port.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { t as SPECIFICATIONS } from "./shared-specifications.js";
2
+ import { TESTS_FOLDER } from "@jterrazz/typescript/rules";
3
+ import { existsSync, readdirSync } from "node:fs";
4
+ import { basename, join } from "node:path";
5
+ //#region lint/kit/project/derived-projects.ts
6
+ const SPECS = "specs";
7
+ const UNIT = "unit";
8
+ const FOLDERED = /* @__PURE__ */ new Set([...SPECIFICATIONS, "component"]);
9
+ const IN_PROCESS = /* @__PURE__ */ new Set([
10
+ "api",
11
+ "integration",
12
+ "job"
13
+ ]);
14
+ const SPECIFICATION_MODULE = ".specification.ts";
15
+ const MODULE_TEST = /\.test\.ts$/u;
16
+ const RENDERED_TEST = /\.test\.tsx$/u;
17
+ const UNWALKED_NAME = /^(?:[._]|node_modules$|dist$)/u;
18
+ const isUnwalked = (name) => name !== TESTS_FOLDER && UNWALKED_NAME.test(name);
19
+ function holds(path, pattern) {
20
+ return readdirSync(path, { withFileTypes: true }).some((entry) => {
21
+ if (isUnwalked(entry.name)) return false;
22
+ return entry.isDirectory() ? holds(join(path, entry.name), pattern) : pattern.test(entry.name);
23
+ });
24
+ }
25
+ const foldersIn = (path) => existsSync(path) ? readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !isUnwalked(entry.name)).map(({ name }) => name).toSorted() : [];
26
+ function runnerOf(root, folder, specification) {
27
+ if (!IN_PROCESS.has(specification)) return {};
28
+ const modules = readdirSync(join(root, folder)).filter((name) => name.endsWith(SPECIFICATION_MODULE));
29
+ const own = `${specification}${SPECIFICATION_MODULE}`;
30
+ return modules.length === 1 && modules[0] === own ? { runner: `./${folder}/${own}` } : {};
31
+ }
32
+ const isSpecsMember = (root) => basename(root) === SPECS && existsSync(join(root, "package.json"));
33
+ /** The projects a tree names, each with the options it derives: one per `specs/<specification>/` folder (an in-process one handed as its `runner` the folder's one specification module, `<specification>.specification.ts`), `unit` over each root holding a module test (`specs/unit/` is one root among them), `component` over each holding a rendered one. */
34
+ function derivedProjects(root) {
35
+ const member = isSpecsMember(root);
36
+ const prefix = member ? "" : `${SPECS}/`;
37
+ const specs = foldersIn(member ? root : join(root, SPECS));
38
+ const named = specs.filter((folder) => FOLDERED.has(folder));
39
+ const roots = [...member ? [] : foldersIn(root).filter((folder) => folder !== SPECS), ...specs.filter((folder) => !named.includes(folder)).map((folder) => `${prefix}${folder}`)];
40
+ const holding = (pattern) => roots.filter((folder) => holds(join(root, folder), pattern));
41
+ const rendered = [...named.includes("component") ? [`${prefix}component/**/*.test.tsx`] : [], ...holding(RENDERED_TEST).map((folder) => `${folder}/**/*.test.tsx`)];
42
+ const unitRoots = holding(MODULE_TEST);
43
+ const projects = /* @__PURE__ */ new Map();
44
+ if (unitRoots.length > 0) projects.set(UNIT, { roots: unitRoots });
45
+ for (const specification of named) projects.set(specification, runnerOf(root, `${prefix}${specification}`, specification));
46
+ if (rendered.length > 0) projects.set("component", { include: rendered });
47
+ return projects;
48
+ }
49
+ /** The specification modules a tree holds, `specs/<specification>/<specification>.specification.ts`: documents bind one by name, and no import reaches it. */
50
+ function specificationModules(root) {
51
+ const member = isSpecsMember(root);
52
+ const prefix = member ? "" : `${SPECS}/`;
53
+ return foldersIn(member ? root : join(root, SPECS)).filter((folder) => FOLDERED.has(folder)).map((folder) => `${prefix}${folder}/${folder}${SPECIFICATION_MODULE}`).filter((path) => existsSync(join(root, path)));
54
+ }
55
+ //#endregion
56
+ export { specificationModules as n, derivedProjects as t };
57
+
58
+ //# sourceMappingURL=shared-derived-projects.js.map