mime-todo-cli/src/commands/InitCommand.js
2026-03-15 05:11:59 +01:00

131 lines
5.2 KiB
JavaScript

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;
}
}