scripts-orchestrator 2.7.0 → 2.8.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 +69 -0
- package/lib/git-cache.js +187 -0
- package/lib/index.js +2 -1
- package/lib/logger.js +53 -0
- package/lib/orchestrator.js +16 -0
- package/lib/process-manager.js +20 -6
- package/package.json +1 -1
- package/scripts-orchestrator.config.js +3 -0
package/README.md
CHANGED
|
@@ -31,6 +31,9 @@ npm install --save-dev scripts-orchestrator
|
|
|
31
31
|
- **Retry Mechanism**: Configurable retry attempts for failed commands
|
|
32
32
|
- **Process Management**: Proper cleanup of background processes
|
|
33
33
|
- **Health Checks**: Verifies service availability before proceeding
|
|
34
|
+
- **Environment Variables**: Pass custom environment variables to commands
|
|
35
|
+
- **Optional Phases**: Mark phases as optional and run them selectively
|
|
36
|
+
- **Git-Based Caching**: Automatically skips execution when git state is unchanged
|
|
34
37
|
- **Comprehensive Logging**: Detailed logging of command execution and results
|
|
35
38
|
|
|
36
39
|
## Configuration
|
|
@@ -45,6 +48,10 @@ Create a configuration file (default: `scripts-orchestrator.config.js`) that def
|
|
|
45
48
|
attempts: 1, // Number of retry attempts
|
|
46
49
|
dependencies: [], // Array of dependent commands
|
|
47
50
|
background: false, // Whether to run in background
|
|
51
|
+
env: { // Optional environment variables
|
|
52
|
+
PORT: 3000,
|
|
53
|
+
NODE_ENV: 'production'
|
|
54
|
+
},
|
|
48
55
|
kill_command: 'kill_storybook', // Optional kill command to kill the process
|
|
49
56
|
health_check: { // Health check configuration
|
|
50
57
|
url: 'http://localhost:port',
|
|
@@ -155,6 +162,52 @@ export default {
|
|
|
155
162
|
};
|
|
156
163
|
```
|
|
157
164
|
|
|
165
|
+
### Using Environment Variables
|
|
166
|
+
|
|
167
|
+
You can pass custom environment variables to commands using the `env` property. This is useful for configuring ports, API endpoints, or any environment-specific settings:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
export default {
|
|
171
|
+
phases: [
|
|
172
|
+
{
|
|
173
|
+
name: 'playwright',
|
|
174
|
+
parallel: [
|
|
175
|
+
{
|
|
176
|
+
command: 'playwright_ci',
|
|
177
|
+
description: 'Run Playwright tests',
|
|
178
|
+
env: {
|
|
179
|
+
PLAYWRIGHT_PORT: 5173,
|
|
180
|
+
API_URL: 'http://localhost:3000',
|
|
181
|
+
TEST_ENV: 'ci'
|
|
182
|
+
},
|
|
183
|
+
status: 'enabled',
|
|
184
|
+
attempts: 1,
|
|
185
|
+
dependencies: [
|
|
186
|
+
{
|
|
187
|
+
command: 'dev',
|
|
188
|
+
background: true,
|
|
189
|
+
env: {
|
|
190
|
+
PORT: 5173
|
|
191
|
+
},
|
|
192
|
+
health_check: {
|
|
193
|
+
url: 'http://localhost:5173',
|
|
194
|
+
max_attempts: 20,
|
|
195
|
+
interval: 2000
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The command will run with the environment variables set, equivalent to:
|
|
207
|
+
```bash
|
|
208
|
+
PLAYWRIGHT_PORT=5173 API_URL=http://localhost:3000 TEST_ENV=ci npm run playwright_ci
|
|
209
|
+
```
|
|
210
|
+
|
|
158
211
|
See more examples [here](./docs/samples.md)
|
|
159
212
|
|
|
160
213
|
## Command Types
|
|
@@ -295,9 +348,25 @@ npm run scripts-orchestrator -- --phases "build,test,optional-e2e,optional-perfo
|
|
|
295
348
|
## Logging
|
|
296
349
|
|
|
297
350
|
- Each command's output is logged to `scripts-orchestrator-logs/<command>.log` in the current working directory
|
|
351
|
+
- Git commit hash is cached in `scripts-orchestrator-logs/.git-hash-cache` for skip detection
|
|
298
352
|
- Provides real-time status updates during execution
|
|
299
353
|
- Summarizes results at the end of execution
|
|
300
354
|
|
|
355
|
+
## Git-Based Caching
|
|
356
|
+
|
|
357
|
+
The orchestrator automatically tracks the git commit hash and repository state to optimize execution:
|
|
358
|
+
|
|
359
|
+
- **On first run**: Records the current git commit hash in `scripts-orchestrator-logs/.git-hash-cache`
|
|
360
|
+
- **On subsequent runs**: Checks if:
|
|
361
|
+
- The git commit hash matches the cached hash
|
|
362
|
+
- There are no staged or unstaged changes in the repository
|
|
363
|
+
- **When conditions are met**: Skips execution entirely with message `✓ Git state unchanged`
|
|
364
|
+
- **When conditions fail**: Runs normally and updates the cache on successful completion
|
|
365
|
+
|
|
366
|
+
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.
|
|
367
|
+
|
|
368
|
+
**Note**: The cache is only updated on successful execution. Failed runs will not update the cache, ensuring subsequent runs will retry.
|
|
369
|
+
|
|
301
370
|
## Exit Codes
|
|
302
371
|
|
|
303
372
|
- `0`: All commands executed successfully
|
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,78 @@ 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
|
+
writeToFile(message) {
|
|
70
|
+
if (this.logStream) {
|
|
71
|
+
// Strip ANSI color codes for file output
|
|
72
|
+
// eslint-disable-next-line no-control-regex
|
|
73
|
+
const cleanMessage = message.replace(/\x1b\[[0-9;]*m/g, '');
|
|
74
|
+
this.logStream.write(`${cleanMessage}\n`);
|
|
75
|
+
}
|
|
28
76
|
}
|
|
29
77
|
|
|
30
78
|
info(message) {
|
|
31
79
|
console.log(chalk.blue(`[INFO] ${message}`));
|
|
80
|
+
this.writeToFile(`[INFO] ${message}`);
|
|
32
81
|
}
|
|
33
82
|
|
|
34
83
|
success(message) {
|
|
35
84
|
console.log(chalk.green(`[SUCCESS] ${message}`));
|
|
85
|
+
this.writeToFile(`[SUCCESS] ${message}`);
|
|
36
86
|
}
|
|
37
87
|
|
|
38
88
|
error(message) {
|
|
39
89
|
console.error(chalk.red(`[ERROR] ${message}`));
|
|
90
|
+
this.writeToFile(`[ERROR] ${message}`);
|
|
40
91
|
}
|
|
41
92
|
|
|
42
93
|
warn(message) {
|
|
43
94
|
console.warn(chalk.yellow(`[WARN] ${message}`));
|
|
95
|
+
this.writeToFile(`[WARN] ${message}`);
|
|
44
96
|
}
|
|
45
97
|
|
|
46
98
|
verbose(message) {
|
|
47
99
|
if (this.isVerbose) {
|
|
48
100
|
console.log(chalk.gray(`[VERBOSE] ${message}`));
|
|
101
|
+
this.writeToFile(`[VERBOSE] ${message}`);
|
|
49
102
|
}
|
|
50
103
|
}
|
|
51
104
|
}
|
package/lib/orchestrator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
7
|
constructor(config, startPhase = null, logFolder = null, phases = null) {
|
|
@@ -14,6 +15,7 @@ export class Orchestrator {
|
|
|
14
15
|
this.failedCommands = [];
|
|
15
16
|
this.skippedCommands = [];
|
|
16
17
|
this.commandTimings = new Map();
|
|
18
|
+
this.gitCache = new GitCache(logFolder);
|
|
17
19
|
|
|
18
20
|
// Set the log folder in process manager
|
|
19
21
|
if (logFolder) {
|
|
@@ -62,6 +64,7 @@ export class Orchestrator {
|
|
|
62
64
|
process_tracking = false,
|
|
63
65
|
health_check,
|
|
64
66
|
kill_command,
|
|
67
|
+
env,
|
|
65
68
|
} = commandConfig;
|
|
66
69
|
|
|
67
70
|
const startTime = Date.now();
|
|
@@ -162,6 +165,7 @@ export class Orchestrator {
|
|
|
162
165
|
healthCheck: health_check,
|
|
163
166
|
kill_command,
|
|
164
167
|
isRetry: attempt > 1,
|
|
168
|
+
env,
|
|
165
169
|
});
|
|
166
170
|
commandOutput = output;
|
|
167
171
|
result = success;
|
|
@@ -235,6 +239,13 @@ export class Orchestrator {
|
|
|
235
239
|
|
|
236
240
|
async run() {
|
|
237
241
|
try {
|
|
242
|
+
// Check if we should skip execution based on git state
|
|
243
|
+
const shouldSkip = await this.gitCache.shouldSkipExecution();
|
|
244
|
+
if (shouldSkip) {
|
|
245
|
+
this.logger.success('🎉 No changes detected, skipping execution!');
|
|
246
|
+
process.exit(0);
|
|
247
|
+
}
|
|
248
|
+
|
|
238
249
|
let hasFailures = false;
|
|
239
250
|
let phaseFailed = false;
|
|
240
251
|
let startPhaseFound = false;
|
|
@@ -338,6 +349,11 @@ export class Orchestrator {
|
|
|
338
349
|
this.logger.error(`Cleanup failed: ${error.message}`);
|
|
339
350
|
}
|
|
340
351
|
|
|
352
|
+
// Update git cache on successful execution
|
|
353
|
+
if (!hasFailures) {
|
|
354
|
+
await this.gitCache.updateCache();
|
|
355
|
+
}
|
|
356
|
+
|
|
341
357
|
// Force exit with appropriate status
|
|
342
358
|
if (hasFailures) {
|
|
343
359
|
this.logger.info('Exiting with failure status...');
|
package/lib/process-manager.js
CHANGED
|
@@ -37,7 +37,7 @@ export class ProcessManager {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false }) {
|
|
40
|
+
async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false, env = null }) {
|
|
41
41
|
const baseDir = this.logFolder ? path.resolve(this.logFolder) : process.cwd();
|
|
42
42
|
const LOGS_DIR = path.join(baseDir, 'scripts-orchestrator-logs');
|
|
43
43
|
// Use only the first word of the command for the log filename
|
|
@@ -62,10 +62,17 @@ export class ProcessManager {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
return new Promise((resolve) => {
|
|
65
|
-
|
|
65
|
+
// Build command with environment variables if provided
|
|
66
|
+
let fullCommand = `npm run ${cmd}`;
|
|
67
|
+
if (env && Object.keys(env).length > 0) {
|
|
68
|
+
const envStr = Object.entries(env).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
69
|
+
fullCommand = `${envStr} npm run ${cmd}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.logger.info(`Running: ${fullCommand}`);
|
|
66
73
|
|
|
67
74
|
// Create isolated environment for each process
|
|
68
|
-
const isolatedEnv = this.createIsolatedEnvironment({ command: cmd });
|
|
75
|
+
const isolatedEnv = this.createIsolatedEnvironment({ command: cmd, env });
|
|
69
76
|
|
|
70
77
|
const options = {
|
|
71
78
|
shell: true,
|
|
@@ -80,8 +87,8 @@ export class ProcessManager {
|
|
|
80
87
|
//this.logger.verbose(`Process options: ${JSON.stringify(options, null, 2)}`);
|
|
81
88
|
|
|
82
89
|
try {
|
|
83
|
-
this.logger.verbose(`Spawning process with command:
|
|
84
|
-
const processInstance = spawn(
|
|
90
|
+
this.logger.verbose(`Spawning process with command: ${fullCommand}`);
|
|
91
|
+
const processInstance = spawn(fullCommand, [], options);
|
|
85
92
|
|
|
86
93
|
processInstance.on('error', (error) => {
|
|
87
94
|
this.logger.error(`Failed to start process: ${error.message}`);
|
|
@@ -235,7 +242,7 @@ export class ProcessManager {
|
|
|
235
242
|
});
|
|
236
243
|
}
|
|
237
244
|
|
|
238
|
-
createIsolatedEnvironment({ command }) {
|
|
245
|
+
createIsolatedEnvironment({ command, env = null }) {
|
|
239
246
|
// Create a deep copy to avoid any reference sharing
|
|
240
247
|
const baseEnv = JSON.parse(JSON.stringify(process.env));
|
|
241
248
|
|
|
@@ -255,6 +262,13 @@ export class ProcessManager {
|
|
|
255
262
|
npm_config_loglevel: 'error',
|
|
256
263
|
};
|
|
257
264
|
|
|
265
|
+
// Merge custom environment variables if provided
|
|
266
|
+
if (env && typeof env === 'object') {
|
|
267
|
+
Object.entries(env).forEach(([key, value]) => {
|
|
268
|
+
isolatedEnv[key] = String(value);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
258
272
|
// Remove any potentially problematic environment variables
|
|
259
273
|
delete isolatedEnv.npm_lifecycle_event;
|
|
260
274
|
delete isolatedEnv.npm_lifecycle_script;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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",
|