diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5ebb3ad23fc..f53def5655c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -113,7 +113,7 @@ "unixify": "^1.0.0", "update-notifier": "^5.0.0", "uuid": "^9.0.0", - "wait-port": "^0.3.0", + "wait-port": "^1.0.1", "winston": "^3.2.1", "write-file-atomic": "^4.0.0" }, @@ -22275,9 +22275,9 @@ } }, "node_modules/wait-port": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.3.1.tgz", - "integrity": "sha512-o8kW8xjslQDrbazXgXeDFt53l128J9xBAiG4aBmr9DcQA05jt0VBf2TE2vc9rxctgZjIuWpiXuO0gIYFo3pZSA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.1.tgz", + "integrity": "sha512-JkEgxQRZqqBz449/bRVQAvl+e8LJ8fpW8J1W7WkKKo8PypoXX7EXGE47BmkNLTb5Ly/eI15IyTeAxDBwOEQ8DQ==", "dependencies": { "chalk": "^4.1.2", "commander": "^9.3.0", @@ -39123,9 +39123,9 @@ } }, "wait-port": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.3.1.tgz", - "integrity": "sha512-o8kW8xjslQDrbazXgXeDFt53l128J9xBAiG4aBmr9DcQA05jt0VBf2TE2vc9rxctgZjIuWpiXuO0gIYFo3pZSA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.1.tgz", + "integrity": "sha512-JkEgxQRZqqBz449/bRVQAvl+e8LJ8fpW8J1W7WkKKo8PypoXX7EXGE47BmkNLTb5Ly/eI15IyTeAxDBwOEQ8DQ==", "requires": { "chalk": "^4.1.2", "commander": "^9.3.0", diff --git a/package.json b/package.json index 921394d35ea..c3fcb2df18f 100644 --- a/package.json +++ b/package.json @@ -326,7 +326,7 @@ "unixify": "^1.0.0", "update-notifier": "^5.0.0", "uuid": "^9.0.0", - "wait-port": "^0.3.0", + "wait-port": "^1.0.1", "winston": "^3.2.1", "write-file-atomic": "^4.0.0" }, diff --git a/src/commands/dev/dev.js b/src/commands/dev/dev.js index b9758e5518d..1a3b46139e4 100644 --- a/src/commands/dev/dev.js +++ b/src/commands/dev/dev.js @@ -178,18 +178,25 @@ const runCommand = (command, env = {}, spinner = null) => { return commandProcess } +/** + * @typedef StartReturnObject + * @property {4 | 6 | undefined=} ipVersion The version the open port was found on + */ + /** * Start a static server if the `useStaticServer` is provided or a framework specific server * @param {object} config * @param {Partial} config.settings - * @returns {Promise} + * @returns {Promise} */ const startFrameworkServer = async function ({ settings }) { if (settings.useStaticServer) { if (settings.command) { runCommand(settings.command, settings.env) } - return await startStaticServer({ settings }) + await startStaticServer({ settings }) + + return {} } log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`) @@ -200,17 +207,17 @@ const startFrameworkServer = async function ({ settings }) { runCommand(settings.command, settings.env, spinner) + let port try { - const open = await waitPort({ + port = await waitPort({ port: settings.frameworkPort, - // Cannot use `localhost` as it may point to IPv4 or IPv6 depending on node version and OS - host: '127.0.0.1', + host: 'localhost', output: 'silent', timeout: FRAMEWORK_PORT_TIMEOUT, ...(settings.pollingStrategies.includes('HTTP') && { protocol: 'http' }), }) - if (!open) { + if (!port.open) { throw new Error(`Timed out waiting for port '${settings.frameworkPort}' to be open`) } @@ -221,6 +228,8 @@ const startFrameworkServer = async function ({ settings }) { log(NETLIFYDEVERR, `Please make sure your framework server is running on port ${settings.frameworkPort}`) exit(1) } + + return { ipVersion: port?.ipVersion } } // 10 minutes @@ -501,7 +510,11 @@ const dev = async (options, command) => { log(`${NETLIFYDEVWARN} Setting up local development server`) - const devCommand = () => startFrameworkServer({ settings }) + const devCommand = async () => { + const { ipVersion } = await startFrameworkServer({ settings }) + // eslint-disable-next-line no-magic-numbers + settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1' + } const startDevOptions = getBuildOptions({ cachedConfig, options, diff --git a/src/lib/http-agent.js b/src/lib/http-agent.js index 6e466a42d36..e586ab4f4f0 100644 --- a/src/lib/http-agent.js +++ b/src/lib/http-agent.js @@ -43,9 +43,9 @@ const tryGetAgent = async ({ certificateFile, httpProxy }) => { return { error: `${httpProxy} must have a scheme of http or https` } } - let open + let port try { - open = await waitPort({ + port = await waitPort({ port: Number.parseInt(proxyUrl.port) || (scheme === 'http' ? DEFAULT_HTTP_PORT : DEFAULT_HTTPS_PORT), host: proxyUrl.hostname, timeout: AGENT_PORT_TIMEOUT, @@ -56,7 +56,7 @@ const tryGetAgent = async ({ certificateFile, httpProxy }) => { return { error: `${httpProxy} is not available.`, message: error.message } } - if (!open) { + if (!port.open) { // timeout error return { error: `Could not connect to '${httpProxy}'` } } diff --git a/src/utils/proxy.js b/src/utils/proxy.js index c49b5b26da9..c39be9695e1 100644 --- a/src/utils/proxy.js +++ b/src/utils/proxy.js @@ -4,6 +4,7 @@ const { once } = require('events') const { readFile } = require('fs').promises const http = require('http') const https = require('https') +const { isIPv6 } = require('net') const path = require('path') const contentType = require('content-type') @@ -281,11 +282,11 @@ const reqToURL = function (req, pathname) { const MILLISEC_TO_SEC = 1e3 -const initializeProxy = async function ({ configPath, distDir, port, projectDir }) { +const initializeProxy = async function ({ configPath, distDir, host, port, projectDir }) { const proxy = httpProxy.createProxyServer({ selfHandleResponse: true, target: { - host: '127.0.0.1', + host, port, }, }) @@ -434,7 +435,9 @@ const onRequest = async ({ addonsUrls, edgeFunctionsProxy, functionsServer, prox const options = { match, addonsUrls, - target: `http://127.0.0.1:${settings.frameworkPort}`, + target: `http://${isIPv6(settings.frameworkHost) ? `[${settings.frameworkHost}]` : settings.frameworkHost}:${ + settings.frameworkPort + }`, publicFolder: settings.dist, functionsServer, functionsPort: settings.functionsPort, @@ -498,6 +501,7 @@ const startProxy = async function ({ state, }) const proxy = await initializeProxy({ + host: settings.frameworkHost, port: settings.frameworkPort, distDir: settings.dist, projectDir, diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts index e1078e4b489..d191901ad06 100644 --- a/src/utils/types.d.ts +++ b/src/utils/types.d.ts @@ -24,6 +24,8 @@ export type BaseServerSettings = { // Framework specific part /** A port where a proxy can listen to it */ frameworkPort?: number + /** The host where a proxy can listen to it */ + frameworkHost?: '127.0.0.1' | '::1' functions?: string /** The command that was provided for the dev config */ command?: string diff --git a/tests/integration/20.command.functions.test.js b/tests/integration/20.command.functions.test.js index db326efee06..4034f62293f 100644 --- a/tests/integration/20.command.functions.test.js +++ b/tests/integration/20.command.functions.test.js @@ -660,7 +660,7 @@ const withFunctionsServer = async ({ builder, args = [], port = DEFAULT_PORT }, ps.stdout.on('data', (data) => console.log(data.toString())) ps.stderr.on('data', (data) => console.log(data.toString())) - const open = await waitPort({ + const { open } = await waitPort({ port, output: 'silent', timeout: SERVE_TIMEOUT,