mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +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.
316 lines
12 KiB
TypeScript
316 lines
12 KiB
TypeScript
import asTableModule from "as-table";
|
|
|
|
import * as lib from "@clusterio/lib";
|
|
import { logger } from "@clusterio/lib";
|
|
import type { Ctl } from "../ctl.js";
|
|
import { print } from "./command_ops.js";
|
|
|
|
const asTable = asTableModule.configure({ delimiter: " | " });
|
|
|
|
export const modPackCommands = new lib.CommandTree({ name: "mod-pack", description: "Mod Pack" });
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["show <mod-pack>", "Show details of mod pack", (yargs) => {
|
|
yargs.positional("mod-pack", { describe: "Mod pack to show", type: "string" });
|
|
}],
|
|
handler: async function(args: { modPack: string }, ctl: Ctl) {
|
|
let mods = new Map(
|
|
(await ctl.send(new lib.ModListRequest())).map(m => [m.id, m])
|
|
);
|
|
let modPack = await ctl.send(
|
|
new lib.ModPackGetRequest(await lib.resolveModPack(ctl, args.modPack))
|
|
);
|
|
|
|
for (let [field, value] of Object.entries(modPack)) {
|
|
if (field === "mods") {
|
|
print(`${field}:`);
|
|
for (let [name, entry] of value) {
|
|
const mod = mods.get(`${name}_${entry.version}`);
|
|
const missing = !mod && "? ";
|
|
const badChecksum = mod && entry.sha1 && entry.sha1 !== mod.sha1 && "! ";
|
|
const warn = missing || badChecksum || "";
|
|
const enabled = entry.enabled ? "" : "(disabled) ";
|
|
print(` ${warn}${enabled}${name} ${entry.version}${entry.sha1 ? ` (${entry.sha1})` : ""}`);
|
|
}
|
|
} else if (field === "settings") {
|
|
print(`${field}:`);
|
|
for (let [scope, settings] of Object.entries(value)) {
|
|
print(` ${scope}:`);
|
|
for (let [setting, settingValue] of settings as any) {
|
|
print(` ${setting}: ${JSON.stringify(settingValue.value)}`);
|
|
}
|
|
}
|
|
} else if (field === "exportManifest") {
|
|
print(`${field}:`);
|
|
if (value && value.assets) {
|
|
print(" assets:");
|
|
for (let [name, fileName] of Object.entries(value.assets)) {
|
|
print(` ${name}: ${fileName}`);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
print(`${field}: ${value}`);
|
|
}
|
|
}
|
|
},
|
|
}));
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: [["list", "l"], "List mod packs in the cluster"],
|
|
handler: async function(args: object, ctl: Ctl) {
|
|
let modPacks = await ctl.send(new lib.ModPackListRequest());
|
|
let fields = ["id", "name", "factorioVersion"];
|
|
for (let entry of modPacks) {
|
|
for (let field of Object.keys(entry)) {
|
|
if (!fields.includes(field)) {
|
|
// @ts-expect-error terrible hack
|
|
delete entry[field];
|
|
}
|
|
}
|
|
}
|
|
print(asTable(modPacks));
|
|
},
|
|
}));
|
|
|
|
function setModPackSettings(
|
|
modPack: lib.ModPack,
|
|
args: {
|
|
boolSetting?: string[],
|
|
intSetting?: string[],
|
|
doubleSetting?: string[],
|
|
stringSetting?: string[],
|
|
colorSetting?: string[],
|
|
},
|
|
) {
|
|
function doSettings(settings: string[], cast: (value: string) => (string | number | boolean)) {
|
|
for (let i = 0; i + 2 < settings.length; i += 3) {
|
|
const [scope, name, value] = settings.slice(i, i + 3);
|
|
if (!["startup", "runtime-global", "runtime-per-user"].includes(scope)) {
|
|
throw new lib.CommandError(
|
|
`Setting scope must be one of startup, runtime-gloabl, or runtime-per-user, not ${scope}`
|
|
);
|
|
}
|
|
const checkedScope = scope as "startup" | "runtime-global" | "runtime-per-user";
|
|
modPack.settings[checkedScope].set(name, { value: cast(value) });
|
|
}
|
|
}
|
|
|
|
doSettings(args.boolSetting || [], value => {
|
|
if (!["true", "false"].includes(value)) {
|
|
throw new lib.CommandError(`boolean value must be one of true or false, not ${value}`);
|
|
}
|
|
return value === "true";
|
|
});
|
|
doSettings(args.intSetting || [], value => {
|
|
// eslint-disable-next-line radix
|
|
let number = Number.parseInt(value);
|
|
if (isNaN(number)) {
|
|
throw new lib.CommandError(`int value must be an integer, not ${value}`);
|
|
}
|
|
return number;
|
|
});
|
|
doSettings(args.doubleSetting || [], value => {
|
|
let number = Number.parseFloat(value);
|
|
if (isNaN(number)) {
|
|
throw new lib.CommandError(`double value must be a number, not ${value}`);
|
|
}
|
|
return number;
|
|
});
|
|
doSettings(args.stringSetting || [], value => value);
|
|
doSettings(args.colorSetting || [], value => {
|
|
let color = JSON.parse(value);
|
|
if (typeof color !== "object" || color === null) {
|
|
throw new lib.CommandError("color value must be a JSON object with r, g, b, a properties");
|
|
}
|
|
return color;
|
|
});
|
|
}
|
|
|
|
function setModPackMods(modPack: lib.ModPack, mods: string[] | undefined) {
|
|
for (let mod of mods || []) {
|
|
const [name, version, sha1] = mod.split(":");
|
|
if (!version) {
|
|
throw new lib.CommandError("Added mod must be formatted as name:version or name:version:sha1");
|
|
}
|
|
if (!lib.isSourceVersion(version)) {
|
|
throw new lib.CommandError("version must match the format digit.digit.digit");
|
|
}
|
|
if (sha1 && !/^[0-9a-f]{40}$/.test(sha1)) {
|
|
throw new lib.CommandError("sha1 must be a 40 digit lower case hex string");
|
|
}
|
|
modPack.mods.set(name, { name, enabled: true, version, sha1 });
|
|
}
|
|
}
|
|
|
|
function setModPackModsEnabled(modPack: lib.ModPack, mods: string[] | undefined, enabled: boolean) {
|
|
for (let mod of mods || []) {
|
|
if (!modPack.mods.has(mod)) {
|
|
throw new lib.CommandError(`Mod named ${mod} does not exist in the mod pack`);
|
|
}
|
|
modPack.mods.get(mod)!.enabled = enabled;
|
|
}
|
|
}
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["create <name> <factorio-version>", "Create mod pack", (yargs) => {
|
|
yargs.positional("name", { describe: "Name of mod pack to create", type: "string" });
|
|
yargs.positional("factorio-version", { describe: "Version of factorio the mod pack is for", type: "string" });
|
|
yargs.options({
|
|
"description": { describe: "Description for mod pack", type: "string" },
|
|
"mods": { describe: "Mods in the form of name:version[:sha1]", array: true, type: "string" },
|
|
"disabled-mods": { describe: "Mods that are in the pack but not enabled", array: true, type: "string" },
|
|
"bool-setting": { describe: "Set boolean setting", array: true, nargs: 3, type: "string" },
|
|
"int-setting": { describe: "Set int setting", array: true, nargs: 3, type: "string" },
|
|
"double-setting": { describe: "Set double setting", array: true, nargs: 3, type: "string" },
|
|
"string-setting": { describe: "Set string setting", array: true, nargs: 3, type: "string" },
|
|
"color-setting": { describe: "Set color setting", array: true, nargs: 3, type: "string" },
|
|
});
|
|
}],
|
|
handler: async function(
|
|
args: {
|
|
name: string,
|
|
factorioVersion: string,
|
|
description?: string,
|
|
mods?: string[],
|
|
disabledMods?: string[],
|
|
boolSetting?: string[],
|
|
intSetting?: string[],
|
|
doubleSetting?: string[],
|
|
stringSetting?: string[],
|
|
colorSetting?: string[],
|
|
},
|
|
ctl: Ctl,
|
|
) {
|
|
const modPack = new lib.ModPack();
|
|
modPack.name = args.name;
|
|
if (args.description) { modPack.description = args.description; }
|
|
if (args.factorioVersion) {
|
|
if (!lib.isPartialVersion(args.factorioVersion)) {
|
|
throw new lib.CommandError("factorio-version must match the format digit.digit[.digit]");
|
|
}
|
|
modPack.factorioVersion = args.factorioVersion;
|
|
}
|
|
setModPackMods(modPack, args.mods);
|
|
setModPackModsEnabled(modPack, args.disabledMods, false);
|
|
setModPackSettings(modPack, args);
|
|
await ctl.send(new lib.ModPackCreateRequest(modPack));
|
|
print(`Created mod pack ${modPack.name} (${modPack.id})`);
|
|
},
|
|
}));
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["import <string>", "Import mod pack string", (yargs) => {
|
|
yargs.positional("string", { describe: "Mod pack string to import", type: "string" });
|
|
}],
|
|
handler: async function(args: { string: string }, ctl: Ctl) {
|
|
const modPack = lib.ModPack.fromModPackString(args.string);
|
|
await ctl.send(new lib.ModPackCreateRequest(modPack));
|
|
print(`Created mod pack ${modPack.name} (${modPack.id})`);
|
|
},
|
|
}));
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["export <mod-pack>", "Export mod pack string", (yargs) => {
|
|
yargs.positional("string", { describe: "Mod pack to export", type: "string" });
|
|
}],
|
|
handler: async function(args: { modPack: string }, ctl: Ctl) {
|
|
const modPack = await ctl.send(
|
|
new lib.ModPackGetRequest(await lib.resolveModPack(ctl, args.modPack))
|
|
);
|
|
print(modPack.toModPackString());
|
|
},
|
|
}));
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["edit <mod-pack>", "Edit mod pack", (yargs) => {
|
|
yargs.positional("mod-pack", { describe: "Mod pack to remove from", type: "string" });
|
|
yargs.options({
|
|
"name": { describe: "New name for mod pack", type: "string" },
|
|
"description": { describe: "New description for mod pack", type: "string" },
|
|
"factorio-version": { describe: "Set version of factorio the mod pack is for", type: "string" },
|
|
"add-mods": { describe: "Mods in the form of name:version[:sha1] to add", array: true, type: "string" },
|
|
"enable-mods": { describe: "Mods to set as enabled", array: true, type: "string" },
|
|
"disable-mods": { describe: "Mods to set as disabled", array: true, type: "string" },
|
|
"remove-mods": { describe: "Name of mods to remove", array: true, type: "string" },
|
|
"bool-setting": { describe: "Set boolean setting", array: true, nargs: 3, type: "string" },
|
|
"int-setting": { describe: "Set int setting", array: true, nargs: 3, type: "string" },
|
|
"double-setting": { describe: "Set double setting", array: true, nargs: 3, type: "string" },
|
|
"string-setting": { describe: "Set string setting", array: true, nargs: 3, type: "string" },
|
|
"color-setting": { describe: "Set color setting", array: true, nargs: 3, type: "string" },
|
|
"remove-setting": { describe: "Remove a setting", array: true, nargs: 2, type: "string" },
|
|
});
|
|
}],
|
|
handler: async function(
|
|
args: {
|
|
modPack: string,
|
|
name?: string,
|
|
description?: string,
|
|
factorioVersion?: string,
|
|
addMods?: string[],
|
|
enableMods?: string[],
|
|
disableMods?: string[],
|
|
removeMods?: string[],
|
|
boolSetting?: string[],
|
|
intSetting?: string[],
|
|
doubleSetting?: string[],
|
|
stringSetting?: string[],
|
|
colorSetting?: string[],
|
|
removeSetting?: string[],
|
|
},
|
|
ctl: Ctl,
|
|
) {
|
|
const modPack = await ctl.send(
|
|
new lib.ModPackGetRequest(await lib.resolveModPack(ctl, args.modPack))
|
|
);
|
|
|
|
if (args.name) { modPack.name = args.name; }
|
|
if (args.description) { modPack.description = args.description; }
|
|
if (args.factorioVersion) {
|
|
if (!lib.isPartialVersion(args.factorioVersion)) {
|
|
throw new lib.CommandError("factorio-version must match the format digit.digit[.digit]");
|
|
}
|
|
modPack.factorioVersion = args.factorioVersion;
|
|
}
|
|
|
|
setModPackMods(modPack, args.addMods);
|
|
for (let name of args.removeMods || []) {
|
|
if (modPack.mods.has(name)) {
|
|
modPack.mods.delete(name);
|
|
} else {
|
|
logger.warn(`Mod ${name} did not exist on ${modPack.name}`);
|
|
}
|
|
}
|
|
setModPackModsEnabled(modPack, args.disableMods, false);
|
|
setModPackModsEnabled(modPack, args.enableMods, true);
|
|
|
|
setModPackSettings(modPack, args);
|
|
if (args.removeSetting) {
|
|
for (let i = 0; i + 1 < args.removeSetting.length; i += 2) {
|
|
const [scope, name] = args.removeSetting.slice(i, i + 2);
|
|
if (!["startup", "runtime-global", "runtime-per-user"].includes(scope)) {
|
|
throw new lib.CommandError(
|
|
`Setting scope must be one of startup, runtime-gloabl, or runtime-per-user, not ${scope}`
|
|
);
|
|
}
|
|
const checkedScope = scope as "startup" | "runtime-global" | "runtime-per-user";
|
|
if (modPack.settings[checkedScope].has(name)) {
|
|
modPack.settings[checkedScope].delete(name);
|
|
} else {
|
|
logger.warn(`Mod setting ${scope} ${name} did not exist on ${modPack.name}`);
|
|
}
|
|
}
|
|
}
|
|
await ctl.send(new lib.ModPackUpdateRequest(modPack));
|
|
},
|
|
}));
|
|
|
|
modPackCommands.add(new lib.Command({
|
|
definition: ["delete <mod-pack>", "Delete mod pack", (yargs) => {
|
|
yargs.positional("mod-pack", { describe: "Mod pack to delete", type: "string" });
|
|
}],
|
|
handler: async function(args: { modPack: string }, ctl: Ctl) {
|
|
const id = await lib.resolveModPack(ctl, args.modPack);
|
|
await ctl.send(new lib.ModPackDeleteRequest(id));
|
|
},
|
|
}));
|