mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Ctl ended up calling its main class for "Control" for historical
reasons, along with its config file and plugin entrypoint.
The plugin entrypoint was renamed to ctl in 808eda0513. This
finishes that renaming by also renaming the main class, default config
file, config-control command, and main entrypoint.
The config-control command is renamed to config to be consistent with
the host and controller's config commands.
The term "control" is still kept in the config and message locations as
a common specifier for both ctl and web_ui.
243 lines
6.9 KiB
TypeScript
243 lines
6.9 KiB
TypeScript
/**
|
|
* Command library
|
|
*
|
|
* Data types and utilites for commands to clusterioctl's CLI.
|
|
*
|
|
* @author Hornwitser
|
|
* @module lib/command
|
|
*/
|
|
import * as libData from "./data/index.js";
|
|
import * as libErrors from "./errors.js";
|
|
import type { Link } from "./link/index.js";
|
|
import type { Argv } from "yargs";
|
|
|
|
export type CommandHandler = (args: any, ctl: any) => Promise<void>;
|
|
export type CommandDefinition = [string | string[], string?, ((yargs: Argv) => void)?];
|
|
|
|
/**
|
|
* Represents a command that can be runned by clusterioctl
|
|
*/
|
|
export class Command {
|
|
private _handler: CommandHandler;
|
|
private _definition: CommandDefinition;
|
|
|
|
name: string;
|
|
alias: string[];
|
|
|
|
/**
|
|
* Define an executable command.
|
|
*
|
|
* @param cmd - Command definiton.
|
|
* @param cmd.definiton -
|
|
* Arguments to pass to yargs .command method to define this
|
|
* command.
|
|
* @param cmd.handler -
|
|
* Async function invoked when the command is executed. Is given
|
|
* the parsed args and a reference to the {@link
|
|
* module:ctl/ctl.Ctl} instance.
|
|
*/
|
|
constructor({ definition, handler }: { definition: CommandDefinition, handler: CommandHandler }) {
|
|
if (definition[0] instanceof Array) {
|
|
this.name = definition[0][0].split(" ")[0];
|
|
this.alias = definition[0].slice(1);
|
|
} else {
|
|
this.name = definition[0].split(" ")[0];
|
|
this.alias = [];
|
|
}
|
|
this._definition = definition;
|
|
this._handler = handler;
|
|
}
|
|
|
|
register(yargs: any) {
|
|
yargs.command(...this._definition);
|
|
}
|
|
|
|
async run(args: Record<string, unknown>, ctl: Link) {
|
|
await this._handler(args, ctl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A node in the command tree that can hold commands and sub trees
|
|
*
|
|
* Container which can contain {@link module:lib.Command}s and other
|
|
* CommandTrees. This is used by clusterioctl to hold the full tree of
|
|
* available commands, you may extend this tree by using a ctl plugin,
|
|
* see {@link module:lib.BaseControlPlugin#addCommands}
|
|
*/
|
|
export class CommandTree {
|
|
public name: string;
|
|
public alias: string[];
|
|
public description: string;
|
|
public subCommands: Map<string, Command | CommandTree>;
|
|
|
|
/**
|
|
* Define a command containing sub commands.
|
|
*
|
|
* @param cmd - Command definition
|
|
* @param cmd.name - Name of the command.
|
|
* @param cmd.alias - Aliases for this tree.
|
|
* @param cmd.description -
|
|
* Descripton to provide for this command tree node.
|
|
*/
|
|
constructor({ name, alias, description }: { name: string, alias?: string[], description: string }) {
|
|
if (typeof name !== "string") {
|
|
throw new Error("name must be a string");
|
|
}
|
|
|
|
this.name = name;
|
|
this.alias = alias || [];
|
|
this.description = description;
|
|
this.subCommands = new Map();
|
|
}
|
|
|
|
/**
|
|
* Add a command or command tree to this tree
|
|
*
|
|
* @param command -
|
|
* The command to add to this command tree.
|
|
*/
|
|
add(command: Command | CommandTree) {
|
|
if (this.subCommands.has(command.name)) {
|
|
throw new Error(`Command ${command.name} already exists`);
|
|
}
|
|
this.subCommands.set(command.name, command);
|
|
for (let alias of command.alias) {
|
|
if (this.subCommands.has(alias)) {
|
|
throw new Error(`Alias ${alias} for command ${command.name} already exists`);
|
|
}
|
|
this.subCommands.set(alias, command);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a command or command tree from this tree
|
|
*
|
|
* @param name -
|
|
* The name of the command or command tree to retrieve.
|
|
* @return
|
|
* The command to add to this command tree.
|
|
*/
|
|
get(name: string) {
|
|
return this.subCommands.get(name);
|
|
}
|
|
|
|
/**
|
|
* Insert the registered commands into the given yargs parser
|
|
*
|
|
* Traverses the the tree of sub-commands recursively and adds all of
|
|
* them to the given yargs parser.
|
|
*
|
|
* @param yargs - yargs parser to register with.
|
|
*/
|
|
register(yargs: any) {
|
|
yargs.command([this.name].concat(this.alias), this.description, (yargs: any) => {
|
|
for (let [name, command] of this.subCommands) {
|
|
// Check if the entry is not an alias.
|
|
if (name === command.name) {
|
|
command.register(yargs);
|
|
}
|
|
}
|
|
}, () => {
|
|
yargs.showHelp();
|
|
yargs.exit();
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Resolve a string with either an id or a name to the id of an entry
|
|
*
|
|
* An integer like string is treated as an id if an entry with that id
|
|
* exists, otherwise it is looked up as a name.
|
|
*/
|
|
function resolveId(entries: { id: number, name: string }[], idOrName: string) {
|
|
if (/^-?\d+$/.test(idOrName)) {
|
|
const id = parseInt(idOrName, 10);
|
|
if (entries.some(entry => entry.id === id)) {
|
|
return id;
|
|
}
|
|
}
|
|
return entries.find(entry => entry.name === idOrName)?.id;
|
|
}
|
|
|
|
/**
|
|
* Resolve a string into a host ID
|
|
*
|
|
* Resolves a string with either an host name or an id into an integer with
|
|
* the host ID.
|
|
*
|
|
* @param client - link to controller to query host on.
|
|
* @param hostName - string with name or id of host.
|
|
* @returns host ID.
|
|
*/
|
|
export async function resolveHost(client: Link, hostName: string) {
|
|
const hosts = await client.sendTo("controller", new libData.HostListRequest());
|
|
const hostId = resolveId(hosts, hostName);
|
|
if (hostId === undefined) {
|
|
throw new libErrors.CommandError(`No host named ${hostName}`);
|
|
}
|
|
return hostId;
|
|
}
|
|
|
|
/**
|
|
* Resolve a string to an instance ID
|
|
*
|
|
* Resolves a string with either an instance name or an id into an integer
|
|
* with the instance ID.
|
|
*
|
|
* @param client - link to controller to query instance on.
|
|
* @param instanceName - string with name or id of instance.
|
|
* @returns instance ID.
|
|
*/
|
|
export async function resolveInstance(client: Link, instanceName: string) {
|
|
const instances = await client.sendTo("controller", new libData.InstanceDetailsListRequest());
|
|
const instanceId = resolveId(instances, instanceName);
|
|
if (instanceId === undefined) {
|
|
throw new libErrors.CommandError(`No instance named ${instanceName}`);
|
|
}
|
|
return instanceId;
|
|
}
|
|
|
|
/**
|
|
* Resolve a string to a mod pack ID
|
|
*
|
|
* Resolevs a string with either a mod pack name or an id into an integer
|
|
* with the mod pack ID.
|
|
*
|
|
* @param client -
|
|
* link to controller to query mod pack on.
|
|
* @param modPackName - string with name or id of mod pack.
|
|
* @returns mod pack ID.
|
|
*/
|
|
export async function resolveModPack(client: Link, modPackName: string) {
|
|
const modPacks = await client.sendTo("controller", new libData.ModPackListRequest());
|
|
const modPackId = resolveId(modPacks, modPackName);
|
|
if (modPackId === undefined) {
|
|
throw new libErrors.CommandError(`No mod pack named ${modPackName}`);
|
|
}
|
|
return modPackId;
|
|
}
|
|
|
|
/**
|
|
* Retrieve role object from string
|
|
*
|
|
* Resolves a string with either a role name or an id into an object
|
|
* representing the role.
|
|
*
|
|
* @param client - link to controller to query role on.
|
|
* @param roleName - string with name or id of role.
|
|
* @returns Role info.
|
|
*/
|
|
export async function retrieveRole(client: Link, roleName: string) {
|
|
const roles = await client.sendTo("controller", new libData.RoleListRequest());
|
|
const roleId = resolveId(roles, roleName);
|
|
const resolvedRole = roles.find(role => role.id === roleId);
|
|
if (!resolvedRole) {
|
|
throw new libErrors.CommandError(`No role named ${roleName}`);
|
|
}
|
|
|
|
return resolvedRole;
|
|
}
|