chore: Add CI job which runs the dev server (#61)

* chore: Add CI job which runs the dev server

* cr

* cr

* cr
This commit is contained in:
Brace Sproul 2025-05-30 13:54:44 -07:00 • committed by GitHub
parent b0eff07d22
commit 56d9df7246
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 212 additions and 18 deletions

View file

@ -89,3 +89,38 @@ jobs:
with:
ignore_words_file: .codespellignore
path: apps/open-swe/src
dev-server-check:
name: Check dev server startup
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/open-swe
steps:
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
working-directory: .
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: "yarn"
- name: Install dependencies
run: yarn install --immutable --mode=skip-build
working-directory: .
- name: Build all packages
run: yarn build
working-directory: .
- name: Build dev server check script
run: yarn tsc --module commonjs --skipLibCheck --outDir ./dist/scripts ./scripts/check-dev-server.ts
- name: Rename compiled script to .cjs
run: mv ./dist/scripts/check-dev-server.js ./dist/scripts/check-dev-server.cjs
- name: Create .env file for dev server check
# .env needs to be in the apps/open-swe directory
run: touch .env
- name: Run dev server check
run: node ./dist/scripts/check-dev-server.cjs
env:
NODE_ENV: development
timeout-minutes: 2

View file

@ -0,0 +1,157 @@
/* eslint-disable no-console */
import { spawn } from "child_process";
import * as path from "path";
/**
* Checks if the development server starts successfully.
* This script starts the dev server and monitors the output for 30 seconds
* to detect any errors that might occur during startup.
*/
function checkDevServer(): Promise<void> {
return new Promise((resolve, reject) => {
console.log("Starting development server in apps/agents...");
const scriptDir = __dirname;
const targetCwd = path.resolve(scriptDir, "..");
const serverProcess = spawn("yarn", ["dev"], {
cwd: targetCwd,
shell: true,
stdio: "pipe",
});
let errorDetected = false;
let output = "";
let serverReady = false;
serverProcess.stdout.on("data", (data) => {
const message = data.toString();
output += message;
console.log(message);
const lowerCaseMessage = message.toLowerCase();
if (
lowerCaseMessage.includes("ready") ||
lowerCaseMessage.includes("started") ||
lowerCaseMessage.includes("server running")
) {
serverReady = true;
console.log("Server ready message detected.");
}
// Check for common error patterns in the output
if (
lowerCaseMessage.includes("error") ||
lowerCaseMessage.includes("exception:") ||
lowerCaseMessage.includes("failed to compile") ||
lowerCaseMessage.includes("failed")
) {
// Avoid flagging warnings as errors if they contain the word 'error'
if (!lowerCaseMessage.includes("warning")) {
errorDetected = true;
console.error("Error detected in server output!");
console.error(output);
} else {
console.log(
"Warning detected, not treating as fatal error:",
message,
);
}
}
});
serverProcess.stderr.on("data", (data) => {
const message = data.toString();
output += message;
console.error("stderr:", message); // Log stderr for debugging
const lowerCaseMessage = message.toLowerCase();
// Stderr output often indicates errors, but sometimes includes warnings or debug info
// Be cautious about immediately flagging all stderr as errors
if (
!lowerCaseMessage.includes("warning:") &&
!lowerCaseMessage.includes("deprecated")
) {
errorDetected = true;
console.error("Potential error detected in server stderr output!");
console.error(output);
}
});
serverProcess.on("error", (error) => {
console.error("Failed to start server process:", error);
errorDetected = true;
});
serverProcess.on("close", (code) => {
console.log(`Server process exited with code ${code}`);
// If the process exits prematurely (and not killed by us), it might be an error
// We will rely on the timeout check primarily, but this can be an indicator
if (code !== 0 && code !== null && !serverProcess.killed) {
// Check if exit was non-zero and not initiated by our kill()
// If it exits early *without* the ready flag set, consider it a failure.
if (!serverReady) {
console.error(`Server process exited prematurely with code ${code}.`);
errorDetected = true;
}
}
});
// Set timeout to wait for server to stabilize or show errors
const timeoutDuration = 15000; // 15 seconds
const timeoutId = setTimeout(() => {
if (!serverProcess.killed) {
console.log(
`Timeout reached (${timeoutDuration / 1000}s). Killing server process.`,
);
const killed = serverProcess.kill("SIGTERM");
if (!killed) {
console.warn(
"Failed to kill server process with SIGTERM, attempting SIGKILL.",
);
serverProcess.kill("SIGKILL");
}
} else {
console.log("Server process already exited before timeout.");
}
if (errorDetected) {
console.error(
"Server check failed! Errors were detected during server startup.",
);
reject(
new Error(
"Errors detected during server startup. Check logs for details.",
),
);
} else if (!serverReady) {
console.error(
"Server check failed! Server did not indicate readiness within the timeout.",
);
reject(
new Error(
"Server did not indicate successful startup within timeout.",
),
);
} else {
console.log(
"Server check passed! Server started successfully and indicated readiness.",
);
resolve();
}
}, timeoutDuration);
// Ensure timeout doesn't keep process alive if promise settles early
serverProcess.on("exit", () => clearTimeout(timeoutId));
});
}
checkDevServer()
.then(() => {
console.log("✅ Dev server check completed successfully!");
process.exit(0);
})
.catch((error) => {
console.error(`❌ Dev server check failed: ${error.message}`);
process.exit(1);
});

View file

@ -1,23 +1,25 @@
{
"extends": "@tsconfig/recommended",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"target": "ES2021",
"lib": ["ES2023"],
"module": "NodeNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
"noImplicitReturns": true,
"declaration": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"useDefineForClassFields": true,
"strictPropertyInitialization": false,
"allowJs": true,
"strict": true,
"strictFunctionTypes": false,
"outDir": "dist",
"types": ["jest", "node"],
"resolveJsonModule": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": ["**/*.ts"],
"exclude": ["node_modules", "dist"]
}