Huge commit. Multiple fixes. Refactored commands. Invitations, resources, template inspection, mnemonic stuff, cli utils, pretty printing, remove unreserve on start, fix connectino requirement for invitations, format cashAddress to lockingBytecode on send, lots and lots of other stuff.
This commit is contained in:
204
src/cli/index.ts
Normal file
204
src/cli/index.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* CLI entry point.
|
||||
*
|
||||
* TODO: Decide the best way to handle CLI arguments. We have the option of:
|
||||
* - Handling it in the `bin` folder
|
||||
* - Switch / if statements in here
|
||||
* - Dedicated command parser
|
||||
* - Separate files?
|
||||
*
|
||||
* What kind of commands do we want to support?
|
||||
* Worth noting that we shouldn't need to list invitations? Maybe we will though? If we do, then we will need to reuse the storage + xo-invitations.db file. I think this is fine to do though?
|
||||
* Nah, lets use the storage + xo-invitations.db file. Will allow us to persist invitations.
|
||||
* How do we want to import invitations though? Should we just take in the ID still? Probably makes more sense to allow for reading from a file though...
|
||||
* But thats an entirely different flow to what we have already. And how would we handle writing the invitation? Do we just overwrite the file? Probably... Just take in an -o option; default to overwrite?
|
||||
*
|
||||
* Commands:
|
||||
* xo-cli mnemonic create [mnemonic seed]
|
||||
* xo-cli mnemonic list
|
||||
*
|
||||
* xo-cli template import <template-file>
|
||||
* xo-cli template list
|
||||
* xo-cli template set-default <template-file> <output-identifier> <role-identifier>
|
||||
*
|
||||
* xo-cli invitation list
|
||||
* xo-cli invitation create <template-file> <action-id> [-o Output file, var-${action-variable-name}=${value}, role=${value}]
|
||||
* xo-cli invitation import <invitation-file>
|
||||
* xo-cli invitation sign <invitation-file>
|
||||
* xo-cli invitation broadcast <invitation-file>
|
||||
*
|
||||
* xo-cli resource list
|
||||
*
|
||||
* universal Args:
|
||||
* -h --help
|
||||
* -m --mnemonic-file <mnemonic-file>
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
|
||||
import { AppService } from "../services/app.js";
|
||||
import { convertArgsToObject } from "./arguments.js";
|
||||
import { bold, dim, formatObject } from "./cli-utils.js";
|
||||
import { listMnemonicFiles, loadMnemonic } from "./mnemonic.js";
|
||||
|
||||
/** File that remembers the last-used mnemonic so `-m` can be omitted. */
|
||||
const WALLET_CONFIG_FILE = ".xo-cli-wallet";
|
||||
|
||||
import {
|
||||
type CommandDependencies,
|
||||
handleMnemonicCommand,
|
||||
handleTemplateCommand,
|
||||
handleInvitationCommand,
|
||||
handleReceiveCommand,
|
||||
handleResourceCommand,
|
||||
} from "./commands/index.js";
|
||||
|
||||
import { handleCompletionsCommand } from "./completions.js";
|
||||
|
||||
const createConditionalLogger = (verbose: boolean) => {
|
||||
return (message: string) => {
|
||||
if (verbose) {
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Main entry point.
|
||||
* We will:
|
||||
* - Initialize the app service?
|
||||
* - Extract the command being called
|
||||
* - Extract CLI Args (Depends on the command being called. Eww. But we can probably use Zod to validate the args in a decent way?)
|
||||
* - Execute the command
|
||||
* - Export if configured?
|
||||
* - Exit with the appropriate code
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
// Initialize the app service
|
||||
// NOTE: We are going to assume that they are using a mnemonic file for now
|
||||
const { args, options } = convertArgsToObject(process.argv.slice(2));
|
||||
|
||||
// Create a verbose logger if the user set the verbose flag
|
||||
const verboseLogger = createConditionalLogger(options["verbose"] === "true");
|
||||
|
||||
// Log the parsed app args
|
||||
verboseLogger(`Parsed args: ${formatObject(args)}`);
|
||||
verboseLogger(`Parsed options: ${formatObject(options)}`);
|
||||
|
||||
// Handle the command
|
||||
const command = args[0];
|
||||
verboseLogger(`Command: ${command}`);
|
||||
if (!command) {
|
||||
// TODO: Print help, probably...
|
||||
console.error("No command provided");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Positional args after the command name (sub-command, files, etc.)
|
||||
const subArgs = args.slice(1);
|
||||
|
||||
// Early handling if we are calling the mnemonic command
|
||||
// TODO: This is ugly. I would like to find a nicer way of doing this.
|
||||
if (command === "completions") {
|
||||
handleCompletionsCommand(subArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "mnemonic") {
|
||||
await handleMnemonicCommand({ verboseLogger }, subArgs, options);
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve mnemonic file: explicit flag > persisted config > error.
|
||||
let mnemonicFile = options["mnemonicFile"];
|
||||
if (!mnemonicFile && existsSync(WALLET_CONFIG_FILE)) {
|
||||
mnemonicFile = readFileSync(WALLET_CONFIG_FILE, "utf8").trim();
|
||||
verboseLogger(`Using persisted wallet: ${mnemonicFile}`);
|
||||
}
|
||||
if (!mnemonicFile) {
|
||||
console.error("No mnemonic file provided");
|
||||
console.log(`You can create a mnemonic file with the following command: xo-cli mnemonic create <mnemonic-seed> or use one of the following files: \n${listMnemonicFiles().join("\n")}`);
|
||||
console.log(`\nTip: pass -m <file> once and it will be remembered in ${WALLET_CONFIG_FILE}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Persist the choice so subsequent commands can omit -m.
|
||||
writeFileSync(WALLET_CONFIG_FILE, mnemonicFile);
|
||||
|
||||
const mnemonic = await loadMnemonic(mnemonicFile);
|
||||
verboseLogger(`Loaded mnemonic from file: ${mnemonicFile} ${mnemonic}`);
|
||||
|
||||
// Create an App instance
|
||||
verboseLogger("Creating app instance...");
|
||||
const app = await AppService.create(mnemonic, {
|
||||
syncServerUrl: options["syncServerUrl"] ?? "http://localhost:3000",
|
||||
engineConfig: {
|
||||
databasePath: options["databasePath"] ?? "./",
|
||||
databaseFilename: options["databaseFilename"] ?? 'xo-wallet.db',
|
||||
},
|
||||
invitationStoragePath: options["invitationStoragePath"] ?? "./xo-invitations.db",
|
||||
});
|
||||
verboseLogger("App instance created");
|
||||
|
||||
// Start the app
|
||||
verboseLogger("Starting app...");
|
||||
await app.start();
|
||||
verboseLogger("App started");
|
||||
|
||||
const commandDependencies: CommandDependencies = {
|
||||
verboseLogger,
|
||||
app,
|
||||
};
|
||||
|
||||
// Handle the command
|
||||
switch (command) {
|
||||
case "template":
|
||||
await handleTemplateCommand(commandDependencies, subArgs, options);
|
||||
break;
|
||||
case "invitation":
|
||||
await handleInvitationCommand(commandDependencies, subArgs, options);
|
||||
break;
|
||||
case "receive":
|
||||
await handleReceiveCommand(commandDependencies, subArgs, options);
|
||||
break;
|
||||
case "resource":
|
||||
await handleResourceCommand(commandDependencies, subArgs, options);
|
||||
break;
|
||||
case "help":
|
||||
await handleHelpCommand(commandDependencies, subArgs, options);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown command: ${command}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Exit the process
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const handleHelpCommand = async (deps: CommandDependencies, args: string[], options: Record<string, string>): Promise<void> => {
|
||||
// Im sorry about the formatting here. I'm not sure how to handle this better.
|
||||
console.log(
|
||||
`${bold("XO-CLI Help:")}
|
||||
|
||||
${bold("Usage:")} xo-cli <command> [options]
|
||||
|
||||
Commands:
|
||||
mnemonic ${dim("Manage mnemonic files")}
|
||||
template ${dim("Manage templates")}
|
||||
invitation ${dim("Manage invitations")}
|
||||
receive ${dim("Generate a single-use receiving address")}
|
||||
resource ${dim("Manage resources")}
|
||||
completions ${dim("Generate shell completion scripts (bash, zsh, fish)")}
|
||||
|
||||
Options:
|
||||
-h, --help ${dim("Show this help message")}
|
||||
-m, --mnemonic-file <mnemonic-file> ${dim("Use a specific mnemonic file")}
|
||||
-v, --verbose ${dim("Show verbose output")}`
|
||||
);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user