scripts-orchestrator 2.5.0 → 2.7.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
@@ -57,6 +57,20 @@ Create a configuration file (default: `scripts-orchestrator.config.js`) that def
57
57
  }
58
58
  ```
59
59
 
60
+ ### Phase Configuration
61
+
62
+ When using the phases format, each phase can have the following properties:
63
+
64
+ ```javascript
65
+ {
66
+ name: 'phase_name', // The name of the phase
67
+ optional: true, // Whether this phase is optional (default: false)
68
+ parallel: [ // Array of commands to run in parallel
69
+ // ... command configurations
70
+ ]
71
+ }
72
+ ```
73
+
60
74
  ## Example Configurations
61
75
 
62
76
  Here are some practical examples of how to configure the orchestrator for different scenarios:
@@ -124,6 +138,18 @@ export default {
124
138
  status: 'enabled'
125
139
  }
126
140
  ]
141
+ },
142
+ {
143
+ name: 'optional-e2e',
144
+ optional: true,
145
+ parallel: [
146
+ {
147
+ command: 'playwright',
148
+ description: 'Run end-to-end tests',
149
+ status: 'enabled',
150
+ attempts: 1
151
+ }
152
+ ]
127
153
  }
128
154
  ]
129
155
  };
@@ -171,6 +197,12 @@ The orchestrator doesn't care what the commands do - it just ensures they run (i
171
197
 
172
198
  # Start from a specific phase with custom config
173
199
  npm run scripts-orchestrator -- ./path/to/your/config.js --phase "playwright"
200
+
201
+ # Run specific optional phases
202
+ npm run scripts-orchestrator -- --phases "optional-e2e,optional-performance"
203
+
204
+ # Run with verbose logging
205
+ npm run scripts-orchestrator -- --verbose
174
206
  ```
175
207
 
176
208
  ### Starting from a Specific Phase
@@ -200,6 +232,59 @@ When starting from a specific phase:
200
232
  - Commands in skipped phases are marked as "skipped" in the final summary
201
233
  - The orchestrator validates that the specified phase exists and shows available phases if not found
202
234
 
235
+ ### Optional Phases
236
+
237
+ You can mark phases as optional by adding `optional: true` to the phase configuration. Optional phases will only run if explicitly requested via the `--phases` command line argument.
238
+
239
+ #### Configuration
240
+ ```javascript
241
+ export default {
242
+ phases: [
243
+ {
244
+ name: 'build',
245
+ parallel: [
246
+ { command: 'build', description: 'Build the project' }
247
+ ]
248
+ },
249
+ {
250
+ name: 'optional-e2e',
251
+ optional: true, // This phase is optional
252
+ parallel: [
253
+ { command: 'playwright', description: 'Run end-to-end tests' }
254
+ ]
255
+ },
256
+ {
257
+ name: 'optional-performance',
258
+ optional: true, // This phase is optional
259
+ parallel: [
260
+ { command: 'lighthouse', description: 'Run performance tests' }
261
+ ]
262
+ }
263
+ ]
264
+ };
265
+ ```
266
+
267
+ #### Usage
268
+ ```bash
269
+ # Run only the default phases (build, test, etc.)
270
+ npm run scripts-orchestrator
271
+
272
+ # Run specific optional phases
273
+ npm run scripts-orchestrator -- --phases "optional-e2e"
274
+
275
+ # Run multiple optional phases
276
+ npm run scripts-orchestrator -- --phases "optional-e2e,optional-performance"
277
+
278
+ # Run all phases including optional ones
279
+ npm run scripts-orchestrator -- --phases "build,test,optional-e2e,optional-performance"
280
+ ```
281
+
282
+ **Note**:
283
+ - Optional phases are skipped by default unless explicitly requested
284
+ - You can combine `--phase` and `--phases` arguments
285
+ - The orchestrator validates that all specified phases exist
286
+ - Commands in skipped optional phases are marked as "skipped" in the final summary
287
+
203
288
  ## Error Handling
204
289
 
205
290
  - The script tracks failed and skipped commands
package/index.js CHANGED
@@ -9,29 +9,43 @@ import path from 'path';
9
9
  import fs from 'fs';
10
10
  import { Orchestrator } from './lib/index.js';
11
11
  import { log } from './lib/logger.js';
12
+ import yargs from 'yargs';
13
+ import { hideBin } from 'yargs/helpers';
12
14
 
