UNPKG

9.39 kBPlain TextView Raw
1#!/usr/bin/env node
2
3const parseArgs = require('minimist')
4
5const argv = parseArgs(process.argv.slice(2), {
6 alias: {
7 p: 'port',
8 H: 'hostname',
9 g: 'gzip',
10 s: 'silent',
11 colors: 'colors',
12 o: 'open',
13 c: 'cache',
14 m: 'micro',
15 history: 'history',
16 https: 'https',
17 C: 'cert',
18 K: 'key',
19 P: 'proxy',
20 h: 'help'
21 },
22 boolean: ['g', 'https', 'colors', 'S', 'history', 'h'],
23 string: ['H', 'C', 'K'],
24 default: {
25 p: process.env.PORT || 4000,
26 H: process.env.HOSTNAME || '0.0.0.0',
27 g: true,
28 c: 24 * 60 * 60,
29 m: 1,
30 colors: true
31 }
32})
33
34if (argv.help) {
35 console.log(`
36 Description
37 Start a HTTP(S) server on a folder.
38
39 Usage
40 $ quasar serve [path]
41 $ quasar serve . # serve current folder
42
43 If you serve a SSR folder built with the CLI then
44 control is yielded to /index.js and params have no effect.
45
46 Options
47 --port, -p Port to use (default: 4000)
48 --hostname, -H Address to use (default: 0.0.0.0)
49 --gzip, -g Compress content (default: true)
50 --silent, -s Supress log message
51 --colors Log messages with colors (default: true)
52 --open, -o Open browser window after starting
53 --cache, -c <number> Cache time (max-age) in seconds;
54 Does not apply to /service-worker.js
55 (default: 86400 - 24 hours)
56 --micro, -m <seconds> Use micro-cache (default: 1 second)
57 --history Use history api fallback;
58 All requests fallback to index.html
59 --https Enable HTTPS
60 --cert, -C [path] Path to SSL cert file (Optional)
61 --key, -K [path] Path to SSL key file (Optional)
62 --proxy <file.js> Proxy specific requests defined in file;
63 File must export Array ({ path, rule })
64 See example below. "rule" is defined at:
65 https://github.com/chimurai/http-proxy-middleware
66 --help, -h Displays this message
67
68 Proxy file example
69 module.exports = [
70 {
71 path: '/api',
72 rule: { target: 'http://www.example.org' }
73 }
74 ]
75 --> will be transformed into app.use(path, httpProxyMiddleware(rule))
76 `)
77 process.exit(0)
78}
79
80const
81 fs = require('fs'),
82 path = require('path')
83
84const root = getAbsolutePath(argv._[0] || '.')
85const resolve = p => path.resolve(root, p)
86
87function getAbsolutePath (pathParam) {
88 return path.isAbsolute(pathParam)
89 ? pathParam
90 : path.join(process.cwd(), pathParam)
91}
92
93const
94 pkgFile = resolve('package.json'),
95 indexFile = resolve('index.js')
96
97let ssrDetected = false
98
99if (fs.existsSync(pkgFile) && fs.existsSync(indexFile)) {
100 const pkg = require(pkgFile)
101 if (pkg.quasar && pkg.quasar.ssr) {
102 console.log('Quasar SSR folder detected.')
103 console.log('Yielding control to its own webserver.')
104 console.log()
105 ssrDetected = true
106 require(indexFile)
107 }
108}
109
110if (ssrDetected === false) {
111 let green, grey, red
112
113 if (argv.colors) {
114 const chalk = require('chalk')
115 green = chalk.green
116 grey = chalk.grey
117 red = chalk.red
118 }
119 else {
120 green = grey = red = text => text
121 }
122
123 const
124 express = require('express'),
125 microCacheSeconds = argv.micro
126 ? parseInt(argv.micro, 10)
127 : false
128
129 function serve (path, cache) {
130 return express.static(resolve(path), {
131 maxAge: cache ? parseInt(argv.cache, 10) * 1000 : 0
132 })
133 }
134
135 const app = express()
136
137 if (!argv.silent) {
138 app.get('*', (req, res, next) => {
139 console.log(
140 `GET ${green(req.url)} ${grey('[' + req.ip + ']')} ${new Date()}`
141 )
142 next()
143 })
144 }
145
146 if (argv.gzip) {
147 const compression = require('compression')
148 app.use(compression({ threshold: 0 }))
149 }
150
151 const serviceWorkerFile = resolve('service-worker.js')
152 if (fs.existsSync(serviceWorkerFile)) {
153 app.use('/service-worker.js', serve('service-worker.js'))
154 }
155
156 if (argv.history) {
157 const history = require('connect-history-api-fallback')
158 app.use(history())
159 }
160
161 app.use('/', serve('.', true))
162
163 if (microCacheSeconds) {
164 const microcache = require('route-cache')
165 app.use(
166 microcache.cacheSeconds(
167 microCacheSeconds,
168 req => req.originalUrl
169 )
170 )
171 }
172
173 if (argv.proxy) {
174 let file = argv.proxy = getAbsolutePath(argv.proxy)
175 if (!fs.existsSync(file)) {
176 console.error('Proxy definition file not found! ' + file)
177 process.exit(1)
178 }
179 file = require(file)
180
181 const proxy = require('http-proxy-middleware')
182 file.forEach(entry => {
183 app.use(entry.path, proxy(entry.rule))
184 })
185 }
186
187 app.get('*', (req, res) => {
188 res.setHeader('Content-Type', 'text/html')
189 res.status(404).send('404 | Page Not Found')
190 if (!argv.silent) {
191 console.log(red(` 404 on ${req.url}`))
192 }
193 })
194
195 function getHostname (host) {
196 return host === '0.0.0.0'
197 ? 'localhost'
198 : host
199 }
200
201 getServer(app).listen(argv.port, argv.hostname, () => {
202 const
203 url = `http${argv.https ? 's' : ''}://${getHostname(argv.hostname)}:${argv.port}`,
204 { version } = require('../package.json')
205
206 const info = [
207 ['Quasar CLI', `v${version}`],
208 ['Listening at', url],
209 ['Web server root', root],
210 argv.https ? ['HTTPS', 'enabled'] : '',
211 argv.gzip ? ['Gzip', 'enabled'] : '',
212 ['Cache (max-age)', argv.cache || 'disabled'],
213 microCacheSeconds ? ['Micro-cache', microCacheSeconds + 's'] : '',
214 argv.history ? ['History mode', 'enabled'] : '',
215 argv.proxy ? ['Proxy definitions', argv.proxy] : ''
216 ]
217 .filter(msg => msg)
218 .map(msg => ' ' + msg[0].padEnd(20, '.') + ' ' + green(msg[1]))
219
220 console.log('\n' + info.join('\n') + '\n')
221
222 if (argv.open) {
223 const isMinimalTerminal = require('../lib/helpers/is-minimal-terminal')
224 if (!isMinimalTerminal) {
225 const opn = require('opn')
226 opn(url)
227 }
228 }
229 })
230
231 function getServer (app) {
232 if (!argv.https) {
233 return app
234 }
235
236 let fakeCert, key, cert
237
238 if (argv.key && argv.cert) {
239 key = getAbsolutePath(argv.key)
240 cert = getAbsolutePath(argv.cert)
241
242 if (fs.existsSync(key)) {
243 key = fs.readFileSync(key)
244 }
245 else {
246 console.error('SSL key file not found!' + key)
247 process.exit(1)
248 }
249
250 if (fs.existsSync(cert)) {
251 cert = fs.readFileSync(cert)
252 }
253 else {
254 console.error('SSL cert file not found!' + cert)
255 process.exit(1)
256 }
257 }
258 else {
259 // Use a self-signed certificate if no certificate was configured.
260 // Cycle certs every 24 hours
261 const certPath = path.join(__dirname, '../ssl-server.pem')
262 let certExists = fs.existsSync(certPath)
263
264 if (certExists) {
265 const certStat = fs.statSync(certPath)
266 const certTtl = 1000 * 60 * 60 * 24
267 const now = new Date()
268
269 // cert is more than 30 days old
270 if ((now - certStat.ctime) / certTtl > 30) {
271 console.log(' SSL Certificate is more than 30 days old. Removing.')
272 const { removeSync } = require('fs-extra')
273 removeSync(certPath)
274 certExists = false
275 }
276 }
277
278 if (!certExists) {
279 console.log(' Generating self signed SSL Certificate...')
280 console.log(' DO NOT use this self-signed certificate in production!')
281
282 const selfsigned = require('selfsigned')
283 const pems = selfsigned.generate(
284 [{ name: 'commonName', value: 'localhost' }],
285 {
286 algorithm: 'sha256',
287 days: 30,
288 keySize: 2048,
289 extensions: [{
290 name: 'basicConstraints',
291 cA: true
292 }, {
293 name: 'keyUsage',
294 keyCertSign: true,
295 digitalSignature: true,
296 nonRepudiation: true,
297 keyEncipherment: true,
298 dataEncipherment: true
299 }, {
300 name: 'subjectAltName',
301 altNames: [
302 {
303 // type 2 is DNS
304 type: 2,
305 value: 'localhost'
306 },
307 {
308 type: 2,
309 value: 'localhost.localdomain'
310 },
311 {
312 type: 2,
313 value: 'lvh.me'
314 },
315 {
316 type: 2,
317 value: '*.lvh.me'
318 },
319 {
320 type: 2,
321 value: '[::1]'
322 },
323 {
324 // type 7 is IP
325 type: 7,
326 ip: '127.0.0.1'
327 },
328 {
329 type: 7,
330 ip: 'fe80::1'
331 }
332 ]
333 }]
334 }
335 )
336
337 try {
338 fs.writeFileSync(certPath, pems.private + pems.cert, { encoding: 'utf-8' })
339 }
340 catch (err) {
341 console.error(' Cannot write certificate file ' + certPath)
342 console.error(' Aborting...')
343 process.exit(1)
344 }
345 }
346
347 fakeCert = fs.readFileSync(certPath)
348 }
349
350 return require('https').createServer({
351 key: key || fakeCert,
352 cert: cert || fakeCert
353 }, app)
354 }
355}