feat: update configuration for standalone server and add assembly script
This commit is contained in:
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn, execSync } from "child_process";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pkgDir = resolve(__dirname, "..");
|
||||
const pkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf-8"));
|
||||
const serverEntry = join(pkgDir, "dist", "server.js");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (["-v", "--version", "version"].includes(args[0])) {
|
||||
console.log(pkg.version);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (["-h", "--help", "help"].includes(args[0])) {
|
||||
console.log(`
|
||||
${pkg.name} v${pkg.version}
|
||||
|
||||
Usage: evoscientist-webui [--port <port>]
|
||||
|
||||
Starts the EvoScientist Web UI and opens it in your browser.
|
||||
Then enter your EvoScientist deployment URL (default http://127.0.0.1:6174)
|
||||
from \`EvoSci deploy\` to connect.
|
||||
|
||||
Options:
|
||||
--port <port> Port to run on (default 4716)
|
||||
-v, --version Show version
|
||||
-h, --help Show this help
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Resolve port: --port flag > PORT env > default.
|
||||
let port = 4716;
|
||||
const portIdx = args.indexOf("--port");
|
||||
if (portIdx !== -1 && args[portIdx + 1]) {
|
||||
port = parseInt(args[portIdx + 1], 10);
|
||||
} else if (process.env.PORT && !Number.isNaN(parseInt(process.env.PORT, 10))) {
|
||||
port = parseInt(process.env.PORT, 10);
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
console.log(` ⏳ Starting EvoScientist Web UI on ${url} …`);
|
||||
|
||||
const child = spawn(process.execPath, [serverEntry], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(port),
|
||||
HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
console.error(` ✗ Failed to start: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Poll until the server responds, then open the browser once.
|
||||
let opened = false;
|
||||
let waited = 0;
|
||||
function openBrowser() {
|
||||
if (opened) return;
|
||||
opened = true;
|
||||
const cmd =
|
||||
process.platform === "darwin"
|
||||
? `open "${url}"`
|
||||
: process.platform === "win32"
|
||||
? `start "" "${url}"`
|
||||
: `xdg-open "${url}"`;
|
||||
try {
|
||||
execSync(cmd, { stdio: "ignore" });
|
||||
} catch {
|
||||
// Browser couldn't be opened automatically — the URL is printed above.
|
||||
}
|
||||
}
|
||||
function poll() {
|
||||
fetch(url)
|
||||
.then(() => {
|
||||
console.log(` ✓ EvoScientist Web UI is running → ${url}`);
|
||||
openBrowser();
|
||||
})
|
||||
.catch(() => {
|
||||
waited += 500;
|
||||
if (waited < 30000) setTimeout(poll, 500);
|
||||
});
|
||||
}
|
||||
setTimeout(poll, 800);
|
||||
|
||||
child.on("exit", (code) => process.exit(code ?? 0));
|
||||
process.on("SIGINT", () => child.kill("SIGINT"));
|
||||
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
// Self-contained server bundle for the npm package (the bin launcher runs
|
||||
// dist/server.js). Needed because /api/skills is a server route.
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
+29
-5
@@ -1,17 +1,41 @@
|
||||
{
|
||||
"name": "evoscientist-webui",
|
||||
"version": "0.1.0",
|
||||
"name": "@evoscientist/webui",
|
||||
"version": "0.0.1",
|
||||
"description": "Web UI for EvoScientist — a self-evolving AI scientist built on DeepAgents/LangGraph.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"evoscientist",
|
||||
"ai-scientist",
|
||||
"research",
|
||||
"deepagents",
|
||||
"langgraph",
|
||||
"chat-ui",
|
||||
"webui"
|
||||
],
|
||||
"bin": {
|
||||
"evoscientist-webui": "./bin/evoscientist-webui.mjs"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack --port 4716",
|
||||
"build": "next build",
|
||||
"build": "next build && node scripts/assemble-standalone.mjs",
|
||||
"start": "next start --port 4716",
|
||||
"start:dist": "node dist/server.js",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
"format:check": "prettier --check .",
|
||||
"prepack": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/core": "1.1.19",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Assemble a self-contained server into dist/ from the Next standalone output.
|
||||
// `next build` with output:"standalone" produces:
|
||||
// .next/standalone/ (server.js + traced node_modules + .next/server)
|
||||
// .next/static/ (NOT inside standalone — must be copied)
|
||||
// We copy everything into dist/ so the published package ships one folder that
|
||||
// `node dist/server.js` can run.
|
||||
import { cp, rm, readdir } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
const STANDALONE = ".next/standalone";
|
||||
const STATIC = ".next/static";
|
||||
const PUBLIC = "public";
|
||||
const OUT = "dist";
|
||||
|
||||
if (!existsSync(STANDALONE)) {
|
||||
console.error(
|
||||
`✗ ${STANDALONE} not found. Did "next build" run with output:"standalone"?`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await rm(OUT, { recursive: true, force: true });
|
||||
await cp(STANDALONE, OUT, { recursive: true });
|
||||
await cp(STATIC, `${OUT}/.next/static`, { recursive: true });
|
||||
if (existsSync(PUBLIC)) {
|
||||
await cp(PUBLIC, `${OUT}/public`, { recursive: true });
|
||||
}
|
||||
|
||||
// Next copies the whole project root into the standalone bundle. Prune it down
|
||||
// to just the runtime essentials (drops src/, configs, and local notes like
|
||||
// CLAUDE.md/AGENTS.md so they never get published).
|
||||
const KEEP = new Set([
|
||||
"server.js",
|
||||
".next",
|
||||
"node_modules",
|
||||
"package.json",
|
||||
"public",
|
||||
]);
|
||||
for (const entry of await readdir(OUT)) {
|
||||
if (!KEEP.has(entry)) {
|
||||
await rm(`${OUT}/${entry}`, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✓ Assembled standalone server into ${OUT}/ (run: node ${OUT}/server.js)`
|
||||
);
|
||||
Reference in New Issue
Block a user