Files
nebula.js/commands/serve/lib/serve.js
2020-04-06 14:04:15 +02:00

179 lines
4.2 KiB
JavaScript

const path = require('path');
const fs = require('fs');
const portfinder = require('portfinder');
const extend = require('extend');
const yargs = require('yargs');
const WebSocket = require('ws').Server;
const chokidar = require('chokidar');
const build = require('@nebula.js/cli-build');
const initConfig = require('./init-config');
const webpackServe = require('./webpack.serve.js');
const useEngine = require('./engine');
const initiateWatch = async ({ snPath, snName, host }) => {
// TODO - timeout
let onInitiated;
const done = new Promise((resolve) => {
onInitiated = resolve;
});
const wsPort = await portfinder.getPortPromise({ port: 5001, host });
const ws = new WebSocket({
port: wsPort,
clientTracking: true,
});
const choker = chokidar.watch(snPath, {
ignoreInitial: false,
disableGlobbing: true,
});
choker.on('add', () => {
onInitiated();
});
choker.on('change', () => {
[...ws.clients].forEach((client) => {
client.send(
JSON.stringify({
changed: [snName],
})
);
});
});
const cache = {};
return {
addRoutes(app) {
app.get('/pkg/:name', async (req, res) => {
const { name } = req.params;
await done;
let file;
if (cache[name]) {
file = cache[name];
} else if (name === snName) {
file = snPath;
} else if (/\.map$/.test(name)) {
const sources = Object.keys(cache);
for (let i = 0; i < sources.length; i++) {
const p = cache[sources[i]];
const filename = path.basename(p);
if (`${filename}.map` === name) {
file = path.resolve(path.dirname(p), name);
break;
}
}
} else {
file = require.resolve(name, {
paths: [process.cwd()],
});
}
cache[name] = file;
if (fs.existsSync(file)) {
const f = fs.readFileSync(file);
res.send(f);
} else {
res.sendStatus('404');
}
});
},
async close() {
await ws.close();
await choker.close();
},
port: wsPort,
};
};
module.exports = async (argv) => {
const context = process.cwd();
let defaultServeConfig = {};
if (!argv.$0) {
defaultServeConfig = initConfig(yargs([])).argv;
}
const serveConfig = extend(true, {}, defaultServeConfig, argv);
let stopEngine = () => {};
if (serveConfig.ACCEPT_EULA) {
stopEngine = await useEngine(serveConfig);
}
const host = serveConfig.host || 'localhost';
const port = serveConfig.port || (await portfinder.getPortPromise({ host }));
const enigmaConfig = serveConfig.enigma;
let snPath;
let snName;
let watcher;
let snUrl;
if (/^https?/.test(serveConfig.entry)) {
snName = 'remote';
snUrl = serveConfig.entry;
} else if (serveConfig.entry) {
snPath = path.resolve(context, serveConfig.entry);
const parsed = path.parse(snPath);
snName = parsed.name;
} else {
if (serveConfig.build !== false) {
watcher = await build({
watch: true,
config: serveConfig.config,
});
}
try {
const externalPkg = require(path.resolve(context, 'package.json')); // eslint-disable-line global-require
const externalEntry = externalPkg.main;
snName = externalPkg.name;
snPath = path.resolve(context, externalEntry);
} catch (e) {
//
}
}
const ww = snUrl ? null : await initiateWatch({ snPath, snName: serveConfig.type || snName, host });
const server = await webpackServe({
host,
port,
disableHostCheck: serveConfig.disableHostCheck,
enigmaConfig,
webIntegrationId: serveConfig.webIntegrationId,
snName: serveConfig.type || snName,
snUrl,
dev: process.env.MONO === 'true',
open: serveConfig.open !== false,
entryWatcher: ww,
watcher,
serveConfig,
});
const close = async () => {
await stopEngine();
if (watcher) {
watcher.close();
}
if (ww) {
await ww.close();
}
server.close();
};
['SIGINT', 'SIGTERM'].forEach((signal) => {
process.on(signal, close);
});
// eslint-disable-next-line consistent-return
return {
url: server.url,
close,
};
};