Files
payloadcms/test/dev.ts
Paul 39462bc6b9 chore: assign available port to env variable in dev suite (#12092)
Previously when the port number was bumped up (eg `3001`) in our dev
suite, HMR wouldn't work because it couldn't reliably read the new used
port and it would default to `3000`.

This assigns it properly to the env var and fixes that issues so HMR in
our dev suite works on other ports too.

Testing steps:
- Have a local instance of dev suite running already on port 3000
- New repo run dev, it will bump to `3001`
- Make any config change and you will see that HMR does not work without
this fix
2025-04-11 19:24:24 +00:00

150 lines
4.3 KiB
TypeScript

import nextEnvImport from '@next/env'
import chalk from 'chalk'
import { createServer } from 'http'
import minimist from 'minimist'
import nextImport from 'next'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import open from 'open'
import { loadEnv } from 'payload/node'
import { parse } from 'url'
import { getNextRootDir } from './helpers/getNextRootDir.js'
import startMemoryDB from './helpers/startMemoryDB.js'
import { runInit } from './runInit.js'
import { child } from './safelyRunScript.js'
import { createTestHooks } from './testHooks.js'
// @todo remove in 4.0 - will behave like this by default in 4.0
process.env.PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY = 'true'
const prod = process.argv.includes('--prod')
if (prod) {
process.argv = process.argv.filter((arg) => arg !== '--prod')
process.env.PAYLOAD_TEST_PROD = 'true'
}
const shouldStartMemoryDB =
process.argv.includes('--start-memory-db') || process.env.START_MEMORY_DB === 'true'
if (shouldStartMemoryDB) {
process.argv = process.argv.filter((arg) => arg !== '--start-memory-db')
process.env.START_MEMORY_DB = 'true'
}
loadEnv()
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const {
_: [_testSuiteArg = '_community'],
...args
} = minimist(process.argv.slice(2))
let testSuiteArg: string | undefined
let testSuiteConfigOverride: string | undefined
if (_testSuiteArg.includes('#')) {
;[testSuiteArg, testSuiteConfigOverride] = _testSuiteArg.split('#')
} else {
testSuiteArg = _testSuiteArg
}
if (!testSuiteArg || !fs.existsSync(path.resolve(dirname, testSuiteArg))) {
console.log(chalk.red(`ERROR: The test folder "${testSuiteArg}" does not exist`))
process.exit(0)
}
console.log(`Selected test suite: ${testSuiteArg}`)
if (args.turbo === true) {
process.env.TURBOPACK = '1'
}
const { beforeTest } = await createTestHooks(testSuiteArg, testSuiteConfigOverride)
await beforeTest()
const { rootDir, adminRoute } = getNextRootDir(testSuiteArg)
await runInit(testSuiteArg, true)
if (shouldStartMemoryDB) {
await startMemoryDB()
}
// This is needed to forward the environment variables to the next process that were created after loadEnv()
// for example process.env.MONGODB_MEMORY_SERVER_URI otherwise app.prepare() will clear them
nextEnvImport.updateInitialEnv(process.env)
// Open the admin if the -o flag is passed
if (args.o) {
await open(`http://localhost:3000${adminRoute}`)
}
const findOpenPort = (startPort: number): Promise<number> => {
return new Promise((resolve, reject) => {
const server = createServer()
server.listen(startPort, () => {
console.log(`✓ Running on port ${startPort}`)
server.close(() => resolve(startPort))
})
server.on('error', () => {
console.log(`⚠ Port ${startPort} is in use, trying ${startPort + 1} instead.`)
findOpenPort(startPort + 1)
.then(resolve)
.catch(reject)
})
})
}
const port = process.env.PORT ? Number(process.env.PORT) : 3000
const availablePort = await findOpenPort(port)
// Assign the available port to process.env.PORT so that the next and our HMR server uses it
// @ts-expect-error - PORT is a string from somewhere
process.env.PORT = availablePort
// @ts-expect-error the same as in test/helpers/initPayloadE2E.ts
const app = nextImport({
dev: true,
hostname: 'localhost',
port: availablePort,
dir: rootDir,
})
const handle = app.getRequestHandler()
let resolveServer: () => void
const serverPromise = new Promise<void>((res) => (resolveServer = res))
void app.prepare().then(() => {
createServer(async (req, res) => {
const parsedUrl = parse(req.url || '', true)
await handle(req, res, parsedUrl)
}).listen(availablePort, () => {
resolveServer()
})
})
await serverPromise
process.env.PAYLOAD_DROP_DATABASE = process.env.PAYLOAD_DROP_DATABASE === 'false' ? 'false' : 'true'
// fetch the admin url to force a render
void fetch(`http://localhost:${availablePort}${adminRoute}`)
void fetch(`http://localhost:${availablePort}/api/access`)
// This ensures that the next-server process is killed when this process is killed and doesn't linger around.
process.on('SIGINT', () => {
if (child) {
child.kill('SIGINT')
}
process.exit(0)
})
process.on('SIGTERM', () => {
if (child) {
child.kill('SIGINT')
}
process.exit(0) // Exit the parent process
})