feat: v6.0.0-alpha.0 - the future is now
This commit is contained in:
239
tools/cli/lib/replace-project-root.js
Normal file
239
tools/cli/lib/replace-project-root.js
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Utility function to replace {project-root} placeholders with actual installation target
|
||||
* Used during BMAD installation to set correct paths in agent and task files
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
/**
|
||||
* Replace {project-root} and {output_folder}/ placeholders in a single file
|
||||
* @param {string} filePath - Path to the file to process
|
||||
* @param {string} projectRoot - The actual project root path to substitute (must include trailing slash)
|
||||
* @param {string} docOut - The document output path (with leading slash)
|
||||
* @param {boolean} removeCompletely - If true, removes placeholders entirely instead of replacing
|
||||
* @returns {boolean} - True if replacements were made, false otherwise
|
||||
*/
|
||||
function replacePlaceholdersInFile(filePath, projectRoot, docOut = '/docs', removeCompletely = false) {
|
||||
try {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
const originalContent = content;
|
||||
|
||||
if (removeCompletely) {
|
||||
// Remove placeholders entirely (for bundling)
|
||||
content = content.replaceAll('{project-root}', '');
|
||||
content = content.replaceAll('{output_folder}/', '');
|
||||
} else {
|
||||
// Handle the combined pattern first to avoid double slashes
|
||||
if (projectRoot && docOut) {
|
||||
// Replace {project-root}{output_folder}/ combinations first
|
||||
// Remove leading slash from docOut since projectRoot has trailing slash
|
||||
// Add trailing slash to docOut
|
||||
const docOutNoLeadingSlash = docOut.replace(/^\//, '');
|
||||
const docOutWithTrailingSlash = docOutNoLeadingSlash.endsWith('/') ? docOutNoLeadingSlash : docOutNoLeadingSlash + '/';
|
||||
content = content.replaceAll('{project-root}{output_folder}/', projectRoot + docOutWithTrailingSlash);
|
||||
}
|
||||
|
||||
// Then replace remaining individual placeholders
|
||||
if (projectRoot) {
|
||||
content = content.replaceAll('{project-root}', projectRoot);
|
||||
}
|
||||
|
||||
if (docOut) {
|
||||
// For standalone {output_folder}/, keep the leading slash and add trailing slash
|
||||
const docOutWithTrailingSlash = docOut.endsWith('/') ? docOut : docOut + '/';
|
||||
content = content.replaceAll('{output_folder}/', docOutWithTrailingSlash);
|
||||
}
|
||||
}
|
||||
|
||||
if (content !== originalContent) {
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${filePath}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy function name for backward compatibility
|
||||
*/
|
||||
function replaceProjectRootInFile(filePath, projectRoot, removeCompletely = false) {
|
||||
return replacePlaceholdersInFile(filePath, projectRoot, '/docs', removeCompletely);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively replace {project-root} and {output_folder}/ in all files in a directory
|
||||
* @param {string} dirPath - Directory to process
|
||||
* @param {string} projectRoot - The actual project root path to substitute (or null to remove)
|
||||
* @param {string} docOut - The document output path (with leading slash)
|
||||
* @param {Array<string>} extensions - File extensions to process (default: ['.md', '.xml', '.yaml'])
|
||||
* @param {boolean} removeCompletely - If true, removes placeholders entirely instead of replacing
|
||||
* @param {boolean} verbose - If true, show detailed output for each file
|
||||
* @returns {Object} - Stats object with counts of files processed and modified
|
||||
*/
|
||||
function replacePlaceholdersInDirectory(
|
||||
dirPath,
|
||||
projectRoot,
|
||||
docOut = '/docs',
|
||||
extensions = ['.md', '.xml', '.yaml'],
|
||||
removeCompletely = false,
|
||||
verbose = false,
|
||||
) {
|
||||
const stats = {
|
||||
processed: 0,
|
||||
modified: 0,
|
||||
errors: 0,
|
||||
};
|
||||
|
||||
function processDirectory(currentPath) {
|
||||
try {
|
||||
const items = fs.readdirSync(currentPath, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(currentPath, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
// Skip node_modules and .git directories
|
||||
if (item.name !== 'node_modules' && item.name !== '.git') {
|
||||
processDirectory(fullPath);
|
||||
}
|
||||
} else if (item.isFile()) {
|
||||
// Check if file has one of the target extensions
|
||||
const ext = path.extname(item.name).toLowerCase();
|
||||
if (extensions.includes(ext)) {
|
||||
stats.processed++;
|
||||
if (replacePlaceholdersInFile(fullPath, projectRoot, docOut, removeCompletely)) {
|
||||
stats.modified++;
|
||||
if (verbose) {
|
||||
console.log(`✓ Updated: ${fullPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory ${currentPath}:`, error.message);
|
||||
stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
processDirectory(dirPath);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy function for backward compatibility
|
||||
*/
|
||||
function replaceProjectRootInDirectory(dirPath, projectRoot, extensions = ['.md', '.xml'], removeCompletely = false) {
|
||||
return replacePlaceholdersInDirectory(dirPath, projectRoot, '/docs', extensions, removeCompletely);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders in a list of specific files
|
||||
* @param {Array<string>} filePaths - Array of file paths to process
|
||||
* @param {string} projectRoot - The actual project root path to substitute (or null to remove)
|
||||
* @param {string} docOut - The document output path (with leading slash)
|
||||
* @param {boolean} removeCompletely - If true, removes placeholders entirely instead of replacing
|
||||
* @returns {Object} - Stats object with counts of files processed and modified
|
||||
*/
|
||||
function replacePlaceholdersInFiles(filePaths, projectRoot, docOut = '/docs', removeCompletely = false) {
|
||||
const stats = {
|
||||
processed: 0,
|
||||
modified: 0,
|
||||
errors: 0,
|
||||
};
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
stats.processed++;
|
||||
try {
|
||||
if (replacePlaceholdersInFile(filePath, projectRoot, docOut, removeCompletely)) {
|
||||
stats.modified++;
|
||||
console.log(`✓ Updated: ${filePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${filePath}:`, error.message);
|
||||
stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy function for backward compatibility
|
||||
*/
|
||||
function replaceProjectRootInFiles(filePaths, projectRoot, removeCompletely = false) {
|
||||
return replacePlaceholdersInFiles(filePaths, projectRoot, '/docs', removeCompletely);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main installation helper - replaces {project-root} and {output_folder}/ during BMAD installation
|
||||
* @param {string} installPath - Path where BMAD is being installed
|
||||
* @param {string} targetProjectRoot - The project root to set in the files (slash will be added)
|
||||
* @param {string} docsOutputPath - The documentation output path (relative to project root)
|
||||
* @param {boolean} verbose - If true, show detailed output
|
||||
* @returns {Object} - Installation stats
|
||||
*/
|
||||
function processInstallation(installPath, targetProjectRoot, docsOutputPath = 'docs', verbose = false) {
|
||||
// Ensure project root has trailing slash since usage is like {project-root}/bmad
|
||||
const projectRootWithSlash = targetProjectRoot.endsWith('/') ? targetProjectRoot : targetProjectRoot + '/';
|
||||
|
||||
// Ensure docs path has leading slash (for internal use) but will add trailing slash during replacement
|
||||
const normalizedDocsPath = docsOutputPath.replaceAll(/^\/+|\/+$/g, '');
|
||||
const docOutPath = normalizedDocsPath ? `/${normalizedDocsPath}` : '/docs';
|
||||
|
||||
if (verbose) {
|
||||
console.log(`\nReplacing {project-root} with: ${projectRootWithSlash}`);
|
||||
console.log(`Replacing {output_folder}/ with: ${docOutPath}/`);
|
||||
console.log(`Processing files in: ${installPath}\n`);
|
||||
}
|
||||
|
||||
const stats = replacePlaceholdersInDirectory(installPath, projectRootWithSlash, docOutPath, ['.md', '.xml', '.yaml'], false, verbose);
|
||||
|
||||
if (verbose) {
|
||||
console.log('\n--- Installation Processing Complete ---');
|
||||
}
|
||||
console.log(`Files processed: ${stats.processed}`);
|
||||
console.log(`Files modified: ${stats.modified}`);
|
||||
if (stats.errors > 0) {
|
||||
console.log(`Errors encountered: ${stats.errors}`);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle helper - removes {project-root}/ references for web bundling
|
||||
* @param {string} bundlePath - Path where files are being bundled
|
||||
* @returns {Object} - Bundle stats
|
||||
*/
|
||||
function processBundleRemoval(bundlePath) {
|
||||
console.log(`\nRemoving {project-root}/ references for bundling`);
|
||||
console.log(`Processing files in: ${bundlePath}\n`);
|
||||
|
||||
const stats = replaceProjectRootInDirectory(bundlePath, null, ['.md', '.xml'], true);
|
||||
|
||||
console.log('\n--- Bundle Processing Complete ---');
|
||||
console.log(`Files processed: ${stats.processed}`);
|
||||
console.log(`Files modified: ${stats.modified}`);
|
||||
if (stats.errors > 0) {
|
||||
console.log(`Errors encountered: ${stats.errors}`);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
replacePlaceholdersInFile,
|
||||
replacePlaceholdersInDirectory,
|
||||
replacePlaceholdersInFiles,
|
||||
replaceProjectRootInFile,
|
||||
replaceProjectRootInDirectory,
|
||||
replaceProjectRootInFiles,
|
||||
processInstallation,
|
||||
processBundleRemoval,
|
||||
};
|
||||
Reference in New Issue
Block a user