Files
Brian Madison a6d075bd0b fix(installer): replace fs-extra with native node:fs to prevent file loss
fs-extra routes all operations through graceful-fs, which globally
monkey-patches node:fs with a deferred retry queue. During multi-module
installs (~500+ file ops), retried unlink operations from one module's
remove phase can fire after the next module's copy phase has written
files, silently deleting them non-deterministically.

Replace fs-extra with a thin fs-native.js wrapper over node:fs/promises
and node:fs. All 21 consumers now use native APIs with no global
monkey-patching, eliminating the retry-queue race condition entirely.

Closes #1779
2026-04-13 00:44:28 -05:00

78 lines
1.9 KiB
JavaScript

const path = require('node:path');
const fs = require('./fs-native');
/**
* Find the BMAD project root directory by looking for package.json
* or specific BMAD markers
*/
function findProjectRoot(startPath = __dirname) {
let currentPath = path.resolve(startPath);
// Keep going up until we find package.json with bmad-method
while (currentPath !== path.dirname(currentPath)) {
const packagePath = path.join(currentPath, 'package.json');
if (fs.existsSync(packagePath)) {
try {
const pkg = fs.readJsonSync(packagePath);
// Check if this is the BMAD project
if (pkg.name === 'bmad-method' || fs.existsSync(path.join(currentPath, 'src', 'core-skills'))) {
return currentPath;
}
} catch {
// Continue searching
}
}
// Also check for src/core-skills as a marker
if (fs.existsSync(path.join(currentPath, 'src', 'core-skills', 'agents'))) {
return currentPath;
}
currentPath = path.dirname(currentPath);
}
// If we can't find it, use process.cwd() as fallback
return process.cwd();
}
// Cache the project root after first calculation
let cachedRoot = null;
function getProjectRoot() {
if (!cachedRoot) {
cachedRoot = findProjectRoot();
}
return cachedRoot;
}
/**
* Get path to source directory
*/
function getSourcePath(...segments) {
return path.join(getProjectRoot(), 'src', ...segments);
}
/**
* Get path to a module's directory
* bmm is a built-in module directly under src/
* core is also directly under src/
* All other modules are stored remote
*/
function getModulePath(moduleName, ...segments) {
if (moduleName === 'core') {
return getSourcePath('core-skills', ...segments);
}
if (moduleName === 'bmm') {
return getSourcePath('bmm-skills', ...segments);
}
return getSourcePath('modules', moduleName, ...segments);
}
module.exports = {
getProjectRoot,
getSourcePath,
getModulePath,
findProjectRoot,
};