@testspectra/cli 1.1.7 → 1.1.8-rc.2

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 (261) hide show
  1. package/bin/.testspectra-runner.hash +1 -1
  2. package/bin/spectra.js +0 -0
  3. package/bin/testspectra-runner.exe +0 -0
  4. package/demo-app/assets/index-BhlW-eXG.css +1 -0
  5. package/demo-app/assets/index-DOtRypCa.js +154 -0
  6. package/demo-app/index.html +2 -2
  7. package/dist/.tsbuildinfo +1 -1
  8. package/dist/commands/__tests__/run-e2e.test.js +270 -29
  9. package/dist/commands/__tests__/run.test.js +92 -16
  10. package/dist/commands/demo.js +17 -7
  11. package/dist/commands/init.d.ts +1 -1
  12. package/dist/commands/init.js +154 -149
  13. package/dist/commands/lint.d.ts +5 -0
  14. package/dist/commands/lint.js +55 -0
  15. package/dist/commands/refactor.d.ts +2 -1
  16. package/dist/commands/refactor.js +129 -35
  17. package/dist/commands/run.d.ts +2 -3
  18. package/dist/commands/run.js +93 -28
  19. package/dist/commands/skills.d.ts +9 -0
  20. package/dist/commands/skills.js +28 -0
  21. package/dist/commands/sync-types.js +1 -0
  22. package/dist/commands/test.d.ts +17 -0
  23. package/dist/commands/test.js +104 -0
  24. package/dist/commands/watch.js +9 -3
  25. package/dist/config/loader.js +1 -1
  26. package/dist/config/schema.d.ts +5 -114
  27. package/dist/config/schema.js +1 -17
  28. package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
  29. package/dist/generator/__tests__/type-generator.test.js +1 -1
  30. package/dist/generator/tsconfig-generator.js +44 -16
  31. package/dist/generator/type-generator.d.ts +24 -1
  32. package/dist/generator/type-generator.js +104 -8
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +76 -40
  35. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  36. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  37. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  38. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  39. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  40. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  41. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  42. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  43. package/dist/linter/__tests__/step-parity.test.js +372 -0
  44. package/dist/linter/discovery.d.ts +30 -0
  45. package/dist/linter/discovery.js +249 -0
  46. package/dist/linter/extractor.d.ts +67 -0
  47. package/dist/linter/extractor.js +245 -0
  48. package/dist/linter/index.d.ts +7 -0
  49. package/dist/linter/index.js +7 -0
  50. package/dist/linter/parser.d.ts +24 -0
  51. package/dist/linter/parser.js +219 -0
  52. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  53. package/dist/linter/schemas/spec.schema.js +150 -0
  54. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  55. package/dist/linter/schemas/suite.schema.js +83 -0
  56. package/dist/linter/spec-linter.d.ts +23 -0
  57. package/dist/linter/spec-linter.js +630 -0
  58. package/dist/linter/types.d.ts +28 -0
  59. package/dist/linter/types.js +1 -0
  60. package/dist/refactor/__tests__/refactor-engine.test.js +698 -0
  61. package/dist/refactor/refactor-engine.d.ts +25 -0
  62. package/dist/refactor/refactor-engine.js +374 -42
  63. package/dist/reporter/semantic-formatter.js +22 -2
  64. package/dist/reporter/types.d.ts +1 -1
  65. package/dist/runner/bridge.d.ts +1 -1
  66. package/dist/runner/bridge.js +34 -21
  67. package/dist/runner/reporter.d.ts +2 -0
  68. package/dist/runner/reporter.js +11 -3
  69. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  70. package/dist/utils/__tests__/api-server.test.js +62 -0
  71. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  72. package/dist/utils/__tests__/demo-server.test.js +35 -0
  73. package/dist/utils/api-server.d.ts +19 -0
  74. package/dist/utils/api-server.js +179 -0
  75. package/dist/utils/demo-server.d.ts +5 -0
  76. package/dist/utils/demo-server.js +8 -1
  77. package/package.json +29 -25
  78. package/templates/default/CLAUDE.md +16 -11
  79. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  80. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  81. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  82. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  83. package/templates/default/package.json +3 -3
  84. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  85. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  86. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -46
  87. package/templates/default/page-objects/MatchersPage/web.ts +43 -37
  88. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  89. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  90. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -36
  91. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  92. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -11
  93. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -12
  94. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -27
  95. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  96. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  97. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -16
  98. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -12
  99. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -24
  100. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
  101. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  102. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  103. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  104. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  105. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  106. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  107. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  108. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  109. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  110. package/templates/default/specs/ApiInterception/suite.md +10 -9
  111. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  112. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  113. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  114. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  115. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  116. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  117. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  118. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  119. package/templates/default/specs/Authentication/suite.md +9 -0
  120. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  121. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -12
  122. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  123. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  124. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  125. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  126. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  127. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -12
  128. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  129. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -12
  130. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  131. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  132. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  133. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  134. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  135. package/templates/default/specs/DeepLink/suite.md +9 -0
  136. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  137. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -12
  138. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  139. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  140. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -12
  141. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  142. package/templates/default/specs/ElementMatchers/suite.md +10 -9
  143. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  144. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  145. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  146. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  147. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  148. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  149. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  150. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  151. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  152. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -12
  153. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  154. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  155. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -12
  156. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  157. package/templates/default/spectra.config.ts +52 -68
  158. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  159. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  160. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  161. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  162. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  163. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  164. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  165. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  166. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db +0 -0
  167. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm +0 -0
  168. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  169. package/templates/nx/.nx/workspace-data/d/daemon.log +1577 -168
  170. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  171. package/templates/nx/.nx/workspace-data/file-map.json +464 -0
  172. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  173. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  174. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  175. package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
  176. package/templates/nx/CLAUDE.md +10 -10
  177. package/templates/nx/package.json +3 -3
  178. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -3
  179. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  180. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -3
  181. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  182. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  183. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  184. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  185. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  186. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  187. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  188. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  189. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  190. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  191. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  192. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  193. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  194. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  195. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  196. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  197. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  198. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  199. package/templates/nx/pnpm-lock.yaml +6663 -6663
  200. package/templates/nx/pnpm-workspace.yaml +9 -9
  201. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  202. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -37
  203. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  204. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  205. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  206. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  207. package/templates/nx/shared/testing/project.json +19 -19
  208. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  209. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  210. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  211. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  212. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  213. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  214. package/templates/nx/spectra.config.ts +52 -68
  215. package/templates/react-component/.cursorrules +17 -0
  216. package/templates/react-component/.vscode/extensions.json +5 -0
  217. package/templates/react-component/CLAUDE.md +14 -0
  218. package/templates/react-component/README.md +69 -0
  219. package/templates/react-component/fixtures/component-mock.json +12 -0
  220. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  221. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  222. package/templates/react-component/package.json +27 -0
  223. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  224. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  225. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  226. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  227. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  228. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  229. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  230. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  231. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  232. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  233. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  234. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  235. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  236. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  237. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  238. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  239. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  240. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  241. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  242. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  243. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  244. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  245. package/templates/react-component/spectra.config.ts +27 -0
  246. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  247. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  248. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  249. package/templates/react-component/src/test-utils.tsx +23 -0
  250. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  251. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  252. package/templates/react-component/tsconfig.json +8 -0
  253. package/bin/testspectra-runner +0 -0
  254. package/demo-app/assets/index-DPfv5_EF.js +0 -116
  255. package/demo-app/assets/index-gIwu2EMV.css +0 -1
  256. package/dist/commands/__tests__/cloud-run.test.js +0 -93
  257. package/dist/commands/cloud-run.d.ts +0 -13
  258. package/dist/commands/cloud-run.js +0 -120
  259. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -17
  260. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -17
  261. /package/dist/{commands/__tests__/cloud-run.test.d.ts → linter/__tests__/anonymous-export.test.d.ts} +0 -0
