scripts-orchestrator 1.2.1 → 1.2.2
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 +138 -0
- package/lib/orchestrator.js +29 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,144 @@ Create a configuration file (default: `scripts-orchestrator.config.js`) that def
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Example Configurations
|
|
60
|
+
|
|
61
|
+
Here are some practical examples of how to configure the orchestrator for different scenarios:
|
|
62
|
+
|
|
63
|
+
### Basic Build and Test Pipeline
|
|
64
|
+
```javascript
|
|
65
|
+
export default [
|
|
66
|
+
{
|
|
67
|
+
command: 'build',
|
|
68
|
+
description: 'Build the project',
|
|
69
|
+
status: 'enabled',
|
|
70
|
+
attempts: 1
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
command: 'test',
|
|
74
|
+
description: 'Run unit tests',
|
|
75
|
+
status: 'enabled',
|
|
76
|
+
attempts: 2,
|
|
77
|
+
should_retry: (output) => {
|
|
78
|
+
// Only retry if there are actual test failures
|
|
79
|
+
const testSummaryMatch = output.match(/Test Suites:.*?(\d+) failed/);
|
|
80
|
+
return testSummaryMatch && parseInt(testSummaryMatch[1]) > 0;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
command: 'lint',
|
|
85
|
+
description: 'Run lint checks',
|
|
86
|
+
status: 'enabled'
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Storybook Testing with Background Process
|
|
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
|
|
108
|
+
}
|
|
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
|
|
131
|
+
}
|
|
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;
|
|
159
|
+
}
|
|
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
|
+
];
|
|
195
|
+
```
|
|
196
|
+
|
|
59
197
|
## Command Types
|
|
60
198
|
|
|
61
199
|
The orchestrator is completely agnostic to what commands it runs. It can execute any npm scripts. Common use cases include:
|
package/lib/orchestrator.js
CHANGED
|
@@ -10,6 +10,18 @@ export class Orchestrator {
|
|
|
10
10
|
this.logger = log;
|
|
11
11
|
this.failedCommands = [];
|
|
12
12
|
this.skippedCommands = [];
|
|
13
|
+
this.commandTimings = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
formatDuration(ms) {
|
|
17
|
+
if (ms < 1000) return `${ms}ms`;
|
|
18
|
+
const seconds = Math.floor(ms / 1000);
|
|
19
|
+
const minutes = Math.floor(seconds / 60);
|
|
20
|
+
const remainingSeconds = seconds % 60;
|
|
21
|
+
if (minutes > 0) {
|
|
22
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
23
|
+
}
|
|
24
|
+
return `${seconds}s`;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
async executeCommand(commandConfig, visited = new Set()) {
|
|
@@ -26,12 +38,15 @@ export class Orchestrator {
|
|
|
26
38
|
health_check,
|
|
27
39
|
} = commandConfig;
|
|
28
40
|
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
|
|
29
43
|
// Check for circular dependencies
|
|
30
44
|
if (visited.has(command)) {
|
|
31
45
|
this.logger.error(
|
|
32
46
|
`Circular dependency detected: ${Array.from(visited).join(' -> ')} -> ${command}`,
|
|
33
47
|
);
|
|
34
48
|
this.failedCommands.push(command);
|
|
49
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
35
50
|
return false;
|
|
36
51
|
}
|
|
37
52
|
visited.add(command);
|
|
@@ -40,6 +55,7 @@ export class Orchestrator {
|
|
|
40
55
|
if (status === 'disabled') {
|
|
41
56
|
this.logger.warn(`Skipping: npm run ${command} (status: disabled)`);
|
|
42
57
|
this.skippedCommands.push(command);
|
|
58
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
43
59
|
visited.delete(command);
|
|
44
60
|
return true;
|
|
45
61
|
}
|
|
@@ -56,6 +72,7 @@ export class Orchestrator {
|
|
|
56
72
|
startedByScript: false,
|
|
57
73
|
process_tracking,
|
|
58
74
|
});
|
|
75
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
59
76
|
visited.delete(command);
|
|
60
77
|
return true;
|
|
61
78
|
}
|
|
@@ -67,6 +84,7 @@ export class Orchestrator {
|
|
|
67
84
|
if (!dependencySuccess) {
|
|
68
85
|
this.logger.error(`Skipping ${command} due to failed dependency`);
|
|
69
86
|
this.skippedCommands.push(command);
|
|
87
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
70
88
|
visited.delete(command);
|
|
71
89
|
return false;
|
|
72
90
|
}
|
|
@@ -83,6 +101,7 @@ export class Orchestrator {
|
|
|
83
101
|
`URL ${dependency.health_check.url} is not available after maximum attempts`,
|
|
84
102
|
);
|
|
85
103
|
this.skippedCommands.push(command);
|
|
104
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
86
105
|
visited.delete(command);
|
|
87
106
|
return false;
|
|
88
107
|
}
|
|
@@ -124,12 +143,16 @@ export class Orchestrator {
|
|
|
124
143
|
this.logger.warn(
|
|
125
144
|
`${command} failed but doesn't meet retry criteria. Skipping retry.`,
|
|
126
145
|
);
|
|
146
|
+
this.failedCommands.push(command);
|
|
127
147
|
break;
|
|
128
148
|
}
|
|
129
149
|
this.logger.error(`Attempt ${attempt}/${attempts} failed for ${command}`);
|
|
150
|
+
} else {
|
|
151
|
+
this.failedCommands.push(command);
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
154
|
|
|
155
|
+
this.commandTimings.set(command, Date.now() - startTime);
|
|
133
156
|
visited.delete(command);
|
|
134
157
|
return result;
|
|
135
158
|
}
|
|
@@ -137,12 +160,15 @@ export class Orchestrator {
|
|
|
137
160
|
summarizeResults() {
|
|
138
161
|
this.logger.info('\nCommand Summary:');
|
|
139
162
|
this.config.forEach(({ command }) => {
|
|
163
|
+
const duration = this.commandTimings.get(command);
|
|
164
|
+
const durationStr = duration ? ` (${this.formatDuration(duration)})` : '';
|
|
165
|
+
|
|
140
166
|
if (this.failedCommands.includes(command)) {
|
|
141
|
-
this.logger.error(`- ${command}:
|
|
167
|
+
this.logger.error(`- ${command}: ❌${durationStr} (See logs/scripts-orchestrator_${command}.log)`);
|
|
142
168
|
} else if (this.skippedCommands.includes(command)) {
|
|
143
|
-
this.logger.warn(`- ${command}:
|
|
169
|
+
this.logger.warn(`- ${command}: ⚠️${durationStr} (Skipped due to failed dependency)`);
|
|
144
170
|
} else {
|
|
145
|
-
this.logger.success(`- ${command}:
|
|
171
|
+
this.logger.success(`- ${command}: ✅${durationStr}`);
|
|
146
172
|
}
|
|
147
173
|
});
|
|
148
174
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scripts-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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",
|