@fragments-sdk/cli 0.2.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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Types for the segments test CLI command
3
+ */
4
+
5
+ import type { PlayFunction, SegmentVariant } from '../core/index.js';
6
+
7
+ /**
8
+ * A test case discovered from segment files
9
+ */
10
+ export interface TestCase {
11
+ /** Unique identifier for this test */
12
+ id: string;
13
+ /** Component name */
14
+ component: string;
15
+ /** Variant name */
16
+ variant: string;
17
+ /** Tags from the variant */
18
+ tags: string[];
19
+ /** The play function to execute */
20
+ play: PlayFunction;
21
+ /** Source file path */
22
+ sourceFile: string;
23
+ /** Story ID for navigation */
24
+ storyId?: string;
25
+ }
26
+
27
+ /**
28
+ * Result of a single step within a test
29
+ */
30
+ export interface StepResult {
31
+ /** Step name */
32
+ name: string;
33
+ /** Step status */
34
+ status: 'passed' | 'failed' | 'skipped';
35
+ /** Duration in milliseconds */
36
+ duration: number;
37
+ /** Error if step failed */
38
+ error?: TestError;
39
+ }
40
+
41
+ /**
42
+ * Error information from a failed test
43
+ */
44
+ export interface TestError {
45
+ /** Error message */
46
+ message: string;
47
+ /** Stack trace */
48
+ stack?: string;
49
+ /** Assertion details if applicable */
50
+ expected?: unknown;
51
+ /** Actual value if applicable */
52
+ actual?: unknown;
53
+ }
54
+
55
+ /**
56
+ * Accessibility violation from axe-core
57
+ */
58
+ export interface A11yViolation {
59
+ /** Violation ID */
60
+ id: string;
61
+ /** Impact level */
62
+ impact: 'critical' | 'serious' | 'moderate' | 'minor';
63
+ /** Description */
64
+ description: string;
65
+ /** Help text */
66
+ help: string;
67
+ /** Help URL */
68
+ helpUrl: string;
69
+ /** Affected nodes */
70
+ nodes: Array<{
71
+ html: string;
72
+ target: string[];
73
+ }>;
74
+ }
75
+
76
+ /**
77
+ * Accessibility check result
78
+ */
79
+ export interface A11yResult {
80
+ /** Number of violations */
81
+ violations: A11yViolation[];
82
+ /** Number of passing checks */
83
+ passes: number;
84
+ }
85
+
86
+ /**
87
+ * Result of a single test case execution
88
+ */
89
+ export interface TestResult {
90
+ /** The test case that was run */
91
+ testCase: TestCase;
92
+ /** Overall test status */
93
+ status: 'passed' | 'failed' | 'skipped';
94
+ /** Total duration in milliseconds */
95
+ duration: number;
96
+ /** Results of individual steps */
97
+ steps: StepResult[];
98
+ /** Error if test failed */
99
+ error?: TestError;
100
+ /** Accessibility check result */
101
+ accessibility?: A11yResult;
102
+ /** Screenshot path for visual regression */
103
+ screenshotPath?: string;
104
+ /** Retry attempt number (0 = first attempt) */
105
+ retryAttempt?: number;
106
+ }
107
+
108
+ /**
109
+ * A test suite groups test results by component
110
+ */
111
+ export interface TestSuite {
112
+ /** Component name */
113
+ name: string;
114
+ /** Test results in this suite */
115
+ tests: TestResult[];
116
+ /** Total duration for the suite */
117
+ duration: number;
118
+ /** Number of passed tests */
119
+ passed: number;
120
+ /** Number of failed tests */
121
+ failed: number;
122
+ /** Number of skipped tests */
123
+ skipped: number;
124
+ }
125
+
126
+ /**
127
+ * Overall result of a test run
128
+ */
129
+ export interface TestRunResult {
130
+ /** Test suites organized by component */
131
+ suites: TestSuite[];
132
+ /** Total number of tests */
133
+ totalTests: number;
134
+ /** Number of passed tests */
135
+ totalPassed: number;
136
+ /** Number of failed tests */
137
+ totalFailed: number;
138
+ /** Number of skipped tests */
139
+ totalSkipped: number;
140
+ /** Total duration in milliseconds */
141
+ totalDuration: number;
142
+ /** Start timestamp */
143
+ startTime: Date;
144
+ /** End timestamp */
145
+ endTime: Date;
146
+ /** Total a11y violations across all tests */
147
+ totalA11yViolations?: number;
148
+ }
149
+
150
+ /**
151
+ * Options for test discovery
152
+ */
153
+ export interface DiscoveryOptions {
154
+ /** Filter by component name */
155
+ component?: string;
156
+ /** Filter by tags (comma-separated) */
157
+ tags?: string[];
158
+ /** Grep pattern to match variant names */
159
+ grep?: string;
160
+ /** Exclude pattern */
161
+ exclude?: string;
162
+ }
163
+
164
+ /**
165
+ * Options for test execution
166
+ */
167
+ export interface RunnerOptions {
168
+ /** Number of parallel browser contexts */
169
+ parallel: number;
170
+ /** Timeout per test in milliseconds */
171
+ timeout: number;
172
+ /** Number of retries for failed tests */
173
+ retries: number;
174
+ /** Stop on first failure */
175
+ bail: boolean;
176
+ /** Run accessibility checks */
177
+ a11y: boolean;
178
+ /** Capture screenshots for visual regression */
179
+ visual: boolean;
180
+ /** Update visual snapshots */
181
+ updateSnapshots: boolean;
182
+ /** Output directory for results */
183
+ outputDir: string;
184
+ /** Browser to use */
185
+ browser: 'chromium' | 'firefox' | 'webkit';
186
+ /** Headless mode */
187
+ headless: boolean;
188
+ /** Dev server URL (if already running) */
189
+ serverUrl?: string;
190
+ /** Port for dev server */
191
+ port: number;
192
+ }
193
+
194
+ /**
195
+ * Reporter interface for outputting test results
196
+ */
197
+ export interface TestReporter {
198
+ /** Called when test run starts */
199
+ onRunStart?(testCount: number): void;
200
+ /** Called when a test starts */
201
+ onTestStart?(testCase: TestCase): void;
202
+ /** Called when a test completes */
203
+ onTestComplete?(result: TestResult): void;
204
+ /** Called when test run completes */
205
+ onRunComplete(result: TestRunResult): Promise<void> | void;
206
+ }
207
+
208
+ /**
209
+ * Configuration for the test command
210
+ */
211
+ export interface TestConfig {
212
+ /** Discovery options */
213
+ discovery: DiscoveryOptions;
214
+ /** Runner options */
215
+ runner: RunnerOptions;
216
+ /** Reporter names */
217
+ reporters: string[];
218
+ /** Watch mode */
219
+ watch: boolean;
220
+ /** CI mode (non-interactive, exit codes) */
221
+ ci: boolean;
222
+ }
223
+
224
+ /**
225
+ * Event emitted during test execution
226
+ */
227
+ export type TestEvent =
228
+ | { type: 'run-start'; testCount: number }
229
+ | { type: 'test-start'; testCase: TestCase }
230
+ | { type: 'test-complete'; result: TestResult }
231
+ | { type: 'run-complete'; result: TestRunResult };
232
+
233
+ /**
234
+ * Browser pool context for parallel execution
235
+ */
236
+ export interface BrowserContext {
237
+ /** Context ID */
238
+ id: number;
239
+ /** Whether context is currently in use */
240
+ busy: boolean;
241
+ /** Playwright browser context */
242
+ context: unknown; // Playwright BrowserContext
243
+ /** Page instance */
244
+ page: unknown; // Playwright Page
245
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Watch mode - file watcher with selective re-run
3
+ */
4
+
5
+ import { watch } from 'node:fs';
6
+ import { resolve, relative } from 'node:path';
7
+ import pc from 'picocolors';
8
+ import type { SegmentsConfig } from '../core/index.js';
9
+ import { discoverSegmentFiles } from '../core/node.js';
10
+ import type { TestCase, RunnerOptions, TestReporter } from './types.js';
11
+ import { discoverTests } from './discovery.js';
12
+ import { runTests } from './runner.js';
13
+
14
+ export interface WatchOptions {
15
+ /** Debounce delay in milliseconds */
16
+ debounceMs?: number;
17
+ /** Clear console on each run */
18
+ clearConsole?: boolean;
19
+ /** Run all tests initially */
20
+ runOnStart?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Start watch mode
25
+ */
26
+ export async function startWatchMode(
27
+ config: SegmentsConfig,
28
+ configDir: string,
29
+ runnerOptions: RunnerOptions,
30
+ reporters: TestReporter[],
31
+ options: WatchOptions = {}
32
+ ): Promise<void> {
33
+ const { debounceMs = 300, clearConsole = true, runOnStart = true } = options;
34
+
35
+ let debounceTimer: NodeJS.Timeout | null = null;
36
+ let isRunning = false;
37
+ let pendingFiles = new Set<string>();
38
+
39
+ // Get files to watch
40
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
41
+ const watchPaths = new Set<string>();
42
+
43
+ for (const file of segmentFiles) {
44
+ watchPaths.add(file.absolutePath);
45
+ }
46
+
47
+ // Also watch the directories containing segment files
48
+ const watchDirs = new Set<string>();
49
+ for (const path of watchPaths) {
50
+ const dir = resolve(path, '..');
51
+ watchDirs.add(dir);
52
+ }
53
+
54
+ console.log();
55
+ console.log(pc.cyan(pc.bold('Segments Test Runner - Watch Mode')));
56
+ console.log(pc.dim(`Watching ${watchPaths.size} files in ${watchDirs.size} directories`));
57
+ console.log(pc.dim('Press Ctrl+C to stop'));
58
+ console.log();
59
+
60
+ // Function to run tests
61
+ const runTestsForFiles = async (changedFiles: string[]): Promise<void> => {
62
+ if (isRunning) {
63
+ // Queue up changed files for next run
64
+ for (const file of changedFiles) {
65
+ pendingFiles.add(file);
66
+ }
67
+ return;
68
+ }
69
+
70
+ isRunning = true;
71
+
72
+ try {
73
+ if (clearConsole) {
74
+ console.clear();
75
+ }
76
+
77
+ console.log(pc.cyan('Changes detected, running tests...'));
78
+ console.log(pc.dim(`Changed: ${changedFiles.map((f) => relative(configDir, f)).join(', ')}`));
79
+ console.log();
80
+
81
+ // Discover tests (filter to changed files if possible)
82
+ const allTests = await discoverTests(config, configDir, {});
83
+
84
+ // Filter to tests from changed files
85
+ const changedRelativePaths = changedFiles.map((f) => relative(configDir, f));
86
+ const testsToRun = changedRelativePaths.length > 0
87
+ ? allTests.filter((t) => changedRelativePaths.some((p) => t.sourceFile.includes(p)))
88
+ : allTests;
89
+
90
+ if (testsToRun.length === 0) {
91
+ console.log(pc.yellow('No tests found in changed files'));
92
+ console.log();
93
+ return;
94
+ }
95
+
96
+ // Run the tests
97
+ await runTests(testsToRun, runnerOptions, reporters);
98
+ } catch (error) {
99
+ console.error(pc.red('Error running tests:'), error);
100
+ } finally {
101
+ isRunning = false;
102
+
103
+ // Check if there are pending files to run
104
+ if (pendingFiles.size > 0) {
105
+ const files = Array.from(pendingFiles);
106
+ pendingFiles.clear();
107
+ await runTestsForFiles(files);
108
+ }
109
+ }
110
+ };
111
+
112
+ // Run initial tests if configured
113
+ if (runOnStart) {
114
+ const allTests = await discoverTests(config, configDir, {});
115
+ if (allTests.length > 0) {
116
+ await runTests(allTests, runnerOptions, reporters);
117
+ } else {
118
+ console.log(pc.yellow('No tests with play functions found'));
119
+ }
120
+ console.log();
121
+ console.log(pc.dim('Watching for changes...'));
122
+ console.log();
123
+ }
124
+
125
+ // Set up file watchers
126
+ const watchers: ReturnType<typeof watch>[] = [];
127
+
128
+ for (const dir of watchDirs) {
129
+ try {
130
+ const watcher = watch(dir, { recursive: false }, (eventType, filename) => {
131
+ if (!filename) return;
132
+
133
+ const fullPath = resolve(dir, filename);
134
+
135
+ // Check if this is a segment file we care about
136
+ if (!watchPaths.has(fullPath)) return;
137
+
138
+ // Debounce the run
139
+ if (debounceTimer) {
140
+ clearTimeout(debounceTimer);
141
+ }
142
+
143
+ pendingFiles.add(fullPath);
144
+
145
+ debounceTimer = setTimeout(() => {
146
+ const files = Array.from(pendingFiles);
147
+ pendingFiles.clear();
148
+ runTestsForFiles(files);
149
+ }, debounceMs);
150
+ });
151
+
152
+ watchers.push(watcher);
153
+ } catch (error) {
154
+ console.warn(pc.yellow(`Warning: Could not watch directory ${dir}`), error);
155
+ }
156
+ }
157
+
158
+ // Handle process termination
159
+ const cleanup = () => {
160
+ console.log();
161
+ console.log(pc.dim('Stopping watch mode...'));
162
+
163
+ for (const watcher of watchers) {
164
+ watcher.close();
165
+ }
166
+
167
+ if (debounceTimer) {
168
+ clearTimeout(debounceTimer);
169
+ }
170
+
171
+ process.exit(0);
172
+ };
173
+
174
+ process.on('SIGINT', cleanup);
175
+ process.on('SIGTERM', cleanup);
176
+
177
+ // Keep process alive
178
+ await new Promise(() => {
179
+ // This promise never resolves, keeping the process alive
180
+ // until terminated
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Interactive watch mode with keyboard controls
186
+ */
187
+ export async function startInteractiveWatchMode(
188
+ config: SegmentsConfig,
189
+ configDir: string,
190
+ runnerOptions: RunnerOptions,
191
+ reporters: TestReporter[]
192
+ ): Promise<void> {
193
+ let lastFailedTests: TestCase[] = [];
194
+
195
+ // Start basic watch mode
196
+ await startWatchMode(config, configDir, runnerOptions, reporters, {
197
+ runOnStart: true,
198
+ clearConsole: true,
199
+ });
200
+ }
@@ -0,0 +1,152 @@
1
+ import { segmentDefinitionSchema, BRAND, type SegmentsConfig } from './core/index.js';
2
+ import {
3
+ discoverSegmentFiles,
4
+ discoverComponentFiles,
5
+ extractComponentName,
6
+ loadSegmentFile,
7
+ type DiscoveredFile,
8
+ } from './core/node.js';
9
+
10
+ export interface ValidationResult {
11
+ valid: boolean;
12
+ errors: ValidationError[];
13
+ warnings: ValidationWarning[];
14
+ }
15
+
16
+ export interface ValidationError {
17
+ file: string;
18
+ message: string;
19
+ details?: string;
20
+ }
21
+
22
+ export interface ValidationWarning {
23
+ file: string;
24
+ message: string;
25
+ }
26
+
27
+ /**
28
+ * Validate segment file schema
29
+ */
30
+ export async function validateSchema(
31
+ config: SegmentsConfig,
32
+ configDir: string
33
+ ): Promise<ValidationResult> {
34
+ const files = await discoverSegmentFiles(config, configDir);
35
+ const errors: ValidationError[] = [];
36
+ const warnings: ValidationWarning[] = [];
37
+
38
+ for (const file of files) {
39
+ try {
40
+ const segment = await loadSegmentFile(file.absolutePath);
41
+
42
+ if (!segment) {
43
+ errors.push({
44
+ file: file.relativePath,
45
+ message: 'No default export found',
46
+ details: `Segment files must have a default export from defineSegment()`,
47
+ });
48
+ continue;
49
+ }
50
+
51
+ const result = segmentDefinitionSchema.safeParse(segment);
52
+
53
+ if (!result.success) {
54
+ const details = result.error.errors
55
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
56
+ .join('; ');
57
+
58
+ errors.push({
59
+ file: file.relativePath,
60
+ message: 'Invalid segment schema',
61
+ details,
62
+ });
63
+ }
64
+ } catch (error) {
65
+ errors.push({
66
+ file: file.relativePath,
67
+ message: 'Failed to load segment file',
68
+ details: error instanceof Error ? error.message : String(error),
69
+ });
70
+ }
71
+ }
72
+
73
+ return {
74
+ valid: errors.length === 0,
75
+ errors,
76
+ warnings,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Validate coverage - ensure all components have segments
82
+ */
83
+ export async function validateCoverage(
84
+ config: SegmentsConfig,
85
+ configDir: string
86
+ ): Promise<ValidationResult> {
87
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
88
+ const componentFiles = await discoverComponentFiles(config, configDir);
89
+ const errors: ValidationError[] = [];
90
+ const warnings: ValidationWarning[] = [];
91
+
92
+ // Build set of documented component names
93
+ const documentedComponents = new Set<string>();
94
+
95
+ for (const file of segmentFiles) {
96
+ try {
97
+ const segment = await loadSegmentFile(file.absolutePath);
98
+
99
+ if (segment?.meta?.name) {
100
+ documentedComponents.add(segment.meta.name);
101
+ }
102
+ } catch {
103
+ // Skip files that fail to load - schema validation will catch these
104
+ }
105
+ }
106
+
107
+ // Check each component file
108
+ for (const file of componentFiles) {
109
+ const componentName = extractComponentName(file.relativePath);
110
+
111
+ // Check if there's a corresponding segment file
112
+ const segmentPath = file.relativePath.replace(
113
+ /\.(tsx?|jsx?)$/,
114
+ BRAND.fileExtension
115
+ );
116
+ const hasSegmentFile = segmentFiles.some(
117
+ (s) => s.relativePath === segmentPath
118
+ );
119
+
120
+ if (!hasSegmentFile && !documentedComponents.has(componentName)) {
121
+ warnings.push({
122
+ file: file.relativePath,
123
+ message: `Component "${componentName}" has no segment documentation`,
124
+ });
125
+ }
126
+ }
127
+
128
+ return {
129
+ valid: errors.length === 0,
130
+ errors,
131
+ warnings,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Run all validations
137
+ */
138
+ export async function validateAll(
139
+ config: SegmentsConfig,
140
+ configDir: string
141
+ ): Promise<ValidationResult> {
142
+ const [schemaResult, coverageResult] = await Promise.all([
143
+ validateSchema(config, configDir),
144
+ validateCoverage(config, configDir),
145
+ ]);
146
+
147
+ return {
148
+ valid: schemaResult.valid && coverageResult.valid,
149
+ errors: [...schemaResult.errors, ...coverageResult.errors],
150
+ warnings: [...schemaResult.warnings, ...coverageResult.warnings],
151
+ };
152
+ }