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

Commit 929c28c

Browse files
authored
Merge pull request #28 from netlify/addOpenBrowser
add openBrowser after netlify dev completes
2 parents ecb0208 + f6ab1c8 commit 929c28c

File tree

2 files changed

+157
-51
lines changed

2 files changed

+157
-51
lines changed

src/commands/dev/index.js

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
const {flags} = require('@oclif/command')
2-
const {spawn} = require('child_process')
1+
const { flags } = require('@oclif/command')
2+
const { spawn } = require('child_process')
33
const http = require('http')
44
const httpProxy = require('http-proxy')
55
const waitPort = require('wait-port')
66
const getPort = require('get-port')
7-
const {serveFunctions} = require('@netlify/zip-it-and-ship-it')
8-
const {serverSettings} = require('../../detect-server')
7+
const { serveFunctions } = require('@netlify/zip-it-and-ship-it')
8+
const { serverSettings } = require('../../detect-server')
9+
const openBrowser = require('./openBrowser')
910
const Command = require('@netlify/cli-utils')
10-
const {getAddons} = require('netlify/src/addons')
11+
const { getAddons } = require('netlify/src/addons')
1112

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

29-
await waitPort({port: settings.proxyPort})
30+
await waitPort({ port: settings.proxyPort })
3031
if (settings.functionsPort) {
31-
await waitPort({port: settings.functionsPort})
32+
await waitPort({ port: settings.functionsPort })
3233
}
33-
const port = await getPort({port: settings.port})
34-
const functionsServer = settings.functionsPort ?
35-
`http://localhost:${settings.functionsPort}` :
36-
null
34+
const port = await getPort({ port: settings.port })
35+
const functionsServer = settings.functionsPort ? `http://localhost:${settings.functionsPort}` : null
3736

3837
const proxy = httpProxy.createProxyServer({
3938
target: {
4039
host: 'localhost',
41-
port: settings.proxyPort,
42-
},
40+
port: settings.proxyPort
41+
}
4342
})
4443

45-
const rewriter = rulesProxy({publicFolder: settings.dist})
44+
const rewriter = rulesProxy({ publicFolder: settings.dist })
4645

