gatsby
Version:
Blazing fast modern site generator for React
282 lines (270 loc) • 9.42 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.getDebugInfo = void 0;
var _path = _interopRequireDefault(require("path"));
var _tmp = _interopRequireDefault(require("tmp"));
var _execa = _interopRequireDefault(require("execa"));
var _detectPortInUseAndPrompt = require("../utils/detect-port-in-use-and-prompt");
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _signalExit = _interopRequireDefault(require("signal-exit"));
var _uuid = require("gatsby-core-utils/uuid");
var _path2 = require("gatsby-core-utils/path");
var _reporter = _interopRequireDefault(require("gatsby-cli/lib/reporter"));
var _getSslCert = require("../utils/get-ssl-cert");
var _gatsbyTelemetry = require("gatsby-telemetry");
// NOTE(@mxstbr): Do not use the reporter in this file, as that has side-effects on import which break structured logging
// Adapted from https://stackoverflow.com/a/16060619
const requireUncached = file => {
try {
delete require.cache[require.resolve(file)];
} catch (e) {
return null;
}
try {
return require(file);
} catch (e) {
return null;
}
};
// Return a user-supplied port otherwise the default Node.js debugging port
const getDebugPort = port => port !== null && port !== void 0 ? port : 9229;
const getDebugInfo = program => {
if (Object.prototype.hasOwnProperty.call(program, `inspect`)) {
return {
port: getDebugPort(program.inspect),
break: false
};
} else if (Object.prototype.hasOwnProperty.call(program, `inspectBrk`)) {
return {
port: getDebugPort(program.inspectBrk),
break: true
};
} else {
return null;
}
};
exports.getDebugInfo = getDebugInfo;
class ControllableScript {
constructor(script, debugInfo) {
this.script = script;
this.debugInfo = debugInfo;
}
start() {
var _global$__GATSBY;
const args = [];
const tmpFileName = _tmp.default.tmpNameSync({
tmpdir: _path.default.join(process.cwd(), `.cache`)
});
_fsExtra.default.outputFileSync(tmpFileName, this.script);
this.isRunning = true;
// Passing --inspect isn't necessary for the child process to launch a port but it allows some editors to automatically attach
if (this.debugInfo) {
if (this.debugInfo.break) {
args.push(`--inspect-brk=${this.debugInfo.port}`);
} else {
args.push(`--inspect=${this.debugInfo.port}`);
}
}
this.process = _execa.default.node(tmpFileName, args, {
env: {
...process.env,
GATSBY_NODE_GLOBALS: JSON.stringify((_global$__GATSBY = global.__GATSBY) !== null && _global$__GATSBY !== void 0 ? _global$__GATSBY : {})
},
stdio: [`inherit`, `inherit`, `inherit`, `ipc`]
});
}
async stop(signal = null, code) {
if (!this.process) {
throw new Error(`Trying to stop the process before starting it`);
}
this.isRunning = false;
try {
if (signal) {
this.process.kill(signal);
} else {
this.process.send({
type: `COMMAND`,
action: {
type: `EXIT`,
payload: code
}
}, () => {
// The try/catch won't suffice for this process.send
// So use the callback to manually catch the Error, otherwise it'll be thrown
// Ref: https://nodejs.org/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback
});
}
} catch (err) {
// Ignore error if process has crashed or already quit.
// Ref: https://github.com/gatsbyjs/gatsby/issues/28011#issuecomment-877302917
}
return new Promise(resolve => {
if (!this.process) {
throw new Error(`Trying to stop the process before starting it`);
}
this.process.on(`exit`, () => {
if (this.process) {
this.process.removeAllListeners();
}
this.process = undefined;
resolve();
});
});
}
onMessage(callback) {
if (!this.process) {
throw new Error(`Trying to attach message handler before process started`);
}
this.process.on(`message`, callback);
}
onExit(callback) {
if (!this.process) {
throw new Error(`Trying to attach exit handler before process started`);
}
this.process.on(`exit`, callback);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
send(msg) {
if (!this.process) {
throw new Error(`Trying to send a message before process started`);
}
this.process.send(msg);
}
}
let isRestarting;
// checks if a string is a valid ip
const REGEX_IP = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
module.exports = async program => {
global.__GATSBY = {
buildId: (0, _uuid.v4)(),
root: program.directory
};
// In some cases, port can actually be a string. But our codebase is expecting it to be a number.
// So we want to early just force it to a number to ensure we always act on a correct type.
program.port = parseInt(program.port + ``, 10);
const developProcessPath = (0, _path2.slash)(require.resolve(`./develop-process`));
try {
program.port = await (0, _detectPortInUseAndPrompt.detectPortInUseAndPrompt)(program.port, program.host);
} catch (e) {
if (e.message === `USER_REJECTED`) {
process.exit(0);
}
throw e;
}
// Run the actual develop server on a random port, and the proxy on the program port
// which users will access
const debugInfo = getDebugInfo(program);
const rootFile = file => _path.default.join(program.directory, file);
// Require gatsby-config.js before accessing process.env, to enable the user to change
// environment variables from the config file.
requireUncached(rootFile(`gatsby-config`));
const developPort = program.port;
// In order to enable custom ssl, --cert-file --key-file and -https flags must all be
// used together
if ((program[`cert-file`] || program[`key-file`]) && !program.https) {
_reporter.default.panic(`for custom ssl --https, --cert-file, and --key-file must be used together`);
}
// Check if https is enabled, then create or get SSL cert.
// Certs are named 'devcert' and issued to the host.
// NOTE(@mxstbr): We mutate program.ssl _after_ passing it
// to the develop process controllable script above because
// that would mean we double SSL browser => proxy => server
if (program.https) {
const sslHost = program.host === `0.0.0.0` || program.host === `::` ? `localhost` : program.host;
if (REGEX_IP.test(sslHost)) {
_reporter.default.panic(`You're trying to generate a ssl certificate for an IP (${sslHost}). Please use a hostname instead.`);
}
const ssl = await (0, _getSslCert.getSslCert)({
name: sslHost,
caFile: program[`ca-file`],
certFile: program[`cert-file`],
keyFile: program[`key-file`],
directory: program.directory
});
if (ssl) {
program.ssl = ssl;
}
}
const developProcess = new ControllableScript(`
const cmd = require(${JSON.stringify(developProcessPath)});
const args = ${JSON.stringify({
...program,
port: developPort,
// TODO(v5): remove
proxyPort: developPort,
debugInfo
})};
cmd(args);
`, debugInfo);
const handleChildProcessIPC = msg => {
if (msg.type === `HEARTBEAT`) return;
if (process.send) {
// Forward IPC
process.send(msg);
}
};
developProcess.start();
developProcess.onMessage(handleChildProcessIPC);
// Plugins can call `process.exit` which would be sent to `develop-process` (child process)
// This needs to be propagated back to the parent process
developProcess.onExit((code, signal) => {
try {
(0, _gatsbyTelemetry.flush)();
} catch (e) {
// nop
}
if (isRestarting) return;
if (signal !== null) {
process.kill(process.pid, signal);
return;
}
if (code !== null) {
process.exit(code);
}
// This should not happen:
// https://nodejs.org/api/child_process.html#child_process_event_exit
// The 'exit' event is emitted after the child process ends. If the process
// exited, code is the final exit code of the process, otherwise null.
// If the process terminated due to receipt of a signal, signal is the
// string name of the signal, otherwise null. One of the two will always be
// non - null.
//
// but just in case let do non-zero exit, because we are in situation
// we don't expect to be possible
process.exit(1);
});
// route ipc messaging to the original develop process
process.on(`message`, msg => {
developProcess.send(msg);
});
process.on(`SIGINT`, async () => {
await shutdownServices({
developProcess
}, `SIGINT`);
process.exit(0);
});
process.on(`SIGTERM`, async () => {
await shutdownServices({
developProcess
}, `SIGTERM`);
process.exit(0);
});
(0, _signalExit.default)((_code, signal) => {
shutdownServices({
developProcess
}, signal);
});
};
function shutdownServices({
developProcess
}, signal) {
try {
(0, _gatsbyTelemetry.flush)();
} catch (e) {
// nop
}
const services = [developProcess.stop(signal)];
return Promise.all(services).catch(() => {}).then(() => {});
}
//# sourceMappingURL=develop.js.map
;