@nsxbet/playwright-orchestrator 0.2.0

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 (59) hide show
  1. package/README.md +161 -0
  2. package/bin/run.js +5 -0
  3. package/dist/commands/assign.d.ts +23 -0
  4. package/dist/commands/assign.d.ts.map +1 -0
  5. package/dist/commands/assign.js +256 -0
  6. package/dist/commands/assign.js.map +1 -0
  7. package/dist/commands/extract-timing.d.ts +40 -0
  8. package/dist/commands/extract-timing.d.ts.map +1 -0
  9. package/dist/commands/extract-timing.js +196 -0
  10. package/dist/commands/extract-timing.js.map +1 -0
  11. package/dist/commands/list-tests.d.ts +16 -0
  12. package/dist/commands/list-tests.d.ts.map +1 -0
  13. package/dist/commands/list-tests.js +98 -0
  14. package/dist/commands/list-tests.js.map +1 -0
  15. package/dist/commands/merge-timing.d.ts +19 -0
  16. package/dist/commands/merge-timing.d.ts.map +1 -0
  17. package/dist/commands/merge-timing.js +217 -0
  18. package/dist/commands/merge-timing.js.map +1 -0
  19. package/dist/core/ckk-algorithm.d.ts +38 -0
  20. package/dist/core/ckk-algorithm.d.ts.map +1 -0
  21. package/dist/core/ckk-algorithm.js +192 -0
  22. package/dist/core/ckk-algorithm.js.map +1 -0
  23. package/dist/core/estimate.d.ts +72 -0
  24. package/dist/core/estimate.d.ts.map +1 -0
  25. package/dist/core/estimate.js +142 -0
  26. package/dist/core/estimate.js.map +1 -0
  27. package/dist/core/grep-pattern.d.ts +61 -0
  28. package/dist/core/grep-pattern.d.ts.map +1 -0
  29. package/dist/core/grep-pattern.js +104 -0
  30. package/dist/core/grep-pattern.js.map +1 -0
  31. package/dist/core/index.d.ts +9 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +9 -0
  34. package/dist/core/index.js.map +1 -0
  35. package/dist/core/lpt-algorithm.d.ts +28 -0
  36. package/dist/core/lpt-algorithm.d.ts.map +1 -0
  37. package/dist/core/lpt-algorithm.js +80 -0
  38. package/dist/core/lpt-algorithm.js.map +1 -0
  39. package/dist/core/slugify.d.ts +13 -0
  40. package/dist/core/slugify.d.ts.map +1 -0
  41. package/dist/core/slugify.js +19 -0
  42. package/dist/core/slugify.js.map +1 -0
  43. package/dist/core/test-discovery.d.ts +46 -0
  44. package/dist/core/test-discovery.d.ts.map +1 -0
  45. package/dist/core/test-discovery.js +192 -0
  46. package/dist/core/test-discovery.js.map +1 -0
  47. package/dist/core/timing-store.d.ts +90 -0
  48. package/dist/core/timing-store.d.ts.map +1 -0
  49. package/dist/core/timing-store.js +280 -0
  50. package/dist/core/timing-store.js.map +1 -0
  51. package/dist/core/types.d.ts +241 -0
  52. package/dist/core/types.d.ts.map +1 -0
  53. package/dist/core/types.js +54 -0
  54. package/dist/core/types.js.map +1 -0
  55. package/dist/index.d.ts +9 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +9 -0
  58. package/dist/index.js.map +1 -0
  59. package/package.json +70 -0
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Default timeout for CKK algorithm in milliseconds
3
+ */
4
+ export const DEFAULT_CKK_TIMEOUT = 500;
5
+ /**
6
+ * Complete Karmarkar-Karp (CKK) inspired algorithm for multi-way number partitioning
7
+ *
8
+ * This algorithm finds the optimal distribution of tests across shards by:
9
+ * 1. Using branch and bound search with aggressive pruning
10
+ * 2. Starting with LPT solution as upper bound
11
+ * 3. Exploring assignment tree, pruning when partial makespan exceeds best
12
+ *
13
+ * Falls back to LPT if search exceeds timeout.
14
+ *
15
+ * @param tests - Tests with their durations
16
+ * @param numShards - Number of shards to distribute across
17
+ * @param timeoutMs - Maximum time to search for optimal solution
18
+ * @returns Optimal (or near-optimal) shard assignments
19
+ */
20
+ export function assignWithCKK(tests, numShards, timeoutMs = DEFAULT_CKK_TIMEOUT) {
21
+ if (tests.length === 0) {
22
+ return {
23
+ assignments: createEmptyAssignments(numShards),
24
+ makespan: 0,
25
+ isOptimal: true,
26
+ };
27
+ }
28
+ if (numShards <= 0) {
29
+ throw new Error('Number of shards must be positive');
30
+ }
31
+ if (numShards >= tests.length) {
32
+ // More shards than tests - each test gets its own shard
33
+ return assignOnePerShard(tests, numShards);
34
+ }
35
+ // Sort tests by duration descending for better pruning
36
+ const sortedTests = [...tests].sort((a, b) => b.duration - a.duration);
37
+ // Get LPT solution as upper bound
38
+ const lptResult = assignWithLPTInternal(sortedTests, numShards);
39
+ let bestMakespan = lptResult.makespan;
40
+ let bestAssignment = lptResult.assignments;
41
+ let isOptimal = false;
42
+ // For small inputs, try to find optimal solution
43
+ const startTime = Date.now();
44
+ // Branch and bound search
45
+ const shardLoads = new Array(numShards).fill(0);
46
+ const shardTests = Array.from({ length: numShards }, () => []);
47
+ function search(testIndex) {
48
+ // Check timeout
49
+ if (Date.now() - startTime > timeoutMs) {
50
+ return false; // Timeout, stop search
51
+ }
52
+ // All tests assigned
53
+ if (testIndex >= sortedTests.length) {
54
+ const currentMakespan = Math.max(...shardLoads);
55
+ if (currentMakespan < bestMakespan) {
56
+ bestMakespan = currentMakespan;
57
+ bestAssignment = shardLoads.map((load, i) => ({
58
+ shardIndex: i + 1,
59
+ tests: [...(shardTests[i] ?? [])],
60
+ expectedDuration: load,
61
+ }));
62
+ isOptimal = true;
63
+ }
64
+ return true;
65
+ }
66
+ const test = sortedTests[testIndex];
67
+ if (!test)
68
+ return true;
69
+ // Calculate lower bound: current max + remaining items distributed perfectly
70
+ const remainingDuration = sortedTests
71
+ .slice(testIndex)
72
+ .reduce((sum, t) => sum + t.duration, 0);
73
+ const currentMax = Math.max(...shardLoads);
74
+ const totalAfter = shardLoads.reduce((sum, l) => sum + l, 0) + remainingDuration;
75
+ const lowerBound = Math.max(currentMax, Math.ceil(totalAfter / numShards));
76
+ // Prune if lower bound exceeds best
77
+ if (lowerBound >= bestMakespan) {
78
+ return true;
79
+ }
80
+ // Try assigning to each shard, starting with least loaded
81
+ const shardOrder = shardLoads
82
+ .map((load, i) => ({ load, index: i }))
83
+ .sort((a, b) => a.load - b.load)
84
+ .map((s) => s.index);
85
+ // Skip duplicate loads to avoid redundant exploration
86
+ const seenLoads = new Set();
87
+ for (const shardIdx of shardOrder) {
88
+ const load = shardLoads[shardIdx];
89
+ if (load === undefined)
90
+ continue;
91
+ // Skip if we've already tried a shard with this load
92
+ if (seenLoads.has(load)) {
93
+ continue;
94
+ }
95
+ seenLoads.add(load);
96
+ // Prune: if adding to this shard exceeds best makespan, skip
97
+ if (load + test.duration >= bestMakespan) {
98
+ continue;
99
+ }
100
+ // Assign test to shard
101
+ shardLoads[shardIdx] = load + test.duration;
102
+ shardTests[shardIdx]?.push(test.testId);
103
+ const completed = search(testIndex + 1);
104
+ if (!completed) {
105
+ // Timeout - restore and return
106
+ shardLoads[shardIdx] = load;
107
+ shardTests[shardIdx]?.pop();
108
+ return false;
109
+ }
110
+ // Restore state for backtracking
111
+ shardLoads[shardIdx] = load;
112
+ shardTests[shardIdx]?.pop();
113
+ }
114
+ return true;
115
+ }
116
+ // Only run search for reasonable input sizes
117
+ if (tests.length <= 50) {
118
+ search(0);
119
+ }
120
+ return {
121
+ assignments: bestAssignment,
122
+ makespan: bestMakespan,
123
+ isOptimal,
124
+ };
125
+ }
126
+ /**
127
+ * Simple LPT algorithm for internal use
128
+ */
129
+ function assignWithLPTInternal(sortedTests, numShards) {
130
+ const shards = Array.from({ length: numShards }, (_, i) => ({
131
+ shardIndex: i + 1,
132
+ tests: [],
133
+ expectedDuration: 0,
134
+ }));
135
+ for (const test of sortedTests) {
136
+ // Find shard with minimum load
137
+ let minShard = shards[0];
138
+ for (const shard of shards) {
139
+ if (minShard && shard.expectedDuration < minShard.expectedDuration) {
140
+ minShard = shard;
141
+ }
142
+ }
143
+ if (minShard) {
144
+ minShard.tests.push(test.testId);
145
+ minShard.expectedDuration += test.duration;
146
+ }
147
+ }
148
+ const makespan = Math.max(...shards.map((s) => s.expectedDuration));
149
+ return { assignments: shards, makespan };
150
+ }
151
+ /**
152
+ * Create empty shard assignments
153
+ */
154
+ function createEmptyAssignments(numShards) {
155
+ return Array.from({ length: numShards }, (_, i) => ({
156
+ shardIndex: i + 1,
157
+ tests: [],
158
+ expectedDuration: 0,
159
+ }));
160
+ }
161
+ /**
162
+ * Assign one test per shard when there are more shards than tests
163
+ */
164
+ function assignOnePerShard(tests, numShards) {
165
+ const assignments = createEmptyAssignments(numShards);
166
+ tests.forEach((test, i) => {
167
+ const assignment = assignments[i];
168
+ if (assignment) {
169
+ assignment.tests.push(test.testId);
170
+ assignment.expectedDuration = test.duration;
171
+ }
172
+ });
173
+ const makespan = tests.length > 0 ? Math.max(...tests.map((t) => t.duration)) : 0;
174
+ return {
175
+ assignments,
176
+ makespan,
177
+ isOptimal: true,
178
+ };
179
+ }
180
+ /**
181
+ * Calculate theoretical lower bound for makespan
182
+ * This is the best possible makespan if we could partition perfectly
183
+ */
184
+ export function calculateLowerBound(tests, numShards) {
185
+ if (tests.length === 0)
186
+ return 0;
187
+ const totalDuration = tests.reduce((sum, t) => sum + t.duration, 0);
188
+ const maxSingleTest = Math.max(...tests.map((t) => t.duration));
189
+ // Lower bound is max of: largest single item OR total/shards
190
+ return Math.max(maxSingleTest, Math.ceil(totalDuration / numShards));
191
+ }
192
+ //# sourceMappingURL=ckk-algorithm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ckk-algorithm.js","sourceRoot":"","sources":["../../src/core/ckk-algorithm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import type { DiscoveredTest, TimingDataV2 } from './types.js';
2
+ /**
3
+ * Default milliseconds per line for duration estimation
4
+ */
5
+ export declare const DEFAULT_MS_PER_LINE = 100;
6
+ /**
7
+ * Default duration for a test when no estimation is possible (30 seconds)
8
+ */
9
+ export declare const DEFAULT_TEST_DURATION = 30000;
10
+ /**
11
+ * Count the number of lines in a file
12
+ */
13
+ export declare function countLines(filePath: string): number;
14
+ /**
15
+ * Estimate the duration of a test file based on its line count
16
+ *
17
+ * @param filePath - Path to the test file
18
+ * @param msPerLine - Milliseconds per line (default: 100)
19
+ * @returns Estimated duration in milliseconds
20
+ */
21
+ export declare function estimateDuration(filePath: string, msPerLine?: number): number;
22
+ /**
23
+ * Estimate durations for multiple files
24
+ *
25
+ * @param testDir - Base test directory
26
+ * @param files - List of file paths (relative to testDir)
27
+ * @param msPerLine - Milliseconds per line
28
+ * @returns Map of file paths to estimated durations
29
+ */
30
+ export declare function estimateDurations(testDir: string, files: string[], msPerLine?: number): Map<string, number>;
31
+ /**
32
+ * Estimate the duration of a single test using fallback strategy:
33
+ * 1. Same-file average: Use average duration of known tests in the same file
34
+ * 2. Global average: Use average duration across all known tests
35
+ * 3. Default constant: Use DEFAULT_TEST_DURATION (30 seconds)
36
+ *
37
+ * @param testId - Test ID to estimate
38
+ * @param timingData - Existing timing data (v2)
39
+ * @returns Estimated duration in milliseconds
40
+ */
41
+ export declare function estimateTestDuration(testId: string, timingData: TimingDataV2 | null): number;
42
+ /**
43
+ * Get durations for a list of discovered tests
44
+ *
45
+ * Uses timing data when available, falls back to estimation
46
+ *
47
+ * @param tests - List of discovered tests
48
+ * @param timingData - Existing timing data (v2)
49
+ * @returns Array of tests with their durations and estimation flag
50
+ */
51
+ export declare function getTestDurations(tests: DiscoveredTest[], timingData: TimingDataV2 | null): Array<{
52
+ testId: string;
53
+ file: string;
54
+ duration: number;
55
+ estimated: boolean;
56
+ }>;
57
+ /**
58
+ * Calculate average test duration from timing data
59
+ *
60
+ * @param timingData - Timing data (v2)
61
+ * @returns Average duration in milliseconds, or DEFAULT_TEST_DURATION if no data
62
+ */
63
+ export declare function calculateAverageTestDuration(timingData: TimingDataV2 | null): number;
64
+ /**
65
+ * Calculate average test duration for tests in a specific file
66
+ *
67
+ * @param file - File name
68
+ * @param timingData - Timing data (v2)
69
+ * @returns Average duration in milliseconds, or null if no tests found for file
70
+ */
71
+ export declare function calculateFileAverageTestDuration(file: string, timingData: TimingDataV2 | null): number | null;
72
+ //# sourceMappingURL=estimate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimate.d.ts","sourceRoot":"","sources":["../../src/core/estimate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/D;;GAEG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQnD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAA4B,GACtC,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,EACf,SAAS,GAAE,MAA4B,GACtC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,YAAY,GAAG,IAAI,GAC9B,MAAM,CA0BR;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EAAE,EACvB,UAAU,EAAE,YAAY,GAAG,IAAI,GAC9B,KAAK,CAAC;IACP,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAoBD;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,YAAY,GAAG,IAAI,GAC9B,MAAM,CAQR;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,YAAY,GAAG,IAAI,GAC9B,MAAM,GAAG,IAAI,CAef"}
@@ -0,0 +1,142 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { parseTestId } from './types.js';
4
+ /**
5
+ * Default milliseconds per line for duration estimation
6
+ */
7
+ export const DEFAULT_MS_PER_LINE = 100;
8
+ /**
9
+ * Default duration for a test when no estimation is possible (30 seconds)
10
+ */
11
+ export const DEFAULT_TEST_DURATION = 30000;
12
+ /**
13
+ * Count the number of lines in a file
14
+ */
15
+ export function countLines(filePath) {
16
+ try {
17
+ const content = fs.readFileSync(filePath, 'utf-8');
18
+ return content.split('\n').length;
19
+ }
20
+ catch {
21
+ // If file can't be read, return a reasonable default
22
+ return 50;
23
+ }
24
+ }
25
+ /**
26
+ * Estimate the duration of a test file based on its line count
27
+ *
28
+ * @param filePath - Path to the test file
29
+ * @param msPerLine - Milliseconds per line (default: 100)
30
+ * @returns Estimated duration in milliseconds
31
+ */
32
+ export function estimateDuration(filePath, msPerLine = DEFAULT_MS_PER_LINE) {
33
+ const lines = countLines(filePath);
34
+ return lines * msPerLine;
35
+ }
36
+ /**
37
+ * Estimate durations for multiple files
38
+ *
39
+ * @param testDir - Base test directory
40
+ * @param files - List of file paths (relative to testDir)
41
+ * @param msPerLine - Milliseconds per line
42
+ * @returns Map of file paths to estimated durations
43
+ */
44
+ export function estimateDurations(testDir, files, msPerLine = DEFAULT_MS_PER_LINE) {
45
+ const estimates = new Map();
46
+ for (const file of files) {
47
+ const fullPath = path.join(testDir, file);
48
+ estimates.set(file, estimateDuration(fullPath, msPerLine));
49
+ }
50
+ return estimates;
51
+ }
52
+ /**
53
+ * Estimate the duration of a single test using fallback strategy:
54
+ * 1. Same-file average: Use average duration of known tests in the same file
55
+ * 2. Global average: Use average duration across all known tests
56
+ * 3. Default constant: Use DEFAULT_TEST_DURATION (30 seconds)
57
+ *
58
+ * @param testId - Test ID to estimate
59
+ * @param timingData - Existing timing data (v2)
60
+ * @returns Estimated duration in milliseconds
61
+ */
62
+ export function estimateTestDuration(testId, timingData) {
63
+ if (!timingData || Object.keys(timingData.tests).length === 0) {
64
+ return DEFAULT_TEST_DURATION;
65
+ }
66
+ const { file } = parseTestId(testId);
67
+ // Strategy 1: Same-file average
68
+ const sameFileTests = Object.entries(timingData.tests).filter(([, data]) => data.file === file);
69
+ if (sameFileTests.length > 0) {
70
+ const sum = sameFileTests.reduce((acc, [, data]) => acc + data.duration, 0);
71
+ return Math.round(sum / sameFileTests.length);
72
+ }
73
+ // Strategy 2: Global average
74
+ const allTests = Object.values(timingData.tests);
75
+ if (allTests.length > 0) {
76
+ const sum = allTests.reduce((acc, data) => acc + data.duration, 0);
77
+ return Math.round(sum / allTests.length);
78
+ }
79
+ // Strategy 3: Default constant
80
+ return DEFAULT_TEST_DURATION;
81
+ }
82
+ /**
83
+ * Get durations for a list of discovered tests
84
+ *
85
+ * Uses timing data when available, falls back to estimation
86
+ *
87
+ * @param tests - List of discovered tests
88
+ * @param timingData - Existing timing data (v2)
89
+ * @returns Array of tests with their durations and estimation flag
90
+ */
91
+ export function getTestDurations(tests, timingData) {
92
+ return tests.map((test) => {
93
+ const existing = timingData?.tests[test.testId];
94
+ if (existing) {
95
+ return {
96
+ testId: test.testId,
97
+ file: test.file,
98
+ duration: existing.duration,
99
+ estimated: false,
100
+ };
101
+ }
102
+ return {
103
+ testId: test.testId,
104
+ file: test.file,
105
+ duration: estimateTestDuration(test.testId, timingData),
106
+ estimated: true,
107
+ };
108
+ });
109
+ }
110
+ /**
111
+ * Calculate average test duration from timing data
112
+ *
113
+ * @param timingData - Timing data (v2)
114
+ * @returns Average duration in milliseconds, or DEFAULT_TEST_DURATION if no data
115
+ */
116
+ export function calculateAverageTestDuration(timingData) {
117
+ if (!timingData || Object.keys(timingData.tests).length === 0) {
118
+ return DEFAULT_TEST_DURATION;
119
+ }
120
+ const durations = Object.values(timingData.tests).map((t) => t.duration);
121
+ const sum = durations.reduce((acc, d) => acc + d, 0);
122
+ return Math.round(sum / durations.length);
123
+ }
124
+ /**
125
+ * Calculate average test duration for tests in a specific file
126
+ *
127
+ * @param file - File name
128
+ * @param timingData - Timing data (v2)
129
+ * @returns Average duration in milliseconds, or null if no tests found for file
130
+ */
131
+ export function calculateFileAverageTestDuration(file, timingData) {
132
+ if (!timingData) {
133
+ return null;
134
+ }
135
+ const fileTests = Object.values(timingData.tests).filter((t) => t.file === file);
136
+ if (fileTests.length === 0) {
137
+ return null;
138
+ }
139
+ const sum = fileTests.reduce((acc, t) => acc + t.duration, 0);
140
+ return Math.round(sum / fileTests.length);
141
+ }
142
+ //# sourceMappingURL=estimate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimate.js","sourceRoot":"","sources":["../../src/core/estimate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Maximum length for a grep pattern before switching to grep-file
3
+ */
4
+ export declare const MAX_GREP_PATTERN_LENGTH = 4000;
5
+ /**
6
+ * Escape regex special characters in a string
7
+ *
8
+ * @param str - String to escape
9
+ * @returns Escaped string safe for use in regex
10
+ */
11
+ export declare function escapeRegex(str: string): string;
12
+ /**
13
+ * Extract the test title from a test ID
14
+ * The title is the last part of the titlePath
15
+ *
16
+ * @param testId - Test ID in format file::describe::testTitle
17
+ * @returns The test title
18
+ */
19
+ export declare function extractTitleFromTestId(testId: string): string;
20
+ /**
21
+ * Generate a grep pattern from a list of test IDs
22
+ *
23
+ * Uses the test title (last part of titlePath) for matching.
24
+ * Escapes regex special characters to ensure exact matching.
25
+ *
26
+ * @param testIds - List of test IDs to include in pattern
27
+ * @returns Grep pattern string that matches any of the tests
28
+ */
29
+ export declare function generateGrepPattern(testIds: string[]): string;
30
+ /**
31
+ * Generate grep patterns for multiple shards
32
+ *
33
+ * @param shardTests - Map of shard index to list of test IDs
34
+ * @returns Map of shard index to grep pattern
35
+ */
36
+ export declare function generateGrepPatterns(shardTests: Record<number, string[]>): Record<number, string>;
37
+ /**
38
+ * Check if a grep pattern is too long and should use --grep-file instead
39
+ *
40
+ * @param pattern - Grep pattern to check
41
+ * @returns True if pattern exceeds maximum length
42
+ */
43
+ export declare function isPatternTooLong(pattern: string): boolean;
44
+ /**
45
+ * Generate content for a grep-file (one pattern per line)
46
+ *
47
+ * @param testIds - List of test IDs
48
+ * @returns File content with one escaped title per line
49
+ */
50
+ export declare function generateGrepFileContent(testIds: string[]): string;
51
+ /**
52
+ * Determine the best grep strategy for a list of tests
53
+ *
54
+ * @param testIds - List of test IDs
55
+ * @returns Object with strategy ('pattern' or 'file') and content
56
+ */
57
+ export declare function determineGrepStrategy(testIds: string[]): {
58
+ strategy: 'pattern' | 'file';
59
+ content: string;
60
+ };
61
+ //# sourceMappingURL=grep-pattern.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep-pattern.d.ts","sourceRoot":"","sources":["../../src/core/grep-pattern.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAY7D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACnC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAOjE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACxD,QAAQ,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAeA"}
@@ -0,0 +1,104 @@
1
+ import { parseTestId } from './types.js';
2
+ /**
3
+ * Regex special characters that need escaping in grep patterns
4
+ */
5
+ const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
6
+ /**
7
+ * Maximum length for a grep pattern before switching to grep-file
8
+ */
9
+ export const MAX_GREP_PATTERN_LENGTH = 4000;
10
+ /**
11
+ * Escape regex special characters in a string
12
+ *
13
+ * @param str - String to escape
14
+ * @returns Escaped string safe for use in regex
15
+ */
16
+ export function escapeRegex(str) {
17
+ return str.replace(REGEX_SPECIAL_CHARS, '\\$&');
18
+ }
19
+ /**
20
+ * Extract the test title from a test ID
21
+ * The title is the last part of the titlePath
22
+ *
23
+ * @param testId - Test ID in format file::describe::testTitle
24
+ * @returns The test title
25
+ */
26
+ export function extractTitleFromTestId(testId) {
27
+ const { titlePath } = parseTestId(testId);
28
+ return titlePath[titlePath.length - 1] || testId;
29
+ }
30
+ /**
31
+ * Generate a grep pattern from a list of test IDs
32
+ *
33
+ * Uses the test title (last part of titlePath) for matching.
34
+ * Escapes regex special characters to ensure exact matching.
35
+ *
36
+ * @param testIds - List of test IDs to include in pattern
37
+ * @returns Grep pattern string that matches any of the tests
38
+ */
39
+ export function generateGrepPattern(testIds) {
40
+ if (testIds.length === 0) {
41
+ return '';
42
+ }
43
+ const titles = testIds.map((id) => {
44
+ const title = extractTitleFromTestId(id);
45
+ return escapeRegex(title);
46
+ });
47
+ // Use OR operator to match any of the titles
48
+ return titles.join('|');
49
+ }
50
+ /**
51
+ * Generate grep patterns for multiple shards
52
+ *
53
+ * @param shardTests - Map of shard index to list of test IDs
54
+ * @returns Map of shard index to grep pattern
55
+ */
56
+ export function generateGrepPatterns(shardTests) {
57
+ const patterns = {};
58
+ for (const [shardIndex, testIds] of Object.entries(shardTests)) {
59
+ patterns[Number(shardIndex)] = generateGrepPattern(testIds);
60
+ }
61
+ return patterns;
62
+ }
63
+ /**
64
+ * Check if a grep pattern is too long and should use --grep-file instead
65
+ *
66
+ * @param pattern - Grep pattern to check
67
+ * @returns True if pattern exceeds maximum length
68
+ */
69
+ export function isPatternTooLong(pattern) {
70
+ return pattern.length > MAX_GREP_PATTERN_LENGTH;
71
+ }
72
+ /**
73
+ * Generate content for a grep-file (one pattern per line)
74
+ *
75
+ * @param testIds - List of test IDs
76
+ * @returns File content with one escaped title per line
77
+ */
78
+ export function generateGrepFileContent(testIds) {
79
+ const titles = testIds.map((id) => {
80
+ const title = extractTitleFromTestId(id);
81
+ return escapeRegex(title);
82
+ });
83
+ return titles.join('\n');
84
+ }
85
+ /**
86
+ * Determine the best grep strategy for a list of tests
87
+ *
88
+ * @param testIds - List of test IDs
89
+ * @returns Object with strategy ('pattern' or 'file') and content
90
+ */
91
+ export function determineGrepStrategy(testIds) {
92
+ if (testIds.length === 0) {
93
+ return { strategy: 'pattern', content: '' };
94
+ }
95
+ const pattern = generateGrepPattern(testIds);
96
+ if (isPatternTooLong(pattern)) {
97
+ return {
98
+ strategy: 'file',
99
+ content: generateGrepFileContent(testIds),
100
+ };
101
+ }
102
+ return { strategy: 'pattern', content: pattern };
103
+ }
104
+ //# sourceMappingURL=grep-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep-pattern.js","sourceRoot":"","sources":["../../src/core/grep-pattern.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAElD;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC;AACnD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAiB;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,sBAAsB,CAAC,EAAE,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,6CAA6C;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAoC;IAEpC,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAE5C,KAAK,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/D,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,OAAO,CAAC,MAAM,GAAG,uBAAuB,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiB;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,sBAAsB,CAAC,EAAE,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAiB;IAIrD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE7C,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC;SAC1C,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,9 @@
1
+ export * from './ckk-algorithm.js';
2
+ export * from './estimate.js';
3
+ export * from './grep-pattern.js';
4
+ export * from './lpt-algorithm.js';
5
+ export * from './slugify.js';
6
+ export * from './test-discovery.js';
7
+ export * from './timing-store.js';
8
+ export * from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ export * from './ckk-algorithm.js';
2
+ export * from './estimate.js';
3
+ export * from './grep-pattern.js';
4
+ export * from './lpt-algorithm.js';
5
+ export * from './slugify.js';
6
+ export * from './test-discovery.js';
7
+ export * from './timing-store.js';
8
+ export * from './types.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { AssignResult, FileWithDuration, ShardAssignment } from './types.js';
2
+ /**
3
+ * Longest Processing Time First (LPT) algorithm for load balancing
4
+ *
5
+ * This greedy algorithm assigns jobs to workers by:
6
+ * 1. Sorting jobs by duration (descending)
7
+ * 2. Assigning each job to the worker with the smallest current load
8
+ *
9
+ * Time complexity: O(n log n) for sorting + O(n log k) for assignment = O(n log n)
10
+ * where n = number of files, k = number of shards
11
+ *
12
+ * @param files - Files with their durations
13
+ * @param numShards - Number of shards to distribute across
14
+ * @returns Shard assignments with expected durations
15
+ */
16
+ export declare function assignWithLPT(files: FileWithDuration[], numShards: number): ShardAssignment[];
17
+ /**
18
+ * Convert shard assignments to the format expected by the CLI output
19
+ */
20
+ export declare function formatAssignResult(assignments: ShardAssignment[], estimatedFiles: string[]): AssignResult;
21
+ /**
22
+ * Calculate the balance metric (max/min ratio) for the assignment
23
+ *
24
+ * A perfectly balanced assignment would have a ratio of 1.0
25
+ * The target is to keep this below 1.2 (20% difference)
26
+ */
27
+ export declare function calculateBalanceRatio(assignments: ShardAssignment[]): number;
28
+ //# sourceMappingURL=lpt-algorithm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lpt-algorithm.d.ts","sourceRoot":"","sources":["../../src/core/lpt-algorithm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,GAChB,eAAe,EAAE,CAoCnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,eAAe,EAAE,EAC9B,cAAc,EAAE,MAAM,EAAE,GACvB,YAAY,CAiBd;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,eAAe,EAAE,GAAG,MAAM,CAa5E"}