-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathbuild-parallel.js
More file actions
291 lines (263 loc) · 10.1 KB
/
build-parallel.js
File metadata and controls
291 lines (263 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/usr/bin/env node
const { spawn } = require('child_process');
const path = require('path');
const minimist = require('minimist');
const fs = require('fs');
/**
* Convert task glob pattern to pnpm workspace filters using path-based approach
* @param {string} taskPattern - Task pattern (e.g., "@(TaskA|TaskB|TaskC)" or "TaskName")
* @returns {string[]} - Array of --filter arguments for pnpm
*/
function convertTaskPatternToWorkspaceFilters(taskPattern) {
if (!taskPattern) {
return [];
}
// Handle @(task1|task2|task3) pattern - convert to single filter with path pattern
if (taskPattern.startsWith('@(') && taskPattern.endsWith(')')) {
const tasksString = taskPattern.slice(2, -1); // Remove @( and )
const tasks = tasksString.split('|').map(task => task.trim());
if (tasks.length === 0) {
return [];
} else if (tasks.length === 1) {
return ['--filter', `"./Tasks/${tasks[0]}"`];
} else {
// Create a single filter pattern that matches multiple task paths
const pathPattern = `./Tasks/{${tasks.join(',')}}`;
return ['--filter', `"${pathPattern}"`];
}
}
// Handle single task or other patterns - use path-based filter
return ['--filter', `"./Tasks/${taskPattern}"`];
}
/**
* Convert parsed arguments to workspace filter arguments for pnpm
* @param {object} argv - Parsed minimist arguments
* @returns {string[]} - Converted arguments for pnpm
*/
function convertArgsToWorkspaceArgs(argv) {
const convertedArgs = [];
// Handle task parameter
if (argv.task) {
const taskFilters = convertTaskPatternToWorkspaceFilters(argv.task);
convertedArgs.push(...taskFilters);
}
// Handle other arguments (excluding task and the command)
Object.keys(argv).forEach(key => {
if (key !== 'task' && key !== '_') {
const value = argv[key];
if (typeof value === 'boolean' && value) {
convertedArgs.push(`--${key}`);
} else if (typeof value !== 'boolean') {
convertedArgs.push(`--${key}=${value}`);
}
}
});
return convertedArgs;
}
/**
* Executes a command with arguments and returns a promise
* @param {string} command - The command to execute
* @param {string[]} args - Array of arguments
* @param {object} options - Spawn options
* @returns {Promise<number>} - Exit code
*/
function executeCommand(command, args, options = {}) {
return new Promise((resolve, reject) => {
// For shell commands, we need to properly quote arguments that contain special characters
const quotedArgs = args.map(arg => {
// Quote arguments that contain pipes, parentheses, or spaces
if (arg.includes('|') || arg.includes('(') || arg.includes(')') || arg.includes(' ')) {
return `"${arg}"`;
}
return arg;
});
const child = spawn(command, quotedArgs, {
stdio: 'inherit',
shell: true,
...options
});
child.on('close', (code) => {
resolve(code);
});
child.on('error', (error) => {
reject(error);
});
});
}
/**
* Run parallel build command
* @param {string[]} additionalArgs - Additional arguments to pass to the commands
*/
async function runBuild(additionalArgs = []) {
// Parse arguments using minimist (same as make.js)
const argv = minimist(additionalArgs);
failIfTasksMissing(argv);
// First run pre-build steps
console.log('Running pre-build steps...');
try {
const preBuildArgs = ['make.js', 'build', '--onlyPreBuildSteps', '--enableConcurrentTaskBuild', ...additionalArgs];
const preBuildExitCode = await executeCommand('node', preBuildArgs);
if (preBuildExitCode !== 0) {
console.error('Pre-build steps failed');
process.exit(preBuildExitCode);
}
} catch (error) {
console.error('Error running pre-build steps:', error.message);
process.exit(1);
}
// Then run parallel build
const pnpmPath = path.join(__dirname, 'node_modules', '.bin', 'pnpm');
const workspaceArgs = convertArgsToWorkspaceArgs(argv);
const args = [
'-r',
'--report-summary',
'--aggregate-output',
'--reporter=append-only',
'--workspace-concurrency=2',
...workspaceArgs, // Move workspace filters before 'run'
'run',
'build',
'--skipPrebuildSteps',
'--enableConcurrentTaskBuild'
];
console.log('Running parallel build...');
console.log('pnpm command:', 'pnpm', args.join(' '));
try {
const exitCode = await executeCommand(pnpmPath, args);
printBuildSummary();
process.exit(exitCode);
} catch (error) {
console.error('Error running build:', error.message);
process.exit(1);
}
}
/**
* Run parallel server build command
* @param {string[]} additionalArgs - Additional arguments to pass to the commands
*/
async function runServerBuild(additionalArgs = []) {
// Parse arguments using minimist (same as make.js)
const argv = minimist(additionalArgs);
failIfTasksMissing(argv);
// First run pre-build steps
console.log('Running pre-build steps for server build...');
try {
const preBuildArgs = ['make.js', 'build', '--onlyPreBuildSteps', '--enableConcurrentTaskBuild', ...additionalArgs];
const preBuildExitCode = await executeCommand('node', preBuildArgs);
if (preBuildExitCode !== 0) {
console.error('Pre-build steps failed for server build');
process.exit(preBuildExitCode);
}
} catch (error) {
console.error('Error running pre-build steps for server build:', error.message);
process.exit(1);
}
// Then run parallel server build
const pnpmPath = path.join(__dirname, 'node_modules', '.bin', 'pnpm');
const workspaceArgs = convertArgsToWorkspaceArgs(argv);
const args = [
'-r',
'--report-summary',
'--aggregate-output',
'--reporter=append-only',
'--workspace-concurrency=2',
...workspaceArgs, // Move workspace filters before 'run'
'run',
'serverBuild',
'--skipPrebuildSteps',
'--enableConcurrentTaskBuild'
];
console.log('Running parallel server build...');
console.log('pnpm command:', 'pnpm', args.join(' '));
try {
const exitCode = await executeCommand(pnpmPath, args);
printBuildSummary();
process.exit(exitCode);
} catch (error) {
console.error('Error running server build:', error.message);
process.exit(1);
}
}
// Parse command line arguments
const command = process.argv[2];
const additionalArgs = process.argv.slice(3); // Capture all arguments after the command
switch (command) {
case 'build':
runBuild(additionalArgs);
break;
case 'serverBuild':
runServerBuild(additionalArgs);
break;
default:
console.log('Usage: node build-parallel.js [build|serverBuild] [additional arguments...]');
console.log('Commands:');
console.log(' build - Run parallel build');
console.log(' serverBuild - Run parallel server build');
console.log('');
console.log('Examples:');
console.log(' node build-parallel.js build --task "MyTask"');
console.log(' node build-parallel.js serverBuild --task "@(TaskA|TaskB|TaskC)"');
console.log('');
console.log('Note: Task patterns are converted to path-based --filter arguments for pnpm workspace filtering');
console.log(' Multiple tasks use a single filter pattern:');
console.log(' - Glob pattern: --task "@(Task1|Task2|Task3)" → --filter "./Tasks/{Task1,Task2,Task3}"');
console.log(' - Single task: --task "Task1" → --filter "./Tasks/Task1"');
process.exit(1);
}
// Print build summary
const printBuildSummary = () => {
const summaryPath = path.join(__dirname, 'pnpm-exec-summary.json');
if (!fs.existsSync(summaryPath)) {
console.log('No build summary found.');
return;
}
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
const execStatus = summary.executionStatus || {};
let total = 0, success = 0, failed = 0, skipped = 0, running = 0;
for (const task in execStatus) {
total++;
const status = execStatus[task].status;
if (status === 'passed') success++;
else if (status === 'failure') failed++;
else if (status === 'running') running++;
else skipped++;
}
console.log('');
console.log('📊 BUILD SUMMARY');
console.log('================================================================================');
console.log(` Total tasks built: ${total}`);
console.log(` ✅ Successful: ${success}`);
console.log(` ❌ Failed: ${failed}`);
console.log(` ⏭️ Skipped: ${skipped}`);
console.log(` 🟡 Running: ${running}`);
console.log('===================================');
};
/**
* Checks if all requested tasks exist in ./Tasks directory. Exits with error if any are missing.
*/
function failIfTasksMissing(argv) {
const argvTask = argv.task;
let requestedTasks = [];
if (argvTask) {
if (argvTask.startsWith('@(') && argvTask.endsWith(')')) {
requestedTasks = argvTask.slice(2, -1).split('|').map(t => t.trim());
} else {
requestedTasks = [argvTask];
}
}
if (requestedTasks.length) {
// Check existence in ./Tasks directory
const tasksDir = path.join(__dirname, 'Tasks');
let missingTasks = [];
for (const t of requestedTasks) {
const taskPath = path.join(tasksDir, t);
if (!fs.existsSync(taskPath)) {
missingTasks.push(t);
}
}
if (missingTasks.length) {
console.error(`Error: The following tasks do not exist: ${missingTasks.join(', ')}`);
process.exit(2);
}
}
}