elit 3.3.3 → 3.3.5

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 (145) hide show
  1. package/dist/build.d.mts +1 -1
  2. package/dist/build.js +1 -0
  3. package/dist/build.js.map +1 -0
  4. package/dist/build.mjs +1 -0
  5. package/dist/build.mjs.map +1 -0
  6. package/dist/chokidar.js +1 -0
  7. package/dist/chokidar.js.map +1 -0
  8. package/dist/chokidar.mjs +1 -0
  9. package/dist/chokidar.mjs.map +1 -0
  10. package/dist/cli.js +4720 -37
  11. package/dist/config.d.mts +3 -1
  12. package/dist/config.d.ts +3 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +1 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/config.mjs +1 -0
  17. package/dist/config.mjs.map +1 -0
  18. package/dist/coverage.d.mts +85 -0
  19. package/dist/coverage.d.ts +76 -0
  20. package/dist/coverage.d.ts.map +1 -0
  21. package/dist/coverage.js +1549 -0
  22. package/dist/coverage.js.map +1 -0
  23. package/dist/coverage.mjs +1520 -0
  24. package/dist/coverage.mjs.map +1 -0
  25. package/dist/database.d.mts +31 -6
  26. package/dist/database.d.ts +31 -6
  27. package/dist/database.d.ts.map +1 -1
  28. package/dist/database.js +60 -33
  29. package/dist/database.js.map +1 -0
  30. package/dist/database.mjs +60 -33
  31. package/dist/database.mjs.map +1 -0
  32. package/dist/dom.js +1 -0
  33. package/dist/dom.js.map +1 -0
  34. package/dist/dom.mjs +1 -0
  35. package/dist/dom.mjs.map +1 -0
  36. package/dist/el.js +1 -0
  37. package/dist/el.js.map +1 -0
  38. package/dist/el.mjs +1 -0
  39. package/dist/el.mjs.map +1 -0
  40. package/dist/fs.js +1 -0
  41. package/dist/fs.js.map +1 -0
  42. package/dist/fs.mjs +1 -0
  43. package/dist/fs.mjs.map +1 -0
  44. package/dist/hmr.js +1 -0
  45. package/dist/hmr.js.map +1 -0
  46. package/dist/hmr.mjs +1 -0
  47. package/dist/hmr.mjs.map +1 -0
  48. package/dist/http.js +1 -0
  49. package/dist/http.js.map +1 -0
  50. package/dist/http.mjs +1 -0
  51. package/dist/http.mjs.map +1 -0
  52. package/dist/https.d.mts +1 -1
  53. package/dist/https.js +1 -0
  54. package/dist/https.js.map +1 -0
  55. package/dist/https.mjs +1 -0
  56. package/dist/https.mjs.map +1 -0
  57. package/dist/index.d.mts +1 -1
  58. package/dist/index.js +1 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.mjs +1 -0
  61. package/dist/index.mjs.map +1 -0
  62. package/dist/mime-types.js +1 -0
  63. package/dist/mime-types.js.map +1 -0
  64. package/dist/mime-types.mjs +1 -0
  65. package/dist/mime-types.mjs.map +1 -0
  66. package/dist/path.js +1 -0
  67. package/dist/path.js.map +1 -0
  68. package/dist/path.mjs +1 -0
  69. package/dist/path.mjs.map +1 -0
  70. package/dist/router.js +1 -0
  71. package/dist/router.js.map +1 -0
  72. package/dist/router.mjs +1 -0
  73. package/dist/router.mjs.map +1 -0
  74. package/dist/runtime.js +1 -0
  75. package/dist/runtime.js.map +1 -0
  76. package/dist/runtime.mjs +1 -0
  77. package/dist/runtime.mjs.map +1 -0
  78. package/dist/{server-Cz3z-5ls.d.mts → server-BFTzgJpO.d.mts} +62 -1
  79. package/dist/{server-BG2CaVMh.d.ts → server-CIXtexNS.d.ts} +62 -1
  80. package/dist/server.d.mts +1 -1
  81. package/dist/server.d.ts +9 -0
  82. package/dist/server.d.ts.map +1 -1
  83. package/dist/server.js +45 -3
  84. package/dist/server.js.map +1 -0
  85. package/dist/server.mjs +45 -3
  86. package/dist/server.mjs.map +1 -0
  87. package/dist/state.d.mts +1 -1
  88. package/dist/state.js +1 -0
  89. package/dist/state.js.map +1 -0
  90. package/dist/state.mjs +1 -0
  91. package/dist/state.mjs.map +1 -0
  92. package/dist/style.js +1 -0
  93. package/dist/style.js.map +1 -0
  94. package/dist/style.mjs +1 -0
  95. package/dist/style.mjs.map +1 -0
  96. package/dist/test-globals.d.ts +184 -0
  97. package/dist/test-reporter.d.mts +77 -0
  98. package/dist/test-reporter.d.ts +73 -0
  99. package/dist/test-reporter.d.ts.map +1 -0
  100. package/dist/test-reporter.js +726 -0
  101. package/dist/test-reporter.js.map +1 -0
  102. package/dist/test-reporter.mjs +696 -0
  103. package/dist/test-reporter.mjs.map +1 -0
  104. package/dist/test-runtime.d.mts +122 -0
  105. package/dist/test-runtime.d.ts +120 -0
  106. package/dist/test-runtime.d.ts.map +1 -0
  107. package/dist/test-runtime.js +1292 -0
  108. package/dist/test-runtime.js.map +1 -0
  109. package/dist/test-runtime.mjs +1269 -0
  110. package/dist/test-runtime.mjs.map +1 -0
  111. package/dist/test.d.mts +39 -0
  112. package/dist/test.d.ts +38 -0
  113. package/dist/test.d.ts.map +1 -0
  114. package/dist/test.js +4966 -0
  115. package/dist/test.js.map +1 -0
  116. package/dist/test.mjs +4944 -0
  117. package/dist/test.mjs.map +1 -0
  118. package/dist/types.d.mts +62 -1
  119. package/dist/types.d.ts +52 -0
  120. package/dist/types.d.ts.map +1 -1
  121. package/dist/types.js +1 -0
  122. package/dist/types.js.map +1 -0
  123. package/dist/types.mjs +1 -0
  124. package/dist/types.mjs.map +1 -0
  125. package/dist/ws.d.ts.map +1 -1
  126. package/dist/ws.js +24 -2
  127. package/dist/ws.js.map +1 -0
  128. package/dist/ws.mjs +24 -2
  129. package/dist/ws.mjs.map +1 -0
  130. package/dist/wss.js +24 -2
  131. package/dist/wss.js.map +1 -0
  132. package/dist/wss.mjs +24 -2
  133. package/dist/wss.mjs.map +1 -0
  134. package/package.json +37 -5
  135. package/src/cli.ts +165 -1
  136. package/src/config.ts +3 -1
  137. package/src/coverage.ts +1479 -0
  138. package/src/database.ts +71 -35
  139. package/src/server.ts +25 -1
  140. package/src/test-globals.d.ts +184 -0
  141. package/src/test-reporter.ts +609 -0
  142. package/src/test-runtime.ts +1359 -0
  143. package/src/test.ts +368 -0
  144. package/src/types.ts +59 -0
  145. package/src/ws.ts +32 -2
