303 lines
7.5 KiB
JavaScript
303 lines
7.5 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const AdmZip = require('adm-zip');
|
|
const processManager = require('./processManager');
|
|
const nginxManager = require('./nginxManager');
|
|
const config = require('../config');
|
|
|
|
// Use absolute paths for container environment
|
|
const DATA_DIR = '/app/data';
|
|
const PROJECTS_FILE = path.join(DATA_DIR, 'projects.json');
|
|
const LOGS_DIR = path.join(DATA_DIR, 'logs');
|
|
const DEPLOY_DIR = '/app/projects';
|
|
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
if (!fs.existsSync(LOGS_DIR)) {
|
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
}
|
|
|
|
if (!fs.existsSync(DEPLOY_DIR)) {
|
|
fs.mkdirSync(DEPLOY_DIR, { recursive: true });
|
|
}
|
|
|
|
const getProjects = () => {
|
|
// Ensure directory exists before writing
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
if (!fs.existsSync(PROJECTS_FILE)) {
|
|
fs.writeFileSync(PROJECTS_FILE, JSON.stringify([]));
|
|
return [];
|
|
}
|
|
return JSON.parse(fs.readFileSync(PROJECTS_FILE, 'utf8'));
|
|
};
|
|
|
|
const saveProjects = (projects) => {
|
|
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2));
|
|
};
|
|
|
|
const addLog = (projectId, message, type = 'info') => {
|
|
const logFile = path.join(LOGS_DIR, `${projectId}.json`);
|
|
const logs = fs.existsSync(logFile)
|
|
? JSON.parse(fs.readFileSync(logFile, 'utf8'))
|
|
: [];
|
|
|
|
logs.push({
|
|
timestamp: new Date().toISOString(),
|
|
message,
|
|
type
|
|
});
|
|
|
|
fs.writeFileSync(logFile, JSON.stringify(logs, null, 2));
|
|
};
|
|
|
|
const calculateFileHash = (filePath) => {
|
|
const content = fs.readFileSync(filePath);
|
|
return crypto.createHash('md5').update(content).digest('hex');
|
|
};
|
|
|
|
const fixHtmlPaths = (projectDir) => {
|
|
const htmlFiles = getAllFiles(projectDir).filter(f => f.endsWith('.html'));
|
|
|
|
for (const htmlFile of htmlFiles) {
|
|
let content = fs.readFileSync(htmlFile, 'utf8');
|
|
let modified = false;
|
|
|
|
// Fix absolute paths in HTML attributes
|
|
// src="/assets/..." -> src="./assets/..."
|
|
// href="/assets/..." -> href="./assets/..."
|
|
// Also fix other common static resource paths
|
|
const patterns = [
|
|
{ regex: /src="\/(?!\/|http)/g, replacement: 'src="./' },
|
|
{ regex: /href="\/(?!\/|http)/g, replacement: 'href="./' },
|
|
{ regex: /src='\/(?!\/|http)/g, replacement: "src='./" },
|
|
{ regex: /href='\/(?!\/|http)/g, replacement: "href='./" },
|
|
];
|
|
|
|
for (const { regex, replacement } of patterns) {
|
|
if (regex.test(content)) {
|
|
content = content.replace(regex, replacement);
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
if (modified) {
|
|
fs.writeFileSync(htmlFile, content);
|
|
console.log(`Fixed paths in ${htmlFile}`);
|
|
}
|
|
}
|
|
};
|
|
|
|
const getAllProjects = () => {
|
|
return getProjects();
|
|
};
|
|
|
|
const getProjectById = (id) => {
|
|
const projects = getProjects();
|
|
return projects.find(p => p.id === id);
|
|
};
|
|
|
|
const createProject = ({ name, description, files }) => {
|
|
const projects = getProjects();
|
|
const id = Date.now().toString();
|
|
|
|
const projectDir = path.join(DEPLOY_DIR, id);
|
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
|
|
const fileInfos = [];
|
|
|
|
files.forEach(file => {
|
|
const destPath = path.join(projectDir, file.originalname);
|
|
fs.copyFileSync(file.path, destPath);
|
|
fs.unlinkSync(file.path);
|
|
|
|
if (file.originalname.endsWith('.zip')) {
|
|
try {
|
|
const zip = new AdmZip(destPath);
|
|
zip.extractAllTo(projectDir, true);
|
|
fs.unlinkSync(destPath);
|
|
addLog(id, `Extracted ${file.originalname}`, 'success');
|
|
} catch (e) {
|
|
addLog(id, `Failed to extract ${file.originalname}: ${e.message}`, 'error');
|
|
}
|
|
} else {
|
|
fileInfos.push({
|
|
name: file.originalname,
|
|
size: file.size,
|
|
hash: calculateFileHash(destPath)
|
|
});
|
|
}
|
|
});
|
|
|
|
const allFiles = getAllFiles(projectDir);
|
|
|
|
// Fix absolute paths in HTML files to relative paths
|
|
fixHtmlPaths(projectDir);
|
|
|
|
const project = {
|
|
id,
|
|
name,
|
|
description,
|
|
files: allFiles.map(f => ({
|
|
name: path.relative(projectDir, f),
|
|
size: fs.statSync(f).size,
|
|
hash: calculateFileHash(f)
|
|
})),
|
|
status: 'stopped',
|
|
port: null,
|
|
url: null,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
lastDeployed: null
|
|
};
|
|
|
|
projects.push(project);
|
|
saveProjects(projects);
|
|
|
|
addLog(id, `Project "${name}" created with ${allFiles.length} file(s)`, 'success');
|
|
|
|
return project;
|
|
};
|
|
|
|
const getAllFiles = (dir) => {
|
|
const files = [];
|
|
const items = fs.readdirSync(dir);
|
|
|
|
for (const item of items) {
|
|
const fullPath = path.join(dir, item);
|
|
if (fs.statSync(fullPath).isDirectory()) {
|
|
files.push(...getAllFiles(fullPath));
|
|
} else {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
|
|
return files;
|
|
};
|
|
|
|
const updateProject = (id, updates) => {
|
|
const projects = getProjects();
|
|
const index = projects.findIndex(p => p.id === id);
|
|
|
|
if (index === -1) return null;
|
|
|
|
projects[index] = {
|
|
...projects[index],
|
|
...updates,
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
saveProjects(projects);
|
|
addLog(id, `Project updated: ${JSON.stringify(updates)}`);
|
|
|
|
return projects[index];
|
|
};
|
|
|
|
const deleteProject = (id) => {
|
|
const projects = getProjects();
|
|
const index = projects.findIndex(p => p.id === id);
|
|
|
|
if (index === -1) return false;
|
|
|
|
const project = projects[index];
|
|
|
|
if (project.status === 'running') {
|
|
processManager.stopProject(project);
|
|
}
|
|
|
|
if (config.useNginx === true || config.useNginx === 'true') {
|
|
nginxManager.removeProjectLocation(id);
|
|
}
|
|
|
|
const projectDir = path.join(DEPLOY_DIR, id);
|
|
if (fs.existsSync(projectDir)) {
|
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
}
|
|
|
|
const logFile = path.join(LOGS_DIR, `${id}.json`);
|
|
if (fs.existsSync(logFile)) {
|
|
fs.unlinkSync(logFile);
|
|
}
|
|
|
|
projects.splice(index, 1);
|
|
saveProjects(projects);
|
|
|
|
return true;
|
|
};
|
|
|
|
const addFilesToProject = (id, files) => {
|
|
const projects = getProjects();
|
|
const project = projects.find(p => p.id === id);
|
|
|
|
if (!project) return null;
|
|
|
|
const projectDir = path.join(DEPLOY_DIR, id);
|
|
|
|
const newFileInfos = files.map(file => {
|
|
const destPath = path.join(projectDir, file.originalname);
|
|
fs.copyFileSync(file.path, destPath);
|
|
fs.unlinkSync(file.path);
|
|
|
|
return {
|
|
name: file.originalname,
|
|
size: file.size,
|
|
hash: calculateFileHash(destPath)
|
|
};
|
|
});
|
|
|
|
project.files = [...project.files, ...newFileInfos];
|
|
project.updatedAt = new Date().toISOString();
|
|
|
|
saveProjects(projects);
|
|
addLog(id, `Added ${files.length} file(s) to project`);
|
|
|
|
return project;
|
|
};
|
|
|
|
const updateProjectStatus = (id, status, port = null, url = null) => {
|
|
const projects = getProjects();
|
|
const project = projects.find(p => p.id === id);
|
|
|
|
if (!project) return null;
|
|
|
|
project.status = status;
|
|
project.port = port;
|
|
project.url = url || config.getProjectUrl(id, port);
|
|
|
|
if (status === 'running') {
|
|
project.lastDeployed = new Date().toISOString();
|
|
addLog(id, `Project started on port ${port}`, 'success');
|
|
} else if (status === 'stopped') {
|
|
addLog(id, 'Project stopped');
|
|
}
|
|
|
|
saveProjects(projects);
|
|
|
|
return project;
|
|
};
|
|
|
|
const getProjectLogs = (id) => {
|
|
const logFile = path.join(LOGS_DIR, `${id}.json`);
|
|
|
|
if (!fs.existsSync(logFile)) {
|
|
return [];
|
|
}
|
|
|
|
return JSON.parse(fs.readFileSync(logFile, 'utf8'));
|
|
};
|
|
|
|
module.exports = {
|
|
getAllProjects,
|
|
getProjectById,
|
|
createProject,
|
|
updateProject,
|
|
deleteProject,
|
|
addFilesToProject,
|
|
updateProjectStatus,
|
|
getProjectLogs
|
|
}; |