@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
package/dist/vitest.js CHANGED
@@ -1,232 +1,55 @@
1
- import { i as GROUND_EXPECTED } from "./fixtures.js";
2
- import { i as SCREENSHOTS_DIR, n as COVERAGE_DIR, o as VITEST_ARTIFACTS_DIR, r as REPORTER_OUTPUT_FILES, t as ATTACHMENTS_DIR } from "./artifacts.js";
3
- import { n as specDescription } from "./literate.js";
1
+ import { a as callerFrames, r as writeInPlace } from "./shared-in-place.js";
2
+ import { t as isRecord } from "./shared-json-shape.js";
3
+ import { n as refusal$1, t as invariant } from "./shared-refusal.js";
4
+ import { i as renderText, n as literate, o as resolveSpecPath } from "./shared-literate-plugin.js";
5
+ import { u as shouldUpdateSnapshots } from "./shared-ledger.js";
6
+ import { i as COVERAGE_DIR, l as VITEST_ARTIFACTS_DIR, n as ATTACHMENTS_DIR, o as REPORTER_OUTPUT_FILES, r as BLOB_DIR, s as SCREENSHOTS_DIR, t as ARTIFACTS_DIR } from "./shared-artifacts.js";
7
+ import { t as derivedProjects } from "./shared-derived-projects.js";
8
+ import { t as TEST_SECTION } from "./shared-test-section.js";
9
+ import { r as servicesSetup } from "./shared-services.global-setup.js";
10
+ import { r as tempRootSetup } from "./shared-temp-root.global-setup.js";
11
+ import { inPlaceSetup } from "./in-place.global-setup.js";
12
+ import { t as routeCoverageSetup } from "./shared-route-coverage.global-setup.js";
13
+ import { mobileSessionSetup } from "./mobile-session.global-setup.js";
4
14
  import { createRequire } from "node:module";
15
+ import { GROUND_GLOB, readSettings } from "@jterrazz/typescript/rules";
5
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
17
  import { basename, dirname, isAbsolute, resolve } from "node:path";
7
18
  import { fileURLToPath } from "node:url";
8
19
  import { configDefaults, mergeConfig } from "vitest/config";
9
- //#region src/runner/literate-plugin.ts
10
- /**
11
- * The `literate()` vite plugin — door one of the spec-document format.
12
- *
13
- * It makes a `<case>.spec.yaml` file a TEST FILE: the glob joins the project's
14
- * test include, and each document is transformed into a one-test module that
15
- * runs the file through the registered `cli` runner. The runner shows the
16
- * document's path as the file and its `description:` as the title, so a failing
17
- * scenario is opened where it is written — not in a `.test.ts` that merely
18
- * points at it.
19
- *
20
- * ```typescript
21
- * // vitest.config.ts
22
- * import { defineConfig } from 'vitest/config';
23
- * import { literate } from '@jterrazz/test/vitest';
24
- *
25
- * export default defineConfig({
26
- * plugins: [literate({ specification: './specs/cli/cli.specification.ts' })],
27
- * });
28
- * ```
29
- */
30
- /** The `.spec.yaml` extension, matched on the id vite hands the plugin. */
31
- const SPEC_FILE = /\.spec\.yaml(?:\?.*)?$/u;
32
- /**
33
- * The module a `<case>.spec.yaml` becomes: one `test()` whose title is the
34
- * document's `description:` and whose body runs the whole file — its ground,
35
- * its servers, every run asserted.
36
- *
37
- * @internal Exported for unit tests.
38
- */
39
- function literateModule(content, filePath, specificationPath) {
40
- const title = specDescription(content, filePath);
41
- return [
42
- "import { test } from 'vitest';",
43
- `import { cli } from ${JSON.stringify(specificationPath)};`,
44
- "",
45
- `test(${JSON.stringify(title)}, async () => {`,
46
- ` await cli.run(${JSON.stringify(filePath)});`,
47
- "});",
48
- ""
49
- ].join("\n");
50
- }
51
- /** Strip vite's query suffix (`?v=…`) from a module id. */
52
- function cleanId(id) {
53
- return id.split("?")[0] ?? id;
54
- }
55
- function literate(options) {
56
- const include = options.include ?? ["**/*.spec.yaml"];
57
- let root = process.cwd();
58
- return {
59
- config: () => ({ test: { include } }),
60
- configResolved: (resolved) => {
61
- root = resolved.root;
62
- },
63
- enforce: "pre",
64
- load: (id) => {
65
- if (!SPEC_FILE.test(id)) return null;
66
- const filePath = cleanId(id);
67
- const specificationPath = isAbsolute(options.specification) ? options.specification : resolve(root, options.specification);
68
- return literateModule(readFileSync(filePath, "utf8"), filePath, specificationPath);
69
- },
70
- name: "jterrazz-test:literate"
71
- };
72
- }
73
- //#endregion
74
- //#region src/runner/preset.ts
75
- /**
76
- * `defineSpecConfig()` — the shared vitest config every repository of the
77
- * ecosystem starts from.
78
- *
79
- * Fourteen hand-rolled `vitest.config.ts` files had converged on the same
80
- * handful of settings and disagreed on the rest by accident; the preset is
81
- * their common ground, stated once. A consumer writes ONE call and keeps
82
- * everything vitest offers: what it passes is a plain vite/vitest config,
83
- * merged OVER the defaults, so any of them is overridable in place.
84
- *
85
- * ```typescript
86
- * // vitest.config.ts
87
- * import { defineSpecConfig } from '@jterrazz/test/vitest';
88
- *
89
- * export default defineSpecConfig({
90
- * literate: { specification: './specs/cli/cli.specification.ts' },
91
- * test: {
92
- * projects: [
93
- * { test: { include: ['src/**\/*.test.ts'], name: 'unit' } },
94
- * { test: { include: ['specs/api/**\/*.test.ts'], name: 'api' } },
95
- * ],
96
- * },
97
- * });
98
- * ```
99
- */
100
- /**
101
- * A run's budget. Vitest's own 5s test / 10s hook defaults were raised by every
102
- * repository that starts real infrastructure, and none of them meant 5s — a
103
- * container boot, a `prisma db push` or a `next build` all outlive it. 30s is
104
- * the value the ecosystem's configs already converged on; a suite that needs
105
- * more (a simulator cold boot, a real image build) states its own per project.
106
- */
20
+ //#region src/vitest/project.ts
107
21
  const TEST_TIMEOUT_MS = 3e4;
108
22
  const HOOK_TIMEOUT_MS = 3e4;
