bb7d1b2c71
* public-gh/master: Fix budget incident resolution edge cases Fix agent budget tab routing Fix budget auth and monthly spend rollups Harden budget enforcement and migration startup Add budget tabs and sidebar budget indicators feat(costs): add billing, quota, and budget control plane refactor(quota): move provider quota logic into adapter layer, add unit tests fix(costs): replace non-null map assertions with nullish coalescing, clarify weekData guard fix(costs): guard byProject against duplicate null keys, memoize ProviderQuotaCard row aggregations fix(costs): align byAgent run filter to startedAt, tighten providerTabItems memo deps, stabilize byProject row keys feat(costs): add agent model breakdown, harden date validation, sync CostByProject type, fix quota threshold and tab-gated queries fix(costs): harden company auth check, fix frozen date memo, hide empty quota rows fix(costs): guard routes, fix DST ranges, sync provider state, wire live updates feat(costs): consolidate /usage into /costs with Spend + Providers tabs feat(usage): add subscription quota windows per provider on /usage page address greptile review: per-provider deficit notch, startedAt filter, weekRange refresh, deduplicate providerDisplayName feat(ui): add resource and usage dashboard (/usage route) # Conflicts: # packages/db/src/migration-runtime.ts # packages/db/src/migrations/meta/0031_snapshot.json # packages/db/src/migrations/meta/_journal.json
194 lines
6.0 KiB
TypeScript
194 lines
6.0 KiB
TypeScript
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
import { createServer } from "node:net";
|
|
import path from "node:path";
|
|
import { ensurePostgresDatabase } from "./client.js";
|
|
import { resolveDatabaseTarget } from "./runtime-config.js";
|
|
|
|
type EmbeddedPostgresInstance = {
|
|
initialise(): Promise<void>;
|
|
start(): Promise<void>;
|
|
stop(): Promise<void>;
|
|
};
|
|
|
|
type EmbeddedPostgresCtor = new (opts: {
|
|
databaseDir: string;
|
|
user: string;
|
|
password: string;
|
|
port: number;
|
|
persistent: boolean;
|
|
initdbFlags?: string[];
|
|
onLog?: (message: unknown) => void;
|
|
onError?: (message: unknown) => void;
|
|
}) => EmbeddedPostgresInstance;
|
|
|
|
export type MigrationConnection = {
|
|
connectionString: string;
|
|
source: string;
|
|
stop: () => Promise<void>;
|
|
};
|
|
|
|
function toError(error: unknown, fallbackMessage: string): Error {
|
|
if (error instanceof Error) return error;
|
|
if (error === undefined) return new Error(fallbackMessage);
|
|
if (typeof error === "string") return new Error(`${fallbackMessage}: ${error}`);
|
|
|
|
try {
|
|
return new Error(`${fallbackMessage}: ${JSON.stringify(error)}`);
|
|
} catch {
|
|
return new Error(`${fallbackMessage}: ${String(error)}`);
|
|
}
|
|
}
|
|
|
|
function readRunningPostmasterPid(postmasterPidFile: string): number | null {
|
|
if (!existsSync(postmasterPidFile)) return null;
|
|
try {
|
|
const pid = Number(readFileSync(postmasterPidFile, "utf8").split("\n")[0]?.trim());
|
|
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
process.kill(pid, 0);
|
|
return pid;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function readPidFilePort(postmasterPidFile: string): number | null {
|
|
if (!existsSync(postmasterPidFile)) return null;
|
|
try {
|
|
const lines = readFileSync(postmasterPidFile, "utf8").split("\n");
|
|
const port = Number(lines[3]?.trim());
|
|
return Number.isInteger(port) && port > 0 ? port : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function isPortInUse(port: number): Promise<boolean> {
|
|
return await new Promise((resolve) => {
|
|
const server = createServer();
|
|
server.unref();
|
|
server.once("error", (error: NodeJS.ErrnoException) => {
|
|
resolve(error.code === "EADDRINUSE");
|
|
});
|
|
server.listen(port, "127.0.0.1", () => {
|
|
server.close();
|
|
resolve(false);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function findAvailablePort(startPort: number): Promise<number> {
|
|
const maxLookahead = 20;
|
|
let port = startPort;
|
|
for (let i = 0; i < maxLookahead; i += 1, port += 1) {
|
|
if (!(await isPortInUse(port))) return port;
|
|
}
|
|
throw new Error(
|
|
`Embedded PostgreSQL could not find a free port from ${startPort} to ${startPort + maxLookahead - 1}`,
|
|
);
|
|
}
|
|
|
|
async function loadEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
|
|
try {
|
|
const mod = await import("embedded-postgres");
|
|
return mod.default as EmbeddedPostgresCtor;
|
|
} catch {
|
|
throw new Error(
|
|
"Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again.",
|
|
);
|
|
}
|
|
}
|
|
|
|
async function ensureEmbeddedPostgresConnection(
|
|
dataDir: string,
|
|
preferredPort: number,
|
|
): Promise<MigrationConnection> {
|
|
const EmbeddedPostgres = await loadEmbeddedPostgresCtor();
|
|
const selectedPort = await findAvailablePort(preferredPort);
|
|
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
|
|
const pgVersionFile = path.resolve(dataDir, "PG_VERSION");
|
|
const runningPid = readRunningPostmasterPid(postmasterPidFile);
|
|
const runningPort = readPidFilePort(postmasterPidFile);
|
|
const preferredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/postgres`;
|
|
|
|
if (!runningPid && existsSync(pgVersionFile)) {
|
|
try {
|
|
await ensurePostgresDatabase(preferredAdminConnectionString, "paperclip");
|
|
process.emitWarning(
|
|
`Adopting an existing PostgreSQL instance on port ${preferredPort} for embedded data dir ${dataDir} because postmaster.pid is missing.`,
|
|
);
|
|
return {
|
|
connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`,
|
|
source: `embedded-postgres@${preferredPort}`,
|
|
stop: async () => {},
|
|
};
|
|
} catch {
|
|
// Fall through and attempt to start the configured embedded cluster.
|
|
}
|
|
}
|
|
|
|
if (runningPid) {
|
|
const port = runningPort ?? preferredPort;
|
|
const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;
|
|
await ensurePostgresDatabase(adminConnectionString, "paperclip");
|
|
return {
|
|
connectionString: `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`,
|
|
source: `embedded-postgres@${port}`,
|
|
stop: async () => {},
|
|
};
|
|
}
|
|
|
|
const instance = new EmbeddedPostgres({
|
|
databaseDir: dataDir,
|
|
user: "paperclip",
|
|
password: "paperclip",
|
|
port: selectedPort,
|
|
persistent: true,
|
|
initdbFlags: ["--encoding=UTF8", "--locale=C"],
|
|
onLog: () => {},
|
|
onError: () => {},
|
|
});
|
|
|
|
if (!existsSync(path.resolve(dataDir, "PG_VERSION"))) {
|
|
try {
|
|
await instance.initialise();
|
|
} catch (error) {
|
|
throw toError(
|
|
error,
|
|
`Failed to initialize embedded PostgreSQL cluster in ${dataDir} on port ${selectedPort}`,
|
|
);
|
|
}
|
|
}
|
|
if (existsSync(postmasterPidFile)) {
|
|
rmSync(postmasterPidFile, { force: true });
|
|
}
|
|
try {
|
|
await instance.start();
|
|
} catch (error) {
|
|
throw toError(error, `Failed to start embedded PostgreSQL on port ${selectedPort}`);
|
|
}
|
|
|
|
const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${selectedPort}/postgres`;
|
|
await ensurePostgresDatabase(adminConnectionString, "paperclip");
|
|
|
|
return {
|
|
connectionString: `postgres://paperclip:paperclip@127.0.0.1:${selectedPort}/paperclip`,
|
|
source: `embedded-postgres@${selectedPort}`,
|
|
stop: async () => {
|
|
await instance.stop();
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function resolveMigrationConnection(): Promise<MigrationConnection> {
|
|
const target = resolveDatabaseTarget();
|
|
if (target.mode === "postgres") {
|
|
return {
|
|
connectionString: target.connectionString,
|
|
source: target.source,
|
|
stop: async () => {},
|
|
};
|
|
}
|
|
|
|
return ensureEmbeddedPostgresConnection(target.dataDir, target.port);
|
|
}
|