@testspectra/cli 1.0.70 → 1.1.0-rc.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 (427) 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-BhlW-eXG.css +1 -0
  7. package/demo-app/assets/index-DOtRypCa.js +154 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  11. package/dist/commands/__tests__/doctor.test.js +39 -0
  12. package/dist/commands/__tests__/init.test.d.ts +1 -0
  13. package/dist/commands/__tests__/init.test.js +148 -0
  14. package/dist/commands/__tests__/license.test.d.ts +1 -0
  15. package/dist/commands/__tests__/license.test.js +13 -0
  16. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  17. package/dist/commands/__tests__/refactor.test.js +64 -0
  18. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  19. package/dist/commands/__tests__/run-e2e.test.js +422 -0
  20. package/dist/commands/__tests__/run.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run.test.js +273 -0
  22. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  23. package/dist/commands/__tests__/watch.test.js +155 -0
  24. package/dist/commands/add.js +86 -85
  25. package/dist/commands/demo.js +21 -11
  26. package/dist/commands/devices.d.ts +14 -2
  27. package/dist/commands/devices.js +44 -34
  28. package/dist/commands/doctor.d.ts +3 -1
  29. package/dist/commands/doctor.js +43 -264
  30. package/dist/commands/init.d.ts +1 -1
  31. package/dist/commands/init.js +211 -196
  32. package/dist/commands/install.d.ts +4 -0
  33. package/dist/commands/install.js +59 -0
  34. package/dist/commands/license.d.ts +4 -0
  35. package/dist/commands/license.js +30 -0
  36. package/dist/commands/lint.d.ts +5 -0
  37. package/dist/commands/lint.js +55 -0
  38. package/dist/commands/refactor.d.ts +9 -0
  39. package/dist/commands/refactor.js +172 -0
  40. package/dist/commands/run.d.ts +23 -2
  41. package/dist/commands/run.js +322 -111
  42. package/dist/commands/skills.d.ts +9 -0
  43. package/dist/commands/skills.js +28 -0
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +15 -0
  46. package/dist/commands/test.d.ts +17 -0
  47. package/dist/commands/test.js +76 -0
  48. package/dist/commands/update.js +39 -43
  49. package/dist/commands/watch.d.ts +6 -0
  50. package/dist/commands/watch.js +55 -45
  51. package/dist/config/loader.d.ts +1 -1
  52. package/dist/config/loader.js +40 -25
  53. package/dist/config/schema.d.ts +104 -145
  54. package/dist/config/schema.js +36 -45
  55. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  56. package/dist/generator/__tests__/tsconfig-isolation.test.js +282 -0
  57. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  58. package/dist/generator/__tests__/type-generator.test.js +386 -0
  59. package/dist/generator/index.d.ts +2 -0
  60. package/dist/generator/index.js +2 -0
  61. package/dist/generator/tsconfig-generator.d.ts +27 -0
  62. package/dist/generator/tsconfig-generator.js +419 -0
  63. package/dist/generator/type-generator.d.ts +59 -0
  64. package/dist/generator/type-generator.js +740 -0
  65. package/dist/index.d.ts +9 -5
  66. package/dist/index.js +165 -51
  67. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  68. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  69. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  70. package/dist/lifecycle/hook-discovery.js +60 -0
  71. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  72. package/dist/lifecycle/hook-dispatcher.js +190 -0
  73. package/dist/lifecycle/index.d.ts +3 -0
  74. package/dist/lifecycle/index.js +3 -0
  75. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  76. package/dist/lifecycle/parallel-grouping.js +151 -0
  77. package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
  78. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  79. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  80. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  81. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  82. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  83. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  84. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  85. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  86. package/dist/linter/__tests__/step-parity.test.js +372 -0
  87. package/dist/linter/discovery.d.ts +30 -0
  88. package/dist/linter/discovery.js +249 -0
  89. package/dist/linter/extractor.d.ts +67 -0
  90. package/dist/linter/extractor.js +245 -0
  91. package/dist/linter/index.d.ts +7 -0
  92. package/dist/linter/index.js +7 -0
  93. package/dist/linter/parser.d.ts +24 -0
  94. package/dist/linter/parser.js +219 -0
  95. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  96. package/dist/linter/schemas/spec.schema.js +150 -0
  97. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  98. package/dist/linter/schemas/suite.schema.js +83 -0
  99. package/dist/linter/spec-linter.d.ts +23 -0
  100. package/dist/linter/spec-linter.js +630 -0
  101. package/dist/linter/types.d.ts +28 -0
  102. package/dist/linter/types.js +1 -0
  103. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  104. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  105. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  106. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  107. package/dist/mobile/device-pool.d.ts +23 -0
  108. package/dist/mobile/device-pool.js +1 -0
  109. package/dist/mobile/index.d.ts +1 -0
  110. package/dist/mobile/index.js +1 -0
  111. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  112. package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
  113. package/dist/refactor/index.d.ts +1 -0
  114. package/dist/refactor/index.js +1 -0
  115. package/dist/refactor/refactor-engine.d.ts +50 -0
  116. package/dist/refactor/refactor-engine.js +587 -0
  117. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  118. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  119. package/dist/reporter/semantic-formatter.d.ts +6 -0
  120. package/dist/reporter/semantic-formatter.js +206 -0
  121. package/dist/reporter/summary-view.d.ts +12 -0
  122. package/dist/reporter/summary-view.js +19 -0
  123. package/dist/reporter/terminal-stream.d.ts +32 -0
  124. package/dist/reporter/terminal-stream.js +82 -0
  125. package/dist/reporter/types.d.ts +2 -0
  126. package/dist/reporter/types.js +1 -0
  127. package/dist/runner/bridge.d.ts +10 -5
  128. package/dist/runner/bridge.js +101 -45
  129. package/dist/runner/reporter.d.ts +32 -2
  130. package/dist/runner/reporter.js +427 -13
  131. package/dist/server/demo.js +43 -44
  132. package/dist/types/generator.d.ts +1 -17
  133. package/dist/types/generator.js +1 -443
  134. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  135. package/dist/utils/__tests__/api-server.test.js +62 -0
  136. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  137. package/dist/utils/__tests__/demo-server.test.js +35 -0
  138. package/dist/utils/api-server.d.ts +19 -0
  139. package/dist/utils/api-server.js +179 -0
  140. package/dist/utils/demo-server.d.ts +5 -0
  141. package/dist/utils/demo-server.js +46 -36
  142. package/package.json +25 -13
  143. package/templates/default/CLAUDE.md +12 -5
  144. package/templates/default/fixtures/apiData.json +24 -0
  145. package/templates/default/package.json +12 -3
  146. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  147. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  148. package/templates/default/page-objects/MatchersPage/mobile.ts +66 -17
  149. package/templates/default/page-objects/MatchersPage/web.ts +22 -2
  150. package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
  151. package/templates/default/page-objects/NavigationPage/web.ts +1 -1
  152. package/templates/default/page-objects/PlaygroundPage/mobile.ts +34 -18
  153. package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
  154. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
  155. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
  156. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
  157. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  158. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  159. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
  160. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
  161. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
  162. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
  163. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  164. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  165. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  166. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  167. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  168. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  169. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  170. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  171. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  172. package/templates/default/specs/ApiInterception/suite.md +10 -0
  173. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  174. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  175. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  176. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  177. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  178. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  179. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  180. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  181. package/templates/default/specs/Authentication/suite.md +9 -0
  182. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  183. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
  184. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  185. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  186. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  187. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  188. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  189. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
  190. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  191. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
  192. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  193. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  194. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  195. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  196. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  197. package/templates/default/specs/DeepLink/suite.md +9 -0
  198. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  199. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
  200. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  201. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  202. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
  203. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  204. package/templates/default/specs/ElementMatchers/suite.md +2 -1
  205. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  206. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  207. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  208. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  209. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  210. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  211. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  212. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  213. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  214. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
  215. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  216. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  217. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
  218. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  219. package/templates/default/spectra.config.ts +22 -31
  220. package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
  221. package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
  222. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  223. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  224. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
  225. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  226. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
  227. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  228. package/templates/default/tsconfig.json +3 -1
  229. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
  230. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  231. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  232. package/templates/nx/.nx/workspace-data/d/daemon.log +5366 -0
  233. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  234. package/templates/nx/.nx/workspace-data/file-map.json +464 -0
  235. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  236. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  237. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  238. package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
  239. package/templates/nx/CLAUDE.md +2 -2
  240. package/templates/nx/package.json +6 -4
  241. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
  242. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  243. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  244. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
  245. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  246. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  247. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  248. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  249. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  250. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
  251. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
  252. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  253. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  254. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  255. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  256. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  257. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  258. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  259. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  260. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  261. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  262. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  263. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  264. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  265. package/templates/nx/pnpm-lock.yaml +6663 -0
  266. package/templates/nx/pnpm-workspace.yaml +2 -1
  267. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  268. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
  269. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
  270. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
  271. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
  272. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
  273. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
  274. package/templates/nx/shared/testing/project.json +19 -0
  275. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
  276. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
  277. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  278. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  279. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
  280. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  281. package/templates/nx/spectra.config.ts +20 -29
  282. package/templates/nx/tsconfig.json +3 -1
  283. package/templates/react-component/.cursorrules +17 -0
  284. package/templates/react-component/.vscode/extensions.json +5 -0
  285. package/templates/react-component/CLAUDE.md +14 -0
  286. package/templates/react-component/README.md +69 -0
  287. package/templates/react-component/fixtures/component-mock.json +12 -0
  288. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  289. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  290. package/templates/react-component/package.json +27 -0
  291. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  292. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  293. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  294. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  295. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  296. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  297. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  298. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  299. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  300. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  301. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  302. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  303. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  304. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  305. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  306. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  307. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  308. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  309. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  310. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  311. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  312. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  313. package/templates/react-component/spectra.config.ts +27 -0
  314. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  315. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  316. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  317. package/templates/react-component/src/test-utils.tsx +23 -0
  318. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  319. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  320. package/templates/react-component/tsconfig.json +8 -0
  321. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  322. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  323. package/dist/index.cjs +0 -101
  324. package/dist/plugin.cjs +0 -575
  325. package/dist/plugin.d.ts +0 -16
  326. package/dist/plugin.js +0 -184
  327. package/dist/step/index.d.ts +0 -6
  328. package/dist/step/index.js +0 -6
  329. package/dist/step/matchers.d.ts +0 -14
  330. package/dist/step/matchers.js +0 -114
  331. package/dist/step/proto.d.ts +0 -46
  332. package/dist/step/proto.js +0 -114
  333. package/dist/step/runner/collection.d.ts +0 -13
  334. package/dist/step/runner/collection.js +0 -51
  335. package/dist/step/runner/single.d.ts +0 -21
  336. package/dist/step/runner/single.js +0 -138
  337. package/dist/step/spectra.d.ts +0 -34
  338. package/dist/step/spectra.js +0 -156
  339. package/dist/step/types.d.ts +0 -32
  340. package/dist/step/types.js +0 -5
  341. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  342. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  343. package/templates/default/tsconfig.spectra.android.json +0 -46
  344. package/templates/default/tsconfig.spectra.ios.json +0 -46
  345. package/templates/default/tsconfig.spectra.json +0 -22
  346. package/templates/default/tsconfig.spectra.web.json +0 -53
  347. package/templates/nx/fixtures/playgroundData.json +0 -7
  348. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  349. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  350. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  351. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  352. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  353. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  354. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  355. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  356. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  357. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  358. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  359. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  360. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  361. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  362. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  363. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  364. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  365. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  366. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  367. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  368. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  369. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  370. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  371. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  372. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  373. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  374. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  375. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  376. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  377. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  378. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  379. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  380. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  381. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  382. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  383. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  384. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  385. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  386. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  387. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  388. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  389. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  390. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  391. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  392. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  393. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  394. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  395. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  396. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  397. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  398. package/templates/nx/specs/Playground/suite.md +0 -9
  399. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  400. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  401. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  402. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  403. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  404. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  405. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  406. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  407. package/templates/nx/tsconfig.spectra.android.json +0 -39
  408. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  409. package/templates/nx/tsconfig.spectra.json +0 -25
  410. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  411. package/templates/nx/tsconfig.spectra.web.json +0 -45
  412. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  413. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  414. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  415. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  416. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  417. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  418. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  419. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  420. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  421. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  422. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  423. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  424. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  425. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  426. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  427. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -1,27 +1,28 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
