import { CLICommand } from "../cli/CLICommand.js"; import { parseTodoFile } from "../file.js"; import { BugzillaClient } from "../bugzilla/client.js"; import { loadConfig } from "../bugzilla/config.js"; export class InitCommand extends CLICommand { name = "init"; help = "Initialize Bugzilla products and components"; description = "Check and optionally create Bugzilla products/components from TODO tracker config"; addArguments(yargs) { return yargs .option("dry-run", { type: "boolean", default: false, description: "Show what would be created without creating", }) .option("confirm", { type: "boolean", default: false, description: "Actually create missing products/components (required for writes)", }) .option("assignee", { type: "string", description: "Default assignee email for new components", }); } async execute(args) { const todo = await parseTodoFile(); if (!todo.bugzilla) { console.error("No application/bugzilla part found in TODO file"); return 1; } const config = loadConfig(); const client = new BugzillaClient(config); const dryRun = args.dryRun; const confirm = args.confirm; const assignee = args.assignee; // Collect unique products and their components from the mappings const productMap = new Map(); for (const m of todo.bugzilla.mappings) { if (!productMap.has(m.product)) productMap.set(m.product, new Set()); productMap.get(m.product).add(m.component); } // Check what exists on the server const states = []; const existingProducts = await client.getAccessibleProducts(); const existingByName = new Map(existingProducts.map(p => [p.name, p])); for (const [productName, componentNames] of productMap) { const existing = existingByName.get(productName); const existingComponents = new Set(existing?.components?.map(c => c.name) ?? []); states.push({ name: productName, exists: !!existing, components: [...componentNames].map(name => ({ name, exists: existingComponents.has(name), })), }); } // Report let allOk = true; for (const product of states) { const pStatus = product.exists ? "ok" : "MISSING"; console.log(`Product: ${product.name} [${pStatus}]`); for (const comp of product.components) { const cStatus = comp.exists ? "ok" : "MISSING"; console.log(` Component: ${comp.name} [${cStatus}]`); if (!comp.exists) allOk = false; } if (!product.exists) allOk = false; } if (allOk) { console.log("\nAll products and components exist. Nothing to do."); return 0; } if (dryRun) { console.log("\n[dry-run] Would create the MISSING items above."); return 0; } if (!confirm) { console.log("\nMissing items found. Run with --confirm to create them."); return 1; } // Need assignee for component creation const needsComponents = states.some(p => p.components.some(c => !c.exists)); if (needsComponents && !assignee) { console.error("\n--assignee is required when creating components"); return 1; } // Create missing products and components let created = 0; for (const product of states) { if (!product.exists) { try { const id = await client.createProduct({ name: product.name, description: product.name, version: "unspecified", }); console.log(`Created product #${id}: ${product.name}`); created++; } catch (err) { console.error(`Error creating product "${product.name}": ${err.message}`); continue; } } for (const comp of product.components) { if (!comp.exists) { try { const id = await client.createComponent({ product: product.name, name: comp.name, description: comp.name, default_assignee: assignee, }); console.log(`Created component #${id}: ${comp.name} (in ${product.name})`); created++; } catch (err) { console.error(`Error creating component "${comp.name}": ${err.message}`); } } } } console.log(`\nInit complete: ${created} items created`); return 0; } }