Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
230
dashcaddy-installer/src/main/file-deployer.js
Normal file
230
dashcaddy-installer/src/main/file-deployer.js
Normal file
@@ -0,0 +1,230 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const copyFile = promisify(fs.copyFile);
|
||||
const mkdir = promisify(fs.mkdir);
|
||||
const readdir = promisify(fs.readdir);
|
||||
|
||||
/**
|
||||
* FileDeployer - Handles copying dashboard and API files to installation directory
|
||||
*
|
||||
* In dev mode: reads from sibling directories (../../../status, ../../../dashcaddy-api)
|
||||
* In packaged exe: reads from extraResources (process.resourcesPath/status, .../dashcaddy-api)
|
||||
*/
|
||||
class FileDeployer {
|
||||
constructor() {
|
||||
this._resolveSourcePaths();
|
||||
}
|
||||
|
||||
_resolveSourcePaths() {
|
||||
let app;
|
||||
try {
|
||||
app = require('electron').app;
|
||||
} catch {
|
||||
// Not in Electron context
|
||||
}
|
||||
|
||||
const isPackaged = app?.isPackaged || false;
|
||||
|
||||
if (isPackaged) {
|
||||
// Packaged exe: files are in extraResources
|
||||
this.dashboardSource = path.join(process.resourcesPath, 'status');
|
||||
this.apiSource = path.join(process.resourcesPath, 'dashcaddy-api');
|
||||
} else {
|
||||
// Dev mode: sibling directories in the project tree
|
||||
this.dashboardSource = path.join(__dirname, '../../../status');
|
||||
this.apiSource = path.join(__dirname, '../../../dashcaddy-api');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively, skipping node_modules, .git, and test files
|
||||
*/
|
||||
async copyDirectory(src, dest, progressCallback = null) {
|
||||
try {
|
||||
await mkdir(dest, { recursive: true });
|
||||
|
||||
const entries = await readdir(src, { withFileTypes: true });
|
||||
let copiedCount = 0;
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
|
||||
// Skip unnecessary directories and files
|
||||
if (['node_modules', '.git', '__tests__', 'test', '.nyc_output', 'coverage'].includes(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
// Skip test files
|
||||
if (entry.name.endsWith('.test.js') || entry.name.endsWith('.spec.js')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
const subResult = await this.copyDirectory(srcPath, destPath, progressCallback);
|
||||
if (subResult.success) copiedCount += subResult.filesCopied;
|
||||
} else {
|
||||
await copyFile(srcPath, destPath);
|
||||
copiedCount++;
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
type: 'file',
|
||||
source: srcPath,
|
||||
destination: destPath,
|
||||
count: copiedCount
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, filesCopied: copiedCount };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy dashboard files to installation directory
|
||||
*/
|
||||
async deployDashboard(installPath, progressCallback = null) {
|
||||
try {
|
||||
const dashboardDest = path.join(installPath, 'sites', 'status');
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
type: 'start',
|
||||
message: 'Deploying dashboard files...',
|
||||
source: this.dashboardSource,
|
||||
destination: dashboardDest
|
||||
});
|
||||
}
|
||||
|
||||
// Verify source exists
|
||||
if (!fs.existsSync(this.dashboardSource)) {
|
||||
throw new Error(`Dashboard source not found at: ${this.dashboardSource}`);
|
||||
}
|
||||
|
||||
const result = await this.copyDirectory(
|
||||
this.dashboardSource,
|
||||
dashboardDest,
|
||||
progressCallback
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
type: 'complete',
|
||||
message: `Dashboard deployed (${result.filesCopied} files)`,
|
||||
filesCopied: result.filesCopied
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: dashboardDest,
|
||||
filesCopied: result.filesCopied
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy API server files to installation directory
|
||||
*/
|
||||
async deployAPI(installPath, progressCallback = null) {
|
||||
try {
|
||||
const apiDest = path.join(installPath, 'sites', 'dashcaddy-api');
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
type: 'start',
|
||||
message: 'Deploying API server files...',
|
||||
source: this.apiSource,
|
||||
destination: apiDest
|
||||
});
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.apiSource)) {
|
||||
throw new Error(`API source not found at: ${this.apiSource}`);
|
||||
}
|
||||
|
||||
const result = await this.copyDirectory(
|
||||
this.apiSource,
|
||||
apiDest,
|
||||
progressCallback
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
type: 'complete',
|
||||
message: `API server deployed (${result.filesCopied} files)`,
|
||||
filesCopied: result.filesCopied
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: apiDest,
|
||||
filesCopied: result.filesCopied
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy both dashboard and API files
|
||||
*/
|
||||
async deployComplete(installPath, progressCallback = null) {
|
||||
try {
|
||||
const dashboardResult = await this.deployDashboard(installPath, progressCallback);
|
||||
if (!dashboardResult.success) {
|
||||
throw new Error(`Dashboard deployment failed: ${dashboardResult.error}`);
|
||||
}
|
||||
|
||||
const apiResult = await this.deployAPI(installPath, progressCallback);
|
||||
if (!apiResult.success) {
|
||||
throw new Error(`API deployment failed: ${apiResult.error}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dashboard: dashboardResult,
|
||||
api: apiResult
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if source directories exist
|
||||
*/
|
||||
async validateSources() {
|
||||
const checks = {
|
||||
dashboard: fs.existsSync(this.dashboardSource),
|
||||
api: fs.existsSync(this.apiSource)
|
||||
};
|
||||
|
||||
return {
|
||||
valid: checks.dashboard && checks.api,
|
||||
checks,
|
||||
paths: {
|
||||
dashboard: this.dashboardSource,
|
||||
api: this.apiSource
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileDeployer;
|
||||
Reference in New Issue
Block a user