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 CHANGED
@@ -88,112 +88,48 @@ export default [
88
88
  ];
89
89
  ```
90
90
 
91
- ### Storybook Testing with Background Process
91
+ ### Basic Build and Test Pipeline with Phases
92
92
  ```javascript
93
- export default [
94
- {
95
- command: 'test-storybook',
96
- description: 'Run Storybook tests',
97
- status: 'enabled',
98
- attempts: 2,
99
- dependencies: [
100
- {
101
- command: 'storybook_silent',
102
- background: true,
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
- ### Playwright Testing with Development Server
116
- ```javascript
117
- export default [
118
- {
119
- command: 'playwright_ci',
120
- description: 'Run Playwright tests',
121
- status: 'enabled',
122
- attempts: 1,
123
- dependencies: [
124
- {
125
- command: 'dev',
126
- background: true,
127
- health_check: {
128
- url: 'http://localhost:5173',
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
+
@@ -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.config.forEach(({ command }) => {
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
- // Run top-level commands in parallel
198
- const tasks = this.config.map((commandConfig) =>
199
- this.executeCommand(commandConfig),
200
- );
213
+ let hasFailures = false;
201
214
 
202
- // Wait for all top-level commands to complete
203
- const results = await Promise.all(tasks);
204
-
205
- // Check if any command failed
206
- const hasFailures = results.some(result => !result) ||
207
- this.failedCommands.length > 0 ||
208
- this.skippedCommands.length > 0;
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));
@@ -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": "1.2.3",
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": "echo \"Error: no test specified\" && exit 1",
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
- "eslint": "^8.0.0"
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/",