mime-todo-cli/lib/commands/InitCommand.ts
Tiara Rodney 932d4ad420
init
2026-03-15 03:02:41 +01:00

157 lines
4.7 KiB
TypeScript

import type { Argv, ArgumentsCamelCase } from "yargs"
import { CLICommand } from "../cli/CLICommand"
import { parseTodoFile } from "../file"
import { BugzillaClient } from "../bugzilla/client"
import { loadConfig } from "../bugzilla/config"
interface ProductState {
name: string
exists: boolean
components: {
name: string
exists: boolean
}[]
}
export class InitCommand extends CLICommand {
readonly name = "init"
readonly help = "Initialize Bugzilla products and components"
readonly description = "Check and optionally create Bugzilla products/components from TODO tracker config"
addArguments(yargs: Argv): Argv {
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: ArgumentsCamelCase): Promise<number> {
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 as boolean
const confirm = args.confirm as boolean
const assignee = args.assignee as string | undefined
// Collect unique products and their components from the mappings
const productMap = new Map<string, Set<string>>()
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: ProductState[] = []
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: any) {
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: any) {
console.error(`Error creating component "${comp.name}": ${err.message}`)
}
}
}
}
console.log(`\nInit complete: ${created} items created`)
return 0
}
}