UNPKG

netlify-cli

Version:

Netlify command line tool

285 lines (251 loc) • 8.29 kB
const childProcess = require('child_process') const path = require('path') const process = require('process') const { flags: flagsLib } = require('@oclif/command') const boxen = require('boxen') const chalk = require('chalk') const StaticServer = require('static-server') const stripAnsiCc = require('strip-ansi-control-characters') const waitPort = require('wait-port') const which = require('which') const wrapAnsi = require('wrap-ansi') const Command = require('../../utils/command') const { serverSettings } = require('../../utils/detect-server') const { getSiteInformation, addEnvVariables } = require('../../utils/dev') const { startLiveTunnel } = require('../../utils/live-tunnel') const { NETLIFYDEV, NETLIFYDEVLOG, NETLIFYDEVWARN, NETLIFYDEVERR } = require('../../utils/logo') const openBrowser = require('../../utils/open-browser') const { startProxy } = require('../../utils/proxy') const { startFunctionsServer } = require('../../utils/serve-functions') const { startForwardProxy } = require('../../utils/traffic-mesh') const startFrameworkServer = async function ({ settings, log, exit }) { if (settings.noCmd) { const server = new StaticServer({ rootPath: settings.dist, name: 'netlify-dev', port: settings.frameworkPort, templates: { notFound: path.join(settings.dist, '404.html'), }, }) await new Promise((resolve) => { server.start(function onListening() { log(`\n${NETLIFYDEVLOG} Server listening to`, settings.frameworkPort) resolve() }) }) return } log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`) const commandBin = await which(settings.command).catch((error) => { if (error.code === 'ENOENT') { throw new Error( `"${settings.command}" could not be found in your PATH. Please make sure that "${settings.command}" is installed and available in your PATH`, ) } throw error }) const ps = childProcess.spawn(commandBin, settings.args, { env: { ...process.env, ...settings.env, FORCE_COLOR: 'true' }, stdio: 'pipe', }) ps.stdout.pipe(stripAnsiCc.stream()).pipe(process.stdout) ps.stderr.pipe(stripAnsiCc.stream()).pipe(process.stderr) process.stdin.pipe(process.stdin) const handleProcessExit = function (code) { log( code > 0 ? NETLIFYDEVERR : NETLIFYDEVWARN, `"${[settings.command, ...settings.args].join(' ')}" exited with code ${code}. Shutting down Netlify Dev server`, ) process.exit(code) } ps.on('close', handleProcessExit) ps.on('SIGINT', handleProcessExit) ps.on('SIGTERM', handleProcessExit) ;['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'].forEach((signal) => { process.on(signal, () => { try { process.kill(-ps.pid) } catch (error) { // Ignore } process.exit() }) }) try { const open = await waitPort({ port: settings.frameworkPort, output: 'silent', timeout: FRAMEWORK_PORT_TIMEOUT }) if (!open) { throw new Error(`Timed out waiting for port '${settings.frameworkPort}' to be open`) } } catch (error) { log(NETLIFYDEVERR, `Netlify Dev could not connect to localhost:${settings.frameworkPort}.`) log(NETLIFYDEVERR, `Please make sure your framework server is running on port ${settings.frameworkPort}`) exit(1) } return ps } // 10 minutes const FRAMEWORK_PORT_TIMEOUT = 6e5 const startProxyServer = async ({ flags, settings, site, log, exit, addonsUrls }) => { let url if (flags.trafficMesh) { url = await startForwardProxy({ port: settings.port, frameworkPort: settings.frameworkPort, functionsPort: settings.functionsPort, publishDir: settings.dist, log, debug: flags.debug, locationDb: flags.locationDb, }) if (!url) { log(NETLIFYDEVERR, `Unable to start forward proxy on port '${settings.port}'`) exit(1) } } else { url = await startProxy(settings, addonsUrls, site.configPath, site.root) if (!url) { log(NETLIFYDEVERR, `Unable to start proxy server on port '${settings.port}'`) exit(1) } } return url } const handleLiveTunnel = async ({ flags, site, api, settings, log }) => { if (flags.live) { const sessionUrl = await startLiveTunnel({ siteId: site.id, netlifyApiToken: api.accessToken, localPort: settings.port, log, }) process.env.BASE_URL = sessionUrl return sessionUrl } } const reportAnalytics = async ({ config, settings }) => { await config.runHook('analytics', { eventName: 'command', payload: { command: 'dev', projectType: settings.framework || 'custom', live: flagsLib.live || false, }, }) } const BANNER_LENGTH = 70 const printBanner = ({ url, log }) => { // boxen doesnt support text wrapping yet https://github.com/sindresorhus/boxen/issues/16 const banner = wrapAnsi(chalk.bold(`${NETLIFYDEVLOG} Server now ready on ${url}`), BANNER_LENGTH) log( boxen(banner, { padding: 1, margin: 1, align: 'center', borderColor: '#00c7b7', }), ) } class DevCommand extends Command { async run() { this.log(`${NETLIFYDEV}`) const { error: errorExit, log, warn, exit } = this const { flags } = this.parse(DevCommand) const { api, site, config, siteInfo } = this.netlify config.dev = { ...config.dev } config.build = { ...config.build } const devConfig = { framework: '#auto', ...(config.build.functions && { functions: config.build.functions }), ...(config.build.publish && { publish: config.build.publish }), ...config.dev, ...flags, } const { addonsUrls, teamEnv, addonsEnv, siteEnv, dotFilesEnv, siteUrl, capabilities } = await getSiteInformation({ flags, api, site, warn, error: errorExit, siteInfo, }) await addEnvVariables({ log, teamEnv, addonsEnv, siteEnv, dotFilesEnv }) let settings = {} try { settings = await serverSettings(devConfig, flags, site.root, log) } catch (error) { log(NETLIFYDEVERR, error.message) exit(1) } await startFunctionsServer({ settings, site, log, warn, errorExit, siteUrl, capabilities, }) await startFrameworkServer({ settings, log, exit }) let url = await startProxyServer({ flags, settings, site, log, exit, addonsUrls }) const liveTunnelUrl = await handleLiveTunnel({ flags, site, api, settings, log }) url = liveTunnelUrl || url if (devConfig.autoLaunch !== false) { await openBrowser({ url, log, silentBrowserNoneError: true }) } await reportAnalytics({ config: this.config, settings }) process.env.URL = url process.env.DEPLOY_URL = url printBanner({ url, log }) } } DevCommand.description = `Local dev server The dev command will run a local dev server with Netlify's proxy and redirect rules ` DevCommand.examples = ['$ netlify dev', '$ netlify dev -c "yarn start"', '$ netlify dev -c hugo'] DevCommand.strict = false DevCommand.flags = { command: flagsLib.string({ char: 'c', description: 'command to run', }), port: flagsLib.integer({ char: 'p', description: 'port of netlify dev', }), targetPort: flagsLib.integer({ description: 'port of target app server', }), staticServerPort: flagsLib.integer({ description: 'port of the static app server used when no framework is detected', hidden: true, }), dir: flagsLib.string({ char: 'd', description: 'dir with static files', }), functions: flagsLib.string({ char: 'f', description: 'specify a functions folder to serve', }), offline: flagsLib.boolean({ char: 'o', description: 'disables any features that require network access', }), live: flagsLib.boolean({ char: 'l', description: 'start a public live session', }), trafficMesh: flagsLib.boolean({ char: 't', hidden: true, description: 'uses Traffic Mesh for proxying requests', }), locationDb: flagsLib.string({ description: 'specify the path to a local GeoIP location database in MMDB format', char: 'g', hidden: true, }), ...DevCommand.flags, } module.exports = DevCommand