@testspectra/cli 1.0.70 → 1.1.0-rc.0

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