scripts-orchestrator 2.6.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 +85 -0
- package/index.js +32 -22
- package/lib/logger.js +4 -0
- package/lib/orchestrator.js +28 -3
- package/lib/process-manager.js +12 -2
- package/package.json +1 -1
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,33 +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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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();
|
|
18
37
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
i++; // Skip the next argument since we consumed it
|
|
26
|
-
} else if (arg === '--logFolder' && i + 1 < args.length) {
|
|
27
|
-
logFolder = args[i + 1];
|
|
28
|
-
i++; // Skip the next argument since we consumed it
|
|
29
|
-
} else if (!arg.startsWith('--') && !configPath) {
|
|
30
|
-
// First non-flag argument is the config path
|
|
31
|
-
configPath = arg;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
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;
|
|
34
44
|
|
|
35
45
|
// Validate config file exists
|
|
36
46
|
if (!fs.existsSync(configPath)) {
|
|
37
47
|
log.error(`Error: Config file not found at ${configPath}`);
|
|
38
|
-
log.error('
|
|
48
|
+
log.error('Use --help for usage information');
|
|
39
49
|
process.exit(1);
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -55,7 +65,7 @@ if (!logFolder && commandsConfig.log_folder) {
|
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
// Create and run the orchestrator
|
|
58
|
-
const orchestrator = new Orchestrator(commandsConfig, startPhase, logFolder);
|
|
68
|
+
const orchestrator = new Orchestrator(commandsConfig, startPhase, logFolder, phases);
|
|
59
69
|
|
|
60
70
|
// Enhanced signal handlers
|
|
61
71
|
const handleSignal = async (signal) => {
|
package/lib/logger.js
CHANGED
|
@@ -12,6 +12,10 @@ const argv = yargs(hideBin(process.argv))
|
|
|
12
12
|
type: 'string',
|
|
13
13
|
description: 'Start execution from a specific phase',
|
|
14
14
|
})
|
|
15
|
+
.option('phases', {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Comma-separated list of phases to run (for optional phases)',
|
|
18
|
+
})
|
|
15
19
|
.option('logFolder', {
|
|
16
20
|
type: 'string',
|
|
17
21
|
description: 'Specify the directory for log files',
|
package/lib/orchestrator.js
CHANGED
|
@@ -3,10 +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, logFolder = null) {
|
|
6
|
+
constructor(config, startPhase = null, logFolder = null, phases = null) {
|
|
7
7
|
this.config = config;
|
|
8
8
|
this.startPhase = startPhase;
|
|
9
9
|
this.logFolder = logFolder;
|
|
10
|
+
this.phases = phases;
|
|
10
11
|
this.processManager = processManager;
|
|
11
12
|
this.healthCheck = healthCheck;
|
|
12
13
|
this.logger = log;
|
|
@@ -53,6 +54,7 @@ export class Orchestrator {
|
|
|
53
54
|
dependencies = [],
|
|
54
55
|
background = false,
|
|
55
56
|
status = 'enabled',
|
|
57
|
+
log,
|
|
56
58
|
logFile,
|
|
57
59
|
attempts = 1,
|
|
58
60
|
retry_command,
|
|
@@ -155,7 +157,7 @@ export class Orchestrator {
|
|
|
155
157
|
|
|
156
158
|
const { success, output } = await this.processManager.runCommand({
|
|
157
159
|
cmd: attempt === 1 ? command : retry_command || command,
|
|
158
|
-
logFile,
|
|
160
|
+
logFile: log || logFile, // Prefer 'log' key over 'logFile' for backwards compatibility
|
|
159
161
|
background,
|
|
160
162
|
healthCheck: health_check,
|
|
161
163
|
kill_command,
|
|
@@ -213,7 +215,9 @@ export class Orchestrator {
|
|
|
213
215
|
|
|
214
216
|
if (this.failedCommands.includes(command)) {
|
|
215
217
|
hasFailures = true;
|
|
216
|
-
|
|
218
|
+
// Get the actual log path from process manager
|
|
219
|
+
const logPath = this.processManager.getLogPath(command);
|
|
220
|
+
this.logger.error(`- ${command}: ❌${durationStr} (See ${logPath})`);
|
|
217
221
|
} else if (this.skippedCommands.includes(command)) {
|
|
218
222
|
hasFailures = true;
|
|
219
223
|
this.logger.warn(`- ${command}: ⚠️${durationStr} (Skipped due to failed dependency)`);
|
|
@@ -261,6 +265,17 @@ export class Orchestrator {
|
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
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
|
+
|
|
264
279
|
if (phaseFailed) {
|
|
265
280
|
// Mark all commands in remaining phases as skipped
|
|
266
281
|
phase.parallel.forEach(({ command }) => {
|
|
@@ -296,6 +311,16 @@ export class Orchestrator {
|
|
|
296
311
|
process.exit(1);
|
|
297
312
|
}
|
|
298
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
|
+
|
|
299
324
|
// Check final status
|
|
300
325
|
hasFailures = hasFailures ||
|
|
301
326
|
this.failedCommands.length > 0 ||
|
package/lib/process-manager.js
CHANGED
|
@@ -18,6 +18,14 @@ export class ProcessManager {
|
|
|
18
18
|
this.logger.verbose(`Log folder set to: ${logFolder}`);
|
|
19
19
|
}
|
|
20
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`);
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
addBackgroundProcess({ command, url, startedByScript, process_tracking, kill_command }) {
|
|
22
30
|
this.logger.verbose(`Adding background process: ${command} (${url})`);
|
|
23
31
|
this.backgroundProcessesDetails.push({
|
|
@@ -32,7 +40,9 @@ export class ProcessManager {
|
|
|
32
40
|
async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false }) {
|
|
33
41
|
const baseDir = this.logFolder ? path.resolve(this.logFolder) : process.cwd();
|
|
34
42
|
const LOGS_DIR = path.join(baseDir, 'scripts-orchestrator-logs');
|
|
35
|
-
|
|
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`);
|
|
36
46
|
|
|
37
47
|
try {
|
|
38
48
|
if (!fs.existsSync(LOGS_DIR)) {
|
|
@@ -504,4 +514,4 @@ export class ProcessManager {
|
|
|
504
514
|
}
|
|
505
515
|
|
|
506
516
|
// For backward compatibility
|
|
507
|
-
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.
|
|
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",
|