UNPKG

netlify-cli

Version:

Netlify command line tool

203 lines (170 loc) • 5.58 kB
const { EOL } = require('os') const path = require('path') const process = require('process') const rl = require('readline') const { getTrafficMeshForLocalSystem } = require('@netlify/traffic-mesh-agent') const execa = require('execa') const waitPort = require('wait-port') const { getPathInProject } = require('../lib/settings') const { clearSpinner, startSpinner, stopSpinner } = require('../lib/spinner') const { createDeferred } = require('./deferred') const { NETLIFYDEVLOG, NETLIFYDEVERR, NETLIFYDEVWARN } = require('./logo') const EDGE_HANDLERS_BUNDLER_CLI_PATH = path.resolve(require.resolve('@netlify/plugin-edge-handlers'), '..', 'cli.js') const startForwardProxy = async ({ port, frameworkPort, functionsPort, publishDir, log, debug, locationDb }) => { const args = [ 'start', 'local', '--port', port, '--forward-proxy', `http://localhost:${frameworkPort}`, '--watch', publishDir, '--bundler', EDGE_HANDLERS_BUNDLER_CLI_PATH, '--log-file', getPathInProject(['logs', 'traffic-mesh.log']), '--progress', ] if (functionsPort) { args.push('--local-services-uri', `http://localhost:${functionsPort}`) } if (debug) { args.push('--debug') } if (locationDb) { args.push('--geo', locationDb) } const { subprocess } = runProcess({ log, args }) const { forwarder, firstBundleReady } = forwardMessagesToLog({ log, subprocess }) subprocess.on('close', process.exit) subprocess.on('SIGINT', process.exit) subprocess.on('SIGTERM', process.exit) ;['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'].forEach((signal) => { process.on(signal, () => { forwarder.close() const sig = signal === 'exit' ? 'SIGTERM' : signal subprocess.kill(sig, { forceKillAfterTimeout: PROXY_EXIT_TIMEOUT, }) }) }) // Wait until the first traffic-mesh bundle is ready // // In case of errors, this delays the startup process until the errors are fixed await firstBundleReady try { const open = await waitPort({ port, output: 'silent', timeout: PROXY_READY_TIMEOUT }) if (!open) { throw new Error(`Timed out waiting for forward proxy to be ready on port '${port}'`) } return `http://localhost:${port}` } catch (error) { log(`${NETLIFYDEVERR}`, error) } } const forwardMessagesToLog = ({ log, subprocess }) => { const { promise: firstBundleReady, reject: firstBundleReject, resolve: firstBundleResolve } = createDeferred() let currentId = null let lastError = null let lastWasSuccess = false let spinner = null const reset = () => { currentId = null spinner = null } const forwarder = rl.createInterface({ input: subprocess.stderr, }) forwarder .on('line', (line) => { let data try { data = JSON.parse(line.trim()) } catch (error) { log(`${NETLIFYDEVERR} Cannot parse log line as JSON: ${line.trim()}${EOL}${EOL}${error}`) return } const { error, id, type } = data switch (type) { case 'bundle:start': currentId = id if (!spinner) { spinner = startSpinner({ text: 'Processing request remaps, header rules and Edge Handlers...' }) } break case 'bundle:success': if (currentId !== id) { return } // Clear spinner if there already is a log line indicating success above, // instead of appending another line of "Yay, your project was bundled!" if (lastWasSuccess) { clearSpinner({ spinner }) } else { stopSpinner({ spinner, error: false, text: 'Done processing request remaps, header rules and Edge Handlers', }) } lastWasSuccess = true lastError = null firstBundleResolve() reset() break case 'bundle:fail': { if (currentId !== id) { return } // Only show the error if it's new const errorMsg = (error && error.msg) || 'Failed processing request remaps, header rules or Edge Handlers' if (errorMsg === lastError) { clearSpinner({ spinner }) } else { stopSpinner({ spinner, error: true, text: errorMsg, }) log( `${NETLIFYDEVLOG} Change any project configuration file (netlify.toml, _headers, _redirects) or any Edge Handlers file to trigger a re-bundle`, ) } lastWasSuccess = false lastError = errorMsg reset() break } default: log(`${NETLIFYDEVWARN} Unknown mesh-forward event '${type}'`) break } }) .on('close', () => { if (spinner) { // Hide the spinner spinner.stop() } reset() }) .on('error', (err) => { stopSpinner({ spinner, error: true, text: `${NETLIFYDEVERR} An error occured while bundling processing the messages from mesh-forward: ${err}`, }) firstBundleReject(err) reset() }) return { forwarder, firstBundleReady } } // 30 seconds const PROXY_READY_TIMEOUT = 3e4 // 2 seconds const PROXY_EXIT_TIMEOUT = 2e3 const runProcess = ({ args }) => { const subprocess = execa(getTrafficMeshForLocalSystem(), args, { stdio: ['inherit', 'inherit', 'pipe'] }) return { subprocess } } module.exports = { runProcess, startForwardProxy }