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

Commit

Permalink
Merge pull request #28 from netlify/addOpenBrowser
Browse files Browse the repository at this point in the history
add openBrowser after netlify dev completes
  • Loading branch information
swyxio authored Mar 16, 2019
2 parents ecb0208 + f6ab1c8 commit 929c28c
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 51 deletions.
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

0 comments on commit 929c28c

Please sign in to comment.