Skip to content
This repository has been archived by the owner on Sep 12, 2019. It is now read-only.

add openBrowser after netlify dev completes #28

Merged
merged 1 commit into from
Mar 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 41 additions & 51 deletions src/commands/dev/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const {flags} = require('@oclif/command')
const {spawn} = require('child_process')
const { flags } = require('@oclif/command')
const { spawn } = require('child_process')
const http = require('http')
const httpProxy = require('http-proxy')
const waitPort = require('wait-port')
const getPort = require('get-port')
const {serveFunctions} = require('@netlify/zip-it-and-ship-it')
const {serverSettings} = require('../../detect-server')
const { serveFunctions } = require('@netlify/zip-it-and-ship-it')
const { serverSettings } = require('../../detect-server')
const openBrowser = require('./openBrowser')
const Command = require('@netlify/cli-utils')
const {getAddons} = require('netlify/src/addons')
const { getAddons } = require('netlify/src/addons')

function cleanExit() {
process.exit()
Expand All @@ -26,47 +27,45 @@ function addonUrl(addonUrls, req) {
async function startProxy(settings, addonUrls) {
const rulesProxy = require('netlify-rules-proxy')

await waitPort({port: settings.proxyPort})
await waitPort({ port: settings.proxyPort })
if (settings.functionsPort) {
await waitPort({port: settings.functionsPort})
await waitPort({ port: settings.functionsPort })
}
const port = await getPort({port: settings.port})
const functionsServer = settings.functionsPort ?
`http://localhost:${settings.functionsPort}` :
null
const port = await getPort({ port: settings.port })
const functionsServer = settings.functionsPort ? `http://localhost:${settings.functionsPort}` : null

const proxy = httpProxy.createProxyServer({
target: {
host: 'localhost',
port: settings.proxyPort,
},
port: settings.proxyPort
}
})

const rewriter = rulesProxy({publicFolder: settings.dist})
const rewriter = rulesProxy({ publicFolder: settings.dist })

const server = http.createServer(function (req, res) {
const server = http.createServer(function(req, res) {
if (isFunction(settings, req)) {
return proxy.web(req, res, {target: functionsServer})
return proxy.web(req, res, { target: functionsServer })
}
let url = addonUrl(addonUrls, req)
if (url) {
return proxy.web(req, res, {target: url})
return proxy.web(req, res, { target: url })
}

rewriter(req, res, () => {
if (isFunction(settings, req)) {
return proxy.web(req, res, {target: functionsServer})
return proxy.web(req, res, { target: functionsServer })
}
url = addonUrl(addonUrls, req)
if (url) {
return proxy.web(req, res, {target: url})
return proxy.web(req, res, { target: url })
}

proxy.web(req, res, {target: `http://localhost:${settings.proxyPort}`})
proxy.web(req, res, { target: `http://localhost:${settings.proxyPort}` })
})
})

server.on('upgrade', function (req, socket, head) {
server.on('upgrade', function(req, socket, head) {
proxy.ws(req, socket, head)
})

Expand All @@ -89,17 +88,17 @@ function startDevServer(settings, log, error) {
name: 'netlify-dev',
port: settings.proxyPort,
templates: {
notFound: '404.html',
},
notFound: '404.html'
}
})

server.start(function () {
server.start(function() {
log('Server listening to', settings.proxyPort)
})
return
}

const ps = spawn(settings.cmd, settings.args, {env: settings.env})
const ps = spawn(settings.cmd, settings.args, { env: settings.env })

ps.stdout.on('data', data => {
log(`${data}`.replace(settings.urlRegexp, `$1$2${settings.port}$3`))
Expand All @@ -119,28 +118,24 @@ function startDevServer(settings, log, error) {

class DevCommand extends Command {
async run() {
const {flags, args} = this.parse(DevCommand)
const {api, site, config} = this.netlify
const { flags, args } = this.parse(DevCommand)
const { api, site, config } = this.netlify
const functionsDir =
flags.functions ||
(config.dev && config.dev.functions) ||
(config.build && config.build.functions)
flags.functions || (config.dev && config.dev.functions) || (config.build && config.build.functions)
const addonUrls = {}
if (site.id && !flags.offline) {
const accessToken = await this.authenticate()
const addons = await getAddons(site.id, accessToken)
if (Array.isArray(addons)) {
addons.forEach(addon => {
addonUrls[addon.slug] = `${addon.config.site_url}/.netlify/${
addon.slug
}`
addonUrls[addon.slug] = `${addon.config.site_url}/.netlify/${addon.slug}`
for (const key in addon.env) {
process.env[key] = process.env[key] || addon.env[key]
}
})
}
const api = this.netlify.api
const apiSite = await api.getSite({site_id: site.id})
const apiSite = await api.getSite({ site_id: site.id })
// TODO: We should move the environment outside of build settings and possibly have a
// `/api/v1/sites/:site_id/environment` endpoint for it that we can also gate access to
// In the future and that we could make context dependend
Expand All @@ -154,55 +149,50 @@ class DevCommand extends Command {
let settings = serverSettings(config.dev)
if (!(settings && settings.cmd)) {
this.log('No dev server detected, using simple static server')
const dist =
(config.dev && config.dev.publish) ||
(config.build && config.build.publish)
const dist = (config.dev && config.dev.publish) || (config.build && config.build.publish)
settings = {
noCmd: true,
port: 8888,
proxyPort: 3999,
dist,
dist
}
}
startDevServer(settings, this.log, this.error)
if (functionsDir) {
const fnSettings = await serveFunctions({functionsDir})
const fnSettings = await serveFunctions({ functionsDir })
settings.functionsPort = fnSettings.port
}

const url = await startProxy(settings, addonUrls)
this.log(`Netlify dev server is now ready on ${url}`)
openBrowser(url)
}
}

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.examples = ['$ netlify dev', '$ netlify dev -c "yarn start"', '$ netlify dev -c hugo']

DevCommand.strict = false

DevCommand.flags = {
cmd: flags.string({char: 'c', description: 'command to run'}),
cmd: flags.string({ char: 'c', description: 'command to run' }),
devport: flags.integer({
char: 'd',
description: 'port of the dev server started by command',
description: 'port of the dev server started by command'
}),
port: flags.integer({char: 'p', description: 'port of netlify dev'}),
dir: flags.integer({char: 'd', description: 'dir with static files'}),
port: flags.integer({ char: 'p', description: 'port of netlify dev' }),
dir: flags.integer({ char: 'd', description: 'dir with static files' }),
functions: flags.string({
char: 'f',
description: 'Specify a functions folder to serve',
description: 'Specify a functions folder to serve'
}),
offline: flags.boolean({
char: 'o',
description: 'disables any features that require network access',
}),
description: 'disables any features that require network access'
})
}

module.exports = DevCommand
116 changes: 116 additions & 0 deletions src/commands/dev/openBrowser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// from https://github.com/facebook/create-react-app/blob/7864ba3ce70892ebe43d56487b45d3267890df14/packages/react-dev-utils/openBrowser.js

'use strict'

var chalk = require('chalk')
var execSync = require('child_process').execSync
var spawn = require('cross-spawn')
var opn = require('opn')

// https://github.com/sindresorhus/opn#app
var OSX_CHROME = 'google chrome'

const Actions = Object.freeze({
NONE: 0,
BROWSER: 1,
SCRIPT: 2
})

function getBrowserEnv() {
// Attempt to honor this environment variable.
// It is specific to the operating system.
// See https://github.com/sindresorhus/opn#app for documentation.
const value = process.env.BROWSER
let action
if (!value) {
// Default.
action = Actions.BROWSER
} else if (value.toLowerCase().endsWith('.js')) {
action = Actions.SCRIPT
} else if (value.toLowerCase() === 'none') {
action = Actions.NONE
} else {
action = Actions.BROWSER
}
return { action, value }
}

function executeNodeScript(scriptPath, url) {
const extraArgs = process.argv.slice(2)
const child = spawn('node', [scriptPath, ...extraArgs, url], {
stdio: 'inherit'
})
child.on('close', code => {
if (code !== 0) {
console.log()
console.log(chalk.red('The script specified as BROWSER environment variable failed.'))
console.log(chalk.cyan(scriptPath) + ' exited with code ' + code + '.')
console.log()
return
}
})
return true
}

function startBrowserProcess(browser, url) {
// If we're on OS X, the user hasn't specifically
// requested a different browser, we can try opening
// Chrome with AppleScript. This lets us reuse an
// existing tab when possible instead of creating a new one.
const shouldTryOpenChromeWithAppleScript =
process.platform === 'darwin' && (typeof browser !== 'string' || browser === OSX_CHROME)

if (shouldTryOpenChromeWithAppleScript) {
try {
// Try our best to reuse existing tab
// on OS X Google Chrome with AppleScript
execSync('ps cax | grep "Google Chrome"')
execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', {
cwd: __dirname,
stdio: 'ignore'
})
return true
} catch (err) {
// Ignore errors.
}
}

// Another special case: on OS X, check if BROWSER has been set to "open".
// In this case, instead of passing `open` to `opn` (which won't work),
// just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
// https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768
if (process.platform === 'darwin' && browser === 'open') {
browser = undefined
}

// Fallback to opn
// (It will always open new tab)
try {
var options = { app: browser, wait: false }
opn(url, options).catch(() => {}) // Prevent `unhandledRejection` error.
return true
} catch (err) {
return false
}
}

/**
* Reads the BROWSER environment variable and decides what to do with it. Returns
* true if it opened a browser or ran a node.js script, otherwise false.
*/
function openBrowser(url) {
const { action, value } = getBrowserEnv()
switch (action) {
case Actions.NONE:
// Special case: BROWSER="none" will prevent opening completely.
return false
case Actions.SCRIPT:
return executeNodeScript(value, url)
case Actions.BROWSER:
return startBrowserProcess(value, url)
default:
throw new Error('Not implemented.')
}
}

module.exports = openBrowser