feat(cli): add Command pipeline base classes

The Command base class, Commands registry, FeedbackMessageType enum,
and four CommandOrigin subclasses (Player/Entity/Block/Server) that
every CLI command in this branch depends on. Without these files the
new commands fail to resolve imports at module load.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ForestOfLight
2026-05-20 03:26:25 -07:00
Unverified
parent abaf275c5d
commit 19cbceb120
8 changed files with 205 additions and 0 deletions
@@ -0,0 +1,12 @@
import { CommandOrigin } from "./CommandOrigin";
import { FeedbackMessageType } from "./FeedbackMessageType";
export class BlockCommandOrigin extends CommandOrigin {
getSource() {
return this.source.sourceBlock;
}
sendMessage() {
return FeedbackMessageType.None;
}
}
@@ -0,0 +1,108 @@
import { CustomCommandSource, CustomCommandStatus, Player, system } from "@minecraft/server";
import { Commands } from "./Commands.js";
import { BlockCommandOrigin } from "./BlockCommandOrigin";
import { EntityCommandOrigin } from "./EntityCommandOrigin";
import { ServerCommandOrigin } from "./ServerCommandOrigin";
import { PlayerCommandOrigin } from "./PlayerCommandOrigin";
import { PACK_IDENTIFIER } from "../../consts.js";
export class Command {
customCommand;
static resolveCommandOrigin(origin) {
switch (origin.sourceType) {
case CustomCommandSource.Block:
return new BlockCommandOrigin(origin);
case CustomCommandSource.Entity:
if (origin.sourceEntity instanceof Player)
return new PlayerCommandOrigin(origin);
return new EntityCommandOrigin(origin);
case CustomCommandSource.Server:
return new ServerCommandOrigin(origin);
default:
throw new Error("Unknown command source: " + origin?.sourceType);
}
}
constructor(customCommand) {
this.customCommand = customCommand;
this.#setDefaultArgs();
Commands.register(this);
system.beforeEvents.startup.subscribe(this.setupForRegistry.bind(this));
}
getName() {
return this.customCommand.name.replace(/^[^:]+:/, '');
}
isCheatsRequired() {
return this.customCommand.cheatsRequired;
}
setupForRegistry(startupEvent) {
this.#registerCommand(startupEvent.customCommandRegistry);
system.beforeEvents.startup.unsubscribe(this.setupForRegistry.bind(this));
}
#registerCommand(customCommandRegistry) {
this.#addPreCallback();
this.#registerEnums(customCommandRegistry);
this.#registerSingleCommand(customCommandRegistry);
this.#registerAliasCommands(customCommandRegistry);
}
#setDefaultArgs() {
if (this.customCommand.cheatsRequired === void 0)
this.customCommand.cheatsRequired = false;
}
#addPreCallback() {
this.callback = (origin, ...args) => {
const source = Command.resolveCommandOrigin(origin);
if (this.#commandSourceIsNotAllowed(source))
return { status: CustomCommandStatus.Failure, message: 'commands.generic.invalidsource' };
return this.customCommand.callback(source, ...args);
}
}
#registerEnums(customCommandRegistry) {
if (this.customCommand.enums) {
for (const customEnum of this.customCommand.enums)
customCommandRegistry.registerEnum(`${PACK_IDENTIFIER}:${customEnum.name}`, customEnum.values);
}
}
#registerSingleCommand(customCommandRegistry, name = this.customCommand.name) {
customCommandRegistry.registerCommand({
name: `${PACK_IDENTIFIER}:${name}`,
description: this.customCommand.description,
permissionLevel: this.customCommand.permissionLevel,
mandatoryParameters: this.#prepParameters(this.customCommand.mandatoryParameters),
optionalParameters: this.#prepParameters(this.customCommand.optionalParameters),
cheatsRequired: this.customCommand.cheatsRequired
}, this.callback);
}
#prepParameters(parameters) {
if (!parameters)
return [];
for (const parameter of parameters) {
if (parameter.name)
parameter.name = `${PACK_IDENTIFIER}:${parameter.name}`;
}
return parameters;
}
#registerAliasCommands(customCommandRegistry) {
if (this.customCommand.aliases) {
for (const alias of this.customCommand.aliases)
this.#registerSingleCommand(customCommandRegistry, alias);
}
}
#commandSourceIsNotAllowed(source) {
if (!this.customCommand.allowedSources)
return false;
return !this.customCommand.allowedSources.includes(source.constructor);
}
}
@@ -0,0 +1,20 @@
import { FeedbackMessageType } from "./FeedbackMessageType";
export class CommandOrigin {
constructor(source) {
this.source = source;
}
getType() {
return this.source.sourceType;
}
getSource() {
throw new Error("getSource() not implemented");
}
sendMessage(message) {
console.error(`Unknown source type: ${this.source.sourceType}`, message);
return FeedbackMessageType.ConsoleError;
}
}
@@ -0,0 +1,15 @@
export class Commands {
static #commands = [];
static register(command) {
this.#commands.push(command);
}
static getAll() {
return [...this.#commands];
}
static clear() {
this.#commands = [];
}
}
@@ -0,0 +1,13 @@
import { CommandOrigin } from "./CommandOrigin";
import { FeedbackMessageType } from "./FeedbackMessageType";
export class EntityCommandOrigin extends CommandOrigin {
getSource() {
return this.source.sourceEntity;
}
sendMessage(message) {
this.getSource().sendMessage(message);
return FeedbackMessageType.ChatMessage;
}
}
@@ -0,0 +1,7 @@
export const FeedbackMessageType = Object.freeze({
None: "none",
ConsoleInfo: "info",
ConsoleWarn: "warn",
ConsoleError: "error",
ChatMessage: "message"
});
@@ -0,0 +1,17 @@
import { CommandOrigin } from "./CommandOrigin";
import { FeedbackMessageType } from "./FeedbackMessageType";
export class PlayerCommandOrigin extends CommandOrigin {
getType() {
return "Player";
}
getSource() {
return this.source.sourceEntity;
}
sendMessage(message) {
this.getSource().sendMessage(message);
return FeedbackMessageType.ChatMessage;
}
}
@@ -0,0 +1,13 @@
import { CommandOrigin } from "./CommandOrigin";
import { FeedbackMessageType } from "./FeedbackMessageType";
export class ServerCommandOrigin extends CommandOrigin {
getSource() {
return this.source.sourceType;
}
sendMessage(message) {
console.log(message);
return FeedbackMessageType.ConsoleInfo;
}
}