@@ -1,24 +1,34 @@
1
1
  import chalk from 'chalk';
2
2
  import * as p from '@clack/prompts';
3
3
  import { DemoServer } from '../utils/demo-server.js';
4
+ import { DemoApiServer } from '../utils/api-server.js';
4
5
  export async function demoCommand(options) {
5
6
  const port = options.port ? parseInt(options.port, 10) : 5173;
6
7
  p.intro(chalk.bold.cyan('🌐 TestSpectra Demo Web App'));
7
8
  const server = new DemoServer(port);
9
+ const apiServer = new DemoApiServer();
8
10
  try {
9
11
  const { url } = await server.start();
10
12
  p.log.success(chalk.green(`Demo web app is live at: ${chalk.bold.underline(url)}`));
13
+ // The web app fetches its API sections from the separate self-hosted API server, so start it
14
+ // too — otherwise the API demo sections show only their offline fallback data.
15
+ try {
16
+ const { url: apiUrl } = await apiServer.start();
17
+ p.log.success(chalk.green(`Demo API server is live at: ${chalk.bold.underline(apiUrl)}`));
18
+ }
19
+ catch (err) {
20
+ p.log.warn(chalk.yellow(`Could not start demo API server: ${err.message}`));
21
+ }
11
22
  p.log.info(chalk.dim("You can now run tests against this server in another terminal (e.g. 'spectra run -t web')."));
12
- p.log.info(chalk.dim('Press Ctrl+C to stop the server.'));
23
+ p.log.info(chalk.dim('Press Ctrl+C to stop the servers.'));
13
24
  // Handle termination gracefully
14
- process.on('SIGINT', async () => {
25
+ const shutdown = async () => {
15
26
  await server.stop();
27
+ await apiServer.stop();
16
28
  process.exit(0);
17
- });
18
- process.on('SIGTERM', async () => {
19
- await server.stop();
20
- process.exit(0);
21
- });
29
+ };
30
+ process.on('SIGINT', shutdown);
31
+ process.on('SIGTERM', shutdown);
22
32
  await new Promise(() => { });
23
33
  }
24
34
  catch (err) {
@@ -1,5 +1,5 @@
1
1
  export interface InitCommandOptions {
2
2
  force?: boolean;
3
- template?: 'default' | 'nx';
3
+ template?: 'default' | 'nx' | 'react-component';
4
4
  }
5
5
  export declare function initCommand(options?: InitCommandOptions): Promise<void>;
@@ -215,10 +215,15 @@ export async function initCommand(options = {}) {
215
215
  hint: 'Interactive workspace feature discovery & centralized config',
216
216
  },
217
217
  {
218
- label: 'Standard Project',
218
+ label: 'Standard E2E Project',
219
219
  value: 'default',
220
220
  hint: 'Standalone single-suite E2E testing project',
221
221
  },
222
+ {
223
+ label: 'React Component Testing',
224
+ value: 'react-component',
225
+ hint: 'Real-browser React component testing with Vite & Testing Library',
226
+ },
222
227
  ],
223
228
  });
