Karan6933's picture
Upload 35 files
05d91af verified
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { detectLanguage } from './lib/detector.js';
import { startProject, stopProject, getProjectStatus, listRunningProjects, stopAllProjects } from './lib/projectManager.js';
import { initStore, createProject, getProject, getAllProjects, updateProject, deleteProject, deleteProjectFiles, generateId } from './lib/projectsStore.js';
import { writeProjectFiles, detectLanguageFromFiles, scaffoldNodeJS, scaffoldPython } from './lib/projectScaffolder.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const projectLogs = new Map();
const projectsBasePath = join(__dirname, 'projects');
initStore();
app.use(cors());
app.use(express.json());
app.get('/', (req, res) => {
const projects = listRunningProjects();
res.json({
message: 'Project Runner Server',
version: '2.1.0',
runningProjects: projects,
endpoints: {
'GET /': 'Server info',
'GET /health': 'Server health status',
'GET /projects': 'List all projects in ./projects folder',
'GET /projects/:id': 'Start/access project by ID',
'GET /projects/:id/status': 'Get project status',
'GET /projects/:id/logs': 'Get project startup logs',
'POST /projects/:id/stop': 'Stop a running project',
'POST /projects/:id/restart': 'Restart a project',
'POST /projects/stop-all': 'Stop all running projects',
'POST /api/deploy': 'Deploy a new project (upload files)',
'GET /api/deployed': 'List all deployed projects',
'GET /api/deployed/:id': 'Get deployed project details',
'POST /api/deployed/:id/start': 'Start a deployed project',
'PUT /api/deployed/:id': 'Update a deployed project',
'DELETE /api/deployed/:id': 'Delete a deployed project'
}
});
});
app.get('/health', (req, res) => {
const projects = listRunningProjects();
res.json({
status: 'ok',
uptime: process.uptime(),
memory: process.memoryUsage(),
projects: {
count: projects.length,
running: projects
}
});
});
app.get('/projects', async (req, res) => {
try {
const { readdir } = await import('node:fs/promises');
const projectsDir = join(__dirname, 'projects');
try {
const entries = await readdir(projectsDir, { withFileTypes: true });
const projects = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const status = getProjectStatus(entry.name);
try {
const langInfo = await detectLanguage(join(projectsDir, entry.name));
projects.push({
id: entry.name,
isDirectory: true,
status: status.status,
language: langInfo ? langInfo.language : 'unknown',
isStreamlit: langInfo?.isStreamlit || false,
tunnelUrl: status.tunnelUrl || null,
localUrl: status.localUrl || null,
tunnelProvider: status.tunnelProvider || null
});
} catch {
projects.push({
id: entry.name,
isDirectory: true,
status: status.status,
language: 'unknown',
isStreamlit: false,
tunnelUrl: null,
localUrl: null,
tunnelProvider: null
});
}
}
}
res.json({ projects });
} catch {
res.status(404).json({ error: 'Projects directory not found' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/projects/:id', async (req, res) => {
const projectId = req.params.id;
const projectsDir = join(__dirname, 'projects');
const projectPath = join(projectsDir, projectId);
const existingStatus = getProjectStatus(projectId);
if (existingStatus.running) {
return res.json({
success: true,
projectId,
status: 'running',
port: existingStatus.port,
tunnelUrl: existingStatus.tunnelUrl,
localUrl: existingStatus.localUrl,
tunnelProvider: existingStatus.tunnelProvider || null,
message: 'Project already running',
iframeUrl: existingStatus.tunnelUrl || existingStatus.localUrl
});
}
try {
const langInfo = await detectLanguage(projectPath);
if (!langInfo) {
return res.status(400).json({
success: false,
error: 'Could not detect project language',
message: 'Make sure your project has a valid entry file (package.json, main.py, index.html, etc.)'
});
}
console.log(`[${projectId}] Detected ${langInfo.language} project (via ${langInfo.detectedFile})`);
const result = await startProject(projectId, projectPath, langInfo.config, langInfo.isStreamlit);
if (result.success) {
res.json({
success: true,
projectId,
language: langInfo.language,
isStreamlit: langInfo.isStreamlit || false,
status: result.status,
port: result.port,
tunnelUrl: result.tunnelUrl,
localUrl: result.localUrl,
pid: result.pid,
message: result.message,
tunnelProvider: result.tunnelUrl?.includes('trycloudflare') ? 'cloudflare' :
result.tunnelUrl?.includes('ngrok') ? 'ngrok' : null,
iframeUrl: result.tunnelUrl || result.localUrl
});
} else {
res.status(500).json({
success: false,
projectId,
error: result.error,
status: result.status,
message: result.message
});
}
} catch (error) {
console.error(`[${projectId}] Error:`, error);
res.status(500).json({
success: false,
projectId,
error: error.message,
status: 'error'
});
}
});
app.get('/projects/:id/status', (req, res) => {
const projectId = req.params.id;
const status = getProjectStatus(projectId);
res.json({
projectId,
...status
});
});
app.get('/projects/:id/logs', (req, res) => {
const projectId = req.params.id;
const logs = projectLogs.get(projectId) || [];
res.json({
projectId,
logs: logs,
hasLogs: logs.length > 0
});
});
app.post('/projects/:id/stop', async (req, res) => {
const projectId = req.params.id;
try {
const result = await stopProject(projectId);
res.json({
projectId,
...result
});
} catch (error) {
res.status(500).json({
success: false,
projectId,
error: error.message
});
}
});
app.post('/projects/:id/restart', async (req, res) => {
const projectId = req.params.id;
const projectsDir = join(__dirname, 'projects');
const projectPath = join(projectsDir, projectId);
try {
await stopProject(projectId);
await new Promise(resolve => setTimeout(resolve, 1000));
const langInfo = await detectLanguage(projectPath);
if (!langInfo) {
return res.status(400).json({
success: false,
error: 'Could not detect project language'
});
}
const result = await startProject(projectId, projectPath, langInfo.config, langInfo.isStreamlit);
res.json({
...result,
projectId,
message: result.success ? `Project restarted. ${result.message}` : result.message
});
} catch (error) {
res.status(500).json({
success: false,
projectId,
error: error.message
});
}
});
app.post('/projects/stop-all', async (req, res) => {
try {
await stopAllProjects();
res.json({
success: true,
message: 'All projects stopped'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
app.post('/api/deploy', async (req, res) => {
try {
const { name, files, language, config } = req.body;
if (!files || typeof files !== 'object') {
return res.status(400).json({
success: false,
error: 'files object is required'
});
}
const projectId = generateId();
const detected = detectLanguageFromFiles(files);
const lang = language || detected.language;
let processedFiles = { ...files };
if (lang === 'nodejs') processedFiles = scaffoldNodeJS(processedFiles);
if (lang === 'python' || lang === 'streamlit') processedFiles = scaffoldPython(processedFiles);
const project = createProject({
name: name || `project-${projectId}`,
language: lang,
files: processedFiles,
config: config || {},
metadata: { detectedLanguage: detected.language, isStreamlit: detected.isStreamlit }
});
await writeProjectFiles(projectId, processedFiles, projectsBasePath);
res.json({
success: true,
projectId: project.id,
name: project.name,
language: project.language,
message: 'Project deployed successfully'
});
} catch (error) {
console.error('Deploy error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
app.get('/api/deployed', (req, res) => {
const projects = getAllProjects();
const result = projects.map(p => ({
id: p.id,
name: p.name,
language: p.language,
isStreamlit: p.metadata?.isStreamlit || false,
createdAt: p.createdAt,
updatedAt: p.updatedAt
}));
res.json({ projects: result });
});
app.get('/api/deployed/:id', (req, res) => {
const project = getProject(req.params.id);
if (!project) {
return res.status(404).json({
success: false,
error: 'Project not found'
});
}
const status = getProjectStatus(req.params.id);
res.json({
success: true,
project: {
id: project.id,
name: project.name,
language: project.language,
isStreamlit: project.metadata?.isStreamlit || false,
files: Object.keys(project.files),
createdAt: project.createdAt,
updatedAt: project.updatedAt,
status: status.status,
tunnelUrl: status.tunnelUrl || null,
localUrl: status.localUrl || null
}
});
});
app.post('/api/deployed/:id/start', async (req, res) => {
const projectId = req.params.id;
const project = getProject(projectId);
if (!project) {
return res.status(404).json({
success: false,
error: 'Project not found'
});
}
const projectPath = join(projectsBasePath, projectId);
const langInfo = await detectLanguage(projectPath);
if (!langInfo) {
return res.status(400).json({
success: false,
error: 'Could not detect project language'
});
}
const result = await startProject(projectId, projectPath, langInfo.config, langInfo.isStreamlit);
res.json(result);
});
app.put('/api/deployed/:id', async (req, res) => {
try {
const projectId = req.params.id;
const project = getProject(projectId);
if (!project) {
return res.status(404).json({
success: false,
error: 'Project not found'
});
}
const { files, name, config } = req.body;
if (files) {
let processedFiles = { ...files };
const lang = req.body.language || project.language;
if (lang === 'nodejs') processedFiles = scaffoldNodeJS(processedFiles);
if (lang === 'python' || lang === 'streamlit') processedFiles = scaffoldPython(processedFiles);
await writeProjectFiles(projectId, processedFiles, projectsBasePath);
project.files = processedFiles;
}
if (name) project.name = name;
if (config) project.config = { ...project.config, ...config };
updateProject(projectId, project);
res.json({
success: true,
projectId,
message: 'Project updated successfully'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
app.delete('/api/deployed/:id', async (req, res) => {
const projectId = req.params.id;
const project = getProject(projectId);
if (!project) {
return res.status(404).json({
success: false,
error: 'Project not found'
});
}
await stopProject(projectId);
await deleteProjectFiles(projectId);
deleteProject(projectId);
res.json({
success: true,
projectId,
message: 'Project deleted successfully'
});
});
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`Project Runner Server running on port ${PORT}`);
console.log(`Access at: http://localhost:${PORT}`);
console.log(`Projects directory: ${join(__dirname, 'projects')}`);
});
process.on('SIGTERM', async () => {
console.log('Shutting down... Stopping all projects...');
await stopAllProjects();
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
process.on('SIGINT', async () => {
console.log('Shutting down... Stopping all projects...');
await stopAllProjects();
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
export default app;