BMAD-METHOD/tools/installer/core/legacy-warnings.js

109 lines
3.4 KiB
JavaScript

const os = require('node:os');
const path = require('node:path');
const semver = require('semver');
const fs = require('../fs-native');
const prompts = require('../prompts');
const MIN_NATIVE_SKILLS_VERSION = '6.1.0';
const LEGACY_PATHS = [
'.agent/workflows',
'.augment/commands',
'.claude/commands',
'.clinerules/workflows',
'.codex/prompts',
'~/.codex/prompts',
'.codebuddy/commands',
'.crush/commands',
'.cursor/commands',
'.gemini/commands',
'.github/agents',
'.github/prompts',
'.iflow/commands',
'.kilocode/workflows',
'.kiro/steering',
'.opencode/agents',
'.opencode/commands',
'.opencode/agent',
'.opencode/command',
'.qwen/commands',
'.roo/commands',
'.rovodev/workflows',
'.trae/rules',
'.windsurf/workflows',
];
function expandPath(p) {
if (p === '~') return os.homedir();
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
return p;
}
function resolveLegacyPath(projectRoot, p) {
if (path.isAbsolute(p) || p.startsWith('~')) return expandPath(p);
return path.join(projectRoot, p);
}
async function findStaleLegacyDirs(projectRoot) {
const findings = [];
for (const legacyPath of LEGACY_PATHS) {
const resolved = resolveLegacyPath(projectRoot, legacyPath);
if (!(await fs.pathExists(resolved))) continue;
try {
const entries = await fs.readdir(resolved);
const bmadEntries = entries.filter(
(e) => typeof e === 'string' && e.toLowerCase().startsWith('bmad') && !e.toLowerCase().startsWith('bmad-os-'),
);
if (bmadEntries.length > 0) {
findings.push({ path: resolved, displayPath: legacyPath, count: bmadEntries.length });
}
} catch {
// Unreadable dir — skip
}
}
return findings;
}
function isPreNativeSkillsVersion(version) {
if (!version) return false;
const coerced = semver.valid(version) || semver.valid(semver.coerce(version));
if (!coerced) return false;
return semver.lt(coerced, MIN_NATIVE_SKILLS_VERSION);
}
async function warnPreNativeSkillsLegacy({ projectRoot, existingVersion } = {}) {
const versionTriggered = isPreNativeSkillsVersion(existingVersion);
const staleDirs = await findStaleLegacyDirs(projectRoot);
if (!versionTriggered && staleDirs.length === 0) return;
if (versionTriggered) {
await prompts.log.warn(
`Detected previous BMAD install v${existingVersion} (pre-${MIN_NATIVE_SKILLS_VERSION}). ` +
`BMAD switched to native skills format in v${MIN_NATIVE_SKILLS_VERSION}; old command/workflow directories from your prior install may still be present.`,
);
}
if (staleDirs.length > 0) {
await prompts.log.warn(
`Found stale BMAD entries in ${staleDirs.length} legacy location(s) that the new installer no longer manages. ` +
`Your AI tool may load these alongside the new skills, causing duplicates. Remove them manually:`,
);
for (const finding of staleDirs) {
await prompts.log.message(` rm -rf "${finding.path}"/bmad* # ${finding.count} stale entr${finding.count === 1 ? 'y' : 'ies'}`);
}
} else if (versionTriggered) {
await prompts.log.message(
' No stale legacy directories detected, but if your AI tool shows duplicate BMAD commands after install, check for old `bmad-*` entries in tool-specific dirs (e.g. .claude/commands, .cursor/commands).',
);
}
}
module.exports = {
warnPreNativeSkillsLegacy,
findStaleLegacyDirs,
isPreNativeSkillsVersion,
LEGACY_PATHS,
MIN_NATIVE_SKILLS_VERSION,
};