Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Port in use checking for development server #6437

Merged
merged 22 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d0ed643
Initial port in use check implementation
Josh-Walker-GM Sep 21, 2022
0bc39a7
Added suggested port, initial prompt and ensured consistent logging s…
Josh-Walker-GM Sep 22, 2022
ca0b549
Removed excess style import
Josh-Walker-GM Sep 22, 2022
e47928c
Changed to use node-portfinder, tidied console logging and added init…
Josh-Walker-GM Sep 22, 2022
466cecd
fix(deps): pin portfinder and dedupe yarn.lock
jtoar Oct 1, 2022
d0e8950
Improved conditionals
Josh-Walker-GM Oct 1, 2022
8b0488f
Added port flag to the watch api-server
Josh-Walker-GM Oct 2, 2022
d33584c
Added working port checking
Josh-Walker-GM Oct 2, 2022
d1aba20
Merge remote-tracking branch 'upstream/main' into dev-server-port
Josh-Walker-GM Oct 12, 2022
95044cd
Made ports consistently a number type, fixed typo in port use warning…
Josh-Walker-GM Oct 12, 2022
90baa43
Added default port options to mock config and updated inline snapshot…
Josh-Walker-GM Oct 12, 2022
3d06f93
Apply suggestions from code review
Josh-Walker-GM Nov 6, 2022
b1ba9bc
Applying more suggestions from code review
Josh-Walker-GM Nov 6, 2022
94bf0be
Changed to a simple error warning
Josh-Walker-GM Nov 9, 2022
0b15df5
style: remove "=" after flag
jtoar Nov 16, 2022
95938da
fix: substring test and snapshot
jtoar Nov 16, 2022
7652b2c
Apply suggestions from code review
Josh-Walker-GM Nov 16, 2022
a6990a5
Remove unused function
Josh-Walker-GM Nov 16, 2022
7c8676c
Merge branch 'main' into dev-server-port
jtoar Nov 17, 2022
bdb32e5
Merge remote-tracking branch 'upstream/main' into dev-server-port
Josh-Walker-GM Nov 18, 2022
b2aaea0
Merge branch 'main' into dev-server-port
jtoar Nov 21, 2022
b6b0aa6
Merge branch 'main' into dev-server-port
jtoar Nov 21, 2022
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
9 changes: 8 additions & 1 deletion packages/api-server/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const argv = yargs(hideBin(process.argv))
description: 'Debugging port',
type: 'number',
})
.option('port', {
alias: 'p',
description: 'Port',
type: 'number',
})
.help()
.alias('help', 'h')
.parseSync()
Expand Down Expand Up @@ -75,10 +80,12 @@ const rebuildApiServer = () => {
forkOpts.execArgv = forkOpts.execArgv.concat([`--inspect=${debugPort}`])
}

const port = argv.port ?? getConfig().api.port

