277 lines
6.7 KiB
JavaScript
277 lines
6.7 KiB
JavaScript
const fs = require("fs-extra");
|
|
const path = require("path");
|
|
const crypto = require("crypto");
|
|
const glob = require("glob");
|
|
const yaml = require("js-yaml");
|
|
|
|
// Dynamic import for ES module
|
|
let chalk;
|
|
|
|
// Initialize ES modules
|
|
async function initializeModules() {
|
|
if (!chalk) {
|
|
chalk = (await import("chalk")).default;
|
|
}
|
|
}
|
|
|
|
class FileManager {
|
|
constructor() {
|
|
this.manifestDir = ".bmad-core";
|
|
this.manifestFile = "install-manifest.yaml";
|
|
}
|
|
|
|
async copyFile(source, destination) {
|
|
try {
|
|
await fs.ensureDir(path.dirname(destination));
|
|
await fs.copy(source, destination);
|
|
return true;
|
|
} catch (error) {
|
|
await initializeModules();
|
|
console.error(chalk.red(`Failed to copy ${source}:`), error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async copyDirectory(source, destination) {
|
|
try {
|
|
await fs.ensureDir(destination);
|
|
await fs.copy(source, destination);
|
|
return true;
|
|
} catch (error) {
|
|
await initializeModules();
|
|
console.error(
|
|
chalk.red(`Failed to copy directory ${source}:`),
|
|
error.message
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async copyGlobPattern(pattern, sourceDir, destDir) {
|
|
const files = glob.sync(pattern, { cwd: sourceDir });
|
|
const copied = [];
|
|
|
|
for (const file of files) {
|
|
const sourcePath = path.join(sourceDir, file);
|
|
const destPath = path.join(destDir, file);
|
|
|
|
if (await this.copyFile(sourcePath, destPath)) {
|
|
copied.push(file);
|
|
}
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
async calculateFileHash(filePath) {
|
|
try {
|
|
const content = await fs.readFile(filePath);
|
|
return crypto
|
|
.createHash("sha256")
|
|
.update(content)
|
|
.digest("hex")
|
|
.slice(0, 16);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async createManifest(installDir, config, files) {
|
|
const manifestPath = path.join(
|
|
installDir,
|
|
this.manifestDir,
|
|
this.manifestFile
|
|
);
|
|
|
|
// Read version from core-config.yaml
|
|
const coreConfigPath = path.join(__dirname, "../../../bmad-core/core-config.yaml");
|
|
let coreVersion = "unknown";
|
|
try {
|
|
const coreConfigContent = await fs.readFile(coreConfigPath, "utf8");
|
|
const coreConfig = yaml.load(coreConfigContent);
|
|
coreVersion = coreConfig.version || "unknown";
|
|
} catch (error) {
|
|
console.warn("Could not read version from core-config.yaml, using 'unknown'");
|
|
}
|
|
|
|
const manifest = {
|
|
version: coreVersion,
|
|
installed_at: new Date().toISOString(),
|
|
install_type: config.installType,
|
|
agent: config.agent || null,
|
|
ides_setup: config.ides || [],
|
|
expansion_packs: config.expansionPacks || [],
|
|
files: [],
|
|
};
|
|
|
|
// Add file information
|
|
for (const file of files) {
|
|
const filePath = path.join(installDir, file);
|
|
const hash = await this.calculateFileHash(filePath);
|
|
|
|
manifest.files.push({
|
|
path: file,
|
|
hash: hash,
|
|
modified: false,
|
|
});
|
|
}
|
|
|
|
// Write manifest
|
|
await fs.ensureDir(path.dirname(manifestPath));
|
|
await fs.writeFile(manifestPath, yaml.dump(manifest, { indent: 2 }));
|
|
|
|
return manifest;
|
|
}
|
|
|
|
async readManifest(installDir) {
|
|
const manifestPath = path.join(
|
|
installDir,
|
|
this.manifestDir,
|
|
this.manifestFile
|
|
);
|
|
|
|
try {
|
|
const content = await fs.readFile(manifestPath, "utf8");
|
|
return yaml.load(content);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async readExpansionPackManifest(installDir, packId) {
|
|
const manifestPath = path.join(
|
|
installDir,
|
|
`.${packId}`,
|
|
this.manifestFile
|
|
);
|
|
|
|
try {
|
|
const content = await fs.readFile(manifestPath, "utf8");
|
|
return yaml.load(content);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async checkModifiedFiles(installDir, manifest) {
|
|
const modified = [];
|
|
|
|
for (const file of manifest.files) {
|
|
const filePath = path.join(installDir, file.path);
|
|
const currentHash = await this.calculateFileHash(filePath);
|
|
|
|
if (currentHash && currentHash !== file.hash) {
|
|
modified.push(file.path);
|
|
}
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
async checkFileIntegrity(installDir, manifest) {
|
|
const result = {
|
|
missing: [],
|
|
modified: []
|
|
};
|
|
|
|
for (const file of manifest.files) {
|
|
const filePath = path.join(installDir, file.path);
|
|
|
|
// Skip checking the manifest file itself - it will always be different due to timestamps
|
|
if (file.path.endsWith('install-manifest.yaml')) {
|
|
continue;
|
|
}
|
|
|
|
if (!(await this.pathExists(filePath))) {
|
|
result.missing.push(file.path);
|
|
} else {
|
|
const currentHash = await this.calculateFileHash(filePath);
|
|
if (currentHash && currentHash !== file.hash) {
|
|
result.modified.push(file.path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async backupFile(filePath) {
|
|
const backupPath = filePath + ".bak";
|
|
let counter = 1;
|
|
let finalBackupPath = backupPath;
|
|
|
|
// Find a unique backup filename
|
|
while (await fs.pathExists(finalBackupPath)) {
|
|
finalBackupPath = `${filePath}.bak${counter}`;
|
|
counter++;
|
|
}
|
|
|
|
await fs.copy(filePath, finalBackupPath);
|
|
return finalBackupPath;
|
|
}
|
|
|
|
async ensureDirectory(dirPath) {
|
|
try {
|
|
await fs.ensureDir(dirPath);
|
|
return true;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async pathExists(filePath) {
|
|
return fs.pathExists(filePath);
|
|
}
|
|
|
|
async readFile(filePath) {
|
|
return fs.readFile(filePath, "utf8");
|
|
}
|
|
|
|
async writeFile(filePath, content) {
|
|
await fs.ensureDir(path.dirname(filePath));
|
|
await fs.writeFile(filePath, content);
|
|
}
|
|
|
|
async removeDirectory(dirPath) {
|
|
await fs.remove(dirPath);
|
|
}
|
|
|
|
async createExpansionPackManifest(installDir, packId, config, files) {
|
|
const manifestPath = path.join(
|
|
installDir,
|
|
`.${packId}`,
|
|
this.manifestFile
|
|
);
|
|
|
|
const manifest = {
|
|
version: config.expansionPackVersion || require("../../../package.json").version,
|
|
installed_at: new Date().toISOString(),
|
|
install_type: config.installType,
|
|
expansion_pack_id: config.expansionPackId,
|
|
expansion_pack_name: config.expansionPackName,
|
|
ides_setup: config.ides || [],
|
|
files: [],
|
|
};
|
|
|
|
// Add file information
|
|
for (const file of files) {
|
|
const filePath = path.join(installDir, file);
|
|
const hash = await this.calculateFileHash(filePath);
|
|
|
|
manifest.files.push({
|
|
path: file,
|
|
hash: hash,
|
|
modified: false,
|
|
});
|
|
}
|
|
|
|
// Write manifest
|
|
await fs.ensureDir(path.dirname(manifestPath));
|
|
await fs.writeFile(manifestPath, yaml.dump(manifest, { indent: 2 }));
|
|
|
|
return manifest;
|
|
}
|
|
}
|
|
|
|
module.exports = new FileManager();
|