scripts-orchestrator 1.2.3 ā 2.0.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 +49 -100
- package/lib/orchestrator.js +52 -12
- package/lib/process-manager.js +32 -4
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -88,112 +88,48 @@ export default [
|
|
|
88
88
|
];
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### Basic Build and Test Pipeline with Phases
|
|
92
92
|
```javascript
|
|
93
|
-
export default
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
wait: 5000,
|
|
104
|
-
health_check: {
|
|
105
|
-
url: 'http://localhost:6006',
|
|
106
|
-
max_attempts: 20,
|
|
107
|
-
interval: 2000
|
|
93
|
+
export default {
|
|
94
|
+
phases: [
|
|
95
|
+
{
|
|
96
|
+
name: 'build',
|
|
97
|
+
parallel: [
|
|
98
|
+
{
|
|
99
|
+
command: 'build',
|
|
100
|
+
description: 'Build the project',
|
|
101
|
+
status: 'enabled',
|
|
102
|
+
attempts: 1
|
|
108
103
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
max_attempts: 20,
|
|
130
|
-
interval: 2000
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'test',
|
|
108
|
+
parallel: [
|
|
109
|
+
{
|
|
110
|
+
command: 'test',
|
|
111
|
+
description: 'Run unit tests',
|
|
112
|
+
status: 'enabled',
|
|
113
|
+
attempts: 2,
|
|
114
|
+
should_retry: (output) => {
|
|
115
|
+
// Only retry if there are actual test failures
|
|
116
|
+
const testSummaryMatch = output.match(/Test Suites:.*?(\d+) failed/);
|
|
117
|
+
return testSummaryMatch && parseInt(testSummaryMatch[1]) > 0;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
command: 'lint',
|
|
122
|
+
description: 'Run lint checks',
|
|
123
|
+
status: 'enabled'
|
|
131
124
|
}
|
|
132
|
-
|
|
133
|
-
]
|
|
134
|
-
}
|
|
135
|
-
];
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Full CI Pipeline with Multiple Checks
|
|
139
|
-
```javascript
|
|
140
|
-
export default [
|
|
141
|
-
{
|
|
142
|
-
command: 'build',
|
|
143
|
-
description: 'Build the project',
|
|
144
|
-
status: 'enabled',
|
|
145
|
-
attempts: 1
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
command: 'test-ci',
|
|
149
|
-
description: 'Run unit tests',
|
|
150
|
-
status: 'enabled',
|
|
151
|
-
attempts: 2,
|
|
152
|
-
should_retry: (output) => {
|
|
153
|
-
const testSummaryMatch = output.match(/Test Suites:.*?(\d+) failed/);
|
|
154
|
-
const hasTestFailures = testSummaryMatch && parseInt(testSummaryMatch[1]) > 0;
|
|
155
|
-
const hasCoverageFailures = output.match(/Jest: "global" coverage threshold/);
|
|
156
|
-
|
|
157
|
-
// Only retry for actual test failures, not coverage issues
|
|
158
|
-
return hasTestFailures;
|
|
125
|
+
]
|
|
159
126
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
command: 'test-storybook',
|
|
163
|
-
description: 'Run Storybook tests',
|
|
164
|
-
status: 'enabled',
|
|
165
|
-
attempts: 2,
|
|
166
|
-
dependencies: [
|
|
167
|
-
{
|
|
168
|
-
command: 'storybook_silent',
|
|
169
|
-
background: true,
|
|
170
|
-
wait: 5000,
|
|
171
|
-
health_check: {
|
|
172
|
-
url: 'http://localhost:6006',
|
|
173
|
-
max_attempts: 20,
|
|
174
|
-
interval: 2000
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
]
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
command: 'stylelint',
|
|
181
|
-
description: 'Run stylelint checks',
|
|
182
|
-
status: 'enabled'
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
command: 'lint',
|
|
186
|
-
description: 'Run lint checks',
|
|
187
|
-
status: 'enabled'
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
command: 'jscpd',
|
|
191
|
-
description: 'Run code duplication checks',
|
|
192
|
-
status: 'enabled'
|
|
193
|
-
}
|
|
194
|
-
];
|
|
127
|
+
]
|
|
128
|
+
};
|
|
195
129
|
```
|
|
196
130
|
|
|
131
|
+
See more examples [here](./docs/samples.md)
|
|
132
|
+
|
|
197
133
|
## Command Types
|
|
198
134
|
|
|
199
135
|
The orchestrator is completely agnostic to what commands it runs. It can execute any npm scripts. Common use cases include:
|
|
@@ -248,6 +184,17 @@ The orchestrator doesn't care what the commands do - it just ensures they run (i
|
|
|
248
184
|
- `0`: All commands executed successfully
|
|
249
185
|
- `1`: One or more commands failed or were skipped
|
|
250
186
|
|
|
187
|
+
|
|
188
|
+
## History
|
|
189
|
+
See [versions](./docs/versions.md)
|
|
190
|
+
|
|
191
|
+
## Roadmap
|
|
192
|
+
- Better UX to indicate what is happening
|
|
193
|
+
- Tests to avoid regression
|
|
194
|
+
- Retry should append to the log file
|
|
195
|
+
- Run any shell command rather than assume the command is specified in package.json (? tentative)
|
|
196
|
+
|
|
197
|
+
|
|
251
198
|
## Disclaimer
|
|
252
199
|
|
|
253
200
|
This software is provided "as is", without warranty of any kind, express or implied. The author(s) shall not be liable for any claims, damages, or other liabilities arising from the use of this software. Users are responsible for testing and verifying the software in their own environment before using it in production.
|
|
@@ -258,3 +205,5 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
258
205
|
|
|
259
206
|
## License
|
|
260
207
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
208
|
+
|
|
209
|
+
|
package/lib/orchestrator.js
CHANGED
|
@@ -11,6 +11,22 @@ export class Orchestrator {
|
|
|
11
11
|
this.failedCommands = [];
|
|
12
12
|
this.skippedCommands = [];
|
|
13
13
|
this.commandTimings = new Map();
|
|
14
|
+
|
|
15
|
+
// Flatten commands for easier tracking
|
|
16
|
+
this.allCommands = this.flattenCommands(config);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
flattenCommands(config) {
|
|
20
|
+
// Handle both old array format and new phases format
|
|
21
|
+
if (Array.isArray(config)) {
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (config.phases) {
|
|
26
|
+
return config.phases.flatMap(phase => phase.parallel || []);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return [];
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
formatDuration(ms) {
|
|
@@ -170,7 +186,7 @@ export class Orchestrator {
|
|
|
170
186
|
this.logger.info('\nCommand Summary:');
|
|
171
187
|
let hasFailures = false;
|
|
172
188
|
|
|
173
|
-
this.
|
|
189
|
+
this.allCommands.forEach(({ command }) => {
|
|
174
190
|
const duration = this.commandTimings.get(command);
|
|
175
191
|
const durationStr = duration ? ` (${this.formatDuration(duration)})` : '';
|
|
176
192
|
|
|
@@ -194,18 +210,42 @@ export class Orchestrator {
|
|
|
194
210
|
|
|
195
211
|
async run() {
|
|
196
212
|
try {
|
|
197
|
-
|
|
198
|
-
const tasks = this.config.map((commandConfig) =>
|
|
199
|
-
this.executeCommand(commandConfig),
|
|
200
|
-
);
|
|
213
|
+
let hasFailures = false;
|
|
201
214
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
// Handle both old array format and new phases format
|
|
216
|
+
if (Array.isArray(this.config)) {
|
|
217
|
+
// Legacy: Run all commands in parallel
|
|
218
|
+
const tasks = this.config.map((commandConfig) =>
|
|
219
|
+
this.executeCommand(commandConfig),
|
|
220
|
+
);
|
|
221
|
+
const results = await Promise.all(tasks);
|
|
222
|
+
hasFailures = results.some(result => !result);
|
|
223
|
+
} else if (this.config.phases) {
|
|
224
|
+
// New: Run phases sequentially, commands within phases in parallel
|
|
225
|
+
for (const phase of this.config.phases) {
|
|
226
|
+
this.logger.info(`\nš Starting phase: ${phase.name}`);
|
|
227
|
+
|
|
228
|
+
const tasks = phase.parallel.map((commandConfig) =>
|
|
229
|
+
this.executeCommand(commandConfig),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const results = await Promise.all(tasks);
|
|
233
|
+
const phaseHasFailures = results.some(result => !result);
|
|
234
|
+
|
|
235
|
+
if (phaseHasFailures) {
|
|
236
|
+
hasFailures = true;
|
|
237
|
+
this.logger.error(`ā Phase "${phase.name}" completed with failures`);
|
|
238
|
+
break; // Stop executing remaining phases on failure
|
|
239
|
+
} else {
|
|
240
|
+
this.logger.success(`ā
Phase "${phase.name}" completed successfully`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check final status
|
|
246
|
+
hasFailures = hasFailures ||
|
|
247
|
+
this.failedCommands.length > 0 ||
|
|
248
|
+
this.skippedCommands.length > 0;
|
|
209
249
|
|
|
210
250
|
// Add a small delay to ensure all processes have finished
|
|
211
251
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
package/lib/process-manager.js
CHANGED
|
@@ -40,15 +40,16 @@ export class ProcessManager {
|
|
|
40
40
|
|
|
41
41
|
return new Promise((resolve) => {
|
|
42
42
|
this.logger.info(`Running: npm run ${cmd}`);
|
|
43
|
+
|
|
44
|
+
// Create isolated environment for each process
|
|
45
|
+
const isolatedEnv = this.createIsolatedEnvironment(cmd);
|
|
46
|
+
|
|
43
47
|
const options = {
|
|
44
48
|
shell: true,
|
|
45
49
|
detached: background,
|
|
46
50
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
47
51
|
cwd: process.cwd(),
|
|
48
|
-
env:
|
|
49
|
-
...process.env,
|
|
50
|
-
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
51
|
-
},
|
|
52
|
+
env: isolatedEnv,
|
|
52
53
|
windowsHide: true,
|
|
53
54
|
...(background ? { processGroup: true } : {}),
|
|
54
55
|
};
|
|
@@ -171,6 +172,33 @@ export class ProcessManager {
|
|
|
171
172
|
});
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
createIsolatedEnvironment(command) {
|
|
176
|
+
// Create a deep copy to avoid any reference sharing
|
|
177
|
+
const baseEnv = JSON.parse(JSON.stringify(process.env));
|
|
178
|
+
|
|
179
|
+
// Set standard environment variables
|
|
180
|
+
const isolatedEnv = {
|
|
181
|
+
...baseEnv,
|
|
182
|
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
183
|
+
// Add command-specific environment isolation
|
|
184
|
+
SCRIPTS_ORCHESTRATOR_COMMAND: command,
|
|
185
|
+
SCRIPTS_ORCHESTRATOR_PID: process.pid.toString(),
|
|
186
|
+
// Force fresh PATH to avoid any dynamic modifications
|
|
187
|
+
PATH: process.env.PATH,
|
|
188
|
+
// Ensure npm/node paths are isolated
|
|
189
|
+
npm_config_cache: path.join(process.cwd(), 'node_modules/.cache/npm'),
|
|
190
|
+
// Prevent npm from sharing config between parallel processes
|
|
191
|
+
npm_config_progress: 'false',
|
|
192
|
+
npm_config_loglevel: 'error',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Remove any potentially problematic environment variables
|
|
196
|
+
delete isolatedEnv.npm_lifecycle_event;
|
|
197
|
+
delete isolatedEnv.npm_lifecycle_script;
|
|
198
|
+
|
|
199
|
+
return isolatedEnv;
|
|
200
|
+
}
|
|
201
|
+
|
|
174
202
|
async cleanup() {
|
|
175
203
|
this.logger.info('\nCleaning up background processes...');
|
|
176
204
|
const killPromises = this.backgroundProcessesDetails.map(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"scripts-orchestrator": "index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
12
|
+
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --detectOpenHandles --runInBand",
|
|
12
13
|
"start": "node index.js",
|
|
13
14
|
"lint": "eslint .",
|
|
14
15
|
"prepare": "npm run lint"
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/Pratishthan/scripts-orchestrator"
|
|
33
|
+
"url": "git+https://github.com/Pratishthan/scripts-orchestrator.git"
|
|
33
34
|
},
|
|
34
35
|
"bugs": {
|
|
35
36
|
"url": "https://github.com/Pratishthan/scripts-orchestrator/issues"
|
|
@@ -39,10 +40,14 @@
|
|
|
39
40
|
"node": ">=14.0.0"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"chalk": "^4.1.2"
|
|
43
|
+
"chalk": "^4.1.2",
|
|
44
|
+
"yargs": "^17.7.2"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
|
-
"
|
|
47
|
+
"@types/jest": "^29.5.14",
|
|
48
|
+
"eslint": "^8.0.0",
|
|
49
|
+
"jest": "^29.7.0",
|
|
50
|
+
"ts-jest": "^29.3.3"
|
|
46
51
|
},
|
|
47
52
|
"files": [
|
|
48
53
|
"lib/",
|