109
- /**
110
- * What a spec STANDS ON is never itself a test: `_fixtures/` holds inputs —
111
- * deliberately-wrong twins, fixture projects a spec lints — and collecting them
112
- * runs a repository's own counter-examples as if they were its suite. Every
113
- * config that ever hit it added the same line by hand.
114
- *
115
- * Merged, not replaced: vite concatenates arrays, so a consumer's `exclude`
116
- * ADDS to this list and never has to spread `configDefaults.exclude` again.
117
- */
118
- const EXCLUDE = [...configDefaults.exclude, "**/_fixtures/**"];
119
- /**
120
- * What a test may never carry into the next one, and the one policy the
121
- * framework states rather than lints.
122
- *
123
- * `retry: 0` is the policy: a flaky test is fixed or deleted, never re-rolled
124
- * until it passes. A retry turns a real defect into a slow one and hides the
125
- * day it becomes permanent.
126
- *
127
- * The three restores are the other half of the same idea. `vi.spyOn`,
128
- * `vi.stubGlobal` and `vi.stubEnv` each own a teardown, and a test that forgets
129
- * one leaks into whatever runs next — a failure that appears in a FILE that did
130
- * nothing wrong and disappears when that file is run alone. Turning them on
131
- * here is what lets `clock.at()` and `vi.stubEnv()` be written without an
132
- * `afterEach`, which is the shape rule J6w asks for.
133
- */
23
+ /** Ground folders hold inputs and deliberately-wrong twins, never tests, and `.artifacts/` copies of the project (a mutation sandbox); vite concatenates, so a consumer's `exclude` adds to this. */
24
+ const EXCLUDE = [
25
+ ...configDefaults.exclude,
26
+ `${ARTIFACTS_DIR}/**`,
27
+ GROUND_GLOB
28
+ ];
134
29
  const HYGIENE = {
135
30
  restoreMocks: true,
136
31
  retry: 0,
137
32
  unstubEnvs: true,
138
33
  unstubGlobals: true
139
34
  };
140
- /**
141
- * Defaults a vitest PROJECT accepts. `coverage` and `reporters` are root-only
142
- * options (vitest omits them from a project's config), so they are absent here
143
- * on purpose — setting them would be silently dropped.
144
- *
145
- * Exported for the project helpers, which build their canonical project ON
146
- * these rather than beside them: a helper's project must carry the same
147
- * budgets and the same artefact directory as a hand-written one.
148
- *
149
- * @internal
150
- */
35
+ /** `coverage` and `reporters` are root-only: vitest drops them from a project. */
151
36
  function projectDefaults() {
152
37
  return {
153
38
  cacheDir: VITEST_ARTIFACTS_DIR,
154
39
  test: {
155
40
  attachmentsDir: ATTACHMENTS_DIR,
156
41
  exclude: EXCLUDE,
42
+ globalSetup: [inPlaceSetup(), tempRootSetup()],
157
43
  hookTimeout: HOOK_TIMEOUT_MS,
158
44
  ...HYGIENE,
159
45
  testTimeout: TEST_TIMEOUT_MS
160
46
  }
161
47
  };
162
48
  }
163
- /**
164
- * Coverage, when `--coverage` asks for it.
165
- *
166
- * `v8` because it is the runtime's own counter: no instrumentation pass, no
167
- * second transform of every file, and the numbers do not move when the bundler
168
- * does. The provider is an OPTIONAL peer (`@vitest/coverage-v8`), so a project
169
- * that never asks for coverage never installs it.
170
- *
171
- * `json-summary` is not a taste: it is the machine-readable report the ratchet
172
- * reads. `text` is for the human running the command, and `html` for the one
173
- * chasing a line. All three land under `.artifacts/vitest/coverage/`, the one
174
- * directory the toolchain's page owns.
175
- *
176
- * What is EXCLUDED is the shape of the answer, not a way to raise the number:
177
- * a type-only module has nothing to execute (v8 reports it as 0/0 or, worse,
178
- * as uncovered lines that cannot be covered), and the CLI entries are proven
179
- * by running the binary, not by importing it.
180
- */
181
- const COVERAGE = {
182
- exclude: [
183
- "**/node_modules/**",
184
- "specs/**",
185
- "**/_fixtures/**",
186
- "**/*.d.ts",
187
- "**/*.test-d.ts",
188
- "**/*.fixtures.ts",
189
- "dist/**",
190
- ".artifacts/**",
191
- "**/*.config.ts",
192
- "**/*.config.mts"
193
- ],
194
- provider: "v8",
195
- reporter: [
196
- "text",
197
- "html",
198
- "json-summary"
199
- ],
200
- reportsDirectory: COVERAGE_DIR
201
- };
202
- /** Defaults for the root config — the project ones, plus what only a root carries. */
203
- function rootDefaults() {
204
- return {
205
- cacheDir: VITEST_ARTIFACTS_DIR,
206
- test: {
207
- attachmentsDir: ATTACHMENTS_DIR,
208
- coverage: COVERAGE,
209
- exclude: EXCLUDE,
210
- hookTimeout: HOOK_TIMEOUT_MS,
211
- ...HYGIENE,
212
- outputFile: REPORTER_OUTPUT_FILES,
213
- testTimeout: TEST_TIMEOUT_MS
214
- }
215
- };
216
- }
217
- /** A glob list with its repeats taken out, or nothing where there is no list. */
218
49
  function eachOnce(globs) {
219
50
  return globs === void 0 ? void 0 : [...new Set(globs)];
220
51
  }
221
- /**
222
- * The glob lists vite CONCATENATES rather than overrides, each glob kept once.
223
- *
224
- * A project built ON the defaults and then merged INTO them by
225
- * `defineSpecConfig` carried every default twice. Glob matching is idempotent,
226
- * so nothing collected differently — but the no-tests banner is the one place a
227
- * consumer ever reads that list, and it read as three merges of the same three
228
- * globs.
229
- */
52
+ /** Vite concatenates these lists: each glob is kept once. */
230
53
  function statedOnce(test) {
231
54
  const exclude = eachOnce(test.exclude);
232
55
  const include = eachOnce(test.include);
@@ -236,11 +59,6 @@ function statedOnce(test) {
236
59
  ...include === void 0 ? {} : { include }
237
60
  };
238
61
  }
