tablinum 0.0.1 → 0.1.1

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 (105) hide show
  1. package/README.md +77 -1
  2. package/dist/crud/collection-handle.d.ts +21 -0
  3. package/dist/crud/query-builder.d.ts +42 -0
  4. package/dist/crud/watch.d.ts +25 -0
  5. package/dist/db/create-tablinum.d.ts +12 -0
  6. package/dist/db/database-handle.d.ts +13 -0
  7. package/dist/db/identity.d.ts +8 -0
  8. package/dist/errors.d.ts +58 -0
  9. package/{src/index.ts → dist/index.d.ts} +4 -24
  10. package/dist/index.js +1833 -0
  11. package/dist/main.d.ts +1 -0
  12. package/dist/schema/collection.d.ts +12 -0
  13. package/dist/schema/field.d.ts +17 -0
  14. package/{src/schema/types.ts → dist/schema/types.d.ts} +5 -10
  15. package/dist/schema/validate.d.ts +13 -0
  16. package/dist/storage/events-store.d.ts +6 -0
  17. package/dist/storage/giftwraps-store.d.ts +6 -0
  18. package/dist/storage/idb.d.ts +35 -0
  19. package/dist/storage/lww.d.ts +10 -0
  20. package/dist/storage/records-store.d.ts +12 -0
  21. package/dist/svelte/collection.svelte.d.ts +20 -0
  22. package/dist/svelte/database.svelte.d.ts +15 -0
  23. package/dist/svelte/index.svelte.d.ts +16 -0
  24. package/dist/svelte/index.svelte.js +2050 -0
  25. package/dist/svelte/live-query.svelte.d.ts +8 -0
  26. package/dist/svelte/query.svelte.d.ts +39 -0
  27. package/dist/sync/gift-wrap.d.ts +9 -0
  28. package/dist/sync/negentropy.d.ts +9 -0
  29. package/dist/sync/publish-queue.d.ts +10 -0
  30. package/dist/sync/relay.d.ts +17 -0
  31. package/dist/sync/sync-service.d.ts +14 -0
  32. package/dist/sync/sync-status.d.ts +7 -0
  33. package/dist/utils/uuid.d.ts +2 -0
  34. package/package.json +22 -1
  35. package/.changeset/README.md +0 -8
  36. package/.changeset/config.json +0 -11
  37. package/.context/attachments/pasted_text_2026-03-07_14-02-40.txt +0 -571
  38. package/.context/attachments/pasted_text_2026-03-07_15-48-27.txt +0 -498
  39. package/.context/notes.md +0 -0
  40. package/.context/plans/add-changesets-to-douala-v4.md +0 -48
  41. package/.context/plans/dexie-js-style-query-language-for-localstr.md +0 -115
  42. package/.context/plans/dexie-js-style-query-language-with-per-collection-.md +0 -336
  43. package/.context/plans/implementation-plan-localstr-v0-2.md +0 -263
  44. package/.context/plans/project-init-effect-v4-bun-oxlint-oxfmt-vitest.md +0 -71
  45. package/.context/plans/revise-localstr-prd-v0-2.md +0 -132
  46. package/.context/plans/svelte-5-runes-bindings-for-localstr.md +0 -233
  47. package/.context/todos.md +0 -0
  48. package/.github/workflows/release.yml +0 -36
  49. package/.oxlintrc.json +0 -8
  50. package/bun.lock +0 -705
  51. package/examples/svelte/bun.lock +0 -261
  52. package/examples/svelte/package.json +0 -21
  53. package/examples/svelte/src/app.html +0 -11
  54. package/examples/svelte/src/lib/db.ts +0 -44
  55. package/examples/svelte/src/routes/+page.svelte +0 -322
  56. package/examples/svelte/svelte.config.js +0 -16
  57. package/examples/svelte/tsconfig.json +0 -6
  58. package/examples/svelte/vite.config.ts +0 -6
  59. package/examples/vanilla/app.ts +0 -219
  60. package/examples/vanilla/index.html +0 -144
  61. package/examples/vanilla/serve.ts +0 -42
  62. package/prds/localstr-v0.2.md +0 -221
  63. package/prek.toml +0 -10
  64. package/scripts/validate.ts +0 -392
  65. package/src/crud/collection-handle.ts +0 -189
  66. package/src/crud/query-builder.ts +0 -414
  67. package/src/crud/watch.ts +0 -78
  68. package/src/db/create-localstr.ts +0 -217
  69. package/src/db/database-handle.ts +0 -16
  70. package/src/db/identity.ts +0 -49
  71. package/src/errors.ts +0 -37
  72. package/src/main.ts +0 -10
  73. package/src/schema/collection.ts +0 -53
  74. package/src/schema/field.ts +0 -25
  75. package/src/schema/validate.ts +0 -111
  76. package/src/storage/events-store.ts +0 -24
  77. package/src/storage/giftwraps-store.ts +0 -23
  78. package/src/storage/idb.ts +0 -244
  79. package/src/storage/lww.ts +0 -17
  80. package/src/storage/records-store.ts +0 -76
  81. package/src/svelte/collection.svelte.ts +0 -87
  82. package/src/svelte/database.svelte.ts +0 -83
  83. package/src/svelte/index.svelte.ts +0 -52
  84. package/src/svelte/live-query.svelte.ts +0 -29
  85. package/src/svelte/query.svelte.ts +0 -101
  86. package/src/sync/gift-wrap.ts +0 -33
  87. package/src/sync/negentropy.ts +0 -83
  88. package/src/sync/publish-queue.ts +0 -61
  89. package/src/sync/relay.ts +0 -239
  90. package/src/sync/sync-service.ts +0 -183
  91. package/src/sync/sync-status.ts +0 -17
  92. package/src/utils/uuid.ts +0 -22
  93. package/src/vendor/negentropy.js +0 -616
  94. package/tests/db/create-localstr.test.ts +0 -174
  95. package/tests/db/identity.test.ts +0 -33
  96. package/tests/main.test.ts +0 -9
  97. package/tests/schema/collection.test.ts +0 -27
  98. package/tests/schema/field.test.ts +0 -41
  99. package/tests/schema/validate.test.ts +0 -85
  100. package/tests/setup.ts +0 -1
  101. package/tests/storage/idb.test.ts +0 -144
  102. package/tests/storage/lww.test.ts +0 -33
  103. package/tests/sync/gift-wrap.test.ts +0 -56
  104. package/tsconfig.json +0 -18
  105. package/vitest.config.ts +0 -8
