scripts-orchestrator 2.7.1 ā 2.9.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 +72 -0
- package/index.js +11 -1
- package/lib/git-cache.js +187 -0
- package/lib/index.js +2 -1
- package/lib/logger.js +66 -0
- package/lib/orchestrator.js +59 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ npm install --save-dev scripts-orchestrator
|
|
|
26
26
|
## Features
|
|
27
27
|
|
|
28
28
|
- **Parallel Execution**: Runs multiple commands concurrently for faster execution
|
|
29
|
+
- **Sequential Mode**: Option to run all commands sequentially for low CPU machines
|
|
29
30
|
- **Dependency Management**: Handles command dependencies and ensures proper execution order
|
|
30
31
|
- **Background Processes**: Supports running commands in the background with health checks
|
|
31
32
|
- **Retry Mechanism**: Configurable retry attempts for failed commands
|
|
@@ -33,6 +34,7 @@ npm install --save-dev scripts-orchestrator
|
|
|
33
34
|
- **Health Checks**: Verifies service availability before proceeding
|
|
34
35
|
- **Environment Variables**: Pass custom environment variables to commands
|
|
35
36
|
- **Optional Phases**: Mark phases as optional and run them selectively
|
|
37
|
+
- **Git-Based Caching**: Automatically skips execution when git state is unchanged
|
|
36
38
|
- **Comprehensive Logging**: Detailed logging of command execution and results
|
|
37
39
|
|
|
38
40
|
## Configuration
|
|
@@ -255,6 +257,12 @@ The orchestrator doesn't care what the commands do - it just ensures they run (i
|
|
|
255
257
|
|
|
256
258
|
# Run with verbose logging
|
|
257
259
|
npm run scripts-orchestrator -- --verbose
|
|
260
|
+
|
|
261
|
+
# Run in sequential mode (for low CPU machines)
|
|
262
|
+
npm run scripts-orchestrator -- --sequential
|
|
263
|
+
|
|
264
|
+
# Specify a custom log folder
|
|
265
|
+
npm run scripts-orchestrator -- --logFolder ./custom-logs
|
|
258
266
|
```
|
|
259
267
|
|
|
260
268
|
### Starting from a Specific Phase
|
|
@@ -337,6 +345,29 @@ npm run scripts-orchestrator -- --phases "build,test,optional-e2e,optional-perfo
|
|
|
337
345
|
- The orchestrator validates that all specified phases exist
|
|
338
346
|
- Commands in skipped optional phases are marked as "skipped" in the final summary
|
|
339
347
|
|
|
348
|
+
### Sequential Mode
|
|
349
|
+
|
|
350
|
+
By default, the orchestrator runs commands within each phase in parallel for optimal performance. However, you can use the `--sequential` flag to run all commands sequentially, which is useful for low CPU machines or when you need to reduce resource consumption.
|
|
351
|
+
|
|
352
|
+
#### Usage
|
|
353
|
+
```bash
|
|
354
|
+
# Run all commands sequentially instead of in parallel
|
|
355
|
+
npm run scripts-orchestrator -- --sequential
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
When running in sequential mode:
|
|
359
|
+
- Commands within each phase are executed one at a time
|
|
360
|
+
- Phases still run sequentially (as they always do)
|
|
361
|
+
- If a command fails, the remaining commands in that phase are skipped
|
|
362
|
+
- Lower CPU and memory usage compared to parallel execution
|
|
363
|
+
- Longer total execution time
|
|
364
|
+
|
|
365
|
+
This is particularly useful for:
|
|
366
|
+
- CI/CD environments with limited resources
|
|
367
|
+
- Development machines with low CPU/memory
|
|
368
|
+
- Debugging individual command failures
|
|
369
|
+
- Avoiding resource contention between commands
|
|
370
|
+
|
|
340
371
|
## Error Handling
|
|
341
372
|
|
|
342
373
|
- The script tracks failed and skipped commands
|
|
@@ -347,9 +378,50 @@ npm run scripts-orchestrator -- --phases "build,test,optional-e2e,optional-perfo
|
|
|
347
378
|
## Logging
|
|
348
379
|
|
|
349
380
|
- Each command's output is logged to `scripts-orchestrator-logs/<command>.log` in the current working directory
|
|
381
|
+
- Main orchestrator logs are saved to `scripts-orchestrator-logs/orchestrator-main-<timestamp>.log`
|
|
382
|
+
- Git commit hash is cached in `scripts-orchestrator-logs/.git-hash-cache` for skip detection
|
|
350
383
|
- Provides real-time status updates during execution
|
|
351
384
|
- Summarizes results at the end of execution
|
|
352
385
|
|
|
386
|
+
### Custom Log Folder
|
|
387
|
+
|
|
388
|
+
You can customize the log folder location using either the command line or configuration file:
|
|
389
|
+
|
|
390
|
+
#### Method 1: Command Line Argument
|
|
391
|
+
```bash
|
|
392
|
+
# Use a custom log folder
|
|
393
|
+
npm run scripts-orchestrator -- --logFolder ./my-custom-logs
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### Method 2: Configuration File
|
|
397
|
+
```javascript
|
|
398
|
+
export default {
|
|
399
|
+
log_folder: './my-custom-logs', // Custom log folder
|
|
400
|
+
phases: [
|
|
401
|
+
// ... your phases
|
|
402
|
+
]
|
|
403
|
+
};
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Note**: Command line arguments take precedence over configuration file settings.
|
|
407
|
+
|
|
408
|
+
All logs (command logs, main orchestrator logs, and git cache) will be stored in the specified folder.
|
|
409
|
+
|
|
410
|
+
## Git-Based Caching
|
|
411
|
+
|
|
412
|
+
The orchestrator automatically tracks the git commit hash and repository state to optimize execution:
|
|
413
|
+
|
|
414
|
+
- **On first run**: Records the current git commit hash in `scripts-orchestrator-logs/.git-hash-cache`
|
|
415
|
+
- **On subsequent runs**: Checks if:
|
|
416
|
+
- The git commit hash matches the cached hash
|
|
417
|
+
- There are no staged or unstaged changes in the repository
|
|
418
|
+
- **When conditions are met**: Skips execution entirely with message `ā Git state unchanged`
|
|
419
|
+
- **When conditions fail**: Runs normally and updates the cache on successful completion
|
|
420
|
+
|
|
421
|
+
This feature is particularly useful in CI/CD pipelines where the same commit might be processed multiple times, saving time and resources by avoiding redundant executions.
|
|
422
|
+
|
|
423
|
+
**Note**: The cache is only updated on successful execution. Failed runs will not update the cache, ensuring subsequent runs will retry.
|
|
424
|
+
|
|
353
425
|
## Exit Codes
|
|
354
426
|
|
|
355
427
|
- `0`: All commands executed successfully
|
package/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const argv = yargs(hideBin(process.argv))
|
|
|
31
31
|
type: 'string',
|
|
32
32
|
description: 'Specify the directory for log files',
|
|
33
33
|
})
|
|
34
|
+
.option('sequential', {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Run all commands sequentially instead of in parallel (for low CPU machines)',
|
|
37
|
+
})
|
|
34
38
|
.help()
|
|
35
39
|
.alias('h', 'help')
|
|
36
40
|
.parse();
|
|
@@ -41,6 +45,7 @@ const configPath = args[0] || './scripts-orchestrator.config.js';
|
|
|
41
45
|
let startPhase = argv.phase;
|
|
42
46
|
let logFolder = argv.logFolder;
|
|
43
47
|
const phases = argv.phases ? argv.phases.split(',').map(p => p.trim()) : null;
|
|
48
|
+
const sequential = argv.sequential || false;
|
|
44
49
|
|
|
45
50
|
// Validate config file exists
|
|
46
51
|
if (!fs.existsSync(configPath)) {
|
|
@@ -64,8 +69,13 @@ if (!logFolder && commandsConfig.log_folder) {
|
|
|
64
69
|
logFolder = commandsConfig.log_folder;
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
// Set the log folder for the main orchestrator logs if specified
|
|
73
|
+
if (logFolder) {
|
|
74
|
+
log.setLogFolder(logFolder);
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
// Create and run the orchestrator
|
|
68
|
-
const orchestrator = new Orchestrator(commandsConfig, startPhase, logFolder, phases);
|
|
78
|
+
const orchestrator = new Orchestrator(commandsConfig, startPhase, logFolder, phases, sequential);
|
|
69
79
|
|
|
70
80
|
// Enhanced signal handlers
|
|
71
81
|
const handleSignal = async (signal) => {
|
package/lib/git-cache.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { log } from './logger.js';
|
|
5
|
+
|
|
6
|
+
export class GitCache {
|
|
7
|
+
constructor(logFolder = 'scripts-orchestrator-logs') {
|
|
8
|
+
this.logFolder = logFolder;
|
|
9
|
+
this.cacheFileName = '.git-hash-cache';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Execute a git command and return the output
|
|
14
|
+
* @param {string[]} args - Git command arguments
|
|
15
|
+
* @returns {Promise<{success: boolean, output: string}>}
|
|
16
|
+
*/
|
|
17
|
+
async executeGitCommand(args) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const gitProcess = spawn('git', args, {
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
shell: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let output = '';
|
|
25
|
+
let errorOutput = '';
|
|
26
|
+
|
|
27
|
+
gitProcess.stdout.on('data', (data) => {
|
|
28
|
+
output += data.toString();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
gitProcess.stderr.on('data', (data) => {
|
|
32
|
+
errorOutput += data.toString();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
gitProcess.on('close', (code) => {
|
|
36
|
+
if (code === 0) {
|
|
37
|
+
resolve({ success: true, output: output.trim() });
|
|
38
|
+
} else {
|
|
39
|
+
resolve({ success: false, output: errorOutput.trim() });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
gitProcess.on('error', (error) => {
|
|
44
|
+
resolve({ success: false, output: error.message });
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the current git commit hash
|
|
51
|
+
* @returns {Promise<string|null>}
|
|
52
|
+
*/
|
|
53
|
+
async getCurrentCommitHash() {
|
|
54
|
+
const result = await this.executeGitCommand(['rev-parse', 'HEAD']);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
return result.output;
|
|
57
|
+
}
|
|
58
|
+
log.verbose(`Failed to get git commit hash: ${result.output}`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if there are any staged or unstaged changes
|
|
64
|
+
* @returns {Promise<boolean>}
|
|
65
|
+
*/
|
|
66
|
+
async hasGitChanges() {
|
|
67
|
+
// Check for staged and unstaged changes
|
|
68
|
+
const statusResult = await this.executeGitCommand(['status', '--porcelain']);
|
|
69
|
+
|
|
70
|
+
if (!statusResult.success) {
|
|
71
|
+
log.verbose(`Failed to check git status: ${statusResult.output}`);
|
|
72
|
+
// If we can't check git status, assume there are changes to be safe
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If there's any output, there are changes
|
|
77
|
+
return statusResult.output.length > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the path to the cache file
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
getCacheFilePath() {
|
|
85
|
+
const baseDir = this.logFolder ? path.resolve(this.logFolder) : process.cwd();
|
|
86
|
+
const LOGS_DIR = path.join(baseDir, 'scripts-orchestrator-logs');
|
|
87
|
+
return path.join(LOGS_DIR, this.cacheFileName);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read the cached git hash
|
|
92
|
+
* @returns {string|null}
|
|
93
|
+
*/
|
|
94
|
+
readCachedHash() {
|
|
95
|
+
const cacheFilePath = this.getCacheFilePath();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
99
|
+
const cachedHash = fs.readFileSync(cacheFilePath, 'utf8').trim();
|
|
100
|
+
log.verbose(`Read cached git hash: ${cachedHash}`);
|
|
101
|
+
return cachedHash;
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
log.verbose(`Failed to read cached git hash: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Write the current git hash to cache
|
|
112
|
+
* @param {string} hash
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
writeCachedHash(hash) {
|
|
116
|
+
const cacheFilePath = this.getCacheFilePath();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Ensure the directory exists
|
|
120
|
+
const cacheDir = path.dirname(cacheFilePath);
|
|
121
|
+
if (!fs.existsSync(cacheDir)) {
|
|
122
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(cacheFilePath, hash, 'utf8');
|
|
126
|
+
log.verbose(`Wrote git hash to cache: ${hash}`);
|
|
127
|
+
return true;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
log.error(`Failed to write git hash to cache: ${error.message}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if the orchestrator should skip running based on git state
|
|
136
|
+
* @returns {Promise<boolean>} - true if should skip, false if should run
|
|
137
|
+
*/
|
|
138
|
+
async shouldSkipExecution() {
|
|
139
|
+
log.verbose('Checking git state for caching...');
|
|
140
|
+
|
|
141
|
+
// Get current commit hash
|
|
142
|
+
const currentHash = await this.getCurrentCommitHash();
|
|
143
|
+
if (!currentHash) {
|
|
144
|
+
log.verbose('Could not get current git hash, will not skip execution');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get cached hash
|
|
149
|
+
const cachedHash = this.readCachedHash();
|
|
150
|
+
if (!cachedHash) {
|
|
151
|
+
log.verbose('No cached git hash found, will not skip execution');
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check if hashes match
|
|
156
|
+
if (currentHash !== cachedHash) {
|
|
157
|
+
log.verbose(`Git hash changed (${cachedHash.substring(0, 7)} -> ${currentHash.substring(0, 7)}), will not skip execution`);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for uncommitted changes
|
|
162
|
+
const hasChanges = await this.hasGitChanges();
|
|
163
|
+
if (hasChanges) {
|
|
164
|
+
log.verbose('Git repository has uncommitted changes, will not skip execution');
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// All conditions met - can skip
|
|
169
|
+
log.info(`ā Git state unchanged (${currentHash.substring(0, 7)}), skipping execution`);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Update the cached git hash with the current commit
|
|
175
|
+
* @returns {Promise<void>}
|
|
176
|
+
*/
|
|
177
|
+
async updateCache() {
|
|
178
|
+
const currentHash = await this.getCurrentCommitHash();
|
|
179
|
+
if (currentHash) {
|
|
180
|
+
this.writeCachedHash(currentHash);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Export a singleton instance
|
|
186
|
+
export const gitCache = new GitCache();
|
|
187
|
+
|
package/lib/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Orchestrator } from './orchestrator.js';
|
|
|
2
2
|
import { ProcessManager } from './process-manager.js';
|
|
3
3
|
import { HealthCheck } from './health-check.js';
|
|
4
4
|
import { Logger } from './logger.js';
|
|
5
|
+
import { GitCache } from './git-cache.js';
|
|
5
6
|
|
|
6
|
-
export { Orchestrator, ProcessManager, HealthCheck, Logger };
|
|
7
|
+
export { Orchestrator, ProcessManager, HealthCheck, Logger, GitCache };
|
|
7
8
|
export default Orchestrator;
|
package/lib/logger.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
4
6
|
|
|
5
7
|
const argv = yargs(hideBin(process.argv))
|
|
6
8
|
.option('verbose', {
|
|
@@ -25,27 +27,91 @@ const argv = yargs(hideBin(process.argv))
|
|
|
25
27
|
class Logger {
|
|
26
28
|
constructor() {
|
|
27
29
|
this.isVerbose = argv.verbose;
|
|
30
|
+
this.logFolder = argv.logFolder || 'scripts-orchestrator-logs';
|
|
31
|
+
this.logFile = null;
|
|
32
|
+
this.logStream = null;
|
|
33
|
+
this.initializeLogFile();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
initializeLogFile() {
|
|
37
|
+
try {
|
|
38
|
+
// Create log directory if it doesn't exist
|
|
39
|
+
if (!fs.existsSync(this.logFolder)) {
|
|
40
|
+
fs.mkdirSync(this.logFolder, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create main log file with timestamp
|
|
44
|
+
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
|
|
45
|
+
this.logFile = path.join(this.logFolder, `orchestrator-main-${timestamp}.log`);
|
|
46
|
+
|
|
47
|
+
// Create write stream
|
|
48
|
+
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
49
|
+
|
|
50
|
+
// Handle stream errors
|
|
51
|
+
this.logStream.on('error', (err) => {
|
|
52
|
+
console.error(`Error writing to log file: ${err.message}`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Ensure the stream is closed on process exit
|
|
56
|
+
process.on('exit', () => {
|
|
57
|
+
if (this.logStream) {
|
|
58
|
+
this.logStream.end();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Write initial log entry
|
|
63
|
+
this.writeToFile(`[START] Orchestrator started at ${new Date().toISOString()}\n`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`Failed to initialize log file: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setLogFolder(newLogFolder) {
|
|
70
|
+
// Close existing stream if it exists
|
|
71
|
+
if (this.logStream) {
|
|
72
|
+
this.logStream.end();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Update log folder
|
|
76
|
+
this.logFolder = newLogFolder;
|
|
77
|
+
|
|
78
|
+
// Reinitialize with new folder
|
|
79
|
+
this.initializeLogFile();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
writeToFile(message) {
|
|
83
|
+
if (this.logStream) {
|
|
84
|
+
// Strip ANSI color codes for file output
|
|
85
|
+
// eslint-disable-next-line no-control-regex
|
|
86
|
+
const cleanMessage = message.replace(/\x1b\[[0-9;]*m/g, '');
|
|
87
|
+
this.logStream.write(`${cleanMessage}\n`);
|
|
88
|
+
}
|
|
28
89
|
}
|
|
29
90
|
|
|
30
91
|
info(message) {
|
|
31
92
|
console.log(chalk.blue(`[INFO] ${message}`));
|
|
93
|
+
this.writeToFile(`[INFO] ${message}`);
|
|
32
94
|
}
|
|
33
95
|
|
|
34
96
|
success(message) {
|
|
35
97
|
console.log(chalk.green(`[SUCCESS] ${message}`));
|
|
98
|
+
this.writeToFile(`[SUCCESS] ${message}`);
|
|
36
99
|
}
|
|
37
100
|
|
|
38
101
|
error(message) {
|
|
39
102
|
console.error(chalk.red(`[ERROR] ${message}`));
|
|
103
|
+
this.writeToFile(`[ERROR] ${message}`);
|
|
40
104
|
}
|
|
41
105
|
|
|
42
106
|
warn(message) {
|
|
43
107
|
console.warn(chalk.yellow(`[WARN] ${message}`));
|
|
108
|
+
this.writeToFile(`[WARN] ${message}`);
|
|
44
109
|
}
|
|
45
110
|
|
|
46
111
|
verbose(message) {
|
|
47
112
|
if (this.isVerbose) {
|
|
48
113
|
console.log(chalk.gray(`[VERBOSE] ${message}`));
|
|
114
|
+
this.writeToFile(`[VERBOSE] ${message}`);
|
|
49
115
|
}
|
|
50
116
|
}
|
|
51
117
|
}
|
package/lib/orchestrator.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { processManager } from './process-manager.js';
|
|
2
2
|
import { healthCheck } from './health-check.js';
|
|
3
3
|
import { log } from './logger.js';
|
|
4
|
+
import { GitCache } from './git-cache.js';
|
|
4
5
|
|
|
5
6
|
export class Orchestrator {
|
|
6
|
-
constructor(config, startPhase = null, logFolder = null, phases = null) {
|
|
7
|
+
constructor(config, startPhase = null, logFolder = null, phases = null, sequential = false) {
|
|
7
8
|
this.config = config;
|
|
8
9
|
this.startPhase = startPhase;
|
|
9
10
|
this.logFolder = logFolder;
|
|
10
11
|
this.phases = phases;
|
|
12
|
+
this.sequential = sequential;
|
|
11
13
|
this.processManager = processManager;
|
|
12
14
|
this.healthCheck = healthCheck;
|
|
13
15
|
this.logger = log;
|
|
14
16
|
this.failedCommands = [];
|
|
15
17
|
this.skippedCommands = [];
|
|
16
18
|
this.commandTimings = new Map();
|
|
19
|
+
this.gitCache = new GitCache(logFolder);
|
|
17
20
|
|
|
18
21
|
// Set the log folder in process manager
|
|
19
22
|
if (logFolder) {
|
|
@@ -237,20 +240,44 @@ export class Orchestrator {
|
|
|
237
240
|
|
|
238
241
|
async run() {
|
|
239
242
|
try {
|
|
243
|
+
// Check if we should skip execution based on git state
|
|
244
|
+
const shouldSkip = await this.gitCache.shouldSkipExecution();
|
|
245
|
+
if (shouldSkip) {
|
|
246
|
+
this.logger.success('š No changes detected, skipping execution!');
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
|
|
240
250
|
let hasFailures = false;
|
|
241
251
|
let phaseFailed = false;
|
|
242
252
|
let startPhaseFound = false;
|
|
243
253
|
|
|
244
254
|
// Handle both old array format and new phases format
|
|
245
255
|
if (Array.isArray(this.config)) {
|
|
246
|
-
// Legacy: Run all commands in parallel
|
|
247
|
-
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
// Legacy: Run all commands in parallel or sequential based on flag
|
|
257
|
+
if (this.sequential) {
|
|
258
|
+
this.logger.info('š Running in sequential mode');
|
|
259
|
+
const results = [];
|
|
260
|
+
for (const commandConfig of this.config) {
|
|
261
|
+
const result = await this.executeCommand(commandConfig);
|
|
262
|
+
results.push(result);
|
|
263
|
+
if (!result) {
|
|
264
|
+
hasFailures = true;
|
|
265
|
+
break; // Stop on first failure in sequential mode
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const tasks = this.config.map((commandConfig) =>
|
|
270
|
+
this.executeCommand(commandConfig),
|
|
271
|
+
);
|
|
272
|
+
const results = await Promise.all(tasks);
|
|
273
|
+
hasFailures = results.some(result => !result);
|
|
274
|
+
}
|
|
252
275
|
} else if (this.config.phases) {
|
|
253
|
-
// New: Run phases sequentially, commands within phases in parallel
|
|
276
|
+
// New: Run phases sequentially, commands within phases in parallel or sequential based on flag
|
|
277
|
+
if (this.sequential) {
|
|
278
|
+
this.logger.info('š Running in sequential mode');
|
|
279
|
+
}
|
|
280
|
+
|
|
254
281
|
for (const phase of this.config.phases) {
|
|
255
282
|
// Check if we should start from this phase
|
|
256
283
|
if (this.startPhase && !startPhaseFound) {
|
|
@@ -289,11 +316,26 @@ export class Orchestrator {
|
|
|
289
316
|
|
|
290
317
|
this.logger.info(`\nš Starting phase: ${phase.name}`);
|
|
291
318
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
319
|
+
let results;
|
|
320
|
+
if (this.sequential) {
|
|
321
|
+
// Run commands sequentially
|
|
322
|
+
results = [];
|
|
323
|
+
for (const commandConfig of phase.parallel) {
|
|
324
|
+
const result = await this.executeCommand(commandConfig);
|
|
325
|
+
results.push(result);
|
|
326
|
+
if (!result) {
|
|
327
|
+
// In sequential mode, stop phase execution on first failure
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
// Run commands in parallel
|
|
333
|
+
const tasks = phase.parallel.map((commandConfig) =>
|
|
334
|
+
this.executeCommand(commandConfig),
|
|
335
|
+
);
|
|
336
|
+
results = await Promise.all(tasks);
|
|
337
|
+
}
|
|
295
338
|
|
|
296
|
-
const results = await Promise.all(tasks);
|
|
297
339
|
const phaseHasFailures = results.some(result => !result);
|
|
298
340
|
|
|
299
341
|
if (phaseHasFailures) {
|
|
@@ -340,6 +382,11 @@ export class Orchestrator {
|
|
|
340
382
|
this.logger.error(`Cleanup failed: ${error.message}`);
|
|
341
383
|
}
|
|
342
384
|
|
|
385
|
+
// Update git cache on successful execution
|
|
386
|
+
if (!hasFailures) {
|
|
387
|
+
await this.gitCache.updateCache();
|
|
388
|
+
}
|
|
389
|
+
|
|
343
390
|
// Force exit with appropriate status
|
|
344
391
|
if (hasFailures) {
|
|
345
392
|
this.logger.info('Exiting with failure status...');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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",
|