@testspectra/cli 1.0.70 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +36 -270
  3. package/bin/.testspectra-runner.hash +1 -0
  4. package/bin/spectra.js +1 -1
  5. package/bin/testspectra-runner +0 -0
  6. package/demo-app/assets/index-DPfv5_EF.js +116 -0
  7. package/demo-app/assets/index-gIwu2EMV.css +1 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
  11. package/dist/commands/__tests__/cloud-run.test.js +93 -0
  12. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  13. package/dist/commands/__tests__/doctor.test.js +39 -0
  14. package/dist/commands/__tests__/init.test.d.ts +1 -0
  15. package/dist/commands/__tests__/init.test.js +148 -0
  16. package/dist/commands/__tests__/license.test.d.ts +1 -0
  17. package/dist/commands/__tests__/license.test.js +13 -0
  18. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  19. package/dist/commands/__tests__/refactor.test.js +64 -0
  20. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run-e2e.test.js +181 -0
  22. package/dist/commands/__tests__/run.test.d.ts +1 -0
  23. package/dist/commands/__tests__/run.test.js +197 -0
  24. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  25. package/dist/commands/__tests__/watch.test.js +155 -0
  26. package/dist/commands/add.js +86 -85
  27. package/dist/commands/cloud-run.d.ts +11 -0
  28. package/dist/commands/cloud-run.js +118 -0
  29. package/dist/commands/demo.js +7 -7
  30. package/dist/commands/devices.d.ts +14 -2
  31. package/dist/commands/devices.js +44 -34
  32. package/dist/commands/doctor.d.ts +3 -1
  33. package/dist/commands/doctor.js +43 -264
  34. package/dist/commands/init.d.ts +1 -1
  35. package/dist/commands/init.js +206 -196
  36. package/dist/commands/install.d.ts +4 -0
  37. package/dist/commands/install.js +59 -0
  38. package/dist/commands/license.d.ts +4 -0
  39. package/dist/commands/license.js +30 -0
  40. package/dist/commands/refactor.d.ts +8 -0
  41. package/dist/commands/refactor.js +78 -0
  42. package/dist/commands/run.d.ts +22 -1
  43. package/dist/commands/run.js +252 -109
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +14 -0
  46. package/dist/commands/update.js +39 -43
  47. package/dist/commands/watch.d.ts +6 -0
  48. package/dist/commands/watch.js +47 -43
  49. package/dist/config/loader.d.ts +1 -1
  50. package/dist/config/loader.js +40 -25
  51. package/dist/config/schema.d.ts +117 -49
  52. package/dist/config/schema.js +41 -34
  53. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  54. package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
  55. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  56. package/dist/generator/__tests__/type-generator.test.js +386 -0
  57. package/dist/generator/index.d.ts +2 -0
  58. package/dist/generator/index.js +2 -0
  59. package/dist/generator/tsconfig-generator.d.ts +27 -0
  60. package/dist/generator/tsconfig-generator.js +391 -0
  61. package/dist/generator/type-generator.d.ts +36 -0
  62. package/dist/generator/type-generator.js +644 -0
  63. package/dist/index.d.ts +9 -5
  64. package/dist/index.js +124 -51
  65. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  66. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  67. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  68. package/dist/lifecycle/hook-discovery.js +60 -0
  69. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  70. package/dist/lifecycle/hook-dispatcher.js +190 -0
  71. package/dist/lifecycle/index.d.ts +3 -0
  72. package/dist/lifecycle/index.js +3 -0
  73. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  74. package/dist/lifecycle/parallel-grouping.js +151 -0
  75. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  76. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  77. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  78. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  79. package/dist/mobile/device-pool.d.ts +23 -0
  80. package/dist/mobile/device-pool.js +1 -0
  81. package/dist/mobile/index.d.ts +1 -0
  82. package/dist/mobile/index.js +1 -0
  83. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  84. package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
  85. package/dist/refactor/index.d.ts +1 -0
  86. package/dist/refactor/index.js +1 -0
  87. package/dist/refactor/refactor-engine.d.ts +25 -0
  88. package/dist/refactor/refactor-engine.js +255 -0
  89. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  90. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  91. package/dist/reporter/semantic-formatter.d.ts +6 -0
  92. package/dist/reporter/semantic-formatter.js +191 -0
  93. package/dist/reporter/summary-view.d.ts +12 -0
  94. package/dist/reporter/summary-view.js +19 -0
  95. package/dist/reporter/terminal-stream.d.ts +32 -0
  96. package/dist/reporter/terminal-stream.js +82 -0
  97. package/dist/reporter/types.d.ts +2 -0
  98. package/dist/reporter/types.js +1 -0
  99. package/dist/runner/bridge.d.ts +10 -5
  100. package/dist/runner/bridge.js +69 -44
  101. package/dist/runner/reporter.d.ts +30 -2
  102. package/dist/runner/reporter.js +419 -13
  103. package/dist/server/demo.js +43 -44
  104. package/dist/types/generator.d.ts +1 -17
  105. package/dist/types/generator.js +1 -443
  106. package/dist/utils/demo-server.js +38 -35
  107. package/package.json +20 -12
  108. package/templates/default/CLAUDE.md +7 -5
  109. package/templates/default/fixtures/apiData.json +24 -0
  110. package/templates/default/package.json +13 -4
  111. package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
  112. package/templates/default/page-objects/MatchersPage/web.ts +14 -0
  113. package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
  114. package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
  115. package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
  116. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
  117. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  118. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  119. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
  120. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  121. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  122. package/templates/default/specs/ApiInterception/suite.md +9 -0
  123. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  124. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  125. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
  126. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  127. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  128. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  129. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  130. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  131. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  132. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  133. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  134. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  135. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  136. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  137. package/templates/default/spectra.config.ts +21 -14
  138. package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
  139. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
  140. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  141. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
  142. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  143. package/templates/default/tsconfig.json +3 -1
  144. package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
  145. package/templates/nx/CLAUDE.md +2 -2
  146. package/templates/nx/package.json +7 -5
  147. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
  148. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  149. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  150. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
  151. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  152. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  153. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  154. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  155. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  156. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  157. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  158. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  159. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  160. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  161. package/templates/nx/pnpm-lock.yaml +6663 -0
  162. package/templates/nx/pnpm-workspace.yaml +2 -1
  163. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  164. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
  165. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
  166. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
  167. package/templates/nx/shared/testing/project.json +19 -0
  168. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  169. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
  170. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  171. package/templates/nx/spectra.config.ts +19 -12
  172. package/templates/nx/tsconfig.json +3 -1
  173. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  174. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  175. package/dist/index.cjs +0 -101
  176. package/dist/plugin.cjs +0 -575
  177. package/dist/plugin.d.ts +0 -16
  178. package/dist/plugin.js +0 -184
  179. package/dist/step/index.d.ts +0 -6
  180. package/dist/step/index.js +0 -6
  181. package/dist/step/matchers.d.ts +0 -14
  182. package/dist/step/matchers.js +0 -114
  183. package/dist/step/proto.d.ts +0 -46
  184. package/dist/step/proto.js +0 -114
  185. package/dist/step/runner/collection.d.ts +0 -13
  186. package/dist/step/runner/collection.js +0 -51
  187. package/dist/step/runner/single.d.ts +0 -21
  188. package/dist/step/runner/single.js +0 -138
  189. package/dist/step/spectra.d.ts +0 -34
  190. package/dist/step/spectra.js +0 -156
  191. package/dist/step/types.d.ts +0 -32
  192. package/dist/step/types.js +0 -5
  193. package/templates/default/tsconfig.spectra.android.json +0 -46
  194. package/templates/default/tsconfig.spectra.ios.json +0 -46
  195. package/templates/default/tsconfig.spectra.json +0 -22
  196. package/templates/default/tsconfig.spectra.web.json +0 -53
  197. package/templates/nx/fixtures/playgroundData.json +0 -7
  198. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  199. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  200. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  201. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  202. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  203. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  204. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  205. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  206. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  207. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  208. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  209. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  210. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  211. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  212. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  213. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  214. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  215. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  216. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  217. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  218. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  219. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  220. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  221. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  222. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  223. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  224. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  225. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  226. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  227. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  228. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  229. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  230. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  231. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  232. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  233. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  234. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  235. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  236. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  237. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  238. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  239. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  240. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  241. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  242. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  243. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  244. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  245. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  246. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  247. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  248. package/templates/nx/specs/Playground/suite.md +0 -9
  249. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  250. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  251. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  252. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  253. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  254. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  255. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  256. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  257. package/templates/nx/tsconfig.spectra.android.json +0 -39
  258. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  259. package/templates/nx/tsconfig.spectra.json +0 -25
  260. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  261. package/templates/nx/tsconfig.spectra.web.json +0 -45
  262. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  263. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  264. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  265. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  266. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  267. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  268. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  269. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  270. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  271. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  272. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  273. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  274. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  275. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  276. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  277. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -1,443 +1 @@
