#!/usr/bin/env node const parseArgs = require('minimist') const argv = parseArgs(process.argv.slice(2), { alias: { p: 'port', H: 'hostname', g: 'gzip', s: 'silent', colors: 'colors', o: 'open', c: 'cache', m: 'micro', history: 'history', https: 'https', C: 'cert', K: 'key', P: 'proxy', h: 'help' }, boolean: ['g', 'https', 'colors', 'S', 'history', 'h'], string: ['H', 'C', 'K'], default: { p: process.env.PORT || 4000, H: process.env.HOSTNAME || '0.0.0.0', g: true, c: 24 * 60 * 60, m: 1, colors: true } }) if (argv.help) { console.log(` Description Start a HTTP(S) server on a folder. Usage $ quasar serve [path] $ quasar serve . # serve current folder If you serve a SSR folder built with the CLI then control is yielded to /index.js and params have no effect. Options --port, -p Port to use (default: 8080) --hostname, -H Address to use (default: 0.0.0.0) --gzip, -g Compress content (default: true) --silent, -s Supress log message --colors Log messages with colors (default: true) --open, -o Open browser window after starting --cache, -c Cache time (max-age) in seconds; Does not apply to /service-worker.js (default: 86400 - 24 hours) --micro, -m Use micro-cache (default: 1 second) --history Use history api fallback; All requests fallback to index.html --https Enable HTTPS --cert, -C [path] Path to SSL cert file (Optional) --key, -K [path] Path to SSL key file (Optional) --proxy Proxy specific requests defined in file; File must export Array ({ path, rule }) See example below. "rule" is defined at: https://github.com/chimurai/http-proxy-middleware --help, -h Displays this message Proxy file example module.exports = [ { path: '/api', rule: { target: 'http://www.example.org' } } ] --> will be transformed into app.use(path, httpProxyMiddleware(rule)) `) process.exit(0) } const fs = require('fs'), path = require('path') const root = getAbsolutePath(argv._[0] || '.') const resolve = p => path.resolve(root, p) function getAbsolutePath (pathParam) { return path.isAbsolute(pathParam) ? pathParam : path.join(process.cwd(), pathParam) } const pkgFile = resolve('package.json'), indexFile = resolve('index.js') let ssrDetected = false if (fs.existsSync(pkgFile) && fs.existsSync(indexFile)) { const pkg = require(pkgFile) if (pkg.quasar && pkg.quasar.ssr) { console.log('Quasar SSR folder detected.') console.log('Yielding control to its own webserver.') console.log() ssrDetected = true require(indexFile) } } if (ssrDetected === false) { let green, grey, red if (argv.colors) { const chalk = require('chalk') green = chalk.green grey = chalk.grey red = chalk.red } else { green = grey = red = text => text } const express = require('express'), microCacheSeconds = argv.micro ? parseInt(argv.micro, 10) : false function serve (path, cache) { return express.static(resolve(path), { maxAge: cache ? parseInt(argv.cache, 10) * 1000 : 0 }) } const app = express() if (!argv.silent) { app.get('*', (req, res, next) => { console.log( `GET ${green(req.url)} ${grey('[' + req.ip + ']')} ${new Date()}` ) next() }) } if (argv.gzip) { const compression = require('compression') app.use(compression({ threshold: 0 })) } const serviceWorkerFile = resolve('service-worker.js') if (fs.existsSync(serviceWorkerFile)) { app.use('/service-worker.js', serve('service-worker.js')) } if (argv.history) { const history = require('connect-history-api-fallback') app.use(history()) } app.use('/', serve('.', true)) if (microCacheSeconds) { const microcache = require('route-cache') app.use( microcache.cacheSeconds( microCacheSeconds, req => req.originalUrl ) ) } if (argv.proxy) { let file = argv.proxy = getAbsolutePath(argv.proxy) if (!fs.existsSync(file)) { console.error('Proxy definition file not found! ' + file) process.exit(1) } file = require(file) const proxy = require('http-proxy-middleware') file.forEach(entry => { app.use(entry.path, proxy(entry.rule)) }) } app.get('*', (req, res) => { res.setHeader('Content-Type', 'text/html') res.status(404).send('404 | Page Not Found') if (!argv.silent) { console.log(red(` 404 on ${req.url}`)) } }) function getHostname (host) { return host === '0.0.0.0' ? 'localhost' : host } getServer(app).listen(argv.port, argv.hostname, () => { const url = `http${argv.https ? 's' : ''}://${getHostname(argv.hostname)}:${argv.port}`, { version } = require('../package.json') const info = [ ['Quasar CLI', `v${version}`], ['Listening at', url], ['Web server root', root], argv.https ? ['HTTPS', 'enabled'] : '', argv.gzip ? ['Gzip', 'enabled'] : '', ['Cache (max-age)', argv.cache || 'disabled'], microCacheSeconds ? ['Micro-cache', microCacheSeconds + 's'] : '', argv.history ? ['History mode', 'enabled'] : '', argv.proxy ? ['Proxy definitions', argv.proxy] : '' ] .filter(msg => msg) .map(msg => ' ' + msg[0].padEnd(20, '.') + ' ' + green(msg[1])) console.log('\n' + info.join('\n') + '\n') if (argv.open) { const isMinimalTerminal = require('../lib/helpers/is-minimal-terminal') if (!isMinimalTerminal) { const opn = require('opn') opn(url) } } }) function getServer (app) { if (!argv.https) { return app } let fakeCert, key, cert if (argv.key && argv.cert) { key = getAbsolutePath(argv.key) cert = getAbsolutePath(argv.cert) if (fs.existsSync(key)) { key = fs.readFileSync(key) } else { console.error('SSL key file not found!' + key) process.exit(1) } if (fs.existsSync(cert)) { cert = fs.readFileSync(cert) } else { console.error('SSL cert file not found!' + cert) process.exit(1) } } else { // Use a self-signed certificate if no certificate was configured. // Cycle certs every 24 hours const certPath = path.join(__dirname, '../ssl-server.pem') let certExists = fs.existsSync(certPath) if (certExists) { const certStat = fs.statSync(certPath) const certTtl = 1000 * 60 * 60 * 24 const now = new Date() // cert is more than 30 days old if ((now - certStat.ctime) / certTtl > 30) { console.log(' SSL Certificate is more than 30 days old. Removing.') const { removeSync } = require('fs-extra') removeSync(certPath) certExists = false } } if (!certExists) { console.log(' Generating self signed SSL Certificate...') console.log(' DO NOT use this self-signed certificate in production!') const selfsigned = require('selfsigned') const pems = selfsigned.generate( [{ name: 'commonName', value: 'localhost' }], { algorithm: 'sha256', days: 30, keySize: 2048, extensions: [{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true }, { name: 'subjectAltName', altNames: [ { // type 2 is DNS type: 2, value: 'localhost' }, { type: 2, value: 'localhost.localdomain' }, { type: 2, value: 'lvh.me' }, { type: 2, value: '*.lvh.me' }, { type: 2, value: '[::1]' }, { // type 7 is IP type: 7, ip: '127.0.0.1' }, { type: 7, ip: 'fe80::1' } ] }] } ) try { fs.writeFileSync(certPath, pems.private + pems.cert, { encoding: 'utf-8' }) } catch (err) { console.error(' Cannot write certificate file ' + certPath) console.error(' Aborting...') process.exit(1) } } fakeCert = fs.readFileSync(certPath) } return require('https').createServer({ key: key || fakeCert, cert: cert || fakeCert }, app) } }