@testspectra/cli 1.1.8-rc.3 → 1.1.8-rc.31

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 (243) hide show
  1. package/README.md +50 -0
  2. package/bin/spectra.js +0 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/__tests__/doctor-filter.test.d.ts +1 -0
  5. package/dist/commands/__tests__/doctor-filter.test.js +37 -0
  6. package/dist/commands/__tests__/run-e2e.test.js +49 -49
  7. package/dist/commands/__tests__/test-error-streaming.test.d.ts +1 -0
  8. package/dist/commands/__tests__/test-error-streaming.test.js +85 -0
  9. package/dist/commands/__tests__/test-scaffolding.test.d.ts +1 -0
  10. package/dist/commands/__tests__/test-scaffolding.test.js +32 -0
  11. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  12. package/dist/commands/__tests__/upgrade.test.js +95 -0
  13. package/dist/commands/add.js +5 -4
  14. package/dist/commands/devices.js +1 -0
  15. package/dist/commands/docs.d.ts +4 -0
  16. package/dist/commands/docs.js +17 -0
  17. package/dist/commands/doctor.d.ts +9 -1
  18. package/dist/commands/doctor.js +17 -3
  19. package/dist/commands/init.js +7 -160
  20. package/dist/commands/install.js +5 -0
  21. package/dist/commands/license.js +1 -0
  22. package/dist/commands/run.d.ts +2 -0
  23. package/dist/commands/run.js +22 -0
  24. package/dist/commands/test.d.ts +13 -0
  25. package/dist/commands/test.js +115 -21
  26. package/dist/commands/upgrade.d.ts +12 -0
  27. package/dist/commands/upgrade.js +197 -0
  28. package/dist/config/schema.d.ts +53 -0
  29. package/dist/config/schema.js +2 -0
  30. package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
  31. package/dist/generator/__tests__/type-generator.test.js +38 -0
  32. package/dist/generator/architecture-doc-generator.d.ts +14 -0
  33. package/dist/generator/architecture-doc-generator.js +80 -0
  34. package/dist/generator/index.d.ts +1 -0
  35. package/dist/generator/index.js +1 -0
  36. package/dist/generator/tsconfig-generator.js +1 -1
  37. package/dist/generator/type-generator.d.ts +5 -2
  38. package/dist/generator/type-generator.js +14 -17
  39. package/dist/index.js +56 -26
  40. package/dist/linter/__tests__/component-test-imports.test.js +51 -51
  41. package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
  42. package/dist/linter/__tests__/spec-schema.test.js +167 -167
  43. package/dist/linter/discovery.d.ts +3 -3
  44. package/dist/linter/discovery.js +7 -7
  45. package/dist/linter/schemas/spec.schema.d.ts +8 -8
  46. package/dist/linter/schemas/suite.schema.d.ts +4 -4
  47. package/dist/runner/bridge.d.ts +1 -1
  48. package/dist/runner/bridge.js +101 -11
  49. package/dist/runner/reporter.d.ts +1 -0
  50. package/dist/runner/reporter.js +12 -6
  51. package/dist/utils/__tests__/dev-server-manager.test.d.ts +1 -0
  52. package/dist/utils/__tests__/dev-server-manager.test.js +72 -0
  53. package/dist/utils/background-update-check.d.ts +1 -0
  54. package/dist/utils/background-update-check.js +43 -0
  55. package/dist/utils/dependency-updates.d.ts +15 -0
  56. package/dist/utils/dependency-updates.js +107 -0
  57. package/dist/utils/dev-server-manager.d.ts +40 -0
  58. package/dist/utils/dev-server-manager.js +257 -0
  59. package/dist/utils/update-notifier.d.ts +2 -0
  60. package/dist/utils/update-notifier.js +67 -0
  61. package/package.json +36 -30
  62. package/templates/default/CLAUDE.md +16 -16
  63. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  64. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  65. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  66. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  67. package/templates/default/package.json +2 -2
  68. package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
  69. package/templates/default/page-objects/AuthPage/web.ts +10 -10
  70. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
  71. package/templates/default/page-objects/MatchersPage/web.ts +43 -43
  72. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  73. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  74. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -42
  75. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  76. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
  77. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
  78. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
  79. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
  80. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
  81. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
  82. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
  83. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
  84. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
  85. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
  86. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
  87. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
  88. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
  89. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
  90. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
  91. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
  92. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
  93. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
  94. package/templates/default/specs/ApiInterception/suite.md +10 -10
  95. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
  96. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
  97. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
  98. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
  99. package/templates/default/specs/ApiSeeding/suite.md +9 -9
  100. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
  101. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
  102. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
  103. package/templates/default/specs/Authentication/suite.md +9 -9
  104. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  105. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -17
  106. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  107. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -17
  108. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
  109. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
  110. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
  111. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
  112. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  113. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -17
  114. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  115. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -14
  116. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
  117. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
  118. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
  119. package/templates/default/specs/DeepLink/suite.md +9 -9
  120. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  121. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -17
  122. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  123. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  124. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -19
  125. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  126. package/templates/default/specs/ElementMatchers/suite.md +10 -10
  127. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  128. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  129. package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
  130. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
  131. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
  132. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
  133. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
  134. package/templates/default/specs/PermissionRationale/suite.md +9 -9
  135. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  136. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -18
  137. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  138. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  139. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -18
  140. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  141. package/templates/default/spectra.config.ts +52 -52
  142. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  143. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  144. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -9
  145. package/templates/default/support/actions/seedSession/web.action.ts +13 -13
  146. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  147. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  148. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  149. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  150. package/templates/docs/ARCHITECTURE.default.md +26 -0
  151. package/templates/docs/ARCHITECTURE.nx.md +119 -0
  152. package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm} +0 -0
  153. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  154. package/templates/nx/.nx/workspace-data/d/daemon.log +6343 -1612
  155. package/templates/nx/.nx/workspace-data/d/server-process.json +1 -1
  156. package/templates/nx/.nx/workspace-data/file-map.json +116 -130
  157. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
  158. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  159. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +504 -504
  160. package/templates/nx/.nx/workspace-data/project-graph.json +560 -559
  161. package/templates/nx/.nx/workspace-data/source-maps.json +336 -0
  162. package/templates/nx/CLAUDE.md +10 -10
  163. package/templates/nx/package.json +3 -3
  164. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  165. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
  166. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  167. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -24
  168. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  169. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  170. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  171. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  172. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  173. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  174. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  175. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  176. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  177. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  178. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  179. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  180. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  181. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
  182. package/templates/nx/packages/playground/e2e/project.json +2 -2
  183. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  184. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  185. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  186. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  187. package/templates/nx/pnpm-lock.yaml +6663 -6663
  188. package/templates/nx/pnpm-workspace.yaml +9 -9
  189. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  190. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -91
  191. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  192. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  193. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  194. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  195. package/templates/nx/shared/testing/project.json +18 -19
  196. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  197. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  198. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  199. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  200. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  201. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  202. package/templates/nx/spectra.config.ts +65 -52
  203. package/templates/react-component/.cursorrules +17 -17
  204. package/templates/react-component/.vscode/extensions.json +5 -5
  205. package/templates/react-component/CLAUDE.md +14 -14
  206. package/templates/react-component/README.md +69 -69
  207. package/templates/react-component/fixtures/component-mock.json +12 -12
  208. package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
  209. package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
  210. package/templates/react-component/package.json +27 -27
  211. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
  212. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
  213. package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
  214. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
  215. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
  216. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
  217. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
  218. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
  219. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
  220. package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
  221. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
  222. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +25 -28
  223. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
  224. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +13 -22
  225. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
  226. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
  227. package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
  228. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
  229. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +24 -28
  230. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
  231. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
  232. package/templates/react-component/specs/InputComponent/suite.md +12 -12
  233. package/templates/react-component/spectra.config.ts +17 -27
  234. package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
  235. package/templates/react-component/src/components/Button/Button.tsx +59 -57
  236. package/templates/react-component/src/components/Input/Input.tsx +40 -41
  237. package/templates/react-component/src/test-utils.tsx +23 -23
  238. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
  239. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
  240. package/bin/.testspectra-runner.hash +0 -1
  241. package/bin/testspectra-runner.exe +0 -0
  242. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  243. /package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
