feat: Add workflow vendoring to web bundler
The web bundler now performs workflow vendoring before bundling agents, similar to the module installer. This ensures that workflows referenced via workflow-install attributes are copied from their source locations to their destination locations before the bundler attempts to resolve and bundle them. Changes: - Added vendorCrossModuleWorkflows() method to WebBundler class - Added updateWorkflowConfigSource() helper method - Integrated vendoring into bundleAll(), bundleModule(), and bundleAgent() - Workflows are vendored before agent discovery and bundling - Config_source is updated in vendored workflows to reference target module This fixes missing dependency warnings for BMGD agents that vendor BMM workflows for Phase 4 (Production) workflows.
This commit is contained in:
parent
f84e18760f
commit
281eac3373
|
|
@ -51,8 +51,13 @@ class WebBundler {
|
|||
console.log(chalk.cyan.bold('═══════════════════════════════════════════════\n'));
|
||||
|
||||
try {
|
||||
// Pre-discover all modules to generate complete manifests
|
||||
// Vendor cross-module workflows FIRST
|
||||
const modules = await this.discoverModules();
|
||||
for (const module of modules) {
|
||||
await this.vendorCrossModuleWorkflows(module);
|
||||
}
|
||||
|
||||
// Pre-discover all modules to generate complete manifests
|
||||
for (const module of modules) {
|
||||
await this.preDiscoverModule(module);
|
||||
}
|
||||
|
|
@ -92,6 +97,9 @@ class WebBundler {
|
|||
teams: [],
|
||||
};
|
||||
|
||||
// Vendor cross-module workflows first (if not already done by bundleAll)
|
||||
await this.vendorCrossModuleWorkflows(moduleName);
|
||||
|
||||
// Pre-discover all agents and teams for manifest generation
|
||||
await this.preDiscoverModule(moduleName);
|
||||
|
||||
|
|
@ -134,6 +142,9 @@ class WebBundler {
|
|||
|
||||
console.log(chalk.dim(` → Processing: ${agentName}`));
|
||||
|
||||
// Vendor cross-module workflows first (if not already done)
|
||||
await this.vendorCrossModuleWorkflows(moduleName);
|
||||
|
||||
const agentPath = path.join(this.modulesPath, moduleName, 'agents', agentFile);
|
||||
|
||||
// Check if agent file exists
|
||||
|
|
@ -433,6 +444,97 @@ class WebBundler {
|
|||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor cross-module workflows for a module
|
||||
* Scans source agent YAML files for workflow-install attributes and copies workflows
|
||||
*/
|
||||
async vendorCrossModuleWorkflows(moduleName) {
|
||||
const modulePath = path.join(this.modulesPath, moduleName);
|
||||
const agentsPath = path.join(modulePath, 'agents');
|
||||
|
||||
if (!(await fs.pathExists(agentsPath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all agent YAML files
|
||||
const files = await fs.readdir(agentsPath);
|
||||
const yamlFiles = files.filter((f) => f.endsWith('.agent.yaml'));
|
||||
|
||||
for (const agentFile of yamlFiles) {
|
||||
const agentPath = path.join(agentsPath, agentFile);
|
||||
const agentYaml = yaml.load(await fs.readFile(agentPath, 'utf8'));
|
||||
|
||||
const menuItems = agentYaml?.agent?.menu || [];
|
||||
const workflowInstallItems = menuItems.filter((item) => item['workflow-install']);
|
||||
|
||||
for (const item of workflowInstallItems) {
|
||||
const sourceWorkflowPath = item.workflow;
|
||||
const installWorkflowPath = item['workflow-install'];
|
||||
|
||||
if (!sourceWorkflowPath || !installWorkflowPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse paths to extract module and workflow location
|
||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||
|
||||
if (!sourceMatch || !installMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceModule = sourceMatch[1];
|
||||
const sourceWorkflowRelPath = sourceMatch[2];
|
||||
const installModule = installMatch[1];
|
||||
const installWorkflowRelPath = installMatch[2];
|
||||
|
||||
// Build actual filesystem paths
|
||||
const actualSourceWorkflowPath = path.join(this.modulesPath, sourceModule, 'workflows', sourceWorkflowRelPath);
|
||||
const actualDestWorkflowPath = path.join(this.modulesPath, installModule, 'workflows', installWorkflowRelPath);
|
||||
|
||||
// Check if source workflow exists
|
||||
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
||||
console.log(chalk.yellow(` ⚠ Source workflow not found for vendoring: ${sourceWorkflowPath}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if destination already exists (skip if already vendored)
|
||||
if (await fs.pathExists(actualDestWorkflowPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get workflow directory (workflow.yaml is in a directory with other files)
|
||||
const sourceWorkflowDir = path.dirname(actualSourceWorkflowPath);
|
||||
const destWorkflowDir = path.dirname(actualDestWorkflowPath);
|
||||
|
||||
// Copy entire workflow directory
|
||||
await fs.copy(sourceWorkflowDir, destWorkflowDir, { overwrite: false });
|
||||
|
||||
// Update config_source in the vendored workflow.yaml
|
||||
const workflowYamlPath = actualDestWorkflowPath;
|
||||
if (await fs.pathExists(workflowYamlPath)) {
|
||||
await this.updateWorkflowConfigSource(workflowYamlPath, installModule);
|
||||
}
|
||||
|
||||
console.log(chalk.dim(` → Vendored workflow: ${sourceWorkflowRelPath} → ${installModule}/workflows/${installWorkflowRelPath}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update config_source in a vendored workflow YAML file
|
||||
*/
|
||||
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
||||
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
||||
|
||||
// Replace config_source with new module reference
|
||||
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/bmad\/[^/]+\/config\.yaml["']?/g;
|
||||
const newConfigSource = `config_source: "{project-root}/bmad/${newModuleName}/config.yaml"`;
|
||||
|
||||
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
|
||||
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-discover all agents and teams in a module for manifest generation
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue