fix: restore installer edge-case handling and path normalization
This commit is contained in:
parent
c108ae4314
commit
dde139a560
|
|
@ -20,6 +20,7 @@ const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder');
|
|||
const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator');
|
||||
const { WorkflowCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/task-tool-command-generator');
|
||||
const { ConfigDrivenIdeSetup } = require('../tools/cli/installers/lib/ide/_config-driven');
|
||||
const { IdeManager } = require('../tools/cli/installers/lib/ide/manager');
|
||||
const { CodexSetup } = require('../tools/cli/installers/lib/ide/codex');
|
||||
const { ModuleManager } = require('../tools/cli/installers/lib/modules/manager');
|
||||
|
|
@ -744,6 +745,117 @@ internal: true
|
|||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 19: Empty Target Artifact Filter Guard
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 19: Empty Artifact Target Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-empty-target-'));
|
||||
const projectDir = path.join(tmpRoot, 'project');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
await fs.ensureDir(projectDir);
|
||||
await fs.ensureDir(bmadDir);
|
||||
|
||||
const setup = new ConfigDrivenIdeSetup('test', {
|
||||
name: 'Test IDE',
|
||||
preferred: false,
|
||||
installer: { target_dir: '.test', template_type: 'default' },
|
||||
});
|
||||
|
||||
const result = await setup.installToTarget(
|
||||
projectDir,
|
||||
bmadDir,
|
||||
{ target_dir: '.test', template_type: 'default', artifact_types: [] },
|
||||
{ silent: true },
|
||||
);
|
||||
|
||||
assert(
|
||||
result.success &&
|
||||
result.results.agents === 0 &&
|
||||
result.results.workflows === 0 &&
|
||||
result.results.tasks === 0 &&
|
||||
result.results.tools === 0,
|
||||
'Installer short-circuits explicit empty artifact target',
|
||||
);
|
||||
|
||||
assert(!(await fs.pathExists(path.join(projectDir, '.test'))), 'Installer does not create output directory for empty artifact target');
|
||||
|
||||
await fs.remove(tmpRoot);
|
||||
} catch (error) {
|
||||
assert(false, 'Empty artifact target guard runs', error.message);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 20: Split Template Extension Override Guard
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 20: Split Template Extension Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const setup = new ConfigDrivenIdeSetup('test', {
|
||||
name: 'Test IDE',
|
||||
preferred: false,
|
||||
installer: { target_dir: '.test', template_type: 'default' },
|
||||
});
|
||||
|
||||
setup.loadSplitTemplates = async () => 'template-content';
|
||||
const loaded = await setup.loadTemplateWithMetadata('default', 'workflow', {
|
||||
header_template: 'header.md',
|
||||
extension: 'toml',
|
||||
});
|
||||
|
||||
assert(loaded.extension === '.toml', 'Split template loader preserves configured extension override');
|
||||
} catch (error) {
|
||||
assert(false, 'Split template extension guard runs', error.message);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 21: Workflow Path Normalization Guard
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 21: Workflow Path Normalization Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const generator = new WorkflowCommandGenerator();
|
||||
generator.loadWorkflowManifest = async () => [
|
||||
{
|
||||
name: 'create-story',
|
||||
description: 'Create Story',
|
||||
module: 'bmm',
|
||||
path: String.raw`C:\repo\src\bmm\workflows\4-implementation\create-story\workflow.md`,
|
||||
},
|
||||
{
|
||||
name: 'validate-workflow',
|
||||
description: 'Validate Workflow',
|
||||
module: 'core',
|
||||
path: String.raw`C:\repo\_bmad\core\workflows\validate-workflow\workflow.md`,
|
||||
},
|
||||
];
|
||||
generator.generateCommandContent = async () => 'content';
|
||||
|
||||
const { artifacts } = await generator.collectWorkflowArtifacts('/tmp');
|
||||
const createStory = artifacts.find((artifact) => artifact.name === 'create-story');
|
||||
const validateWorkflow = artifacts.find((artifact) => artifact.name === 'validate-workflow');
|
||||
|
||||
assert(
|
||||
createStory?.workflowPath === 'bmm/workflows/4-implementation/create-story/workflow.md',
|
||||
'Workflow artifact path normalizes Windows src path to module-relative path',
|
||||
createStory?.workflowPath,
|
||||
);
|
||||
assert(
|
||||
validateWorkflow?.workflowPath === 'core/workflows/validate-workflow/workflow.md',
|
||||
'Workflow artifact path strips _bmad prefix after separator normalization',
|
||||
validateWorkflow?.workflowPath,
|
||||
);
|
||||
} catch (error) {
|
||||
assert(false, 'Workflow path normalization guard runs', error.message);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Summary
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
*/
|
||||
async installToTarget(projectDir, bmadDir, config, options) {
|
||||
const { target_dir, template_type, artifact_types } = config;
|
||||
|
||||
// Skip explicitly empty targets to avoid creating empty command directories.
|
||||
if (Array.isArray(artifact_types) && artifact_types.length === 0) {
|
||||
return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0 } };
|
||||
}
|
||||
|
||||
const targetPath = path.join(projectDir, target_dir);
|
||||
await this.ensureDir(targetPath);
|
||||
|
||||
|
|
@ -250,12 +256,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
// Check for separate header/body templates
|
||||
if (header_template || body_template) {
|
||||
const template = await this.loadSplitTemplates(templateType, artifactType, header_template, body_template);
|
||||
return { template, extension: '.md' };
|
||||
return { template, extension: this.normalizeExtension(config.extension) };
|
||||
}
|
||||
|
||||
// Load combined template with extension detection
|
||||
const templateBaseName = artifactType ? `${templateType}-${artifactType}` : templateType;
|
||||
for (const extension of supportedExtensions) {
|
||||
const templateName = `${templateType}-${artifactType}${extension}`;
|
||||
const templateName = `${templateBaseName}${extension}`;
|
||||
const templatePath = path.join(__dirname, 'templates', 'combined', templateName);
|
||||
if (await fs.pathExists(templatePath)) {
|
||||
return {
|
||||
|
|
@ -329,6 +336,19 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
return `${header}\n${body}`;
|
||||
}
|
||||
|
||||
normalizeExtension(extension) {
|
||||
if (!extension) {
|
||||
return '.md';
|
||||
}
|
||||
|
||||
const trimmed = String(extension).trim();
|
||||
if (trimmed === '') {
|
||||
return '.md';
|
||||
}
|
||||
|
||||
return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default minimal template
|
||||
* @param {string} artifactType - Artifact type
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ class WorkflowCommandGenerator {
|
|||
for (const workflow of allWorkflows) {
|
||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
||||
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
|
||||
let workflowRelPath = workflow.path;
|
||||
let workflowRelPath = workflow.path || '';
|
||||
workflowRelPath = workflowRelPath.replaceAll('\\', '/');
|
||||
// Remove _bmad/ prefix if present to get relative path from project root
|
||||
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
|
||||
if (workflowRelPath.includes('_bmad/')) {
|
||||
|
|
@ -76,6 +77,11 @@ class WorkflowCommandGenerator {
|
|||
if (parts.length > 1) {
|
||||
workflowRelPath = parts.slice(1).join('/');
|
||||
}
|
||||
} else if (workflowRelPath.includes('/src/')) {
|
||||
const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/);
|
||||
if (match) {
|
||||
workflowRelPath = `${match[1]}/${match[2]}`;
|
||||
}
|
||||
}
|
||||
artifacts.push({
|
||||
type: 'workflow-command',
|
||||
|
|
|
|||
Loading…
Reference in New Issue