@epublishing/grunt-epublishing 1.1.4 → 1.2.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/lib/cli.js +134 -58
- package/lib/task-worker.js +75 -0
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Implementation
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Main entry point for the build tools.
|
|
5
5
|
* Orchestrates webpack, sass, concat/minify tasks.
|
|
6
|
+
*
|
|
7
|
+
* Heavy modules (webpack, sass-embedded, terser) are lazy-loaded inside
|
|
8
|
+
* their task functions so that subprocess workers only pay for what they use.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
'use strict';
|
|
9
12
|
|
|
13
|
+
const os = require('os');
|
|
10
14
|
const fs = require('fs');
|
|
11
15
|
const path = require('path');
|
|
12
16
|
const { spawn } = require('child_process');
|
|
13
17
|
const { program } = require('commander');
|
|
14
18
|
const chalk = require('chalk');
|
|
15
19
|
const ora = require('ora');
|
|
16
|
-
const Resolver = require('@epublishing/jade-resolver');
|
|
17
20
|
const { loadConfig } = require('./config-loader');
|
|
18
|
-
const { configureWebpack, runWebpack, watchWebpack, splitEntries } = require('./webpack.config');
|
|
19
|
-
const { compileSass } = require('./sass-compiler');
|
|
20
|
-
const { runConcatMinify } = require('./concat-minify');
|
|
21
|
-
const { writeTsConfigs, cleanTsConfigs } = require('./tsconfig-gen');
|
|
22
21
|
|
|
23
22
|
// Package info for banner
|
|
24
23
|
const pkg = require('../package.json');
|
|
@@ -28,7 +27,7 @@ const pkg = require('../package.json');
|
|
|
28
27
|
*/
|
|
29
28
|
function printBanner(options) {
|
|
30
29
|
if (options.noBanner) return;
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
console.log(chalk.cyan(`
|
|
33
32
|
╔═══════════════════════════════════════════╗
|
|
34
33
|
║ ║
|
|
@@ -43,7 +42,7 @@ function printBanner(options) {
|
|
|
43
42
|
*/
|
|
44
43
|
function printOptions(options) {
|
|
45
44
|
if (options.noBanner) return;
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
const flags = [];
|
|
48
47
|
if (options.watch) flags.push('watch');
|
|
49
48
|
if (options.noMinify) flags.push('no-minify');
|
|
@@ -51,7 +50,8 @@ function printOptions(options) {
|
|
|
51
50
|
if (options.lint) flags.push('lint');
|
|
52
51
|
if (options.verbose) flags.push('verbose');
|
|
53
52
|
if (options.parallel) flags.push('parallel');
|
|
54
|
-
|
|
53
|
+
if (options.isolateTasks) flags.push('isolate-tasks');
|
|
54
|
+
|
|
55
55
|
if (flags.length > 0) {
|
|
56
56
|
console.log(chalk.gray(` Options: ${flags.join(', ')}\n`));
|
|
57
57
|
}
|
|
@@ -64,6 +64,7 @@ function printOptions(options) {
|
|
|
64
64
|
* @param {Object} options - Build options
|
|
65
65
|
*/
|
|
66
66
|
async function runNpmInstall(config, options) {
|
|
67
|
+
const Resolver = require('@epublishing/jade-resolver');
|
|
67
68
|
const paths = config.paths || {};
|
|
68
69
|
// Build paths object with ONLY jade and jade child gem directories (absolute paths)
|
|
69
70
|
const gemPaths = {};
|
|
@@ -125,35 +126,36 @@ async function runNpmInstall(config, options) {
|
|
|
125
126
|
* @param {Object} options - Build options
|
|
126
127
|
*/
|
|
127
128
|
async function runWebpackBuild(config, options) {
|
|
129
|
+
const { configureWebpack, runWebpack, splitEntries } = require('./webpack.config');
|
|
128
130
|
const spinner = ora('Building webpack bundles...').start();
|
|
129
|
-
|
|
131
|
+
|
|
130
132
|
try {
|
|
131
133
|
const webpackConfigs = configureWebpack(config, options);
|
|
132
134
|
const targets = Object.keys(webpackConfigs);
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
if (targets.length === 0) {
|
|
135
137
|
spinner.info('No webpack targets configured');
|
|
136
138
|
return;
|
|
137
139
|
}
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
for (const targetName of targets) {
|
|
140
142
|
const targetConfig = webpackConfigs[targetName];
|
|
141
|
-
|
|
143
|
+
|
|
142
144
|
// Split entries if there are many (memory optimization)
|
|
143
145
|
if (targetConfig.entry && Object.keys(targetConfig.entry).length > 10 && !options.parallel) {
|
|
144
146
|
const batches = splitEntries(targetConfig.entry, 10);
|
|
145
147
|
spinner.text = `Building ${targetName} (${batches.length} batches)...`;
|
|
146
|
-
|
|
148
|
+
|
|
147
149
|
for (let i = 0; i < batches.length; i++) {
|
|
148
150
|
const batchConfig = { ...targetConfig, entry: batches[i] };
|
|
149
151
|
spinner.text = `Building ${targetName} batch ${i + 1}/${batches.length}...`;
|
|
150
|
-
|
|
152
|
+
|
|
151
153
|
const stats = await runWebpack(batchConfig);
|
|
152
|
-
|
|
154
|
+
|
|
153
155
|
if (options.verbose) {
|
|
154
156
|
console.log(stats.toString({ colors: true }));
|
|
155
157
|
}
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
// Allow GC between batches
|
|
158
160
|
if (global.gc) {
|
|
159
161
|
global.gc();
|
|
@@ -162,13 +164,13 @@ async function runWebpackBuild(config, options) {
|
|
|
162
164
|
} else {
|
|
163
165
|
spinner.text = `Building ${targetName}...`;
|
|
164
166
|
const stats = await runWebpack(targetConfig);
|
|
165
|
-
|
|
167
|
+
|
|
166
168
|
if (options.verbose) {
|
|
167
169
|
console.log(stats.toString({ colors: true }));
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
|
-
|
|
173
|
+
|
|
172
174
|
spinner.succeed(`Webpack: ${targets.length} target(s) built`);
|
|
173
175
|
} catch (error) {
|
|
174
176
|
spinner.fail('Webpack build failed');
|
|
@@ -182,56 +184,57 @@ async function runWebpackBuild(config, options) {
|
|
|
182
184
|
* @param {Object} options - Build options
|
|
183
185
|
*/
|
|
184
186
|
async function runWebpackWatch(config, options) {
|
|
187
|
+
const { configureWebpack, watchWebpack } = require('./webpack.config');
|
|
185
188
|
const webpackConfigs = configureWebpack(config, { ...options, watch: true });
|
|
186
189
|
const targets = Object.keys(webpackConfigs);
|
|
187
|
-
|
|
190
|
+
|
|
188
191
|
if (targets.length === 0) {
|
|
189
192
|
console.log(chalk.yellow('No webpack targets configured'));
|
|
190
193
|
return;
|
|
191
194
|
}
|
|
192
|
-
|
|
195
|
+
|
|
193
196
|
console.log(chalk.cyan('Starting webpack watch mode...'));
|
|
194
|
-
|
|
197
|
+
|
|
195
198
|
const watchers = [];
|
|
196
|
-
|
|
199
|
+
|
|
197
200
|
for (const targetName of targets) {
|
|
198
201
|
const targetConfig = webpackConfigs[targetName];
|
|
199
|
-
|
|
202
|
+
|
|
200
203
|
const watcher = watchWebpack(targetConfig, (err, stats) => {
|
|
201
204
|
if (err) {
|
|
202
205
|
console.error(chalk.red(`[${targetName}] Error:`, err.message));
|
|
203
206
|
return;
|
|
204
207
|
}
|
|
205
|
-
|
|
208
|
+
|
|
206
209
|
const info = stats.toJson();
|
|
207
|
-
|
|
210
|
+
|
|
208
211
|
if (stats.hasErrors()) {
|
|
209
212
|
console.error(chalk.red(`[${targetName}] Errors:`));
|
|
210
213
|
info.errors.forEach(e => console.error(e.message));
|
|
211
214
|
return;
|
|
212
215
|
}
|
|
213
|
-
|
|
216
|
+
|
|
214
217
|
if (stats.hasWarnings() && options.verbose) {
|
|
215
218
|
console.warn(chalk.yellow(`[${targetName}] Warnings:`));
|
|
216
219
|
info.warnings.forEach(w => console.warn(w.message));
|
|
217
220
|
}
|
|
218
|
-
|
|
221
|
+
|
|
219
222
|
console.log(chalk.green(`[${targetName}] Rebuilt in ${info.time}ms`));
|
|
220
223
|
});
|
|
221
|
-
|
|
224
|
+
|
|
222
225
|
watchers.push(watcher);
|
|
223
226
|
}
|
|
224
|
-
|
|
227
|
+
|
|
225
228
|
// Handle process termination
|
|
226
229
|
const cleanup = () => {
|
|
227
230
|
console.log(chalk.yellow('\nStopping watchers...'));
|
|
228
231
|
watchers.forEach(w => w.close());
|
|
229
232
|
process.exit(0);
|
|
230
233
|
};
|
|
231
|
-
|
|
234
|
+
|
|
232
235
|
process.on('SIGINT', cleanup);
|
|
233
236
|
process.on('SIGTERM', cleanup);
|
|
234
|
-
|
|
237
|
+
|
|
235
238
|
// Keep process alive
|
|
236
239
|
await new Promise(() => {});
|
|
237
240
|
}
|
|
@@ -242,17 +245,18 @@ async function runWebpackWatch(config, options) {
|
|
|
242
245
|
* @param {Object} options - Build options
|
|
243
246
|
*/
|
|
244
247
|
async function runSassBuild(config, options) {
|
|
248
|
+
const { compileSass } = require('./sass-compiler');
|
|
245
249
|
const spinner = ora('Compiling Sass...').start();
|
|
246
|
-
|
|
250
|
+
|
|
247
251
|
try {
|
|
248
252
|
const results = await compileSass(config, {
|
|
249
253
|
verbose: options.verbose,
|
|
250
254
|
maxConcurrency: options.parallel ? 4 : 2,
|
|
251
255
|
});
|
|
252
|
-
|
|
256
|
+
|
|
253
257
|
const successful = results.filter(r => r.success).length;
|
|
254
258
|
const failed = results.filter(r => !r.success).length;
|
|
255
|
-
|
|
259
|
+
|
|
256
260
|
if (failed > 0) {
|
|
257
261
|
spinner.warn(`Sass: ${successful} compiled, ${failed} failed`);
|
|
258
262
|
results.filter(r => !r.success).forEach(r => {
|
|
@@ -275,29 +279,30 @@ async function runSassBuild(config, options) {
|
|
|
275
279
|
* @param {Object} options - Build options
|
|
276
280
|
*/
|
|
277
281
|
async function runConcatMinifyBuild(config, options) {
|
|
282
|
+
const { runConcatMinify } = require('./concat-minify');
|
|
278
283
|
const spinner = ora('Concatenating and minifying...').start();
|
|
279
|
-
|
|
284
|
+
|
|
280
285
|
try {
|
|
281
286
|
const results = await runConcatMinify(config, {
|
|
282
287
|
verbose: options.verbose,
|
|
283
288
|
noMinify: options.noMinify,
|
|
284
289
|
});
|
|
285
|
-
|
|
290
|
+
|
|
286
291
|
const concatCount = results.concat.filter(r => r.success).length;
|
|
287
292
|
const minifyCount = results.minify.filter(r => r.success).length;
|
|
288
|
-
|
|
293
|
+
|
|
289
294
|
if (concatCount > 0 || minifyCount > 0) {
|
|
290
295
|
spinner.succeed(`Concat: ${concatCount} bundles, Minify: ${minifyCount} files`);
|
|
291
296
|
} else {
|
|
292
297
|
spinner.info('No concat/minify targets configured');
|
|
293
298
|
}
|
|
294
|
-
|
|
299
|
+
|
|
295
300
|
// Report any failures
|
|
296
301
|
const failures = [
|
|
297
302
|
...results.concat.filter(r => !r.success),
|
|
298
303
|
...results.minify.filter(r => !r.success),
|
|
299
304
|
];
|
|
300
|
-
|
|
305
|
+
|
|
301
306
|
if (failures.length > 0) {
|
|
302
307
|
console.warn(chalk.yellow(' Some targets failed:'));
|
|
303
308
|
failures.forEach(f => {
|
|
@@ -317,30 +322,30 @@ async function runConcatMinifyBuild(config, options) {
|
|
|
317
322
|
*/
|
|
318
323
|
async function runClean(config, options) {
|
|
319
324
|
const spinner = ora('Cleaning...').start();
|
|
320
|
-
|
|
325
|
+
|
|
321
326
|
try {
|
|
322
327
|
const cleanConfig = config.clean || {};
|
|
323
328
|
let cleaned = 0;
|
|
324
|
-
|
|
329
|
+
|
|
325
330
|
for (const [targetName, targetConfig] of Object.entries(cleanConfig)) {
|
|
326
331
|
const src = Array.isArray(targetConfig.src) ? targetConfig.src : [targetConfig.src];
|
|
327
|
-
|
|
332
|
+
|
|
328
333
|
for (const pattern of src) {
|
|
329
334
|
const resolvedPath = path.resolve(config.paths?.site || process.cwd(), pattern);
|
|
330
|
-
|
|
335
|
+
|
|
331
336
|
// Simple glob handling for clean
|
|
332
337
|
if (pattern.includes('*')) {
|
|
333
338
|
// Skip complex globs for now
|
|
334
339
|
continue;
|
|
335
340
|
}
|
|
336
|
-
|
|
341
|
+
|
|
337
342
|
if (fs.existsSync(resolvedPath)) {
|
|
338
343
|
await fs.promises.rm(resolvedPath, { recursive: true, force: true });
|
|
339
344
|
cleaned++;
|
|
340
345
|
}
|
|
341
346
|
}
|
|
342
347
|
}
|
|
343
|
-
|
|
348
|
+
|
|
344
349
|
spinner.succeed(`Cleaned ${cleaned} path(s)`);
|
|
345
350
|
} catch (error) {
|
|
346
351
|
spinner.fail('Clean failed');
|
|
@@ -348,6 +353,60 @@ async function runClean(config, options) {
|
|
|
348
353
|
}
|
|
349
354
|
}
|
|
350
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Run a single build task in an isolated subprocess with a controlled heap.
|
|
358
|
+
* When the subprocess exits, the OS fully reclaims all its memory.
|
|
359
|
+
*
|
|
360
|
+
* @param {string} taskName - Task to run (webpack, sass, concat)
|
|
361
|
+
* @param {Object} configPaths - config.paths object (all strings, serializable)
|
|
362
|
+
* @param {Object} options - Serializable build options
|
|
363
|
+
* @param {number} heapSize - Max heap size in MB for the subprocess
|
|
364
|
+
* @returns {Promise<void>}
|
|
365
|
+
*/
|
|
366
|
+
function runTaskInSubprocess(taskName, configPaths, options, heapSize) {
|
|
367
|
+
return new Promise((resolve, reject) => {
|
|
368
|
+
const tmpFile = path.join(os.tmpdir(), `epb-paths-${process.pid}-${taskName}.json`);
|
|
369
|
+
fs.writeFileSync(tmpFile, JSON.stringify(configPaths));
|
|
370
|
+
|
|
371
|
+
const workerPath = path.join(__dirname, 'task-worker.js');
|
|
372
|
+
const serializableOptions = {
|
|
373
|
+
verbose: options.verbose || false,
|
|
374
|
+
noMinify: options.noMinify || false,
|
|
375
|
+
analyze: options.analyze || false,
|
|
376
|
+
lint: options.lint || false,
|
|
377
|
+
parallel: false, // never parallel inside a subprocess
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const nodeArgs = [
|
|
381
|
+
`--max-old-space-size=${heapSize}`,
|
|
382
|
+
workerPath,
|
|
383
|
+
taskName,
|
|
384
|
+
tmpFile,
|
|
385
|
+
JSON.stringify(serializableOptions),
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
const proc = spawn(process.execPath, nodeArgs, {
|
|
389
|
+
stdio: 'inherit',
|
|
390
|
+
// Clear NODE_OPTIONS so our --max-old-space-size takes effect
|
|
391
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
proc.on('close', (code) => {
|
|
395
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
396
|
+
if (code === 0) {
|
|
397
|
+
resolve();
|
|
398
|
+
} else {
|
|
399
|
+
reject(new Error(`${taskName} subprocess exited with code ${code}`));
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
proc.on('error', (err) => {
|
|
404
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
405
|
+
reject(err);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
351
410
|
/**
|
|
352
411
|
* Run full build
|
|
353
412
|
* @param {Object} config - Build configuration
|
|
@@ -355,9 +414,10 @@ async function runClean(config, options) {
|
|
|
355
414
|
*/
|
|
356
415
|
async function runFullBuild(config, options) {
|
|
357
416
|
const startTime = Date.now();
|
|
358
|
-
|
|
417
|
+
|
|
359
418
|
// Generate tsconfig files first
|
|
360
419
|
try {
|
|
420
|
+
const { writeTsConfigs } = require('./tsconfig-gen');
|
|
361
421
|
const written = await writeTsConfigs(process.cwd());
|
|
362
422
|
if (options.verbose && written.length > 0) {
|
|
363
423
|
console.log(chalk.gray(` Generated ${written.length} tsconfig.json file(s)`));
|
|
@@ -365,9 +425,19 @@ async function runFullBuild(config, options) {
|
|
|
365
425
|
} catch {
|
|
366
426
|
// TSConfig generation is optional, continue on error
|
|
367
427
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
428
|
+
|
|
429
|
+
if (options.isolateTasks) {
|
|
430
|
+
// Subprocess isolation: each task runs in its own Node process.
|
|
431
|
+
// When the subprocess exits, the OS fully reclaims all memory.
|
|
432
|
+
const heapSize = options.heapSize || 512;
|
|
433
|
+
|
|
434
|
+
console.log(chalk.gray(` Running tasks in isolated subprocesses (heap: ${heapSize}MB)\n`));
|
|
435
|
+
|
|
436
|
+
await runTaskInSubprocess('webpack', config.paths, options, heapSize);
|
|
437
|
+
await runTaskInSubprocess('sass', config.paths, options, heapSize);
|
|
438
|
+
await runTaskInSubprocess('concat', config.paths, options, heapSize);
|
|
439
|
+
} else if (options.parallel) {
|
|
440
|
+
// Run tasks in parallel (uses more memory)
|
|
371
441
|
await Promise.all([
|
|
372
442
|
runWebpackBuild(config, options),
|
|
373
443
|
runSassBuild(config, options),
|
|
@@ -377,20 +447,21 @@ async function runFullBuild(config, options) {
|
|
|
377
447
|
// Sequential execution with GC hints between tasks
|
|
378
448
|
await runWebpackBuild(config, options);
|
|
379
449
|
if (global.gc) global.gc();
|
|
380
|
-
|
|
450
|
+
|
|
381
451
|
await runSassBuild(config, options);
|
|
382
452
|
if (global.gc) global.gc();
|
|
383
|
-
|
|
453
|
+
|
|
384
454
|
await runConcatMinifyBuild(config, options);
|
|
385
455
|
}
|
|
386
|
-
|
|
456
|
+
|
|
387
457
|
// Clean tsconfig files after build
|
|
388
458
|
try {
|
|
459
|
+
const { cleanTsConfigs } = require('./tsconfig-gen');
|
|
389
460
|
await cleanTsConfigs(process.cwd());
|
|
390
461
|
} catch {
|
|
391
462
|
// Cleanup is optional
|
|
392
463
|
}
|
|
393
|
-
|
|
464
|
+
|
|
394
465
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
395
466
|
console.log(chalk.green(`\n✓ Build completed in ${duration}s\n`));
|
|
396
467
|
}
|
|
@@ -410,6 +481,8 @@ async function run(argv) {
|
|
|
410
481
|
.option('--lint', 'Run ESLint')
|
|
411
482
|
.option('-v, --verbose', 'Verbose output')
|
|
412
483
|
.option('--parallel', 'Run tasks in parallel (uses more memory)')
|
|
484
|
+
.option('--isolate-tasks', 'Run each task in an isolated subprocess for lower memory usage')
|
|
485
|
+
.option('--heap-size <mb>', 'Max heap size in MB per subprocess (default: 512)', parseInt)
|
|
413
486
|
.option('--no-banner', 'Hide banner')
|
|
414
487
|
.option('--env <env>', 'Set NODE_ENV')
|
|
415
488
|
.option('--gc-between-tasks', 'Force garbage collection between tasks')
|
|
@@ -437,7 +510,7 @@ async function run(argv) {
|
|
|
437
510
|
|
|
438
511
|
// Load configuration
|
|
439
512
|
const spinner = ora('Loading configuration...').start();
|
|
440
|
-
|
|
513
|
+
|
|
441
514
|
let config;
|
|
442
515
|
try {
|
|
443
516
|
config = await loadConfig(process.cwd(), { verbose: options.verbose });
|
|
@@ -487,7 +560,8 @@ async function run(argv) {
|
|
|
487
560
|
await runClean(config, options);
|
|
488
561
|
break;
|
|
489
562
|
|
|
490
|
-
case 'tsconfig':
|
|
563
|
+
case 'tsconfig': {
|
|
564
|
+
const { writeTsConfigs } = require('./tsconfig-gen');
|
|
491
565
|
const tsconfigSpinner = ora('Generating tsconfig.json files...').start();
|
|
492
566
|
try {
|
|
493
567
|
const written = await writeTsConfigs(process.cwd());
|
|
@@ -497,8 +571,10 @@ async function run(argv) {
|
|
|
497
571
|
throw error;
|
|
498
572
|
}
|
|
499
573
|
break;
|
|
574
|
+
}
|
|
500
575
|
|
|
501
|
-
case 'clean-tsconfig':
|
|
576
|
+
case 'clean-tsconfig': {
|
|
577
|
+
const { cleanTsConfigs } = require('./tsconfig-gen');
|
|
502
578
|
const cleanTsconfigSpinner = ora('Cleaning tsconfig.json files...').start();
|
|
503
579
|
try {
|
|
504
580
|
const removed = await cleanTsConfigs(process.cwd());
|
|
@@ -508,6 +584,7 @@ async function run(argv) {
|
|
|
508
584
|
throw error;
|
|
509
585
|
}
|
|
510
586
|
break;
|
|
587
|
+
}
|
|
511
588
|
|
|
512
589
|
case 'all':
|
|
513
590
|
default:
|
|
@@ -532,4 +609,3 @@ module.exports = {
|
|
|
532
609
|
runClean,
|
|
533
610
|
runFullBuild,
|
|
534
611
|
};
|
|
535
|
-
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Task Worker - Isolated subprocess for a single build task.
|
|
4
|
+
*
|
|
5
|
+
* Invoked by the main CLI in --isolate-tasks mode. Each task runs in
|
|
6
|
+
* its own Node process with a controlled heap size (via --max-old-space-size).
|
|
7
|
+
* When the subprocess exits, the OS fully reclaims all its memory.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node --max-old-space-size=512 task-worker.js <task> <paths-file> [options-json]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const taskName = args[0];
|
|
19
|
+
const pathsFile = args[1];
|
|
20
|
+
const optionsJson = args[2] || '{}';
|
|
21
|
+
|
|
22
|
+
if (!taskName || !pathsFile) {
|
|
23
|
+
console.error('Usage: task-worker.js <task> <paths-file> [options-json]');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Read paths (written by the main process after bundle exec discovery)
|
|
28
|
+
const paths = JSON.parse(fs.readFileSync(pathsFile, 'utf8'));
|
|
29
|
+
const options = JSON.parse(optionsJson);
|
|
30
|
+
|
|
31
|
+
// Rebuild config from paths — re-reads grunt-config.js files but skips
|
|
32
|
+
// the expensive `bundle exec` gem-path discovery (already done by parent).
|
|
33
|
+
const { baseConfig, mergeConfig } = require('./config-loader');
|
|
34
|
+
const { resolveTemplates } = require('./template-resolver');
|
|
35
|
+
|
|
36
|
+
let config = JSON.parse(JSON.stringify(baseConfig));
|
|
37
|
+
config.paths = paths;
|
|
38
|
+
|
|
39
|
+
if (paths.jade) config = mergeConfig(config, paths.jade);
|
|
40
|
+
for (const [key, val] of Object.entries(paths)) {
|
|
41
|
+
if (key.startsWith('jade_') && val) config = mergeConfig(config, val);
|
|
42
|
+
}
|
|
43
|
+
if (paths.site) config = mergeConfig(config, paths.site);
|
|
44
|
+
config = resolveTemplates(config, config.paths);
|
|
45
|
+
|
|
46
|
+
// Import only the task function we need (lazy loading keeps memory low)
|
|
47
|
+
const {
|
|
48
|
+
runWebpackBuild,
|
|
49
|
+
runSassBuild,
|
|
50
|
+
runConcatMinifyBuild,
|
|
51
|
+
} = require('./cli');
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
switch (taskName) {
|
|
55
|
+
case 'webpack':
|
|
56
|
+
await runWebpackBuild(config, options);
|
|
57
|
+
break;
|
|
58
|
+
case 'sass':
|
|
59
|
+
await runSassBuild(config, options);
|
|
60
|
+
break;
|
|
61
|
+
case 'concat':
|
|
62
|
+
await runConcatMinifyBuild(config, options);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`Unknown task: ${taskName}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main()
|
|
70
|
+
.then(() => process.exit(0))
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
console.error(`[task-worker] ${taskName} failed:`, err.message);
|
|
73
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
package/package.json
CHANGED