13
- // Parse command line arguments
14
- const args = process.argv.slice(2);
15
- let configPath = './scripts-orchestrator.config.js';
16
- let startPhase = null;
15
+ // Parse command line arguments using yargs
16
+ const argv = yargs(hideBin(process.argv))
17
+ .option('verbose', {
18
+ alias: 'v',
19
+ type: 'boolean',
20
+ description: 'Run with verbose logging',
21
+ })
22
+ .option('phase', {
23
+ type: 'string',
24
+ description: 'Start execution from a specific phase',
25
+ })
26
+ .option('phases', {
27
+ type: 'string',
28
+ description: 'Comma-separated list of phases to run (for optional phases)',
29
+ })
30
+ .option('logFolder', {
31
+ type: 'string',
32
+ description: 'Specify the directory for log files',
33
+ })
34
+ .help()
35
+ .alias('h', 'help')
36
+ .parse();
17
37
 
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
- }
38
+ // Extract arguments
39
+ const args = argv._;
40
+ const configPath = args[0] || './scripts-orchestrator.config.js';
41
+ let startPhase = argv.phase;
42
+ let logFolder = argv.logFolder;
43
+ const phases = argv.phases ? argv.phases.split(',').map(p => p.trim()) : null;
30
44
 
31
45
  // Validate config file exists
32
46
  if (!fs.existsSync(configPath)) {
33
47
  log.error(`Error: Config file not found at ${configPath}`);
34
- log.error('Usage: scripts-orchestrator [path-to-config-file] [--phase <phase-name>]');
48
+ log.error('Use --help for usage information');
35
49
  process.exit(1);
36
50
  }
37
51
 
@@ -45,8 +59,13 @@ if (!startPhase && commandsConfig.start_phase) {
45
59
  startPhase = commandsConfig.start_phase;
46
60
  }
47
61
 
62
+ // Check for log_folder in config if not provided via command line
63
+ if (!logFolder && commandsConfig.log_folder) {
64
+ logFolder = commandsConfig.log_folder;
65
+ }
66
+
48
67
  // Create and run the orchestrator
49
- const orchestrator = new Orchestrator(commandsConfig, startPhase);
68
+ const orchestrator = new Orchestrator(commandsConfig, startPhase, logFolder, phases);
50
69
 
51
70
  // Enhanced signal handlers
52
71
  const handleSignal = async (signal) => {
package/lib/logger.js CHANGED
@@ -8,6 +8,18 @@ const argv = yargs(hideBin(process.argv))
8
8
  type: 'boolean',
9
9
  description: 'Run with verbose logging',
10
10
  })
11
+ .option('phase', {
12
+ type: 'string',
13
+ description: 'Start execution from a specific phase',
14
+ })
15
+ .option('phases', {
16
+ type: 'string',
17
+ description: 'Comma-separated list of phases to run (for optional phases)',
18
+ })
19
+ .option('logFolder', {
20
+ type: 'string',
21
+ description: 'Specify the directory for log files',
22
+ })
11
23
  .parse();
12
24
 
