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
|
||||
|
||||
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(CorsLayer::permissive());
|
||||
|
||||
let port: u16 = std::env::var("PORT")
|
||||
let port: u16 = std::env::var("BACKEND_PORT")
|
||||
.or_else(|_| std::env::var("PORT"))
|
||||
.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 actual_port = listener.local_addr()?.port(); // get → 53427 (example)
|
||||
|
||||
@@ -5,6 +5,8 @@ use directories::ProjectDirs;
|
||||
pub mod shell;
|
||||
pub mod text;
|
||||
|
||||
const PROJECT_ROOT: &str = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
/// Cache for WSL2 detection result
|
||||
static WSL2_CACHE: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
@@ -31,18 +33,18 @@ pub fn is_wsl2() -> bool {
|
||||
}
|
||||
|
||||
pub fn asset_dir() -> std::path::PathBuf {
|
||||
let proj = if cfg!(debug_assertions) {
|
||||
ProjectDirs::from("ai", "bloop-dev", env!("CARGO_PKG_NAME"))
|
||||
.expect("OS didn't give us a home directory")
|
||||
if cfg!(debug_assertions) {
|
||||
std::path::PathBuf::from(PROJECT_ROOT).join("../dev_assets")
|
||||
} else {
|
||||
ProjectDirs::from("ai", "bloop", env!("CARGO_PKG_NAME"))
|
||||
.expect("OS didn't give us a home directory")
|
||||
};
|
||||
.data_dir()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
// ✔ macOS → ~/Library/Application Support/MyApp
|
||||
// ✔ Linux → ~/.local/share/myapp (respects XDG_DATA_HOME)
|
||||
// ✔ Windows → %APPDATA%\Example\MyApp
|
||||
proj.data_dir().to_path_buf()
|
||||
}
|
||||
|
||||
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: {
|
||||
port: 3000,
|
||||
port: parseInt(process.env.FRONTEND_PORT || '3000'),
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3001',
|
||||
target: `http://localhost:${process.env.BACKEND_PORT || '3001'}`,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
"version": "0.0.32",
|
||||
"private": true,
|
||||
"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:single": "npm run frontend:build && cargo build --release --manifest-path backend/Cargo.toml",
|
||||
"build:npm": "./build-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",
|
||||
"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:run": "cargo run --manifest-path backend/Cargo.toml",
|
||||
"backend:test": "cargo test --lib",
|
||||
"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": {
|
||||
"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