@@ -0,0 +1,197 @@
1
+ import { execSync, execFileSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { RustCoreBridge } from '../runner/bridge.js';
6
+ import { checkDriverUpdates } from '../utils/dependency-updates.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const PACKAGES = ['@testspectra/cli', '@testspectra/matchers', '@testspectra/react'];
10
+ /** Filters requested package names against the known catalog; empty means "all". */
11
+ function selectPackages(requested) {
12
+ if (requested.length === 0)
13
+ return [...PACKAGES];
14
+ const known = new Set(PACKAGES);
15
+ const unknown = requested.filter((p) => !known.has(p));
16
+ if (unknown.length > 0) {
17
+ console.warn(`\x1b[33mIgnoring unknown TestSpectra package(s): ${unknown.join(', ')}\x1b[0m`);
18
+ }
19
+ return requested.filter((p) => known.has(p));
20
+ }
21
+ function getLatestVersion(pkg) {
22
+ try {
23
+ const out = execFileSync('npm', ['view', pkg, 'version', '--json'], {
24
+ stdio: ['ignore', 'pipe', 'ignore'],
25
+ timeout: 10000,
26
+ })
27
+ .toString()
28
+ .trim()
29
+ .replace(/"/g, '');
30
+ return out || null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function getCurrentVersion(pkg, cwd) {
37
+ let cur = path.resolve(cwd);
38
+ while (cur && cur !== path.dirname(cur)) {
39
+ const pkgJson = path.join(cur, 'node_modules', pkg, 'package.json');
40
+ if (fs.existsSync(pkgJson)) {
41
+ try {
42
+ const p = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
43
+ return p.version ?? null;
44
+ }
45
+ catch { }
46
+ }
47
+ cur = path.dirname(cur);
48
+ }
49
+ return null;
50
+ }
51
+ function detectPackageManager(cwd) {
52
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
53
+ return 'pnpm';
54
+ if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock')))
55
+ return 'bun';
56
+ return 'npm';
57
+ }
58
+ function collectPackageUpdates(cwd, packages) {
59
+ return packages.map((pkg) => {
60
+ const current = getCurrentVersion(pkg, cwd);
61
+ const latest = getLatestVersion(pkg);
62
+ return { pkg, current, latest, needsUpdate: !!latest && latest !== current };
63
+ });
64
+ }
65
+ function printUpdateRow(label, current, latest, needsUpdate) {
66
+ const currentStr = current ? `\x1b[33mv${current}\x1b[0m` : '\x1b[90m(not installed)\x1b[0m';
67
+ const latestStr = latest ? `\x1b[32mv${latest}\x1b[0m` : '\x1b[90m(unknown)\x1b[0m';
68
+ const arrow = needsUpdate ? ` → ${latestStr}` : '';
69
+ const status = needsUpdate ? '\x1b[33m↑\x1b[0m' : '\x1b[32m✓\x1b[0m';
70
+ console.log(`\x1b[36m│\x1b[0m ${status} ${label.padEnd(28)} ${currentStr}${arrow}`);
71
+ }
72
+ function buildInstallCmd(pm, pkgSpecs, cwd) {
73
+ const list = pkgSpecs.join(' ');
74
+ switch (pm) {
75
+ case 'pnpm': {
76
+ const isWorkspaceRoot = fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml'));
77
+ return isWorkspaceRoot ? `pnpm add -D -w ${list}` : `pnpm add -D ${list}`;
78
+ }
79
+ case 'bun':
80
+ return `bun add -d ${list}`;
81
+ case 'npm':
82
+ default:
83
+ return `npm install -D ${list}`;
84
+ }
85
+ }
86
+ export async function upgradeCommand(requestedPackages = [], options = {}) {
87
+ const cwd = process.cwd();
88
+ const pm = detectPackageManager(cwd);
89
+ const selected = selectPackages(requestedPackages);
90
+ const targeted = requestedPackages.length > 0;
91
+ if (targeted && selected.length === 0) {
92
+ console.error(`\x1b[31mNo known TestSpectra package matched: ${requestedPackages.join(', ')}. Known packages: ${PACKAGES.join(', ')}\x1b[0m`);
93
+ process.exitCode = 1;
94
+ return;
95
+ }
96
+ // Machine-readable mode for the VS Code Preflight panel: implies `--check`, never mutates.
97
+ if (options.json) {
98
+ const packages = collectPackageUpdates(cwd, selected);
99
+ const drivers = targeted ? [] : await checkDriverUpdates();
100
+ const updatesAvailable = [...packages, ...drivers].filter((u) => u.needsUpdate).length;
101
+ console.log(JSON.stringify({ packages, drivers, updatesAvailable }, null, 2));
102
+ return;
103
+ }
104
+ console.log('\x1b[36m┌ 🚀 TestSpectra Self-Upgrade Mechanism\x1b[0m');
105
+ console.log('\x1b[36m│\x1b[0m');
106
+ const updates = [];
107
+ let packagesUpdated = false;
108
+ // Phase 1: NPM Packages
109
+ if (!options.driversOnly) {
110
+ console.log(`\x1b[36m│\x1b[0m \x1b[1mPhase 1: Workspace Packages\x1b[0m (Manager: \x1b[33m${pm}\x1b[0m)`);
111
+ console.log('\x1b[36m│\x1b[0m Fetching latest versions from npm registry...');
112
+ updates.push(...collectPackageUpdates(cwd, selected));
113
+ for (const { pkg, current, latest, needsUpdate } of updates) {
114
+ const currentStr = current ? `\x1b[33mv${current}\x1b[0m` : '\x1b[90m(not installed)\x1b[0m';
115
+ const latestStr = latest ? `\x1b[32mv${latest}\x1b[0m` : '\x1b[31m(unavailable)\x1b[0m';
116
+ const arrow = needsUpdate ? ` → ${latestStr}` : '';
117
+ const status = !latest ? '\x1b[31m✗\x1b[0m' : needsUpdate ? '\x1b[33m↑\x1b[0m' : '\x1b[32m✓\x1b[0m';
118
+ console.log(`\x1b[36m│\x1b[0m ${status} ${pkg.padEnd(28)} ${currentStr}${arrow}`);
119
+ }
120
+ const toUpdate = updates.filter((u) => u.needsUpdate && u.latest);
121
+ console.log('\x1b[36m│\x1b[0m');
122
+ if (options.check) {
123
+ console.log(`\x1b[36m│\x1b[0m \x1b[33m${toUpdate.length} package update(s) available. Run \x1b[36mspectra upgrade\x1b[33m to install.\x1b[0m`);
124
+ }
125
+ else if (toUpdate.length > 0) {
126
+ const pkgSpecs = toUpdate.map((u) => `${u.pkg}@${u.latest}`);
127
+ const installCmd = buildInstallCmd(pm, pkgSpecs, cwd);
128
+ console.log(`\x1b[36m│\x1b[0m Running: \x1b[90m${installCmd}\x1b[0m`);
129
+ try {
130
+ execSync(installCmd, { cwd, stdio: 'inherit' });
131
+ packagesUpdated = true;
132
+ }
133
+ catch (err) {
134
+ console.error(`\x1b[31m│ ✗ Package upgrade failed: ${err.message}\x1b[0m`);
135
+ process.exit(1);
136
+ }
137
+ }
138
+ else {
139
+ console.log('\x1b[36m│\x1b[0m \x1b[32m✓ All TestSpectra packages are already up-to-date!\x1b[0m');
140
+ }
141
+ console.log('\x1b[36m│\x1b[0m');
142
+ }
143
+ if (options.check) {
144
+ if (!targeted) {
145
+ const drivers = await checkDriverUpdates();
146
+ if (drivers.length > 0) {
147
+ console.log('\x1b[36m│\x1b[0m \x1b[1mDriver & Agent Status\x1b[0m');
148
+ for (const dep of drivers) {
149
+ printUpdateRow(dep.name, dep.current, dep.latest, dep.needsUpdate);
150
+ }
151
+ console.log('\x1b[36m│\x1b[0m');
152
+ }
153
+ }
154
+ console.log('\x1b[36m└──────────────────────────────────────────────────────────\x1b[0m\n');
155
+ return;
156
+ }
157
+ // Phase 2: Drivers and Binaries
158
+ if (!options.packagesOnly && !targeted) {
159
+ console.log(`\x1b[36m│\x1b[0m \x1b[1mPhase 2: System Binaries & Drivers\x1b[0m`);
160
+ try {
161
+ const binPath = RustCoreBridge.resolveBinaryPath();
162
+ // Using doctor --fix as the underlying mechanism for pulling drivers
163
+ // In the future, this could be passed a specific --force-latest flag
164
+ console.log(`\x1b[36m│\x1b[0m Forcing update for ADB and Spectra Android Agent...`);
165
+ const home = process.env.HOME || process.env.USERPROFILE || '';
166
+ const globalDriversDir = path.join(home, '.testspectra', 'drivers');
167
+ execSync(`"${binPath}" doctor --fix`, {
168
+ cwd,
169
+ stdio: 'inherit',
170
+ env: {
171
+ ...process.env,
172
+ TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
173
+ TEST_SPECTRA_FORCE_LATEST: '1',
174
+ },
175
+ });
176
+ console.log('\x1b[36m│\x1b[0m \x1b[32m✓ Drivers updated successfully.\x1b[0m');
177
+ }
178
+ catch (err) {
179
+ console.error(`\x1b[31m│ ✗ Driver upgrade failed: ${err.message}\x1b[0m`);
180
+ // We don't exit 1 here so we can still print the post-upgrade report
181
+ }
182
+ console.log('\x1b[36m│\x1b[0m');
183
+ }
184
+ // Phase 3: Post-Upgrade Report
185
+ console.log('\x1b[36m└──────────────────────────────────────────────────────────\x1b[0m');
186
+ console.log('\x1b[32m🎉 TestSpectra upgrade complete!\x1b[0m');
187
+ if (!options.driversOnly && packagesUpdated) {
188
+ const toUpdate = updates.filter((u) => u.needsUpdate && u.latest);
189
+ for (const u of toUpdate) {
190
+ console.log(` \x1b[32m✓\x1b[0m ${u.pkg}: v${u.current ?? '?'} → v${u.latest}`);
191
+ }
192
+ }
193
+ if (!options.packagesOnly) {
194
+ console.log(` \x1b[32m✓\x1b[0m System drivers and agents synchronized to latest versions`);
195
+ }
196
+ console.log('');
197
+ }
@@ -1,3 +1,42 @@
1
+ /**
2
+ * Configuration for an individual local development server lifecycle.
3
+ */
4
+ export interface DevServerConfig {
5
+ /**
6
+ * Unique name or alias identifying this server (e.g. "sso", "adminConsole", "globalSettings").
7
+ */
8
+ name?: string;
9
+ /**
10
+ * Shell command to launch the development server (e.g. "pnpm --filter sso dev" or "nx serve sso").
11
+ */
12
+ command: string;
13
+ /**
14
+ * Target URL or healthcheck endpoint (e.g. "https://sso.tagsamurai.local:5174").
15
+ */
16
+ url: string;
17
+ /**
18
+ * Optional port number (inferred from `url` if omitted).
19
+ */
20
+ port?: number;
21
+ /**
22
+ * Maximum wait timeout in milliseconds for the server to become interactive and accept HTTP connections.
23
+ * @default 60000
24
+ */
25
+ timeout?: number;
26
+ /**
27
+ * Whether to reuse an already running development server if its port/URL is actively responding.
28
+ * @default true
29
+ */
30
+ reuseExistingServer?: boolean;
31
+ /**
32
+ * Working directory from which to launch `command`. Defaults to workspace root.
33
+ */
34
+ cwd?: string;
35
+ /**
36
+ * Custom environment variables passed to the spawned development server process.
37
+ */
38
+ env?: Record<string, string>;
39
+ }
1
40
  /**
2
41
  * Configuration for web automation and browser test runners.
3
42
  */
@@ -7,6 +46,20 @@ export interface WebConfig {
7
46
  * @default "http://localhost:3000"
8
47
  */
9
48
  baseUrl: string;
49
+ /**
50
+ * Multi-host / multi-app mapping dictionary for micro-frontends and multi-entrypoint monorepos.
51
+ * Maps named aliases to full base URLs (e.g. `{ sso: "https://sso.tagsamurai.local:5174", fam: "https://fam.tagsamurai.local:5175" }`).
52
+ * Allows tests to navigate using `Spectra.navigate('sso:/login')` or CLI `--host sso`.
53
+ */
54
+ hosts?: Record<string, string>;
55
+ /**
56
+ * Array of development servers to auto-start and healthcheck before running tests.
57
+ */
58
+ devServers?: DevServerConfig[];
59
+ /**
60
+ * Convenience single development server configuration.
61
+ */
62
+ devServer?: DevServerConfig;
10
63
  /**
11
64
  * Maximum number of parallel browser sessions/workers spawned during test execution.
12
65
  * Can be an explicit number, numeric string, or `"auto"`.
@@ -22,6 +22,8 @@ export function defineConfig(config) {
22
22
  export const DEFAULT_CONFIG_DATA = {
23
23
  webConfig: {
24
24
  baseUrl: 'http://localhost:3000',
25
+ hosts: {},
26
+ devServers: [],
25
27
  maxConcurrentSessions: '1',
26
28
  headless: true,
27
29
  implicitWait: '5000',
@@ -41,7 +41,8 @@ describe('Platform TSConfigs & Type Isolation (docs/v2/cli/tsconfig-platform-iso
41
41
  expect(rootConfig.references).toEqual([{ path: './.testspectra/tsconfig.json' }]);
42
42
  // Verify dynamic master solution references all 5 platform solution tsconfigs in .testspectra/tsconfig/
43
43
  const solutionConfig = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'tsconfig.json'), 'utf-8'));
44
- expect(solutionConfig.compilerOptions.noEmit).toBe(true);
44
+ expect(solutionConfig.compilerOptions.composite).toBe(true);
45
+ expect(solutionConfig.compilerOptions.noEmit).toBeUndefined();
45
46
  expect(solutionConfig.references).toEqual([
46
47
  { path: './tsconfig/web.json' },
47
48
  { path: './tsconfig/mobile.json' },
@@ -383,4 +383,42 @@ async function verifyZeroImport() {
383
383
  expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'))).toBe(false);
384
384
  expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'))).toBe(false);
385
385
  });
386
+ it('nx monorepo: does NOT treat feature library as shared library and discovers nested e2e subproject', () => {
387
+ // 1. Shared library with testspectra:shared
388
+ const sharedDir = path.join(tmpDir, 'shared', 'testing');
389
+ fs.mkdirSync(path.join(sharedDir, 'page-objects', 'SharedNav'), { recursive: true });
390
+ fs.writeFileSync(path.join(sharedDir, 'project.json'), JSON.stringify({ name: 'shared-testing', projectType: 'library', tags: ['testspectra:shared'] }));
391
+ fs.writeFileSync(path.join(sharedDir, 'page-objects', 'SharedNav', 'common.ts'), 'export default class SharedNav {}');
392
+ // 2. Nx Feature library (projectType: "library", standard Nx tags, NO testspectra tags)
393
+ const featureLibDir = path.join(tmpDir, 'packages', 'features', 'auth');
394
+ fs.mkdirSync(featureLibDir, { recursive: true });
395
+ fs.writeFileSync(path.join(featureLibDir, 'project.json'), JSON.stringify({
396
+ name: '@tagsamurai/feature-auth',
397
+ projectType: 'library',
398
+ sourceRoot: 'packages/features/auth',
399
+ targets: {},
400
+ tags: ['type:feature', 'scope:feature-auth'],
401
+ }));
402
+ // 3. Nested E2E subproject inside feature library
403
+ const featureE2eDir = path.join(featureLibDir, 'e2e');
404
+ fs.mkdirSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001'), { recursive: true });
405
+ fs.writeFileSync(path.join(featureE2eDir, 'project.json'), JSON.stringify({
406
+ name: 'feature-auth-e2e',
407
+ projectType: 'application',
408
+ tags: ['testspectra:e2e', 'testspectra:scope:feature-auth'],
409
+ }));
410
+ fs.writeFileSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001', 'web.test.ts'), 'it("auth test", async () => {});');
411
+ const scopes = TypeGenerator.getEntityScopes(tmpDir);
412
+ expect(scopes).toHaveLength(2);
413
+ const sharedScope = scopes.find((s) => s.isShared);
414
+ expect(sharedScope).toBeDefined();
415
+ expect(sharedScope?.name).toBe('shared');
416
+ expect(sharedScope?.dir).toBe(path.resolve(sharedDir));
417
+ const featureScope = scopes.find((s) => !s.isShared);
418
+ expect(featureScope).toBeDefined();
419
+ expect(featureScope?.name).toBe('feature-auth');
420
+ expect(featureScope?.dir).toBe(path.resolve(featureE2eDir));
421
+ // Verify packages/features/auth is NOT in scopes
422
+ expect(scopes.some((s) => s.dir === path.resolve(featureLibDir))).toBe(false);
423
+ });
386
424
  });
@@ -0,0 +1,14 @@
1
+ export interface ArchitectureDocResult {
2
+ mode: 'nx' | 'default';
3
+ path: string;
4
+ sharedTestingRelPath?: string;
5
+ featureCount?: number;
6
+ }
7
+ export declare function buildFeatureModulesSection(cwd: string, featureE2eDirs: string[]): string;
8
+ /**
9
+ * (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
10
+ * scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
11
+ * (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
12
+ * features are added/removed/renamed without needing to re-run `spectra init`.
13
+ */
14
+ export declare function generateArchitectureDoc(cwd: string): ArchitectureDocResult;
@@ -0,0 +1,80 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { TsConfigGenerator } from './tsconfig-generator.js';
5
+ function resolveDocsTemplateDir() {
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const candidates = [
9
+ path.resolve(__dirname, '../templates/docs'),
10
+ path.resolve(__dirname, '../../templates/docs'),
11
+ path.resolve(__dirname, '../../../templates/docs'),
12
+ ];
13
+ return candidates.find((d) => fs.existsSync(d)) ?? candidates[candidates.length - 1];
14
+ }
15
+ function renderArchitectureDoc(templatePath, replacements) {
16
+ let content = fs.readFileSync(templatePath, 'utf-8');
17
+ for (const [token, value] of Object.entries(replacements)) {
18
+ content = content.replaceAll(token, value);
19
+ }
20
+ return content;
21
+ }
22
+ /** Reads a feature e2e dir's project.json for its Nx project name, falling back to `<parentDirName>-<e2eDirName>`. */
23
+ function resolveNxProjectName(featureDir) {
24
+ const parentName = path.basename(path.dirname(featureDir));
25
+ const fallback = `${parentName}-${path.basename(featureDir)}`;
26
+ const projJsonPath = path.join(featureDir, 'project.json');
27
+ if (!fs.existsSync(projJsonPath))
28
+ return fallback;
29
+ try {
30
+ const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
31
+ return typeof pObj.name === 'string' && pObj.name ? pObj.name : fallback;
32
+ }
33
+ catch {
34
+ return fallback;
35
+ }
36
+ }
37
+ export function buildFeatureModulesSection(cwd, featureE2eDirs) {
38
+ return featureE2eDirs
39
+ .map((d) => {
40
+ const rel = path.relative(cwd, d).replace(/\\/g, '/');
41
+ const featureName = path.basename(path.dirname(d));
42
+ const e2eName = resolveNxProjectName(d);
43
+ return `- \`./${rel}\` (**${featureName}**, Nx Project: \`${e2eName}\`):
44
+ - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
45
+ - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
46
+ - \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
47
+ })
48
+ .join('\n');
49
+ }
50
+ /**
51
+ * (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
52
+ * scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
53
+ * (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
54
+ * features are added/removed/renamed without needing to re-run `spectra init`.
55
+ */
56
+ export function generateArchitectureDoc(cwd) {
57
+ const docsTemplateDir = resolveDocsTemplateDir();
58
+ const destPath = path.join(cwd, 'ARCHITECTURE.md');
59
+ const scopes = TsConfigGenerator.getEntityScopes(cwd);
60
+ const isNxMode = scopes.length > 0 || fs.existsSync(path.join(cwd, 'nx.json'));
61
+ if (!isNxMode) {
62
+ fs.copyFileSync(path.join(docsTemplateDir, 'ARCHITECTURE.default.md'), destPath);
63
+ return { mode: 'default', path: destPath };
64
+ }
65
+ const sharedScope = scopes.find((s) => s.isShared);
66
+ const featureE2eDirs = scopes.filter((s) => !s.isShared).map((s) => s.dir);
67
+ const sharedTestingRelPath = sharedScope ? path.relative(cwd, sharedScope.dir).replace(/\\/g, '/') : 'shared/testing';
68
+ const e2eFolderName = featureE2eDirs.length > 0 ? path.basename(featureE2eDirs[0]) : 'e2e';
69
+ const content = renderArchitectureDoc(path.join(docsTemplateDir, 'ARCHITECTURE.nx.md'), {
70
+ __SHARED_TESTING_REL_PATH__: sharedTestingRelPath,
71
+ __FEATURE_COUNT__: String(featureE2eDirs.length),
72
+ __FEATURE_MODULES_SECTION__: buildFeatureModulesSection(cwd, featureE2eDirs),
73
+ __E2E_FOLDER_NAME__: e2eFolderName,
74
+ __SAMPLE_FEATURE_E2E_PATH__: featureE2eDirs.length > 0
75
+ ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/')
76
+ : `modules/auth/${e2eFolderName}`,
77
+ });
78
+ fs.writeFileSync(destPath, content, 'utf-8');
79
+ return { mode: 'nx', path: destPath, sharedTestingRelPath, featureCount: featureE2eDirs.length };
80
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './type-generator.js';
2
2
  export * from './tsconfig-generator.js';
3
+ export * from './architecture-doc-generator.js';
@@ -1,2 +1,3 @@
1
1
  export * from './type-generator.js';
2
2
  export * from './tsconfig-generator.js';
3
+ export * from './architecture-doc-generator.js';
@@ -16,7 +16,7 @@ export class TsConfigGenerator {
16
16
  moduleResolution: 'NodeNext',
17
17
  skipLibCheck: true,
18
18
  strict: true,
19
- noEmit: true,
19
+ composite: true,
20
20
  },
21
21
  files: [],
22
22
  references: references || [
@@ -9,8 +9,11 @@ export interface EntityScope {
9
9
  * Discovers all feature directories and shared library directories.
10
10
  * Standard discovery hierarchy:
11
11
  * 1. Nx / Monorepo Project Descriptors (`project.json`):
12
- * - projectType: "library" OR tags: ["type:shared", "type:library", "scope:shared"] => Shared Library Scope (isShared: true)
13
- * - projectType: "application" OR tags: ["type:e2e", "type:app"] => Feature Scope (isShared: false)
12
+ * - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
13
+ * - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
14
+ * - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
15
+ * continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
16
+ * are discovered.
14
17
  * 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
15
18
  * - Custom include path for shared testing utilities
16
19
  * 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
@@ -13,8 +13,11 @@ export const PLATFORM_HIERARCHY = {
13
13
  * Discovers all feature directories and shared library directories.
14
14
  * Standard discovery hierarchy:
15
15
  * 1. Nx / Monorepo Project Descriptors (`project.json`):
16
- * - projectType: "library" OR tags: ["type:shared", "type:library", "scope:shared"] => Shared Library Scope (isShared: true)
17
- * - projectType: "application" OR tags: ["type:e2e", "type:app"] => Feature Scope (isShared: false)
16
+ * - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
17
+ * - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
18
+ * - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
19
+ * continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
20
+ * are discovered.
18
21
  * 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
19
22
  * - Custom include path for shared testing utilities
20
23
  * 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
@@ -48,16 +51,9 @@ export function discoverEntityScopes(cwd) {
48
51
  if (fs.existsSync(projectJsonPath)) {
49
52
  try {
50
53
  const pObj = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
51
- const projectType = pObj.projectType;
52
54
  const tags = Array.isArray(pObj.tags) ? pObj.tags : [];
53
- const isSharedLib = projectType === 'library' ||
54
- tags.includes('type:shared') ||
55
- tags.includes('type:library') ||
56
- tags.includes('scope:shared');
57
- const isE2eApp = projectType === 'application' ||
58
- tags.includes('type:e2e') ||
59
- tags.includes('type:app') ||
60
- tags.some((t) => t.startsWith('scope:') && t !== 'scope:shared');
55
+ const isSharedLib = tags.includes('testspectra:shared');
56
+ const isE2eApp = tags.includes('testspectra:e2e') || tags.some((t) => t.startsWith('testspectra:scope:'));
61
57
  if (isSharedLib) {
62
58
  if (!scopes.some((s) => s.dir === dir)) {
63
59
  scopes.push({ name: 'shared', dir, isShared: true });
@@ -65,9 +61,9 @@ export function discoverEntityScopes(cwd) {
65
61
  return; // Do not recurse into subdirectories of an identified project
66
62
  }
67
63
  else if (isE2eApp) {
68
- // Extract scope name from tags (e.g. "scope:matchers" => "matchers") or project name
69
- const scopeTag = tags.find((t) => t.startsWith('scope:') && t !== 'scope:shared');
70
- let featureName = scopeTag ? scopeTag.replace('scope:', '') : '';
64
+ // Extract scope name from tags (e.g. "testspectra:scope:matchers" => "matchers") or project name
65
+ const scopeTag = tags.find((t) => t.startsWith('testspectra:scope:'));
66
+ let featureName = scopeTag ? scopeTag.replace('testspectra:scope:', '') : '';
71
67
  if (!featureName && pObj.name) {
72
68
  featureName = pObj.name.replace(/-e2e$/, '').replace(/^e2e-/, '');
73
69
  }
@@ -329,6 +325,9 @@ export class TypeGenerator {
329
325
  const hierarchy = PLATFORM_HIERARCHY[platform];
330
326
  let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
331
327
  content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
328
+ content += `/// <reference types="@testspectra/matchers" />\n`;
329
+ content += `/// <reference path="./fixtures.d.ts" />\n`;
330
+ content += `/// <reference path="${envRefPath}" />\n\n`;
332
331
  // TestSpectra's Rust-native orchestrator worker (core/orchestrator/src/worker/bdd_runner.ts)
333
332
  // registers its own `it`/`test`/lifecycle-hook globals directly — there is no WDIO or Mocha
334
333
  // runtime involved at all (not just for component testing; this is true across every
@@ -346,9 +345,7 @@ export class TypeGenerator {
346
345
  // — the Rust orchestrator's BDD runtime has no `expect` global at all.
347
346
  content += `declare const expect: import('@testspectra/react').ExpectStatic;\n`;
348
347
  }
349
- content += `/// <reference types="@testspectra/matchers" />\n`;
350
- content += `/// <reference path="./fixtures.d.ts" />\n`;
351
- content += `/// <reference path="${envRefPath}" />\n\n`;
348
+ content += `\n`;
352
349
  // 1. Page Objects Global Declarations
353
350
  const declaredPOMs = new Set();
354
351
  for (const poDir of poDirs) {