mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:33 +00:00
91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
import { realpathSync } from "node:fs";
|
|
import { createConnection, createServer, type Server } from "node:net";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const worktreeRoot = realpathSync(fileURLToPath(new URL("..", import.meta.url)));
|
|
|
|
export function worktreeId(root: string): string {
|
|
let hash = 0xcbf29ce484222325n;
|
|
for (const byte of Buffer.from(root)) {
|
|
hash = BigInt.asUintN(64, (hash ^ BigInt(byte)) * 0x100000001b3n);
|
|
}
|
|
return `ai.l0ng.openidea.dev.w${hash.toString(16).padStart(16, "0")}`;
|
|
}
|
|
|
|
export interface DevSession {
|
|
worktree: string;
|
|
identifier: string;
|
|
pid: number;
|
|
devUrl?: string;
|
|
}
|
|
|
|
export function preferredVitePort(identifier: string): number {
|
|
return 15000 + (Number.parseInt(identifier.slice(-8), 16) % 20000);
|
|
}
|
|
|
|
function readSession(port: number): Promise<DevSession | undefined> {
|
|
return new Promise((resolve) => {
|
|
const socket = createConnection({ host: "127.0.0.1", port });
|
|
let data = "";
|
|
const finish = (session?: DevSession) => {
|
|
socket.destroy();
|
|
resolve(session);
|
|
};
|
|
socket.setTimeout(1000, () => finish());
|
|
socket.on("error", () => finish());
|
|
socket.on("data", (chunk) => {
|
|
data += chunk.toString();
|
|
if (data.length > 16384) finish();
|
|
});
|
|
socket.on("end", () => {
|
|
try {
|
|
finish(JSON.parse(data));
|
|
} catch {
|
|
finish();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// A live OS listener is the lock: no stale PID files after crashes or PID reuse.
|
|
export async function claimSession(
|
|
session: DevSession,
|
|
firstPort = 43000 + (Number.parseInt(session.identifier.slice(-8), 16) % 10000),
|
|
): Promise<{ server: Server } | { existing: DevSession }> {
|
|
for (let offset = 0; offset < 100; offset++) {
|
|
const port = firstPort + offset;
|
|
const server = createServer((socket) => {
|
|
socket.on("error", () => {});
|
|
socket.end(JSON.stringify(session));
|
|
});
|
|
try {
|
|
await new Promise<void>((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(port, "127.0.0.1", () => {
|
|
server.removeListener("error", reject);
|
|
resolve();
|
|
});
|
|
});
|
|
return { server };
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code !== "EADDRINUSE") throw error;
|
|
const existing = await readSession(port);
|
|
if (existing?.worktree === session.worktree && existing.identifier === session.identifier) {
|
|
return { existing };
|
|
}
|
|
}
|
|
}
|
|
throw new Error("No available development session port");
|
|
}
|
|
|
|
export function withDevConfig(args: string[], devUrl: string): string[] {
|
|
const separator = args.indexOf("--");
|
|
const index = separator < 0 ? args.length : separator;
|
|
return [
|
|
...args.slice(0, index),
|
|
"--config",
|
|
JSON.stringify({ build: { beforeDevCommand: "", devUrl } }),
|
|
...args.slice(index),
|
|
];
|
|
}
|