From 56d9df72469b32b1edb6cbddd6bf93d372c97525 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 30 May 2025 13:54:44 -0700 Subject: [PATCH] chore: Add CI job which runs the dev server (#61) * chore: Add CI job which runs the dev server * cr * cr * cr --- .github/workflows/ci.yml | 35 +++++ apps/open-swe/scripts/check-dev-server.ts | 157 ++++++++++++++++++++++ tsconfig.json | 38 +++--- 3 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 apps/open-swe/scripts/check-dev-server.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ee940a..5faf2a49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/apps/open-swe/scripts/check-dev-server.ts b/apps/open-swe/scripts/check-dev-server.ts new file mode 100644 index 00000000..2a0b2daa --- /dev/null +++ b/apps/open-swe/scripts/check-dev-server.ts @@ -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 { + 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); + }); diff --git a/tsconfig.json b/tsconfig.json index bb5584ed..2eff4856 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] }