224
229
  if (p.isCancel(templateChoice)) {
@@ -552,135 +557,135 @@ export async function initCommand(options = {}) {
552
557
  // 7. Generate Ambient Types in ROOT only
553
558
  TypeGenerator.writeDeclarationFiles(cwd);
554
559
  ensureVsCodeExtensions(cwd);
555
- const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
556
-
557
- This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
558
-
559
- ---
560
-
561
- ## 1. Directory & File Overview
562
-
563
- ### Centralized Root Elements
564
- - \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
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/\`.
566
- - \`./nx.json\`: Nx target defaults with execution caching and inputs.
567
- - \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
568
- - \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
569
-
570
- ### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
571
- Provides cross-feature reusable test entities with **100% zero-import global resolution**:
572
- - \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
573
- - \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
574
- - \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
575
- - \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
576
-
577
- ### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
560
+ const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
561
+
562
+ This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
563
+
564
+ ---
565
+
566
+ ## 1. Directory & File Overview
567
+
568
+ ### Centralized Root Elements
569
+ - \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
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/\`.
571
+ - \`./nx.json\`: Nx target defaults with execution caching and inputs.
572
+ - \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
573
+ - \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
574
+
575
+ ### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
576
+ Provides cross-feature reusable test entities with **100% zero-import global resolution**:
577
+ - \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
578
+ - \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
579
+ - \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
580
+ - \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
581
+
582
+ ### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
578
583
  ${featureE2eDirs
579
584
  .map((d) => {
580
585
  const rel = path.relative(cwd, d);
581
586
  const name = path.basename(path.dirname(d));
582
587
  const e2eName = `${name}-${e2eFolderName}`;
583
- return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
584
- - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
585
- - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
588
+ return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
589
+ - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
590
+ - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
586
591
  - \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
587
592
  })
588
- .join('\n')}
589
-
590
- ---
591
-
592
- ## 2. Zero-Import Consumption Flow & Mechanics
593
-
594
- TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
595
-
596
- ### How It Works:
597
- 1. **Centralized Ambient Aggregation**:
598
- TestSpectra Language Service Plugin & CLI automatically scan:
599
- - **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
600
- - **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
601
- 2. **Ambient Typing Generation**:
602
- These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
603
- 3. **Instant IDE Autocomplete & Zero Clutter**:
604
- Specs consume both local feature objects and shared library utilities globally.
605
-
606
- ### Code Example:
607
- \`\`\`typescript
608
- // packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
609
- // 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
610
-
611
- it("should navigate via shared header and login with local POM", async () => {
612
- // 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
613
- await NavigationBar.goToLogin();
614
-
615
- // 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
616
- await Step.loginAsAdmin();
617
-
618
- // 3. Consumed from Local Feature POM (page-objects/LoginPage)
619
- await LoginPage.flashAlert.shouldBeVisible();
620
- await LoginPage.flashAlert.shouldContainText("Welcome");
621
-
622
- // 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
623
- await Spectra.dismissBanner();
624
- });
625
- \`\`\`
626
-
627
- ---
628
-
629
- ## 3. How to Run Tests
630
-
631
- You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
632
-
633
- ### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
634
-
635
- \`\`\`bash
636
- # 1. Run E2E for a SPECIFIC feature module (e.g. auth)
637
- pnpm nx run auth-${e2eFolderName}:e2e
638
-
639
- # 2. Run E2E for a specific feature on ANDROID or IOS
640
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
641
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
642
-
643
- # 3. Run E2E for a specific feature in HEADLESS browser mode
644
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
645
-
646
- # 4. Run ALL feature E2E suites across the entire monorepo in parallel
647
- pnpm nx run-many -t e2e
648
-
649
- # 5. Run only E2E tests for features modified in current Git branch / PR
650
- pnpm nx affected -t e2e
651
- \`\`\`
652
-
653
- ### Option B: From Inside the Feature Directory (Direct CLI)
654
-
655
- \`\`\`bash
656
- # Navigate to the feature e2e folder
657
- cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
658
-
659
- # Run all specs in this feature
660
- pnpm spectra run
661
-
662
- # Run a specific test case file
663
- pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
664
-
665
- # Target Android device or emulator
666
- pnpm spectra run --target android
667
-
668
- # Run in headed / interactive browser mode
669
- pnpm spectra run --no-headless
670
- \`\`\`
671
-
672
- ---
673
-
674
- ## 4. Type Checking & Validation
675
-
676
- \`\`\`bash
677
- # Type check all E2E projects via Nx
678
- pnpm nx run-many -t type-check
679
-
680
- # Or standard TypeScript project references build from root
681
- pnpm type-check
682
- # or: npx tsc -b
683
- \`\`\`
593
+ .join('\n')}
594
+
595
+ ---
596
+
597
+ ## 2. Zero-Import Consumption Flow & Mechanics
598
+
599
+ TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
600
+
601
+ ### How It Works:
602
+ 1. **Centralized Ambient Aggregation**:
603
+ TestSpectra Language Service Plugin & CLI automatically scan:
604
+ - **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
605
+ - **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
606
+ 2. **Ambient Typing Generation**:
607
+ These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
608
+ 3. **Instant IDE Autocomplete & Zero Clutter**:
609
+ Specs consume both local feature objects and shared library utilities globally.
610
+
611
+ ### Code Example:
612
+ \`\`\`typescript
613
+ // packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
614
+ // 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
615
+
616
+ it("should navigate via shared header and login with local POM", async () => {
617
+ // 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
618
+ await NavigationBar.goToLogin();
619
+
620
+ // 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
621
+ await Step.loginAsAdmin();
622
+
623
+ // 3. Consumed from Local Feature POM (page-objects/LoginPage)
624
+ await LoginPage.flashAlert.shouldBeVisible();
625
+ await LoginPage.flashAlert.shouldContainText("Welcome");
626
+
627
+ // 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
628
+ await Spectra.dismissBanner();
629
+ });
630
+ \`\`\`
631
+
632
+ ---
633
+
634
+ ## 3. How to Run Tests
635
+
636
+ You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
637
+
638
+ ### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
639
+
640
+ \`\`\`bash
641
+ # 1. Run E2E for a SPECIFIC feature module (e.g. auth)
642
+ pnpm nx run auth-${e2eFolderName}:e2e
643
+
644
+ # 2. Run E2E for a specific feature on ANDROID or IOS
645
+ pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
646
+ pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
647
+
648
+ # 3. Run E2E for a specific feature in HEADLESS browser mode
649
+ pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
650
+
651
+ # 4. Run ALL feature E2E suites across the entire monorepo in parallel
652
+ pnpm nx run-many -t e2e
653
+
654
+ # 5. Run only E2E tests for features modified in current Git branch / PR
655
+ pnpm nx affected -t e2e
656
+ \`\`\`
657
+
658
+ ### Option B: From Inside the Feature Directory (Direct CLI)
659
+
660
+ \`\`\`bash
661
+ # Navigate to the feature e2e folder
662
+ cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
663
+
664
+ # Run all specs in this feature
665
+ pnpm spectra run
666
+
667
+ # Run a specific test case file
668
+ pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
669
+
670
+ # Target Android device or emulator
671
+ pnpm spectra run --target android
672
+
673
+ # Run in headed / interactive browser mode
674
+ pnpm spectra run --no-headless
675
+ \`\`\`
676
+
677
+ ---
678
+
679
+ ## 4. Type Checking & Validation
680
+
681
+ \`\`\`bash
682
+ # Type check all E2E projects via Nx
683
+ pnpm nx run-many -t type-check
684
+
685
+ # Or standard TypeScript project references build from root
686
+ pnpm type-check
687
+ # or: npx tsc -b
688
+ \`\`\`
684
689
  `;
685
690
  fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
686
691
  // 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
@@ -736,32 +741,32 @@ pnpm type-check
736
741
  TypeGenerator.writeDeclarationFiles(cwd);
737
742
  ensureVsCodeExtensions(cwd);
738
743
  // Standalone Architecture Documentation
739
- const standaloneArchDoc = `# TestSpectra Project Architecture
740
-
741
- This project is built with the TestSpectra testing framework.
742
-
743
- ## Structure & File Map
744
- - \`./spectra.config.ts\`: Typed runner configuration.
745
- - \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
746
- - \`./page-objects/\`: Page Object models per platform.
747
- - \`./steps/\`: Business flows accessible globally via \`Step.*\`.
748
- - \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
749
- - \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
750
- - \`./global-hooks/\`: Global suite lifecycle hooks.
751
- - \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
752
-
753
- ## Execution
754
- \`\`\`bash
755
- # Run web test suite
756
- pnpm test
757
-
758
- # Run Android / iOS
759
- pnpm spectra run --target android
760
- pnpm spectra run --target ios
761
-
762
- # Type check
763
- pnpm type-check
764
- \`\`\`
744
+ const standaloneArchDoc = `# TestSpectra Project Architecture
745
+
746
+ This project is built with the TestSpectra testing framework.
747
+
748
+ ## Structure & File Map
749
+ - \`./spectra.config.ts\`: Typed runner configuration.
750
+ - \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
751
+ - \`./page-objects/\`: Page Object models per platform.
752
+ - \`./steps/\`: Business flows accessible globally via \`Step.*\`.
753
+ - \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
754
+ - \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
755
+ - \`./global-hooks/\`: Global suite lifecycle hooks.
756
+ - \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
757
+
758
+ ## Execution
759
+ \`\`\`bash
760
+ # Run web test suite
761
+ pnpm test
762
+
763
+ # Run Android / iOS
764
+ pnpm spectra run --target android
765
+ pnpm spectra run --target ios
766
+
767
+ # Type check
768
+ pnpm type-check
769
+ \`\`\`
765
770
  `;
766
771
  fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
767
772
  // Safely update root README.md (do not overwrite existing content)
@@ -0,0 +1,5 @@
1
+ import type { LintOptions } from '../linter/types.js';
2
+ export interface LintCommandOptions extends LintOptions {
3
+ path?: string;
4
+ }
5
+ export declare function lintCommand(targetPath?: string, options?: LintCommandOptions): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import { SpecLinter } from '../linter/spec-linter.js';
4
+ export async function lintCommand(targetPath, options = {}) {
5
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
6
+ try {
7
+ const result = SpecLinter.validateWorkspace(cwd, targetPath);
8
+ if (options.json) {
9
+ console.log(JSON.stringify(result, null, 2));
10
+ if (!result.valid || (options.strict && result.stats.warningCount > 0)) {
11
+ process.exit(1);
12
+ }
13
+ return;
14
+ }
15
+ console.log(chalk.bold('\n🔍 TestSpectra Spec & Code Linter\n'));
16
+ // Group diagnostics by file
17
+ const diagnosticsByFile = new Map();
18
+ for (const diag of result.diagnostics) {
19
+ const relPath = path.relative(cwd, diag.filePath);
20
+ const list = diagnosticsByFile.get(relPath) || [];
21
+ list.push(diag);
22
+ diagnosticsByFile.set(relPath, list);
23
+ }
24
+ if (diagnosticsByFile.size === 0) {
25
+ console.log(chalk.green(` ✓ ${result.stats.totalSpecs} specs and ${result.stats.totalSuites} suites verified.`));
26
+ console.log(chalk.green.bold('\n✓ All spec schemas are valid. 0 lint errors found.\n'));
27
+ return;
28
+ }
29
+ for (const [file, diags] of diagnosticsByFile.entries()) {
30
+ console.log(chalk.bold(` ${file}`));
31
+ for (const diag of diags) {
32
+ const icon = diag.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
33
+ const codeTag = chalk.gray(`[${diag.code}]`);
34
+ const loc = chalk.dim(`:${diag.line}`);
35
+ console.log(` ${icon} ${codeTag} ${diag.message} ${loc}`);
36
+ }
37
+ console.log();
38
+ }
39
+ const { errorCount, warningCount } = result.stats;
40
+ if (errorCount > 0) {
41
+ console.log(chalk.red.bold(`✖ ${errorCount} lint error${errorCount > 1 ? 's' : ''}${warningCount > 0 ? ` and ${warningCount} warning${warningCount > 1 ? 's' : ''}` : ''} found.\n`));
42
+ process.exit(1);
43
+ }
44
+ else if (warningCount > 0) {
45
+ console.log(chalk.yellow.bold(`⚠ ${warningCount} lint warning${warningCount > 1 ? 's' : ''} found.\n`));
46
+ if (options.strict) {
47
+ process.exit(1);
48
+ }
49
+ }
50
+ }
51
+ catch (err) {
52
+ console.error(chalk.red(`\n✖ Fatal error during linting: ${err?.message || err}\n`));
53
+ process.exit(2);
54
+ }
55
+ }
@@ -1,3 +1,4 @@
1
+ import { RefactorResult } from '../refactor/refactor-engine.js';
1
2
  export interface RefactorCommandOptions {
2
3
  cwd?: string;
3
4
  dryRun?: boolean;
@@ -5,4 +6,4 @@ export interface RefactorCommandOptions {
5
6
  from?: string;
6
7
  to?: string;
7
8
  }
8
- export declare function refactorCommand(entityType?: string, oldName?: string, newName?: string, options?: RefactorCommandOptions): Promise<void>;
9
+ export declare function refactorCommand(entityType?: string, oldName?: string, newName?: string, options?: RefactorCommandOptions): Promise<RefactorResult>;
@@ -5,52 +5,142 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
5
5
  let targetType = 'page-object';
6
6
  let fromSymbol = oldName || '';
7
7
  let toSymbol = newName || '';
8
- if (options.from && options.to) {
9
- // Path-based refactoring
10
- if (options.from.includes('page-objects')) {
11
- targetType = 'page-object';
12
- fromSymbol = path.basename(options.from);
13
- toSymbol = path.basename(options.to);
8
+ // 1. Path-based refactoring via --from and --to flags
9
+ if (options.from || options.to) {
10
+ if (!options.from || !options.to) {
11
+ const errorMsg = 'Both --from and --to must be specified for path-based refactoring.';
12
+ const result = {
13
+ status: 'failed',
14
+ entityType: 'page-object',
15
+ oldSymbol: options.from || '',
16
+ newSymbol: options.to || '',
17
+ modifiedFiles: [],
18
+ occurrencesReplaced: 0,
19
+ error: errorMsg,
20
+ };
21
+ if (options.json) {
22
+ console.log(JSON.stringify(result, null, 2));
23
+ }
24
+ else {
25
+ console.error(`\x1b[31m✗ ${errorMsg}\x1b[0m`);
26
+ }
27
+ process.exitCode = 1;
28
+ return result;
14
29
  }
15
- else if (options.from.includes('steps')) {
16
- targetType = 'step';
17
- fromSymbol = path.basename(options.from);
18
- toSymbol = path.basename(options.to);
30
+ const pathResult = await RefactorEngine.renameFromPaths({
31
+ workspaceRoot,
32
+ fromPath: options.from,
33
+ toPath: options.to,
34
+ dryRun: options.dryRun,
35
+ });
36
+ if (options.json) {
37
+ console.log(JSON.stringify(pathResult, null, 2));
38
+ if (pathResult.status === 'failed') {
39
+ process.exitCode = 1;
40
+ }
41
+ return pathResult;
19
42
  }
20
- else if (options.from.includes('actions')) {
21
- targetType = 'action';
22
- fromSymbol = path.basename(options.from);
23
- toSymbol = path.basename(options.to);
43
+ if (pathResult.status === 'success') {
44
+ const modeStr = options.dryRun ? '[Dry-Run] ' : '';
45
+ console.log(`\x1b[32m✓ ${modeStr}Refactored ${pathResult.entityType} "${pathResult.oldSymbol}" → "${pathResult.newSymbol}"\x1b[0m`);
46
+ console.log(` Modified files: ${pathResult.modifiedFiles.length}`);
47
+ console.log(` Occurrences replaced: ${pathResult.occurrencesReplaced}`);
48
+ for (const f of pathResult.modifiedFiles) {
49
+ console.log(` ↳ ${f}`);
50
+ }
24
51
  }
25
- else if (options.from.includes('fixtures')) {
26
- targetType = 'fixture';
27
- fromSymbol = path.parse(options.from).name;
28
- toSymbol = path.parse(options.to).name;
52
+ else {
53
+ console.error(`\x1b[31m✗ Refactoring failed: ${pathResult.error}\x1b[0m`);
54
+ process.exitCode = 1;
29
55
  }
56
+ return pathResult;
30
57
  }
31
- else {
32
- if (!entityType || !oldName || !newName) {
33
- console.error('Usage: spectra refactor <page-object|step|action|fixture> <oldName> <newName>');
34
- process.exit(1);
58
+ // 2. Also support positional path syntax: spectra refactor <fromPath> <toPath>
59
+ if (entityType && oldName && !newName && RefactorEngine.parseEntityFromPath(entityType)) {
60
+ const pathResult = await RefactorEngine.renameFromPaths({
61
+ workspaceRoot,
62
+ fromPath: entityType,
63
+ toPath: oldName,
64
+ dryRun: options.dryRun,
65
+ });
66
+ if (options.json) {
67
+ console.log(JSON.stringify(pathResult, null, 2));
68
+ if (pathResult.status === 'failed') {
69
+ process.exitCode = 1;
70
+ }
71
+ return pathResult;
35
72
  }
36
- const normalized = entityType.toLowerCase().replace(/_/g, '-');
37
- if (['page-object', 'po', 'page'].includes(normalized)) {
38
- targetType = 'page-object';
73
+ if (pathResult.status === 'success') {
74
+ const modeStr = options.dryRun ? '[Dry-Run] ' : '';
75
+ console.log(`\x1b[32m✓ ${modeStr}Refactored ${pathResult.entityType} "${pathResult.oldSymbol}" → "${pathResult.newSymbol}"\x1b[0m`);
76
+ console.log(` Modified files: ${pathResult.modifiedFiles.length}`);
77
+ console.log(` Occurrences replaced: ${pathResult.occurrencesReplaced}`);
78
+ for (const f of pathResult.modifiedFiles) {
79
+ console.log(` ↳ ${f}`);
80
+ }
39
81
  }
40
- else if (['step', 'steps'].includes(normalized)) {
41
- targetType = 'step';
82
+ else {
83
+ console.error(`\x1b[31m✗ Refactoring failed: ${pathResult.error}\x1b[0m`);
84
+ process.exitCode = 1;
42
85
  }
43
- else if (['action', 'actions'].includes(normalized)) {
44
- targetType = 'action';
86
+ return pathResult;
87
+ }
88
+ // 3. Positional syntax: spectra refactor <entityType> <oldName> <newName>
89
+ if (!entityType || !oldName || !newName) {
90
+ const errorMsg = 'Usage: spectra refactor <page-object|step|action|fixture> <oldName> <newName> or spectra refactor --from <path> --to <path>';
91
+ const result = {
92
+ status: 'failed',
93
+ entityType: 'page-object',
94
+ oldSymbol: oldName || '',
95
+ newSymbol: newName || '',
96
+ modifiedFiles: [],
97
+ occurrencesReplaced: 0,
98
+ error: errorMsg,
99
+ };
100
+ if (options.json) {
101
+ console.log(JSON.stringify(result, null, 2));
45
102
  }
46
- else if (['fixture', 'fixtures'].includes(normalized)) {
47
- targetType = 'fixture';
103
+ else {
104
+ console.error(errorMsg);
105
+ }
106
+ process.exitCode = 1;
107
+ return result;
108
+ }
109
+ const normalized = entityType.toLowerCase().replace(/_/g, '-');
110
+ if (['page-object', 'po', 'page', 'pageobject'].includes(normalized)) {
111
+ targetType = 'page-object';
112
+ }
113
+ else if (['step', 'steps'].includes(normalized)) {
114
+ targetType = 'step';
115
+ }
116
+ else if (['action', 'actions'].includes(normalized)) {
117
+ targetType = 'action';
118
+ }
119
+ else if (['fixture', 'fixtures'].includes(normalized)) {
120
+ targetType = 'fixture';
121
+ }
122
+ else {
123
+ const errorMsg = `Unknown refactor entity type: "${entityType}". Valid types: page-object, step, action, fixture.`;
124
+ const result = {
125
+ status: 'failed',
126
+ entityType: 'page-object',
127
+ oldSymbol: oldName,
128
+ newSymbol: newName,
129
+ modifiedFiles: [],
130
+ occurrencesReplaced: 0,
131
+ error: errorMsg,
132
+ };
133
+ if (options.json) {
134
+ console.log(JSON.stringify(result, null, 2));
48
135
  }
49
136
  else {
50
- console.error(`Unknown refactor entity type: "${entityType}". Valid types: page-object, step, action, fixture.`);
51
- process.exit(1);
137
+ console.error(`\x1b[31m✗ ${errorMsg}\x1b[0m`);
52
138
  }
139
+ process.exitCode = 1;
140
+ return result;
53
141
  }
142
+ fromSymbol = oldName;
143
+ toSymbol = newName;
54
144
  const result = await RefactorEngine.renameSymbol({
55
145
  workspaceRoot,
56
146
  type: targetType,
@@ -60,7 +150,10 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
60
150
  });
61
151
  if (options.json) {
62
152
  console.log(JSON.stringify(result, null, 2));
63
- return;
153
+ if (result.status === 'failed') {
154
+ process.exitCode = 1;
155
+ }
156
+ return result;
64
157
  }
65
158
  if (result.status === 'success') {
66
159
  const modeStr = options.dryRun ? '[Dry-Run] ' : '';
@@ -73,6 +166,7 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
73
166
  }
74
167
  else {
75
168
  console.error(`\x1b[31m✗ Refactoring failed: ${result.error}\x1b[0m`);
76
- process.exit(1);
169
+ process.exitCode = 1;
77
170
  }
171
+ return result;
78
172
  }