import "./lib/hijack-clack-logger" import { log } from "@clack/prompts" import { red } from "ansis" import { createRequire } from "node:module" import { z } from "zod" import { acceptCommand } from "./commands/accept" import { accountsCommand } from "./commands/accounts" import { automationCommand } from "./commands/automation" import { dataCommand } from "./commands/data" import { debugCommand } from "./commands/debug" import { deployCommand } from "./commands/deploy" import { deploymentCommand } from "./commands/deployment" import { initCommand } from "./commands/init" import { loginCommand } from "./commands/login" import { logoutCommand } from "./commands/logout" import { meCommand } from "./commands/me" import { orgInviteCommand } from "./commands/org-invite" import { orgUpgradeCommand } from "./commands/org-upgrade" import { organizationCommand } from "./commands/org" import { projectCommand } from "./commands/project" import { queryCommand } from "./commands/query" import { searchCommand } from "./commands/search" import { defineCommand, getSubcommands, hasOption, type Command, } from "./lib/command" import { globalOptions } from "./lib/global-options" import { clearSpinner } from "./lib/spinner" import { showUsage } from "./usage" import { formatErrorMessage, writeJsonError } from "./utils" const CLI_VERSION = z .object({ version: z.string() }) .parse(createRequire(import.meta.url)("../../package.json")).version /** Root CLI command definition. */ export const main = defineCommand({ name: "automate", version: CLI_VERSION, description: "Automate.ax CLI", options: globalOptions, optionDescriptions: { json: "Output JSON" }, groups: [ { heading: "ACCOUNT", commands: [loginCommand, logoutCommand, meCommand], }, { heading: "RESOURCES", commands: [ accountsCommand, organizationCommand, projectCommand, orgInviteCommand, orgUpgradeCommand, ], }, { heading: "AUTOMATIONS", commands: [ automationCommand, initCommand, deployCommand, deploymentCommand, ], }, { heading: "EXECUTION DATA", commands: [dataCommand, queryCommand, searchCommand], }, { heading: "OTHER", commands: [acceptCommand, debugCommand], }, ], }) interface ResolvedCommand { args: string[] command: Command path: Command[] } /** * Runs the Automate.ax CLI. * * @param rawArgs - Arguments after the executable name. */ export async function runMain(rawArgs = process.argv.slice(2)) { try { const resolved = resolveCommand(main, rawArgs) if (hasOption(rawArgs, "--version", "-v")) { console.log(CLI_VERSION) return 0 } if (hasOption(rawArgs, "--help", "-h")) { showUsage(resolved.command, resolved.path) return 0 } if (!resolved.command.execute) { if (!hasOption(rawArgs, "--json")) { showUsage(resolved.command, resolved.path) } throw new Error("No command specified.") } await resolved.command.execute(resolved.args) return 0 } catch (error) { clearSpinner() if (hasOption(rawArgs, "--json")) { writeJsonError(error) return 1 } log.message(red(`${formatErrorMessage(error).replace(/^✖ ?/, "")}\n`), { output: process.stderr, symbol: red("✖"), }) return 1 } } /** * Resolves the deepest selected command and leaves its arguments intact. * * @param command - Current command. * @param rawArgs - Arguments not consumed as command names. * @param path - Ancestor commands. * @throws {Error} When a parent receives an unknown command name. */ function resolveCommand( command: Command, rawArgs: readonly string[], path: readonly Command[] = [], ): ResolvedCommand { const subcommands = getSubcommands(command) if (subcommands.length === 0) { return { args: [...rawArgs], command, path: [...path] } } const commandIndex = findCommandIndex(command, rawArgs) const commandName = commandIndex === undefined ? undefined : rawArgs[commandIndex] const subcommand = subcommands.find( ({ aliases, name }) => name === commandName || aliases?.includes(commandName ?? ""), ) if (!subcommand) { if (commandName === undefined && hasOption(rawArgs, "--help", "-h")) { return { args: [...rawArgs], command, path: [...path] } } if (command.defaultSubcommand && commandName === undefined) { return resolveCommand(command.defaultSubcommand, rawArgs, [ ...path, command, ]) } if (commandName === undefined) { return { args: [...rawArgs], command, path: [...path] } } throw new Error(`Unknown command: ${commandName}`) } return resolveCommand( subcommand, rawArgs.filter((_, index) => index !== commandIndex), [...path, command], ) } /** * Finds the first positional token after the current command's options. * * @param command - Current command. * @param rawArgs - Remaining arguments. */ function findCommandIndex(command: Command, rawArgs: readonly string[]) { const options = { ...command.defaultSubcommand?.options, ...command.options, } for (let index = 0; index < rawArgs.length; index++) { const arg = rawArgs[index]! if (arg === "--") return index + 1 if (!arg.startsWith("-")) return index const longName = arg.startsWith("--") ? arg.slice(2).split("=", 1)[0] : undefined if ( (longName ? options[longName] : Object.values(options).find(({ short }) => short === arg.slice(1)) )?.type === "string" && !arg.includes("=") ) { index++ } } } export { showUsage }