scripts-orchestrator 1.2.3 → 2.1.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 +57 -107
- package/lib/orchestrator.js +60 -16
- package/lib/process-manager.js +72 -9
- package/package.json +10 -5
- package/scripts-orchestrator.config.js +110 -78
package/README.md
CHANGED
|
@@ -39,13 +39,14 @@ Create a configuration file (default: `scripts-orchestrator.config.js`) that def
|
|
|
39
39
|
|
|
40
40
|
```javascript
|
|
41
41
|
{
|
|
42
|
-
command: 'command_name',
|
|
43
|
-
description: 'Description',
|
|
44
|
-
status: 'enabled',
|
|
45
|
-
attempts: 1,
|
|
46
|
-
dependencies: [],
|
|
47
|
-
background: false,
|
|
48
|
-
|
|
42
|
+
command: 'command_name', // The npm script to run
|
|
43
|
+
description: 'Description', // Optional description
|
|
44
|
+
status: 'enabled', // 'enabled' or 'disabled'
|
|
45
|
+
attempts: 1, // Number of retry attempts
|
|
46
|
+
dependencies: [], // Array of dependent commands
|
|
47
|
+
background: false, // Whether to run in background
|
|
48
|
+
kill_command: 'kill_storybook', // Optional kill command to kill the process
|
|
49
|
+
health_check: { // Health check configuration
|
|
49
50
|
url: 'http://localhost:port',
|
|
50
51
|
max_attempts: 20,
|
|
51
52
|
interval: 2000
|
|
@@ -88,112 +89,48 @@ export default [
|
|
|
88
89
|
];
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
###
|
|
92
|
+
### Basic Build and Test Pipeline with Phases
|
|
92
93
|
```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
|
|
94
|
+
export default {
|
|
95
|
+
phases: [
|
|
96
|
+
{
|
|
97
|
+
name: 'build',
|
|
98
|
+
parallel: [
|
|
99
|
+
{
|
|
100
|
+
command: 'build',
|
|
101
|
+
description: 'Build the project',
|
|
102
|
+
status: 'enabled',
|
|
103
|
+
attempts: 1
|
|
108
104
|
}
|
|
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
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'test',
|
|
109
|
+
parallel: [
|
|
110
|
+
{
|
|
111
|
+
command: 'test',
|
|
112
|
+
description: 'Run unit tests',
|
|
113
|
+
status: 'enabled',
|
|
114
|
+
attempts: 2,
|
|
115
|
+
should_retry: (output) => {
|
|
116
|
+
// Only retry if there are actual test failures
|
|
117
|
+
const testSummaryMatch = output.match(/Test Suites:.*?(\d+) failed/);
|
|
118
|
+
return testSummaryMatch && parseInt(testSummaryMatch[1]) > 0;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
command: 'lint',
|
|
123
|
+
description: 'Run lint checks',
|
|
124
|
+
status: 'enabled'
|
|
131
125
|
}
|
|
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;
|
|
126
|
+
]
|
|
159
127
|
}
|
|
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
|
-
];
|
|
128
|
+
]
|
|
129
|
+
};
|
|
195
130
|
```
|
|
196
131
|
|
|
132
|
+
See more examples [here](./docs/samples.md)
|
|
133
|
+
|
|
197
134
|
## Command Types
|
|
198
135
|
|
|
199
136
|
The orchestrator is completely agnostic to what commands it runs. It can execute any npm scripts. Common use cases include:
|
|
@@ -248,6 +185,17 @@ The orchestrator doesn't care what the commands do - it just ensures they run (i
|
|
|
248
185
|
- `0`: All commands executed successfully
|
|
249
186
|
- `1`: One or more commands failed or were skipped
|
|
250
187
|
|
|
188
|
+
|
|
189
|
+
## History
|
|
190
|
+
See [versions](./docs/versions.md)
|
|
191
|
+
|
|
192
|
+
## Roadmap
|
|
193
|
+
- Better UX to indicate what is happening
|
|
194
|
+
- Tests to avoid regression
|
|
195
|
+
- Retry should append to the log file
|
|
196
|
+
- Run any shell command rather than assume the command is specified in package.json (? tentative)
|
|
197
|
+
|
|
198
|
+
|
|
251
199
|
## Disclaimer
|
|
252
200
|
|
|
253
201
|
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 +206,5 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
258
206
|
|
|
259
207
|
## License
|
|
260
208
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
209
|
+
|
|
210
|
+
|
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) {
|
|
@@ -36,6 +52,7 @@ export class Orchestrator {
|
|
|
36
52
|
should_retry,
|
|
37
53
|
process_tracking = false,
|
|
38
54
|
health_check,
|
|
55
|
+
kill_command,
|
|
39
56
|
} = commandConfig;
|
|
40
57
|
|
|
41
58
|
const startTime = Date.now();
|
|
@@ -71,6 +88,7 @@ export class Orchestrator {
|
|
|
71
88
|
url: checkUrl,
|
|
72
89
|
startedByScript: false,
|
|
73
90
|
process_tracking,
|
|
91
|
+
kill_command,
|
|
74
92
|
});
|
|
75
93
|
this.commandTimings.set(command, Date.now() - startTime);
|
|
76
94
|
visited.delete(command);
|
|
@@ -128,12 +146,14 @@ export class Orchestrator {
|
|
|
128
146
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
const { success, output } = await this.processManager.runCommand(
|
|
132
|
-
attempt === 1 ? command : retry_command || command,
|
|
149
|
+
const { success, output } = await this.processManager.runCommand({
|
|
150
|
+
cmd: attempt === 1 ? command : retry_command || command,
|
|
133
151
|
logFile,
|
|
134
152
|
background,
|
|
135
|
-
health_check,
|
|
136
|
-
|
|
153
|
+
healthCheck: health_check,
|
|
154
|
+
kill_command,
|
|
155
|
+
isRetry: attempt > 1,
|
|
156
|
+
});
|
|
137
157
|
commandOutput = output;
|
|
138
158
|
result = success;
|
|
139
159
|
|
|
@@ -170,7 +190,7 @@ export class Orchestrator {
|
|
|
170
190
|
this.logger.info('\nCommand Summary:');
|
|
171
191
|
let hasFailures = false;
|
|
172
192
|
|
|
173
|
-
this.
|
|
193
|
+
this.allCommands.forEach(({ command }) => {
|
|
174
194
|
const duration = this.commandTimings.get(command);
|
|
175
195
|
const durationStr = duration ? ` (${this.formatDuration(duration)})` : '';
|
|
176
196
|
|
|
@@ -194,18 +214,42 @@ export class Orchestrator {
|
|
|
194
214
|
|
|
195
215
|
async run() {
|
|
196
216
|
try {
|
|
197
|
-
|
|
198
|
-
const tasks = this.config.map((commandConfig) =>
|
|
199
|
-
this.executeCommand(commandConfig),
|
|
200
|
-
);
|
|
217
|
+
let hasFailures = false;
|
|
201
218
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
// Handle both old array format and new phases format
|
|
220
|
+
if (Array.isArray(this.config)) {
|
|
221
|
+
// Legacy: Run all commands in parallel
|
|
222
|
+
const tasks = this.config.map((commandConfig) =>
|
|
223
|
+
this.executeCommand(commandConfig),
|
|
224
|
+
);
|
|
225
|
+
const results = await Promise.all(tasks);
|
|
226
|
+
hasFailures = results.some(result => !result);
|
|
227
|
+
} else if (this.config.phases) {
|
|
228
|
+
// New: Run phases sequentially, commands within phases in parallel
|
|
229
|
+
for (const phase of this.config.phases) {
|
|
230
|
+
this.logger.info(`\n🔄 Starting phase: ${phase.name}`);
|
|
231
|
+
|
|
232
|
+
const tasks = phase.parallel.map((commandConfig) =>
|
|
233
|
+
this.executeCommand(commandConfig),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const results = await Promise.all(tasks);
|
|
237
|
+
const phaseHasFailures = results.some(result => !result);
|
|
238
|
+
|
|
239
|
+
if (phaseHasFailures) {
|
|
240
|
+
hasFailures = true;
|
|
241
|
+
this.logger.error(`❌ Phase "${phase.name}" completed with failures`);
|
|
242
|
+
break; // Stop executing remaining phases on failure
|
|
243
|
+
} else {
|
|
244
|
+
this.logger.success(`✅ Phase "${phase.name}" completed successfully`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check final status
|
|
250
|
+
hasFailures = hasFailures ||
|
|
251
|
+
this.failedCommands.length > 0 ||
|
|
252
|
+
this.skippedCommands.length > 0;
|
|
209
253
|
|
|
210
254
|
// Add a small delay to ensure all processes have finished
|
|
211
255
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
package/lib/process-manager.js
CHANGED
|
@@ -11,17 +11,18 @@ export class ProcessManager {
|
|
|
11
11
|
this.backgroundProcessesDetails = [];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
addBackgroundProcess({ command, url, startedByScript, process_tracking }) {
|
|
14
|
+
addBackgroundProcess({ command, url, startedByScript, process_tracking, kill_command }) {
|
|
15
15
|
this.logger.verbose(`Adding background process: ${command} (${url})`);
|
|
16
16
|
this.backgroundProcessesDetails.push({
|
|
17
17
|
command,
|
|
18
18
|
url,
|
|
19
19
|
startedByScript,
|
|
20
20
|
process_tracking,
|
|
21
|
+
kill_command,
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async runCommand(cmd, logFile, background = false, healthCheck = null) {
|
|
25
|
+
async runCommand({ cmd, logFile, background = false, healthCheck = null, kill_command = null, isRetry = false }) {
|
|
25
26
|
const LOGS_DIR = path.resolve(process.cwd(), 'scripts-orchestrator-logs');
|
|
26
27
|
const LOG_FILE = logFile || path.join(LOGS_DIR, `${cmd}.log`);
|
|
27
28
|
|
|
@@ -31,8 +32,12 @@ export class ProcessManager {
|
|
|
31
32
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (!isRetry) {
|
|
36
|
+
this.logger.verbose(`Clearing log file at ${LOG_FILE}`);
|
|
37
|
+
fs.writeFileSync(LOG_FILE, ''); // Clear the log file
|
|
38
|
+
} else {
|
|
39
|
+
this.logger.verbose(`Appending to existing log file at ${LOG_FILE} (retry attempt)`);
|
|
40
|
+
}
|
|
36
41
|
} catch (error) {
|
|
37
42
|
this.logger.error(`Failed to setup log file: ${error.message}`);
|
|
38
43
|
return Promise.resolve({ success: false, output: '' });
|
|
@@ -40,15 +45,16 @@ export class ProcessManager {
|
|
|
40
45
|
|
|
41
46
|
return new Promise((resolve) => {
|
|
42
47
|
this.logger.info(`Running: npm run ${cmd}`);
|
|
48
|
+
|
|
49
|
+
// Create isolated environment for each process
|
|
50
|
+
const isolatedEnv = this.createIsolatedEnvironment({ command: cmd });
|
|
51
|
+
|
|
43
52
|
const options = {
|
|
44
53
|
shell: true,
|
|
45
54
|
detached: background,
|
|
46
55
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
47
56
|
cwd: process.cwd(),
|
|
48
|
-
env:
|
|
49
|
-
...process.env,
|
|
50
|
-
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
51
|
-
},
|
|
57
|
+
env: isolatedEnv,
|
|
52
58
|
windowsHide: true,
|
|
53
59
|
...(background ? { processGroup: true } : {}),
|
|
54
60
|
};
|
|
@@ -102,6 +108,7 @@ export class ProcessManager {
|
|
|
102
108
|
startTime: Date.now(),
|
|
103
109
|
url: healthCheck?.url,
|
|
104
110
|
startedByScript: true,
|
|
111
|
+
kill_command,
|
|
105
112
|
});
|
|
106
113
|
|
|
107
114
|
this.logger.verbose(`Unreferencing process ${processGroupId}`);
|
|
@@ -171,10 +178,46 @@ export class ProcessManager {
|
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
|
|
181
|
+
createIsolatedEnvironment({ command }) {
|
|
182
|
+
// Create a deep copy to avoid any reference sharing
|
|
183
|
+
const baseEnv = JSON.parse(JSON.stringify(process.env));
|
|
184
|
+
|
|
185
|
+
// Set standard environment variables
|
|
186
|
+
const isolatedEnv = {
|
|
187
|
+
...baseEnv,
|
|
188
|
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
189
|
+
// Add command-specific environment isolation
|
|
190
|
+
SCRIPTS_ORCHESTRATOR_COMMAND: command,
|
|
191
|
+
SCRIPTS_ORCHESTRATOR_PID: process.pid.toString(),
|
|
192
|
+
// Force fresh PATH to avoid any dynamic modifications
|
|
193
|
+
PATH: process.env.PATH,
|
|
194
|
+
// Ensure npm/node paths are isolated
|
|
195
|
+
npm_config_cache: path.join(process.cwd(), 'node_modules/.cache/npm'),
|
|
196
|
+
// Prevent npm from sharing config between parallel processes
|
|
197
|
+
npm_config_progress: 'false',
|
|
198
|
+
npm_config_loglevel: 'error',
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Remove any potentially problematic environment variables
|
|
202
|
+
delete isolatedEnv.npm_lifecycle_event;
|
|
203
|
+
delete isolatedEnv.npm_lifecycle_script;
|
|
204
|
+
|
|
205
|
+
return isolatedEnv;
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
async cleanup() {
|
|
175
209
|
this.logger.info('\nCleaning up background processes...');
|
|
210
|
+
|
|
211
|
+
// Debug: Log the number of processes we're tracking
|
|
212
|
+
this.logger.info(`- Found ${this.backgroundProcessesDetails.length} background processes to clean up`);
|
|
213
|
+
|
|
214
|
+
// Debug: Log each process details
|
|
215
|
+
this.backgroundProcessesDetails.forEach(({ command, pgid, url, startedByScript, kill_command }, index) => {
|
|
216
|
+
this.logger.verbose(`- Process ${index + 1}: command=${command}, pgid=${pgid}, url=${url}, startedByScript=${startedByScript}, kill_command=${kill_command}`);
|
|
217
|
+
});
|
|
218
|
+
|
|
176
219
|
const killPromises = this.backgroundProcessesDetails.map(
|
|
177
|
-
async ({ command, pgid, url, startedByScript }) => {
|
|
220
|
+
async ({ command, pgid, url, startedByScript, kill_command }) => {
|
|
178
221
|
if (!startedByScript) {
|
|
179
222
|
this.logger.verbose(
|
|
180
223
|
`- Skipping cleanup for ${command} (${url}) as it was not started by this script`,
|
|
@@ -182,6 +225,26 @@ export class ProcessManager {
|
|
|
182
225
|
return;
|
|
183
226
|
}
|
|
184
227
|
|
|
228
|
+
this.logger.verbose(`- Processing cleanup for ${command} (kill_command: ${kill_command})`);
|
|
229
|
+
|
|
230
|
+
// Try custom kill command first if specified
|
|
231
|
+
if (kill_command) {
|
|
232
|
+
try {
|
|
233
|
+
this.logger.verbose(`- Using custom kill command: npm run ${kill_command}`);
|
|
234
|
+
const result = await this.runCommand({ cmd: kill_command, logFile: null, background: false });
|
|
235
|
+
if (result.success) {
|
|
236
|
+
this.logger.verbose(`- Successfully killed ${command} using custom command`);
|
|
237
|
+
return;
|
|
238
|
+
} else {
|
|
239
|
+
this.logger.verbose('- Custom kill command failed, falling back to process signals');
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.verbose(`- Custom kill command error: ${error.message}, falling back`);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
this.logger.verbose(`- No kill_command specified for ${command}, using process signals`);
|
|
246
|
+
}
|
|
247
|
+
|
|
185
248
|
try {
|
|
186
249
|
// First try to kill the process group
|
|
187
250
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.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/",
|
|
@@ -1,83 +1,115 @@
|
|
|
1
|
-
export default
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
export default {
|
|
2
|
+
phases: [
|
|
3
|
+
{
|
|
4
|
+
name: 'build',
|
|
5
|
+
parallel: [
|
|
6
|
+
{
|
|
7
|
+
command: 'build',
|
|
8
|
+
description: 'Build the project',
|
|
9
|
+
status: 'enabled',
|
|
10
|
+
attempts: 1,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
command: 'stylelint',
|
|
14
|
+
description: 'Run stylelint checks',
|
|
15
|
+
status: 'enabled',
|
|
16
|
+
},
|
|
17
|
+
{ command: 'lint', description: 'Run lint checks', status: 'enabled' },
|
|
18
|
+
{
|
|
19
|
+
command: 'jscpd',
|
|
20
|
+
description: 'Run code duplication checks',
|
|
21
|
+
status: 'enabled',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'storybook tests',
|
|
27
|
+
parallel: [
|
|
28
|
+
{
|
|
29
|
+
command: 'test-storybook',
|
|
30
|
+
description: 'Run Storybook tests',
|
|
31
|
+
status: 'enabled',
|
|
32
|
+
attempts: 2,
|
|
33
|
+
dependencies: [
|
|
34
|
+
{
|
|
35
|
+
command: 'storybook_silent',
|
|
36
|
+
background: true,
|
|
37
|
+
wait: 5000,
|
|
38
|
+
kill_command: 'kill_storybook',
|
|
39
|
+
dependencies: [],
|
|
40
|
+
// Add process tracking
|
|
41
|
+
process_tracking: true,
|
|
42
|
+
// Add health check
|
|
43
|
+
health_check: {
|
|
44
|
+
url: 'http://localhost:6006',
|
|
45
|
+
max_attempts: 20,
|
|
46
|
+
interval: 2000,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'unit tests',
|
|
55
|
+
parallel: [
|
|
56
|
+
{
|
|
57
|
+
command: 'test-ci',
|
|
58
|
+
description: 'Run unit tests',
|
|
59
|
+
status: 'enabled',
|
|
60
|
+
attempts: 2,
|
|
61
|
+
should_retry: (output) => {
|
|
62
|
+
// Check for test failures in both formats
|
|
63
|
+
const testSuiteFailureMatch = output.match(
|
|
64
|
+
/Test Suites:.*?\(\d+\) failed/,
|
|
65
|
+
);
|
|
66
|
+
const individualTestFailureMatch =
|
|
67
|
+
output.match(/✘\s*(\d+)\s*failing/);
|
|
68
|
+
|
|
69
|
+
const hasTestSuiteFailures =
|
|
70
|
+
testSuiteFailureMatch && parseInt(testSuiteFailureMatch[1]) > 0;
|
|
71
|
+
const hasIndividualTestFailures =
|
|
72
|
+
individualTestFailureMatch &&
|
|
73
|
+
parseInt(individualTestFailureMatch[1]) > 0;
|
|
74
|
+
|
|
75
|
+
const hasTestFailures =
|
|
76
|
+
hasTestSuiteFailures || hasIndividualTestFailures;
|
|
18
77
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
// Check for "Test suite failed to run" in logs
|
|
79
|
+
if (output.includes('Test suite failed to run')) {
|
|
80
|
+
console.error('Certain tests could not be run');
|
|
81
|
+
return false; // Don't retry if certain tests could not be run
|
|
82
|
+
}
|
|
24
83
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
84
|
+
if (!hasTestFailures) {
|
|
85
|
+
console.log(
|
|
86
|
+
'Tests have passed but coverage thresholds have not been met',
|
|
87
|
+
);
|
|
88
|
+
return false; // Don't retry if only coverage failed
|
|
89
|
+
}
|
|
31
90
|
|
|
32
|
-
|
|
91
|
+
return hasTestFailures; // Only retry if there are actual test failures
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
33
95
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Add health check
|
|
50
|
-
health_check: {
|
|
51
|
-
url: 'http://localhost:6006',
|
|
52
|
-
max_attempts: 20,
|
|
53
|
-
interval: 2000,
|
|
96
|
+
{
|
|
97
|
+
name: 'playwright',
|
|
98
|
+
parallel: [
|
|
99
|
+
{
|
|
100
|
+
command: 'playwright_ci',
|
|
101
|
+
description: 'Run Playwright tests',
|
|
102
|
+
status: 'enabled',
|
|
103
|
+
attempts: 1, //Playwright internally retries in CI mode
|
|
104
|
+
dependencies: [
|
|
105
|
+
{
|
|
106
|
+
command: 'dev',
|
|
107
|
+
background: true,
|
|
108
|
+
url: 'http://localhost:5173',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
54
111
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
command: 'stylelint',
|
|
60
|
-
description: 'Run stylelint checks',
|
|
61
|
-
status: 'enabled',
|
|
62
|
-
},
|
|
63
|
-
{ command: 'lint', description: 'Run lint checks', status: 'enabled' },
|
|
64
|
-
{
|
|
65
|
-
command: 'jscpd',
|
|
66
|
-
description: 'Run code duplication checks',
|
|
67
|
-
status: 'enabled',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
command: 'playwright_ci',
|
|
71
|
-
description: 'Run Playwright tests',
|
|
72
|
-
status: 'enabled',
|
|
73
|
-
attempts: 1, //Playwright internally retries in CI mode
|
|
74
|
-
dependencies: [
|
|
75
|
-
{
|
|
76
|
-
command: 'dev',
|
|
77
|
-
background: true,
|
|
78
|
-
url: 'http://localhost:5173',
|
|
79
|
-
kill: 'application_end',
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
];
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|