cli/server: harden run startup for port conflicts and entrypoint loading

This commit is contained in:
Dotta
2026-03-03 10:41:31 -06:00
parent f640b0445d
commit 20737e2a11
2 changed files with 66 additions and 37 deletions

View File

@@ -64,41 +64,60 @@ export async function runCommand(opts: RunOptions): Promise<void> {
await importServerEntry();
}
function formatError(err: unknown): string {
if (err instanceof Error) {
if (err.message && err.message.trim().length > 0) return err.message;
return err.name;
}
if (typeof err === "string") return err;
try {
return JSON.stringify(err);
} catch {
return String(err);
}
}
function isModuleNotFoundError(err: unknown): boolean {
if (!(err instanceof Error)) return false;
const code = (err as { code?: unknown }).code;
if (code === "ERR_MODULE_NOT_FOUND") return true;
return err.message.includes("Cannot find module");
}
async function importServerEntry(): Promise<void> {
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
const fileCandidates = [
path.resolve(projectRoot, "server/dist/index.js"),
path.resolve(projectRoot, "server/src/index.ts"),
path.resolve(projectRoot, "server/dist/index.js"),
];
const existingFileCandidates = fileCandidates.filter((filePath) => fs.existsSync(filePath));
if (existingFileCandidates.length > 0) {
for (const filePath of existingFileCandidates) {
try {
await import(pathToFileURL(filePath).href);
return;
} catch (err) {
throw new Error(`Failed to start Paperclip server from ${filePath}: ${formatError(err)}`);
}
}
}
const specifierCandidates: string[] = [
"@paperclipai/server/dist/index.js",
"@paperclipai/server/src/index.ts",
];
const importErrors: string[] = [];
const specifierCandidates: string[] = ["@paperclipai/server/dist/index.js", "@paperclipai/server/src/index.ts"];
const missingErrors: string[] = [];
for (const specifier of specifierCandidates) {
try {
await import(specifier);
return;
} catch (err) {
importErrors.push(`${specifier}: ${err instanceof Error ? err.message : String(err)}`);
}
}
for (const filePath of fileCandidates) {
if (!fs.existsSync(filePath)) continue;
try {
await import(pathToFileURL(filePath).href);
return;
} catch (err) {
importErrors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
if (isModuleNotFoundError(err)) {
missingErrors.push(`${specifier}: ${formatError(err)}`);
continue;
}
throw new Error(`Failed to start Paperclip server from ${specifier}: ${formatError(err)}`);
}
}
throw new Error(
`Could not start Paperclip server entrypoint. Tried: ${[...specifierCandidates, ...fileCandidates].join(", ")}\n` +
importErrors.join("\n"),
`Could not locate a Paperclip server entrypoint. Tried: ${[...fileCandidates, ...specifierCandidates].join(", ")}\n${missingErrors.join("\n")}`,
);
}

View File

@@ -225,27 +225,14 @@ if (config.databaseUrl) {
}
const dataDir = resolve(config.embeddedPostgresDataDir);
const port = config.embeddedPostgresPort;
const configuredPort = config.embeddedPostgresPort;
let port = configuredPort;
if (config.databaseMode === "postgres") {
logger.warn("Database mode is postgres but no connection string was set; falling back to embedded PostgreSQL");
}
logger.info(`No DATABASE_URL set — using embedded PostgreSQL (${dataDir}) on port ${port}`);
embeddedPostgres = new EmbeddedPostgres({
databaseDir: dataDir,
user: "paperclip",
password: "paperclip",
port,
persistent: true,
});
const clusterVersionFile = resolve(dataDir, "PG_VERSION");
if (!existsSync(clusterVersionFile)) {
await embeddedPostgres.initialise();
} else {
logger.info(`Embedded PostgreSQL cluster already exists (${clusterVersionFile}); skipping init`);
}
const postmasterPidFile = resolve(dataDir, "postmaster.pid");
const isPidRunning = (pid: number): boolean => {
try {
@@ -271,8 +258,31 @@ if (config.databaseUrl) {
const runningPid = getRunningPid();
if (runningPid) {
logger.warn({ pid: runningPid }, "Embedded PostgreSQL already running; reusing existing process");
logger.warn({ pid: runningPid, port }, "Embedded PostgreSQL already running; reusing existing process");
} else {
const detectedPort = await detectPort(configuredPort);
if (detectedPort !== configuredPort) {
logger.warn(
{ requestedPort: configuredPort, selectedPort: detectedPort },
"Embedded PostgreSQL port is in use; using next free port",
);
}
port = detectedPort;
logger.info(`No DATABASE_URL set — using embedded PostgreSQL (${dataDir}) on port ${port}`);
embeddedPostgres = new EmbeddedPostgres({
databaseDir: dataDir,
user: "paperclip",
password: "paperclip",
port,
persistent: true,
});
if (!existsSync(clusterVersionFile)) {
await embeddedPostgres.initialise();
} else {
logger.info(`Embedded PostgreSQL cluster already exists (${clusterVersionFile}); skipping init`);
}
if (existsSync(postmasterPidFile)) {
logger.warn("Removing stale embedded PostgreSQL lock file");
rmSync(postmasterPidFile, { force: true });