1
- import fs from "fs";
2
- import path from "path";
3
- const PLATFORM_HIERARCHY = {
4
- web: ["web", "common"],
5
- ios: ["ios", "mobile", "common", "web"],
6
- android: ["android", "mobile", "common", "web"],
7
- mobile: ["mobile", "common", "android", "ios", "web"],
8
- common: ["common", "web", "mobile", "android", "ios"],
9
- };
10
- export class TypeGenerator {
11
- /**
12
- * Discovers all feature directories and shared library directories.
13
- * Returns a map of scope -> base directory (e.g. { "shared": ".../shared/testing", "auth": ".../packages/features/auth/e2e" })
14
- */
15
- static getEntityScopes(cwd) {
16
- const scopes = [];
17
- // 1. Scan for shared directories
18
- const sharedCandidates = [
19
- path.join(cwd, "shared", "testing"),
20
- path.join(cwd, "shared"),
21
- path.join(cwd, "testing"),
22
- ];
23
- for (const cand of sharedCandidates) {
24
- if (fs.existsSync(cand) &&
25
- (fs.existsSync(path.join(cand, "page-objects")) ||
26
- fs.existsSync(path.join(cand, "support")) ||
27
- fs.existsSync(path.join(cand, "steps")) ||
28
- fs.existsSync(path.join(cand, "actions")) ||
29
- fs.existsSync(path.join(cand, "fixtures")))) {
30
- if (!scopes.some((s) => s.dir === cand)) {
31
- scopes.push({ name: "shared", dir: cand, isShared: true });
32
- }
33
- }
34
- }
35
- // 2. Scan recursively for feature e2e directories
36
- function scanForFeatures(dir, depth) {
37
- if (depth <= 0 || !fs.existsSync(dir))
38
- return;
39
- const base = path.basename(dir);
40
- if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
41
- return;
42
- if (base === "e2e" || base === "tests" || base === "specs") {
43
- const featureDir = path.dirname(dir);
44
- const featureName = path.basename(featureDir);
45
- if (!scopes.some((s) => s.dir === dir)) {
46
- scopes.push({ name: featureName, dir: dir, isShared: false });
47
- }
48
- return;
49
- }
50
- try {
51
- const entries = fs.readdirSync(dir, { withFileTypes: true });
52
- for (const entry of entries) {
53
- if (entry.isDirectory()) {
54
- scanForFeatures(path.join(dir, entry.name), depth - 1);
55
- }
56
- }
57
- }
58
- catch { }
59
- }
60
- scanForFeatures(cwd, 6);
61
- return scopes;
62
- }
63
- static getPageObjectsDirsForScope(scopeDir, sharedDir) {
64
- const dirs = [];
65
- function scanDir(dir, maxDepth) {
66
- if (maxDepth <= 0 || !fs.existsSync(dir))
67
- return;
68
- const base = path.basename(dir);
69
- if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
70
- return;
71
- if (base === "page-objects" || base === "pageobjects") {
72
- if (!dirs.includes(dir))
73
- dirs.push(dir);
74
- return;
75
- }
76
- try {
77
- const entries = fs.readdirSync(dir, { withFileTypes: true });
78
- for (const entry of entries) {
79
- if (entry.isDirectory()) {
80
- scanDir(path.join(dir, entry.name), maxDepth - 1);
81
- }
82
- }
83
- }
84
- catch { }
85
- }
86
- if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
87
- scanDir(scopeDir, 6);
88
- }
89
- else {
90
- const candidates = [
91
- path.join(scopeDir, "page-objects"),
92
- path.join(scopeDir, "pageobjects"),
93
- ];
94
- if (sharedDir) {
95
- candidates.push(path.join(sharedDir, "page-objects"));
96
- candidates.push(path.join(sharedDir, "pageobjects"));
97
- }
98
- for (const c of candidates) {
99
- if (fs.existsSync(c) && !dirs.includes(c))
100
- dirs.push(c);
101
- }
102
- }
103
- return dirs;
104
- }
105
- static getEntityDirsForScope(entityType, scopeDir, sharedDir) {
106
- const dirs = [];
107
- function scanDir(dir, maxDepth) {
108
- if (maxDepth <= 0 || !fs.existsSync(dir))
109
- return;
110
- const base = path.basename(dir);
111
- if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
112
- return;
113
- if (base === entityType) {
114
- if (!dirs.includes(dir))
115
- dirs.push(dir);
116
- return;
117
- }
118
- try {
119
- const entries = fs.readdirSync(dir, { withFileTypes: true });
120
- for (const entry of entries) {
121
- if (entry.isDirectory()) {
122
- scanDir(path.join(dir, entry.name), maxDepth - 1);
123
- }
124
- }
125
- }
126
- catch { }
127
- }
128
- if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
129
- scanDir(scopeDir, 6);
130
- }
131
- else {
132
- const candidates = [
133
- path.join(scopeDir, entityType),
134
- path.join(scopeDir, "support", entityType),
135
- path.join(scopeDir, "support", "testing", entityType),
136
- ];
137
- if (sharedDir) {
138
- candidates.push(path.join(sharedDir, entityType));
139
- candidates.push(path.join(sharedDir, "support", entityType));
140
- candidates.push(path.join(sharedDir, "support", "testing", entityType));
141
- }
142
- for (const c of candidates) {
143
- if (fs.existsSync(c) && !dirs.includes(c))
144
- dirs.push(c);
145
- }
146
- }
147
- return dirs;
148
- }
149
- static generateDeclarationsForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
150
- const poDirs = this.getPageObjectsDirsForScope(scopeDir, sharedDir);
151
- const actionsDirs = this.getEntityDirsForScope("actions", scopeDir, sharedDir);
152
- const stepsDirs = this.getEntityDirsForScope("steps", scopeDir, sharedDir);
153
- const platforms = ["web", "android", "ios", "mobile", "common"];
154
- const results = {};
155
- for (const platform of platforms) {
156
- const hierarchy = PLATFORM_HIERARCHY[platform];
157
- let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
158
- content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
159
- content += `/// <reference types="@wdio/globals/types" />\n`;
160
- content += `/// <reference types="@wdio/mocha-framework" />\n`;
161
- content += `/// <reference types="@testspectra/matchers" />\n`;
162
- content += `/// <reference path="./fixtures.d.ts" />\n\n`;
163
- // 1. Page Objects Global Declarations
164
- const declaredPOMs = new Set();
165
- for (const poDir of poDirs) {
166
- if (!fs.existsSync(poDir))
167
- continue;
168
- const poEntries = fs.readdirSync(poDir, { withFileTypes: true });
169
- for (const entry of poEntries) {
170
- if (entry.name.startsWith("."))
171
- continue;
172
- if (entry.isDirectory()) {
173
- const className = entry.name;
174
- if (declaredPOMs.has(className))
175
- continue;
176
- const classFolder = path.join(poDir, className);
177
- let matchedFile = null;
178
- for (const stem of hierarchy) {
179
- const candidates = [
180
- path.join(classFolder, `${stem}.ts`),
181
- path.join(classFolder, `${stem}.page.ts`),
182
- path.join(classFolder, `${stem}.po.ts`),
183
- path.join(classFolder, "index.ts"),
184
- path.join(classFolder, "common.ts"),
185
- ];
186
- for (const cand of candidates) {
187
- if (fs.existsSync(cand)) {
188
- matchedFile = path.relative(outputTypesDir, cand).replace(/\\/g, "/").replace(/\.ts$/, ".js");
189
- if (!matchedFile.startsWith("."))
190
- matchedFile = `./${matchedFile}`;
191
- break;
192
- }
193
- }
194
- if (matchedFile)
195
- break;
196
- }
197
- if (matchedFile) {
198
- content += `declare const ${className}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
199
- declaredPOMs.add(className);
200
- }
201
- }
202
- else if (entry.isFile() && entry.name.endsWith(".ts")) {
203
- const parts = entry.name.split(".");
204
- const className = parts[0];
205
- if (declaredPOMs.has(className))
206
- continue;
207
- let isMatch = false;
208
- if (parts.length === 2) {
209
- // E.g. ProfilePage.ts (applies to all platforms)
210
- isMatch = true;
211
- }
212
- else if (parts.length > 2) {
213
- // E.g. ProfilePage.web.ts or ProfilePage.android.ts
214
- const stem = parts[1];
215
- isMatch = hierarchy.includes(stem);
216
- }
217
- if (isMatch) {
218
- let rel = path.relative(outputTypesDir, path.join(poDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
219
- if (!rel.startsWith("."))
220
- rel = `./${rel}`;
221
- content += `declare const ${className}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
222
- declaredPOMs.add(className);
223
- }
224
- }
225
- }
226
- }
227
- // 2. Built-in Spectra Static Methods & Custom Actions
228
- content += `interface TestSpectraCustomActions {\n`;
229
- const declaredActions = new Set();
230
- for (const actionsDir of actionsDirs) {
231
- if (!fs.existsSync(actionsDir))
232
- continue;
233
- const actionEntries = fs.readdirSync(actionsDir, { withFileTypes: true });
234
- for (const entry of actionEntries) {
235
- if (entry.name.startsWith("."))
236
- continue;
237
- if (entry.isDirectory()) {
238
- const actionName = entry.name;
239
- if (declaredActions.has(actionName))
240
- continue;
241
- const actionFolder = path.join(actionsDir, actionName);
242
- let matchedFile = null;
243
- for (const stem of hierarchy) {
244
- const cand1 = path.join(actionFolder, `${stem}.action.ts`);
245
- const cand2 = path.join(actionFolder, `${stem}.ts`);
246
- if (fs.existsSync(cand1)) {
247
- matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
248
- break;
249
- }
250
- else if (fs.existsSync(cand2)) {
251
- matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
252
- break;
253
- }
254
- }
255
- if (matchedFile) {
256
- if (!matchedFile.startsWith("."))
257
- matchedFile = `./${matchedFile}`;
258
- const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
259
- content += ` "${actionName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
260
- if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
261
- content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
262
- }
263
- declaredActions.add(actionName);
264
- }
265
- }
266
- else if (entry.isFile() && entry.name.endsWith(".ts")) {
267
- const parts = entry.name.split(".");
268
- const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
269
- const actionName = parts[0];
270
- if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
271
- if (declaredActions.has(actionName))
272
- continue;
273
- let rel = path.relative(outputTypesDir, path.join(actionsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
274
- if (!rel.startsWith("."))
275
- rel = `./${rel}`;
276
- const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
277
- content += ` "${actionName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
278
- if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
279
- content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
280
- }
281
- declaredActions.add(actionName);
282
- }
283
- }
284
- }
285
- }
286
- content += `}\n`;
287
- content += `type TestSpectraActions = import('@testspectra/matchers').SpectraStatic & TestSpectraCustomActions;\n`;
288
- content += `declare const Spectra: TestSpectraActions;\n\n`;
289
- // 3. Built-in Steps Declaration
290
- content += `interface TestSpectraSteps {\n`;
291
- const declaredSteps = new Set();
292
- for (const stepsDir of stepsDirs) {
293
- if (!fs.existsSync(stepsDir))
294
- continue;
295
- const stepEntries = fs.readdirSync(stepsDir, { withFileTypes: true });
296
- for (const entry of stepEntries) {
297
- if (entry.name.startsWith("."))
298
- continue;
299
- if (entry.isDirectory()) {
300
- const stepName = entry.name;
301
- if (declaredSteps.has(stepName))
302
- continue;
303
- const stepFolder = path.join(stepsDir, stepName);
304
- let matchedFile = null;
305
- for (const stem of hierarchy) {
306
- const cand1 = path.join(stepFolder, `${stem}.step.ts`);
307
- const cand2 = path.join(stepFolder, `${stem}.ts`);
308
- if (fs.existsSync(cand1)) {
309
- matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
310
- break;
311
- }
312
- else if (fs.existsSync(cand2)) {
313
- matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
314
- break;
315
- }
316
- }
317
- if (matchedFile) {
318
- if (!matchedFile.startsWith("."))
319
- matchedFile = `./${matchedFile}`;
320
- const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
321
- content += ` "${stepName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
322
- if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
323
- content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
324
- }
325
- declaredSteps.add(stepName);
326
- }
327
- }
328
- else if (entry.isFile() && entry.name.endsWith(".ts")) {
329
- const parts = entry.name.split(".");
330
- const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
331
- const stepName = parts[0];
332
- if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
333
- if (declaredSteps.has(stepName))
334
- continue;
335
- let rel = path.relative(outputTypesDir, path.join(stepsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
336
- if (!rel.startsWith("."))
337
- rel = `./${rel}`;
338
- const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
339
- content += ` "${stepName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
340
- if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
341
- content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
342
- }
343
- declaredSteps.add(stepName);
344
- }
345
- }
346
- }
347
- }
348
- content += `}\n`;
349
- content += `declare const Step: TestSpectraSteps;\n\n`;
350
- results[platform] = content;
351
- }
352
- return results;
353
- }
354
- static generateFixturesDeclarationForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
355
- const fixturesDirs = this.getEntityDirsForScope("fixtures", scopeDir, sharedDir);
356
- let content = `// Auto-generated ambient fixture declarations for TestSpectra\n`;
357
- content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
358
- content += `interface TestSpectraFixtures {\n`;
359
- const declaredFixtures = new Set();
360
- function scanFixtureDir(dir) {
361
- if (!fs.existsSync(dir))
362
- return;
363
- try {
364
- const entries = fs.readdirSync(dir, { withFileTypes: true });
365
- for (const entry of entries) {
366
- if (entry.name.startsWith("."))
367
- continue;
368
- if (entry.isDirectory()) {
369
- scanFixtureDir(path.join(dir, entry.name));
370
- }
371
- else if (entry.isFile()) {
372
- const parsed = path.parse(entry.name);
373
- const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
374
- if (declaredFixtures.has(varName))
375
- continue;
376
- if (entry.name.endsWith(".json")) {
377
- let rel = path.relative(outputTypesDir, path.join(dir, entry.name)).replace(/\\/g, "/");
378
- if (!rel.startsWith("."))
379
- rel = `./${rel}`;
380
- content += ` readonly ${varName}: typeof import('${rel}');\n`;
381
- }
382
- else {
383
- content += ` readonly ${varName}: string;\n`;
384
- }
385
- declaredFixtures.add(varName);
386
- }
387
- }
388
- }
389
- catch { }
390
- }
391
- for (const fixturesDir of fixturesDirs) {
392
- scanFixtureDir(fixturesDir);
393
- }
394
- content += `}\n`;
395
- content += `declare const Fixture: TestSpectraFixtures;\n`;
396
- return content;
397
- }
398
- static writeDeclarationFiles(cwd) {
399
- const rootTypesDir = path.join(cwd, ".testspectra", "types");
400
- if (!fs.existsSync(rootTypesDir)) {
401
- fs.mkdirSync(rootTypesDir, { recursive: true });
402
- }
403
- const scopes = this.getEntityScopes(cwd);
404
- const sharedScope = scopes.find((s) => s.isShared);
405
- // 1. Generate Global / Standalone Root Types (or fallback)
406
- const rootFixtures = this.generateFixturesDeclarationForScope(cwd, rootTypesDir, cwd);
407
- fs.writeFileSync(path.join(rootTypesDir, "fixtures.d.ts"), rootFixtures, "utf-8");
408
- const rootDeclarations = this.generateDeclarationsForScope(cwd, rootTypesDir, cwd);
409
- for (const [platform, content] of Object.entries(rootDeclarations)) {
410
- fs.writeFileSync(path.join(rootTypesDir, `${platform}.d.ts`), content, "utf-8");
411
- }
412
- // 2. Generate Scoped Types for Shared Library: .testspectra/types/shared/
413
- if (sharedScope) {
414
- const sharedTypesDir = path.join(rootTypesDir, "shared");
415
- if (!fs.existsSync(sharedTypesDir))
416
- fs.mkdirSync(sharedTypesDir, { recursive: true });
417
- const sharedFixtures = this.generateFixturesDeclarationForScope(cwd, sharedTypesDir, sharedScope.dir);
418
- fs.writeFileSync(path.join(sharedTypesDir, "fixtures.d.ts"), sharedFixtures, "utf-8");
419
- // Shared scope has NO feature dependencies (only shared itself)
420
- const sharedDeclarations = this.generateDeclarationsForScope(cwd, sharedTypesDir, sharedScope.dir);
421
- for (const [platform, content] of Object.entries(sharedDeclarations)) {
422
- fs.writeFileSync(path.join(sharedTypesDir, `${platform}.d.ts`), content, "utf-8");
423
- }
424
- }
425
- // 3. Generate Scoped Types for Feature Modules: .testspectra/types/features/<featureName>/
426
- const featureScopes = scopes.filter((s) => !s.isShared);
427
- for (const feature of featureScopes) {
428
- const featureTypesDir = path.join(rootTypesDir, "features", feature.name);
429
- if (!fs.existsSync(featureTypesDir))
430
- fs.mkdirSync(featureTypesDir, { recursive: true });
431
- const featureFixtures = this.generateFixturesDeclarationForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
432
- fs.writeFileSync(path.join(featureTypesDir, "fixtures.d.ts"), featureFixtures, "utf-8");
433
- // Feature scope gets (feature + shared) ONLY
434
- const featureDeclarations = this.generateDeclarationsForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
435
- for (const [platform, content] of Object.entries(featureDeclarations)) {
436
- fs.writeFileSync(path.join(featureTypesDir, `${platform}.d.ts`), content, "utf-8");
437
- }
438
- }
439
- // Universal ambient index
440
- const universalPath = path.join(rootTypesDir, "index.d.ts");
441
- fs.writeFileSync(universalPath, `/// <reference path="./common.d.ts" />\n`, "utf-8");
442
- }
443
- }
1
+ export * from '../generator/type-generator.js';
@@ -1,20 +1,20 @@
1
- import http from "http";
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
5
  const MIME_TYPES = {
6
- ".html": "text/html",
7
- ".js": "application/javascript",
8
- ".css": "text/css",
9
- ".json": "application/json",
10
- ".png": "image/png",
11
- ".jpg": "image/jpeg",
12
- ".gif": "image/gif",
13
- ".svg": "image/svg+xml",
14
- ".ico": "image/x-icon",
15
- ".woff": "font/woff",
16
- ".woff2": "font/woff2",
17
- ".ttf": "font/ttf",
6
+ '.html': 'text/html',
7
+ '.js': 'application/javascript',
8
+ '.css': 'text/css',
9
+ '.json': 'application/json',
10
+ '.png': 'image/png',
11
+ '.jpg': 'image/jpeg',
12
+ '.gif': 'image/gif',
13
+ '.svg': 'image/svg+xml',
14
+ '.ico': 'image/x-icon',
15
+ '.woff': 'font/woff',
16
+ '.woff2': 'font/woff2',
17
+ '.ttf': 'font/ttf',
18
18
  };
