scripts-orchestrator 2.4.1 → 2.5.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
@@ -165,8 +165,41 @@ The orchestrator doesn't care what the commands do - it just ensures they run (i
165
165
 
166
166
  # Or specify a custom config file
167
167
  npm run scripts-orchestrator -- ./path/to/your/config.js
168
+
169
+ # Start from a specific phase
170
+ npm run scripts-orchestrator -- --phase "unit tests"
171
+
172
+ # Start from a specific phase with custom config
173
+ npm run scripts-orchestrator -- ./path/to/your/config.js --phase "playwright"
168
174
  ```
169
175
 
176
+ ### Starting from a Specific Phase
177
+
178
+ You can start the orchestrator from a specific phase instead of running all phases from the beginning. This is useful for debugging or when you want to skip earlier phases that have already been completed.
179
+
180
+ #### Method 1: Command Line Argument
181
+ ```bash
182
+ # Start from the "unit tests" phase
183
+ npm run scripts-orchestrator -- --phase "unit tests"
184
+ ```
185
+
186
+ #### Method 2: Configuration File
187
+ ```javascript
188
+ export default {
189
+ start_phase: "unit tests", // Start from this phase
190
+ phases: [
191
+ // ... your phases
192
+ ]
193
+ };
194
+ ```
195
+
196
+ **Note**: Command line arguments take precedence over configuration file settings.
197
+
198
+ When starting from a specific phase:
199
+ - All phases before the specified phase are skipped
200
+ - Commands in skipped phases are marked as "skipped" in the final summary
201
+ - The orchestrator validates that the specified phase exists and shows available phases if not found
202
+
170
203
  ## Error Handling
171
204
 
172
205
  - The script tracks failed and skipped commands
package/index.js CHANGED
@@ -10,13 +10,28 @@ import fs from 'fs';
10
10
  import { Orchestrator } from './lib/index.js';
11
11
  import { log } from './lib/logger.js';
12
12
 
13
- // Get config file path from command line arguments
14
- const configPath = process.argv[2] || './scripts-orchestrator.config.js';
13
+ // Parse command line arguments
14
+ const args = process.argv.slice(2);
15
+ let configPath = './scripts-orchestrator.config.js';
16
+ let startPhase = null;
17
+
18
+ // Parse arguments
19
+ for (let i = 0; i < args.length; i++) {
20
+ const arg = args[i];
21
+
22
+ if (arg === '--phase' && i + 1 < args.length) {
23
+ startPhase = args[i + 1];
24
+ i++; // Skip the next argument since we consumed it
25
+ } else if (!arg.startsWith('--') && !configPath) {
26
+ // First non-flag argument is the config path
27
+ configPath = arg;
28
+ }
29
+ }
15
30
 
16
31
  // Validate config file exists
17
32
  if (!fs.existsSync(configPath)) {
18
33
  log.error(`Error: Config file not found at ${configPath}`);
19
- log.error('Usage: scripts-orchestrator [path-to-config-file]');
34
+ log.error('Usage: scripts-orchestrator [path-to-config-file] [--phase <phase-name>]');
20
35
  process.exit(1);
21
36
  }
22
37
 
@@ -25,8 +40,13 @@ const configFilePath = path.resolve(process.cwd(), configPath);
25
40
  const fileUrl = new URL(`file://${configFilePath}`).href;
26
41
  const commandsConfig = (await import(fileUrl)).default;
27
42
 
43
+ // Check for start_phase in config if not provided via command line
44
+ if (!startPhase && commandsConfig.start_phase) {
45
+ startPhase = commandsConfig.start_phase;
46
+ }
47
+
28
48
  // Create and run the orchestrator
29
- const orchestrator = new Orchestrator(commandsConfig);
49
+ const orchestrator = new Orchestrator(commandsConfig, startPhase);
30
50
 
31
51
  // Enhanced signal handlers
