UNPKG

@log4brains/cli

Version:

Log4brains architecture knowledge base CLI

236 lines (195 loc) 7.49 kB
import commander from 'commander'; import terminalLink from 'terminal-link'; import { Log4brainsError, Log4brains } from '@log4brains/core'; import { FailureExit } from '@log4brains/cli-common'; import path from 'path'; import fs, { promises } from 'fs'; import execa from 'execa'; class ListCommand { constructor({ l4bInstance, appConsole }) { this.l4bInstance = l4bInstance; this.console = appConsole; } async execute(opts) { const filters = {}; if (opts.statuses) { filters.statuses = opts.statuses.split(","); } const adrs = await this.l4bInstance.searchAdrs(filters); const table = this.console.createTable({ head: ["Slug", "Status", "Package", "Title"] }); adrs.forEach(adr => { table.push([adr.slug, adr.status.toUpperCase(), adr.package || "", adr.title || "Untitled"]); }); this.console.printTable(table, opts.raw); } } async function previewAdr(slug) { var _subprocess$stdout, _subprocess$stderr; const subprocess = execa("log4brains", ["preview", slug], { stdio: "inherit" }); (_subprocess$stdout = subprocess.stdout) == null ? void 0 : _subprocess$stdout.pipe(process.stdout); (_subprocess$stderr = subprocess.stderr) == null ? void 0 : _subprocess$stderr.pipe(process.stderr); await subprocess; } /* eslint-disable no-await-in-loop */ class NewCommand { constructor({ l4bInstance, appConsole }) { this.l4bInstance = l4bInstance; this.console = appConsole; } detectCurrentPackageFromCwd() { const { packages } = this.l4bInstance.config.project; if (!packages) { return undefined; } const cwd = path.resolve("."); const match = packages.filter(pkg => cwd.includes(pkg.path)).sort((a, b) => a.path.length - b.path.length).pop(); // returns the most precise path (ie. longest) return match == null ? void 0 : match.name; } // eslint-disable-next-line sonarjs/cognitive-complexity async execute(opts, titleArg) { const { packages } = this.l4bInstance.config.project; let pkg = opts.package; if (!opts.quiet && !pkg && packages && packages.length > 0) { const currentPackage = this.detectCurrentPackageFromCwd(); const packageChoices = [{ name: `Global`, value: "" }, ...packages.map(p => ({ name: `Package: ${p.name}`, value: p.name }))]; pkg = (await this.console.askListQuestion("For which package do you want to create this new ADR?", packageChoices, currentPackage)) || undefined; } if (opts.quiet && !titleArg) { throw new Log4brainsError("<title> is required when using --quiet"); } let title; do { title = titleArg || (await this.console.askInputQuestion("Title of the solved problem and its solution?")); if (!title.trim()) { this.console.warn("Please enter a title"); } } while (!title.trim()); // const slug = await this.console.askInputQuestion( // "We pre-generated a slug to identify this ADR. Press [ENTER] or enter another one.", // await this.l4bInstance.generateAdrSlug(title, pkg) // ); const slug = await this.l4bInstance.generateAdrSlug(title, pkg); const adrDto = await this.l4bInstance.createAdrFromTemplate(slug, title); // --from option (used by init-log4brains to create the starter ADRs) // Since this is a private use case, we don't include it in CORE for now if (opts.from) { if (!fs.existsSync(opts.from)) { throw new Log4brainsError("The given file does not exist", opts.from); } // TODO: use streams await promises.writeFile(adrDto.file.absolutePath, await promises.readFile(opts.from, "utf-8"), "utf-8"); } if (opts.quiet) { this.console.println(adrDto.slug); process.exit(0); } const activeAdrs = await this.l4bInstance.searchAdrs({ statuses: ["accepted"] }); if (activeAdrs.length > 0) { const supersedeChoices = [{ name: "No", value: "" }, ...activeAdrs.map(a => ({ name: a.title || "Untitled", value: a.slug }))]; const supersededSlug = await this.console.askListQuestion("Does this ADR supersede a previous one?", supersedeChoices, ""); if (supersededSlug !== "") { await this.l4bInstance.supersedeAdr(supersededSlug, slug); this.console.debug(`${supersededSlug} was marked as superseded by ${slug}`); } } this.console.println(); this.console.success(`New ADR created: ${adrDto.file.relativePath}`); this.console.println(); const actionChoices = [{ name: "Edit and preview", value: "edit-and-preview" }, { name: "Edit", value: "edit" }, { name: "Later", value: "close" }]; const action = await this.console.askListQuestion("How would you like to edit it?", actionChoices, "edit-and-preview"); if (action === "edit-and-preview" || action === "edit") { await this.l4bInstance.openAdrInEditor(slug, () => { this.console.warn("We were not able to detect your preferred editor :("); this.console.warn("You can define it by setting your $VISUAL or $EDITOR environment variable in ~/.zshenv or ~/.bashrc"); }); if (action === "edit-and-preview") { await previewAdr(slug); } } process.exit(0); } } const templateExampleUrl = "https://raw.githubusercontent.com/thomvaill/log4brains/stable/packages/init/assets/template.md"; let l4bInstance; function getL4bInstance() { if (!l4bInstance) { l4bInstance = Log4brains.createFromCwd(process.env.LOG4BRAINS_CWD || "."); } return l4bInstance; } function execWithErrorHandler(promise, appConsole) { promise.catch(err => { if (err instanceof Log4brainsError && err.name === "The template.md file does not exist") { appConsole.error(err); appConsole.printlnErr(`You can use this ${terminalLink("template", templateExampleUrl)} as an example`); throw new FailureExit(); } }); return promise; } function createCli({ appConsole }) { const program = new commander.Command(); const adr = program.command("adr").description("Group of commands to manage your ADRs..."); adr.command("new [title]").description("Create an ADR", { title: "The title of the ADR. Required if --quiet is passed" }).option("-q, --quiet", "Disable interactive mode", false).option("-p, --package <package>", "To create the ADR for a specific package").option("--from <file>", "Copy <file> contents into the ADR instead of using the default template").action((title, opts) => { const cmd = new NewCommand({ l4bInstance: getL4bInstance(), appConsole }); return execWithErrorHandler(cmd.execute(opts, title), appConsole); }); // adr // .command("quick") // .description("Create a one-sentence ADR (Y-Statement)") // .action( // (): Promise<void> => { // // TODO // } // ); adr.command("list").option("-s, --statuses <statuses>", "Filter on the given statuses, comma-separated") // TODO: list available statuses .option("-r, --raw", "Use a raw format instead of a table", false).description("List ADRs").action(opts => { const cmd = new ListCommand({ l4bInstance: getL4bInstance(), appConsole }); return execWithErrorHandler(cmd.execute(opts), appConsole); }); return program; } export { createCli }; //# sourceMappingURL=index.module.js.map