@noego/wood 0.6.2 → 0.7.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 (242) hide show
  1. package/README.md +7 -0
  2. package/dist/browser-test-setup.cjs +29 -0
  3. package/dist/browser-test-setup.cjs.map +1 -0
  4. package/dist/browser-test-setup.d.cts +8 -0
  5. package/dist/browser-test-setup.d.ts +8 -0
  6. package/dist/browser-test-setup.js +9 -0
  7. package/dist/browser-test-setup.js.map +1 -0
  8. package/dist/client/global_polyfill.d.cts +1 -0
  9. package/dist/client/global_polyfill.d.ts +1 -0
  10. package/dist/client/index.cjs +7 -0
  11. package/dist/client/index.cjs.map +1 -1
  12. package/dist/client/index.d.cts +7 -4
  13. package/dist/client/index.d.ts +7 -4
  14. package/dist/client/index.js +6 -1
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/codegen/renderer_app_generator.cjs +97 -24
  17. package/dist/codegen/renderer_app_generator.cjs.map +1 -1
  18. package/dist/codegen/renderer_app_generator.js +97 -24
  19. package/dist/codegen/renderer_app_generator.js.map +1 -1
  20. package/dist/codegen/type_generator.cjs +9 -0
  21. package/dist/codegen/type_generator.cjs.map +1 -1
  22. package/dist/codegen/type_generator.js +9 -0
  23. package/dist/codegen/type_generator.js.map +1 -1
  24. package/dist/config/config_parser.cjs +10 -0
  25. package/dist/config/config_parser.cjs.map +1 -1
  26. package/dist/config/config_parser.js +10 -0
  27. package/dist/config/config_parser.js.map +1 -1
  28. package/dist/fetch.cjs +38 -0
  29. package/dist/fetch.cjs.map +1 -0
  30. package/dist/fetch.d.cts +7 -0
  31. package/dist/fetch.d.ts +7 -0
  32. package/dist/fetch.js +12 -0
  33. package/dist/fetch.js.map +1 -0
  34. package/dist/frontend/frontend_page.cjs +3 -3
  35. package/dist/frontend/frontend_page.cjs.map +1 -1
  36. package/dist/frontend/frontend_page.d.cts +1 -1
  37. package/dist/frontend/frontend_page.d.ts +1 -1
  38. package/dist/frontend/frontend_page.js +3 -3
  39. package/dist/frontend/frontend_page.js.map +1 -1
  40. package/dist/frontend/frontend_surface.cjs +31 -17
  41. package/dist/frontend/frontend_surface.cjs.map +1 -1
  42. package/dist/frontend/frontend_surface.d.cts +12 -2
  43. package/dist/frontend/frontend_surface.d.ts +12 -2
  44. package/dist/frontend/frontend_surface.js +37 -17
  45. package/dist/frontend/frontend_surface.js.map +1 -1
  46. package/dist/frontend/index.d.cts +2 -0
  47. package/dist/frontend/index.d.ts +2 -0
  48. package/dist/index.cjs +5 -0
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +25 -0
  51. package/dist/index.d.ts +25 -0
  52. package/dist/index.js +3 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/loader/index.cjs +19 -2
  55. package/dist/loader/index.cjs.map +1 -1
  56. package/dist/loader/index.d.cts +1 -0
  57. package/dist/loader/index.d.ts +1 -0
  58. package/dist/loader/index.js +19 -1
  59. package/dist/loader/index.js.map +1 -1
  60. package/dist/loader/loader_orchestration.cjs +82 -0
  61. package/dist/loader/loader_orchestration.cjs.map +1 -0
  62. package/dist/loader/loader_orchestration.d.cts +66 -0
  63. package/dist/loader/loader_orchestration.d.ts +66 -0
  64. package/dist/loader/loader_orchestration.js +51 -0
  65. package/dist/loader/loader_orchestration.js.map +1 -0
  66. package/dist/loader/loader_runner.cjs +46 -41
  67. package/dist/loader/loader_runner.cjs.map +1 -1
  68. package/dist/loader/loader_runner.d.cts +12 -6
  69. package/dist/loader/loader_runner.d.ts +12 -6
  70. package/dist/loader/loader_runner.js +50 -41
  71. package/dist/loader/loader_runner.js.map +1 -1
  72. package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
  73. package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
  74. package/dist/navigation/index.cjs +15 -2
  75. package/dist/navigation/index.cjs.map +1 -1
  76. package/dist/navigation/index.d.cts +2 -0
  77. package/dist/navigation/index.d.ts +2 -0
  78. package/dist/navigation/index.js +8 -1
  79. package/dist/navigation/index.js.map +1 -1
  80. package/dist/runtime/get_container.cjs +2 -0
  81. package/dist/runtime/get_container.cjs.map +1 -1
  82. package/dist/runtime/get_container.js +2 -0
  83. package/dist/runtime/get_container.js.map +1 -1
  84. package/dist/runtime/index.cjs +5 -0
  85. package/dist/runtime/index.cjs.map +1 -1
  86. package/dist/runtime/index.d.cts +2 -0
  87. package/dist/runtime/index.d.ts +2 -0
  88. package/dist/runtime/index.js +3 -0
  89. package/dist/runtime/index.js.map +1 -1
  90. package/dist/runtime/runtime_capabilities.cjs +35 -0
  91. package/dist/runtime/runtime_capabilities.cjs.map +1 -0
  92. package/dist/runtime/runtime_capabilities.d.cts +6 -0
  93. package/dist/runtime/runtime_capabilities.d.ts +6 -0
  94. package/dist/runtime/runtime_capabilities.js +11 -0
  95. package/dist/runtime/runtime_capabilities.js.map +1 -0
  96. package/dist/runtime/runtime_module.cjs +2 -0
  97. package/dist/runtime/runtime_module.cjs.map +1 -1
  98. package/dist/runtime/runtime_module.js +2 -0
  99. package/dist/runtime/runtime_module.js.map +1 -1
  100. package/dist/test-wood.cjs +150 -0
  101. package/dist/test-wood.cjs.map +1 -0
  102. package/dist/test-wood.d.cts +62 -0
  103. package/dist/test-wood.d.ts +62 -0
  104. package/dist/test-wood.js +115 -0
  105. package/dist/test-wood.js.map +1 -0
  106. package/dist/testing/browser.cjs +92 -163
  107. package/dist/testing/browser.cjs.map +1 -1
  108. package/dist/testing/browser.d.cts +14 -13
  109. package/dist/testing/browser.d.ts +14 -13
  110. package/dist/testing/browser.js +88 -162
  111. package/dist/testing/browser.js.map +1 -1
  112. package/dist/testing/create_controller_harness.d.cts +2 -1
  113. package/dist/testing/create_controller_harness.d.ts +2 -1
  114. package/dist/testing/create_router_bridge.cjs +19 -15
  115. package/dist/testing/create_router_bridge.cjs.map +1 -1
  116. package/dist/testing/create_router_bridge.d.cts +8 -1
  117. package/dist/testing/create_router_bridge.d.ts +8 -1
  118. package/dist/testing/create_router_bridge.js +18 -15
  119. package/dist/testing/create_router_bridge.js.map +1 -1
  120. package/dist/testing/index.cjs +12 -4
  121. package/dist/testing/index.cjs.map +1 -1
  122. package/dist/testing/index.d.cts +11 -5
  123. package/dist/testing/index.d.ts +11 -5
  124. package/dist/testing/index.js +9 -3
  125. package/dist/testing/index.js.map +1 -1
  126. package/dist/testing/native_browser/browser_pool.cjs +31 -0
  127. package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
  128. package/dist/testing/native_browser/browser_pool.d.cts +1 -0
  129. package/dist/testing/native_browser/browser_pool.d.ts +1 -0
  130. package/dist/testing/native_browser/browser_pool.js +6 -0
  131. package/dist/testing/native_browser/browser_pool.js.map +1 -0
  132. package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
  133. package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
  134. package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
  135. package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
  136. package/dist/testing/native_browser/happy_dom_tab.js +73 -0
  137. package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
  138. package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
  139. package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
  140. package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
  141. package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
  142. package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
  143. package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
  144. package/dist/testing/native_browser/host_transport.cjs +89 -0
  145. package/dist/testing/native_browser/host_transport.cjs.map +1 -0
  146. package/dist/testing/native_browser/host_transport.d.cts +44 -0
  147. package/dist/testing/native_browser/host_transport.d.ts +44 -0
  148. package/dist/testing/native_browser/host_transport.js +65 -0
  149. package/dist/testing/native_browser/host_transport.js.map +1 -0
  150. package/dist/testing/native_browser/native_browser.types.cjs +17 -0
  151. package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
  152. package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
  153. package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
  154. package/dist/testing/native_browser/native_browser.types.js +1 -0
  155. package/dist/testing/native_browser/native_browser.types.js.map +1 -0
  156. package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
  157. package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
  158. package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
  159. package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
  160. package/dist/testing/native_browser/native_browser_environment.js +109 -0
  161. package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
  162. package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
  163. package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
  164. package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
  165. package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
  166. package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
  167. package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
  168. package/dist/testing/native_browser/playwright_tab.cjs +124 -0
  169. package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
  170. package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
  171. package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
  172. package/dist/testing/native_browser/playwright_tab.js +100 -0
  173. package/dist/testing/native_browser/playwright_tab.js.map +1 -0
  174. package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
  175. package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
  176. package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
  177. package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
  178. package/dist/testing/native_browser/renderer_compilation.js +115 -0
  179. package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
  180. package/dist/testing/native_browser/renderer_entry.cjs +148 -0
  181. package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
  182. package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
  183. package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
  184. package/dist/testing/native_browser/renderer_entry.js +114 -0
  185. package/dist/testing/native_browser/renderer_entry.js.map +1 -0
  186. package/dist/testing/registration_only_root.cjs +61 -5
  187. package/dist/testing/registration_only_root.cjs.map +1 -1
  188. package/dist/testing/registration_only_root.d.cts +25 -4
  189. package/dist/testing/registration_only_root.d.ts +25 -4
  190. package/dist/testing/registration_only_root.js +60 -5
  191. package/dist/testing/registration_only_root.js.map +1 -1
  192. package/dist/testing/runner_importer.cjs +75 -0
  193. package/dist/testing/runner_importer.cjs.map +1 -0
  194. package/dist/testing/runner_importer.d.cts +8 -0
  195. package/dist/testing/runner_importer.d.ts +8 -0
  196. package/dist/testing/runner_importer.js +41 -0
  197. package/dist/testing/runner_importer.js.map +1 -0
  198. package/dist/testing/test_wood_config.cjs +387 -272
  199. package/dist/testing/test_wood_config.cjs.map +1 -1
  200. package/dist/testing/test_wood_config.d.cts +18 -123
  201. package/dist/testing/test_wood_config.d.ts +18 -123
  202. package/dist/testing/test_wood_config.js +396 -270
  203. package/dist/testing/test_wood_config.js.map +1 -1
  204. package/dist/testing/wood_config_source.cjs +12 -0
  205. package/dist/testing/wood_config_source.cjs.map +1 -1
  206. package/dist/testing/wood_config_source.d.cts +10 -0
  207. package/dist/testing/wood_config_source.d.ts +10 -0
  208. package/dist/testing/wood_config_source.js +12 -0
  209. package/dist/testing/wood_config_source.js.map +1 -1
  210. package/dist/testing/wood_env_renderer.cjs +28 -3
  211. package/dist/testing/wood_env_renderer.cjs.map +1 -1
  212. package/dist/testing/wood_env_renderer.d.cts +12 -1
  213. package/dist/testing/wood_env_renderer.d.ts +12 -1
  214. package/dist/testing/wood_env_renderer.js +28 -3
  215. package/dist/testing/wood_env_renderer.js.map +1 -1
  216. package/dist/testing/wood_lazy_runtime.cjs +109 -0
  217. package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
  218. package/dist/testing/wood_lazy_runtime.d.cts +36 -0
  219. package/dist/testing/wood_lazy_runtime.d.ts +36 -0
  220. package/dist/testing/wood_lazy_runtime.js +84 -0
  221. package/dist/testing/wood_lazy_runtime.js.map +1 -0
  222. package/dist/testing/wood_test_selection.cjs +34 -55
  223. package/dist/testing/wood_test_selection.cjs.map +1 -1
  224. package/dist/testing/wood_test_selection.d.cts +18 -24
  225. package/dist/testing/wood_test_selection.d.ts +18 -24
  226. package/dist/testing/wood_test_selection.js +30 -54
  227. package/dist/testing/wood_test_selection.js.map +1 -1
  228. package/dist/testing-api.cjs +17 -0
  229. package/dist/testing-api.cjs.map +1 -0
  230. package/dist/testing-api.d.cts +18 -0
  231. package/dist/testing-api.d.ts +18 -0
  232. package/dist/testing-api.js +1 -0
  233. package/dist/testing-api.js.map +1 -0
  234. package/dist/types/config.cjs.map +1 -1
  235. package/dist/types/config.d.cts +4 -0
  236. package/dist/types/config.d.ts +4 -0
  237. package/docs/native-browser-testing.md +164 -0
  238. package/package.json +31 -5
  239. package/src/components/NavigationShell.browser.test.ts +10 -10
  240. package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
  241. package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
  242. package/src/components/NavigationShell.svelte +50 -24