32
52
  const handleSignal = async (signal) => {
@@ -3,8 +3,9 @@ import { healthCheck } from './health-check.js';
3
3
  import { log } from './logger.js';
4
4
 
5
5
  export class Orchestrator {
6
- constructor(config) {
6
+ constructor(config, startPhase = null) {
7
7
  this.config = config;
8
+ this.startPhase = startPhase;
8
9
  this.processManager = processManager;
9
10
  this.healthCheck = healthCheck;
10
11
  this.logger = log;
@@ -226,6 +227,7 @@ export class Orchestrator {
226
227
  try {
227
228
  let hasFailures = false;
228
229
  let phaseFailed = false;
230
+ let startPhaseFound = false;
229
231
 
230
232
  // Handle both old array format and new phases format
231
233
  if (Array.isArray(this.config)) {
@@ -238,6 +240,21 @@ export class Orchestrator {
238
240
  } else if (this.config.phases) {
239
241
  // New: Run phases sequentially, commands within phases in parallel
240
242
  for (const phase of this.config.phases) {
243
+ // Check if we should start from this phase
244
+ if (this.startPhase && !startPhaseFound) {
245
+ if (phase.name === this.startPhase) {
246
+ startPhaseFound = true;
247
+ this.logger.info(`\nšŸŽÆ Starting from phase: ${phase.name}`);
248
+ } else {
249
+ // Mark all commands in previous phases as skipped
250
+ phase.parallel.forEach(({ command }) => {
251
+ this.skippedCommands.push(command);
252
+ this.commandTimings.set(command, 0);
253
+ });
254
+ continue;
255
+ }
256
+ }
257
+
241
258
  if (phaseFailed) {
242
259
  // Mark all commands in remaining phases as skipped
243
260
  phase.parallel.forEach(({ command }) => {
@@ -266,6 +283,13 @@ export class Orchestrator {
266
283
  }
267
284
  }
268
285
 
286
+ // Validate start phase if specified
287
+ if (this.startPhase && !startPhaseFound) {
288
+ const availablePhases = this.config.phases.map(p => p.name).join(', ');
289
+ this.logger.error(`āŒ Start phase "${this.startPhase}" not found. Available phases: ${availablePhases}`);
290
+ process.exit(1);
291
+ }
292
+
269
293
  // Check final status
270
294
  hasFailures = hasFailures ||
271
295
  this.failedCommands.length > 0 ||
@@ -276,16 +300,32 @@ export class Orchestrator {
276
300
 
277
301
  this.summarizeResults();
278
302
 
279
- // Exit with appropriate status
303
+ // Cleanup before exit since finally blocks don't run after process.exit()
304
+ try {
305
+ await this.processManager.cleanup();
306
+ } catch (error) {
307
+ this.logger.error(`Cleanup failed: ${error.message}`);
308
+ }
309
+
310
+ // Force exit with appropriate status
280
311
  if (hasFailures) {
312
+ this.logger.info('Exiting with failure status...');
281
313
  process.exit(1);
314
+ } else {
315
+ this.logger.info('Exiting with success status...');
316
+ process.exit(0);
282
317
  }
283
- } finally {
318
+ } catch (error) {
319
+ this.logger.error(`Orchestrator failed: ${error.message}`);
320
+
321
+ // Cleanup on error
284
322
  try {
285
323
  await this.processManager.cleanup();
286
- } catch (error) {
287
- this.logger.error(`Cleanup failed: ${error.message}`);
324
+ } catch (cleanupError) {
325
+ this.logger.error(`Cleanup failed: ${cleanupError.message}`);
288
326
  }
327
+
328
+ process.exit(1);
289
329
  }
290
330
  }
291
331
  }
@@ -246,25 +246,29 @@ export class ProcessManager {
246
246
  }
247
247
 
248
248
  async cleanup() {
249
- this.logger.info('\nCleaning up background processes...');
249
+ try {
250
+ this.logger.info('\nCleaning up background processes...');
250
251
 
251
- // Debug: Log the number of processes we're tracking
252
- this.logger.info(`- Found ${this.backgroundProcessesDetails.length} background processes to clean up`);
252
+ // Debug: Log the number of processes we're tracking
253
+ this.logger.info(`- Found ${this.backgroundProcessesDetails.length} background processes to clean up`);
253
254
 
254
- // Debug: Log each process details
255
- this.backgroundProcessesDetails.forEach(({ command, pgid, url, startedByScript, kill_command }, index) => {
256
- this.logger.verbose(`- Process ${index + 1}: command=${command}, pgid=${pgid}, url=${url}, startedByScript=${startedByScript}, kill_command=${kill_command}`);
257
- });
255
+ // Debug: Log each process details
256
+ this.backgroundProcessesDetails.forEach(({ command, pgid, url, startedByScript, kill_command }, index) => {
257
+ this.logger.verbose(`- Process ${index + 1}: command=${command}, pgid=${pgid}, url=${url}, startedByScript=${startedByScript}, kill_command=${kill_command}`);
258
+ });
258
259
 
259
- const killPromises = this.backgroundProcessesDetails.map(
260
- async ({ command, pgid, url, startedByScript, kill_command }) => {
261
- await this.cleanupProcess({ command, pgid, url, startedByScript, kill_command });
262
- },
263
- );
260
+ const killPromises = this.backgroundProcessesDetails.map(
261
+ async ({ command, pgid, url, startedByScript, kill_command }) => {
262
+ await this.cleanupProcess({ command, pgid, url, startedByScript, kill_command });
263
+ },
264
+ );
264
265
 
265
- await Promise.all(killPromises);
266
- this.backgroundProcesses = [];
267
- this.backgroundProcessesDetails = [];
266
+ await Promise.allSettled(killPromises);
267
+ this.backgroundProcesses = [];
268
+ this.backgroundProcessesDetails = [];
269
+ } catch (error) {
270
+ this.logger.error(`Cleanup failed: ${error.message}`);
271
+ }
268
272
  }
269
273
 
270
274
  async cleanupCommand(commandName) {
@@ -288,7 +292,7 @@ export class ProcessManager {
288
292
  }
289
293
  );
290
294
 
291
- await Promise.all(killPromises);
295
+ await Promise.allSettled(killPromises);
292
296
 
293
297
  // Remove the cleaned up processes from our tracking arrays
294
298
  this.backgroundProcesses = this.backgroundProcesses.filter(pgid =>
@@ -359,17 +363,19 @@ export class ProcessManager {
359
363
  process.kill(pgid, 'SIGTERM');
360
364
 
361
365
  await new Promise((resolve, reject) => {
362
- const timeout = setTimeout(() => {
363
- clearInterval(checkInterval);
366
+ let timeout, checkInterval;
367
+
368
+ timeout = setTimeout(() => {
369
+ if (checkInterval) clearInterval(checkInterval);
364
370
  reject(new Error('Process termination timeout'));
365
371
  }, 5000);
366
372
 
367
- const checkInterval = setInterval(() => {
373
+ checkInterval = setInterval(() => {
368
374
  try {
369
375
  process.kill(pgid, 0);
370
376
  } catch (error) {
371
- clearInterval(checkInterval);
372
- clearTimeout(timeout);
377
+ if (checkInterval) clearInterval(checkInterval);
378
+ if (timeout) clearTimeout(timeout);
373
379
  resolve();
374
380
  }
375
381
  }, 100);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scripts-orchestrator",
3
- "version": "2.4.1",
3
+ "version": "2.5.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",