- import * as p from "@clack/prompts";
5
- import chalk from "chalk";
6
- import { ConfigLoader } from "../config/loader.js";
7
- import { TypeGenerator } from "../types/generator.js";
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import * as p from '@clack/prompts';
5
+ import chalk from 'chalk';
6
+ import { ConfigLoader } from '../config/loader.js';
7
+ import { TypeGenerator } from '../types/generator.js';
8
+ import { TsConfigGenerator } from '../generator/tsconfig-generator.js';
8
9
  function parsePnpmWorkspaceGlobs(content) {
9
- const lines = content.split("\n");
10
+ const lines = content.split('\n');
10
11
  const globs = [];
11
12
  let inPackages = false;
12
13
  for (const line of lines) {
13
14
  const trimmed = line.trim();
14
- if (trimmed.startsWith("packages:")) {
15
+ if (trimmed.startsWith('packages:')) {
15
16
  inPackages = true;
16
17
  continue;
17
18
  }
18
19
  if (inPackages) {
19
- if (trimmed.startsWith("-")) {
20
- const item = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "");
20
+ if (trimmed.startsWith('-')) {
21
+ const item = trimmed.replace(/^-\s*['"]?/, '').replace(/['"]?\s*$/, '');
21
22
  if (item)
22
23
  globs.push(item);
23
24
  }
24
- else if (trimmed && !trimmed.startsWith("#")) {
25
+ else if (trimmed && !trimmed.startsWith('#')) {
25
26
  break;
26
27
  }
27
28
  }
@@ -29,27 +30,28 @@ function parsePnpmWorkspaceGlobs(content) {
29
30
  return globs;
30
31
  }
31
32
  function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
32
- const normalizedTarget = targetPath.replace(/\\/g, "/");
33
+ const normalizedTarget = targetPath.replace(/\\/g, '/');
33
34
  if (!fs.existsSync(pnpmWorkspacePath)) {
34
35
  const newContent = `packages:\n - '${normalizedTarget}'\n`;
35
- fs.writeFileSync(pnpmWorkspacePath, newContent, "utf-8");
36
+ fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
36
37
  return;
37
38
  }
38
- const content = fs.readFileSync(pnpmWorkspacePath, "utf-8");
39
+ const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
39
40
  const currentGlobs = parsePnpmWorkspaceGlobs(content);
40
41
  // Check if target is already covered by an existing glob or exact path
41
42
  const isAlreadyCovered = currentGlobs.some((glob) => {
42
- const normGlob = glob.replace(/\\/g, "/");
43
+ const normGlob = glob.replace(/\\/g, '/');
43
44
  if (normGlob === normalizedTarget)
44
45
  return true;
45
46
  if (normGlob === `${normalizedTarget}/*` || normGlob === `${normalizedTarget}/**`)
46
47
  return true;
47
- if (normGlob.endsWith("/*")) {
48
+ if (normGlob.endsWith('/*')) {
48
49
  const parent = normGlob.slice(0, -2);
49
- if (normalizedTarget.startsWith(`${parent}/`) && normalizedTarget.split("/").length === parent.split("/").length + 1)
50
+ if (normalizedTarget.startsWith(`${parent}/`) &&
51
+ normalizedTarget.split('/').length === parent.split('/').length + 1)
50
52
  return true;
51
53
  }
52
- if (normGlob.endsWith("/**")) {
54
+ if (normGlob.endsWith('/**')) {
53
55
  const parent = normGlob.slice(0, -3);
54
56
  if (normalizedTarget.startsWith(`${parent}/`))
55
57
  return true;
@@ -58,42 +60,42 @@ function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
58
60
  });
59
61
  if (!isAlreadyCovered) {
60
62
  // Insert new package pattern right under packages:
61
- const lines = content.split("\n");
63
+ const lines = content.split('\n');
62
64
  let insertIndex = -1;
63
65
  for (let i = 0; i < lines.length; i++) {
64
- if (lines[i].trim().startsWith("packages:")) {
66
+ if (lines[i].trim().startsWith('packages:')) {
65
67
  insertIndex = i + 1;
66
68
  break;
67
69
  }
68
70
  }
69
71
  if (insertIndex !== -1) {
70
72
  lines.splice(insertIndex, 0, ` - '${normalizedTarget}'`);
71
- fs.writeFileSync(pnpmWorkspacePath, lines.join("\n"), "utf-8");
73
+ fs.writeFileSync(pnpmWorkspacePath, lines.join('\n'), 'utf-8');
72
74
  }
73
75
  else {
74
76
  const newContent = `packages:\n - '${normalizedTarget}'\n` + content;
75
- fs.writeFileSync(pnpmWorkspacePath, newContent, "utf-8");
77
+ fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
76
78
  }
77
79
  }
78
80
  }
79
81
  function ensureVsCodeExtensions(cwd) {
80
- const vscodeDir = path.join(cwd, ".vscode");
81
- const extFile = path.join(vscodeDir, "extensions.json");
82
+ const vscodeDir = path.join(cwd, '.vscode');
83
+ const extFile = path.join(vscodeDir, 'extensions.json');
82
84
  if (!fs.existsSync(vscodeDir))
83
85
  fs.mkdirSync(vscodeDir, { recursive: true });
84
86
  let config = { recommendations: [] };
85
87
  if (fs.existsSync(extFile)) {
86
88
  try {
87
- config = JSON.parse(fs.readFileSync(extFile, "utf-8"));
89
+ config = JSON.parse(fs.readFileSync(extFile, 'utf-8'));
88
90
  }
89
91
  catch { }
90
92
  }
91
93
  if (!Array.isArray(config.recommendations))
92
94
  config.recommendations = [];
93
- if (!config.recommendations.includes("testspectra.testspectra-vscode")) {
94
- config.recommendations.push("testspectra.testspectra-vscode");
95
+ if (!config.recommendations.includes('testspectra.testspectra-vscode')) {
96
+ config.recommendations.push('testspectra.testspectra-vscode');
95
97
  }
96
- fs.writeFileSync(extFile, JSON.stringify(config, null, 2) + "\n", "utf-8");
98
+ fs.writeFileSync(extFile, JSON.stringify(config, null, 2) + '\n', 'utf-8');
97
99
  }
98
100
  function scanDirectoriesForProjects(cwd, patterns) {
99
101
  const foundProjects = [];
@@ -102,11 +104,11 @@ function scanDirectoriesForProjects(cwd, patterns) {
102
104
  if (currentDepth > maxDepth || !fs.existsSync(dir))
103
105
  return;
104
106
  const base = path.basename(dir);
105
- if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git" || base === "shared") {
107
+ if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git' || base === 'shared') {
106
108
  return;
107
109
  }
108
- const hasPkg = fs.existsSync(path.join(dir, "package.json"));
109
- const hasNx = fs.existsSync(path.join(dir, "project.json"));
110
+ const hasPkg = fs.existsSync(path.join(dir, 'package.json'));
111
+ const hasNx = fs.existsSync(path.join(dir, 'project.json'));
110
112
  if ((hasPkg || hasNx) && dir !== cwd) {
111
113
  const rel = path.relative(cwd, dir);
112
114
  if (!visited.has(rel)) {
@@ -114,7 +116,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
114
116
  let name = path.basename(dir);
115
117
  if (hasPkg) {
116
118
  try {
117
- const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
119
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
118
120
  if (pkg.name)
119
121
  name = pkg.name;
120
122
  }
@@ -122,7 +124,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
122
124
  }
123
125
  else if (hasNx) {
124
126
  try {
125
- const nxJson = JSON.parse(fs.readFileSync(path.join(dir, "project.json"), "utf-8"));
127
+ const nxJson = JSON.parse(fs.readFileSync(path.join(dir, 'project.json'), 'utf-8'));
126
128
  if (nxJson.name)
127
129
  name = nxJson.name;
128
130
  }
@@ -150,7 +152,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
150
152
  // If patterns exist from pnpm-workspace.yaml, search those roots
151
153
  if (patterns.length > 0) {
152
154
  for (const pat of patterns) {
153
- const cleanPat = pat.replace(/\/\*\*?$/, "").replace(/\/\*$/, "");
155
+ const cleanPat = pat.replace(/\/\*\*?$/, '').replace(/\/\*$/, '');
154
156
  const searchRoot = path.join(cwd, cleanPat);
155
157
  if (fs.existsSync(searchRoot)) {
156
158
  const stat = fs.statSync(searchRoot);
@@ -167,7 +169,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
167
169
  }
168
170
  // Fallback search in standard directories if nothing found
169
171
  if (foundProjects.length === 0) {
170
- for (const fallback of ["modules", "packages", "apps", "libs", "features"]) {
172
+ for (const fallback of ['modules', 'packages', 'apps', 'libs', 'features']) {
171
173
  const fbPath = path.join(cwd, fallback);
172
174
  if (fs.existsSync(fbPath)) {
173
175
  search(fbPath, 1, 3);
@@ -183,16 +185,16 @@ export async function initCommand(options = {}) {
183
185
  p.log.warn(chalk.yellow(`Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`));
184
186
  return;
185
187
  }
186
- p.intro(chalk.bold.cyan("✨ TestSpectra Enterprise Scaffolder"));
188
+ p.intro(chalk.bold.cyan('✨ TestSpectra Enterprise Scaffolder'));
187
189
  // Detect monorepo environment
188
190
  let isMonorepo = false;
189
191
  let pnpmPatterns = [];
190
- const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
191
- const nxJsonPath = path.join(cwd, "nx.json");
192
+ const pnpmWorkspacePath = path.join(cwd, 'pnpm-workspace.yaml');
193
+ const nxJsonPath = path.join(cwd, 'nx.json');
192
194
  if (fs.existsSync(pnpmWorkspacePath)) {
193
195
  isMonorepo = true;
194
196
  try {
195
- const content = fs.readFileSync(pnpmWorkspacePath, "utf-8");
197
+ const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
196
198
  pnpmPatterns = parsePnpmWorkspaceGlobs(content);
197
199
  }
198
200
  catch { }
@@ -204,28 +206,33 @@ export async function initCommand(options = {}) {
204
206
  let selectedTemplate = options.template;
205
207
  if (!selectedTemplate && !options.force) {
206
208
  const templateChoice = await p.select({
207
- message: "Select TestSpectra setup mode:",
208
- initialValue: isMonorepo ? "nx" : "default",
209
+ message: 'Select TestSpectra setup mode:',
210
+ initialValue: isMonorepo ? 'nx' : 'default',
209
211
  options: [
210
212
  {
211
- label: "Nx Monorepo (Recommended)",
212
- value: "nx",
213
- hint: "Interactive workspace feature discovery & centralized config",
213
+ label: 'Nx Monorepo (Recommended)',
214
+ value: 'nx',
215
+ hint: 'Interactive workspace feature discovery & centralized config',
214
216
  },
215
217
  {
216
- label: "Standard Project",
217
- value: "default",
218
- hint: "Standalone single-suite E2E testing project",
218
+ label: 'Standard E2E Project',
219
+ value: 'default',
220
+ hint: 'Standalone single-suite E2E testing project',
221
+ },
222
+ {
223
+ label: 'React Component Testing',
224
+ value: 'react-component',
225
+ hint: 'Real-browser React component testing with Vite & Testing Library',
219
226
  },
220
227
  ],
221
228
  });
222
229
  if (p.isCancel(templateChoice)) {
223
- p.cancel("Setup cancelled.");
230
+ p.cancel('Setup cancelled.');
224
231
  return;
225
232
  }
226
233
  selectedTemplate = templateChoice;
227
234
  }
228
- const templateName = selectedTemplate || (isMonorepo ? "nx" : "default");
235
+ const templateName = selectedTemplate || (isMonorepo ? 'nx' : 'default');
229
236
  // 1. Resolve template directory
230
237
  const __filename = fileURLToPath(import.meta.url);
231
238
  const __dirname = path.dirname(__filename);
@@ -241,14 +248,14 @@ export async function initCommand(options = {}) {
241
248
  return;
242
249
  }
243
250
  // 2. Resolve CLI dependency version
244
- let cliVersion = "^1.0.65";
251
+ let cliVersion = '^1.0.65';
245
252
  try {
246
253
  let pDir = __dirname;
247
254
  while (pDir !== path.dirname(pDir)) {
248
- const cand = path.join(pDir, "package.json");
255
+ const cand = path.join(pDir, 'package.json');
249
256
  if (fs.existsSync(cand)) {
250
- const cliPkg = JSON.parse(fs.readFileSync(cand, "utf-8"));
251
- if (cliPkg.name === "@testspectra/cli" && cliPkg.version) {
257
+ const cliPkg = JSON.parse(fs.readFileSync(cand, 'utf-8'));
258
+ if (cliPkg.name === '@testspectra/cli' && cliPkg.version) {
252
259
  cliVersion = `^${cliPkg.version}`;
253
260
  break;
254
261
  }
@@ -258,11 +265,11 @@ export async function initCommand(options = {}) {
258
265
  }
259
266
  catch { }
260
267
  const projectName = path.basename(cwd);
261
- const cliDepVersion = cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`;
268
+ const cliDepVersion = cliVersion.startsWith('^') ? cliVersion : `^${cliVersion}`;
262
269
  // Helper: Copy directory contents recursively
263
270
  function copyRecursive(src, dest, replacements = {}) {
264
271
  const base = path.basename(src);
265
- if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
272
+ if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git') {
266
273
  return;
267
274
  }
268
275
  const stat = fs.statSync(src);
@@ -279,26 +286,30 @@ export async function initCommand(options = {}) {
279
286
  if (fs.existsSync(dest) && !options.force) {
280
287
  return;
281
288
  }
282
- let content = fs.readFileSync(src, "utf-8");
289
+ let content = fs.readFileSync(src, 'utf-8');
283
290
  for (const [k, v] of Object.entries(replacements)) {
284
291
  content = content.replaceAll(k, v);
285
292
  }
286
- if (dest.endsWith("package.json")) {
293
+ if (dest.endsWith('package.json')) {
287
294
  try {
288
295
  const pkg = JSON.parse(content);
289
- if (!pkg.name || pkg.name.startsWith("@testspectra/template-") || pkg.name === "spectra-monorepo") {
296
+ if (!pkg.name || pkg.name.startsWith('@testspectra/template-') || pkg.name === 'spectra-monorepo') {
290
297
  pkg.name = projectName;
291
298
  }
292
299
  if (pkg.devDependencies) {
293
300
  for (const key of Object.keys(pkg.devDependencies)) {
294
- if (key.startsWith("@testspectra/") || pkg.devDependencies[key] === "workspace:*") {
301
+ if (key.startsWith('@testspectra/') ||
302
+ pkg.devDependencies[key] === 'workspace:*' ||
303
+ (typeof pkg.devDependencies[key] === 'string' && pkg.devDependencies[key].startsWith('link:'))) {
295
304
  pkg.devDependencies[key] = cliDepVersion;
296
305
  }
297
306
  }
298
307
  }
299
308
  if (pkg.dependencies) {
300
309
  for (const key of Object.keys(pkg.dependencies)) {
301
- if (key.startsWith("@testspectra/") || pkg.dependencies[key] === "workspace:*") {
310
+ if (key.startsWith('@testspectra/') ||
311
+ pkg.dependencies[key] === 'workspace:*' ||
312
+ (typeof pkg.dependencies[key] === 'string' && pkg.dependencies[key].startsWith('link:'))) {
302
313
  pkg.dependencies[key] = cliDepVersion;
303
314
  }
304
315
  }
@@ -307,11 +318,11 @@ export async function initCommand(options = {}) {
307
318
  }
308
319
  catch { }
309
320
  }
310
- fs.writeFileSync(dest, content, "utf-8");
321
+ fs.writeFileSync(dest, content, 'utf-8');
311
322
  }
312
323
  }
313
324
  // --- MONOREPO INTERACTIVE FLOW ---
314
- if (templateName === "nx") {
325
+ if (templateName === 'nx') {
315
326
  let chosenProjects = [];
316
327
  if (options.force) {
317
328
  chosenProjects = detectedProjects.map((p) => p.relPath);
@@ -319,86 +330,81 @@ export async function initCommand(options = {}) {
319
330
  else if (detectedProjects.length > 0) {
320
331
  p.log.step(chalk.cyan(`Monorepo Auto-Discovery: Found ${detectedProjects.length} workspace feature package(s)`));
321
332
  const projectChoices = await p.multiselect({
322
- message: "Select workspace features to initialize with TestSpectra:",
333
+ message: 'Select workspace features to initialize with TestSpectra:',
323
334
  options: detectedProjects.map((proj) => ({
324
335
  value: proj.relPath,
325
336
  label: `${proj.relPath} (${proj.name})`,
326
- hint: proj.hasNxProject ? "Nx project.json" : "package.json",
337
+ hint: proj.hasNxProject ? 'Nx project.json' : 'package.json',
327
338
  })),
328
339
  initialValues: detectedProjects.map((p) => p.relPath),
329
340
  required: true,
330
341
  });
331
342
  if (p.isCancel(projectChoices)) {
332
- p.cancel("Setup cancelled.");
343
+ p.cancel('Setup cancelled.');
333
344
  return;
334
345
  }
335
346
  chosenProjects = projectChoices;
336
347
  }
337
348
  else {
338
349
  // No packages auto-detected — ask user to manually enter feature names
339
- p.log.warn(chalk.yellow("No feature packages found in workspace. You can enter feature names to scaffold."));
350
+ p.log.warn(chalk.yellow('No feature packages found in workspace. You can enter feature names to scaffold.'));
340
351
  const featureNamesInput = await p.text({
341
- message: "Enter feature module names (comma-separated), or leave blank to skip:",
342
- placeholder: "playground, matchers, navigation",
343
- defaultValue: "",
352
+ message: 'Enter feature module names (comma-separated), or leave blank to skip:',
353
+ placeholder: 'playground, matchers, navigation',
354
+ defaultValue: '',
344
355
  });
345
356
  if (p.isCancel(featureNamesInput)) {
346
- p.cancel("Setup cancelled.");
357
+ p.cancel('Setup cancelled.');
347
358
  return;
348
359
  }
349
360
  const raw = featureNamesInput.trim();
350
361
  if (raw) {
351
362
  // Build relPaths from feature names using a sensible default location
352
363
  const featureBaseInput = await p.text({
353
- message: "Enter base directory for feature modules:",
354
- placeholder: "packages",
355
- defaultValue: "packages",
364
+ message: 'Enter base directory for feature modules:',
365
+ placeholder: 'packages',
366
+ defaultValue: 'packages',
356
367
  });
357
368
  if (p.isCancel(featureBaseInput)) {
358
- p.cancel("Setup cancelled.");
369
+ p.cancel('Setup cancelled.');
359
370
  return;
360
371
  }
361
- const featureBase = featureBaseInput.trim() || "packages";
362
- chosenProjects = raw.split(",").map((n) => `${featureBase}/${n.trim()}`).filter(Boolean);
372
+ const featureBase = featureBaseInput.trim() || 'packages';
373
+ chosenProjects = raw
374
+ .split(',')
375
+ .map((n) => `${featureBase}/${n.trim()}`)
376
+ .filter(Boolean);
363
377
  }
364
378
  }
365
- let e2eFolderName = "e2e";
366
- let sharedTestingRelPath = "shared/testing";
379
+ let e2eFolderName = 'e2e';
380
+ let sharedTestingRelPath = 'shared/testing';
367
381
  if (!options.force) {
368
382
  const e2eFolderNameInput = await p.text({
369
- message: "Enter E2E test folder name for each selected feature:",
370
- placeholder: "e2e",
371
- defaultValue: "e2e",
383
+ message: 'Enter E2E test folder name for each selected feature:',
384
+ placeholder: 'e2e',
385
+ defaultValue: 'e2e',
372
386
  });
373
387
  if (p.isCancel(e2eFolderNameInput)) {
374
- p.cancel("Setup cancelled.");
388
+ p.cancel('Setup cancelled.');
375
389
  return;
376
390
  }
377
391
  const sharedTestingPathInput = await p.text({
378
- message: "Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):",
379
- placeholder: "shared/testing",
380
- defaultValue: "shared/testing",
392
+ message: 'Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):',
393
+ placeholder: 'shared/testing',
394
+ defaultValue: 'shared/testing',
381
395
  });
382
396
  if (p.isCancel(sharedTestingPathInput)) {
383
- p.cancel("Setup cancelled.");
397
+ p.cancel('Setup cancelled.');
384
398
  return;
385
399
  }
386
- e2eFolderName = e2eFolderNameInput.trim() || "e2e";
387
- sharedTestingRelPath = sharedTestingPathInput.trim() || "shared/testing";
400
+ e2eFolderName = e2eFolderNameInput.trim() || 'e2e';
401
+ sharedTestingRelPath = sharedTestingPathInput.trim() || 'shared/testing';
388
402
  }
389
403
  const sharedTestingAbsPath = path.join(cwd, sharedTestingRelPath);
390
404
  const s = p.spinner();
391
- s.start("Scaffolding distributed monorepo structure and ambient types...");
405
+ s.start('Scaffolding distributed monorepo structure and ambient types...');
392
406
  // 1. Centralized Root Configs & Orchestration
393
- const rootConfigFiles = [
394
- "spectra.config.ts",
395
- "tsconfig.spectra.json",
396
- "tsconfig.spectra.shared.json",
397
- "tsconfig.spectra.web.json",
398
- "tsconfig.spectra.android.json",
399
- "tsconfig.spectra.ios.json",
400
- "nx.json",
401
- ];
407
+ const rootConfigFiles = ['spectra.config.ts', 'nx.json'];
402
408
  for (const cf of rootConfigFiles) {
403
409
  const src = path.join(templateDir, cf);
404
410
  const dest = path.join(cwd, cf);
@@ -406,31 +412,33 @@ export async function initCommand(options = {}) {
406
412
  fs.copyFileSync(src, dest);
407
413
  }
408
414
  }
415
+ // Write tsconfig.json and auto-generate .testspectra/tsconfig.json and sub-configs
416
+ TsConfigGenerator.scaffoldConfigs(cwd, options.force);
409
417
  // Ensure root package.json has TestSpectra devDependencies
410
- const rootPkgPath = path.join(cwd, "package.json");
418
+ const rootPkgPath = path.join(cwd, 'package.json');
411
419
  if (!fs.existsSync(rootPkgPath)) {
412
- const templatePkgSrc = path.join(templateDir, "package.json");
420
+ const templatePkgSrc = path.join(templateDir, 'package.json');
413
421
  if (fs.existsSync(templatePkgSrc)) {
414
422
  try {
415
- const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc, "utf-8"));
416
- if (!tPkg.name || tPkg.name.startsWith("@testspectra/template-") || tPkg.name === "spectra-monorepo") {
423
+ const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc, 'utf-8'));
424
+ if (!tPkg.name || tPkg.name.startsWith('@testspectra/template-') || tPkg.name === 'spectra-monorepo') {
417
425
  tPkg.name = projectName;
418
426
  }
419
427
  if (tPkg.devDependencies) {
420
428
  for (const key of Object.keys(tPkg.devDependencies)) {
421
- if (key.startsWith("@testspectra/") || tPkg.devDependencies[key] === "workspace:*") {
429
+ if (key.startsWith('@testspectra/') || tPkg.devDependencies[key] === 'workspace:*') {
422
430
  tPkg.devDependencies[key] = cliDepVersion;
423
431
  }
424
432
  }
425
433
  }
426
434
  if (tPkg.dependencies) {
427
435
  for (const key of Object.keys(tPkg.dependencies)) {
428
- if (key.startsWith("@testspectra/") || tPkg.dependencies[key] === "workspace:*") {
436
+ if (key.startsWith('@testspectra/') || tPkg.dependencies[key] === 'workspace:*') {
429
437
  tPkg.dependencies[key] = cliDepVersion;
430
438
  }
431
439
  }
432
440
  }
433
- fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2), "utf-8");
441
+ fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2), 'utf-8');
434
442
  }
435
443
  catch {
436
444
  fs.copyFileSync(templatePkgSrc, rootPkgPath);
@@ -439,107 +447,116 @@ export async function initCommand(options = {}) {
439
447
  }
440
448
  else {
441
449
  try {
442
- const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
450
+ const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));
443
451
  if (!rootPkg.devDependencies)
444
452
  rootPkg.devDependencies = {};
445
- if (!rootPkg.devDependencies["@testspectra/cli"] || rootPkg.devDependencies["@testspectra/cli"] === "workspace:*")
446
- rootPkg.devDependencies["@testspectra/cli"] = cliDepVersion;
447
- if (!rootPkg.devDependencies["@testspectra/matchers"] || rootPkg.devDependencies["@testspectra/matchers"] === "workspace:*")
448
- rootPkg.devDependencies["@testspectra/matchers"] = cliDepVersion;
449
- if (!rootPkg.devDependencies["typescript"])
450
- rootPkg.devDependencies["typescript"] = "^5.4.5";
451
- if (!rootPkg.devDependencies["@wdio/cli"])
452
- rootPkg.devDependencies["@wdio/cli"] = "^9.2.8";
453
- if (!rootPkg.devDependencies["@wdio/local-runner"])
454
- rootPkg.devDependencies["@wdio/local-runner"] = "^9.2.8";
455
- if (!rootPkg.devDependencies["@wdio/spec-reporter"])
456
- rootPkg.devDependencies["@wdio/spec-reporter"] = "^9.2.8";
457
- if (!rootPkg.devDependencies["@wdio/devtools-service"])
458
- rootPkg.devDependencies["@wdio/devtools-service"] = "^8.46.0";
459
- if (!rootPkg.devDependencies["@wdio/globals"])
460
- rootPkg.devDependencies["@wdio/globals"] = "^9.2.8";
461
- if (!rootPkg.devDependencies["@wdio/mocha-framework"])
462
- rootPkg.devDependencies["@wdio/mocha-framework"] = "^9.2.8";
463
- if (!rootPkg.devDependencies["@types/node"])
464
- rootPkg.devDependencies["@types/node"] = "^20.14.0";
465
- if (!rootPkg.devDependencies["webdriverio"])
466
- rootPkg.devDependencies["webdriverio"] = "^9.2.8";
467
- fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2), "utf-8");
453
+ if (!rootPkg.devDependencies['@testspectra/cli'] ||
454
+ rootPkg.devDependencies['@testspectra/cli'] === 'workspace:*' ||
455
+ (typeof rootPkg.devDependencies['@testspectra/cli'] === 'string' &&
456
+ rootPkg.devDependencies['@testspectra/cli'].startsWith('link:')))
457
+ rootPkg.devDependencies['@testspectra/cli'] = cliDepVersion;
458
+ if (!rootPkg.devDependencies['@testspectra/matchers'] ||
459
+ rootPkg.devDependencies['@testspectra/matchers'] === 'workspace:*' ||
460
+ (typeof rootPkg.devDependencies['@testspectra/matchers'] === 'string' &&
461
+ rootPkg.devDependencies['@testspectra/matchers'].startsWith('link:')))
462
+ rootPkg.devDependencies['@testspectra/matchers'] = cliDepVersion;
463
+ if (!rootPkg.devDependencies['typescript'])
464
+ rootPkg.devDependencies['typescript'] = '^5.4.5';
465
+ if (!rootPkg.devDependencies['@wdio/cli'])
466
+ rootPkg.devDependencies['@wdio/cli'] = '^9.2.8';
467
+ if (!rootPkg.devDependencies['@wdio/local-runner'])
468
+ rootPkg.devDependencies['@wdio/local-runner'] = '^9.2.8';
469
+ if (!rootPkg.devDependencies['@wdio/spec-reporter'])
470
+ rootPkg.devDependencies['@wdio/spec-reporter'] = '^9.2.8';
471
+ if (!rootPkg.devDependencies['@wdio/devtools-service'])
472
+ rootPkg.devDependencies['@wdio/devtools-service'] = '^8.46.0';
473
+ if (!rootPkg.devDependencies['@wdio/globals'])
474
+ rootPkg.devDependencies['@wdio/globals'] = '^9.2.8';
475
+ if (!rootPkg.devDependencies['@wdio/mocha-framework'])
476
+ rootPkg.devDependencies['@wdio/mocha-framework'] = '^9.2.8';
477
+ if (!rootPkg.devDependencies['@types/node'])
478
+ rootPkg.devDependencies['@types/node'] = '^20.14.0';
479
+ if (!rootPkg.devDependencies['webdriverio'])
480
+ rootPkg.devDependencies['webdriverio'] = '^9.2.8';
481
+ if (!rootPkg.scripts)
482
+ rootPkg.scripts = {};
483
+ if (!rootPkg.scripts['postinstall'])
484
+ rootPkg.scripts['postinstall'] = 'spectra sync-types';
485
+ if (!rootPkg.scripts['type-check'])
486
+ rootPkg.scripts['type-check'] = 'tsc -b';
487
+ fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2), 'utf-8');
468
488
  }
469
489
  catch { }
470
490
  }
471
491
  // 2. Centralized Root .testspectra folder for cache/logs
472
- const rootTestDataDir = path.join(cwd, ".testspectra");
492
+ const rootTestDataDir = path.join(cwd, '.testspectra');
473
493
  if (!fs.existsSync(rootTestDataDir)) {
474
494
  fs.mkdirSync(rootTestDataDir, { recursive: true });
475
495
  }
476
496
  // 3. Centralized Shared Testing Library
477
- const sharedTestingSrc = path.join(templateDir, "shared/testing");
497
+ const sharedTestingSrc = path.join(templateDir, 'shared/testing');
478
498
  if (fs.existsSync(sharedTestingSrc)) {
479
499
  copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
480
500
  }
481
501
  // 4. Distribute E2E Folders to Selected Feature Modules
482
- const sampleE2eSrc = path.join(templateDir, "packages/playground/e2e");
502
+ const sampleE2eSrc = path.join(templateDir, 'packages/playground/e2e');
483
503
  const featureE2eDirs = [];
484
504
  for (const projRel of chosenProjects) {
485
505
  const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
486
506
  const featureName = path.basename(projRel);
487
507
  const e2eProjectName = `${featureName}-${e2eFolderName}`;
488
508
  copyRecursive(sampleE2eSrc, targetE2eDir, {
489
- "playground-e2e": e2eProjectName,
490
- "packages/playground/e2e": path.relative(cwd, targetE2eDir).replace(/\\/g, "/"),
491
- "scope:playground": `scope:${featureName}`,
509
+ 'playground-e2e': e2eProjectName,
510
+ 'packages/playground/e2e': path.relative(cwd, targetE2eDir).replace(/\\/g, '/'),
511
+ 'scope:playground': `scope:${featureName}`,
492
512
  });
493
513
  // Adjust project.json
494
- const projJsonPath = path.join(targetE2eDir, "project.json");
514
+ const projJsonPath = path.join(targetE2eDir, 'project.json');
495
515
  if (fs.existsSync(projJsonPath)) {
496
516
  try {
497
- const pObj = JSON.parse(fs.readFileSync(projJsonPath, "utf-8"));
517
+ const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
498
518
  pObj.name = e2eProjectName;
499
- pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
500
- pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
501
- pObj.targets["type-check"].options.cwd = ".";
502
- pObj.tags = [`scope:${featureName}`, "type:e2e"];
503
- fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), "utf-8");
519
+ pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
520
+ pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
521
+ pObj.targets['type-check'].options.cwd = '.';
522
+ pObj.tags = [`scope:${featureName}`, 'type:e2e'];
523
+ fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
504
524
  }
505
525
  catch { }
506
526
  }
507
527
  // Ensure no local spectra.config.ts (centralized in root)
508
- const localConfig = path.join(targetE2eDir, "spectra.config.ts");
528
+ const localConfig = path.join(targetE2eDir, 'spectra.config.ts');
509
529
  if (fs.existsSync(localConfig))
510
530
  fs.unlinkSync(localConfig);
511
531
  featureE2eDirs.push(targetE2eDir);
512
532
  }
513
533
  // 5. Ensure pnpm-workspace.yaml includes shared library (feature modules are already part of existing workspace globs)
514
- const pnpmWorkspaceFilePath = path.join(cwd, "pnpm-workspace.yaml");
534
+ const pnpmWorkspaceFilePath = path.join(cwd, 'pnpm-workspace.yaml');
515
535
  ensurePnpmWorkspaceIncludes(pnpmWorkspaceFilePath, sharedTestingRelPath);
516
- // 6. Update Root Solution tsconfig.json to reference tsconfig.spectra.json
517
- const rootTsConfigPath = path.join(cwd, "tsconfig.json");
536
+ // 6. Update Root Solution tsconfig.json to reference .testspectra/tsconfig.json
537
+ const rootTsConfigPath = path.join(cwd, 'tsconfig.json');
518
538
  let rootTsConfig = { files: [], references: [] };
519
539
  if (fs.existsSync(rootTsConfigPath)) {
520
540
  try {
521
- rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, "utf-8"));
541
+ rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, 'utf-8'));
522
542
  if (!rootTsConfig.references)
523
543
  rootTsConfig.references = [];
524
- if (!rootTsConfig.references.some((r) => r.path === "./tsconfig.spectra.json")) {
525
- rootTsConfig.references.push({ path: "./tsconfig.spectra.json" });
544
+ rootTsConfig.references = rootTsConfig.references.filter((r) => r.path !== './tsconfig.spectra.json');
545
+ if (!rootTsConfig.references.some((r) => r.path === './.testspectra/tsconfig.json')) {
546
+ rootTsConfig.references.push({ path: './.testspectra/tsconfig.json' });
526
547
  }
527
548
  }
528
549
  catch {
529
- rootTsConfig = { files: [], references: [{ path: "./tsconfig.spectra.json" }] };
550
+ rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
530
551
  }
531
552
  }
532
553
  else {
533
- rootTsConfig = { files: [], references: [{ path: "./tsconfig.spectra.json" }] };
554
+ rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
534
555
  }
535
- fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), "utf-8");
556
+ fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), 'utf-8');
536
557
  // 7. Generate Ambient Types in ROOT only
537
558
  TypeGenerator.writeDeclarationFiles(cwd);
538
559
  ensureVsCodeExtensions(cwd);
539
- const sharedPoLine = `${sharedTestingRelPath}/page-objects`.padEnd(28, " ");
540
- const sharedStepsLine = `${sharedTestingRelPath}/steps`.padEnd(28, " ");
541
- const sharedActLine = `${sharedTestingRelPath}/actions`.padEnd(28, " ");
542
- const sharedFixLine = `${sharedTestingRelPath}/fixtures`.padEnd(28, " ");
543
560
  const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
544
561
 
545
562
  This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
@@ -550,14 +567,10 @@ This workspace uses TestSpectra's **"Centralized Configuration, Distributed Impl
550
567
 
551
568
  ### Centralized Root Elements
552
569
  - \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
553
- - \`./.testspectra/\`: Local test project cache (execution logs, temp artifacts, and universal ambient types in \`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
554
- - \`./tsconfig.spectra.json\`: Solution TypeScript entry point equipped with TestSpectra language service plugin.
555
- - \`./tsconfig.spectra.web.json\`: Strongly-typed composite configuration for Web platform tests.
556
- - \`./tsconfig.spectra.android.json\`: Strongly-typed composite configuration for Android platform tests.
557
- - \`./tsconfig.spectra.ios.json\`: Strongly-typed composite configuration for iOS platform tests.
570
+ - \`./.testspectra/\`: Local test project cache, runtime metadata, auto-generated master TSConfig (\`.testspectra/tsconfig.json\`), platform sub-configs (\`.testspectra/tsconfig/\`), and universal ambient types (\`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
558
571
  - \`./nx.json\`: Nx target defaults with execution caching and inputs.
559
572
  - \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
560
- - \`./tsconfig.json\`: Solution-style TypeScript orchestrator linking all distributed E2E projects and shared libraries.
573
+ - \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
561
574
 
562
575
  ### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
563
576
  Provides cross-feature reusable test entities with **100% zero-import global resolution**:
@@ -567,7 +580,8 @@ Provides cross-feature reusable test entities with **100% zero-import global res
567
580
  - \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
568
581
 
569
582
  ### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
570
- ${featureE2eDirs.map((d) => {
583
+ ${featureE2eDirs
584
+ .map((d) => {
571
585
  const rel = path.relative(cwd, d);
572
586
  const name = path.basename(path.dirname(d));
573
587
  const e2eName = `${name}-${e2eFolderName}`;
@@ -575,7 +589,8 @@ ${featureE2eDirs.map((d) => {
575
589
  - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
576
590
  - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
577
591
  - \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
578
- }).join("\n")}
592
+ })
593
+ .join('\n')}
579
594
 
580
595
  ---
581
596
 
@@ -644,7 +659,7 @@ pnpm nx affected -t e2e
644
659
 
645
660
  \`\`\`bash
646
661
  # Navigate to the feature e2e folder
647
- cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, "/") : `modules/auth/${e2eFolderName}`}
662
+ cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
648
663
 
649
664
  # Run all specs in this feature
650
665
  pnpm spectra run
@@ -672,12 +687,12 @@ pnpm type-check
672
687
  # or: npx tsc -b
673
688
  \`\`\`
674
689
  `;
675
- fs.writeFileSync(path.join(cwd, "ARCHITECTURE.md"), archDocContent, "utf-8");
690
+ fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
676
691
  // 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
677
- const rootGitignorePath = path.join(cwd, ".gitignore");
678
- const ignoreEntries = [".testspectra/", "*.tsbuildinfo", ".nx/cache"];
692
+ const rootGitignorePath = path.join(cwd, '.gitignore');
693
+ const ignoreEntries = ['.testspectra/', '*.tsbuildinfo', '.nx/cache'];
679
694
  if (fs.existsSync(rootGitignorePath)) {
680
- let giContent = fs.readFileSync(rootGitignorePath, "utf-8");
695
+ let giContent = fs.readFileSync(rootGitignorePath, 'utf-8');
681
696
  let added = false;
682
697
  for (const entry of ignoreEntries) {
683
698
  if (!giContent.includes(entry)) {
@@ -686,42 +701,42 @@ pnpm type-check
686
701
  }
687
702
  }
688
703
  if (added) {
689
- fs.writeFileSync(rootGitignorePath, giContent.trim() + "\n", "utf-8");
704
+ fs.writeFileSync(rootGitignorePath, giContent.trim() + '\n', 'utf-8');
690
705
  }
691
706
  }
692
707
  else {
693
- fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join("\n")}\n`, "utf-8");
708
+ fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join('\n')}\n`, 'utf-8');
694
709
  }
695
710
  // 9. Safely append to root README.md (do not overwrite existing content)
696
- const rootReadmePath = path.join(cwd, "README.md");
711
+ const rootReadmePath = path.join(cwd, 'README.md');
697
712
  const archSection = `\n## E2E Testing Architecture (TestSpectra)\n\nThis workspace uses TestSpectra's **Centralized Configuration, Distributed Implementation** architecture.\nDetailed architecture, folder map, and zero-import mechanics are documented in: \nšŸ‘‰ **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run all E2E tests**: \`pnpm nx run-many -t e2e\`\n- **Run affected E2E tests**: \`pnpm nx affected -t e2e\`\n- **Type-check E2E suites**: \`pnpm type-check\`\n`;
698
713
  if (fs.existsSync(rootReadmePath)) {
699
- const existing = fs.readFileSync(rootReadmePath, "utf-8");
700
- if (!existing.includes("ARCHITECTURE.md")) {
701
- fs.appendFileSync(rootReadmePath, archSection, "utf-8");
714
+ const existing = fs.readFileSync(rootReadmePath, 'utf-8');
715
+ if (!existing.includes('ARCHITECTURE.md')) {
716
+ fs.appendFileSync(rootReadmePath, archSection, 'utf-8');
702
717
  }
703
718
  }
704
719
  else {
705
720
  const readmeContent = `# ${projectName}\n\nAutomated cross-platform E2E testing powered by [TestSpectra](https://github.com/testspectra).\n${archSection}`;
706
- fs.writeFileSync(rootReadmePath, readmeContent, "utf-8");
721
+ fs.writeFileSync(rootReadmePath, readmeContent, 'utf-8');
707
722
  }
708
- s.stop("Monorepo scaffolding complete!");
709
- p.log.success(chalk.green("Centralized Configuration: ./spectra.config.ts"));
710
- p.log.success(chalk.green("Centralized App Data/Cache: ./.testspectra/"));
723
+ s.stop('Monorepo scaffolding complete!');
724
+ p.log.success(chalk.green('Centralized Configuration: ./spectra.config.ts'));
725
+ p.log.success(chalk.green('Centralized App Data/Cache: ./.testspectra/'));
711
726
  p.log.success(chalk.green(`Shared Testing Library: ./${sharedTestingRelPath}`));
712
- p.log.success(chalk.green("Architecture Documentation: ./ARCHITECTURE.md"));
713
- p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join("\n")}`));
714
- p.outro(chalk.bold.green("šŸŽ‰ Enterprise Nx Monorepo ready for testing!"));
727
+ p.log.success(chalk.green('Architecture Documentation: ./ARCHITECTURE.md'));
728
+ p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join('\n')}`));
729
+ p.outro(chalk.bold.green('šŸŽ‰ Enterprise Nx Monorepo ready for testing!'));
715
730
  return;
716
731
  }
717
732
  // --- STANDALONE / DEFAULT PROJECT FLOW ---
718
733
  const s = p.spinner();
719
- s.start("Scaffolding standalone TestSpectra project...");
734
+ s.start('Scaffolding standalone TestSpectra project...');
720
735
  copyRecursive(templateDir, cwd);
721
- const standaloneWsPath = path.join(cwd, "pnpm-workspace.yaml");
736
+ const standaloneWsPath = path.join(cwd, 'pnpm-workspace.yaml');
722
737
  if (!fs.existsSync(standaloneWsPath) || options.force) {
723
738
  const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
724
- fs.writeFileSync(standaloneWsPath, pnpmWsContent, "utf-8");
739
+ fs.writeFileSync(standaloneWsPath, pnpmWsContent, 'utf-8');
725
740
  }
726
741
  TypeGenerator.writeDeclarationFiles(cwd);
727
742
  ensureVsCodeExtensions(cwd);
@@ -753,22 +768,22 @@ pnpm spectra run --target ios
753
768
  pnpm type-check
754
769
  \`\`\`
755
770
  `;
756
- fs.writeFileSync(path.join(cwd, "ARCHITECTURE.md"), standaloneArchDoc, "utf-8");
771
+ fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
757
772
  // Safely update root README.md (do not overwrite existing content)
758
- const standaloneReadmePath = path.join(cwd, "README.md");
773
+ const standaloneReadmePath = path.join(cwd, 'README.md');
759
774
  const standaloneArchSection = `\n## E2E Testing Architecture (TestSpectra)\n\nDetailed test architecture and folder map are documented in: \nšŸ‘‰ **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run tests**: \`pnpm test\`\n- **Type-check**: \`pnpm type-check\`\n`;
760
775
  if (fs.existsSync(standaloneReadmePath)) {
761
- const existing = fs.readFileSync(standaloneReadmePath, "utf-8");
762
- if (!existing.includes("ARCHITECTURE.md")) {
763
- fs.appendFileSync(standaloneReadmePath, standaloneArchSection, "utf-8");
776
+ const existing = fs.readFileSync(standaloneReadmePath, 'utf-8');
777
+ if (!existing.includes('ARCHITECTURE.md')) {
778
+ fs.appendFileSync(standaloneReadmePath, standaloneArchSection, 'utf-8');
764
779
  }
765
780
  }
766
781
  else {
767
782
  const standaloneReadme = `# ${projectName}\n\nTestSpectra automated testing project.\n${standaloneArchSection}`;
768
- fs.writeFileSync(standaloneReadmePath, standaloneReadme, "utf-8");
783
+ fs.writeFileSync(standaloneReadmePath, standaloneReadme, 'utf-8');
769
784
  }
770
- s.stop("Standalone project ready!");
771
- p.log.success(chalk.green("Generated ambient multi-platform types in .testspectra/types/"));
772
- p.log.success(chalk.green("Architecture documentation created at ./ARCHITECTURE.md"));
773
- p.outro(chalk.bold.green("šŸŽ‰ Project initialized successfully!"));
785
+ s.stop('Standalone project ready!');
786
+ p.log.success(chalk.green('Generated ambient multi-platform types in .testspectra/types/'));
787
+ p.log.success(chalk.green('Architecture documentation created at ./ARCHITECTURE.md'));
788
+ p.outro(chalk.bold.green('šŸŽ‰ Project initialized successfully!'));
774
789
  }