47-
const server = http.createServer(function (req, res) {
46+
const server = http.createServer(function(req, res) {
4847
if (isFunction(settings, req)) {
49-
return proxy.web(req, res, {target: functionsServer})
48+
return proxy.web(req, res, { target: functionsServer })
5049
}
5150
let url = addonUrl(addonUrls, req)
5251
if (url) {
53-
return proxy.web(req, res, {target: url})
52+
return proxy.web(req, res, { target: url })
5453
}
5554

5655
rewriter(req, res, () => {
5756
if (isFunction(settings, req)) {
58-
return proxy.web(req, res, {target: functionsServer})
57+
return proxy.web(req, res, { target: functionsServer })
5958
}
6059
url = addonUrl(addonUrls, req)
6160
if (url) {
62-
return proxy.web(req, res, {target: url})
61+
return proxy.web(req, res, { target: url })
6362
}
6463

65-
proxy.web(req, res, {target: `http://localhost:${settings.proxyPort}`})
64+
proxy.web(req, res, { target: `http://localhost:${settings.proxyPort}` })
6665
})
6766
})
6867

69-
server.on('upgrade', function (req, socket, head) {
68+
server.on('upgrade', function(req, socket, head) {
7069
proxy.ws(req, socket, head)
7170
})
7271

@@ -89,17 +88,17 @@ function startDevServer(settings, log, error) {
8988
name: 'netlify-dev',
9089
port: settings.proxyPort,
9190
templates: {
92-
notFound: '404.html',
93-
},
91+
notFound: '404.html'
92+
}
9493
})
9594

96-
server.start(function () {
95+
server.start(function() {
9796
log('Server listening to', settings.proxyPort)
9897
})
9998
return
10099
}
101100

102-
const ps = spawn(settings.cmd, settings.args, {env: settings.env})
101+
const ps = spawn(settings.cmd, settings.args, { env: settings.env })
103102

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

120119
class DevCommand extends Command {
121120
async run() {
122-
const {flags, args} = this.parse(DevCommand)
123-
const {api, site, config} = this.netlify
121+
const { flags, args } = this.parse(DevCommand)
122+
const { api, site, config } = this.netlify
124123
const functionsDir =
125-
flags.functions ||
126-
(config.dev && config.dev.functions) ||
127-
(config.build && config.build.functions)
124+
flags.functions || (config.dev && config.dev.functions) || (config.build && config.build.functions)
128125
const addonUrls = {}
129126
if (site.id && !flags.offline) {
130127
const accessToken = await this.authenticate()
131128
const addons = await getAddons(site.id, accessToken)
132129
if (Array.isArray(addons)) {
133130
addons.forEach(addon => {
134-
addonUrls[addon.slug] = `${addon.config.site_url}/.netlify/${
135-
addon.slug
136-
}`
131+
addonUrls[addon.slug] = `${addon.config.site_url}/.netlify/${addon.slug}`
137132
for (const key in addon.env) {
138133
process.env[key] = process.env[key] || addon.env[key]
139134
}
140135
})
141136
}
142137
const api = this.netlify.api
143-
const apiSite = await api.getSite({site_id: site.id})
138+
const apiSite = await api.getSite({ site_id: site.id })
144139
// TODO: We should move the environment outside of build settings and possibly have a
145140
// `/api/v1/sites/:site_id/environment` endpoint for it that we can also gate access to
146141
// In the future and that we could make context dependend
@@ -154,55 +149,50 @@ class DevCommand extends Command {
154149
let settings = serverSettings(config.dev)
155150
if (!(settings && settings.cmd)) {
156151
this.log('No dev server detected, using simple static server')
157-
const dist =
158-
(config.dev && config.dev.publish) ||
159-
(config.build && config.build.publish)
152+
const dist = (config.dev && config.dev.publish) || (config.build && config.build.publish)
160153
settings = {
161154
noCmd: true,
162155
port: 8888,
163156
proxyPort: 3999,
164-
dist,
157+
dist
165158
}
166159
}
167160
startDevServer(settings, this.log, this.error)
168161
if (functionsDir) {
169-
const fnSettings = await serveFunctions({functionsDir})
162+
const fnSettings = await serveFunctions({ functionsDir })
170163
settings.functionsPort = fnSettings.port
171164
}
172165

173166
const url = await startProxy(settings, addonUrls)
174167
this.log(`Netlify dev server is now ready on ${url}`)
168+
openBrowser(url)
175169
}
176170
}
177171

178172
DevCommand.description = `Local dev server
179173
The dev command will run a local dev server with Netlify's proxy and redirect rules
180174
`
181175

182-
DevCommand.examples = [
183-
'$ netlify dev',
184-
'$ netlify dev -c "yarn start"',
185-
'$ netlify dev -c hugo',
186-
]
176+
DevCommand.examples = ['$ netlify dev', '$ netlify dev -c "yarn start"', '$ netlify dev -c hugo']
187177

188178
DevCommand.strict = false
189179

190180
DevCommand.flags = {
191-
cmd: flags.string({char: 'c', description: 'command to run'}),
181+
cmd: flags.string({ char: 'c', description: 'command to run' }),
192182
devport: flags.integer({
193183
char: 'd',
194-
description: 'port of the dev server started by command',
184+
description: 'port of the dev server started by command'
195185
}),
196-
port: flags.integer({char: 'p', description: 'port of netlify dev'}),
197-
dir: flags.integer({char: 'd', description: 'dir with static files'}),
186+
port: flags.integer({ char: 'p', description: 'port of netlify dev' }),
187+
dir: flags.integer({ char: 'd', description: 'dir with static files' }),
198188
functions: flags.string({
199189
char: 'f',
200-
description: 'Specify a functions folder to serve',
190+
description: 'Specify a functions folder to serve'
201191
}),
202192
offline: flags.boolean({
203193
char: 'o',
204-
description: 'disables any features that require network access',
205-
}),
194+
description: 'disables any features that require network access'
195+
})
206196
}
207197

208198
module.exports = DevCommand

src/commands/dev/openBrowser.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// from https://github.com/facebook/create-react-app/blob/7864ba3ce70892ebe43d56487b45d3267890df14/packages/react-dev-utils/openBrowser.js
2+
3+
'use strict'
4+
5+
var chalk = require('chalk')
6+
var execSync = require('child_process').execSync
7+
var spawn = require('cross-spawn')
8+
var opn = require('opn')
9+
10+
// https://github.com/sindresorhus/opn#app
11+
var OSX_CHROME = 'google chrome'
12+
13+
const Actions = Object.freeze({
14+
NONE: 0,
15+
BROWSER: 1,
16+
SCRIPT: 2
17+
})
18+
19+
function getBrowserEnv() {
20+
// Attempt to honor this environment variable.
21+
// It is specific to the operating system.
22+
// See https://github.com/sindresorhus/opn#app for documentation.
23+
const value = process.env.BROWSER
24+
let action
25+
if (!value) {
26+
// Default.
27+
action = Actions.BROWSER
28+
} else if (value.toLowerCase().endsWith('.js')) {
29+
action = Actions.SCRIPT
30+
} else if (value.toLowerCase() === 'none') {
31+
action = Actions.NONE
32+
} else {
33+
action = Actions.BROWSER
34+
}
35+
return { action, value }
36+
}
37+
38+
function executeNodeScript(scriptPath, url) {
39+
const extraArgs = process.argv.slice(2)
40+
const child = spawn('node', [scriptPath, ...extraArgs, url], {
41+
stdio: 'inherit'
42+
})
43+
child.on('close', code => {
44+
if (code !== 0) {
45+
console.log()
46+
console.log(chalk.red('The script specified as BROWSER environment variable failed.'))
47+
console.log(chalk.cyan(scriptPath) + ' exited with code ' + code + '.')
48+
console.log()
49+
return
50+
}
51+
})
52+
return true
53+
}
54+
55+
function startBrowserProcess(browser, url) {
56+
// If we're on OS X, the user hasn't specifically
57+
// requested a different browser, we can try opening
58+
// Chrome with AppleScript. This lets us reuse an
59+
// existing tab when possible instead of creating a new one.
60+
const shouldTryOpenChromeWithAppleScript =
61+
process.platform === 'darwin' && (typeof browser !== 'string' || browser === OSX_CHROME)
62+
63+
if (shouldTryOpenChromeWithAppleScript) {
64+
try {
65+
// Try our best to reuse existing tab
66+
// on OS X Google Chrome with AppleScript
67+
execSync('ps cax | grep "Google Chrome"')
68+
execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', {
69+
cwd: __dirname,
70+
stdio: 'ignore'
71+
})
72+
return true
73+
} catch (err) {
74+
// Ignore errors.
75+
}
76+
}
77+
78+
// Another special case: on OS X, check if BROWSER has been set to "open".
79+
// In this case, instead of passing `open` to `opn` (which won't work),
80+
// just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
81+
// https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768
82+
if (process.platform === 'darwin' && browser === 'open') {
83+
browser = undefined
84+
}
85+
86+
// Fallback to opn
87+
// (It will always open new tab)
88+
try {
89+
var options = { app: browser, wait: false }
90+
opn(url, options).catch(() => {}) // Prevent `unhandledRejection` error.
91+
return true
92+
} catch (err) {
93+
return false
94+
}
95+
}
96+
97+
/**
98+
* Reads the BROWSER environment variable and decides what to do with it. Returns
99+
* true if it opened a browser or ran a node.js script, otherwise false.
100+
*/
101+
function openBrowser(url) {
102+
const { action, value } = getBrowserEnv()
103+
switch (action) {
104+
case Actions.NONE:
105+
// Special case: BROWSER="none" will prevent opening completely.
106+
return false
107+
case Actions.SCRIPT:
108+
return executeNodeScript(value, url)
109+
case Actions.BROWSER:
110+
return startBrowserProcess(value, url)
111+
default:
112+
throw new Error('Not implemented.')
113+
}
114+
}
115+
116+
module.exports = openBrowser

0 commit comments

Comments
 (0)