UNPKG

13.1 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3const fs = require('fs-extra')
4const path = require('path')
5const program = require('commander')
6const pm2 = require('pm2')
7const chalk = require('chalk')
8const npmRunScript = require('npm-run-script')
9const opn = require('opn')
10
11const checkFileUpdate = require('../libs/check-file-change')
12const contentWaiting = require('../defaults/content-waiting')
13const { keyFileProjectConfigTemp } = require('../defaults/before-build')
14
15const __ = require('../utils/translate')
16const sleep = require('../utils/sleep')
17const getPort = require('../utils/get-port')
18// const spinner = require('../utils/spinner')
19// const readBuildConfigFile = require('../utils/read-build-config-file')
20const getAppType = require('../utils/get-app-type')
21const setEnvFromCommand = require('../utils/set-env-from-command')
22const getChunkmapPath = require('../utils/get-chunkmap-path')
23const initNodeEnv = require('../utils/init-node-env')
24const getCwd = require('../utils/get-cwd')
25const getPathnameDevServerStart = require('../utils/get-pathname-dev-server-start')
26// const terminate = require('../utils/terminate')
27const removeTempProjectConfig = require('../libs/remove-temp-project-config')
28const validateConfig = require('../libs/validate-config')
29
30program
31 .version(require('../package').version, '-v, --version')
32 .usage('[options]')
33 .option('-c, --client', 'Set STAGE to CLIENT')
34 .option('-s, --server', 'Set STAGE to SERVER')
35 .option('-g, --global', 'Connect to global PM2')
36 .option('--stage <stage>', 'Set STAGE')
37 .option('--config <config-file-path>', 'Set config file')
38 .option('--type <project-type>', 'Set project type')
39 .option('--no-open', 'Don\'t open browser automatically')
40 .parse(process.argv)
41
42/**
43 * 进入开发环境
44 * ****************************************************************************
45 * 同构 (isomorphic)
46 * 以 PM2 进程方式顺序执行以下流程
47 * 1. 启动 webpack-dev-server (STAGE: client)
48 * 2. 启动 webpack (watch mode) (STAGE: server)
49 * 3. 运行 /server/index.js
50 * ****************************************************************************
51 * 单页面应用 (SPA)
52 * 强制设置 STAGE 为 client,并启动 webpack-dev-server
53 * ****************************************************************************
54 */
55const run = async () => {
56
57 // 清除所有临时配置文件
58 await removeTempProjectConfig()
59
60 // 清空 log
61 process.stdout.write('\x1B[2J\x1B[0f')
62
63 const {
64 client, server,
65 stage: _stage,
66 config,
67 type,
68 global = false,
69 open = true,
70 } = program
71
72 initNodeEnv()
73 setEnvFromCommand({
74 config,
75 type,
76 })
77
78 let stage = _stage ? _stage : (client ? 'client' : (server ? 'server' : false))
79
80 // if (!stage) {
81 // console.log(
82 // chalk.redBright('× ')
83 // + __('dev.missing_stage', {
84 // example: 'koot-dev ' + chalk.green('--client'),
85 // indent: ' '
86 // })
87 // )
88 // return
89 // }
90
91 // 读取项目信息
92 // const { dist, port } = await readBuildConfigFile()
93 const {
94 dist,
95 port,
96 [keyFileProjectConfigTemp]: fileProjectConfigTemp
97 } = await validateConfig()
98 const appType = await getAppType()
99 const cwd = getCwd()
100 const packageInfo = await fs.readJson(path.resolve(cwd, 'package.json'))
101 const {
102 name
103 } = packageInfo
104
105 // 如果有临时项目配置文件,更改环境变量
106 if (fileProjectConfigTemp) {
107 process.env.KOOT_PROJECT_CONFIG_PATHNAME = fileProjectConfigTemp
108 }
109
110 // 如果为 SPA,强制设置 STAGE
111 if (process.env.WEBPACK_BUILD_TYPE === 'spa') {
112 process.env.WEBPACK_BUILD_STAGE = 'client'
113 stage = 'client'
114 }
115
116 // 如果配置中存在 port,修改环境变量
117 if (typeof port !== 'undefined')
118 process.env.SERVER_PORT = getPort(port, 'dev')
119
120 // 如果设置了 stage,仅运行该 stage
121 if (stage) {
122 const cmd = `koot-build --stage ${stage} --env dev`
123 const child = npmRunScript(cmd, {})
124 child.once('error', (error) => {
125 console.trace(error)
126 process.exit(1)
127 })
128 child.once('exit', (/*exitCode*/) => {
129 // console.trace('exit in', exitCode)
130 // process.exit(exitCode)
131 })
132 return
133 }
134
135 // 没有设置 STAGE,开始 PM2 进程
136 let waitingSpinner = false
137 // spinner(
138 // chalk.yellowBright('[koot/build] ')
139 // + __('build.build_start', {
140 // type: chalk.cyanBright(appType),
141 // stage: chalk.green('client'),
142 // env: chalk.green('dev'),
143 // })
144 // )
145
146 const processes = []
147 const pathChunkmap = getChunkmapPath(dist)
148 const pathServerJS = path.resolve(dist, 'server/index.js')
149 const pathServerStartFlag = getPathnameDevServerStart()
150
151 { // 在脚本进程关闭/结束时,同时关闭打开的 PM2 进程
152 process.stdin.resume()
153 const removeListeners = () => {
154 process.removeListener('exit', exitHandler)
155 process.removeListener('SIGINT', exitHandler)
156 process.removeListener('SIGUSR1', exitHandler)
157 process.removeListener('SIGUSR2', exitHandler)
158 process.removeListener('uncaughtException', exitHandler)
159 }
160 const exitHandler = async (/*options, err*/) => {
161 await removeTempProjectConfig()
162 if (Array.isArray(processes) && processes.length) {
163 if (waitingSpinner) waitingSpinner.stop()
164 await sleep(300)
165 process.stdout.write('\x1B[2J\x1B[0f')
166 console.log(chalk.redBright('Waiting for killing processes...'))
167 for (let process of processes) {
168 await new Promise((resolve, reject) => {
169 // console.log(process)
170 pm2.delete(process.name, (err, proc) => {
171 // console.log('err', err)
172 // console.log('proc', proc)
173 if (Array.isArray(proc) && proc.every(p => p.status === 'stopped'))
174 return resolve(proc)
175 if (err) return reject(err)
176 reject('stop failed')
177 })
178 })
179 }
180 pm2.disconnect()
181 await sleep(300)
182 // w.stop()
183 try {
184 // console.log(process.pid)
185 removeListeners()
186 console.log('Press CTRL+C again to exit.')
187 // process.kill(process.pid)
188 process.exit(1)
189 } catch (e) {
190 console.log(e)
191 }
192 } else {
193 removeListeners()
194 // 清空 log
195 process.stdout.write('\x1B[2J\x1B[0f')
196 console.log('Press CTRL+C again to exit.')
197 process.exit(1)
198 }
199 }
200 // do something when app is closing
201 process.on('exit', exitHandler);
202 // catches ctrl+c event
203 process.on('SIGINT', exitHandler);
204 // catches "kill pid" (for example: nodemon restart)
205 process.on('SIGUSR1', exitHandler);
206 process.on('SIGUSR2', exitHandler);
207 // catches uncaught exceptions
208 process.on('uncaughtException', exitHandler);
209 }
210
211 // 根据 stage 开启 PM2 进程
212 const start = (stage) => new Promise(async (resolve, reject) => {
213 const pathLogOut = path.resolve(cwd, `logs/dev/${stage}.log`)
214 const pathLogErr = path.resolve(cwd, `logs/dev/${stage}-error.log`)
215 if (fs.existsSync(pathLogOut)) await fs.remove(pathLogOut)
216 if (fs.existsSync(pathLogErr)) await fs.remove(pathLogErr)
217 await fs.ensureFile(pathLogOut)
218 await fs.ensureFile(pathLogErr)
219 const config = {
220 name: `dev-${stage}-${name}`,
221 script: path.resolve(__dirname, './build.js'),
222 args: `--stage ${stage} --env dev`,
223 cwd: cwd,
224 output: pathLogOut,
225 error: pathLogErr,
226 autorestart: false,
227 }
228 if (stage === 'run') {
229 Object.assign(config, {
230 script: pathServerJS,
231 watch: [pathServerJS],
232 watch_options: {
233 usePolling: true,
234 },
235 // autorestart: true,
236 })
237 delete config.args
238 // console.log(config)
239 // await fs.writeJson(
240 // path.resolve(__dirname, '../1.json'),
241 // config,
242 // {
243 // spaces: 4
244 // }
245 // )
246 }
247 // console.log(config)
248 // processes.push(config.name)
249 pm2.start(
250 config,
251 (err, proc) => {
252 // console.log(err)
253 if (err) return reject(err)
254 processes.push({
255 ...proc,
256 name: config.name,
257 })
258 // console.log(JSON.stringify(proc))
259 // fs.writeJsonSync(
260 // path.resolve(__dirname, '../2.json'),
261 // proc,
262 // {
263 // spaces: 4
264 // }
265 // )
266 resolve(proc)
267 }
268 )
269 })
270
271 // 连接 PM2
272 // console.log('noDaemon', !global)
273 pm2.connect(!global, async (err) => {
274 if (err) {
275 // console.error(err)
276 process.exit(2)
277 }
278
279 console.log(
280 ` `
281 + chalk.yellowBright('[koot/build] ')
282 + __('build.build_start', {
283 type: chalk.cyanBright(appType),
284 stage: chalk.green('client'),
285 env: chalk.green('dev'),
286 })
287 )
288
289 // 清空 chunkmap 文件
290 await fs.ensureFile(pathChunkmap)
291 await fs.writeFile(pathChunkmap, contentWaiting)
292
293 // 清空 server 打包结果文件
294 await fs.ensureFile(pathServerJS)
295 await fs.writeFile(pathServerJS, contentWaiting)
296
297 // 清空服务器启动成功标识文件
298 await fs.ensureFile(pathServerStartFlag)
299 await fs.writeFile(pathServerStartFlag, contentWaiting)
300
301 // 启动 client webpack-dev-server
302 await start('client')
303
304 // 监视 chunkmap 文件,如果修改,进入下一步
305 await checkFileUpdate(pathChunkmap, contentWaiting)
306 // waitingSpinner.succeed()
307 console.log(
308 chalk.green('√ ')
309 + chalk.yellowBright('[koot/build] ')
310 + __('build.build_complete', {
311 type: chalk.cyanBright(appType),
312 stage: chalk.green('client'),
313 env: chalk.green('dev'),
314 })
315 )
316
317 // 启动 server webpack
318 // waitingSpinner = spinner(
319 // chalk.yellowBright('[koot/build] ')
320 // + __('build.build_start', {
321 // type: chalk.cyanBright(appType),
322 // stage: chalk.green('server'),
323 // env: chalk.green('dev'),
324 // })
325 // )
326 console.log(
327 ` `
328 + chalk.yellowBright('[koot/build] ')
329 + __('build.build_start', {
330 type: chalk.cyanBright(appType),
331 stage: chalk.green('server'),
332 env: chalk.green('dev'),
333 })
334 )
335 await start('server')
336
337 // 监视 server.js 文件,如果修改,进入下一步
338 await checkFileUpdate(pathServerJS, contentWaiting)
339 // waitingSpinner.succeed()
340
341 // 执行
342 // waitingSpinner = spinner(
343 // chalk.yellowBright('[koot/build] ')
344 // + 'waiting...'
345 // )
346 await start('run')
347
348 // 监视服务器启动标识文件,如果修改,进入下一步
349 await checkFileUpdate(pathServerStartFlag, contentWaiting)
350 await sleep(100)
351
352 console.log(
353 chalk.green('√ ')
354 + chalk.yellowBright('[koot/build] ')
355 + __('build.build_complete', {
356 type: chalk.cyanBright(appType),
357 stage: chalk.green('server'),
358 env: chalk.green('dev'),
359 })
360 )
361
362 // waitingSpinner.stop()
363 // waitingSpinner = undefined
364 npmRunScript(`pm2 logs`)
365
366 if (open)
367 return opn(`http://localhost:${process.env.SERVER_PORT}/`)
368 })
369}
370
371run()