mirror of
https://gh-proxy.org/https://github.com/klinsmaya/open-connector.git
synced 2026-11-08 01:16:52 +08:00
## Summary Add OOMOL cloud OAuth as a provider authorization source. Users can save an encrypted project API key, select a cloud provider configuration, authorize named connections, and run their actions and proxy requests through SaaS. Existing connections retain their source when the provider default changes, and third-party credentials remain in SaaS. - Implement project discovery, OAuth request synchronization, remote execution, and persistent cleanup of disconnected cloud accounts. Add SQLite, PostgreSQL, and D1 migrations and storage support, with instance reset and maintenance tooling. - Add Console project and authorization settings, localized errors, disconnect notifications, and styled OAuth completion pages that close after successful authorization. Align local development callback origins and proxy routes. - Improve safe request diagnostics and retry transient project health-check failures once. Execution requests are not automatically replayed. - Group storage implementations by database backend, share guarded HTTP requests with Marketplace, update Zod to 4.6.5, and load provider icons from the public catalog. - Document setup, maintenance, and the manual acceptance checklist. ## Validation - `npm run fix-check` passed. - `npm run build:web` passed. - Latest focused regression run: 346 tests passed across 33 files covering Console, SaaS project/client/routes, OAuth completion, and guarded HTTP requests. - Earlier implementation validation included the broader runtime/storage suites and a real PostgreSQL integration run. Full deployed action/proxy, cleanup recovery, and D1 maintenance acceptance remain documented manual checks; they are not claimed as completed by the unit tests. ## Deployment notes Apply migrations `0014_saas_project` and `0015_saas_cleanup_runtime`, configure encryption and an explicit `OOMOL_CONNECT_ORIGIN`, and enable the relevant cleanup scheduling. The remote SaaS deployment must support the project OAuth and remote execution APIs described in `docs/saas-oauth.md`.
96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import type { ChildProcess } from "node:child_process";
|
|
|
|
import { spawn, spawnSync } from "node:child_process";
|
|
|
|
interface DevProcess {
|
|
name: string;
|
|
child: ChildProcess;
|
|
}
|
|
|
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
|
|
runChecked(process.execPath, ["scripts/ensure-generated.ts"]);
|
|
|
|
const apiPort = process.env.PORT ?? "3000";
|
|
process.env.OOMOL_CONNECT_ORIGIN ??= "http://localhost:5173";
|
|
|
|
const processes: DevProcess[] = [
|
|
startProcess("api", process.execPath, ["--watch", "--watch-preserve-output", "src/server/index.ts"]),
|
|
startProcess("web", npmCommand, ["run", "dev", "--workspace", "web", "--", "--clearScreen", "false"]),
|
|
];
|
|
|
|
console.log(`API runtime: http://localhost:${apiPort}`);
|
|
console.log("Web console: http://localhost:5173");
|
|
|
|
await waitForProcesses(processes);
|
|
|
|
function runChecked(command: string, args: string[]): void {
|
|
const result = spawnSync(command, args, {
|
|
cwd: process.cwd(),
|
|
stdio: "inherit",
|
|
});
|
|
|
|
if (result.status !== 0) {
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
}
|
|
|
|
function startProcess(name: string, command: string, args: string[]): DevProcess {
|
|
return {
|
|
name,
|
|
child: spawn(command, args, {
|
|
cwd: process.cwd(),
|
|
stdio: "inherit",
|
|
}),
|
|
};
|
|
}
|
|
|
|
function waitForProcesses(processes: DevProcess[]): Promise<void> {
|
|
let shuttingDown = false;
|
|
|
|
return new Promise((resolve) => {
|
|
const shutdown = (exitCode: number, signal?: NodeJS.Signals): void => {
|
|
if (shuttingDown) {
|
|
return;
|
|
}
|
|
|
|
shuttingDown = true;
|
|
process.exitCode = exitCode;
|
|
stopProcesses(processes, signal);
|
|
resolve();
|
|
};
|
|
|
|
for (const processInfo of processes) {
|
|
processInfo.child.on("error", (error) => {
|
|
console.error(`${processInfo.name} failed to start: ${error.message}`);
|
|
shutdown(1);
|
|
});
|
|
processInfo.child.on("close", (code, signal) => {
|
|
if (!shuttingDown) {
|
|
if (signal && signal !== "SIGINT" && signal !== "SIGTERM") {
|
|
console.error(`${processInfo.name} exited with ${signal}.`);
|
|
} else if (code != null && code !== 0) {
|
|
console.error(`${processInfo.name} exited with code ${code}.`);
|
|
}
|
|
shutdown(code ?? exitCodeForSignal(signal));
|
|
}
|
|
});
|
|
}
|
|
|
|
process.once("SIGINT", () => shutdown(0, "SIGINT"));
|
|
process.once("SIGTERM", () => shutdown(0, "SIGTERM"));
|
|
});
|
|
}
|
|
|
|
function exitCodeForSignal(signal: NodeJS.Signals | null): number {
|
|
return signal === "SIGINT" || signal === "SIGTERM" || signal == null ? 0 : 1;
|
|
}
|
|
|
|
function stopProcesses(processes: DevProcess[], signal: NodeJS.Signals = "SIGTERM"): void {
|
|
for (const processInfo of processes) {
|
|
if (processInfo.child.exitCode == null && !processInfo.child.killed) {
|
|
processInfo.child.kill(signal);
|
|
}
|
|
}
|
|
}
|