@@ -1,571 +0,0 @@
1
- import { exec, type ChildProcess } from 'node:child_process';
2
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
- import { basename, dirname, join } from 'node:path';
4
-
5
- interface ValidationError {
6
- tool: string;
7
- file?: string;
8
- line?: number;
9
- column?: number;
10
- message: string;
11
- code?: string;
12
- severity: 'error' | 'warning';
13
- }
14
-
15
- interface ValidationResult {
16
- success: boolean;
17
- errors: ValidationError[];
18
- summary: string;
19
- duration_ms: number;
20
- stopped_early: boolean;
21
- next_action?: string;
22
- }
23
-
24
- const MAX_ERRORS = 3;
25
- const E2E_SKIP_RUNTIME = process.env.VALIDATE_SKIP_E2E_RUNTIME === '1';
26
- const ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-9;?]*[A-Za-z]`, 'g');
27
-
28
- // Shared state for fail-fast behavior
29
- let collectedErrors: ValidationError[] = [];
30
- let failedTools: string[] = [];
31
- let aborted = false;
32
- const runningProcesses: ChildProcess[] = [];
33
-
34
- function abort() {
35
- aborted = true;
36
- // Kill all running child processes
37
- for (const proc of runningProcesses) {
38
- proc.kill('SIGTERM');
39
- }
40
- }
41
-
42
- function addErrors(toolName: string, errors: ValidationError[]): boolean {
43
- if (aborted || errors.length === 0) return aborted;
44
-
45
- if (!failedTools.includes(toolName)) {
46
- failedTools.push(toolName);
47
- }
48
-
49
- for (const error of errors) {
50
- if (collectedErrors.length >= MAX_ERRORS) {
51
- abort();
52
- return true;
53
- }
54
- collectedErrors.push(error);
55
- }
56
-
57
- if (collectedErrors.length >= MAX_ERRORS) {
58
- abort();
59
- return true;
60
- }
61
-
62
- return false;
63
- }
64
-
65
- function run(cmd: string): Promise<{ stdout: string; stderr: string; success: boolean }> {
66
- return new Promise((resolve) => {
67
- if (aborted) {
68
- resolve({ stdout: '', stderr: '', success: false });
69
- return;
70
- }
71
-
72
- const proc = exec(
73
- cmd,
74
- { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 },
75
- (error, stdout, stderr) => {
76
- const idx = runningProcesses.indexOf(proc);
77
- if (idx !== -1) runningProcesses.splice(idx, 1);
78
-
79
- if (aborted) {
80
- resolve({ stdout: '', stderr: '', success: false });
81
- return;
82
- }
83
-
84
- resolve({
85
- stdout: stdout ?? '',
86
- stderr: stderr ?? '',
87
- success: !error
88
- });
89
- }
90
- );
91
-
92
- runningProcesses.push(proc);
93
- });
94
- }
95
-
96
- function summarizeFailureOutput(text: string): string | undefined {
97
- const lines = normalizeOutputLines(text);
98
-
99
- if (lines.length === 0) return undefined;
100
-
101
- const prioritized = lines.find((line) =>
102
- /(^error\b|failed|ELIFECYCLE|\[WebServer\])/i.test(line)
103
- );
104
- return prioritized ?? lines[0];
105
- }
106
-
107
- function normalizeOutputLines(text: string): string[] {
108
- return text
109
- .split('\n')
110
- .map((line) => line.replace(ANSI_ESCAPE_PATTERN, '').trim())
111
- .filter((line) => line.length > 0);
112
- }
113
-
114
- function truncateText(text: string, maxLength = 220): string {
115
- return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}...`;
116
- }
117
-
118
- function summarizePlaywrightFailure(text: string): string | undefined {
119
- const lines = normalizeOutputLines(text);
120
- const failureIndex = lines.findIndex((line) => /^\d+\)\s+\[[^\]]+\]\s+›/.test(line));
121
- const failureLine = failureIndex >= 0 ? lines[failureIndex] : undefined;
122
- const detailLine =
123
- (failureIndex >= 0 &&
124
- lines
125
- .slice(failureIndex + 1)
126
- .find((line) => /^Error:/i.test(line) || /expect\(.+\)\..+\sfailed/i.test(line))) ??
127
- lines.find((line) => /^Error:/i.test(line));
128
-
129
- if (failureLine && detailLine) {
130
- return truncateText(`${failureLine} :: ${detailLine}`);
131
- }
132
-
133
- if (failureLine) {
134
- return truncateText(failureLine);
135
- }
136
-
137
- return undefined;
138
- }
139
-
140
- function countListedTestsForProject(output: string, projectName: string): number {
141
- return output
142
- .split('\n')
143
- .map((line) => line.trim())
144
- .filter((line) => line.startsWith(`[${projectName}]`) && line.includes('›')).length;
145
- }
146
-
147
- async function runTestCoverage(): Promise<ValidationError[]> {
148
- if (aborted) return [];
149
- const errors: ValidationError[] = [];
150
-
151
- const { stdout } = await run('git ls-files "src/**/*.ts" "src/**/*.svelte"');
152
- if (aborted) return [];
153
-
154
- const sourceFiles = stdout.split('\n').filter(Boolean);
155
-
156
- const skipPatterns = [
157
- /\.test\.ts$/,
158
- /\.spec\.ts$/,
159
- /\.d\.ts$/,
160
- /index\.ts$/,
161
- /types\.ts$/,
162
- /\.styles\.ts$/,
163
- /\.stories\.svelte$/,
164
- /\.test-wrapper\.svelte$/,
165
- /\+error\.svelte$/,
166
- /\/data\/Data\.svelte$/,
167
- /\.remote\.ts$/,
168
- /\/server\/auth\.ts$/,
169
- /\/server\/access\.ts$/,
170
- /\/server\/auth-guards\.ts$/,
171
- /\/server\/db\/schema\//,
172
- /\/testing\/mocks\.ts$/,
173
- /\+layout\.(server\.)?ts$/,
174
- /\+layout\.svelte$/,
175
- /\+page\.server\.ts$/,
176
- /\+page\.svelte$/,
177
- /\+page\.ts$/
178
- ];
179
-
180
- for (const file of sourceFiles) {
181
- if (aborted) break;
182
- if (skipPatterns.some((p) => p.test(file))) continue;
183
-
184
- const dir = dirname(file);
185
- const name = basename(file).replace(/\.(ts|svelte)$/, '');
186
- const alternateName = name.startsWith('+') ? name.slice(1) : null;
187
-
188
- const testPaths = [
189
- join(dir, `${name}.test.ts`),
190
- join(dir, `${name}.spec.ts`),
191
- join(dir, `${name}.svelte.test.ts`),
192
- join(dir, `${name}.svelte.spec.ts`),
193
- join(dir, `${name}.ssr.test.ts`),
194
- join(dir, `${name}.ssr.spec.ts`),
195
- join(dir, '__tests__', `${name}.test.ts`),
196
- join(dir, '__tests__', `${name}.spec.ts`)
197
- ];
198
- if (alternateName) {
199
- testPaths.push(
200
- join(dir, `${alternateName}.test.ts`),
201
- join(dir, `${alternateName}.spec.ts`),
202
- join(dir, '__tests__', `${alternateName}.test.ts`),
203
- join(dir, '__tests__', `${alternateName}.spec.ts`)
204
- );
205
- }
206
-
207
- const hasTest = testPaths.some((p) => existsSync(p));
208
-
209
- if (!hasTest) {
210
- errors.push({
211
- tool: 'test-coverage',
212
- file,
213
- severity: 'error',
214
- message: `Missing test file. Expected: ${name}.test.ts${alternateName ? ` or ${alternateName}.test.ts` : ''}`
215
- });
216
- }
217
- }
218
-
219
- return errors;
220
- }
221
-
222
- async function runPrettier(): Promise<ValidationError[]> {
223
- if (aborted) return [];
224
- const errors: ValidationError[] = [];
225
- const { stdout, success } = await run(
226
- 'pnpm exec prettier --check --plugin prettier-plugin-svelte .'
227
- );
228
-
229
- if (aborted) return [];
230
-
231
- if (!success) {
232
- for (const line of stdout.split('\n')) {
233
- if (line.startsWith('[warn]') || line.includes('Forgot to run') || line.includes('Checking'))
234
- continue;
235
- if (line.trim()) {
236
- errors.push({
237
- tool: 'prettier',
238
- file: line.trim(),
239
- severity: 'error',
240
- message: 'File needs formatting. Run: pnpm exec prettier --write ' + line.trim()
241
- });
242
- }
243
- }
244
- }
245
-
246
- return errors;
247
- }
248
-
249
- async function runOxlint(): Promise<ValidationError[]> {
250
- if (aborted) return [];
251
- const errors: ValidationError[] = [];
252
- const { stdout } = await run('pnpm exec oxlint --format json');
253
-
254
- if (aborted) return [];
255
-
256
- try {
257
- // oxlint outputs JSON with diagnostics array
258
- const result = JSON.parse(stdout);
259
- for (const diagnostic of result.diagnostics ?? []) {
260
- if (diagnostic.severity === 'error') {
261
- const label = diagnostic.labels?.[0];
262
- errors.push({
263
- tool: 'oxlint',
264
- file: diagnostic.filename,
265
- line: label?.span?.line,
266
- column: label?.span?.column,
267
- code: diagnostic.code,
268
- severity: 'error',
269
- message: diagnostic.message
270
- });
271
- }
272
- }
273
- } catch {
274
- // JSON parse failed, no errors to report
275
- }
276
-
277
- return errors;
278
- }
279
-
280
- async function runSvelteCheck(): Promise<ValidationError[]> {
281
- if (aborted) return [];
282
- const errors: ValidationError[] = [];
283
- const { stdout } = await run('pnpm exec svelte-check --output machine-verbose');
284
-
285
- if (aborted) return [];
286
-
287
- for (const line of stdout.split('\n')) {
288
- const match = line.match(/^(\w+)\s+(.+):(\d+):(\d+)\s+(.+)$/);
289
- if (match && match[1] === 'ERROR') {
290
- errors.push({
291
- tool: 'svelte-check',
292
- file: match[2],
293
- line: parseInt(match[3]),
294
- column: parseInt(match[4]),
295
- severity: 'error',
296
- message: match[5]
297
- });
298
- }
299
- }
300
-
301
- return errors;
302
- }
303
-
304
- async function runTypeScript(): Promise<ValidationError[]> {
305
- if (aborted) return [];
306
- const errors: ValidationError[] = [];
307
- const { stdout } = await run('pnpm exec tsc --noEmit --pretty false');
308
-
309
- if (aborted) return [];
310
-
311
- for (const line of stdout.split('\n')) {
312
- const match = line.match(/^(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/);
313
- if (match) {
314
- errors.push({
315
- tool: 'tsc',
316
- file: match[1],
317
- line: parseInt(match[2]),
318
- column: parseInt(match[3]),
319
- code: match[4],
320
- severity: 'error',
321
- message: match[5]
322
- });
323
- }
324
- }
325
-
326
- return errors;
327
- }
328
-
329
- const E2E_REQUIRED_ROUTES = ['/', '/login', '/login/check-email', '/account', '/dashboard'];
330
- const E2E_MIN_CHROMIUM_TESTS = 10;
331
- const E2E_MIN_CHROMIUM_AUTH_TESTS = 1;
332
-
333
- async function runE2ECoverage(): Promise<ValidationError[]> {
334
- if (aborted) return [];
335
- const errors: ValidationError[] = [];
336
- const e2eDir = 'e2e';
337
-
338
- if (!existsSync(e2eDir)) {
339
- errors.push({
340
- tool: 'e2e-coverage',
341
- severity: 'error',
342
- message: 'Missing e2e/ directory. Run: mkdir e2e'
343
- });
344
- return errors;
345
- }
346
-
347
- // Run Playwright discovery for chromium.
348
- const {
349
- stdout: chromiumListStdout,
350
- stderr: chromiumListStderr,
351
- success: chromiumListSuccess
352
- } = await run('pnpm exec playwright test --project=chromium --list');
353
- if (aborted) return [];
354
-
355
- if (!chromiumListSuccess) {
356
- const output = `${chromiumListStderr}\n${chromiumListStdout}`.trim();
357
- const firstLine = summarizeFailureOutput(output);
358
- errors.push({
359
- tool: 'e2e-coverage',
360
- severity: 'error',
361
- message: `Playwright chromium discovery failed${firstLine ? `: ${firstLine}` : ''}`
362
- });
363
- return errors;
364
- }
365
-
366
- const chromiumTotal = countListedTestsForProject(chromiumListStdout, 'chromium');
367
-
368
- if (chromiumTotal < E2E_MIN_CHROMIUM_TESTS) {
369
- errors.push({
370
- tool: 'e2e-coverage',
371
- severity: 'error',
372
- message: `Chromium E2E test count too low (${chromiumTotal}). Minimum required: ${E2E_MIN_CHROMIUM_TESTS}`
373
- });
374
- }
375
-
376
- // Ensure authenticated project is still covered.
377
- const {
378
- stdout: chromiumAuthListStdout,
379
- stderr: chromiumAuthListStderr,
380
- success: chromiumAuthListSuccess
381
- } = await run('pnpm exec playwright test --project=chromium-auth --list');
382
- if (aborted) return [];
383
-
384
- if (!chromiumAuthListSuccess) {
385
- const output = `${chromiumAuthListStderr}\n${chromiumAuthListStdout}`.trim();
386
- const firstLine = summarizeFailureOutput(output);
387
- errors.push({
388
- tool: 'e2e-coverage',
389
- severity: 'error',
390
- message: `Playwright chromium-auth discovery failed${firstLine ? `: ${firstLine}` : ''}`
391
- });
392
- return errors;
393
- }
394
-
395
- const chromiumAuthTotal = countListedTestsForProject(chromiumAuthListStdout, 'chromium-auth');
396
-
397
- if (chromiumAuthTotal < E2E_MIN_CHROMIUM_AUTH_TESTS) {
398
- errors.push({
399
- tool: 'e2e-coverage',
400
- severity: 'error',
401
- message: `Chromium-auth E2E test count too low (${chromiumAuthTotal}). Minimum required: ${E2E_MIN_CHROMIUM_AUTH_TESTS}`
402
- });
403
- }
404
-
405
- const testFiles: string[] = [];
406
- const walk = (dir: string) => {
407
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
408
- const fullPath = join(dir, entry.name);
409
- if (entry.isDirectory()) {
410
- walk(fullPath);
411
- continue;
412
- }
413
- if (entry.isFile() && entry.name.endsWith('.spec.ts')) {
414
- testFiles.push(fullPath);
415
- }
416
- }
417
- };
418
- walk(e2eDir);
419
-
420
- if (testFiles.length === 0) {
421
- errors.push({
422
- tool: 'e2e-coverage',
423
- severity: 'error',
424
- message: 'No E2E test files found in e2e/ directory'
425
- });
426
- return errors;
427
- }
428
-
429
- const testedRoutes = new Set<string>();
430
- for (const file of testFiles) {
431
- const content = readFileSync(file, 'utf-8');
432
- const matches = content.matchAll(/page\.goto\(['"]([^'"]+)['"]\)/g);
433
- for (const match of matches) {
434
- testedRoutes.add(match[1]);
435
- }
436
- }
437
-
438
- for (const route of E2E_REQUIRED_ROUTES) {
439
- if (!testedRoutes.has(route)) {
440
- errors.push({
441
- tool: 'e2e-coverage',
442
- severity: 'error',
443
- message: `Missing E2E test for critical route: ${route}`
444
- });
445
- }
446
- }
447
-
448
- return errors;
449
- }
450
-
451
- async function runE2EChromium(): Promise<ValidationError[]> {
452
- if (aborted || E2E_SKIP_RUNTIME) return [];
453
-
454
- const errors: ValidationError[] = [];
455
- const { stdout, stderr, success } = await run(
456
- 'pnpm exec playwright test --project=chromium --workers=1 --reporter=line'
457
- );
458
-
459
- if (aborted) return [];
460
-
461
- if (!success) {
462
- const output = `${stderr}\n${stdout}`.trim();
463
- const firstLine = summarizePlaywrightFailure(output) ?? summarizeFailureOutput(output);
464
- errors.push({
465
- tool: 'e2e-chromium',
466
- severity: 'error',
467
- message: `Chromium E2E run failed${firstLine ? `: ${firstLine}` : ''}`
468
- });
469
- }
470
-
471
- return errors;
472
- }
473
-
474
- async function runVitest(): Promise<ValidationError[]> {
475
- if (aborted) return [];
476
- const errors: ValidationError[] = [];
477
- const { stdout } = await run('pnpm exec vitest run --reporter=json');
478
-
479
- if (aborted) return [];
480
-
481
- try {
482
- const result = JSON.parse(stdout);
483
- for (const file of result.testResults ?? []) {
484
- for (const test of file.assertionResults ?? []) {
485
- if (test.status === 'failed') {
486
- errors.push({
487
- tool: 'vitest',
488
- file: file.name,
489
- line: test.location?.line,
490
- severity: 'error',
491
- message: `${test.ancestorTitles.join(' > ')} > ${test.title}: ${test.failureMessages?.[0]?.split('\n')[0] ?? 'failed'}`
492
- });
493
- }
494
- }
495
- }
496
- } catch {
497
- // JSON parse failed, no errors to report
498
- }
499
-
500
- return errors;
501
- }
502
-
503
- interface Check {
504
- name: string;
505
- fn: () => Promise<ValidationError[]>;
506
- }
507
-
508
- async function validate(): Promise<ValidationResult> {
509
- const start = performance.now();
510
-
511
- // Reset global state
512
- collectedErrors = [];
513
- failedTools = [];
514
- aborted = false;
515
-
516
- const allChecks: Check[] = [
517
- { name: 'prettier', fn: runPrettier },
518
- { name: 'oxlint', fn: runOxlint },
519
- { name: 'svelte-check', fn: runSvelteCheck },
520
- { name: 'tsc', fn: runTypeScript },
521
- { name: 'test-coverage', fn: runTestCoverage },
522
- { name: 'e2e-coverage', fn: runE2ECoverage },
523
- { name: 'vitest', fn: runVitest }
524
- ];
525
-
526
- if (!E2E_SKIP_RUNTIME) {
527
- allChecks.push({ name: 'e2e-chromium', fn: runE2EChromium });
528
- }
529
-
530
- console.error(`→ Running ${allChecks.length} checks in parallel...`);
531
-
532
- // Run all checks in parallel with fail-fast
533
- await Promise.all(
534
- allChecks.map(async (check) => {
535
- const checkStart = performance.now();
536
- const errors = await check.fn();
537
- const checkDuration = Math.round(performance.now() - checkStart);
538
-
539
- // Add errors and check if we should abort
540
- const wasAborted = addErrors(check.name, errors);
541
-
542
- const status = aborted && errors.length === 0 ? '○' : errors.length === 0 ? '✓' : '✗';
543
- const suffix = wasAborted && errors.length === 0 ? ' (cancelled)' : '';
544
- console.error(` ${status} ${check.name} (${checkDuration}ms)${suffix}`);
545
- })
546
- );
547
-
548
- const duration_ms = Math.round(performance.now() - start);
549
-
550
- const errorSummary = `✗ ${collectedErrors.length} error(s) from ${failedTools.join(', ')}`;
551
-
552
- return {
553
- success: collectedErrors.length === 0,
554
- errors: collectedErrors,
555
- summary:
556
- collectedErrors.length === 0
557
- ? `✓ All ${allChecks.length} checks passed in ${duration_ms}ms`
558
- : errorSummary,
559
- duration_ms,
560
- stopped_early: aborted,
561
- next_action:
562
- collectedErrors.length > 0
563
- ? 'Spawn a sub-agent to fix these errors, then re-run validation.'
564
- : undefined
565
- };
566
- }
567
-
568
- validate().then((result) => {
569
- console.log(JSON.stringify(result, null, 2));
570
- process.exit(result.success ? 0 : 1);
571
- });