scripts-orchestrator 2.0.0 → 2.1.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.
- package/README.md +8 -7
- package/lib/orchestrator.js +8 -4
- package/lib/process-manager.js +42 -7
- package/package.json +1 -1
- package/scripts-orchestrator.config.js +110 -78
package/README.md
CHANGED
|
@@ -39,13 +39,14 @@ Create a configuration file (default: `scripts-orchestrator.config.js`) that def
|
|
|
39
39
|
|
|
40
40
|
```javascript
|
|
41
41
|
{
|
|
42
|
-
command: 'command_name',
|
|
43
|
-
description: 'Description',
|
|
44
|
-
status: 'enabled',
|
|
45
|
-
attempts: 1,
|
|
46
|
-
dependencies: [],
|
|
47
|
-
background: false,
|
|
48
|
-
|
|
42
|
+
command: 'command_name', // The npm script to run
|
|
43
|
+
description: 'Description', // Optional description
|
|
44
|
+
status: 'enabled', // 'enabled' or 'disabled'
|
|
45
|
+
attempts: 1, // Number of retry attempts
|
|
46
|
+
dependencies: [], // Array of dependent commands
|
|
47
|
+
background: false, // Whether to run in background
|
|
48
|
+
kill_command: 'kill_storybook', // Optional kill command to kill the process
|
|
49
|
+
health_check: { // Health check configuration
|
|
49
50
|
url: 'http://localhost:port',
|
|
50
51
|
max_attempts: 20,
|
|
51
52
|
interval: 2000
|
package/lib/orchestrator.js
CHANGED
|
@@ -52,6 +52,7 @@ export class Orchestrator {
|
|
|
52
52
|
should_retry,
|
|
53
53
|
process_tracking = false,
|
|
54
54
|
health_check,
|
|
55
|
+
kill_command,
|
|
55
56
|
} = commandConfig;
|
|
56
57
|
|
|
57
58
|
const startTime = Date.now();
|
|
@@ -87,6 +88,7 @@ export class Orchestrator {
|
|
|
87
88
|
url: checkUrl,
|
|
88
89
|
startedByScript: false,
|
|
89
90
|
process_tracking,
|
|
91
|
+
kill_command,
|
|
90
92
|
});
|
|
91
93
|
this.commandTimings.set(command, Date.now() - startTime);
|
|
92
94
|
visited.delete(command);
|
|
@@ -144,12 +146,14 @@ export class Orchestrator {
|
|
|
144
146
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
const { success, output } = await this.processManager.runCommand(
|
|
148
|
-
attempt === 1 ? command : retry_command || command,
|
|
149
|
+
const { success, output } = await this.processManager.runCommand({
|
|
150
|
+
cmd: attempt === 1 ? command : retry_command || command,
|
|
149
151
|
logFile,
|
|
150
152
|
background,
|
|
151
|
-
health_check,
|
|
152
|
-
|
|
153
|
+
healthCheck: health_check,
|
|
154
|
+
kill_command,
|
|
155
|
+
isRetry: attempt > 1,
|
|
156
|
+
});
|
|
153
157
|
commandOutput = output;
|
|
154
158
|
result = success;
|
|
155
159
|
|
package/lib/process-manager.js
CHANGED
|
@@ -11,17 +11,18 @@ export class ProcessManager {
|
|
|
11
11
|
this.backgroundProcessesDetails = [];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
addBackgroundProcess({ command, url, startedByScript, process_tracking }) {
|
|
14
|
+
addBackgroundProcess({ command, url, startedByScript, process_tracking, kill_command }) {
|
|
15
15
|
this.logger.verbose(`Adding background process: ${command} (${url})`);
|
|
16
16
|
this.backgroundProcessesDetails.push({
|
|
17
17
|
command,
|
|
18
18
|
url,
|
|
19
19
|
startedByScript,
|
|
20
20
|
process_tracking,
|
|
21
|
+
kill_command,
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async runCommand(cmd, logFile, background = false, healthCheck = null) {
|
|
25
|
+
async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false }) {
|
|
25
26
|
const LOGS_DIR = path.resolve(process.cwd(), 'scripts-orchestrator-logs');
|
|
26
27
|
const LOG_FILE = logFile || path.join(LOGS_DIR, `${cmd}.log`);
|
|
27
28
|
|
|
@@ -31,8 +32,12 @@ export class ProcessManager {
|
|
|
31
32
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (!isRetry) {
|
|
36
|
+
this.logger.verbose(`Clearing log file at ${LOG_FILE}`);
|
|
37
|
+
fs.writeFileSync(LOG_FILE, ''); // Clear the log file
|
|
38
|
+
} else {
|
|
39
|
+
this.logger.verbose(`Appending to existing log file at ${LOG_FILE} (retry attempt)`);
|
|
40
|
+
}
|
|
36
41
|
} catch (error) {
|
|
37
42
|
this.logger.error(`Failed to setup log file: ${error.message}`);
|
|
38
43
|
return Promise.resolve({ success: false, output: '' });
|
|
@@ -42,7 +47,7 @@ export class ProcessManager {
|
|
|
42
47
|
this.logger.info(`Running: npm run ${cmd}`);
|
|
43
48
|
|
|
44
49
|
// Create isolated environment for each process
|
|
45
|
-
const isolatedEnv = this.createIsolatedEnvironment(cmd);
|
|
50
|
+
const isolatedEnv = this.createIsolatedEnvironment({ command: cmd });
|
|
46
51
|
|
|
47
52
|
const options = {
|
|
48
53
|
shell: true,
|
|
@@ -103,6 +108,7 @@ export class ProcessManager {
|
|
|
103
108
|
startTime: Date.now(),
|
|
104
109
|
url: healthCheck?.url,
|
|
105
110
|
startedByScript: true,
|
|
111
|
+
kill_command,
|
|
106
112
|
});
|
|
107
113
|
|
|
108
114
|
this.logger.verbose(`Unreferencing process ${processGroupId}`);
|
|
@@ -172,7 +178,7 @@ export class ProcessManager {
|
|
|
172
178
|
});
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
createIsolatedEnvironment(command) {
|
|
181
|
+
createIsolatedEnvironment({ command }) {
|
|
176
182
|
// Create a deep copy to avoid any reference sharing
|
|
177
183
|
const baseEnv = JSON.parse(JSON.stringify(process.env));
|
|
178
184
|
|
|
@@ -201,8 +207,17 @@ export class ProcessManager {
|
|
|
201
207
|
|
|
202
208
|
async cleanup() {
|
|
203
209
|
this.logger.info('\nCleaning up background processes...');
|
|
210
|
+
|
|
211
|
+
// Debug: Log the number of processes we're tracking
|
|
212
|
+
this.logger.info(`- Found ${this.backgroundProcessesDetails.length} background processes to clean up`);
|
|
213
|
+
|
|
214
|
+
// Debug: Log each process details
|
|
215
|
+
this.backgroundProcessesDetails.forEach(({ command, pgid, url, startedByScript, kill_command }, index) => {
|
|
216
|
+
this.logger.verbose(`- Process ${index + 1}: command=${command}, pgid=${pgid}, url=${url}, startedByScript=${startedByScript}, kill_command=${kill_command}`);
|
|
217
|
+
});
|
|
218
|
+
|
|
204
219
|
const killPromises = this.backgroundProcessesDetails.map(
|
|
205
|
-
async ({ command, pgid, url, startedByScript }) => {
|
|
220
|
+
async ({ command, pgid, url, startedByScript, kill_command }) => {
|
|
206
221
|
if (!startedByScript) {
|
|
207
222
|
this.logger.verbose(
|
|
208
223
|
`- Skipping cleanup for ${command} (${url}) as it was not started by this script`,
|
|
@@ -210,6 +225,26 @@ export class ProcessManager {
|
|
|
210
225
|
return;
|
|
211
226
|
}
|
|
212
227
|
|
|
228
|
+
this.logger.verbose(`- Processing cleanup for ${command} (kill_command: ${kill_command})`);
|
|
229
|
+
|
|
230
|
+
// Try custom kill command first if specified
|
|
231
|
+
if (kill_command) {
|
|
232
|
+
try {
|
|
233
|
+
this.logger.verbose(`- Using custom kill command: npm run ${kill_command}`);
|
|
234
|
+
const result = await this.runCommand({ cmd: kill_command, logFile: null, background: false });
|
|
235
|
+
if (result.success) {
|
|
236
|
+
this.logger.verbose(`- Successfully killed ${command} using custom command`);
|
|
237
|
+
return;
|
|
238
|
+
} else {
|
|
239
|
+
this.logger.verbose('- Custom kill command failed, falling back to process signals');
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.verbose(`- Custom kill command error: ${error.message}, falling back`);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
this.logger.verbose(`- No kill_command specified for ${command}, using process signals`);
|
|
246
|
+
}
|
|
247
|
+
|
|
213
248
|
try {
|
|
214
249
|
// First try to kill the process group
|
|
215
250
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A powerful script orchestrator for running parallel commands with dependency management, background processes, and health checks",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -1,83 +1,115 @@
|
|
|
1
|
-
export default
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
export default {
|
|
2
|
+
phases: [
|
|
3
|
+
{
|
|
4
|
+
name: 'build',
|
|
5
|
+
parallel: [
|
|
6
|
+
{
|
|
7
|
+
command: 'build',
|
|
8
|
+
description: 'Build the project',
|
|
9
|
+
status: 'enabled',
|
|
10
|
+
attempts: 1,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
command: 'stylelint',
|
|
14
|
+
description: 'Run stylelint checks',
|
|
15
|
+
status: 'enabled',
|
|
16
|
+
},
|
|
17
|
+
{ command: 'lint', description: 'Run lint checks', status: 'enabled' },
|
|
18
|
+
{
|
|
19
|
+
command: 'jscpd',
|
|
20
|
+
description: 'Run code duplication checks',
|
|
21
|
+
status: 'enabled',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'storybook tests',
|
|
27
|
+
parallel: [
|
|
28
|
+
{
|
|
29
|
+
command: 'test-storybook',
|
|
30
|
+
description: 'Run Storybook tests',
|
|
31
|
+
status: 'enabled',
|
|
32
|
+
attempts: 2,
|
|
33
|
+
dependencies: [
|
|
34
|
+
{
|
|
35
|
+
command: 'storybook_silent',
|
|
36
|
+
background: true,
|
|
37
|
+
wait: 5000,
|
|
38
|
+
kill_command: 'kill_storybook',
|
|
39
|
+
dependencies: [],
|
|
40
|
+
// Add process tracking
|
|
41
|
+
process_tracking: true,
|
|
42
|
+
// Add health check
|
|
43
|
+
health_check: {
|
|
44
|
+
url: 'http://localhost:6006',
|
|
45
|
+
max_attempts: 20,
|
|
46
|
+
interval: 2000,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'unit tests',
|
|
55
|
+
parallel: [
|
|
56
|
+
{
|
|
57
|
+
command: 'test-ci',
|
|
58
|
+
description: 'Run unit tests',
|
|
59
|
+
status: 'enabled',
|
|
60
|
+
attempts: 2,
|
|
61
|
+
should_retry: (output) => {
|
|
62
|
+
// Check for test failures in both formats
|
|
63
|
+
const testSuiteFailureMatch = output.match(
|
|
64
|
+
/Test Suites:.*?\(\d+\) failed/,
|
|
65
|
+
);
|
|
66
|
+
const individualTestFailureMatch =
|
|
67
|
+
output.match(/✘\s*(\d+)\s*failing/);
|
|
68
|
+
|
|
69
|
+
const hasTestSuiteFailures =
|
|
70
|
+
testSuiteFailureMatch && parseInt(testSuiteFailureMatch[1]) > 0;
|
|
71
|
+
const hasIndividualTestFailures =
|
|
72
|
+
individualTestFailureMatch &&
|
|
73
|
+
parseInt(individualTestFailureMatch[1]) > 0;
|
|
74
|
+
|
|
75
|
+
const hasTestFailures =
|
|
76
|
+
hasTestSuiteFailures || hasIndividualTestFailures;
|
|
18
77
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
// Check for "Test suite failed to run" in logs
|
|
79
|
+
if (output.includes('Test suite failed to run')) {
|
|
80
|
+
console.error('Certain tests could not be run');
|
|
81
|
+
return false; // Don't retry if certain tests could not be run
|
|
82
|
+
}
|
|
24
83
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
84
|
+
if (!hasTestFailures) {
|
|
85
|
+
console.log(
|
|
86
|
+
'Tests have passed but coverage thresholds have not been met',
|
|
87
|
+
);
|
|
88
|
+
return false; // Don't retry if only coverage failed
|
|
89
|
+
}
|
|
31
90
|
|
|
32
|
-
|
|
91
|
+
return hasTestFailures; // Only retry if there are actual test failures
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
33
95
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Add health check
|
|
50
|
-
health_check: {
|
|
51
|
-
url: 'http://localhost:6006',
|
|
52
|
-
max_attempts: 20,
|
|
53
|
-
interval: 2000,
|
|
96
|
+
{
|
|
97
|
+
name: 'playwright',
|
|
98
|
+
parallel: [
|
|
99
|
+
{
|
|
100
|
+
command: 'playwright_ci',
|
|
101
|
+
description: 'Run Playwright tests',
|
|
102
|
+
status: 'enabled',
|
|
103
|
+
attempts: 1, //Playwright internally retries in CI mode
|
|
104
|
+
dependencies: [
|
|
105
|
+
{
|
|
106
|
+
command: 'dev',
|
|
107
|
+
background: true,
|
|
108
|
+
url: 'http://localhost:5173',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
54
111
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
command: 'stylelint',
|
|
60
|
-
description: 'Run stylelint checks',
|
|
61
|
-
status: 'enabled',
|
|
62
|
-
},
|
|
63
|
-
{ command: 'lint', description: 'Run lint checks', status: 'enabled' },
|
|
64
|
-
{
|
|
65
|
-
command: 'jscpd',
|
|
66
|
-
description: 'Run code duplication checks',
|
|
67
|
-
status: 'enabled',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
command: 'playwright_ci',
|
|
71
|
-
description: 'Run Playwright tests',
|
|
72
|
-
status: 'enabled',
|
|
73
|
-
attempts: 1, //Playwright internally retries in CI mode
|
|
74
|
-
dependencies: [
|
|
75
|
-
{
|
|
76
|
-
command: 'dev',
|
|
77
|
-
background: true,
|
|
78
|
-
url: 'http://localhost:5173',
|
|
79
|
-
kill: 'application_end',
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
];
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|