Files
Codewalkers/apps/server/preview/port-allocator.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt
standard monorepo conventions (apps/ for runnable apps, packages/
for reusable libraries). Update all config files, shared package
imports, test fixtures, and documentation to reflect new paths.

Key fixes:
- Update workspace config to ["apps/*", "packages/*"]
- Update tsconfig.json rootDir/include for apps/server/
- Add apps/web/** to vitest exclude list
- Update drizzle.config.ts schema path
- Fix ensure-schema.ts migration path detection (3 levels up in dev,
  2 levels up in dist)
- Fix tests/integration/cli-server.test.ts import paths
- Update packages/shared imports to apps/server/ paths
- Update all docs/ files with new paths
2026-03-03 11:22:53 +01:00

64 lines
1.7 KiB
TypeScript

/**
* Port Allocator
*
* Finds the next available port for a preview deployment.
* Queries running preview containers and performs a bind test.
*/
import { createServer } from 'node:net';
import { getPreviewPorts } from './docker-client.js';
import { createModuleLogger } from '../logger/index.js';
const log = createModuleLogger('preview:port');
/** Starting port for preview deployments */
const BASE_PORT = 9100;
/** Maximum port to try before giving up */
const MAX_PORT = 9200;
/**
* Allocate the next available port for a preview deployment.
*
* 1. Queries running preview containers for their cw.port labels
* 2. Finds the next port >= BASE_PORT that isn't in use
* 3. Performs a bind test to verify no external conflict
*
* @returns An available port number
* @throws If no port is available in the range
*/
export async function allocatePort(): Promise<number> {
const usedPorts = new Set(await getPreviewPorts());
log.debug({ usedPorts: Array.from(usedPorts) }, 'ports in use by previews');
for (let port = BASE_PORT; port < MAX_PORT; port++) {
if (usedPorts.has(port)) continue;
if (await isPortAvailable(port)) {
log.info({ port }, 'allocated port');
return port;
}
}
throw new Error(`No available ports in range ${BASE_PORT}-${MAX_PORT}`);
}
/**
* Test if a port is available by attempting to bind to it.
*/
async function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = createServer();
server.once('error', () => {
resolve(false);
});
server.listen(port, '127.0.0.1', () => {
server.close(() => {
resolve(true);
});
});
});
}