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 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', // 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
- health_check: { // Health check configuration
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
@@ -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
 
@@ -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
- this.logger.verbose(`Clearing log file at ${LOG_FILE}`);
35
- fs.writeFileSync(LOG_FILE, ''); // Clear the log file
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.0.0",
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
- command: 'build',
4
- description: 'Build the project',
5
- status: 'enabled',
6
- attempts: 1,
7
- },
8
- {
9
- command: 'test-ci',
10
- description: 'Run unit tests',
11
- status: 'enabled',
12
- attempts: 2,
13
- should_retry: (output) => {
14
- // Check for actual test failures in the summary
15
- const testSummaryMatch = output.match(/Test Suites:.*?(\d+) failed/);
16
- const hasTestFailures =
17
- testSummaryMatch && parseInt(testSummaryMatch[1]) > 0;
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
- // Check for coverage failures
20
- const coverageSummaryMatch = output.match(
21
- /Jest: "global" coverage threshold/,
22
- );
23
- const hasCoverageFailures = coverageSummaryMatch !== null;
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
- if (!hasTestFailures && hasCoverageFailures) {
26
- console.log(
27
- 'Tests have passed but coverage thresholds have not been met',
28
- );
29
- return false; // Don't retry if only coverage failed
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
- return hasTestFailures; // Only retry if there are actual test failures
91
+ return hasTestFailures; // Only retry if there are actual test failures
92
+ },
93
+ },
94
+ ],
33
95
  },
34
- },
35
- {
36
- command: 'test-storybook',
37
- description: 'Run Storybook tests',
38
- status: 'enabled',
39
- attempts: 2,
40
- dependencies: [
41
- {
42
- command: 'storybook_silent',
43
- background: true,
44
- wait: 5000,
45
- kill_script: 'kill_storybook',
46
- dependencies: [],
47
- // Add process tracking
48
- process_tracking: true,
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
+ };