Enterprise AI Workspace prototype: LobeChat (de-branded) + Enterprise Gateway + Postgres/Redis stack
Revalidate Docs / Revalidate Docs (push) Failing after 2s
E2E CI / Check Duplicate Run (push) Failing after 5s
Test CI / Check Duplicate Run (push) Failing after 6s
E2E CI / Test Web App (push) Has been skipped
Test CI / Test Packages (push) Has been skipped
Test CI / Test App (shard 1/3) (push) Has been skipped
Test CI / Test App (shard 2/3) (push) Has been skipped
Test CI / Test App (shard 3/3) (push) Has been skipped
Test CI / Test Desktop App (push) Has been skipped
🔄 Branch Synchronization / sync-branches (push) Failing after 11s
Test CI / Test Database (push) Has been skipped
Test CI / Merge and Upload App Coverage (push) Has been skipped
Database Schema Visualization CI / build (push) Failing after 4m14s
Revalidate Docs / Revalidate Docs (push) Failing after 2s
E2E CI / Check Duplicate Run (push) Failing after 5s
Test CI / Check Duplicate Run (push) Failing after 6s
E2E CI / Test Web App (push) Has been skipped
Test CI / Test Packages (push) Has been skipped
Test CI / Test App (shard 1/3) (push) Has been skipped
Test CI / Test App (shard 2/3) (push) Has been skipped
Test CI / Test App (shard 3/3) (push) Has been skipped
Test CI / Test Desktop App (push) Has been skipped
🔄 Branch Synchronization / sync-branches (push) Failing after 11s
Test CI / Test Database (push) Has been skipped
Test CI / Merge and Upload App Coverage (push) Has been skipped
Database Schema Visualization CI / build (push) Failing after 4m14s
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { type ChildProcess, spawn } from 'node:child_process';
|
||||
import dotenv from 'dotenv';
|
||||
import net from 'node:net';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const NEXT_HOST = 'localhost';
|
||||
|
||||
/**
|
||||
* Resolve the Next.js dev port.
|
||||
* Priority: -p CLI flag > PORT env var > 3010.
|
||||
*/
|
||||
const resolveNextPort = (): number => {
|
||||
const pIndex = process.argv.indexOf('-p');
|
||||
if (pIndex !== -1 && process.argv[pIndex + 1]) {
|
||||
return Number(process.argv[pIndex + 1]);
|
||||
}
|
||||
if (process.env.PORT) return Number(process.env.PORT);
|
||||
return 3010;
|
||||
};
|
||||
|
||||
const NEXT_PORT = resolveNextPort();
|
||||
const NEXT_ROOT_URL = `http://${NEXT_HOST}:${NEXT_PORT}/`;
|
||||
const NEXT_READY_TIMEOUT_MS = 180_000;
|
||||
const NEXT_READY_RETRY_MS = 400;
|
||||
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
let nextProcess: ChildProcess | undefined;
|
||||
let viteProcess: ChildProcess | undefined;
|
||||
let shuttingDown = false;
|
||||
|
||||
const runNpmScript = (scriptName: string) =>
|
||||
spawn(npmCommand, ['run', scriptName], {
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const isPortOpen = (host: string, port: number) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
const onDone = (result: boolean) => {
|
||||
socket.removeAllListeners();
|
||||
socket.destroy();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
socket.once('connect', () => onDone(true));
|
||||
socket.once('error', () => onDone(false));
|
||||
socket.setTimeout(1_000, () => onDone(false));
|
||||
});
|
||||
|
||||
const waitForNextReady = async () => {
|
||||
const startedAt = Date.now();
|
||||
|
||||
while (Date.now() - startedAt < NEXT_READY_TIMEOUT_MS) {
|
||||
if (await isPortOpen(NEXT_HOST, NEXT_PORT)) return;
|
||||
await wait(NEXT_READY_RETRY_MS);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Next server was not ready within ${NEXT_READY_TIMEOUT_MS / 1000}s on ${NEXT_HOST}:${NEXT_PORT}`,
|
||||
);
|
||||
};
|
||||
|
||||
const prewarmNextRootCompile = async () => {
|
||||
const response = await fetch(NEXT_ROOT_URL, { signal: AbortSignal.timeout(120_000) });
|
||||
console.log(`✅ Next prewarm request finished (${response.status}) ${NEXT_ROOT_URL}`);
|
||||
};
|
||||
|
||||
const runNextBackgroundTasks = () => {
|
||||
setTimeout(() => {
|
||||
console.log(`🔁 Next server URL: ${NEXT_ROOT_URL}`);
|
||||
}, 2_000);
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
await waitForNextReady();
|
||||
await prewarmNextRootCompile();
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Next prewarm skipped:', error);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const terminateChild = (child?: ChildProcess) => {
|
||||
if (!child || child.killed) return;
|
||||
child.kill('SIGTERM');
|
||||
};
|
||||
|
||||
const shutdownAll = (signal: NodeJS.Signals) => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
|
||||
terminateChild(viteProcess);
|
||||
terminateChild(nextProcess);
|
||||
|
||||
process.exitCode = signal === 'SIGINT' ? 130 : 143;
|
||||
};
|
||||
|
||||
const watchChildExit = (child: ChildProcess, name: 'next' | 'vite') => {
|
||||
child.once('exit', (code, signal) => {
|
||||
if (!shuttingDown) {
|
||||
console.error(
|
||||
`❌ ${name} exited unexpectedly (code: ${code ?? 'null'}, signal: ${signal ?? 'null'})`,
|
||||
);
|
||||
shutdownAll('SIGTERM');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
process.once('SIGINT', () => shutdownAll('SIGINT'));
|
||||
process.once('SIGTERM', () => shutdownAll('SIGTERM'));
|
||||
|
||||
nextProcess = spawn('npx', ['next', 'dev', '-p', String(NEXT_PORT)], {
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
watchChildExit(nextProcess, 'next');
|
||||
|
||||
viteProcess = runNpmScript('dev:spa');
|
||||
watchChildExit(viteProcess, 'vite');
|
||||
runNextBackgroundTasks();
|
||||
|
||||
await Promise.race([
|
||||
new Promise((resolve) => nextProcess?.once('exit', resolve)),
|
||||
new Promise((resolve) => viteProcess?.once('exit', resolve)),
|
||||
]);
|
||||
};
|
||||
|
||||
void main().catch((error) => {
|
||||
console.error('❌ dev startup sequence failed:', error);
|
||||
shutdownAll('SIGTERM');
|
||||
});
|
||||
Reference in New Issue
Block a user