354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
import { existsSync, readFileSync } from "fs";
|
|
import path from "path";
|
|
import { generateTemplateIdentifier } from "@xo-cash/engine";
|
|
import type { XOTemplate } from "@xo-cash/types";
|
|
|
|
import { bold, dim, formatObject } from "../utils.js";
|
|
import { resolveTemplateReferences } from "../../utils/templates.js";
|
|
import type { CommandDependencies, CommandIO } from "./types.js";
|
|
import { CommandError } from "./types.js";
|
|
import { resolveTemplate } from "../utils.js";
|
|
|
|
/**
|
|
* Prints the help message for the template command
|
|
*/
|
|
export const printTemplateHelp = (io: CommandIO): void => {
|
|
io.out(
|
|
`
|
|
${bold("Usage:")} xo-cli template <sub-command>
|
|
|
|
${bold("Sub-commands:")}
|
|
- import <template-file> ${dim("Import a template from a file")}
|
|
- list ${dim("List all templates")}
|
|
- list <category> <identifier> ${dim("List all options of the field type in a template")}
|
|
- inspect <category> <identifier> <field> ${dim("Inspect a field in a template")}
|
|
- set-default <template-file> <output-identifier> <role-identifier> ${dim("Set the default template")}
|
|
`,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Handles the template list command.
|
|
* Throws CommandError on failure, returns result data on success.
|
|
* @param deps - The command dependencies.
|
|
* @param args - Positional args after the command name, e.g. ["list", "action"] or ["list", "action", "1234567890"].
|
|
*/
|
|
export const handleTemplateListCommand = async (
|
|
deps: CommandDependencies,
|
|
args: string[],
|
|
): Promise<{ count?: number }> => {
|
|
const templateCategory = args[0];
|
|
deps.io.verbose(`Template list category: ${templateCategory}`);
|
|
|
|
if (!templateCategory) {
|
|
const templates = await deps.app.engine.listImportedTemplates();
|
|
const formattedTemplates = templates.map(
|
|
(template: XOTemplate) =>
|
|
`${bold(generateTemplateIdentifier(template))} - ${dim(template.name)} ${dim(template.description)}`,
|
|
);
|
|
deps.io.out(formattedTemplates.join("\n"));
|
|
return { count: templates.length };
|
|
}
|
|
|
|
const templateIdentifier = args[1];
|
|
deps.io.verbose(`Template identifier: ${templateIdentifier}`);
|
|
|
|
if (!templateIdentifier) {
|
|
deps.io.err("No template identifier provided");
|
|
throw new CommandError(
|
|
"template.list.identifier_missing",
|
|
"No template identifier provided",
|
|
);
|
|
}
|
|
|
|
const rawTemplate = await deps.app.engine.getTemplate(templateIdentifier);
|
|
if (!rawTemplate) {
|
|
deps.io.err(`No template found: ${templateIdentifier}`);
|
|
throw new CommandError(
|
|
"template.list.not_found",
|
|
`No template found: ${templateIdentifier}`,
|
|
);
|
|
}
|
|
|
|
const template = await resolveTemplateReferences(rawTemplate);
|
|
deps.io.verbose(`Template: ${formatObject(template)}`);
|
|
|
|
switch (templateCategory) {
|
|
case "action": {
|
|
const actions = template.actions;
|
|
const formattedActions = Object.entries(actions).map(
|
|
([actionIdentifier, action]) =>
|
|
`${bold(actionIdentifier)} ${dim(action.name)} ${dim(action.description)}`,
|
|
);
|
|
deps.io.out(formattedActions.join("\n"));
|
|
return {};
|
|
}
|
|
case "transaction": {
|
|
const transactions = template.transactions;
|
|
const formattedTransactions = Object.entries(transactions).map(
|
|
([transactionIdentifier, transaction]) =>
|
|
`${bold(transactionIdentifier)} ${dim(transaction.name)} ${dim(transaction.description)}`,
|
|
);
|
|
deps.io.out(formattedTransactions.join("\n"));
|
|
return {};
|
|
}
|
|
case "output": {
|
|
const outputs = template.outputs;
|
|
const formattedOutputs = Object.entries(outputs).map(
|
|
([outputIdentifier, output]) =>
|
|
`${bold(outputIdentifier)} ${dim(output.name)} ${dim(output.description)}`,
|
|
);
|
|
deps.io.out(formattedOutputs.join("\n"));
|
|
return {};
|
|
}
|
|
case "lockingscript": {
|
|
const lockingscripts = template.lockingScripts;
|
|
const formattedLockingscripts = Object.entries(lockingscripts).map(
|
|
([lockingScriptIdentifier, lockingScript]) =>
|
|
`${bold(lockingScriptIdentifier)} ${dim(lockingScript.name)} ${dim(lockingScript.description)}`,
|
|
);
|
|
deps.io.out(formattedLockingscripts.join("\n"));
|
|
return {};
|
|
}
|
|
case "variable": {
|
|
const variables = template.variables || {};
|
|
const formattedVariables = Object.entries(variables).map(
|
|
([variableIdentifier, variable]) =>
|
|
`${bold(variableIdentifier)} ${dim(variable.name)} ${dim(variable.description)}`,
|
|
);
|
|
deps.io.out(formattedVariables.join("\n"));
|
|
return {};
|
|
}
|
|
default: {
|
|
deps.io.verbose(`Unknown template category: ${templateCategory}`);
|
|
throw new CommandError(
|
|
"template.list.category_unknown",
|
|
`Unknown template category: ${templateCategory}`,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Prints the help message for the template inspect command
|
|
*/
|
|
export const printTemplateInspectHelp = (io: CommandIO): void => {
|
|
io.out(
|
|
`
|
|
${bold("Usage:")} xo-cli template inspect <category> <identifier> <field>
|
|
|
|
${bold("Arguments:")}
|
|
<category> ${dim("The category of the template to inspect")}
|
|
<identifier> ${dim("The identifier of the template to inspect")}
|
|
<field> ${dim("The field of the template to inspect")}
|
|
|
|
${bold("Categories:")}
|
|
- action <action-identifier> ${dim("Inspect an action")}
|
|
- transaction <transaction-identifier> ${dim("Inspect a transaction")}
|
|
- output <output-identifier> ${dim("Inspect an output")}
|
|
- lockingscript <lockingscript-identifier> ${dim("Inspect a lockingscript")}
|
|
- variable <variable-identifier> ${dim("Inspect a variable")}
|
|
`,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Handles the template inspect command.
|
|
* Throws CommandError on failure, returns empty object on success.
|
|
* @param deps - The command dependencies.
|
|
* @param args - Positional args after the command name, e.g. ["inspect", "transaction", "1234567890"].
|
|
*/
|
|
export const handleTemplateInspectCommand = async (
|
|
deps: CommandDependencies,
|
|
args: string[],
|
|
): Promise<Record<string, never>> => {
|
|
const templateCategory = args[0];
|
|
const templateQuery = args[1];
|
|
const templateField = args[2];
|
|
|
|
deps.io.verbose(
|
|
`Template inspect args - category: ${templateCategory}, identifier: ${templateQuery}, field: ${templateField}`,
|
|
);
|
|
|
|
if (!templateCategory || !templateQuery || !templateField) {
|
|
deps.io.err("No template category, identifier, or field provided");
|
|
printTemplateInspectHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.inspect.arguments_missing",
|
|
"No template category, identifier, or field provided",
|
|
);
|
|
}
|
|
|
|
const originalTemplate = await resolveTemplate(deps, templateQuery);
|
|
deps.io.verbose(`Original Template: ${formatObject(originalTemplate)}`);
|
|
|
|
const template = await resolveTemplateReferences(originalTemplate);
|
|
deps.io.verbose(`Extended Template: ${formatObject(template)}`);
|
|
|
|
switch (templateCategory) {
|
|
case "action": {
|
|
const action = template.actions[templateField];
|
|
if (!action) {
|
|
deps.io.err(`No action found: ${templateField}`);
|
|
throw new CommandError(
|
|
"template.inspect.action_missing",
|
|
`No action found: ${templateField}`,
|
|
);
|
|
}
|
|
deps.io.out(formatObject(action));
|
|
return {};
|
|
}
|
|
case "transaction": {
|
|
const transaction = template.transactions?.[templateField];
|
|
if (!transaction) {
|
|
deps.io.err(`No transaction found: ${templateField}`);
|
|
throw new CommandError(
|
|
"template.inspect.transaction_missing",
|
|
`No transaction found: ${templateField}`,
|
|
);
|
|
}
|
|
deps.io.out(formatObject(transaction));
|
|
return {};
|
|
}
|
|
case "output": {
|
|
const output = template.outputs[templateField];
|
|
if (!output) {
|
|
deps.io.err(`No output found: ${templateField}`);
|
|
throw new CommandError(
|
|
"template.inspect.output_missing",
|
|
`No output found: ${templateField}`,
|
|
);
|
|
}
|
|
deps.io.out(formatObject(output));
|
|
return {};
|
|
}
|
|
case "lockingscript": {
|
|
const lockingscript = template.lockingScripts[templateField];
|
|
if (!lockingscript) {
|
|
deps.io.err(`No lockingscript found: ${templateField}`);
|
|
throw new CommandError(
|
|
"template.inspect.lockingscript_missing",
|
|
`No lockingscript found: ${templateField}`,
|
|
);
|
|
}
|
|
deps.io.out(formatObject(lockingscript));
|
|
return {};
|
|
}
|
|
case "variable": {
|
|
const variable = template.variables?.[templateField];
|
|
if (!variable) {
|
|
deps.io.err(`No variable found: ${templateField}`);
|
|
throw new CommandError(
|
|
"template.inspect.variable_missing",
|
|
`No variable found: ${templateField}`,
|
|
);
|
|
}
|
|
deps.io.out(formatObject(variable));
|
|
return {};
|
|
}
|
|
default: {
|
|
deps.io.verbose(`Unknown template category: ${templateCategory}`);
|
|
throw new CommandError(
|
|
"template.inspect.category_unknown",
|
|
`Unknown template category: ${templateCategory}`,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles the template command.
|
|
* Throws CommandError on failure, returns result data on success.
|
|
* @param deps - The command dependencies.
|
|
* @param args - Positional args after the command name, e.g. ["import", "template.json"] or ["set-default", "tpl", "out", "role"].
|
|
* @param options - Parsed option flags.
|
|
*/
|
|
export const handleTemplateCommand = async (
|
|
deps: CommandDependencies,
|
|
args: string[],
|
|
_options: Record<string, string>,
|
|
): Promise<{ templateFile?: string; count?: number }> => {
|
|
const subCommand = args[0];
|
|
|
|
if (!subCommand) {
|
|
deps.io.verbose("No sub-command provided");
|
|
printTemplateHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.subcommand.missing",
|
|
"No sub-command provided",
|
|
);
|
|
}
|
|
|
|
switch (subCommand) {
|
|
case "import": {
|
|
const templateFile = args[1];
|
|
deps.io.verbose(`Template file: ${templateFile}`);
|
|
|
|
if (!templateFile) {
|
|
deps.io.verbose("No template file provided");
|
|
printTemplateHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.import.file_missing",
|
|
"No template file provided",
|
|
);
|
|
}
|
|
|
|
const templatePath = path.resolve(`${process.cwd()}/${templateFile}`);
|
|
deps.io.verbose(`Template path: ${templatePath}`);
|
|
|
|
if (!existsSync(templatePath)) {
|
|
deps.io.err(`Template file does not exist: ${templatePath}`);
|
|
printTemplateHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.import.file_not_found",
|
|
`Template file does not exist: ${templatePath}`,
|
|
);
|
|
}
|
|
|
|
const template = await readFileSync(templatePath, "utf8");
|
|
|
|
deps.io.verbose(`Importing template: ${templateFile}`);
|
|
await deps.app.engine.importTemplate(template);
|
|
deps.io.verbose(`Template imported: ${templateFile}`);
|
|
return { templateFile };
|
|
}
|
|
case "list": {
|
|
return handleTemplateListCommand(deps, args.slice(1));
|
|
}
|
|
case "inspect": {
|
|
return handleTemplateInspectCommand(deps, args.slice(1));
|
|
}
|
|
case "set-default": {
|
|
const templateFile = args[1];
|
|
const outputIdentifier = args[2];
|
|
const roleIdentifier = args[3];
|
|
if (!templateFile || !outputIdentifier || !roleIdentifier) {
|
|
deps.io.verbose(
|
|
"No template file, output identifier, or role identifier provided",
|
|
);
|
|
printTemplateHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.default.arguments_missing",
|
|
"No template file, output identifier, or role identifier provided",
|
|
);
|
|
}
|
|
deps.io.verbose(
|
|
`Template file: ${templateFile}, output identifier: ${outputIdentifier}, role identifier: ${roleIdentifier}`,
|
|
);
|
|
await deps.app.engine.setDefaultLockingParameters(
|
|
templateFile,
|
|
outputIdentifier,
|
|
roleIdentifier,
|
|
);
|
|
return {};
|
|
}
|
|
default:
|
|
deps.io.verbose(`Unknown template sub-command: ${subCommand}`);
|
|
printTemplateHelp(deps.io);
|
|
throw new CommandError(
|
|
"template.subcommand.unknown",
|
|
`Unknown template sub-command: ${subCommand}`,
|
|
);
|
|
}
|
|
};
|