@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,196 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { Command, Flags } from '@oclif/core';
4
+ import { buildTestId } from '../core/index.js';
5
+ export default class ExtractTiming extends Command {
6
+ static description = 'Extract timing data from Playwright JSON report (file-level or test-level)';
7
+ static examples = [
8
+ '<%= config.bin %> extract-timing --report-file ./playwright-report/results.json --output-file ./timing.json',
9
+ '<%= config.bin %> extract-timing --report-file ./results.json --shard 1 --project "Mobile Chrome" --level test',
10
+ ];
11
+ static flags = {
12
+ 'report-file': Flags.string({
13
+ char: 'r',
14
+ description: 'Path to Playwright JSON report file',
15
+ required: true,
16
+ }),
17
+ 'output-file': Flags.string({
18
+ char: 'o',
19
+ description: 'Path to write timing JSON output',
20
+ }),
21
+ shard: Flags.integer({
22
+ char: 's',
23
+ description: 'Shard index for the artifact',
24
+ default: 1,
25
+ }),
26
+ project: Flags.string({
27
+ char: 'p',
28
+ description: 'Playwright project name',
29
+ default: 'default',
30
+ }),
31
+ level: Flags.string({
32
+ char: 'l',
33
+ description: 'Extraction level: file or test',
34
+ default: 'test',
35
+ options: ['file', 'test'],
36
+ }),
37
+ verbose: Flags.boolean({
38
+ char: 'v',
39
+ description: 'Show verbose output',
40
+ default: false,
41
+ }),
42
+ };
43
+ async run() {
44
+ const { flags } = await this.parse(ExtractTiming);
45
+ // Read Playwright report
46
+ const reportPath = path.resolve(flags['report-file']);
47
+ let report;
48
+ try {
49
+ const content = fs.readFileSync(reportPath, 'utf-8');
50
+ report = JSON.parse(content);
51
+ }
52
+ catch {
53
+ this.error(`Failed to read Playwright report: ${reportPath}`);
54
+ }
55
+ let output;
56
+ if (flags.level === 'test') {
57
+ // Extract test-level durations
58
+ const testDurations = this.extractTestDurations(report);
59
+ if (flags.verbose) {
60
+ this.log(`Extracted timing for ${Object.keys(testDurations).length} tests`);
61
+ }
62
+ // Create test-level artifact
63
+ const artifact = {
64
+ shard: flags.shard,
65
+ project: flags.project,
66
+ tests: testDurations,
67
+ };
68
+ output = JSON.stringify(artifact, null, 2);
69
+ }
70
+ else {
71
+ // Extract file-level durations (legacy)
72
+ const fileDurations = this.extractFileDurations(report);
73
+ if (flags.verbose) {
74
+ this.log(`Extracted timing for ${Object.keys(fileDurations).length} files`);
75
+ }
76
+ // Create file-level artifact
77
+ const artifact = {
78
+ shard: flags.shard,
79
+ project: flags.project,
80
+ files: fileDurations,
81
+ };
82
+ output = JSON.stringify(artifact, null, 2);
83
+ }
84
+ // Output
85
+ if (flags['output-file']) {
86
+ fs.writeFileSync(flags['output-file'], output, 'utf-8');
87
+ if (flags.verbose) {
88
+ this.log(`Wrote timing data to ${flags['output-file']}`);
89
+ }
90
+ }
91
+ else {
92
+ this.log(output);
93
+ }
94
+ }
95
+ /**
96
+ * Extract test-level durations from Playwright report
97
+ *
98
+ * Each test is identified by: file::describe::testTitle
99
+ */
100
+ extractTestDurations(report) {
101
+ const testDurations = {};
102
+ for (const suite of report.suites) {
103
+ this.extractTestsFromSuite(suite, [], testDurations);
104
+ }
105
+ return testDurations;
106
+ }
107
+ /**
108
+ * Recursively extract test durations from a suite
109
+ */
110
+ extractTestsFromSuite(suite, parentTitles, testDurations) {
111
+ const file = this.normalizeFilePath(suite.file);
112
+ const currentTitles = suite.title && suite.title !== ''
113
+ ? [...parentTitles, suite.title]
114
+ : parentTitles;
115
+ // Process specs (actual tests)
116
+ if (suite.specs) {
117
+ for (const spec of suite.specs) {
118
+ const titlePath = [...currentTitles, spec.title];
119
+ const testId = buildTestId(file, titlePath);
120
+ // Sum all result durations (including retries)
121
+ let totalDuration = 0;
122
+ for (const test of spec.tests) {
123
+ for (const result of test.results) {
124
+ totalDuration += result.duration;
125
+ }
126
+ }
127
+ testDurations[testId] = totalDuration;
128
+ }
129
+ }
130
+ // Process nested suites
131
+ if (suite.suites) {
132
+ for (const nestedSuite of suite.suites) {
133
+ // Pass the file from parent if nested suite doesn't have one
134
+ const nestedWithFile = {
135
+ ...nestedSuite,
136
+ file: nestedSuite.file || suite.file,
137
+ };
138
+ this.extractTestsFromSuite(nestedWithFile, currentTitles, testDurations);
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Extract file durations from Playwright report (legacy file-level)
144
+ *
145
+ * The report structure has nested suites where the top-level suite
146
+ * represents the file. We sum up all test durations within each file.
147
+ */
148
+ extractFileDurations(report) {
149
+ const fileDurations = {};
150
+ for (const suite of report.suites) {
151
+ const file = this.normalizeFilePath(suite.file);
152
+ const duration = this.calculateSuiteDuration(suite);
153
+ fileDurations[file] = duration;
154
+ }
155
+ return fileDurations;
156
+ }
157
+ /**
158
+ * Normalize file path to be relative and consistent
159
+ */
160
+ normalizeFilePath(filePath) {
161
+ // Extract just the filename or relative path
162
+ // Remove any absolute path prefix
163
+ const parts = filePath.split('/');
164
+ // Find the index of common test directories
165
+ const testDirIndex = parts.findIndex((p) => p === 'e2e' || p === 'test' || p === '__tests__');
166
+ if (testDirIndex !== -1) {
167
+ return parts.slice(testDirIndex + 1).join('/');
168
+ }
169
+ // Just return the filename
170
+ return parts[parts.length - 1] ?? filePath;
171
+ }
172
+ /**
173
+ * Calculate total duration for a suite (recursively including nested suites)
174
+ */
175
+ calculateSuiteDuration(suite) {
176
+ let total = 0;
177
+ // Sum durations from specs
178
+ if (suite.specs) {
179
+ for (const spec of suite.specs) {
180
+ for (const test of spec.tests) {
181
+ for (const result of test.results) {
182
+ total += result.duration;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ // Sum durations from nested suites
188
+ if (suite.suites) {
189
+ for (const nestedSuite of suite.suites) {
190
+ total += this.calculateSuiteDuration(nestedSuite);
191
+ }
192
+ }
193
+ return total;
194
+ }
195
+ }
196
+ //# sourceMappingURL=extract-timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-timing.js","sourceRoot":"","sources":["../../src/commands/extract-timing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ListTests extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'test-dir': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'output-format': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'glob-pattern': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'use-fallback': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private outputResult;
15
+ }
16
+ //# sourceMappingURL=list-tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-tests.d.ts","sourceRoot":"","sources":["../../src/commands/list-tests.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAQ7C,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,OAAO;IAC5C,OAAgB,WAAW,SACiD;IAE5E,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;;;;MA6BnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC1B,OAAO,CAAC,YAAY;CA4BrB"}
@@ -0,0 +1,98 @@
1
+ import * as path from 'node:path';
2
+ import { Command, Flags } from '@oclif/core';
3
+ import { discoverTests, discoverTestsFromFiles, groupTestsByFile, } from '../core/index.js';
4
+ export default class ListTests extends Command {
5
+ static description = 'Discover all tests in a Playwright project using --list or file parsing';
6
+ static examples = [
7
+ '<%= config.bin %> list-tests --test-dir ./src/test/e2e',
8
+ '<%= config.bin %> list-tests --test-dir ./e2e --project "Mobile Chrome" --output-format json',
9
+ ];
10
+ static flags = {
11
+ 'test-dir': Flags.string({
12
+ char: 'd',
13
+ description: 'Path to test directory',
14
+ required: true,
15
+ }),
16
+ project: Flags.string({
17
+ char: 'p',
18
+ description: 'Playwright project name',
19
+ }),
20
+ 'output-format': Flags.string({
21
+ char: 'f',
22
+ description: 'Output format',
23
+ default: 'json',
24
+ options: ['json', 'text'],
25
+ }),
26
+ 'glob-pattern': Flags.string({
27
+ description: 'Glob pattern for test files (used for fallback discovery)',
28
+ default: '**/*.spec.ts',
29
+ }),
30
+ 'use-fallback': Flags.boolean({
31
+ description: 'Use file parsing instead of Playwright --list',
32
+ default: false,
33
+ }),
34
+ verbose: Flags.boolean({
35
+ char: 'v',
36
+ description: 'Show verbose output',
37
+ default: false,
38
+ }),
39
+ };
40
+ async run() {
41
+ const { flags } = await this.parse(ListTests);
42
+ const testDir = path.resolve(flags['test-dir']);
43
+ if (flags.verbose) {
44
+ this.log(`Discovering tests in ${testDir}...`);
45
+ }
46
+ let tests;
47
+ try {
48
+ if (flags['use-fallback']) {
49
+ // Use file parsing
50
+ tests = discoverTestsFromFiles(testDir, flags['glob-pattern']);
51
+ if (flags.verbose) {
52
+ this.log('Using file parsing for test discovery');
53
+ }
54
+ }
55
+ else {
56
+ // Use Playwright --list
57
+ tests = discoverTests(testDir, flags.project);
58
+ if (flags.verbose) {
59
+ this.log('Using Playwright --list for test discovery');
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ // Fallback to file parsing if Playwright --list fails
65
+ if (flags.verbose) {
66
+ this.warn('Playwright --list failed, falling back to file parsing');
67
+ }
68
+ tests = discoverTestsFromFiles(testDir, flags['glob-pattern']);
69
+ }
70
+ if (flags.verbose) {
71
+ this.log(`Discovered ${tests.length} tests`);
72
+ }
73
+ this.outputResult(tests, flags['output-format'], flags.verbose);
74
+ }
75
+ outputResult(tests, format, verbose = false) {
76
+ if (format === 'json') {
77
+ this.log(JSON.stringify(tests));
78
+ }
79
+ else {
80
+ // Text format - group by file
81
+ const grouped = groupTestsByFile(tests);
82
+ this.log('\n=== Discovered Tests ===\n');
83
+ for (const [file, fileTests] of grouped) {
84
+ this.log(`${file}:`);
85
+ for (const test of fileTests) {
86
+ const titlePath = test.titlePath.join(' > ');
87
+ this.log(` - ${titlePath}`);
88
+ if (verbose) {
89
+ this.log(` ID: ${test.testId}`);
90
+ }
91
+ }
92
+ this.log('');
93
+ }
94
+ this.log(`Total: ${tests.length} tests in ${grouped.size} files`);
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=list-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-tests.js","sourceRoot":"","sources":["../../src/commands/list-tests.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class MergeTiming extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ existing: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ new: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
8
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ alpha: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'prune-days': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'current-files': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ level: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ private runTestLevel;
17
+ private runFileLevel;
18
+ }
19
+ //# sourceMappingURL=merge-timing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-timing.d.ts","sourceRoot":"","sources":["../../src/commands/merge-timing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAgB7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,OAAgB,WAAW,SACiD;IAE5E,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;;;;;;MAwCnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAeZ,YAAY;YAkHZ,YAAY;CA4G3B"}
@@ -0,0 +1,217 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { Command, Flags } from '@oclif/core';
4
+ import { DEFAULT_EMA_ALPHA, DEFAULT_PRUNE_DAYS, isTimingDataV2, loadTimingData, mergeTestTimingData, mergeTimingData, pruneTestTimingData, pruneTimingData, saveTimingData, } from '../core/index.js';
5
+ export default class MergeTiming extends Command {
6
+ static description = 'Merge timing data from multiple shards using Exponential Moving Average';
7
+ static examples = [
8
+ '<%= config.bin %> merge-timing --existing ./timing.json --new ./shard-1.json ./shard-2.json --output ./timing.json',
9
+ '<%= config.bin %> merge-timing --new ./shard-*.json --output ./timing.json --alpha 0.3 --prune-days 30',
10
+ '<%= config.bin %> merge-timing --new ./shard-*.json --output ./timing.json --level test',
11
+ ];
12
+ static flags = {
13
+ existing: Flags.string({
14
+ char: 'e',
15
+ description: 'Path to existing timing data JSON (optional)',
16
+ }),
17
+ new: Flags.string({
18
+ char: 'n',
19
+ description: 'Paths to new timing artifact files',
20
+ multiple: true,
21
+ required: true,
22
+ }),
23
+ output: Flags.string({
24
+ char: 'o',
25
+ description: 'Path to write merged timing data',
26
+ required: true,
27
+ }),
28
+ alpha: Flags.string({
29
+ char: 'a',
30
+ description: 'EMA smoothing factor (0-1)',
31
+ default: String(DEFAULT_EMA_ALPHA),
32
+ }),
33
+ 'prune-days': Flags.integer({
34
+ description: 'Remove entries older than N days',
35
+ default: DEFAULT_PRUNE_DAYS,
36
+ }),
37
+ 'current-files': Flags.string({
38
+ description: 'Path to file listing current test files/IDs (for pruning deleted tests)',
39
+ }),
40
+ level: Flags.string({
41
+ char: 'l',
42
+ description: 'Data level: file or test',
43
+ default: 'test',
44
+ options: ['file', 'test'],
45
+ }),
46
+ verbose: Flags.boolean({
47
+ char: 'v',
48
+ description: 'Show verbose output',
49
+ default: false,
50
+ }),
51
+ };
52
+ async run() {
53
+ const { flags } = await this.parse(MergeTiming);
54
+ const alpha = Number.parseFloat(flags.alpha);
55
+ if (Number.isNaN(alpha) || alpha < 0 || alpha > 1) {
56
+ this.error('Alpha must be a number between 0 and 1');
57
+ }
58
+ if (flags.level === 'test') {
59
+ await this.runTestLevel(flags, alpha);
60
+ }
61
+ else {
62
+ await this.runFileLevel(flags, alpha);
63
+ }
64
+ }
65
+ async runTestLevel(flags, alpha) {
66
+ // Load existing timing data
67
+ let existingData = null;
68
+ if (flags.existing) {
69
+ const loaded = loadTimingData(flags.existing);
70
+ if (isTimingDataV2(loaded)) {
71
+ existingData = loaded;
72
+ }
73
+ else if (flags.verbose) {
74
+ this.warn('Existing timing data is v1 (file-level), starting fresh for test-level');
75
+ }
76
+ }
77
+ if (flags.verbose) {
78
+ const testCount = existingData
79
+ ? Object.keys(existingData.tests).length
80
+ : 0;
81
+ this.log(`Loaded existing timing data with ${testCount} tests`);
82
+ }
83
+ // Load new timing artifacts
84
+ const newArtifacts = [];
85
+ for (const artifactPath of flags.new) {
86
+ try {
87
+ const content = fs.readFileSync(path.resolve(artifactPath), 'utf-8');
88
+ const artifact = JSON.parse(content);
89
+ // Check if it has tests (v2) or files (v1)
90
+ if (artifact.tests) {
91
+ newArtifacts.push(artifact);
92
+ if (flags.verbose) {
93
+ this.log(`Loaded timing artifact from shard ${artifact.shard} with ${Object.keys(artifact.tests).length} tests`);
94
+ }
95
+ }
96
+ else if (flags.verbose) {
97
+ this.warn(`Artifact ${artifactPath} is file-level, skipping for test-level merge`);
98
+ }
99
+ }
100
+ catch {
101
+ this.warn(`Failed to load timing artifact: ${artifactPath}`);
102
+ }
103
+ }
104
+ if (newArtifacts.length === 0) {
105
+ this.warn('No valid test-level timing artifacts found');
106
+ return;
107
+ }
108
+ // Merge timing data using EMA
109
+ let mergedData = mergeTestTimingData(existingData, newArtifacts, alpha);
110
+ if (flags.verbose) {
111
+ this.log(`Merged timing data now has ${Object.keys(mergedData.tests).length} tests`);
112
+ }
113
+ // Load current test IDs for pruning if provided
114
+ let currentTestIds;
115
+ if (flags['current-files']) {
116
+ try {
117
+ const content = fs.readFileSync(flags['current-files'], 'utf-8');
118
+ currentTestIds = content.split('\n').filter((line) => line.trim());
119
+ }
120
+ catch {
121
+ this.warn(`Failed to load current test IDs list: ${flags['current-files']}`);
122
+ }
123
+ }
124
+ // Prune old entries
125
+ const beforePrune = Object.keys(mergedData.tests).length;
126
+ mergedData = pruneTestTimingData(mergedData, flags['prune-days'], currentTestIds);
127
+ const afterPrune = Object.keys(mergedData.tests).length;
128
+ if (flags.verbose && beforePrune !== afterPrune) {
129
+ this.log(`Pruned ${beforePrune - afterPrune} old entries`);
130
+ }
131
+ // Save merged data
132
+ saveTimingData(flags.output, mergedData);
133
+ if (flags.verbose) {
134
+ this.log(`Saved merged timing data to ${flags.output}`);
135
+ }
136
+ // Output summary
137
+ this.log(JSON.stringify({
138
+ tests: Object.keys(mergedData.tests).length,
139
+ updatedAt: mergedData.updatedAt,
140
+ version: mergedData.version,
141
+ }));
142
+ }
143
+ async runFileLevel(flags, alpha) {
144
+ // Load existing timing data
145
+ const existingData = flags.existing
146
+ ? loadTimingData(flags.existing)
147
+ : { version: 1, updatedAt: new Date().toISOString(), files: {} };
148
+ if (flags.verbose) {
149
+ const fileCount = 'files' in existingData ? Object.keys(existingData.files).length : 0;
150
+ this.log(`Loaded existing timing data with ${fileCount} files`);
151
+ }
152
+ // Load new timing artifacts
153
+ const newArtifacts = [];
154
+ for (const artifactPath of flags.new) {
155
+ try {
156
+ const content = fs.readFileSync(path.resolve(artifactPath), 'utf-8');
157
+ const artifact = JSON.parse(content);
158
+ // Check if it has files (v1)
159
+ if (artifact.files) {
160
+ newArtifacts.push(artifact);
161
+ if (flags.verbose) {
162
+ this.log(`Loaded timing artifact from shard ${artifact.shard} with ${Object.keys(artifact.files).length} files`);
163
+ }
164
+ }
165
+ else if (flags.verbose) {
166
+ this.warn(`Artifact ${artifactPath} is test-level, skipping for file-level merge`);
167
+ }
168
+ }
169
+ catch {
170
+ this.warn(`Failed to load timing artifact: ${artifactPath}`);
171
+ }
172
+ }
173
+ if (newArtifacts.length === 0) {
174
+ this.warn('No valid file-level timing artifacts found');
175
+ return;
176
+ }
177
+ // Merge timing data using EMA
178
+ let mergedData = mergeTimingData(existingData, newArtifacts, alpha);
179
+ if (flags.verbose) {
180
+ this.log(`Merged timing data now has ${Object.keys(mergedData.files).length} files`);
181
+ }
182
+ // Load current files for pruning if provided
183
+ let currentFiles;
184
+ if (flags['current-files']) {
185
+ try {
186
+ const content = fs.readFileSync(flags['current-files'], 'utf-8');
187
+ currentFiles = content.split('\n').filter((line) => line.trim());
188
+ }
189
+ catch {
190
+ this.warn(`Failed to load current files list: ${flags['current-files']}`);
191
+ }
192
+ }
193
+ // Prune old entries
194
+ const beforePrune = Object.keys(mergedData.files).length;
195
+ const prunedData = pruneTimingData(mergedData, flags['prune-days'], currentFiles);
196
+ // pruneTimingData can return v1 or v2, but for file-level we expect v1
197
+ if ('files' in prunedData) {
198
+ mergedData = prunedData;
199
+ }
200
+ const afterPrune = Object.keys(mergedData.files).length;
201
+ if (flags.verbose && beforePrune !== afterPrune) {
202
+ this.log(`Pruned ${beforePrune - afterPrune} old entries`);
203
+ }
204
+ // Save merged data
205
+ saveTimingData(flags.output, mergedData);
206
+ if (flags.verbose) {
207
+ this.log(`Saved merged timing data to ${flags.output}`);
208
+ }
209
+ // Output summary
210
+ this.log(JSON.stringify({
211
+ files: Object.keys(mergedData.files).length,
212
+ updatedAt: mergedData.updatedAt,
213
+ version: mergedData.version,
214
+ }));
215
+ }
216
+ }
217
+ //# sourceMappingURL=merge-timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-timing.js","sourceRoot":"","sources":["../../src/commands/merge-timing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { TestShardAssignment, TestWithDuration } from './types.js';
2
+ /**
3
+ * Default timeout for CKK algorithm in milliseconds
4
+ */
5
+ export declare const DEFAULT_CKK_TIMEOUT = 500;
6
+ /**
7
+ * Result from the CKK algorithm
8
+ */
9
+ export interface CKKResult {
10
+ /** Shard assignments */
11
+ assignments: TestShardAssignment[];
12
+ /** Maximum shard duration (makespan) */
13
+ makespan: number;
14
+ /** Whether the solution is provably optimal */
15
+ isOptimal: boolean;
16
+ }
17
+ /**
18
+ * Complete Karmarkar-Karp (CKK) inspired algorithm for multi-way number partitioning
19
+ *
20
+ * This algorithm finds the optimal distribution of tests across shards by:
21
+ * 1. Using branch and bound search with aggressive pruning
22
+ * 2. Starting with LPT solution as upper bound
23
+ * 3. Exploring assignment tree, pruning when partial makespan exceeds best
24
+ *
25
+ * Falls back to LPT if search exceeds timeout.
26
+ *
27
+ * @param tests - Tests with their durations
28
+ * @param numShards - Number of shards to distribute across
29
+ * @param timeoutMs - Maximum time to search for optimal solution
30
+ * @returns Optimal (or near-optimal) shard assignments
31
+ */
32
+ export declare function assignWithCKK(tests: TestWithDuration[], numShards: number, timeoutMs?: number): CKKResult;
33
+ /**
34
+ * Calculate theoretical lower bound for makespan
35
+ * This is the best possible makespan if we could partition perfectly
36
+ */
37
+ export declare function calculateLowerBound(tests: TestWithDuration[], numShards: number): number;
38
+ //# sourceMappingURL=ckk-algorithm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ckk-algorithm.d.ts","sourceRoot":"","sources":["../../src/core/ckk-algorithm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAA4B,GACtC,SAAS,CA8HX;AA4ED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR"}