84 lines
3.1 KiB
JavaScript
84 lines
3.1 KiB
JavaScript
const path = require('node:path');
|
|
const fs = require('../fs-native');
|
|
|
|
const STORY_SKILL = 'bmad-story-automator';
|
|
const REVIEW_SKILL = 'bmad-story-automator-review';
|
|
|
|
async function installBmaRuntimePackage(moduleName, sourcePath, targetPath, fileTrackingCallback = null) {
|
|
if (moduleName !== 'bma') return false;
|
|
|
|
const storyTarget = path.join(targetPath, STORY_SKILL);
|
|
const reviewTarget = path.join(targetPath, REVIEW_SKILL);
|
|
if (!(await fs.pathExists(path.join(storyTarget, 'SKILL.md')))) {
|
|
throw new Error(`BMad Automator payload missing installed skill: ${STORY_SKILL}`);
|
|
}
|
|
if (!(await fs.pathExists(path.join(reviewTarget, 'SKILL.md')))) {
|
|
throw new Error(`BMad Automator payload missing installed skill: ${REVIEW_SKILL}`);
|
|
}
|
|
|
|
const repoRoot = await findAutomatorRepoRoot(sourcePath);
|
|
const sourceRoot = path.join(repoRoot, 'source');
|
|
const runtimeFiles = {
|
|
pyproject: path.join(sourceRoot, 'pyproject.toml'),
|
|
readme: path.join(sourceRoot, 'README.md'),
|
|
license: path.join(sourceRoot, 'LICENSE'),
|
|
scripts: path.join(sourceRoot, 'scripts'),
|
|
src: path.join(sourceRoot, 'src'),
|
|
};
|
|
|
|
for (const [label, requiredPath] of Object.entries(runtimeFiles)) {
|
|
if (!(await fs.pathExists(requiredPath))) {
|
|
throw new Error(`BMad Automator runtime ${label} missing: ${requiredPath}`);
|
|
}
|
|
}
|
|
|
|
await copyTracked(runtimeFiles.pyproject, path.join(storyTarget, 'pyproject.toml'), fileTrackingCallback);
|
|
await copyTracked(runtimeFiles.readme, path.join(storyTarget, 'README.md'), fileTrackingCallback);
|
|
await copyTracked(runtimeFiles.license, path.join(storyTarget, 'LICENSE'), fileTrackingCallback);
|
|
await copyTracked(runtimeFiles.scripts, path.join(storyTarget, 'scripts'), fileTrackingCallback);
|
|
await copyTracked(runtimeFiles.src, path.join(storyTarget, 'src'), fileTrackingCallback);
|
|
await fs.chmod(path.join(storyTarget, 'scripts', 'story-automator'), 0o755);
|
|
|
|
return true;
|
|
}
|
|
|
|
async function findAutomatorRepoRoot(sourcePath) {
|
|
let current = path.resolve(sourcePath);
|
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
if (
|
|
(await fs.pathExists(path.join(current, 'source', 'scripts', 'story-automator'))) &&
|
|
(await fs.pathExists(path.join(current, 'payload', '.claude', 'skills', STORY_SKILL, 'SKILL.md')))
|
|
) {
|
|
return current;
|
|
}
|
|
const parent = path.dirname(current);
|
|
if (parent === current) break;
|
|
current = parent;
|
|
}
|
|
throw new Error(`BMad Automator runtime source not found above: ${sourcePath}`);
|
|
}
|
|
|
|
async function copyTracked(source, target, fileTrackingCallback) {
|
|
await fs.remove(target);
|
|
await fs.copy(source, target, { overwrite: true });
|
|
await trackRecursive(target, fileTrackingCallback);
|
|
}
|
|
|
|
async function trackRecursive(target, fileTrackingCallback) {
|
|
if (!fileTrackingCallback) return;
|
|
const stat = await fs.stat(target);
|
|
if (stat.isFile()) {
|
|
fileTrackingCallback(target);
|
|
return;
|
|
}
|
|
if (!stat.isDirectory()) return;
|
|
const entries = await fs.readdir(target, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
await trackRecursive(path.join(target, entry.name), fileTrackingCallback);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
installBmaRuntimePackage,
|
|
};
|