@@ -1,5 +1,24 @@
1
+ import { defineModule, LoadAs } from "@noego/ioc";
1
2
  import { createWoodRoot } from "../runtime/get_container.js";
2
3
  import { TestWoodError } from "./test_wood.js";
4
+ const APPLICATION_TEST_MARK = /* @__PURE__ */ Symbol.for("@noego/testing.application.v1");
5
+ function refine(binding, lifetime) {
6
+ if (lifetime === "singleton") binding.singleton();
7
+ else if (lifetime === "scoped") binding.scoped();
8
+ else if (lifetime === "transient") binding.transient();
9
+ }
10
+ function lifetimeOf(options) {
11
+ switch (options?.loadAs) {
12
+ case LoadAs.Singleton:
13
+ return "singleton";
14
+ case LoadAs.Scoped:
15
+ return "scoped";
16
+ case LoadAs.Transient:
17
+ return "transient";
18
+ default:
19
+ return void 0;
20
+ }
21
+ }
3
22
  function errorMessage(error) {
4
23
  const message = error instanceof Error ? error.message : String(error);
5
24
  const nested = error?.failures;
@@ -28,12 +47,24 @@ async function cleanupAll(message, actions) {
28
47
  if (failures.length) throw new TestWoodCleanupError(message, failures);
29
48
  }
30
49
  class RegistrationOnlyRoot {
31
- constructor(hookName) {
50
+ /**
51
+ * @param hookName the app hook that may register on this root
52
+ * @param applicationTest the config path identifying the application test;
53
+ * registered on the ACTUAL root under the shared
54
+ * `@noego/testing.application.v1` marker before any hook runs, so
55
+ * frameworks reading the IoC async-context ABI (e.g. Proper's migration
56
+ * guard) recognize Wood startup/invocation as an application test.
57
+ * Production roots are never marked.
58
+ */
59
+ constructor(hookName, applicationTest) {
32
60
  this.hookName = hookName;
33
61
  this.owners = /* @__PURE__ */ new Set();
34
62
  this.actualRoot = createWoodRoot();
35
63
  this.target = null;
36
64
  this.replayed = false;
65
+ /** Every production registration as a declarative binding (the shared overlay catalog input). */
66
+ this.declarations = [];
67
+ this.actualRoot.registerFunction(APPLICATION_TEST_MARK, () => applicationTest, { owned: false });
37
68
  const registrationOnly = this;
38
69
  this.root = new Proxy(this.actualRoot, {
39
70
  get(_target, property) {
@@ -41,6 +72,8 @@ class RegistrationOnlyRoot {
41
72
  return (classDefinition, options) => {
42
73
  registrationOnly.assertRecording("registerClass");
43
74
  registrationOnly.declare(classDefinition);
75
+ const lifetime = lifetimeOf(options);
76
+ registrationOnly.declarations.push((bind) => refine(bind(classDefinition).toSelf(), lifetime));
44
77
  registrationOnly.actualRoot.registerClass(classDefinition, options);
45
78
  };
46
79
  }
@@ -48,9 +81,10 @@ class RegistrationOnlyRoot {
48
81
  return (token, factory, options) => {
49
82
  registrationOnly.assertRecording("registerFunction");
50
83
  registrationOnly.declare(token);
51
- for (const dependency of options?.param ?? []) {
52
- registrationOnly.declare(dependency);
53
- }
84
+ const dependencies = [...options?.param ?? []];
85
+ for (const dependency of dependencies) registrationOnly.declare(dependency);
86
+ const lifetime = lifetimeOf(options);
87
+ registrationOnly.declarations.push((bind) => refine(bind(token).toFactory(factory, dependencies), lifetime));
54
88
  registrationOnly.actualRoot.registerFunction(token, factory, options);
55
89
  };
56
90
  }
@@ -88,6 +122,25 @@ class RegistrationOnlyRoot {
88
122
  this.declare(injected?.has(index) ? injected.get(index) : design[index]);
89
123
  }
90
124
  }
125
+ /**
126
+ * Declare a token this root binds later (at runtime initialization) as a
127
+ * provider so the shared overlay catalog can resolve replacements for it.
128
+ * The placeholder factory is description only and never resolves.
129
+ */
130
+ declareBinding(token, lifetime) {
131
+ this.declare(token);
132
+ const hookName = this.hookName;
133
+ this.declarations.push((bind) => refine(bind(token).toFactory(() => {
134
+ throw new TestWoodError(`${hookName} declared-only binding ${String(token)} has no production factory`);
135
+ }, []), lifetime));
136
+ }
137
+ /** The recorded production registrations as a declarative module (description only, never built). */
138
+ module() {
139
+ const declarations = [...this.declarations];
140
+ return defineModule(`@noego/wood/testing/${this.hookName}`, (bind) => {
141
+ for (const declaration of declarations) declaration(bind);
142
+ });
143
+ }
91
144
  replay(root) {
92
145
  if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);
93
146
  this.replayed = true;
@@ -105,11 +158,12 @@ class RegistrationOnlyRoot {
105
158
  }
106
159
  }
107
160
  }
108
- async function buildRecordedEnvironment(builder, recorded, owner) {
161
+ async function buildRecordedEnvironment(builder, recorded, owner, borrowedTokens = []) {
109
162
  const partial = { root: recorded.actualRoot };
110
163
  try {
111
164
  const environment = await builder.build({
112
165
  root: recorded.actualRoot,
166
+ borrowedTokens,
113
167
  prepare: (freshRoot) => {
114
168
  partial.root = freshRoot;
115
169
  recorded.replay(freshRoot);
@@ -133,6 +187,7 @@ async function buildRecordedEnvironment(builder, recorded, owner) {
133
187
  }
134
188
  }
135
189
  export {
190
+ APPLICATION_TEST_MARK,
136
191
  RegistrationOnlyRoot,
137
192
  TestWoodCleanupError,
138
193
  buildRecordedEnvironment,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/registration_only_root.ts"],"sourcesContent":["import type { IContainer } from '@noego/ioc';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport type { TestEnvironment, TestIocBuilder } from '@noego/testing';\nimport { TestWoodError } from './test_wood.js';\n\n\nexport interface CleanupFailure {\n readonly owner: string;\n readonly error: unknown;\n}\n\nfunction errorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const nested = (error as { failures?: Array<{ error: unknown }> } | null)?.failures;\n return Array.isArray(nested) ? message + ': ' + nested.map(failure => errorMessage(failure.error)).join('; ') : message;\n}\n\n/**\n * Error raised after every requested owner has been given a chance to clean up.\n * The structured failures are retained for assertions and callers that need to\n * inspect the original errors.\n */\nexport class TestWoodCleanupError extends TestWoodError {\n readonly failures: readonly CleanupFailure[];\n\n constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown) {\n super(\n `${message} (${failures.length} failure(s)):\\n ` +\n failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join('\\n '),\n );\n this.name = 'TestWoodCleanupError';\n this.failures = failures;\n if (cause !== undefined) (this as Error & { cause?: unknown }).cause = cause;\n }\n}\n\n/** Run every cleanup action in order, collecting rather than short-circuiting. */\nexport async function cleanupAll(\n message: string,\n actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>,\n): Promise<void> {\n const failures: CleanupFailure[] = [];\n for (const [owner, action] of actions) {\n try {\n await action();\n } catch (error) {\n failures.push({ owner, error });\n }\n }\n if (failures.length) throw new TestWoodCleanupError(message, failures);\n}\n\n/**\n * A registration-only view of the real root owned by this environment.\n *\n * Registrations land immediately on that root; shared @noego/testing applies\n * scenario overlays to it without replay. Resolution is unavailable until it has been\n * built and the test overlays and method decorator are installed.\n */\nexport class RegistrationOnlyRoot {\n readonly owners = new Set<unknown>();\n readonly root: IContainer;\n\n readonly actualRoot = createWoodRoot();\n private target: IContainer | null = null;\n private replayed = false;\n\n constructor(private readonly hookName: string) {\n const registrationOnly = this;\n this.root = new Proxy(this.actualRoot, {\n get(_target, property) {\n if (property === 'registerClass') {\n return (classDefinition: new (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerClass');\n registrationOnly.declare(classDefinition);\n registrationOnly.actualRoot.registerClass(classDefinition, options as never);\n };\n }\n if (property === 'registerFunction') {\n return (token: unknown, factory: (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerFunction');\n registrationOnly.declare(token);\n for (const dependency of (options as { param?: unknown[] } | undefined)?.param ?? []) {\n registrationOnly.declare(dependency);\n }\n registrationOnly.actualRoot.registerFunction(token, factory, options as never);\n };\n }\n if (property === 'isRegistered') {\n return (token: unknown): boolean =>\n registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);\n }\n if (property === 'isDisposed') {\n return (): boolean => registrationOnly.target?.isDisposed() ?? false;\n }\n\n const live = registrationOnly.target;\n if (live) {\n const value = Reflect.get(live as object, property, live);\n return typeof value === 'function' ? value.bind(live) : value;\n }\n\n return (..._args: unknown[]): never => {\n const method = String(property);\n throw new TestWoodError(\n `${registrationOnly.hookName} may only register dependencies before test overlays; ` +\n `${method}() attempted eager resolution or root mutation. Move resolution to start(), ` +\n 'a page lifecycle, or the built test environment.',\n );\n };\n },\n });\n }\n\n declare(token: unknown): void {\n if (this.owners.has(token)) return;\n this.owners.add(token);\n if (typeof token !== 'function') return;\n const injected = (Reflect as any).getMetadata?.(\n Symbol.for('ioc:inject:tokens'),\n token,\n ) as Map<number, unknown> | undefined;\n const design = (Reflect as any).getMetadata?.('design:paramtypes', token) ?? [];\n const indices = new Set<number>([...design.keys(), ...(injected?.keys() ?? [])]);\n for (const index of indices) {\n this.declare(injected?.has(index) ? injected.get(index) : design[index]);\n }\n }\n\n replay(root: IContainer): void {\n if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);\n this.replayed = true;\n if (root !== this.actualRoot) throw new TestWoodError('testWood requires @noego/testing with build({ root }) support; update the shared testing package.');\n }\n\n bind(root: IContainer): void {\n if (!this.replayed) throw new TestWoodError(`${this.hookName} registrations must be replayed before binding`);\n this.target = root;\n }\n\n private assertRecording(method: string): void {\n if (this.replayed) {\n throw new TestWoodError(\n `${this.hookName} cannot call ${method}() after test overlays have been installed`,\n );\n }\n }\n}\n\n/**\n * Materialize shared @noego/testing overlays on the same production root,\n * disposing the partially built root if materialization fails.\n */\nexport async function buildRecordedEnvironment(\n builder: TestIocBuilder,\n recorded: RegistrationOnlyRoot,\n owner: string,\n): Promise<TestEnvironment> {\n const partial: { root?: IContainer } = { root: recorded.actualRoot };\n try {\n const environment = await builder.build({\n root: recorded.actualRoot,\n prepare: (freshRoot) => {\n partial.root = freshRoot;\n recorded.replay(freshRoot);\n },\n });\n recorded.bind(environment.root);\n return environment;\n } catch (error) {\n const failures: CleanupFailure[] = [];\n if (partial.root) {\n try {\n await partial.root.dispose();\n } catch (cleanupError) {\n failures.push({ owner: `${owner} root rollback`, error: cleanupError });\n }\n }\n if (failures.length) {\n throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/testing/registration_only_root.ts"],"sourcesContent":["import type { ApplicationModule, BindFunction, BindingRefinement, IContainer, Lifetime } from '@noego/ioc';\nimport { defineModule, LoadAs } from '@noego/ioc';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport type { TestEnvironment, TestIocBuilder } from '@noego/testing';\nimport { TestWoodError } from './test_wood.js';\n\ntype ContainerOptions = { loadAs?: LoadAs; param?: unknown[] };\n\n/** Shared application-test marker (same symbol `@noego/app` registers). */\nexport const APPLICATION_TEST_MARK = Symbol.for('@noego/testing.application.v1');\n\nfunction refine(binding: BindingRefinement, lifetime: Lifetime | undefined): void {\n if (lifetime === 'singleton') binding.singleton();\n else if (lifetime === 'scoped') binding.scoped();\n else if (lifetime === 'transient') binding.transient();\n}\n\nfunction lifetimeOf(options: Partial<ContainerOptions> | undefined): Lifetime | undefined {\n switch (options?.loadAs) {\n case LoadAs.Singleton: return 'singleton';\n case LoadAs.Scoped: return 'scoped';\n case LoadAs.Transient: return 'transient';\n default: return undefined;\n }\n}\n\n\nexport interface CleanupFailure {\n readonly owner: string;\n readonly error: unknown;\n}\n\nfunction errorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const nested = (error as { failures?: Array<{ error: unknown }> } | null)?.failures;\n return Array.isArray(nested) ? message + ': ' + nested.map(failure => errorMessage(failure.error)).join('; ') : message;\n}\n\n/**\n * Error raised after every requested owner has been given a chance to clean up.\n * The structured failures are retained for assertions and callers that need to\n * inspect the original errors.\n */\nexport class TestWoodCleanupError extends TestWoodError {\n readonly failures: readonly CleanupFailure[];\n\n constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown) {\n super(\n `${message} (${failures.length} failure(s)):\\n ` +\n failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join('\\n '),\n );\n this.name = 'TestWoodCleanupError';\n this.failures = failures;\n if (cause !== undefined) (this as Error & { cause?: unknown }).cause = cause;\n }\n}\n\n/** Run every cleanup action in order, collecting rather than short-circuiting. */\nexport async function cleanupAll(\n message: string,\n actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>,\n): Promise<void> {\n const failures: CleanupFailure[] = [];\n for (const [owner, action] of actions) {\n try {\n await action();\n } catch (error) {\n failures.push({ owner, error });\n }\n }\n if (failures.length) throw new TestWoodCleanupError(message, failures);\n}\n\n/**\n * A registration-only view of the real root owned by this environment.\n *\n * Registrations land immediately on that root; shared @noego/testing applies\n * scenario overlays to it without replay. Resolution is unavailable until it has been\n * built and the test overlays and method decorator are installed.\n */\nexport class RegistrationOnlyRoot {\n readonly owners = new Set<unknown>();\n readonly root: IContainer;\n\n readonly actualRoot = createWoodRoot();\n private target: IContainer | null = null;\n private replayed = false;\n /** Every production registration as a declarative binding (the shared overlay catalog input). */\n private readonly declarations: Array<(bind: BindFunction) => void> = [];\n\n /**\n * @param hookName the app hook that may register on this root\n * @param applicationTest the config path identifying the application test;\n * registered on the ACTUAL root under the shared\n * `@noego/testing.application.v1` marker before any hook runs, so\n * frameworks reading the IoC async-context ABI (e.g. Proper's migration\n * guard) recognize Wood startup/invocation as an application test.\n * Production roots are never marked.\n */\n constructor(private readonly hookName: string, applicationTest: string) {\n this.actualRoot.registerFunction(APPLICATION_TEST_MARK, () => applicationTest, { owned: false });\n const registrationOnly = this;\n this.root = new Proxy(this.actualRoot, {\n get(_target, property) {\n if (property === 'registerClass') {\n return (classDefinition: new (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerClass');\n registrationOnly.declare(classDefinition);\n const lifetime = lifetimeOf(options as Partial<ContainerOptions> | undefined);\n registrationOnly.declarations.push((bind) => refine(bind(classDefinition).toSelf(), lifetime));\n registrationOnly.actualRoot.registerClass(classDefinition, options as never);\n };\n }\n if (property === 'registerFunction') {\n return (token: unknown, factory: (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerFunction');\n registrationOnly.declare(token);\n const dependencies = [...((options as { param?: unknown[] } | undefined)?.param ?? [])];\n for (const dependency of dependencies) registrationOnly.declare(dependency);\n const lifetime = lifetimeOf(options as Partial<ContainerOptions> | undefined);\n registrationOnly.declarations.push((bind) => refine(bind(token).toFactory(factory, dependencies), lifetime));\n registrationOnly.actualRoot.registerFunction(token, factory, options as never);\n };\n }\n if (property === 'isRegistered') {\n return (token: unknown): boolean =>\n registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);\n }\n if (property === 'isDisposed') {\n return (): boolean => registrationOnly.target?.isDisposed() ?? false;\n }\n\n const live = registrationOnly.target;\n if (live) {\n const value = Reflect.get(live as object, property, live);\n return typeof value === 'function' ? value.bind(live) : value;\n }\n\n return (..._args: unknown[]): never => {\n const method = String(property);\n throw new TestWoodError(\n `${registrationOnly.hookName} may only register dependencies before test overlays; ` +\n `${method}() attempted eager resolution or root mutation. Move resolution to start(), ` +\n 'a page lifecycle, or the built test environment.',\n );\n };\n },\n });\n }\n\n declare(token: unknown): void {\n if (this.owners.has(token)) return;\n this.owners.add(token);\n if (typeof token !== 'function') return;\n const injected = (Reflect as any).getMetadata?.(\n Symbol.for('ioc:inject:tokens'),\n token,\n ) as Map<number, unknown> | undefined;\n const design = (Reflect as any).getMetadata?.('design:paramtypes', token) ?? [];\n const indices = new Set<number>([...design.keys(), ...(injected?.keys() ?? [])]);\n for (const index of indices) {\n this.declare(injected?.has(index) ? injected.get(index) : design[index]);\n }\n }\n\n /**\n * Declare a token this root binds later (at runtime initialization) as a\n * provider so the shared overlay catalog can resolve replacements for it.\n * The placeholder factory is description only and never resolves.\n */\n declareBinding(token: unknown, lifetime: Lifetime): void {\n this.declare(token);\n const hookName = this.hookName;\n this.declarations.push((bind) => refine(bind(token).toFactory(() => {\n throw new TestWoodError(`${hookName} declared-only binding ${String(token)} has no production factory`);\n }, []), lifetime));\n }\n\n /** The recorded production registrations as a declarative module (description only, never built). */\n module(): ApplicationModule {\n const declarations = [...this.declarations];\n return defineModule(`@noego/wood/testing/${this.hookName}`, (bind) => {\n for (const declaration of declarations) declaration(bind);\n });\n }\n\n replay(root: IContainer): void {\n if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);\n this.replayed = true;\n if (root !== this.actualRoot) throw new TestWoodError('testWood requires @noego/testing with build({ root }) support; update the shared testing package.');\n }\n\n bind(root: IContainer): void {\n if (!this.replayed) throw new TestWoodError(`${this.hookName} registrations must be replayed before binding`);\n this.target = root;\n }\n\n private assertRecording(method: string): void {\n if (this.replayed) {\n throw new TestWoodError(\n `${this.hookName} cannot call ${method}() after test overlays have been installed`,\n );\n }\n }\n}\n\n/**\n * Materialize shared @noego/testing overlays on the same production root,\n * disposing the partially built root if materialization fails.\n */\nexport async function buildRecordedEnvironment(\n builder: TestIocBuilder,\n recorded: RegistrationOnlyRoot,\n owner: string,\n borrowedTokens: readonly unknown[] = [],\n): Promise<TestEnvironment> {\n const partial: { root?: IContainer } = { root: recorded.actualRoot };\n try {\n const environment = await builder.build({\n root: recorded.actualRoot,\n borrowedTokens,\n prepare: (freshRoot) => {\n partial.root = freshRoot;\n recorded.replay(freshRoot);\n },\n });\n recorded.bind(environment.root);\n return environment;\n } catch (error) {\n const failures: CleanupFailure[] = [];\n if (partial.root) {\n try {\n await partial.root.dispose();\n } catch (cleanupError) {\n failures.push({ owner: `${owner} root rollback`, error: cleanupError });\n }\n }\n if (failures.length) {\n throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var runner_importer_exports = {};
30
+ __export(runner_importer_exports, {
31
+ vitestModuleImporter: () => vitestModuleImporter
32
+ });
33
+ module.exports = __toCommonJS(runner_importer_exports);
34
+ var import_node_path = __toESM(require("node:path"), 1);
35
+ var import_node_module = require("node:module");
36
+ var import_node_fs = require("node:fs");
37
+ var import_node_url = require("node:url");
38
+ var import_test_wood = require("./test_wood.cjs");
39
+ async function vitestModuleImporter(configPath) {
40
+ const roots = [.../* @__PURE__ */ new Set([process.cwd(), import_node_path.default.dirname(import_node_path.default.resolve(configPath))])];
41
+ let entry;
42
+ let version;
43
+ for (const root of roots) {
44
+ const require2 = (0, import_node_module.createRequire)(import_node_path.default.join(root, "package.json"));
45
+ try {
46
+ const manifest = require2.resolve("vitest/package.json");
47
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(manifest, "utf8"));
48
+ version = pkg.version;
49
+ const exported = pkg.exports?.["."]?.import;
50
+ const imported = typeof exported === "string" ? exported : exported?.default;
51
+ if (typeof imported !== "string") throw new Error("Vitest has no declared ESM entry");
52
+ entry = import_node_path.default.resolve(import_node_path.default.dirname(manifest), imported);
53
+ break;
54
+ } catch {
55
+ }
56
+ }
57
+ if (!entry || !version) throw new import_test_wood.TestWoodError("testWood under Vitest requires a project-local Vitest installation; none was found");
58
+ if (![2, 3, 4].includes(Number(version.split(".")[0]))) {
59
+ throw new import_test_wood.TestWoodError(`testWood under Vitest: unsupported Vitest version ${version}; expected a qualified 2.x, 3.x or 4.x project`);
60
+ }
61
+ const runner = await import(
62
+ /* @vite-ignore */
63
+ (0, import_node_url.pathToFileURL)(entry).href
64
+ );
65
+ if (typeof runner.vi?.importActual !== "function") {
66
+ throw new import_test_wood.TestWoodError(`testWood under Vitest: Vitest ${version} has no public importActual module API`);
67
+ }
68
+ const importActual = runner.vi.importActual.bind(runner.vi);
69
+ return (absolutePath) => importActual(absolutePath);
70
+ }
71
+ // Annotate the CommonJS export names for ESM import in node:
72
+ 0 && (module.exports = {
73
+ vitestModuleImporter
74
+ });
75
+ //# sourceMappingURL=runner_importer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/runner_importer.ts"],"sourcesContent":["/**\n * Project-local Vitest module identity for `testWood(configPath)` builds.\n *\n * Under Vitest, hook/controller/loader modules are imported through the\n * runner's public module API (`vi.importActual`) rather than a raw dynamic\n * import, so the framework's load of a source file is the SAME module the\n * test file imported (one class identity, one token identity). The closure\n * belongs to one build and one project; nothing process-global is touched.\n */\nimport path from 'node:path';\nimport { createRequire } from 'node:module';\nimport { readFileSync } from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport type { ModuleImporter } from './wood_config_source.cjs';\nimport { TestWoodError } from './test_wood.cjs';\n\nexport async function vitestModuleImporter(configPath: string): Promise<ModuleImporter> {\n const roots = [...new Set([process.cwd(), path.dirname(path.resolve(configPath))])];\n let entry: string | undefined;\n let version: string | undefined;\n for (const root of roots) {\n const require = createRequire(path.join(root, 'package.json'));\n try {\n const manifest = require.resolve('vitest/package.json');\n const pkg = JSON.parse(readFileSync(manifest, 'utf8')) as { version: string; exports?: Record<string, unknown> };\n version = pkg.version;\n const exported = (pkg.exports?.['.'] as { import?: string | { default?: string } } | undefined)?.import;\n const imported = typeof exported === 'string' ? exported : exported?.default;\n if (typeof imported !== 'string') throw new Error('Vitest has no declared ESM entry');\n entry = path.resolve(path.dirname(manifest), imported);\n break;\n } catch { /* try the source config's project before reporting unavailable */ }\n }\n if (!entry || !version) throw new TestWoodError('testWood under Vitest requires a project-local Vitest installation; none was found');\n if (![2, 3, 4].includes(Number(version.split('.')[0]))) {\n throw new TestWoodError(`testWood under Vitest: unsupported Vitest version ${version}; expected a qualified 2.x, 3.x or 4.x project`);\n }\n const runner = await import(/* @vite-ignore */ pathToFileURL(entry).href) as {\n vi?: { importActual?: (path: string) => Promise<unknown> };\n };\n if (typeof runner.vi?.importActual !== 'function') {\n throw new TestWoodError(`testWood under Vitest: Vitest ${version} has no public importActual module API`);\n }\n const importActual = runner.vi.importActual.bind(runner.vi);\n return (absolutePath) => importActual(absolutePath);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,uBAAiB;AACjB,yBAA8B;AAC9B,qBAA6B;AAC7B,sBAA8B;AAE9B,uBAA8B;AAE9B,eAAsB,qBAAqB,YAA6C;AACtF,QAAM,QAAQ,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,IAAI,GAAG,iBAAAA,QAAK,QAAQ,iBAAAA,QAAK,QAAQ,UAAU,CAAC,CAAC,CAAC,CAAC;AAClF,MAAI;AACJ,MAAI;AACJ,aAAW,QAAQ,OAAO;AACxB,UAAMC,eAAU,kCAAc,iBAAAD,QAAK,KAAK,MAAM,cAAc,CAAC;AAC7D,QAAI;AACF,YAAM,WAAWC,SAAQ,QAAQ,qBAAqB;AACtD,YAAM,MAAM,KAAK,UAAM,6BAAa,UAAU,MAAM,CAAC;AACrD,gBAAU,IAAI;AACd,YAAM,WAAY,IAAI,UAAU,GAAG,GAA8D;AACjG,YAAM,WAAW,OAAO,aAAa,WAAW,WAAW,UAAU;AACrE,UAAI,OAAO,aAAa,SAAU,OAAM,IAAI,MAAM,kCAAkC;AACpF,cAAQ,iBAAAD,QAAK,QAAQ,iBAAAA,QAAK,QAAQ,QAAQ,GAAG,QAAQ;AACrD;AAAA,IACF,QAAQ;AAAA,IAAqE;AAAA,EAC/E;AACA,MAAI,CAAC,SAAS,CAAC,QAAS,OAAM,IAAI,+BAAc,oFAAoF;AACpI,MAAI,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,SAAS,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG;AACtD,UAAM,IAAI,+BAAc,qDAAqD,OAAO,gDAAgD;AAAA,EACtI;AACA,QAAM,SAAS,MAAM;AAAA;AAAA,QAA0B,+BAAc,KAAK,EAAE;AAAA;AAGpE,MAAI,OAAO,OAAO,IAAI,iBAAiB,YAAY;AACjD,UAAM,IAAI,+BAAc,iCAAiC,OAAO,wCAAwC;AAAA,EAC1G;AACA,QAAM,eAAe,OAAO,GAAG,aAAa,KAAK,OAAO,EAAE;AAC1D,SAAO,CAAC,iBAAiB,aAAa,YAAY;AACpD;","names":["path","require"]}
@@ -0,0 +1,8 @@
1
+ import { ModuleImporter } from './wood_config_source.cjs';
2
+ import '../types/config.cjs';
3
+ import '../types/operations.cjs';
4
+ import '../types/views.cjs';
5
+
6
+ declare function vitestModuleImporter(configPath: string): Promise<ModuleImporter>;
7
+
8
+ export { vitestModuleImporter };
@@ -0,0 +1,8 @@
1
+ import { ModuleImporter } from './wood_config_source.js';
2
+ import '../types/config.js';
3
+ import '../types/operations.js';
4
+ import '../types/views.js';
5
+
6
+ declare function vitestModuleImporter(configPath: string): Promise<ModuleImporter>;
7
+
8
+ export { vitestModuleImporter };
@@ -0,0 +1,41 @@
1
+ import path from "node:path";
2
+ import { createRequire } from "node:module";
3
+ import { readFileSync } from "node:fs";
4
+ import { pathToFileURL } from "node:url";
5
+ import { TestWoodError } from "./test_wood.js";
6
+ async function vitestModuleImporter(configPath) {
7
+ const roots = [.../* @__PURE__ */ new Set([process.cwd(), path.dirname(path.resolve(configPath))])];
8
+ let entry;
9
+ let version;
10
+ for (const root of roots) {
11
+ const require2 = createRequire(path.join(root, "package.json"));
12
+ try {
13
+ const manifest = require2.resolve("vitest/package.json");
14
+ const pkg = JSON.parse(readFileSync(manifest, "utf8"));
15
+ version = pkg.version;
16
+ const exported = pkg.exports?.["."]?.import;
17
+ const imported = typeof exported === "string" ? exported : exported?.default;
18
+ if (typeof imported !== "string") throw new Error("Vitest has no declared ESM entry");
19
+ entry = path.resolve(path.dirname(manifest), imported);
20
+ break;
21
+ } catch {
22
+ }
23
+ }
24
+ if (!entry || !version) throw new TestWoodError("testWood under Vitest requires a project-local Vitest installation; none was found");
25
+ if (![2, 3, 4].includes(Number(version.split(".")[0]))) {
26
+ throw new TestWoodError(`testWood under Vitest: unsupported Vitest version ${version}; expected a qualified 2.x, 3.x or 4.x project`);
27
+ }
28
+ const runner = await import(
29
+ /* @vite-ignore */
30
+ pathToFileURL(entry).href
31
+ );
32
+ if (typeof runner.vi?.importActual !== "function") {
33
+ throw new TestWoodError(`testWood under Vitest: Vitest ${version} has no public importActual module API`);
34
+ }
35
+ const importActual = runner.vi.importActual.bind(runner.vi);
36
+ return (absolutePath) => importActual(absolutePath);
37
+ }
38
+ export {
39
+ vitestModuleImporter
40
+ };
41
+ //# sourceMappingURL=runner_importer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/runner_importer.ts"],"sourcesContent":["/**\n * Project-local Vitest module identity for `testWood(configPath)` builds.\n *\n * Under Vitest, hook/controller/loader modules are imported through the\n * runner's public module API (`vi.importActual`) rather than a raw dynamic\n * import, so the framework's load of a source file is the SAME module the\n * test file imported (one class identity, one token identity). The closure\n * belongs to one build and one project; nothing process-global is touched.\n */\nimport path from 'node:path';\nimport { createRequire } from 'node:module';\nimport { readFileSync } from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport type { ModuleImporter } from './wood_config_source.js';\nimport { TestWoodError } from './test_wood.js';\n\nexport async function vitestModuleImporter(configPath: string): Promise<ModuleImporter> {\n const roots = [...new Set([process.cwd(), path.dirname(path.resolve(configPath))])];\n let entry: string | undefined;\n let version: string | undefined;\n for (const root of roots) {\n const require = createRequire(path.join(root, 'package.json'));\n try {\n const manifest = require.resolve('vitest/package.json');\n const pkg = JSON.parse(readFileSync(manifest, 'utf8')) as { version: string; exports?: Record<string, unknown> };\n version = pkg.version;\n const exported = (pkg.exports?.['.'] as { import?: string | { default?: string } } | undefined)?.import;\n const imported = typeof exported === 'string' ? exported : exported?.default;\n if (typeof imported !== 'string') throw new Error('Vitest has no declared ESM entry');\n entry = path.resolve(path.dirname(manifest), imported);\n break;\n } catch { /* try the source config's project before reporting unavailable */ }\n }\n if (!entry || !version) throw new TestWoodError('testWood under Vitest requires a project-local Vitest installation; none was found');\n if (![2, 3, 4].includes(Number(version.split('.')[0]))) {\n throw new TestWoodError(`testWood under Vitest: unsupported Vitest version ${version}; expected a qualified 2.x, 3.x or 4.x project`);\n }\n const runner = await import(/* @vite-ignore */ pathToFileURL(entry).href) as {\n vi?: { importActual?: (path: string) => Promise<unknown> };\n };\n if (typeof runner.vi?.importActual !== 'function') {\n throw new TestWoodError(`testWood under Vitest: Vitest ${version} has no public importActual module API`);\n }\n const importActual = runner.vi.importActual.bind(runner.vi);\n return (absolutePath) => importActual(absolutePath);\n}\n"],"mappings":"AASA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAE9B,SAAS,qBAAqB;AAE9B,eAAsB,qBAAqB,YAA6C;AACtF,QAAM,QAAQ,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,QAAQ,KAAK,QAAQ,UAAU,CAAC,CAAC,CAAC,CAAC;AAClF,MAAI;AACJ,MAAI;AACJ,aAAW,QAAQ,OAAO;AACxB,UAAMA,WAAU,cAAc,KAAK,KAAK,MAAM,cAAc,CAAC;AAC7D,QAAI;AACF,YAAM,WAAWA,SAAQ,QAAQ,qBAAqB;AACtD,YAAM,MAAM,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AACrD,gBAAU,IAAI;AACd,YAAM,WAAY,IAAI,UAAU,GAAG,GAA8D;AACjG,YAAM,WAAW,OAAO,aAAa,WAAW,WAAW,UAAU;AACrE,UAAI,OAAO,aAAa,SAAU,OAAM,IAAI,MAAM,kCAAkC;AACpF,cAAQ,KAAK,QAAQ,KAAK,QAAQ,QAAQ,GAAG,QAAQ;AACrD;AAAA,IACF,QAAQ;AAAA,IAAqE;AAAA,EAC/E;AACA,MAAI,CAAC,SAAS,CAAC,QAAS,OAAM,IAAI,cAAc,oFAAoF;AACpI,MAAI,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,SAAS,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG;AACtD,UAAM,IAAI,cAAc,qDAAqD,OAAO,gDAAgD;AAAA,EACtI;AACA,QAAM,SAAS,MAAM;AAAA;AAAA,IAA0B,cAAc,KAAK,EAAE;AAAA;AAGpE,MAAI,OAAO,OAAO,IAAI,iBAAiB,YAAY;AACjD,UAAM,IAAI,cAAc,iCAAiC,OAAO,wCAAwC;AAAA,EAC1G;AACA,QAAM,eAAe,OAAO,GAAG,aAAa,KAAK,OAAO,EAAE;AAC1D,SAAO,CAAC,iBAAiB,aAAa,YAAY;AACpD;","names":["require"]}