// Start API server
httpServerProcess = fork(
path.join(__dirname, 'index.js'),
['api', '--port', getConfig().api.port.toString()],
['api', '--port', port.toString()],
forkOpts
)
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"param-case": "3.0.4",
"pascalcase": "1.0.0",
"pluralize": "8.0.0",
"portfinder": "1.0.32",
"prettier": "2.7.1",
"prisma": "4.6.1",
"prompts": "2.4.2",
Expand Down
19 changes: 14 additions & 5 deletions packages/cli/src/commands/__tests__/dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ describe('yarn rw dev', () => {

it('Should run api and web dev servers, and by default', async () => {
getConfig.mockReturnValue({
web: {},
web: {
port: 8910,
},
api: {
port: 8911,
debugPort: 18911,
},
})
Expand All @@ -92,16 +95,19 @@ describe('yarn rw dev', () => {
)

expect(apiCommand.command).toMatchInlineSnapshot(
`"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --debug-port 18911 | rw-log-formatter""`
`"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
)

expect(generateCommand.command).toEqual('yarn rw-gen-watch')
})

it('Debug port passed in command line overrides TOML', async () => {
getConfig.mockReturnValue({
web: {},
web: {
port: 8910,
},
api: {
port: 8911,
debugPort: 505050,
},
})
Expand All @@ -115,14 +121,17 @@ describe('yarn rw dev', () => {
const apiCommand = find(concurrentlyArgs, { name: 'api' })

expect(apiCommand.command).toContain(
'yarn rw-api-server-watch --debug-port 90909090'
'yarn rw-api-server-watch --port 8911 --debug-port 90909090'
)
})

it('Can disable debugger by setting toml to false', async () => {
getConfig.mockReturnValue({
web: {},
web: {
port: 8910,
},
api: {
port: 8911,
debugPort: false,
},
})
Expand Down
64 changes: 61 additions & 3 deletions packages/cli/src/commands/devHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { errorTelemetry } from '@redwoodjs/telemetry'
import { getPaths } from '../lib'
import c from '../lib/colors'
import { generatePrismaClient } from '../lib/generatePrismaClient'
import { getFreePort } from '../lib/ports'

const defaultApiDebugPort = 18911

Expand All @@ -23,6 +24,63 @@ export const handler = async ({
}) => {
const rwjsPaths = getPaths()

// Starting values of ports from config (redwood.toml)
let apiPreferredPort = parseInt(getConfig().api.port)
let webPreferredPort = parseInt(getConfig().web.port)

// Assume we can have the ports we want
let apiAvailablePort = apiPreferredPort
let apiPortChangeNeeded = false
let webAvailablePort = webPreferredPort
let webPortChangeNeeded = false

// Check api port
if (side.includes('api')) {
apiAvailablePort = await getFreePort(apiPreferredPort)
if (apiAvailablePort === -1) {
console.error(`Error could not determine a free port for the api server`)
process.exit(1)
}
apiPortChangeNeeded = apiAvailablePort !== apiPreferredPort
}

// Check web port
if (side.includes('web')) {
// Extract any ports the user forwarded to the webpack server and prefer that instead
const forwardedPortMatches = [
...forward.matchAll(/\-\-port(\=|\s)(?<port>[^\s]*)/g),
]
if (forwardedPortMatches.length) {
webPreferredPort = forwardedPortMatches.pop().groups.port
}

webAvailablePort = await getFreePort(webPreferredPort, [
apiPreferredPort,
apiAvailablePort,
])
if (webAvailablePort === -1) {
console.error(`Error could not determine a free port for the web server`)
process.exit(1)
}
webPortChangeNeeded = webAvailablePort !== webPreferredPort
}

// Check for port conflict and exit with message if found
if (apiPortChangeNeeded || webPortChangeNeeded) {
let message = `The currently configured ports for the development server are unavailable. Suggested changes to your ports, which can be changed in redwood.toml, are:\n`
message += apiPortChangeNeeded
? ` - API to use port ${apiAvailablePort} instead of your currently configured ${apiPreferredPort}\n`
: ``
message += webPortChangeNeeded
? ` - Web to use port ${webAvailablePort} instead of your currently configured ${webPreferredPort}\n`
: ``
console.error(message)
console.error(
`Cannot run the development server until your configured ports are changed or become available.`
)
process.exit(1)
}

if (side.includes('api')) {
try {
await generatePrismaClient({
Expand All @@ -39,7 +97,7 @@ export const handler = async ({
}

try {
await shutdownPort(getConfig().api.port)
await shutdownPort(apiAvailablePort)
} catch (e) {
errorTelemetry(process.argv, `Error shutting down "api": ${e.message}`)
console.error(
Expand All @@ -50,7 +108,7 @@ export const handler = async ({

if (side.includes('web')) {
try {
await shutdownPort(getConfig().web.port)
await shutdownPort(webAvailablePort)
} catch (e) {
errorTelemetry(process.argv, `Error shutting down "web": ${e.message}`)
console.error(
Expand Down Expand Up @@ -86,7 +144,7 @@ export const handler = async ({
const jobs = {
api: {
name: 'api',
command: `yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch ${getApiDebugFlag()} | rw-log-formatter"`,
command: `yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch --port ${apiAvailablePort} ${getApiDebugFlag()} | rw-log-formatter"`,
prefixColor: 'cyan',
runWhen: () => fs.existsSync(rwjsPaths.api.src),
},
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/lib/ports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import portfinder from 'portfinder'

/**
* Finds a free port
* @param {[number]} requestedPort Port to start searching from
* @param {[number[]]} excludePorts Array of port numbers to exclude
* @return {[number]} A free port equal or higher than requestedPort but not within excludePorts. If no port can be found then returns -1
*/
export async function getFreePort(requestedPort, excludePorts = []) {
try {
let freePort = await portfinder.getPortPromise({
port: requestedPort,
})
if (excludePorts.includes(freePort)) {
freePort = await getFreePort(freePort + 1, excludePorts)
}
return freePort
} catch (error) {
return -1
}
}
33 changes: 27 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6659,6 +6659,7 @@ __metadata:
param-case: 3.0.4
pascalcase: 1.0.0
pluralize: 8.0.0
portfinder: 1.0.32
prettier: 2.7.1
prisma: 4.6.1
prompts: 2.4.2
Expand Down Expand Up @@ -11317,6 +11318,15 @@ __metadata:
languageName: node
linkType: hard

"async@npm:^2.6.4":
version: 2.6.4
resolution: "async@npm:2.6.4"
dependencies:
lodash: ^4.17.14
checksum: 0ebb3273ef96513389520adc88e0d3c45e523d03653cc9b66f5c46f4239444294899bfd13d2b569e7dbfde7da2235c35cf5fd3ece9524f935d41bbe4efccdad0
languageName: node
linkType: hard

"async@npm:^3.1.0, async@npm:^3.2.0, async@npm:^3.2.3":
version: 3.2.4
resolution: "async@npm:3.2.4"
Expand Down Expand Up @@ -22255,7 +22265,7 @@ __metadata:
languageName: node
linkType: hard

"lodash@npm:4.17.21, lodash@npm:>=4.17.21, lodash@npm:^4.0.0, lodash@npm:^4.11.2, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.0":
"lodash@npm:4.17.21, lodash@npm:>=4.17.21, lodash@npm:^4.0.0, lodash@npm:^4.11.2, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.0":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
Expand Down Expand Up @@ -23181,14 +23191,14 @@ __metadata:
languageName: node
linkType: hard

"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3":
version: 0.5.5
resolution: "mkdirp@npm:0.5.5"
"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.6":
version: 0.5.6
resolution: "mkdirp@npm:0.5.6"
dependencies:
minimist: ^1.2.5
minimist: ^1.2.6
bin:
mkdirp: bin/cmd.js
checksum: 4469faeeba703bc46b7cdbe3097d6373747a581eb8b556ce41c8fd25a826eb3254466c6522ba823c2edb0b6f0da7beb91cf71f040bc4e361534a3e67f0994bd0
checksum: e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01
languageName: node
linkType: hard

Expand Down Expand Up @@ -25251,6 +25261,17 @@ __metadata:
languageName: node
linkType: hard

"portfinder@npm:1.0.32":
version: 1.0.32
resolution: "portfinder@npm:1.0.32"
dependencies:
async: ^2.6.4
debug: ^3.2.7
mkdirp: ^0.5.6
checksum: cef8b567b78aabccc59fe8e103bac8b394bb45a6a69be626608f099f454124c775aaf47b274c006332c07ab3f501cde55e49aaeb9d49d78d90362d776a565cbf
languageName: node
linkType: hard

"posix-character-classes@npm:^0.1.0":
version: 0.1.1
resolution: "posix-character-classes@npm:0.1.1"
Expand Down