tops-bmad
Version:
CLI tool to install BMAD workflow files into any project with integrated Shai-Hulud 2.0 security scanning
129 lines (111 loc) • 4.63 kB
JavaScript
import fs from "fs-extra";
import path from "path";
import yaml from "js-yaml";
/**
* Creates or updates the project configuration file
* @param {Object} config - Configuration object with project details
* @returns {Promise<void>}
*/
export async function saveProjectConfig(config) {
try {
// Ensure .bmad/bmm directory exists
const configDir = path.join(process.cwd(), ".bmad", "bmm");
await fs.ensureDir(configDir);
const configPath = path.join(configDir, "config.yaml");
// Create project config object
const projectConfig = {
project: {
name: config.projectName || "",
applicationType: config.applicationType || "",
deviceSupport: config.deviceSupport || [],
multiTenancy: config.multiTenancy || false,
roleBasedAccess: config.roleBasedAccess || false,
backgroundCrons: config.backgroundCrons || false,
thirdPartyTools: config.thirdPartyTools || []
}
};
// Check if config file already exists
if (await fs.pathExists(configPath)) {
// Read existing config as text to preserve comments and formatting
const existingContent = await fs.readFile(configPath, "utf8");
// Check if project section already exists in the file (as a top-level key)
// Look for "project:" at the start of a line (possibly with whitespace before it)
const hasProjectSection = /^project:\s*$/m.test(existingContent);
if (hasProjectSection) {
// Project section exists, replace it while preserving everything else
const projectYaml = yaml.dump(projectConfig, {
indent: 2,
lineWidth: -1,
noRefs: true
});
// Find the project section and replace everything from "project:" to end of file
// This regex matches from "project:" to end of file
const replaceRegex = /^project:[\s\S]*$/m;
const lines = existingContent.split('\n');
let projectStartIndex = -1;
// Find the line with "project:"
for (let i = 0; i < lines.length; i++) {
if (/^\s*project:\s*$/.test(lines[i])) {
projectStartIndex = i;
break;
}
}
if (projectStartIndex >= 0) {
// Replace from project: line to end of file
const beforeProject = lines.slice(0, projectStartIndex).join('\n');
const updatedContent = beforeProject + '\n\n' + projectYaml.trim();
await fs.writeFile(configPath, updatedContent, "utf8");
console.log(`✅ Project configuration updated in ${configPath}`);
} else {
// Fallback: append if we can't find the exact location
const separator = existingContent.trim().endsWith('\n') ? '\n\n' : '\n\n';
const updatedContent = existingContent.trim() + separator + projectYaml.trim();
await fs.writeFile(configPath, updatedContent, "utf8");
console.log(`✅ Project configuration appended to ${configPath}`);
}
} else {
// Project section doesn't exist, append it preserving all existing content
const projectYaml = yaml.dump(projectConfig, {
indent: 2,
lineWidth: -1,
noRefs: true
});
// Add newline separator
const separator = existingContent.trim().endsWith('\n') ? '\n\n' : '\n\n';
const updatedContent = existingContent.trim() + separator + projectYaml;
await fs.writeFile(configPath, updatedContent, "utf8");
console.log(`✅ Project configuration appended to ${configPath}`);
}
} else {
// File doesn't exist, create new one
const yamlContent = yaml.dump(projectConfig, {
indent: 2,
lineWidth: -1,
noRefs: true
});
await fs.writeFile(configPath, yamlContent, "utf8");
console.log(`✅ Configuration saved to ${configPath}`);
}
} catch (error) {
console.error("❌ Error saving project configuration:", error.message);
throw error;
}
}
/**
* Reads the project configuration file
* @returns {Promise<Object|null>}
*/
export async function loadProjectConfig() {
try {
const configPath = path.join(process.cwd(), ".bmad", "bmm", "config.yaml");
if (!(await fs.pathExists(configPath))) {
return null;
}
const yamlContent = await fs.readFile(configPath, "utf8");
const config = yaml.load(yamlContent);
return config;
} catch (error) {
console.warn("⚠️ Warning: Could not load project configuration:", error.message);
return null;
}
}