Dev server updates (#52)
* Start dev server on any port * Move dev DB into project folder * Choose free ports for dev server * Reliability * Both processes get env vars * And open browser
This commit is contained in:
committed by
GitHub
parent
3dcd2ef701
commit
fcaf99ebf6
5
.gitignore
vendored
5
.gitignore
vendored
@@ -74,4 +74,7 @@ backend/bindings
|
|||||||
build-npm-package-codesign.sh
|
build-npm-package-codesign.sh
|
||||||
|
|
||||||
npx-cli/dist
|
npx-cli/dist
|
||||||
db.sqlite
|
backend/db.sqlite
|
||||||
|
|
||||||
|
# Development ports file
|
||||||
|
.dev-ports.json
|
||||||
@@ -187,10 +187,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.layer(Extension(app_state))
|
.layer(Extension(app_state))
|
||||||
.layer(CorsLayer::permissive());
|
.layer(CorsLayer::permissive());
|
||||||
|
|
||||||
let port: u16 = std::env::var("PORT")
|
let port: u16 = std::env::var("BACKEND_PORT")
|
||||||
|
.or_else(|_| std::env::var("PORT"))
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|p| p.parse().ok())
|
.and_then(|p| p.parse().ok())
|
||||||
.unwrap_or(if cfg!(debug_assertions) { 3001 } else { 0 });
|
.unwrap_or(0); // Use 0 to find free port if no specific port provided
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await?;
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await?;
|
||||||
let actual_port = listener.local_addr()?.port(); // get → 53427 (example)
|
let actual_port = listener.local_addr()?.port(); // get → 53427 (example)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use directories::ProjectDirs;
|
|||||||
pub mod shell;
|
pub mod shell;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
const PROJECT_ROOT: &str = env!("CARGO_MANIFEST_DIR");
|
||||||
|
|
||||||
/// Cache for WSL2 detection result
|
/// Cache for WSL2 detection result
|
||||||
static WSL2_CACHE: OnceLock<bool> = OnceLock::new();
|
static WSL2_CACHE: OnceLock<bool> = OnceLock::new();
|
||||||
|
|
||||||
@@ -31,18 +33,18 @@ pub fn is_wsl2() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn asset_dir() -> std::path::PathBuf {
|
pub fn asset_dir() -> std::path::PathBuf {
|
||||||
let proj = if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
ProjectDirs::from("ai", "bloop-dev", env!("CARGO_PKG_NAME"))
|
std::path::PathBuf::from(PROJECT_ROOT).join("../dev_assets")
|
||||||
.expect("OS didn't give us a home directory")
|
|
||||||
} else {
|
} else {
|
||||||
ProjectDirs::from("ai", "bloop", env!("CARGO_PKG_NAME"))
|
ProjectDirs::from("ai", "bloop", env!("CARGO_PKG_NAME"))
|
||||||
.expect("OS didn't give us a home directory")
|
.expect("OS didn't give us a home directory")
|
||||||
};
|
.data_dir()
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
// ✔ macOS → ~/Library/Application Support/MyApp
|
// ✔ macOS → ~/Library/Application Support/MyApp
|
||||||
// ✔ Linux → ~/.local/share/myapp (respects XDG_DATA_HOME)
|
// ✔ Linux → ~/.local/share/myapp (respects XDG_DATA_HOME)
|
||||||
// ✔ Windows → %APPDATA%\Example\MyApp
|
// ✔ Windows → %APPDATA%\Example\MyApp
|
||||||
proj.data_dir().to_path_buf()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_path() -> std::path::PathBuf {
|
pub fn config_path() -> std::path::PathBuf {
|
||||||
|
|||||||
19
dev_assets/config.json
Normal file
19
dev_assets/config.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"theme": "light",
|
||||||
|
"executor": {
|
||||||
|
"type": "claude"
|
||||||
|
},
|
||||||
|
"disclaimer_acknowledged": true,
|
||||||
|
"onboarding_acknowledged": true,
|
||||||
|
"sound_alerts": true,
|
||||||
|
"sound_file": "abstract-sound4",
|
||||||
|
"push_notifications": true,
|
||||||
|
"editor": {
|
||||||
|
"editor_type": "vscode",
|
||||||
|
"custom_command": null
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"token": "",
|
||||||
|
"default_pr_base": "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
dev_assets/db.sqlite
Normal file
BIN
dev_assets/db.sqlite
Normal file
Binary file not shown.
@@ -11,10 +11,10 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: parseInt(process.env.FRONTEND_PORT || '3000'),
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3001',
|
target: `http://localhost:${process.env.BACKEND_PORT || '3001'}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,19 +3,20 @@
|
|||||||
"version": "0.0.32",
|
"version": "0.0.32",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"cargo watch -w backend -x 'run --manifest-path backend/Cargo.toml'\" \"npm run frontend:dev\"",
|
"dev": "export FRONTEND_PORT=$(node scripts/manage-dev-ports.js frontend) && export BACKEND_PORT=$(node scripts/manage-dev-ports.js backend) && concurrently \"cargo watch -w backend -x 'run --manifest-path backend/Cargo.toml'\" \"npm run frontend:dev\"",
|
||||||
"build": "npm run frontend:build && cargo build --release --manifest-path backend/Cargo.toml && cargo build --release --bin mcp_task_server --manifest-path backend/Cargo.toml",
|
"build": "npm run frontend:build && cargo build --release --manifest-path backend/Cargo.toml && cargo build --release --bin mcp_task_server --manifest-path backend/Cargo.toml",
|
||||||
"build:single": "npm run frontend:build && cargo build --release --manifest-path backend/Cargo.toml",
|
"build:single": "npm run frontend:build && cargo build --release --manifest-path backend/Cargo.toml",
|
||||||
"build:npm": "./build-npm-package.sh",
|
"build:npm": "./build-npm-package.sh",
|
||||||
"test:npm": "./test-npm-package.sh",
|
"test:npm": "./test-npm-package.sh",
|
||||||
"frontend:dev": "cd frontend && npm run dev",
|
"frontend:dev": "cd frontend && npm run dev -- --port ${FRONTEND_PORT:-3000} --open",
|
||||||
"frontend:build": "cd frontend && npm run build",
|
"frontend:build": "cd frontend && npm run build",
|
||||||
"backend:dev": "cargo watch -w backend -x 'run --manifest-path backend/Cargo.toml'",
|
"backend:dev": "BACKEND_PORT=$(node scripts/manage-dev-ports.js backend) cargo watch -w backend -x 'run --manifest-path backend/Cargo.toml'",
|
||||||
"backend:build": "cargo build --release --manifest-path backend/Cargo.toml",
|
"backend:build": "cargo build --release --manifest-path backend/Cargo.toml",
|
||||||
"backend:run": "cargo run --manifest-path backend/Cargo.toml",
|
"backend:run": "cargo run --manifest-path backend/Cargo.toml",
|
||||||
"backend:test": "cargo test --lib",
|
"backend:test": "cargo test --lib",
|
||||||
"generate-types": "cd backend && cargo run --bin generate_types",
|
"generate-types": "cd backend && cargo run --bin generate_types",
|
||||||
"prepare-db": "node scripts/prepare-db.js"
|
"prepare-db": "node scripts/prepare-db.js",
|
||||||
|
"dev:clear-ports": "node scripts/manage-dev-ports.js clear"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
|
|||||||
199
scripts/manage-dev-ports.js
Normal file
199
scripts/manage-dev-ports.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const net = require("net");
|
||||||
|
|
||||||
|
const PORTS_FILE = path.join(__dirname, "..", ".dev-ports.json");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a port is available
|
||||||
|
*/
|
||||||
|
function isPortAvailable(port) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const sock = net.createConnection({ port, host: "localhost" });
|
||||||
|
sock.on("connect", () => {
|
||||||
|
sock.destroy();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
sock.on("error", () => resolve(true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a free port starting from a given port
|
||||||
|
*/
|
||||||
|
async function findFreePort(startPort = 3000) {
|
||||||
|
let port = startPort;
|
||||||
|
while (!(await isPortAvailable(port))) {
|
||||||
|
port++;
|
||||||
|
if (port > 65535) {
|
||||||
|
throw new Error("No available ports found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load existing ports from file
|
||||||
|
*/
|
||||||
|
function loadPorts() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(PORTS_FILE)) {
|
||||||
|
const data = fs.readFileSync(PORTS_FILE, "utf8");
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to load existing ports:", error.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save ports to file
|
||||||
|
*/
|
||||||
|
function savePorts(ports) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(PORTS_FILE, JSON.stringify(ports, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save ports:", error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that saved ports are still available
|
||||||
|
*/
|
||||||
|
async function verifyPorts(ports) {
|
||||||
|
const frontendAvailable = await isPortAvailable(ports.frontend);
|
||||||
|
const backendAvailable = await isPortAvailable(ports.backend);
|
||||||
|
|
||||||
|
if (process.argv[2] === "get" && (!frontendAvailable || !backendAvailable)) {
|
||||||
|
console.log(
|
||||||
|
`Port availability check failed: frontend:${ports.frontend}=${frontendAvailable}, backend:${ports.backend}=${backendAvailable}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frontendAvailable && backendAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate ports for development
|
||||||
|
*/
|
||||||
|
async function allocatePorts() {
|
||||||
|
// Try to load existing ports first
|
||||||
|
const existingPorts = loadPorts();
|
||||||
|
|
||||||
|
if (existingPorts) {
|
||||||
|
// Verify existing ports are still available
|
||||||
|
if (await verifyPorts(existingPorts)) {
|
||||||
|
if (process.argv[2] === "get") {
|
||||||
|
console.log("Reusing existing dev ports:");
|
||||||
|
console.log(`Frontend: ${existingPorts.frontend}`);
|
||||||
|
console.log(`Backend: ${existingPorts.backend}`);
|
||||||
|
}
|
||||||
|
return existingPorts;
|
||||||
|
} else {
|
||||||
|
if (process.argv[2] === "get") {
|
||||||
|
console.log(
|
||||||
|
"Existing ports are no longer available, finding new ones..."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find new free ports
|
||||||
|
const frontendPort = await findFreePort(3000);
|
||||||
|
const backendPort = await findFreePort(frontendPort + 1);
|
||||||
|
|
||||||
|
const ports = {
|
||||||
|
frontend: frontendPort,
|
||||||
|
backend: backendPort,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
savePorts(ports);
|
||||||
|
|
||||||
|
if (process.argv[2] === "get") {
|
||||||
|
console.log("Allocated new dev ports:");
|
||||||
|
console.log(`Frontend: ${ports.frontend}`);
|
||||||
|
console.log(`Backend: ${ports.backend}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ports (allocate if needed)
|
||||||
|
*/
|
||||||
|
async function getPorts() {
|
||||||
|
const ports = await allocatePorts();
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear saved ports
|
||||||
|
*/
|
||||||
|
function clearPorts() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(PORTS_FILE)) {
|
||||||
|
fs.unlinkSync(PORTS_FILE);
|
||||||
|
console.log("Cleared saved dev ports");
|
||||||
|
} else {
|
||||||
|
console.log("No saved ports to clear");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to clear ports:", error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI interface
|
||||||
|
if (require.main === module) {
|
||||||
|
const command = process.argv[2];
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "get":
|
||||||
|
getPorts()
|
||||||
|
.then((ports) => {
|
||||||
|
console.log(JSON.stringify(ports));
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "clear":
|
||||||
|
clearPorts();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "frontend":
|
||||||
|
getPorts()
|
||||||
|
.then((ports) => {
|
||||||
|
console.log(ports.frontend);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "backend":
|
||||||
|
getPorts()
|
||||||
|
.then((ports) => {
|
||||||
|
console.log(ports.backend);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log("Usage:");
|
||||||
|
console.log(
|
||||||
|
" node manage-dev-ports.js get - Get all ports (allocate if needed)"
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
" node manage-dev-ports.js frontend - Get frontend port only"
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
" node manage-dev-ports.js backend - Get backend port only"
|
||||||
|
);
|
||||||
|
console.log(" node manage-dev-ports.js clear - Clear saved ports");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getPorts, clearPorts, findFreePort };
|
||||||
Reference in New Issue
Block a user