refactor(installer): trim shard-doc prototype path to lean behavior
This commit is contained in:
parent
d5bb2398bd
commit
b08495c6e4
|
|
@ -27,7 +27,6 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
super(platformCode, platformConfig.name, platformConfig.preferred);
|
||||
this.platformConfig = platformConfig;
|
||||
this.installerConfig = platformConfig.installer || null;
|
||||
this._manifestCache = new Map();
|
||||
|
||||
// Set configDir from target_dir so base-class detect() works
|
||||
if (this.installerConfig?.target_dir) {
|
||||
|
|
@ -117,7 +116,6 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
*/
|
||||
async installToTarget(projectDir, bmadDir, config, options) {
|
||||
const { target_dir, template_type, artifact_types } = config;
|
||||
this._manifestCache = new Map();
|
||||
|
||||
// Skip targets with explicitly empty artifact_types array
|
||||
// This prevents creating empty directories when no artifacts will be written
|
||||
|
|
@ -506,13 +504,13 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|||
const sourceRef = this.resolveArtifactSourceRef(artifact, bmadDir);
|
||||
const duplicatePrototypeIds = await this.getPrototypeSkillIdsForArtifact(artifact, bmadDir, sourceRef);
|
||||
for (const prototypeId of duplicatePrototypeIds) {
|
||||
if (!this.isSafeSkillFolderName(prototypeId)) continue;
|
||||
if (prototypeId === skillName) continue;
|
||||
if (typeof prototypeId !== 'string' || !prototypeId.trim()) continue;
|
||||
const prototypeDir = path.join(targetPath, prototypeId);
|
||||
await this.ensureDir(prototypeDir);
|
||||
const sourceSkillContent = await this.readPrototypeSourceSkillContent(sourceRef, prototypeId);
|
||||
const prototypeContent = sourceSkillContent ?? this.transformToSkillFormat(content, prototypeId);
|
||||
await this.writeFile(path.join(prototypeDir, 'SKILL.md'), prototypeContent);
|
||||
if (!sourceSkillContent) continue;
|
||||
await this.writeFile(path.join(prototypeDir, 'SKILL.md'), sourceSkillContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,11 +524,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|||
const sourceRef = sourceRefOverride ?? this.resolveArtifactSourceRef(artifact, bmadDir);
|
||||
if (!sourceRef) return [];
|
||||
|
||||
let manifest = this._manifestCache.get(sourceRef.dirPath);
|
||||
if (manifest === undefined) {
|
||||
manifest = await loadSkillManifest(sourceRef.dirPath);
|
||||
this._manifestCache.set(sourceRef.dirPath, manifest);
|
||||
}
|
||||
const manifest = await loadSkillManifest(sourceRef.dirPath);
|
||||
return getPrototypeIds(manifest, sourceRef.filename);
|
||||
}
|
||||
|
||||
|
|
@ -542,40 +536,12 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|||
* @returns {Promise<string|null>} Source SKILL.md content or null
|
||||
*/
|
||||
async readPrototypeSourceSkillContent(sourceRef, prototypeId) {
|
||||
if (!sourceRef || !this.isSafeSkillFolderName(prototypeId)) return null;
|
||||
|
||||
const resolvedSourceDir = path.resolve(sourceRef.dirPath);
|
||||
const sourceSkillPath = path.resolve(resolvedSourceDir, prototypeId, 'SKILL.md');
|
||||
const relativeToSourceDir = path.relative(resolvedSourceDir, sourceSkillPath);
|
||||
|
||||
if (
|
||||
relativeToSourceDir === '..' ||
|
||||
relativeToSourceDir.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relativeToSourceDir)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!sourceRef || typeof prototypeId !== 'string' || !prototypeId.trim()) return null;
|
||||
const sourceSkillPath = path.join(sourceRef.dirPath, prototypeId, 'SKILL.md');
|
||||
if (!(await fs.pathExists(sourceSkillPath))) return null;
|
||||
return fs.readFile(sourceSkillPath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate skill folder names used for source lookup.
|
||||
* @param {string} skillId - Candidate skill ID
|
||||
* @returns {boolean} True if safe to use as a folder name segment
|
||||
*/
|
||||
isSafeSkillFolderName(skillId) {
|
||||
return (
|
||||
typeof skillId === 'string' &&
|
||||
skillId.length > 0 &&
|
||||
skillId !== '.' &&
|
||||
skillId !== '..' &&
|
||||
!skillId.includes('/') &&
|
||||
!skillId.includes('\\')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the artifact source directory + filename within installed bmad tree.
|
||||
* @param {Object} artifact - Artifact metadata
|
||||
|
|
@ -615,15 +581,8 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|||
const filename = path.basename(normalized);
|
||||
if (!filename || filename === '.' || filename === '..') return null;
|
||||
|
||||
const resolvedBmadDir = path.resolve(bmadDir);
|
||||
const relativeDir = path.dirname(normalized);
|
||||
const dirPath = relativeDir === '.' ? resolvedBmadDir : path.resolve(resolvedBmadDir, relativeDir);
|
||||
const pathFromBmadRoot = path.relative(resolvedBmadDir, dirPath);
|
||||
|
||||
if (pathFromBmadRoot === '..' || pathFromBmadRoot.startsWith(`..${path.sep}`) || path.isAbsolute(pathFromBmadRoot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dirPath = relativeDir === '.' ? bmadDir : path.join(bmadDir, relativeDir);
|
||||
return { dirPath, filename };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ function parseUnderscoreName(filename) {
|
|||
|
||||
/**
|
||||
* Resolve the skill name for an artifact.
|
||||
* Prefers canonicalId from a skill manifest sidecar when available,
|
||||
* Prefers canonicalId from a bmad-skill-manifest.yaml sidecar when available,
|
||||
* falling back to the path-derived name from toDashPath().
|
||||
*
|
||||
* @param {Object} artifact - Artifact object (must have relativePath; may have canonicalId)
|
||||
|
|
|
|||
|
|
@ -2,34 +2,28 @@ const path = require('node:path');
|
|||
const fs = require('fs-extra');
|
||||
const yaml = require('yaml');
|
||||
|
||||
const SKILL_MANIFEST_FILENAMES = ['skill-manifest.yaml', 'bmad-skill-manifest.yaml', 'manifest.yaml'];
|
||||
|
||||
/**
|
||||
* Load skill manifest from a directory.
|
||||
* Single-entry manifests (canonicalId at top level) apply to all files in the directory.
|
||||
* Multi-entry manifests are keyed by source filename.
|
||||
* @param {string} dirPath - Directory to check for supported manifest filenames
|
||||
* @param {string} dirPath - Directory to check for bmad-skill-manifest.yaml
|
||||
* @returns {Object|null} Parsed manifest or null
|
||||
*/
|
||||
async function loadSkillManifest(dirPath) {
|
||||
for (const manifestFilename of SKILL_MANIFEST_FILENAMES) {
|
||||
const manifestPath = path.join(dirPath, manifestFilename);
|
||||
const manifestPath = path.join(dirPath, 'bmad-skill-manifest.yaml');
|
||||
try {
|
||||
if (!(await fs.pathExists(manifestPath))) continue;
|
||||
if (!(await fs.pathExists(manifestPath))) return null;
|
||||
const content = await fs.readFile(manifestPath, 'utf8');
|
||||
const parsed = yaml.parse(content);
|
||||
if (!parsed || typeof parsed !== 'object') return null;
|
||||
if (parsed.canonicalId) return { __single: parsed };
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Failed to parse ${manifestFilename} in ${dirPath}: ${error.message}`);
|
||||
console.warn(`Warning: Failed to parse bmad-skill-manifest.yaml in ${dirPath}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonicalId for a specific file from a loaded skill manifest.
|
||||
* @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
|
||||
|
|
|
|||
Loading…
Reference in New Issue