239
- /**
240
- * Build a project ON the defaults — the ONE way a project is built here.
241
- *
242
- * @internal
243
- */
244
62
  function onProjectDefaults(stated) {
245
63
  const merged = mergeConfig(projectDefaults(), stated);
246
64
  return merged.test === void 0 ? merged : {
@@ -248,151 +66,139 @@ function onProjectDefaults(stated) {
248
66
  test: statedOnce(merged.test)
249
67
  };
250
68
  }
251
- /**
252
- * Is every project an inline object — one this preset merged its defaults into?
253
- * A project named by a glob or handed as a promise carries a config this call
254
- * never saw, and the root's exclusions are the only ones it gets from here.
255
- */
256
- function allInline(projects) {
257
- return projects.every((project) => typeof project === "object" && project !== null && !("then" in project));
258
- }
259
- /**
260
- * The root's exclusions, beside projects that already carry them.
261
- *
262
- * With `projects`, the root collects nothing itself: its `exclude` only travels
263
- * INTO each project, where vitest CONCATENATES it with the project's own — so
264
- * every preset glob reached the banner twice. The preset's own are dropped
265
- * here, since each inline project states them in full; what the CONSUMER stated
266
- * at the root stays, because no project holds a copy of that.
267
- */
268
- function rootExcludeBeside(projects, exclude) {
269
- return allInline(projects) ? exclude.filter((glob) => !EXCLUDE.includes(glob)) : exclude;
270
- }
271
- /**
272
- * A project inherits NOTHING from the root `test` block — vitest resolves each
273
- * project as its own config — so the defaults are merged into every inline one.
274
- * A project declared as a glob string, a promise or a function is handed back
275
- * untouched: there is no object to merge into.
276
- */
277
- function withProjectDefaults(project) {
278
- if (typeof project !== "object" || project === null || "then" in project) return project;
279
- return onProjectDefaults(project);
280
- }
281
- function defineSpecConfig(config = {}) {
282
- const { literate: literateOptions, ...userConfig } = config;
283
- const stated = literateOptions ? mergeConfig({ plugins: [literate(literateOptions)] }, userConfig) : userConfig;
284
- const merged = mergeConfig(rootDefaults(), stated);
285
- const root = merged.test === void 0 ? void 0 : statedOnce(merged.test);
286
- if (root === void 0) return merged;
287
- const { projects } = root;
288
- if (projects === void 0) return {
289
- ...merged,
290
- test: root
291
- };
292
- const inline = projects.map(withProjectDefaults);
69
+ /** A stated `include` or `exclude` replaces the canonical one; the group is 0 until a specification states its own. */
70
+ function projectTest(name, options, canonical) {
71
+ const exclude = options.exclude ?? canonical.exclude;
293
72
  return {
294
- ...merged,
295
- test: {
296
- ...root,
297
- ...root.exclude === void 0 ? {} : { exclude: rootExcludeBeside(inline, root.exclude) },
298
- projects: inline
73
+ ...exclude === void 0 ? {} : { exclude },
74
+ include: options.include ?? canonical.include,
75
+ ...options.serial === true ? { fileParallelism: false } : {},
76
+ name,
77
+ sequence: { groupOrder: 0 },
78
+ ...options.timeout === void 0 ? {} : {
79
+ hookTimeout: options.timeout,
80
+ testTimeout: options.timeout
299
81
  }
300
82
  };
301
83
  }
302
- //#endregion
303
- //#region src/runner/facet-project.ts
304
- /** The globs a node facet collects, and the ones a caller stated instead. */
305
- function facetProject(facet, options) {
306
- return onProjectDefaults({ test: {
307
- ...options.exclude === void 0 ? {} : { exclude: options.exclude },
308
- include: options.include ?? [`specs/${facet}/**/*.spec.ts`],
309
- ...options.serial === true ? { fileParallelism: false } : {},
310
- name: facet,
311
- sequence: { groupOrder: 0 },
312
- ...options.timeout === void 0 ? {} : { testTimeout: options.timeout }
84
+ function specificationProject(specification, options) {
85
+ return onProjectDefaults({ test: projectTest(specification, options, { include: [`specs/${specification}/**/*.spec.ts`] }) });
86
+ }
87
+ function inProcessProject(specification, options) {
88
+ const project = specificationProject(specification, options);
89
+ return options.runner === void 0 ? project : mergeConfig(project, { test: {
90
+ isolate: false,
91
+ setupFiles: [options.runner]
313
92
  } });
314
93
  }
315
94
  //#endregion
316
- //#region src/facets/api/api.project.ts
317
- /** `api()` — the app met through HTTP. */
95
+ //#region src/specifications/api/api.project.ts
318
96
  function api(options = {}) {
319
- return facetProject("api", options);
97
+ return mergeConfig(inProcessProject("api", options), { test: { globalSetup: [servicesSetup(), routeCoverageSetup()] } });
320
98
  }
321
99
  //#endregion
322
- //#region src/facets/cli/cli.project.ts
323
- /** The literate defaults — the path convention `cli()` assumes when nothing says otherwise. */
324
- const CLI_SPECIFICATION = "./specs/cli/cli.specification.ts";
325
- const CLI_DOCUMENTS = ["specs/cli/**/*.spec.yaml"];
326
- /**
327
- * A cli project collects the facet's `.test.ts` files AND its documents: a
328
- * `<case>.spec.yaml` is a test file of its own (docs/07 § the literate door),
329
- * so the plugin's glob has to join the include of the project that collects
330
- * them — which is this one, never the root config.
331
- */
100
+ //#region src/specifications/cli/cli.project.ts
101
+ /** A cli project: the specification's `.spec.ts` files and its documents (docs/guides/spec-documents.md); the plugin's glob joins this project's include, never the root config. */
332
102
  function cli(options = {}) {
333
- const { literate: literateOptions, ...common } = options;
334
- const project = facetProject("cli", common);
335
- if (literateOptions === false) return project;
336
- const documents = literateOptions?.include ?? CLI_DOCUMENTS;
103
+ const { literate: door, ...common } = options;
104
+ const project = specificationProject("cli", common);
105
+ if (door === false) return project;
337
106
  return mergeConfig(project, { plugins: [literate({
338
- include: documents,
339
- specification: literateOptions?.specification ?? CLI_SPECIFICATION
107
+ include: door?.include ?? ["specs/cli/**/*.spec.yaml"],
108
+ runner: door?.runner ?? "./specs/cli/cli.specification.ts"
340
109
  })] });
341
110
  }
342
111
  //#endregion
343
- //#region src/seams/vitest-browser/commands.ts
344
- /** Where a golden sits: `_expected/<name>` beside the test that asked for it. */
112
+ //#region src/stubs/delivery/browser-page/worker-script.ts
113
+ /** From this package's own install: `@vitest/browser` resolves it only where the installer hoists, and a consumer declaring `msw` is what `no-redundant-test-dependency` forbids. */
114
+ function mswWorkerScript() {
115
+ return createRequire(import.meta.url).resolve("msw/mockServiceWorker.js");
116
+ }
117
+ //#endregion
118
+ //#region src/specifications/component/golden/commands.ts
119
+ function testFile(testPath) {
120
+ if (testPath === void 0) throw invariant("a golden command reached the server with no test path");
121
+ return testPath;
122
+ }
345
123
  function goldenPath(testPath, name) {
346
- if (testPath === void 0) throw new Error("golden: the command carries no test path — a golden is read from a test file.");
347
- return resolve(dirname(testPath), GROUND_EXPECTED, name);
124
+ return resolveSpecPath("toMatch", name, dirname(testFile(testPath)));
348
125
  }
349
- /** The ARIA snapshot of the rendered `<body>`, as YAML. */
350
126
  const ariaTree = async (context) => {
351
127
  return await (await context.frame()).locator("body").ariaSnapshot();
352
128
  };
353
- /**
354
- * The ARIA snapshot of ONE node, named by the selector the page's locator
355
- * carries — the accessible name, as the browser computed it, for a page that
356
- * cannot compute one itself.
357
- */
358
- const ariaNode = async (context, selector) => {
359
- return await (await context.frame()).locator(selector).ariaSnapshot();
360
- };
361
- /**
362
- * A line the page has for whoever is running the suite, written on the node
363
- * side's stderr — the one channel vitest's default reporter passes through.
364
- * The console is captured and dropped, so a page that printed to it printed to
365
- * nobody.
366
- */
367
- const notify = (_context, line) => {
368
- process.stderr.write(`${line}\n`);
369
- };
370
- /** The golden's current content, or `null` when it does not exist yet. */
371
129
  const goldenRead = (context, name) => {
372
130
  const path = goldenPath(context.testPath, name);
373
131
  return existsSync(path) ? readFileSync(path, "utf8") : null;
374
132
  };
375
- /** Write the golden — update mode's half, and the path it wrote, for the message. */
376
133
  const goldenWrite = (context, name, content) => {
377
134
  const path = goldenPath(context.testPath, name);
378
135
  mkdirSync(dirname(path), { recursive: true });
379
- writeFileSync(path, content, "utf8");
136
+ writeFileSync(path, content);
380
137
  return path;
381
138
  };
382
- /**
383
- * The commands, as `component()` registers them — one list, so the names the
384
- * page calls and the names the project registers cannot drift.
385
- */
139
+ const inPlaceWrite = (context, stack, text) => {
140
+ const file = testFile(context.testPath);
141
+ const frame = context.project.browser?.parseStacktrace(stack).find((candidate) => candidate.file === file);
142
+ if (frame === void 0) throw new Error(refusal$1({
143
+ fix: "call toMatch() from the test file itself, or compare to a golden file: toMatch(file(...))",
144
+ found: "update mode: no frame of this toMatch call is the test file, so the value in place has nowhere to be written",
145
+ read: "docs/reference/tokens.md#a-value-written-in-place-is-rewritten-in-the-test",
146
+ why: "A value written in place is rewritten in the test that states it."
147
+ }));
148
+ writeInPlace({
149
+ column: frame.column,
150
+ file,
151
+ line: frame.line
152
+ }, (indent) => renderText(text, indent), context.project.getProvidedContext().inPlaceRewrites);
153
+ return `${file}:${frame.line}`;
154
+ };
155
+ /** One list, so the names the page calls and the names the project registers cannot drift. */
386
156
  const COMPONENT_COMMANDS = {
387
- ariaNode,
388
157
  ariaTree,
389
158
  goldenRead,
390
159
  goldenWrite,
391
- notify
160
+ inPlaceWrite
392
161
  };
393
162
  //#endregion
394
- //#region src/facets/component/component.project.ts
395
- /** The pipeline keys a consumer's Vite config contributes, and nothing else. */
163
+ //#region src/specifications/component/peers/peer-versions.ts
164
+ const requireFrom$1 = createRequire(import.meta.url);
165
+ function versionOf(specifier) {
166
+ const path = requireFrom$1.resolve(`${specifier}/package.json`);
167
+ const manifest = JSON.parse(String(readFileSync(path)));
168
+ if (typeof manifest === "object" && manifest !== null && "version" in manifest) return String(manifest.version);
169
+ throw new Error(refusal$1({
170
+ fix: `reinstall ${specifier}: \`npm install ${specifier}\``,
171
+ found: `the installed ${specifier} manifest carries no version`,
172
+ read: "docs/start/set-up-a-project.md#what-to-install",
173
+ why: "The component project reads the version of the peers it configures from their installed manifests."
174
+ }));
175
+ }
176
+ /** Vite 8 transforms with oxc and warns on every `esbuild` option; Vite 6 and 7 have no `oxc` key. */
177
+ function transformKey() {
178
+ return Number.parseInt(versionOf("vite").split(".")[0] ?? "", 10) >= 8 ? "oxc" : "esbuild";
179
+ }
180
+ /** `@vitest/browser-playwright` peers vitest on an exact version; a mismatch fails deep in the tester naming neither. */
181
+ function assertProviderPin(runner, provider) {
182
+ if (provider === null) throw new Error(refusal$1("seam-is-an-optional-peer", {
183
+ fix: `npm i -D @vitest/browser-playwright@${runner} playwright`,
184
+ found: "component() requires `@vitest/browser-playwright`, and it is not installed"
185
+ }));
186
+ if (runner !== provider) throw new Error(refusal$1({
187
+ fix: `bump both to the same version in one change: npm i -D vitest@${provider} @vitest/browser-playwright@${provider}`,
188
+ found: `component(): @vitest/browser-playwright is ${provider}, and vitest is ${runner}`,
189
+ read: "docs/start/set-up-a-project.md#what-to-install",
190
+ why: "`@vitest/browser-playwright` peers vitest on an exact version."
191
+ }));
192
+ }
193
+ function installedVersion(specifier) {
194
+ try {
195
+ return versionOf(specifier);
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+ //#endregion
201
+ //#region src/specifications/component/component.project.ts
396
202
  const PIPELINE_KEYS = [
397
203
  "assetsInclude",
398
204
  "css",
@@ -400,44 +206,25 @@ const PIPELINE_KEYS = [
400
206
  "envPrefix",
401
207
  "esbuild",
402
208
  "json",
209
+ "optimizeDeps",
403
210
  "oxc",
404
211
  "plugins",
405
212
  "resolve"
406
213
  ];
407
- /**
408
- * The seam's own runtime, pre-bundled so a cold cache never reloads mid-run.
409
- *
410
- * `msw` and `msw/browser` are NOT here: `@vitest/browser` already puts both in
411
- * `optimizeDeps.exclude`, and Vite's esbuild optimizer (6 and 7) treats an
412
- * entry that is both included and excluded as fatal — dependency optimisation
413
- * throws before a single test runs.
414
- */
415
- const SEAM_DEPENDENCIES = [
214
+ const PAGE_DEPENDENCIES = [
416
215
  "react",
417
216
  "react-dom/client",
418
217
  "react/jsx-dev-runtime",
419
218
  "react/jsx-runtime",
420
- "vitest-browser-react"
219
+ "vitest-browser-react",
220
+ "vitest-mock-extended"
421
221
  ];
422
222
  const requireFrom = createRequire(import.meta.url);
423
- /** This module's own file — the frames the caller search walks past. */
424
223
  const HELPERS_FILE = import.meta.filename;
425
- /** A stack frame's file, in either of the two shapes V8 prints it in. */
426
- const STACK_FRAME = /(?:\((?<wrapped>[^()]+):\d+:\d+\)|at (?<bare>[^()\s]+):\d+:\d+)$/u;
427
- /** A config vite bundled before importing it — `<its name>.timestamp-<hash>.mjs`. */
428
224
  const BUNDLED_CONFIG = /^(?<name>.+)\.timestamp-[^.]+\.mjs$/u;
429
- /** The file of the first frame below this module — who called the helper. */
430
225
  function callerFile() {
431
- const { stack } = /* @__PURE__ */ new Error("@jterrazz/test: reading the call site of a project helper");
432
- for (const frame of stack?.split("\n").slice(1) ?? []) {
433
- const groups = STACK_FRAME.exec(frame.trim())?.groups;
434
- const location = groups?.wrapped ?? groups?.bare;
435
- if (location === void 0 || location.startsWith("node:")) continue;
436
- const file = location.startsWith("file:") ? fileURLToPath(location) : location;
437
- if (file !== HELPERS_FILE) return file;
438
- }
226
+ return callerFrames().find(({ file }) => file !== HELPERS_FILE)?.file;
439
227
  }
440
- /** The nearest directory at or above the cwd holding a file of this name. */
441
228
  function nearestHolding(name) {
442
229
  let directory = process.cwd();
443
230
  for (;;) {
@@ -447,27 +234,6 @@ function nearestHolding(name) {
447
234
  directory = parent;
448
235
  }
449
236
  }
450
- /**
451
- * The directory of the CONFIG that called a helper — what the relative paths
452
- * it was handed mean.
453
- *
454
- * Not `process.cwd()`: a `vitest.config.ts` is read by more tools than the
455
- * runner, and knip and `typescript check` load every config of a repository
456
- * from the repository ROOT — `component({ vite: './web/vite.config.ts' })`
457
- * written beside an app resolved against the wrong tree and threw before a
458
- * single test ran. `import.meta` cannot answer either: it describes THIS
459
- * module. And vitest hands a project neither its root nor its config file — a
460
- * project function is called with `{ command, mode, isPreview, isSsrBuild }`
461
- * and nothing else.
462
- *
463
- * So the call site is read off the stack, where a loader that keeps a config's
464
- * identity (node, tsx, jiti — what those tools use) names the file itself.
465
- * Vite's own loader does not: it BUNDLES the config into
466
- * `node_modules/.vite-temp/` and only the config's NAME survives the move. The
467
- * name is what is then looked for upward from the cwd, because that loader is
468
- * the runner's and a runner is started in the tree it tests. A config loaded
469
- * by the runner from another tree entirely states `root` itself.
470
- */
471
237
  function callerDirectory() {
472
238
  const file = callerFile();
473
239
  if (file === void 0) return process.cwd();
@@ -475,7 +241,6 @@ function callerDirectory() {
475
241
  if (bundled === void 0 || basename(dirname(file)) !== ".vite-temp") return dirname(file);
476
242
  return nearestHolding(bundled) ?? process.cwd();
477
243
  }
478
- /** Is this specifier installed here? An absent optional peer is simply not pre-bundled. */
479
244
  function resolves(specifier) {
480
245
  try {
481
246
  requireFrom.resolve(specifier);
@@ -484,20 +249,10 @@ function resolves(specifier) {
484
249
  return false;
485
250
  }
486
251
  }
487
- /**
488
- * Serve msw's worker script from the package's OWN install, at the path the
489
- * worker asks for, with the scope header a worker registered at `/` needs.
490
- *
491
- * `@vitest/browser` resolves `/mockServiceWorker.js` too, but from ITS own
492
- * directory — and it does not depend on `msw`, so that resolution only lands
493
- * where the installer hoists. The package that OWNS the dependency is the one
494
- * that can always find it, and it is this one. Without either, a consumer runs
495
- * `msw init` and declares `msw`, which F8 forbids.
496
- */
497
252
  function mswWorkerPlugin() {
498
253
  return {
499
254
  configureServer(server) {
500
- const script = requireFrom.resolve("msw/mockServiceWorker.js");
255
+ const script = mswWorkerScript();
501
256
  server.middlewares.use("/mockServiceWorker.js", (_request, response) => {
502
257
  response.setHeader("content-type", "text/javascript");
503
258
  response.setHeader("service-worker-allowed", "/");
@@ -507,16 +262,7 @@ function mswWorkerPlugin() {
507
262
  name: "@jterrazz/test:msw-worker"
508
263
  };
509
264
  }
510
- /**
511
- * Keep an app's Vite PIPELINE and drop everything that says where the app
512
- * lives.
513
- *
514
- * An allow-list, not a drop-list: a consumer's config carries keys the
515
- * framework has never heard of — `environments`, `ssr`, `appType`, `publicDir`
516
- * — and one of them re-roots the run and the project collects nothing. Naming
517
- * what the pipeline IS keeps an unknown key from leaking in. The consumer's
518
- * `plugins` are CONCATENATED after the seam's, never assigned over them.
519
- */
265
+ const isPluginList = (plugins) => Array.isArray(plugins);
520
266
  async function reroot(source, root) {
521
267
  const environment = {
522
268
  command: "serve",
@@ -524,144 +270,90 @@ async function reroot(source, root) {
524
270
  mode: "test"
525
271
  };
526
272
  let loaded;
527
- let from = "the value it was given";
273
+ let found;
274
+ let fix;
528
275
  if (typeof source === "string") {
529
276
  const { loadConfigFromFile } = await import("vite");
530
- from = isAbsolute(source) ? source : resolve(root, source);
277
+ const from = isAbsolute(source) ? source : resolve(root, source);
531
278
  loaded = (await loadConfigFromFile(environment, from))?.config;
532
- } else if (typeof source === "function") loaded = await source(environment);
533
- else loaded = await source;
534
- if (loaded === void 0) throw new Error(`component({ vite }): no config at ${from}`);
279
+ found = `${from} exports no config`;
280
+ fix = "point `vite` at the app's own vite.config.ts";
281
+ } else if (typeof source === "function") {
282
+ loaded = await source(environment);
283
+ if (typeof loaded === "function") {
284
+ found = "the config function returned a function, not a config";
285
+ fix = "call the framework's factory and pass what it returns (for Astro, `getViteConfig({}, { root: import.meta.dirname })`, never `getViteConfig`)";
286
+ } else {
287
+ found = "the config function returned no config";
288
+ fix = "return the framework's config from the function (for Astro, `getViteConfig({}, { root: import.meta.dirname })`)";
289
+ }
290
+ } else {
291
+ loaded = await source;
292
+ found = "the config promise resolved to no config";
293
+ fix = "resolve the promise to the framework's config object";
294
+ }
295
+ if (typeof loaded !== "object" || loaded === null) throw new Error(refusal$1({
296
+ fix,
297
+ found: `component({ vite }): ${found}`,
298
+ read: "docs/guides/component-specs.md#the-constructor-there-is-none",
299
+ why: "A component renders through the Vite pipeline the app builds with, read from its config."
300
+ }));
535
301
  const inactive = transformKey() === "oxc" ? "esbuild" : "oxc";
536
302
  const kept = {};
537
303
  const config = { ...loaded };
538
- for (const key of PIPELINE_KEYS) if (config[key] !== void 0 && key !== inactive) kept[key] = config[key];
304
+ for (const key of PIPELINE_KEYS) if (key !== inactive) kept[key] = config[key];
539
305
  return kept;
540
306
  }
541
- /** What a missing optional peer costs, said once rather than as a resolver stack. */
542
- const PROVIDER_MISSING = "component(): @vitest/browser-playwright is an optional peer — install it beside the runner it pins (npm i -D @vitest/browser-playwright@<the installed vitest version> playwright).";
543
- /** The installed version of a package, read from its own manifest. */
544
- function versionOf(specifier) {
545
- const manifest = JSON.parse(readFileSync(requireFrom.resolve(`${specifier}/package.json`), "utf8"));
546
- if (typeof manifest === "object" && manifest !== null && "version" in manifest) return String(manifest.version);
547
- throw new Error(`component(): ${specifier} is installed without a version.`);
548
- }
549
- /**
550
- * Which of the two transformer keys the INSTALLED Vite actually reads.
551
- *
552
- * Vite 8 transforms with oxc and prints a warning for every `esbuild` option it
553
- * is handed; Vite 6 and 7 transform with esbuild and have no `oxc` key at all.
554
- * The inactive one is dropped rather than carried as a second, ignored
555
- * statement — a config that says the same thing twice is a config that will one
556
- * day say two different things.
557
- */
558
- function transformKey() {
559
- return Number.parseInt(versionOf("vite").split(".")[0] ?? "", 10) >= 8 ? "oxc" : "esbuild";
307
+ function optimizeDepsOf(declared) {
308
+ const own = isRecord(declared) ? { ...declared } : {};
309
+ const listed = "include" in own && Array.isArray(own.include) ? own.include : [];
310
+ return {
311
+ ...own,
312
+ include: [.../* @__PURE__ */ new Set([...PAGE_DEPENDENCIES.filter(resolves), ...listed.filter((entry) => typeof entry === "string")])]
313
+ };
560
314
  }
561
- /**
562
- * The JSX default, on the key the running Vite reads, yielding to the
563
- * consumer's own: a `jsxImportSource` or a classic runtime is the app's
564
- * statement, not the seam's to overwrite.
565
- */
566
315
  function jsxPipeline(pipeline) {
567
316
  const key = transformKey();
568
317
  const stated = pipeline[key];
569
318
  if (key === "oxc") return { oxc: stated ?? { jsx: { runtime: "automatic" } } };
570
319
  return { esbuild: stated ?? { jsx: "automatic" } };
571
320
  }
572
- /**
573
- * `@vitest/browser-playwright` peers vitest on an EXACT version: 4.1.10 wants
574
- * 4.1.10, not `^4.1`. A mismatch fails deep inside the tester with a message
575
- * about a missing runner, so the pair is checked where the project is built
576
- * and both versions are named.
577
- */
578
- function assertProviderPin() {
579
- const runner = versionOf("vitest");
580
- let provider;
581
- try {
582
- provider = versionOf("@vitest/browser-playwright");
583
- } catch {
584
- throw new Error(PROVIDER_MISSING);
585
- }
586
- if (runner !== provider) throw new Error(`component(): @vitest/browser-playwright@${provider} peers vitest on an exact version, and vitest is ${runner}. Bump both to the same version in one change (npm i -D vitest@${provider} @vitest/browser-playwright@${provider}).`);
587
- }
588
- /** The framework's own update flag, read where `process` exists. */
589
- function updating() {
590
- return process.env.TEST_UPDATE === "1" || process.argv.includes("-u") || process.argv.includes("--update");
591
- }
592
- /**
593
- * Hand the project's `wrap` to the page as a STATIC import.
594
- *
595
- * The page could fetch the module by URL when the first render needs it, and
596
- * that is what breaks: a dependency Vite never scanned is discovered mid-run,
597
- * the optimizer rewrites its cache, and every module the page is holding —
598
- * the test's own included — 404s behind a stale `?v=` hash. A setup file is in
599
- * the graph before the first test runs, so the wrap and everything it imports
600
- * are scanned, pre-bundled, and never re-optimised under the run.
601
- *
602
- * It is written into `.artifacts/` rather than served from memory because
603
- * vitest resolves `setupFiles` as paths on disk; it is generated, so it belongs
604
- * with everything else a tool generates, and never on the row.
605
- *
606
- * The wrapper lands on `globalThis` under a shared symbol rather than an
607
- * export: the setup module and the package's browser build are separate graphs
608
- * in the page and share no module instance.
609
- */
610
321
  function writeSetupFile(wrap, root) {
611
322
  const absolute = resolve(root, wrap);
612
323
  const path = resolve(root, VITEST_ARTIFACTS_DIR, "component-setup.mjs");
613
324
  mkdirSync(dirname(path), { recursive: true });
614
325
  writeFileSync(path, [
615
- "// Generated by `component({ wrap })` — @jterrazz/test. Do not edit.",
326
+ "// Generated by `component({ wrap })` of @jterrazz/test. Do not edit.",
616
327
  `import wrap from ${JSON.stringify(absolute)};`,
617
328
  "globalThis[Symbol.for('@jterrazz/test:component-wrap')] = wrap;",
618
329
  ""
619
330
  ].join("\n"), "utf8");
620
331
  return path;
621
332
  }
622
- /**
623
- * What the page cannot read for itself: update mode is a `process.env`/`argv`
624
- * read, and the project's clock is stated where `process` exists.
625
- */
626
333
  function providedToPage(options) {
627
334
  return {
628
335
  ...options.clock === void 0 ? {} : { componentClock: options.clock },
629
336
  componentViewport: viewportOf(options),
630
- update: updating()
337
+ update: shouldUpdateSnapshots()
631
338
  };
632
339
  }
633
- /** The page size every render of the project starts at. */
634
340
  function viewportOf(options) {
635
341
  return options.viewport ?? {
636
342
  height: 720,
637
343
  width: 1280
638
344
  };
639
345
  }
640
- /** The globs a component project collects, and the ones it stays out of. */
641
- function componentGlobs(options) {
642
- return {
643
- exclude: options.exclude ?? (options.include === void 0 ? ["specs/**"] : []),
644
- include: options.include ?? ["**/*.test.tsx"]
645
- };
646
- }
647
- /**
648
- * A component test renders in a real Chromium — the browser the website facet
649
- * already drives through the same `playwright` peer.
650
- *
651
- * The default `include` reaches the whole project, so the default `exclude`
652
- * keeps it out of `specs/`, where the assembled product is specified. State
653
- * your own `include` and the exclusion is yours to state too.
654
- */
346
+ /** A component project: renders in a real Chromium; the default `exclude` keeps it out of `specs/`. */
655
347
  async function component(options = {}) {
656
348
  const root = options.root ?? callerDirectory();
657
- assertProviderPin();
349
+ assertProviderPin(versionOf("vitest"), installedVersion("@vitest/browser-playwright"));
658
350
  const { playwright } = await import("@vitest/browser-playwright");
659
351
  const pipeline = options.vite === void 0 ? {} : await reroot(options.vite, root);
660
- const consumerPlugins = Array.isArray(pipeline.plugins) ? pipeline.plugins : [];
352
+ const consumerPlugins = isPluginList(pipeline.plugins) ? pipeline.plugins : [];
661
353
  return onProjectDefaults({
662
354
  ...pipeline,
663
355
  ...jsxPipeline(pipeline),
664
- optimizeDeps: { include: SEAM_DEPENDENCIES.filter(resolves) },
356
+ optimizeDeps: optimizeDepsOf(pipeline.optimizeDeps),
665
357
  plugins: [mswWorkerPlugin(), ...consumerPlugins],
666
358
  test: {
667
359
  browser: {
@@ -677,76 +369,232 @@ async function component(options = {}) {
677
369
  screenshotDirectory: SCREENSHOTS_DIR,
678
370
  viewport: viewportOf(options)
679
371
  },
680
- ...componentGlobs(options),
681
- ...options.serial === true ? { fileParallelism: false } : {},
682
- name: "component",
372
+ ...projectTest("component", options, {
373
+ exclude: options.include === void 0 ? ["specs/**"] : [],
374
+ include: ["**/*.test.tsx"]
375
+ }),
683
376
  provide: providedToPage(options),
684
377
  ...options.wrap === void 0 ? {} : { setupFiles: [writeSetupFile(options.wrap, root)] },
685
- sequence: { groupOrder: 2 },
686
- ...options.timeout === void 0 ? {} : { testTimeout: options.timeout }
378
+ sequence: { groupOrder: 2 }
687
379
  }
688
380
  });
689
381
  }
690
382
  //#endregion
691
- //#region src/facets/integration/integration.project.ts
692
- /** `integration()` — a module against real services, or against a golden. */
383
+ //#region src/specifications/integration/integration.project.ts
693
384
  function integration(options = {}) {
694
- return facetProject("integration", options);
385
+ return mergeConfig(inProcessProject("integration", options), { test: { globalSetup: [servicesSetup()] } });
386
+ }
387
+ //#endregion
388
+ //#region src/specifications/job/job.project.ts
389
+ function job(options = {}) {
390
+ return mergeConfig(inProcessProject("job", options), { test: { globalSetup: [servicesSetup()] } });
695
391
  }
696
392
  //#endregion
697
- //#region src/facets/jobs/jobs.project.ts
698
- /** `jobs()` — what a name triggers in-process. */
699
- function jobs(options = {}) {
700
- return facetProject("jobs", options);
393
+ //#region src/specifications/lint/lint.project.ts
394
+ function lint(options = {}) {
395
+ return specificationProject("lint", options);
701
396
  }
702
397
  //#endregion
703
- //#region src/facets/mobile/mobile.project.ts
704
- /**
705
- * `mobile()` — the app on a simulator. A simulator is the one thing that
706
- * cannot share a machine with itself, so it runs last and alone (group 3).
707
- */
398
+ //#region src/specifications/mobile/mobile.project.ts
399
+ /** A simulator cannot share a machine with itself: serial. A global setup holds one driver session for the run. */
708
400
  function mobile(options = {}) {
709
- return mergeConfig(facetProject("mobile", options), { test: { sequence: { groupOrder: 3 } } });
401
+ const { device, root, ...common } = options;
402
+ return mergeConfig(specificationProject("mobile", common), { test: {
403
+ fileParallelism: false,
404
+ globalSetup: [mobileSessionSetup()],
405
+ ...device === void 0 ? {} : { provide: { mobileAppium: {
406
+ device,
407
+ root: root ?? "."
408
+ } } },
409
+ sequence: { groupOrder: 3 }
410
+ } });
710
411
  }
711
412
  //#endregion
712
- //#region src/facets/website/website.project.ts
713
- /**
714
- * A website spec meets the assembled product through a served page, in the
715
- * Chromium playwright drives. It opens a browser, so it is scheduled before the
716
- * component project and never beside it: two Chromiums must not share a slot on
717
- * a 2-vCPU runner.
718
- */
413
+ //#region src/specifications/website/website.project.ts
719
414
  function website(options = {}) {
720
- return onProjectDefaults({ test: {
721
- ...options.exclude === void 0 ? {} : { exclude: options.exclude },
722
- ...options.serial === true ? { fileParallelism: false } : {},
723
- include: options.include ?? ["specs/website/**/*.spec.ts"],
724
- name: "website",
725
- sequence: { groupOrder: 1 },
726
- ...options.timeout === void 0 ? {} : { testTimeout: options.timeout }
415
+ return mergeConfig(specificationProject("website", options), { test: { sequence: { groupOrder: 1 } } });
416
+ }
417
+ //#endregion
418
+ //#region src/vitest/preset.ts
419
+ const COVERAGE = {
420
+ exclude: [
421
+ "**/node_modules/**",
422
+ "specs/**",
423
+ GROUND_GLOB,
424
+ "**/*.d.ts",
425
+ "**/*.test-d.ts",
426
+ "**/*.fixtures.ts",
427
+ ".artifacts/**",
428
+ "**/*.config.ts",
429
+ "**/*.config.mts"
430
+ ],
431
+ autoAttachSubprocess: true,
432
+ excludeAfterRemap: true,
433
+ provider: "v8",
434
+ reporter: [
435
+ "text",
436
+ "html",
437
+ "json-summary"
438
+ ],
439
+ reportsDirectory: COVERAGE_DIR
440
+ };
441
+ function shardOf(argv) {
442
+ for (const [index, argument] of argv.entries()) {
443
+ if (argument.startsWith("--shard=")) return argument.slice(8);
444
+ if (argument === "--shard") return argv[index + 1];
445
+ }
446
+ }
447
+ function reporterOutputFiles(argv) {
448
+ const shard = shardOf(argv);
449
+ return shard === void 0 ? REPORTER_OUTPUT_FILES : {
450
+ ...REPORTER_OUTPUT_FILES,
451
+ blob: `${BLOB_DIR}/blob-${shard.replace("/", "-")}.json`
452
+ };
453
+ }
454
+ function rootDefaults() {
455
+ return mergeConfig(projectDefaults(), { test: {
456
+ coverage: COVERAGE,
457
+ outputFile: reporterOutputFiles(process.argv)
727
458
  } });
728
459
  }
460
+ function isInline(project) {
461
+ return isRecord(project) && !("then" in project);
462
+ }
463
+ function rootExcludeBeside(projects, exclude) {
464
+ return projects.every(isInline) ? exclude.filter((glob) => !EXCLUDE.includes(glob)) : exclude;
465
+ }
466
+ function withProjectDefaults(project) {
467
+ return isInline(project) ? onProjectDefaults(project) : project;
468
+ }
469
+ function presetConfig(stated = {}) {
470
+ const merged = mergeConfig(rootDefaults(), stated);
471
+ const root = statedOnce(merged.test ?? {});
472
+ const { projects } = root;
473
+ if (projects === void 0) return {
474
+ ...merged,
475
+ test: root
476
+ };
477
+ const inline = projects.map(withProjectDefaults);
478
+ return {
479
+ ...merged,
480
+ test: {
481
+ ...root,
482
+ ...root.exclude === void 0 ? {} : { exclude: rootExcludeBeside(inline, root.exclude) },
483
+ projects: inline
484
+ }
485
+ };
486
+ }
729
487
  //#endregion
730
- //#region src/runner/projects.ts
731
- /**
732
- * A module test runs beside the module it covers: no DOM, no browser, no
733
- * pipeline. Its suffix is `.test.ts` and its place is OUTSIDE `specs/`, where
734
- * the assembled product is specified in `.spec.ts` — the two exclusions say
735
- * exactly that. `.test.tsx` is the third exclusion because a rendered thing is
736
- * a component test: a project's dependency scan reads every file its globs
737
- * match, and a `.tsx` collected here would be optimised for a page that never
738
- * opens.
739
- */
488
+ //#region src/vitest/unit.project.ts
489
+ /** `.test.tsx` is excluded: a rendered thing is a component test, and the dependency scan would optimise it for a page that never opens. */
740
490
  function unit(options = {}) {
741
491
  const fromRoots = options.roots?.map((root) => `${root.replace(/\/+$/u, "")}/**/*.test.ts`);
742
492
  return onProjectDefaults({ test: {
743
- exclude: options.exclude ?? ["specs/**", "**/*.test.tsx"],
744
- include: options.include ?? fromRoots ?? ["**/*.test.ts"],
745
- ...options.serial === true ? { fileParallelism: false } : {},
746
- name: "unit",
747
- sequence: { groupOrder: 0 },
748
- ...options.timeout === void 0 ? {} : { testTimeout: options.timeout }
493
+ ...projectTest("unit", options, {
494
+ exclude: fromRoots === void 0 ? ["specs/**", "**/*.test.tsx"] : ["**/*.test.tsx"],
495
+ include: fromRoots ?? ["**/*.test.ts"]
496
+ }),
497
+ ...options.isolate === void 0 ? {} : { isolate: options.isolate }
749
498
  } });
750
499
  }
751
500
  //#endregion
752
- export { api, cli, component, defineSpecConfig, integration, jobs, literate, mobile, unit, website };
501
+ //#region src/index.vitest.ts
502
+ /** The shared vitest config: what you pass is merged over the defaults. */
503
+ function defineSpecConfig(config = {}) {
504
+ const { literate: literateOptions, ...userConfig } = config;
505
+ return presetConfig(literateOptions ? mergeConfig({ plugins: [literate(literateOptions)] }, userConfig) : userConfig);
506
+ }
507
+ const BUILDERS = /* @__PURE__ */ new Map([
508
+ ["unit", unit],
509
+ ["lint", lint],
510
+ ["cli", cli],
511
+ ["api", api],
512
+ ["job", job],
513
+ ["integration", integration],
514
+ ["website", website],
515
+ ["mobile", mobile],
516
+ ["component", component]
517
+ ]);
518
+ const anchored = (root, path) => isAbsolute(path) ? path : resolve(root, path);
519
+ const anchoredTarget = (root, target) => typeof target === "string" && target.startsWith(".") ? anchored(root, target) : target;
520
+ function aliasesFrom(root, vite) {
521
+ if (!isRecord(vite) || !isRecord(vite.resolve)) return vite;
522
+ const stated = vite.resolve.alias;
523
+ if (Array.isArray(stated)) {
524
+ const alias = stated.map((entry) => isRecord(entry) ? {
525
+ ...entry,
526
+ replacement: anchoredTarget(root, entry.replacement)
527
+ } : entry);
528
+ return {
529
+ ...vite,
530
+ resolve: {
531
+ ...vite.resolve,
532
+ alias
533
+ }
534
+ };
535
+ }
536
+ if (!isRecord(stated)) return vite;
537
+ const alias = Object.fromEntries(Object.entries(stated).map(([name, target]) => [name, anchoredTarget(root, target)]));
538
+ return {
539
+ ...vite,
540
+ resolve: {
541
+ ...vite.resolve,
542
+ alias
543
+ }
544
+ };
545
+ }
546
+ function refused(found, fix) {
547
+ return new Error(refusal$1({
548
+ fix,
549
+ found,
550
+ read: "docs/start/set-up-a-project.md#the-vitest-config",
551
+ why: "a project is derived from the tree, and a declared one only states how it departs from that, with its reason"
552
+ }));
553
+ }
554
+ function declaredProjects(declared) {
555
+ if (declared === void 0) return /* @__PURE__ */ new Map();
556
+ if (!isRecord(declared)) throw refused("`test.projects` is not a record", "key each project by its specification: `projects: { cli: { timeout: 120_000, reason: ... } }`");
557
+ const projects = /* @__PURE__ */ new Map();
558
+ for (const [specification, project] of Object.entries(declared)) {
559
+ if (!BUILDERS.has(specification)) throw refused(`\`test.projects\` names the specification \`${specification}\``, `key it by one of ${[...BUILDERS.keys()].join(", ")}`);
560
+ const { reason, ...options } = isRecord(project) ? project : {};
561
+ if (typeof reason !== "string" || reason.trim() === "") throw refused(`\`test.projects.${specification}\` departs from its derived project with no reason`, `state why under \`test.projects.${specification}.reason\`, or delete what the tree already derives`);
562
+ projects.set(specification, options);
563
+ }
564
+ return projects;
565
+ }
566
+ async function projectOf(root, specification, { setup, ...options }) {
567
+ const builder = BUILDERS.get(specification);
568
+ if (builder === void 0) return {};
569
+ const componentRoot = anchored(root, typeof options.root === "string" ? options.root : ".");
570
+ const project = await builder({
571
+ ...options,
572
+ ...specification === "component" ? { root: componentRoot } : {},
573
+ vite: aliasesFrom(root, options.vite)
574
+ });
575
+ const setups = Array.isArray(setup) ? setup.filter((path) => typeof path === "string") : [];
576
+ return mergeConfig({ test: { globalSetup: setups.map((path) => anchored(root, path)) } }, project);
577
+ }
578
+ async function derivedFrom(root) {
579
+ const { problem, resolved } = await readSettings(root);
580
+ if (problem !== null) throw new Error(refusal$1({
581
+ fix: "correct jterrazz.config.ts as the finding says, then run vitest again",
582
+ found: `jterrazz.config.ts ${problem}`,
583
+ read: "docs/start/set-up-a-project.md#the-vitest-config",
584
+ why: "the test projects are declared there, and a config read without them would run none of them"
585
+ }));
586
+ const section = resolved.sections[TEST_SECTION];
587
+ const declared = declaredProjects(isRecord(section) ? section.projects : void 0);
588
+ const own = resolved.own[TEST_SECTION]?.projects;
589
+ const derived = derivedProjects(root);
590
+ const specifications = [...BUILDERS.keys()].filter((specification) => derived.has(specification) || isRecord(own) && own[specification] !== void 0);
591
+ return presetConfig(specifications.length === 0 ? {} : { test: { projects: await Promise.all(specifications.map(async (specification) => await projectOf(root, specification, {
592
+ ...derived.get(specification),
593
+ ...declared.get(specification)
594
+ }))) } });
595
+ }
596
+ const vitestConfig = async (configUrl) => await derivedFrom(dirname(fileURLToPath(configUrl)));
597
+ //#endregion
598
+ export { api, cli, component, defineSpecConfig, integration, job, lint, literate, mobile, unit, vitestConfig, website };
599
+
600
+ //# sourceMappingURL=vitest.js.map