1 | #!/usr/bin/env node
|
2 |
|
3 | const fs = require('fs-extra');
|
4 | const path = require('path');
|
5 | const program = require('commander');
|
6 | const pm2 = require('pm2');
|
7 | const chalk = require('chalk');
|
8 | const npmRunScript = require('npm-run-script');
|
9 | const opn = require('open');
|
10 |
|
11 | const before = require('./_before');
|
12 |
|
13 | const contentWaiting = require('../defaults/content-waiting');
|
14 | const {
|
15 | keyFileProjectConfigTempFull,
|
16 | keyFileProjectConfigTempPortionServer,
|
17 | keyFileProjectConfigTempPortionClient,
|
18 | keyFileProjectConfigTempPortionOtherClient,
|
19 | filenameWebpackDevServerPortTemp,
|
20 | filenameBuilding
|
21 |
|
22 |
|
23 | } = require('../defaults/before-build');
|
24 |
|
25 | const checkFileUpdate = require('../libs/check-file-change');
|
26 | const removeTempBuild = require('../libs/remove-temp-build');
|
27 | const removeTempProjectConfig = require('../libs/remove-temp-project-config');
|
28 | const validateConfig = require('../libs/validate-config');
|
29 | const validateConfigDist = require('../libs/validate-config-dist');
|
30 | const getDirDevTmp = require('../libs/get-dir-dev-tmp');
|
31 | const getDirDevCache = require('../libs/get-dir-dev-cache');
|
32 | const getDirTemp = require('../libs/get-dir-tmp');
|
33 |
|
34 | const __ = require('../utils/translate');
|
35 | const sleep = require('../utils/sleep');
|
36 | const spinner = require('../utils/spinner');
|
37 |
|
38 | const getAppType = require('../utils/get-app-type');
|
39 | const setEnvFromCommand = require('../utils/set-env-from-command');
|
40 | const getChunkmapPath = require('../utils/get-chunkmap-path');
|
41 | const initNodeEnv = require('../utils/init-node-env');
|
42 | const getCwd = require('../utils/get-cwd');
|
43 | const getPathnameDevServerStart = require('../utils/get-pathname-dev-server-start');
|
44 | const getLogMsg = require('../libs/get-log-msg');
|
45 | const log = require('../libs/log');
|
46 |
|
47 |
|
48 | const kootWebpackBuildVendorDll = require('koot-webpack/build-vendor-dll');
|
49 |
|
50 | let exiting = false;
|
51 |
|
52 | program
|
53 | .version(require('../package').version, '-v, --version')
|
54 | .usage('[options]')
|
55 | .option('-c, --client', 'Set STAGE to CLIENT')
|
56 | .option('-s, --server', 'Set STAGE to SERVER')
|
57 | .option('-g, --global', 'Connect to global PM2')
|
58 | .option('--stage <stage>', 'Set STAGE')
|
59 | .option(
|
60 | '--dest <destination-path>',
|
61 | 'Set destination directory (for temporary files)'
|
62 | )
|
63 | .option('--config <config-file-path>', 'Set config file')
|
64 | .option('--type <project-type>', 'Set project type')
|
65 | .option('--port <port>', 'Set server port')
|
66 | .option('--no-open', "Don't open browser automatically")
|
67 | .option('--no-dll', "Don't use Webpack's DLL plugin")
|
68 | .option('--koot-test', 'Koot test mode')
|
69 | .option('--koot-development', 'Koot development mode')
|
70 | .parse(process.argv);
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | const run = async () => {
|
86 | process.env.WEBPACK_BUILD_ENV = 'dev';
|
87 |
|
88 |
|
89 | await removeTempProjectConfig();
|
90 |
|
91 | await before(program);
|
92 |
|
93 |
|
94 | process.stdout.write('\x1B[2J\x1B[0f');
|
95 |
|
96 | const {
|
97 | client,
|
98 | server,
|
99 | stage: _stage,
|
100 | dest,
|
101 | config,
|
102 | type,
|
103 | global = false,
|
104 | open = true,
|
105 | port,
|
106 | dll = true,
|
107 | kootTest = false,
|
108 | kootDevelopment = false
|
109 | } = program;
|
110 |
|
111 | initNodeEnv();
|
112 | setEnvFromCommand({
|
113 | config,
|
114 | type,
|
115 | port
|
116 | });
|
117 |
|
118 | let stage = (() => {
|
119 | if (_stage) return _stage;
|
120 | if (client) return 'client';
|
121 | if (server) return 'server';
|
122 |
|
123 |
|
124 | return false;
|
125 | })();
|
126 |
|
127 |
|
128 | const buildCmdArgs =
|
129 | '--env dev' +
|
130 | (typeof dest === 'string' ? ` --dest ${dest}` : '') +
|
131 | (typeof config === 'string' ? ` --config ${config}` : '') +
|
132 | (typeof type === 'string' ? ` --type ${type}` : '') +
|
133 | (kootTest ? ` --koot-test` : '') +
|
134 | (kootDevelopment ? ` --koot-development` : '') +
|
135 | ' --koot-dev';
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | const cwd = getCwd();
|
145 | const dirDevTemp = getDirDevTmp(cwd);
|
146 | await fs.ensureDir(dirDevTemp);
|
147 | await fs.emptyDir(dirDevTemp);
|
148 | const dirCache = getDirDevCache();
|
149 | await fs.ensureDir(dirCache);
|
150 | await fs.emptyDir(dirCache);
|
151 |
|
152 |
|
153 | const kootConfig = await validateConfig();
|
154 |
|
155 |
|
156 | if (dest) kootConfig.dist = validateConfigDist(dest);
|
157 |
|
158 | const {
|
159 | dist,
|
160 |
|
161 | devPort,
|
162 | [keyFileProjectConfigTempFull]: fileProjectConfigTempFull,
|
163 | [keyFileProjectConfigTempPortionServer]: fileProjectConfigTempPortionServer,
|
164 | [keyFileProjectConfigTempPortionClient]: fileProjectConfigTempPortionClient,
|
165 | [keyFileProjectConfigTempPortionOtherClient]: fileProjectConfigTempPortionOtherClient
|
166 | } = kootConfig;
|
167 | const [devMemoryAllocationClient, devMemoryAllocationServer] = (() => {
|
168 | const { devMemoryAllocation } = kootConfig;
|
169 | if (!devMemoryAllocation) return [undefined, undefined];
|
170 | if (typeof devMemoryAllocation === 'object') {
|
171 | return [devMemoryAllocation.client, devMemoryAllocation.server];
|
172 | }
|
173 | if (isNaN(devMemoryAllocation)) return [undefined, undefined];
|
174 | return [parseInt(devMemoryAllocation), parseInt(devMemoryAllocation)];
|
175 | })();
|
176 | const appType = await getAppType();
|
177 | const packageInfo = await fs.readJson(path.resolve(cwd, 'package.json'));
|
178 | const { name } = packageInfo;
|
179 |
|
180 |
|
181 | await fs.ensureDir(dist);
|
182 | await fs.emptyDir(dist);
|
183 | await fs.ensureDir(path.resolve(dist, 'public'));
|
184 | await fs.ensureDir(path.resolve(dist, 'server'));
|
185 |
|
186 |
|
187 | const processes = [];
|
188 |
|
189 |
|
190 | const waitingSpinner = false;
|
191 |
|
192 |
|
193 | await removeTempBuild(dist);
|
194 |
|
195 |
|
196 | if (fileProjectConfigTempFull)
|
197 | process.env.KOOT_PROJECT_CONFIG_FULL_PATHNAME = fileProjectConfigTempFull;
|
198 | if (fileProjectConfigTempPortionServer)
|
199 | process.env.KOOT_PROJECT_CONFIG_PORTION_SERVER_PATHNAME = fileProjectConfigTempPortionServer;
|
200 | if (fileProjectConfigTempPortionClient)
|
201 | process.env.KOOT_PROJECT_CONFIG_PORTION_CLIENT_PATHNAME = fileProjectConfigTempPortionClient;
|
202 | if (fileProjectConfigTempPortionOtherClient)
|
203 | process.env.KOOT_PROJECT_CONFIG_PORTION_OTHER_CLIENT_PATHNAME = fileProjectConfigTempPortionOtherClient;
|
204 |
|
205 |
|
206 | if (process.env.WEBPACK_BUILD_TYPE === 'spa') {
|
207 | process.env.WEBPACK_BUILD_STAGE = 'client';
|
208 | stage = 'client';
|
209 | }
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | process.env.SERVER_PORT = devPort;
|
215 |
|
216 |
|
217 | process.env.KOOT_DEV_START_TIME = Date.now();
|
218 | if (typeof kootConfig.devServer === 'object')
|
219 | process.env.KOOT_DEV_WDS_EXTEND_CONFIG = JSON.stringify(
|
220 | kootConfig.devServer
|
221 | );
|
222 |
|
223 |
|
224 | await sleep(1000);
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | const removeAllExitListeners = () => {
|
232 | process.removeListener('exit', exitHandler);
|
233 | process.removeListener('SIGINT', exitHandler);
|
234 | process.removeListener('SIGUSR1', exitHandler);
|
235 | process.removeListener('SIGUSR2', exitHandler);
|
236 | process.removeListener('uncaughtException', exitHandler);
|
237 | };
|
238 | const exitHandler = async (options = {}) => {
|
239 | if (exiting) return;
|
240 |
|
241 | exiting = true;
|
242 |
|
243 | let { silent = false } = options;
|
244 | const { error = false } = options;
|
245 |
|
246 | if (error) silent = true;
|
247 |
|
248 | const PromiseAll = Promise.allSettled
|
249 | ? Promise.allSettled.bind(Promise)
|
250 | : Promise.all.bind(Promise);
|
251 |
|
252 | if (Array.isArray(processes) && processes.length) {
|
253 | if (waitingSpinner) waitingSpinner.stop();
|
254 |
|
255 | if (!silent) console.log(' ');
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 | const kill = p => {
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | return new Promise((resolve, reject) => {
|
272 |
|
273 |
|
274 | pm2.delete(p.pm2_env.pm_id, (err, proc) => {
|
275 |
|
276 |
|
277 | if (err) return reject(err);
|
278 |
|
279 | resolve(proc);
|
280 | });
|
281 | });
|
282 | };
|
283 |
|
284 |
|
285 |
|
286 | await PromiseAll(processes.map(kill)).catch(console.error);
|
287 | pm2.disconnect();
|
288 |
|
289 | }
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | try {
|
302 | await PromiseAll([
|
303 | removeTempProjectConfig(),
|
304 | removeTempBuild(dist),
|
305 | fs.emptyDir(getDirDevTmp(cwd)),
|
306 |
|
307 | fs.remove(getDirTemp())
|
308 | ]);
|
309 | } catch (e) {}
|
310 |
|
311 | removeAllExitListeners();
|
312 |
|
313 | try {
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | if (process.send) {
|
319 | process.send('Koot dev mode exit successfully');
|
320 | }
|
321 | try {
|
322 | process.kill(process.pid);
|
323 | } catch (e) {}
|
324 | if (!error) {
|
325 |
|
326 | console.log(
|
327 | '\n\n\n' +
|
328 | chalk.cyanBright('Press CTRL+C again to exit.') +
|
329 | '\n\n'
|
330 | );
|
331 | }
|
332 | process.exit(1);
|
333 | } catch (e) {
|
334 | console.error(e);
|
335 | }
|
336 |
|
337 |
|
338 | };
|
339 |
|
340 | process.stdin.resume();
|
341 |
|
342 | process.on('exit', exitHandler);
|
343 |
|
344 | process.on('SIGINT', exitHandler);
|
345 |
|
346 | process.on('SIGUSR1', exitHandler);
|
347 | process.on('SIGUSR2', exitHandler);
|
348 |
|
349 | process.on('uncaughtException', exitHandler);
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 | if (dll && process.env.WEBPACK_BUILD_STAGE !== 'server') {
|
357 | const msg = getLogMsg(false, 'dev', __('dev.build_dll'));
|
358 | const waiting = spinner(msg + '...');
|
359 | let error;
|
360 | let result;
|
361 |
|
362 |
|
363 | try {
|
364 | if (stage) {
|
365 | process.env.WEBPACK_BUILD_STAGE = stage;
|
366 | result = await kootWebpackBuildVendorDll(kootConfig);
|
367 | } else {
|
368 | const stageCurrent = process.env.WEBPACK_BUILD_STAGE;
|
369 |
|
370 | process.env.WEBPACK_BUILD_STAGE = 'client';
|
371 | result = await kootWebpackBuildVendorDll(kootConfig);
|
372 | await sleep(500);
|
373 | process.env.WEBPACK_BUILD_STAGE = 'server';
|
374 | result = await kootWebpackBuildVendorDll(kootConfig);
|
375 |
|
376 | process.env.WEBPACK_BUILD_STAGE = stageCurrent;
|
377 | }
|
378 | } catch (e) {
|
379 | waiting.stop();
|
380 | spinner(msg).fail();
|
381 | if (
|
382 | result &&
|
383 | Array.isArray(result.errors) &&
|
384 | result.errors.length
|
385 | ) {
|
386 | error = result.errors;
|
387 | result.errors.forEach(e => console.error(e));
|
388 | } else {
|
389 | error = e;
|
390 | console.error(e);
|
391 | }
|
392 | process.exit();
|
393 | }
|
394 |
|
395 | if (error) return;
|
396 |
|
397 | await sleep(500);
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | waiting.stop();
|
403 | spinner(msg).succeed();
|
404 | }
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 | if (stage) {
|
412 | const cmd = `koot-build --stage ${stage} ${buildCmdArgs}`;
|
413 | const child = npmRunScript(cmd, {});
|
414 | child.once('error', error => {
|
415 |
|
416 | console.trace(error);
|
417 | process.exit(1);
|
418 | });
|
419 | child.once('exit', async () => {
|
420 |
|
421 |
|
422 | });
|
423 |
|
424 |
|
425 | if (process.env.WEBPACK_BUILD_TYPE === 'spa') {
|
426 |
|
427 | let flagCreated = false;
|
428 | const fileFlagBuilding = path.resolve(dist, filenameBuilding);
|
429 | await new Promise(resolve => {
|
430 | const wait = () =>
|
431 | setTimeout(() => {
|
432 | if (!flagCreated) {
|
433 | flagCreated = fs.existsSync(fileFlagBuilding);
|
434 | return wait();
|
435 | }
|
436 | if (!fs.existsSync(fileFlagBuilding)) return resolve();
|
437 | wait();
|
438 | }, 1000);
|
439 | wait();
|
440 | });
|
441 |
|
442 |
|
443 |
|
444 | await new Promise(resolve => {
|
445 | setTimeout(() => {
|
446 | log('success', 'dev', __('dev.spa_success'));
|
447 |
|
448 | console.log(
|
449 | ' @ ' +
|
450 | chalk.green(
|
451 | `http://localhost:${process.env.SERVER_PORT}/`
|
452 | )
|
453 | );
|
454 |
|
455 | console.log(' ');
|
456 |
|
457 | console.log('------------------------------');
|
458 |
|
459 | console.log(' ');
|
460 | resolve();
|
461 | }, 500);
|
462 | });
|
463 |
|
464 | if (open) openBrowserPage();
|
465 | }
|
466 |
|
467 | return;
|
468 | }
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 | const pathChunkmap = getChunkmapPath(dist);
|
485 | const pathServerJS = path.resolve(dist, 'server/index.js');
|
486 | const pathServerStartFlag = getPathnameDevServerStart();
|
487 |
|
488 |
|
489 | const start = stage =>
|
490 | new Promise(async (resolve, reject) => {
|
491 |
|
492 |
|
493 | const pathLogOut = path.resolve(getDirDevTmp(cwd), `${stage}.log`);
|
494 | const pathLogErr = path.resolve(
|
495 | getDirDevTmp(cwd),
|
496 | `${stage}-error.log`
|
497 | );
|
498 | if (fs.existsSync(pathLogOut)) await fs.remove(pathLogOut);
|
499 | if (fs.existsSync(pathLogErr)) await fs.remove(pathLogErr);
|
500 | await fs.ensureFile(pathLogOut);
|
501 | await fs.ensureFile(pathLogErr);
|
502 |
|
503 | const config = {
|
504 | name: `${stage}-${name}`,
|
505 | script: path.resolve(__dirname, './build.js'),
|
506 | args: `--stage ${stage} ${buildCmdArgs}`,
|
507 | cwd: cwd,
|
508 | output: pathLogOut,
|
509 | error: pathLogErr,
|
510 | autorestart: true
|
511 | };
|
512 |
|
513 | switch (stage) {
|
514 | case 'client': {
|
515 | if (devMemoryAllocationClient)
|
516 | config.node_args = `--max-old-space-size=${devMemoryAllocationClient}`;
|
517 | break;
|
518 | }
|
519 | case 'server': {
|
520 | if (devMemoryAllocationServer)
|
521 | config.node_args = `--max-old-space-size=${devMemoryAllocationServer}`;
|
522 | break;
|
523 | }
|
524 | case 'run': {
|
525 | Object.assign(config, {
|
526 | script: pathServerJS,
|
527 | watch: path.dirname(pathServerJS),
|
528 | ignore_watch: [
|
529 | '.server-start',
|
530 | 'node_modules',
|
531 | config.output,
|
532 | config.error
|
533 | ],
|
534 | watch_options: {
|
535 | cwd: path.dirname(pathServerJS)
|
536 |
|
537 | }
|
538 |
|
539 | });
|
540 |
|
541 | delete config.args;
|
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 | break;
|
551 | }
|
552 | case 'main': {
|
553 | const mainScript = path.resolve(
|
554 | __dirname,
|
555 | '../ReactApp/server/index-dev.js'
|
556 | );
|
557 | Object.assign(config, {
|
558 | script: mainScript,
|
559 | watch: path.dirname(mainScript)
|
560 |
|
561 |
|
562 |
|
563 | });
|
564 | delete config.args;
|
565 | break;
|
566 | }
|
567 | default: {
|
568 | }
|
569 | }
|
570 |
|
571 |
|
572 |
|
573 | pm2.start(config, (err, proc) => {
|
574 |
|
575 | if (err) return reject(err);
|
576 | proc.forEach(p => {
|
577 | processes.push({
|
578 | ...p,
|
579 | name: config.name,
|
580 | pid: p.pid || p.process.pid
|
581 | });
|
582 | });
|
583 |
|
584 |
|
585 |
|
586 |
|
587 | resolve(proc);
|
588 | });
|
589 | });
|
590 |
|
591 |
|
592 | const complete = () => {
|
593 | npmRunScript(`pm2 logs`);
|
594 | if (open) return opn(`http://localhost:${process.env.SERVER_PORT}/`);
|
595 | };
|
596 |
|
597 |
|
598 | const encounterError = e => {
|
599 | const error = e instanceof Error ? e : new Error(e);
|
600 | exitHandler({ error: true });
|
601 | throw error;
|
602 | };
|
603 |
|
604 |
|
605 |
|
606 | try {
|
607 | pm2.connect(!global, async err => {
|
608 | if (err) {
|
609 |
|
610 | process.exit(2);
|
611 | }
|
612 |
|
613 |
|
614 | console.log(
|
615 | ` ` +
|
616 | chalk.yellowBright('[koot/build] ') +
|
617 | __('build.build_start', {
|
618 | type: chalk.cyanBright(__(`appType.${appType}`)),
|
619 | stage: chalk.green('client'),
|
620 | env: chalk.green('dev')
|
621 | })
|
622 | );
|
623 |
|
624 |
|
625 | await fs.ensureFile(pathChunkmap);
|
626 | await fs.writeFile(pathChunkmap, contentWaiting);
|
627 |
|
628 |
|
629 | await fs.ensureFile(pathServerJS);
|
630 | await fs.writeFile(pathServerJS, contentWaiting);
|
631 |
|
632 |
|
633 | await fs.ensureFile(pathServerStartFlag);
|
634 | await fs.writeFile(pathServerStartFlag, contentWaiting);
|
635 |
|
636 |
|
637 | await start('client');
|
638 |
|
639 |
|
640 |
|
641 | await checkFileUpdate(pathChunkmap, contentWaiting);
|
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 | console.log(
|
648 | chalk.green('√ ') +
|
649 | chalk.yellowBright('[koot/build] ') +
|
650 | __('build.build_complete', {
|
651 | type: chalk.cyanBright(__(`appType.${appType}`)),
|
652 | stage: chalk.green('client'),
|
653 | env: chalk.green('dev')
|
654 | })
|
655 | );
|
656 |
|
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 |
|
664 |
|
665 |
|
666 |
|
667 |
|
668 |
|
669 |
|
670 |
|
671 | console.log(
|
672 | ` ` +
|
673 | chalk.yellowBright('[koot/build] ') +
|
674 | __('build.build_start', {
|
675 | type: chalk.cyanBright(__(`appType.${appType}`)),
|
676 | stage: chalk.green('server'),
|
677 | env: chalk.green('dev')
|
678 | })
|
679 | );
|
680 | await start('server');
|
681 |
|
682 |
|
683 | await checkFileUpdate(pathServerJS, contentWaiting);
|
684 |
|
685 |
|
686 |
|
687 |
|
688 |
|
689 |
|
690 |
|
691 |
|
692 | await sleep(500);
|
693 |
|
694 | console.log(
|
695 | chalk.green('√ ') +
|
696 | chalk.yellowBright('[koot/build] ') +
|
697 | __('build.build_complete', {
|
698 | type: chalk.cyanBright(__(`appType.${appType}`)),
|
699 | stage: chalk.green('server'),
|
700 | env: chalk.green('dev')
|
701 | })
|
702 | );
|
703 |
|
704 |
|
705 | await start('run');
|
706 |
|
707 |
|
708 | const errServerRun = await checkFileUpdate(
|
709 | pathServerStartFlag,
|
710 | contentWaiting
|
711 | );
|
712 |
|
713 |
|
714 | await fs.remove(
|
715 | path.resolve(
|
716 | getDirDevTmp(cwd),
|
717 | filenameWebpackDevServerPortTemp
|
718 | )
|
719 | );
|
720 |
|
721 |
|
722 |
|
723 |
|
724 |
|
725 | let infosServer;
|
726 | try {
|
727 | infosServer = JSON.parse(errServerRun);
|
728 | } catch (e) {}
|
729 |
|
730 | if (
|
731 | typeof infosServer !== 'object' &&
|
732 | errServerRun !== ' ' &&
|
733 | errServerRun
|
734 | ) {
|
735 |
|
736 |
|
737 | console.log(' ');
|
738 |
|
739 | console.log(chalk.redBright(errServerRun));
|
740 |
|
741 | console.log(' ');
|
742 | return await exitHandler({
|
743 | silent: true
|
744 | });
|
745 | }
|
746 |
|
747 | await start('main');
|
748 | await checkFileUpdate(pathServerStartFlag, contentWaiting);
|
749 |
|
750 | return complete();
|
751 | });
|
752 | } catch (e) {
|
753 | encounterError(e);
|
754 | }
|
755 | };
|
756 |
|
757 | const openBrowserPage = () => {
|
758 | return opn(`http://localhost:${process.env.SERVER_PORT}/`);
|
759 | };
|
760 |
|
761 | run().catch(err => {
|
762 | console.error(err);
|
763 | });
|