package/src/test.ts ADDED
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Jest-compatible Test Runner powered by esbuild
3
+ *
4
+ * Main entry point for running tests with Jest-like API.
5
+ */
6
+
7
+ import { readdirSync } from './fs';
8
+ import { join, relative } from './path';
9
+ import { runTests, setupGlobals, clearGlobals, resetCoveredFiles, getCoveredFiles } from './test-runtime';
10
+ import { TestReporter, DotReporter, JsonReporter, VerboseReporter } from './test-reporter';
11
+
12
+ export interface TestOptions {
13
+ files?: string[];
14
+ include?: string[];
15
+ exclude?: string[];
16
+ reporter?: 'default' | 'dot' | 'json' | 'verbose';
17
+ timeout?: number;
18
+ bail?: boolean;
19
+ run?: boolean;
20
+ watch?: boolean;
21
+ endToEnd?: boolean;
22
+ colors?: boolean;
23
+ globals?: boolean;
24
+ describePattern?: string;
25
+ testPattern?: string;
26
+ coverage?: {
27
+ enabled: boolean;
28
+ provider: 'v8' | 'istanbul';
29
+ reporter?: ('text' | 'html' | 'lcov' | 'json' | 'coverage-final.json' | 'clover')[];
30
+ include?: string[];
31
+ exclude?: string[];
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Convert glob pattern to regex (safe from ReDoS)
37
+ */
38
+ function globToRegex(pattern: string): RegExp {
39
+ // Handle brace expansion: {ts,js} -> (ts|js)
40
+ // Use string operations instead of regex to avoid ReDoS
41
+ let expanded = pattern;
42
+ const openBraceIndex = pattern.indexOf('{');
43
+ if (openBraceIndex !== -1) {
44
+ const closeBraceIndex = pattern.indexOf('}', openBraceIndex);
45
+ if (closeBraceIndex !== -1) {
46
+ const options = pattern.slice(openBraceIndex + 1, closeBraceIndex).split(',');
47
+ const before = pattern.slice(0, openBraceIndex);
48
+ const after = pattern.slice(closeBraceIndex + 1);
49
+ expanded = before + '(' + options.join('|') + ')' + after;
50
+ }
51
+ }
52
+
53
+ // Convert to regex using character-by-character processing
54
+ // This avoids using .replace() with regex on user input
55
+ let regexStr = '^';
56
+ for (let i = 0; i < expanded.length; i++) {
57
+ const char = expanded[i];
58
+ switch (char) {
59
+ case '.':
60
+ regexStr += '\\.';
61
+ break;
62
+ case '*':
63
+ regexStr += '.*';
64
+ break;
65
+ case '?':
66
+ regexStr += '.';
67
+ break;
68
+ case '+':
69
+ case '^':
70
+ case '$':
71
+ case '|':
72
+ case '(':
73
+ case ')':
74
+ case '[':
75
+ case ']':
76
+ case '{':
77
+ case '}':
78
+ case '\\':
79
+ regexStr += '\\' + char;
80
+ break;
81
+ default:
82
+ regexStr += char;
83
+ }
84
+ }
85
+ regexStr += '$';
86
+
87
+ return new RegExp(regexStr);
88
+ }
89
+
90
+ /**
91
+ * Check if a file path matches a pattern (safe from ReDoS)
92
+ */
93
+ function matchesPattern(relativePath: string, pattern: string): boolean {
94
+ // Handle brace expansion: {test,spec} or {ts,js,tsx,etc}
95
+ // Use string operations instead of regex to avoid ReDoS
96
+ const openBraceIndex = pattern.indexOf('{');
97
+
98
+ if (openBraceIndex !== -1) {
99
+ const closeBraceIndex = pattern.indexOf('}', openBraceIndex);
100
+ if (closeBraceIndex !== -1) {
101
+ // Expand braces to multiple patterns
102
+ const options = pattern.slice(openBraceIndex + 1, closeBraceIndex).split(',');
103
+ const before = pattern.slice(0, openBraceIndex);
104
+ const after = pattern.slice(closeBraceIndex + 1);
105
+
106
+ for (const option of options) {
107
+ const testPattern = before + option + after;
108
+ if (matchesPattern(relativePath, testPattern)) {
109
+ return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+ }
115
+
116
+ // Simple glob to regex conversion
117
+ const regex = globToRegex(pattern);
118
+ return regex.test(relativePath);
119
+ }
120
+
121
+ /**
122
+ * Find all test files matching patterns
123
+ */
124
+ function findTestFiles(
125
+ root: string,
126
+ include: string[],
127
+ exclude: string[]
128
+ ): string[] {
129
+ const files: string[] = [];
130
+
131
+ // Normalize path to use forward slashes for pattern matching
132
+ // This ensures cross-platform compatibility
133
+ function normalizePathForPattern(path: string): string {
134
+ return path.replace(/\\/g, '/');
135
+ }
136
+
137
+ function scanDir(dir: string) {
138
+ try {
139
+ const entries = readdirSync(dir, { withFileTypes: true });
140
+
141
+ for (const entry of entries) {
142
+ if (typeof entry === 'string') continue;
143
+
144
+ const fullPath = join(dir, entry.name);
145
+
146
+ if (entry.isDirectory()) {
147
+ // Check if directory should be excluded
148
+ const relativePath = normalizePathForPattern(relative(root, fullPath));
149
+ if (exclude.some(pattern => matchesPattern(relativePath, pattern))) {
150
+ continue;
151
+ }
152
+ scanDir(fullPath);
153
+ } else if (entry.isFile()) {
154
+ // Check if file matches include patterns
155
+ const relativePath = normalizePathForPattern(relative(root, fullPath));
156
+ // First check if file should be excluded
157
+ if (exclude.some(pattern => matchesPattern(relativePath, pattern))) {
158
+ continue;
159
+ }
160
+ for (const pattern of include) {
161
+ if (matchesPattern(relativePath, pattern)) {
162
+ files.push(fullPath);
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ } catch (error) {
169
+ // Ignore permission errors
170
+ }
171
+ }
172
+
173
+ scanDir(root);
174
+ return files;
175
+ }
176
+
177
+ /**
178
+ * Run tests with Jest-compatible interface
179
+ */
180
+ export async function runJestTests(options: TestOptions = {}) {
181
+ const {
182
+ include = ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
183
+ exclude = ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/.elit-tests-temp/**'],
184
+ reporter = 'default',
185
+ timeout = 5000,
186
+ bail = false,
187
+ globals = true,
188
+ } = options;
189
+
190
+ const root = process.cwd();
191
+ const files = options.files || findTestFiles(root, include, exclude);
192
+
193
+ if (files.length === 0) {
194
+ console.log('\n No test files found\n');
195
+ return {
196
+ success: true,
197
+ passed: 0,
198
+ failed: 0,
199
+ total: 0,
200
+ };
201
+ }
202
+
203
+ // Setup globals if enabled
204
+ if (globals) {
205
+ setupGlobals();
206
+ }
207
+
208
+ // Create reporter
209
+ let testReporter;
210
+ switch (reporter) {
211
+ case 'dot':
212
+ testReporter = new DotReporter();
213
+ break;
214
+ case 'json':
215
+ testReporter = new JsonReporter();
216
+ break;
217
+ case 'verbose':
218
+ testReporter = new VerboseReporter();
219
+ break;
220
+ default:
221
+ testReporter = new TestReporter({ colors: true });
222
+ }
223
+
224
+ // Notify start
225
+ if ('onRunStart' in testReporter) {
226
+ testReporter.onRunStart(files);
227
+ }
228
+
229
+ // Reset coverage tracking before running tests
230
+ resetCoveredFiles();
231
+
232
+ // Run tests
233
+ const results = await runTests({
234
+ files,
235
+ timeout,
236
+ bail,
237
+ describePattern: options.describePattern,
238
+ testPattern: options.testPattern,
239
+ });
240
+ // Notify individual test results
241
+ if ('onTestResult' in testReporter) {
242
+ for (const result of results.results) {
243
+ testReporter.onTestResult(result);
244
+ }
245
+ }
246
+
247
+ // Notify end
248
+ if ('onRunEnd' in testReporter) {
249
+ testReporter.onRunEnd(results.results);
250
+ }
251
+
252
+ // Clear globals
253
+ if (globals) {
254
+ clearGlobals();
255
+ }
256
+
257
+ // Generate coverage if enabled
258
+ if (options.coverage?.enabled) {
259
+ await generateCoverage(options.coverage, results.results);
260
+ }
261
+
262
+ return {
263
+ success: results.failed === 0,
264
+ passed: results.passed,
265
+ failed: results.failed,
266
+ total: results.passed + results.failed + results.skipped + results.todo,
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Generate coverage report
272
+ */
273
+ async function generateCoverage(
274
+ options: {
275
+ provider: 'v8' | 'istanbul';
276
+ reporter?: ('text' | 'html' | 'lcov' | 'json' | 'coverage-final.json' | 'clover')[];
277
+ include?: string[];
278
+ exclude?: string[];
279
+ },
280
+ testResults?: any[]
281
+ ) {
282
+ const { processCoverage, generateTextReport, generateHtmlReport, generateCoverageFinalJson, generateCloverXml } = await import('./coverage');
283
+
284
+ // Get covered files from test execution
285
+ const coveredFilesForCoverage = getCoveredFiles();
286
+
287
+ const coverageMap = await processCoverage({
288
+ reportsDirectory: './coverage',
289
+ include: options.include || ['**/*.ts', '**/*.js'],
290
+ exclude: options.exclude || ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**'],
291
+ reporter: options.reporter || ['text', 'html'],
292
+ coveredFiles: coveredFilesForCoverage,
293
+ });
294
+
295
+ const reporters = options.reporter || ['text', 'html'];
296
+ if (reporters.includes('text')) {
297
+ console.log('\n' + generateTextReport(coverageMap, testResults));
298
+ }
299
+
300
+ if (reporters.includes('html')) {
301
+ generateHtmlReport(coverageMap, './coverage');
302
+ console.log(`\n Coverage report: coverage/index.html\n`);
303
+ }
304
+
305
+ if (reporters.includes('coverage-final.json')) {
306
+ generateCoverageFinalJson(coverageMap, './coverage');
307
+ console.log(`\n Coverage report: coverage/coverage-final.json\n`);
308
+ }
309
+
310
+ if (reporters.includes('clover')) {
311
+ generateCloverXml(coverageMap, './coverage');
312
+ console.log(`\n Coverage report: coverage/clover.xml\n`);
313
+ }
314
+ }
315
+
316
+ // ============================================================================
317
+ // Watch Mode
318
+ // ============================================================================
319
+
320
+ export async function runWatchMode(options: TestOptions = {}) {
321
+ const chokidar = await import('chokidar');
322
+
323
+ console.log('\n � watch mode - files will be re-run on change\n');
324
+
325
+ let isRunning = false;
326
+ let needsRerun = false;
327
+
328
+ const runTests = async () => {
329
+ if (isRunning) {
330
+ needsRerun = true;
331
+ return;
332
+ }
333
+
334
+ isRunning = true;
335
+ needsRerun = false;
336
+
337
+ console.clear();
338
+ await runJestTests(options);
339
+
340
+ isRunning = false;
341
+
342
+ if (needsRerun) {
343
+ await runTests();
344
+ }
345
+ };
346
+
347
+ // Initial run
348
+ await runTests();
349
+
350
+ const { include, exclude } = options;
351
+ const watchPatterns = include || ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'];
352
+ const ignoredPatterns = exclude || ['**/node_modules/**', '**/dist/**', '**/coverage/**'];
353
+
354
+ const watcher = chokidar.default.watch(watchPatterns, {
355
+ ignored: ignoredPatterns,
356
+ persistent: true,
357
+ });
358
+
359
+ watcher.on('change', async (path) => {
360
+ console.log(`\n 📄 ${path} changed\n`);
361
+ await runTests();
362
+ });
363
+
364
+ watcher.on('add', async (path) => {
365
+ console.log(`\n 📄 ${path} added\n`);
366
+ await runTests();
367
+ });
368
+ }
package/src/types.ts CHANGED
@@ -268,3 +268,62 @@ export interface PreviewOptions {
268
268
  /** Environment variables to inject (prefix with VITE_ for client access) */
269
269
  env?: Record<string, string>;
270
270
  }
271
+
272
+ // ===== Test Types =====
273
+
274
+ export type TestEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime';
275
+
276
+ export type TestCoverageProvider = 'v8' | 'istanbul';
277
+
278
+ export type TestCoverageReporter = 'text' | 'json' | 'html' | 'lcov' | 'lcovonly' | 'coverage-final.json' | 'clover';
279
+
280
+ export interface TestCoverageOptions {
281
+ provider?: TestCoverageProvider;
282
+ reporter?: TestCoverageReporter[];
283
+ dir?: string;
284
+ include?: string[];
285
+ exclude?: string[];
286
+ thresholds?: {
287
+ lines?: number;
288
+ functions?: number;
289
+ branches?: number;
290
+ statements?: number;
291
+ };
292
+ all?: boolean;
293
+ }
294
+
295
+ export interface TestOptions {
296
+ environment?: TestEnvironment;
297
+ globals?: boolean;
298
+ setupFiles?: string[];
299
+ include?: string[];
300
+ exclude?: string[];
301
+ testTimeout?: number;
302
+ isolate?: boolean;
303
+ pool?: string;
304
+ poolOptions?: {
305
+ threads?: {
306
+ singleThread?: boolean;
307
+ minThreads?: number;
308
+ maxThreads?: number;
309
+ isolate?: boolean;
310
+ };
311
+ forks?: {
312
+ singleFork?: boolean;
313
+ minForks?: number;
314
+ maxForks?: number;
315
+ isolate?: boolean;
316
+ };
317
+ };
318
+ coverage?: TestCoverageOptions;
319
+ watch?: boolean;
320
+ ui?: boolean;
321
+ reporter?: 'verbose' | 'dot' | 'json' | 'tap';
322
+ bail?: number | boolean;
323
+ pattern?: string | RegExp;
324
+ colors?: boolean;
325
+ retry?: number;
326
+ includeSrc?: string[];
327
+ excludeSrc?: string[];
328
+ env?: Record<string, string>;
329
+ }
package/src/ws.ts CHANGED
@@ -348,15 +348,45 @@ export class WebSocketServer extends EventEmitter {
348
348
  });
349
349
 
350
350
  socket.on('error', (error: Error) => {
351
+ // Silently ignore connection errors (ECONNABORTED, ECONNRESET, etc.)
352
+ // These are normal when clients disconnect during HMR
353
+ const errorCode = (error as any).code;
354
+ if (errorCode === 'ECONNABORTED' || errorCode === 'ECONNRESET' || errorCode === 'EPIPE') {
355
+ // Silently ignore - connection was closed by client
356
+ return;
357
+ }
358
+ // Only emit other errors
351
359
  client.emit('error', error);
352
360
  });
353
361
 
354
362
  // Override send method
355
363
  client.send = (data: Data, _options?: any, callback?: (err?: Error) => void) => {
364
+ // Check if socket is still writable
365
+ if (!socket.writable || client.readyState !== ReadyState.OPEN) {
366
+ const err = new Error('WebSocket is not open');
367
+ (err as any).code = 'WS_NOT_OPEN';
368
+ queueCallback(callback, err);
369
+ return;
370
+ }
371
+
356
372
  try {
357
373
  const frame = this._createFrame(data);
358
- socket.write(frame);
359
- queueCallback(callback);
374
+ socket.write(frame, (err?: Error) => {
375
+ // Handle async write errors (ECONNABORTED, ECONNRESET, etc.)
376
+ if (err) {
377
+ const errorCode = (err as any).code;
378
+ // Silently ignore connection errors - these are normal during HMR
379
+ if (errorCode !== 'ECONNABORTED' && errorCode !== 'ECONNRESET' && errorCode !== 'EPIPE') {
380
+ queueCallback(callback, err);
381
+ } else {
382
+ // Connection closed - mark client as closed
383
+ client.readyState = ReadyState.CLOSED;
384
+ queueCallback(callback); // Call without error for graceful handling
385
+ }
386
+ } else {
387
+ queueCallback(callback);
388
+ }
389
+ });
360
390
  } catch (error) {
361
391
  queueCallback(callback, error as Error);
362
392
  }