19
19
  export class DemoServer {
20
20
  server = null;
@@ -26,14 +26,14 @@ export class DemoServer {
26
26
  const __filename = fileURLToPath(import.meta.url);
27
27
  const __dirname = path.dirname(__filename);
28
28
  const candidates = [
29
- path.resolve(__dirname, "../../demo-app"),
30
- path.resolve(__dirname, "../demo-app"),
31
- path.resolve(__dirname, "../../../demo-app"),
32
- path.resolve(__dirname, "../../../apps/demo-web-app/dist"),
33
- path.resolve(__dirname, "../../../../apps/demo-web-app/dist"),
29
+ path.resolve(__dirname, '../../demo-app'),
30
+ path.resolve(__dirname, '../demo-app'),
31
+ path.resolve(__dirname, '../../../demo-app'),
32
+ path.resolve(__dirname, '../../../apps/demo-web-app/dist'),
33
+ path.resolve(__dirname, '../../../../apps/demo-web-app/dist'),
34
34
  ];
35
35
  for (const cand of candidates) {
36
- if (fs.existsSync(cand) && fs.existsSync(path.join(cand, "index.html"))) {
36
+ if (fs.existsSync(cand) && fs.existsSync(path.join(cand, 'index.html'))) {
37
37
  return cand;
38
38
  }
39
39
  }
@@ -46,32 +46,32 @@ export class DemoServer {
46
46
  }
47
47
  return new Promise((resolve, reject) => {
48
48
  this.server = http.createServer((req, res) => {
49
- const reqUrl = req.url ? req.url.split("?")[0] : "/";
50
- let safePath = path.normalize(reqUrl).replace(/^(\.\.[\/\\])+/, "");
51
- if (safePath === "/" || safePath === "\\")
52
- safePath = "/index.html";
49
+ const reqUrl = req.url ? req.url.split('?')[0] : '/';
50
+ let safePath = path.normalize(reqUrl).replace(/^(\.\.[/\\])+/, '');
51
+ if (safePath === '/' || safePath === '\\')
52
+ safePath = '/index.html';
53
53
  let filePath = path.join(demoDir, safePath);
54
54
  // Fallback to index.html for SPA routes
55
55
  if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
56
- filePath = path.join(demoDir, "index.html");
56
+ filePath = path.join(demoDir, 'index.html');
57
57
  }
58
58
  const ext = path.extname(filePath).toLowerCase();
59
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
59
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
60
60
  try {
61
61
  const content = fs.readFileSync(filePath);
62
62
  res.writeHead(200, {
63
- "Content-Type": contentType,
64
- "Access-Control-Allow-Origin": "*",
63
+ 'Content-Type': contentType,
64
+ 'Access-Control-Allow-Origin': '*',
65
65
  });
66
66
  res.end(content);
67
67
  }
68
68
  catch {
69
- res.writeHead(404, { "Content-Type": "text/plain" });
70
- res.end("Not Found");
69
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
70
+ res.end('Not Found');
71
71
  }
72
72
  });
73
- this.server.on("error", (err) => {
74
- if (err.code === "EADDRINUSE") {
73
+ this.server.on('error', (err) => {
74
+ if (err.code === 'EADDRINUSE') {
75
75
  // Port already in use (e.g. user already running server)
76
76
  resolve({ url: `http://localhost:${this.port}`, port: this.port });
77
77
  }
@@ -79,13 +79,16 @@ export class DemoServer {
79
79
  reject(err);
80
80
  }
81
81
  });
82
- this.server.listen(this.port, "0.0.0.0", () => {
82
+ this.server.listen(this.port, '0.0.0.0', () => {
83
83
  resolve({ url: `http://localhost:${this.port}`, port: this.port });
84
84
  });
85
85
  });
86
86
  }
87
87
  async stop() {
88
88
  if (this.server) {
89
+ if (typeof this.server.closeAllConnections === 'function') {
90
+ this.server.closeAllConnections();
91
+ }
89
92
  return new Promise((resolve) => {
90
93
  this.server?.close(() => {
91
94
  this.server = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.70",
3
+ "version": "1.1.1",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,32 +16,40 @@
16
16
  "spectra": "./bin/spectra.js",
17
17
  "testspectra": "./bin/spectra.js"
18
18
  },
19
- "scripts": {
20
- "build": "tsc && node scripts/copy-demo.js",
21
- "watch": "tsc -w",
22
- "prepublishOnly": "npm run build"
23
- },
24
19
  "files": [
25
20
  "dist",
26
21
  "bin",
27
22
  "templates",
28
23
  "demo-app",
29
- "README.md"
24
+ "README.md",
25
+ "LICENSE.md"
30
26
  ],
31
27
  "dependencies": {
32
28
  "@clack/prompts": "^1.7.0",
33
- "@testspectra/matchers": "^1.0.70",
34
29
  "chalk": "^5.3.0",
35
30
  "commander": "^12.1.0",
36
31
  "dotenv": "^16.4.5",
37
32
  "ora": "^8.0.1",
38
- "zod": "^3.23.8"
33
+ "zod": "^3.23.8",
34
+ "@testspectra/matchers": "1.1.0"
39
35
  },
40
36
  "devDependencies": {
41
37
  "@types/node": "^20.14.0",
38
+ "@wdio/globals": "^9.24.0",
39
+ "@wdio/mocha-framework": "^9.24.0",
40
+ "@wdio/types": "^9.24.0",
42
41
  "esbuild": "^0.28.2",
43
- "typescript": "^5.4.5"
42
+ "typescript": "^5.6.3",
43
+ "vitest": "^2.1.8"
44
44
  },
45
45
  "type": "module",
46
- "license": "MIT"
47
- }
46
+ "license": "SEE LICENSE IN LICENSE.md",
47
+ "scripts": {
48
+ "build:rust": "node scripts/ensure-runner-binary.js",
49
+ "prebuild": "node scripts/ensure-runner-binary.js",
50
+ "build": "tsc && node scripts/copy-demo.js",
51
+ "watch": "tsc -w",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest"
54
+ }
55
+ }
@@ -1,9 +1,11 @@
1
1
  # CLAUDE.md - TestSpectra Framework Conventions
2
2
 
3
3
  ## Commands
4
- - Run all Web tests: `pnpm spectra run -t web`
5
- - Run specific spec: `pnpm spectra run "specs/Playground/TC-0001-form-controls/web.test.ts" -t web`
6
- - Run Mobile tests: `pnpm spectra run --target android` or `pnpm spectra run --target ios`
4
+ - Run all Web tests: `pnpm test:web` (or `spectra run -t web`)
5
+ - Run all Mobile tests: `pnpm test:mobile` (or `spectra run -t mobile`)
6
+ - Run Android tests: `pnpm test:android` (or `spectra run -t android`)
7
+ - Run iOS tests: `pnpm test:ios` (or `spectra run -t ios`)
8
+ - Run specific spec: `spectra run "specs/Playground/TC-0001-form-controls/mobile.test.ts" -t mobile`
7
9
  - Type checking: `pnpm type-check` (or `tsc -b`)
8
- - Preflight doctor: `pnpm spectra doctor`
9
- - List devices: `pnpm spectra devices`
10
+ - Preflight doctor: `pnpm doctor` (or `spectra doctor`)
11
+ - Sync ambient types: `pnpm sync-types`