13
25
  class Logger {
@@ -3,9 +3,11 @@ import { healthCheck } from './health-check.js';
3
3
  import { log } from './logger.js';
4
4
 
5
5
  export class Orchestrator {
6
- constructor(config, startPhase = null) {
6
+ constructor(config, startPhase = null, logFolder = null, phases = null) {
7
7
  this.config = config;
8
8
  this.startPhase = startPhase;
9
+ this.logFolder = logFolder;
10
+ this.phases = phases;
9
11
  this.processManager = processManager;
10
12
  this.healthCheck = healthCheck;
11
13
  this.logger = log;
@@ -13,6 +15,11 @@ export class Orchestrator {
13
15
  this.skippedCommands = [];
14
16
  this.commandTimings = new Map();
15
17
 
18
+ // Set the log folder in process manager
19
+ if (logFolder) {
20
+ this.processManager.setLogFolder(logFolder);
21
+ }
22
+
16
23
  // Flatten commands for easier tracking
17
24
  this.allCommands = this.flattenCommands(config);
18
25
  }
@@ -47,6 +54,7 @@ export class Orchestrator {
47
54
  dependencies = [],
48
55
  background = false,
49
56
  status = 'enabled',
57
+ log,
50
58
  logFile,
51
59
  attempts = 1,
52
60
  retry_command,
@@ -149,7 +157,7 @@ export class Orchestrator {
149
157
 
150
158
  const { success, output } = await this.processManager.runCommand({
151
159
  cmd: attempt === 1 ? command : retry_command || command,
152
- logFile,
160
+ logFile: log || logFile, // Prefer 'log' key over 'logFile' for backwards compatibility
153
161
  background,
154
162
  healthCheck: health_check,
155
163
  kill_command,
@@ -207,7 +215,9 @@ export class Orchestrator {
207
215
 
208
216
  if (this.failedCommands.includes(command)) {
209
217
  hasFailures = true;
210
- this.logger.error(`- ${command}: ❌${durationStr} (See logs/scripts-orchestrator_${command}.log)`);
218
+ // Get the actual log path from process manager
219
+ const logPath = this.processManager.getLogPath(command);
220
+ this.logger.error(`- ${command}: ❌${durationStr} (See ${logPath})`);
211
221
  } else if (this.skippedCommands.includes(command)) {
212
222
  hasFailures = true;
213
223
  this.logger.warn(`- ${command}: ⚠️${durationStr} (Skipped due to failed dependency)`);
@@ -255,6 +265,17 @@ export class Orchestrator {
255
265
  }
256
266
  }
257
267
 
268
+ // Check if this is an optional phase that should be skipped
269
+ if (phase.optional === true && this.phases && !this.phases.includes(phase.name)) {
270
+ this.logger.info(`\n⏭️ Skipping optional phase: ${phase.name} (not explicitly requested)`);
271
+ // Mark all commands in this phase as skipped
272
+ phase.parallel.forEach(({ command }) => {
273
+ this.skippedCommands.push(command);
274
+ this.commandTimings.set(command, 0);
275
+ });
276
+ continue;
277
+ }
278
+
258
279
  if (phaseFailed) {
259
280
  // Mark all commands in remaining phases as skipped
260
281
  phase.parallel.forEach(({ command }) => {
@@ -290,6 +311,16 @@ export class Orchestrator {
290
311
  process.exit(1);
291
312
  }
292
313
 
314
+ // Validate phases if specified
315
+ if (this.phases) {
316
+ const availablePhases = this.config.phases.map(p => p.name);
317
+ const invalidPhases = this.phases.filter(phase => !availablePhases.includes(phase));
318
+ if (invalidPhases.length > 0) {
319
+ this.logger.error(`❌ Invalid phases specified: ${invalidPhases.join(', ')}. Available phases: ${availablePhases.join(', ')}`);
320
+ process.exit(1);
321
+ }
322
+ }
323
+
293
324
  // Check final status
294
325
  hasFailures = hasFailures ||
295
326
  this.failedCommands.length > 0 ||
@@ -10,6 +10,20 @@ export class ProcessManager {
10
10
  this.logger = log;
11
11
  this.backgroundProcesses = [];
12
12
  this.backgroundProcessesDetails = [];
13
+ this.logFolder = 'scripts-orchestrator-logs'; // Default log folder
14
+ }
15
+
16
+ setLogFolder(logFolder) {
17
+ this.logFolder = logFolder;
18
+ this.logger.verbose(`Log folder set to: ${logFolder}`);
19
+ }
20
+
21
+ getLogPath(command) {
22
+ const baseDir = this.logFolder ? path.resolve(this.logFolder) : process.cwd();
23
+ const LOGS_DIR = path.join(baseDir, 'scripts-orchestrator-logs');
24
+ // Use only the first word of the command for the log filename
25
+ const logName = command.split(/\s+/)[0];
26
+ return path.join(LOGS_DIR, `${logName}.log`);
13
27
  }
14
28
 
15
29
  addBackgroundProcess({ command, url, startedByScript, process_tracking, kill_command }) {
@@ -24,8 +38,11 @@ export class ProcessManager {
24
38
  }
25
39
 
26
40
  async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false }) {
27
- const LOGS_DIR = path.resolve(process.cwd(), 'scripts-orchestrator-logs');
28
- const LOG_FILE = logFile || path.join(LOGS_DIR, `${cmd}.log`);
41
+ const baseDir = this.logFolder ? path.resolve(this.logFolder) : process.cwd();
42
+ const LOGS_DIR = path.join(baseDir, 'scripts-orchestrator-logs');
43
+ // Use only the first word of the command for the log filename
44
+ const logName = cmd.split(/\s+/)[0];
45
+ const LOG_FILE = logFile || path.join(LOGS_DIR, `${logName}.log`);
29
46
 
30
47
  try {
31
48
  if (!fs.existsSync(LOGS_DIR)) {
@@ -497,4 +514,4 @@ export class ProcessManager {
497
514
  }
498
515
 
499
516
  // For backward compatibility
500
- export const processManager = new ProcessManager();
517
+ export const processManager = new ProcessManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scripts-orchestrator",
3
- "version": "2.5.0",
3
+ "version": "2.7.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",