Merge branch 'master' into feature/plugin-install

This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-09-12 22:10:52 +01:00
commit 512f62c22b
277 files changed
+2557 -2079

No files matched your search

+5 -6
View File
@@ -6,12 +6,11 @@
* clean-all: Removes all build, test, and execution artifacts. Including cluster config and logs!
*/
"use strict";
const path = require("path");
const fs = require("node:fs/promises");
const yargs = require("yargs");
import path from "node:path";
import fs from "node:fs/promises";
import yargs from "yargs";
const npmPackage = require("./package.json");
import npmPackage from "./package.json" with { type: "json" };
const workspaces = npmPackage.workspaces
.map(p => p.slice(0, -2))
.filter(p => !p.includes("external_plugins"));
@@ -116,6 +115,6 @@ async function main() {
}
// Run main if started from command line
if (module === require.main) {
if (import.meta.main) {
main();
}
+2 -2
View File
@@ -229,8 +229,8 @@ Files containing and exporting a single class should be named the same as the cl
Relative imports in lib package are prefixed with lib to make them easier to distinguish, as the names tend to be very generic.
E.g.:
const libLink = require("./link");
const libLuaTools = require("./lua_tools");
import libLink from "./link";
import libLuaTools from "./lua_tools";
Note that no sub-modules of the lib package is imported directly from the outside of it with the exception of `build_mod`.
+3 -8
View File
@@ -14,7 +14,7 @@ See the following example code for a hypothetical `Data` class with a single pro
* A Data class example
* @alias module:Data
*/
class Data {
export class Data {
/** @type {string} */
member = "foo";
@@ -41,8 +41,6 @@ class Data {
return json;
}
}
module.exports = Data;
```
Serialising an instance of `Data` for JSON is as simple as doing `serialised = JSON.stringify(data)` as the `toJSON` method is automatically called by `JSON.stringify` to create a suitable object.
@@ -53,14 +51,13 @@ For example a class holding a data property with an instance of the Data class f
```js
// Composed.js
"use strict";
const Data = require("./Data");
import Data from "./Data";
/**
* Composed data class example
* @alias module:Data
*/
class Composed {
export class Composed {
/** @type {module:Data|undefined} */
data;
@@ -83,8 +80,6 @@ class Composed {
// toJSON is optional and not needed for this particular class
}
module.exports = Composed;
```
It's easy to create a new instance of Composed by calling `new Composed(new Data("spam"))` and serialising to and from JSON is the same as for Data.
+7
View File
@@ -247,6 +247,13 @@ This command lists all plugins currently known to the remote. Newly installed pl
If remote plugin updates are enabled on the remote, this will execute `npm update --save <name>`. Changes will not be applied until the remote is restarted. If the `--restart` flag is provided, the remote will automatically restart once the update is complete.
### Update All
ctl> controller update --all [--restart]
ctl> host update <host> --all [--restart]
Updates the Clusterio package and all plugins installed from npm in a single `npm update --save` command. Updating everything at once avoids ending up with multiple versions of `@clusterio/lib` in the installation. Requires both remote updates and remote plugin updates to be enabled on the remote.
### Install
ctl> controller plugin install <name> [--restart]
+55 -19
View File
@@ -10,6 +10,7 @@ The plugin classes have pre-defined hooks that are called during various stages
- [Defining the plugin class](#defining-the-plugin-class)
- [Logging Messages](#logging-messages)
- [Plugin Configuration](#plugin-configuration)
- [Plugin Permissions](#plugin-permissions)
- [Communicating with Factorio](#communicating-with-factorio)
- [Defining Link Messages](#defining-link-messages)
- [Sending Link Messages](#sending-link-messages)
@@ -48,12 +49,12 @@ By default the main entrypoint is the `index.js` file, but this may be changed b
Here's an example of `index.js`:
```js
module.exports.plugin = {
export const plugin = {
name: "foo_frobber",
title: "Foo Frobber",
description: "Does advanced frobnication",
instanceEntrypoint: "instance",
controllerEntrypoint: "controller",
instanceEntrypoint: "instance.js",
controllerEntrypoint: "controller.js",
messages: {
/* See below */
},
@@ -101,6 +102,10 @@ The following properties are recognized:
Object with link messages definitions for this plugin.
See guide for [defining link messages](#defining-link-messages) below.
**permissions**:
Array of permission definitions for this plugin.
See [Plugin Permissions](#plugin-permissions)
The optional module folder contains a Clusterio module that will be patched into the save when the plugin is loaded.
See the section on [Clusterio Modules](developing-for-clusterio.md) in the Developing for Clusterio document.
The only restriction imposed on modules embedded into plugins is that they must be named the same as the plugin.
@@ -117,9 +122,9 @@ The plugin class should derive from its respective base class defined in `lib/pl
For example, to define a ControllerPlugin class the following code can be used:
```js
const { BaseControllerPlugin } = require("@clusterio/controller");
import { BaseControllerPlugin } from "@clusterio/controller";
class ControllerPlugin extends BaseControllerPlugin {
export class ControllerPlugin extends BaseControllerPlugin {
async init() {
this.foo = 42;
await this.startFrobnication();
@@ -127,10 +132,6 @@ class ControllerPlugin extends BaseControllerPlugin {
// ...
}
module.exports = {
ControllerPlugin,
}
```
For the instance plugin it's exactly the same except "Controller" is replaced with "Instance", for host plugins "Host" is used and for the clusterioctl plugin "Ctl" is used.
@@ -253,6 +254,45 @@ async init() {
```
## Plugin Permissions
Permissions are granted to roles and checked on users, both on the controller and in the web UI.
A plugin declares the permissions it uses under `permissions` in the `plugin` export.
Names must start with the plugin name followed by a dot.
For example in index.ts:
```ts
import type * as lib from "@clusterio/lib";
declare module "@clusterio/lib" {
// Extend the interface of known permission names so that
// user.checkPermission(...) accepts our permission.
export interface Permissions {
"foo_frobber.frobnicate": never;
}
}
export default {
...
permissions: [
{
name: "foo_frobber.frobnicate",
title: "Frobnicate",
description: "Run frobnication on instances.",
grantByDefault: false, // Whether the generated Player role is granted this permission
},
],
} satisfies lib.PluginDeclaration;
```
The `Permissions` interface only exists at the type level, its values are always `never` and only the keys are used.
Adding your permission names to it gives you autocomplete and a compile error on typos when calling `user.checkPermission`, `account.hasPermission` or setting the `permission` of a web UI page.
In JavaScript the `declare module` block is not needed.
Permissions declared this way are registered when the plugin is loaded.
The older `lib.definePermission()` function still works, but a plugin should not use both for the same permission name.
## Communicating with Factorio
For pushing data into Factorio there's RCON, which lets you send arbitrary Lua commands to invoke whatever code you want in the game.
@@ -525,7 +565,7 @@ In its simplest form collecting data from plugins consists of defining the metri
For example:
```js
const { Counter } = require("@clusterio/lib");
import { Counter } from "@clusterio/lib";
const fooMetric = new Counter(
"clusterio_foo_frobber_foo_metric", "Measures the level of foo",
@@ -541,7 +581,7 @@ It's recommended that plugin metrics follow `clusterio_<plugin_name>_<metric_nam
For metrics that are per-instance, you must define an `instance_id` label and set it accordingly, for example:
```js
const { Counter } = require("@clusterio/lib");
import { Counter } from "@clusterio/lib";
const barMetric = new Gauge(
"clusterio_foo_frobber_bar_metric", "Bar instance level",
@@ -565,7 +605,7 @@ The control entrypoint for plugins allows you to extend clustectl with your own
The creation of custom commands typically starts with defining a command tree for the plugin:
```js
const { Command, CommandTree } = require("@clusterio/lib");
import { Command, CommandTree } from "@clusterio/lib";
const fooFrobberCommands = new CommandTree({
name: "foo-frobber", description: "Foo Frobber Plugin commands"
});
@@ -574,7 +614,7 @@ const fooFrobberCommands = new CommandTree({
Then commands are added to the the plugin's command tree:
```js
const info = require("./info");
import { messages } from "./index.js";
fooFrobberCommands.add(new Command({
definition: ["frobnicate <type>", "Do frobnications", (yargs) => {
@@ -600,15 +640,11 @@ Note that messages sent from clusterioctl needs to have `"control-controller"` a
To have the command tree become part of clusterioctl it needs to be added to the rootCommand tree in `addCommands` callback of the Ctl plugin:
```js
const { BaseCtlPlugin } = require("@clusterio/ctl");
import { BaseCtlPlugin } from "@clusterio/ctl";
class CtlPlugin extends BaseCtlPlugin {
export class CtlPlugin extends BaseCtlPlugin {
async addCommands(rootCommand) {
rootCommand.add(fooFrobberCommands);
}
}
module.exports = {
CtlPlugin,
}
```
+2 -2
View File
@@ -418,7 +418,7 @@ export default defineConfig([{
...globals.mocha,
},
sourceType: "commonjs",
sourceType: "module",
parserOptions: {},
},
@@ -489,7 +489,7 @@ export default defineConfig([{
"node/shebang": "off",
},
}, {
files: ["packages/create/templates/**/*.{t,j}s?(x)"],
files: ["packages/create/templates/**/*.?(c){t,j}s?(x)"],
"rules": {
"node/no-missing-require": "off",
+1
View File
@@ -3,6 +3,7 @@
"private": true,
"files": [],
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
"type": "module",
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch",
+13 -12
View File
@@ -27,12 +27,12 @@ import jwt from "jsonwebtoken";
// homebrew modules
import * as lib from "@clusterio/lib";
const { ConsoleTransport, levels, logger } = lib;
import { ConsoleTransport, levels, logger } from "@clusterio/lib";
import Controller from "./src/Controller";
import UserManager from "./src/UserManager";
import UserRecord from "./src/UserRecord";
import { version } from "./package.json";
import Controller from "./src/Controller.js";
import UserManager from "./src/UserManager.js";
import UserRecord from "./src/UserRecord.js";
import packageConfig from "./package.json" with { type: "json" };
// globals
let controller: Controller;
@@ -206,7 +206,7 @@ async function handleBootstrapCommand(
console.log(content);
} else {
logger.info(`Writing ${args.output}`);
await lib.safeOutputFile(args.output, content);
await lib.safeOutputFile(args.output, content, { mode: 0o600 });
}
}
}
@@ -216,7 +216,7 @@ async function handleCopyStaticCommand(
pluginInfos: lib.PluginNodeEnvInfo[],
) {
await fs.cp(
path.join(__dirname, "..", "web", "static"),
path.join(import.meta.dirname, "..", "web", "static"),
args.target,
{
recursive: true,
@@ -268,7 +268,7 @@ interface InitializeParameters {
async function initialize(): Promise<InitializeParameters> {
// argument parsing
const args = await yargs
const args = await yargs(process.argv.slice(2))
.scriptName("controller")
.usage("$0 <command> [options]")
.option("log-level", {
@@ -340,7 +340,7 @@ async function initialize(): Promise<InitializeParameters> {
})
.demandCommand(1, "You need to specify a command to run")
.strict()
.argv
.parse()
;
// Combined log stream of the whole cluster.
@@ -377,7 +377,7 @@ async function initialize(): Promise<InitializeParameters> {
let command = args._[0];
let shouldRun = false;
if (command === "run") {
logger.info(`Starting Clusterio controller ${version}`);
logger.info(`Starting Clusterio controller ${packageConfig.version}`);
if (args.recovery) {
logger.warn("Controller recovery mode enabled. Some features will be disabled.");
}
@@ -402,6 +402,7 @@ async function initialize(): Promise<InitializeParameters> {
logger.info("Loading Plugin info");
const pluginInfos = await lib.loadPluginInfos(pluginList);
lib.registerPluginMessages(pluginInfos);
lib.registerPluginPermissions(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
const controllerConfigPath = args.config;
@@ -426,7 +427,7 @@ async function initialize(): Promise<InitializeParameters> {
}
}
controllerConfig.set("controller.version", version); // Allows tracking last loaded version
controllerConfig.set("controller.version", packageConfig.version); // Allows tracking last loaded version
if (!controllerConfig.get("controller.auth_secret")) {
logger.info("Generating new controller authentication secret");
@@ -537,6 +538,6 @@ ${err.stack}`
});
}
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+14 -14
View File
@@ -1,17 +1,17 @@
import { bootstrap } from "./controller";
export { default as Controller } from "./src/Controller";
export { default as ControllerRouter } from "./src/ControllerRouter";
export { default as ControlConnection } from "./src/ControlConnection";
export { default as HostConnection } from "./src/HostConnection";
export { default as InstanceRecord } from "./src/InstanceRecord";
export { default as InstanceManager } from "./src/InstanceManager";
export { default as HostRecord } from "./src/HostRecord";
export { default as BaseControllerPlugin } from "./src/BaseControllerPlugin";
export { default as UserManager } from "./src/UserManager";
export { default as UserRecord } from "./src/UserRecord";
export { default as User } from "./src/User";
export { default as WsServer } from "./src/WsServer";
import { bootstrap } from "./controller.js";
export { default as Controller } from "./src/Controller.js";
export { default as ControllerRouter } from "./src/ControllerRouter.js";
export { default as ControlConnection } from "./src/ControlConnection.js";
export { default as HostConnection } from "./src/HostConnection.js";
export { default as InstanceRecord } from "./src/InstanceRecord.js";
export { default as InstanceManager } from "./src/InstanceManager.js";
export { default as HostRecord } from "./src/HostRecord.js";
export { default as BaseControllerPlugin } from "./src/BaseControllerPlugin.js";
export { default as UserManager } from "./src/UserManager.js";
export { default as UserRecord } from "./src/UserRecord.js";
export { default as User } from "./src/User.js";
export { default as WsServer } from "./src/WsServer.js";
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+1
View File
@@ -22,6 +22,7 @@
"access": "public"
},
"type": "module",
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --mode production",
+4 -4
View File
@@ -1,12 +1,12 @@
import path from "path";
import fs from "node:fs/promises";
import type Controller from "./Controller";
import type WsServerConnector from "./WsServerConnector";
import type Controller from "./Controller.js";
import type WsServerConnector from "./WsServerConnector.js";
import * as lib from "@clusterio/lib";
const { logger } = lib;
import * as routes from "./routes";
import { logger } from "@clusterio/lib";
import * as routes from "./routes.js";
/**
@@ -2,10 +2,10 @@ import type {
CollectorResult, Event, InstanceStatus, Logger,
ModPack, ModInfo, PlayerEvent, PluginNodeEnvInfo,
} from "@clusterio/lib";
import type Controller from "./Controller";
import type InstanceRecord from "./InstanceRecord";
import type ControlConnection from "./ControlConnection";
import type HostConnection from "./HostConnection";
import type Controller from "./Controller.js";
import type InstanceRecord from "./InstanceRecord.js";
import type ControlConnection from "./ControlConnection.js";
import type HostConnection from "./HostConnection.js";
/**
* Base class for controller plugins
+19 -10
View File
@@ -1,15 +1,15 @@
import type WsServerConnector from "./WsServerConnector";
import type WsServerConnector from "./WsServerConnector.js";
import events from "events";
import * as lib from "@clusterio/lib";
const { logFilter, logger } = lib;
import { logFilter, logger } from "@clusterio/lib";
import BaseConnection from "./BaseConnection";
import User from "./User";
import UserRecord from "./UserRecord";
import * as routes from "./routes";
import Controller from "./Controller";
import BaseConnection from "./BaseConnection.js";
import User from "./User.js";
import UserRecord from "./UserRecord.js";
import * as routes from "./routes.js";
import Controller from "./Controller.js";
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
@@ -128,6 +128,7 @@ export default class ControlConnection extends BaseConnection {
this.handle(lib.PluginListRequest, this.handlePluginListRequest.bind(this));
this.handle(lib.PluginUpdateRequest, this.handlePluginUpdateRequest.bind(this));
this.handle(lib.PluginInstallRequest, this.handlePluginInstallRequest.bind(this));
this.handle(lib.UpdateAllRequest, this.handleUpdateAllRequest.bind(this));
this.handle(lib.DebugDumpWsRequest, this.handleDebugDumpWsRequest.bind(this));
}
@@ -169,7 +170,7 @@ export default class ControlConnection extends BaseConnection {
}
if (typeof permission === "string") {
this.user.checkPermission(permission);
this.user.checkPermission(permission as lib.PermissionName);
return;
}
@@ -1307,6 +1308,16 @@ export default class ControlConnection extends BaseConnection {
return await lib.handlePluginInstall(request.pluginPackage);
}
async handleUpdateAllRequest(request: lib.UpdateAllRequest) {
if (!this._controller.config.get("controller.allow_remote_updates")) {
throw new lib.RequestError("Remote updates are disabled on this machine");
}
if (!this._controller.config.get("controller.allow_plugin_updates")) {
throw new lib.RequestError("Plugin updates are disabled on this machine");
}
return await lib.handleUpdateAll("@clusterio/controller", this._controller.pluginInfos);
}
async handlePluginListRequest(request: lib.PluginListRequest) {
return this._controller.pluginInfos.map(pluginInfo => lib.PluginDetails.fromNodeEnvInfo(
pluginInfo,
@@ -1325,5 +1336,3 @@ export default class ControlConnection extends BaseConnection {
this._controller.debugEvents.on("message", this.ws_dumper);
}
}
module.exports = ControlConnection;
+41 -30
View File
@@ -1,6 +1,6 @@
import type winston from "winston";
import { BlockList, type AddressInfo, isIPv4, isIPv6 } from "net";
import type { ControllerArgs } from "../controller";
import type { ControllerArgs } from "../controller.js";
import express, { type Request, type Response, type NextFunction, type Application } from "express";
import type { Static } from "@sinclair/typebox";
import finalhandler from "finalhandler";
@@ -14,22 +14,23 @@ import jwt from "jsonwebtoken";
import path from "path";
import semver from "semver";
import stream from "stream";
import { fileURLToPath, pathToFileURL } from "node:url";
import * as lib from "@clusterio/lib";
const { logger, Summary, Gauge } = lib;
import { logger, Summary, Gauge } from "@clusterio/lib";
import HttpCloser from "./HttpCloser";
import InstanceRecord from "./InstanceRecord";
import * as metrics from "./metrics";
import * as routes from "./routes";
import User from "./User";
import UserRecord from "./UserRecord";
import UserManager from "./UserManager";
import WsServer from "./WsServer";
import HostRecord from "./HostRecord";
import BaseControllerPlugin from "./BaseControllerPlugin";
import ControllerRouter from "./ControllerRouter";
import InstanceManager from "./InstanceManager";
import HttpCloser from "./HttpCloser.js";
import InstanceRecord from "./InstanceRecord.js";
import * as metrics from "./metrics.js";
import * as routes from "./routes.js";
import User from "./User.js";
import UserRecord from "./UserRecord.js";
import UserManager from "./UserManager.js";
import WsServer from "./WsServer.js";
import HostRecord from "./HostRecord.js";
import BaseControllerPlugin from "./BaseControllerPlugin.js";
import ControllerRouter from "./ControllerRouter.js";
import InstanceManager from "./InstanceManager.js";
const endpointDurationSummary = new Summary(
"clusterio_controller_http_endpoint_duration_seconds",
@@ -345,7 +346,7 @@ export default class Controller {
// Start webpack development server if enabled
if (args.dev || args.devPlugin) {
this._startDevServer(args);
await this._startDevServer(args);
}
this.config.on("fieldChanged", (field, curr, prev) => {
@@ -383,7 +384,7 @@ export default class Controller {
Controller.addAppRoutes(this.app, this.pluginInfos);
if (!args.dev) {
let manifestPath = path.join(__dirname, "..", "..", "web", "manifest.json");
let manifestPath = path.join(import.meta.dirname, "..", "..", "web", "manifest.json");
let manifest = await Controller.loadJsonObject(manifestPath);
if (!manifest["main.js"]) {
@@ -458,12 +459,13 @@ export default class Controller {
async _startDevServer(args: ControllerArgs) {
logger.warn("Webpack development mode enabled");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpack = (await import("webpack")).default;
const webpackDevMiddleware = (await import("webpack-dev-middleware")).default;
const webpackConfigs = [];
if (args.dev) {
webpackConfigs.push(require("../../../webpack.config")({})); // Path outside of build
// @ts-expect-error this file is run from a different folder
webpackConfigs.push((await import("../../../webpack.config.cjs")).default({})); // Path outside of build
}
if (args.devPlugin) {
let devPlugins = new Map();
@@ -472,7 +474,10 @@ export default class Controller {
if (!info) {
throw new lib.StartupError(`No plugin named ${name}`);
}
let config = require(path.posix.join(info.requirePath, "webpack.config"))({});
const webpackConfig = path.posix.join(info.requirePath, "webpack.config.cjs");
let config = (await import(
path.isAbsolute(webpackConfig) ? pathToFileURL(webpackConfig).href : webpackConfig
)).default({});
devPlugins.set(name, webpackConfigs.length);
webpackConfigs.push(config);
}
@@ -627,7 +632,9 @@ export default class Controller {
try {
// First check the clusterio version
const runningVersion = this.config.get("controller.version");
const packageJsonPath = require.resolve("@clusterio/controller/package.json");
const packageJsonPath = fileURLToPath(
import.meta.resolve("@clusterio/controller/package.json"),
);
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
if (runningVersion !== packageJson.version) {
this.config.restartRequired = true;
@@ -644,15 +651,14 @@ export default class Controller {
}
packageName = pluginInfo.npmPackage ?? pluginInfo.name;
const pluginPackageJsonPath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
const pluginPackageJson = JSON.parse(await fs.readFile(pluginPackageJsonPath, "utf8"));
const pluginPackageJson = JSON.parse(await fs.readFile(pluginInfo.packagePath, "utf8"));
if (pluginInfo.version !== pluginPackageJson.version) {
this.config.restartRequired = true;
return true;
}
}
} catch (err: any) {
logger.warn(`Failed to read package json for ${packageName}:\n${err.stack ?? err.message}`);
logger.warn(`Failed to read package.json for ${packageName}:\n${err.stack ?? err.message}`);
}
return false;
@@ -661,7 +667,7 @@ export default class Controller {
async checkRestartDowngrade() {
try {
const runningVersion = this.config.get("controller.version");
const packageJsonPath = require.resolve("@clusterio/controller/package.json");
const packageJsonPath = fileURLToPath(import.meta.resolve("@clusterio/controller/package.json"));
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
const installedVersion = packageJson.version;
@@ -902,7 +908,7 @@ export default class Controller {
// Set folder to serve static content from (the website)
const staticOptions = { immutable: true, maxAge: 1000 * 86400 * 365 };
app.use("/static",
express.static(path.join(__dirname, "..", "..", "web", "static"), staticOptions)
express.static(path.join(import.meta.dirname, "..", "..", "web", "static"), staticOptions)
);
app.use("/static", express.static("static", staticOptions)); // Used for data export files
@@ -1125,9 +1131,11 @@ export default class Controller {
for (let pluginInfo of this.pluginInfos) {
try {
let manifestPath = path.posix.join(pluginInfo.requirePath, "dist", "web", "manifest.json");
pluginInfo.manifest = await Controller.loadJsonObject(require.resolve(manifestPath), true);
pluginInfo.manifest = await Controller.loadJsonObject(import.meta.resolve(manifestPath), true);
} catch (err) {
logger.warn(`Unable to load dist/web/manifest.json for plugin ${pluginInfo.name}`);
if (lib.pluginNeedsWebBuild(pluginInfo)) {
logger.warn(`Unable to load dist/web/manifest.json for plugin ${pluginInfo.name}`);
}
}
if (!this.config.get(`${pluginInfo.name}.load_plugin`)) {
@@ -1238,11 +1246,14 @@ export default class Controller {
mainBundle = res.app.locals.mainBundle;
} else {
let stats = res.locals.webpack.devMiddleware.stats.stats[0];
mainBundle = stats.toJson().assetsByChunkName["main"];
mainBundle = stats.toJson().assetsByChunkName["main"][0];
}
mainBundle = routes.stripStaticPrefix(mainBundle);
fs.readFile(path.join(__dirname, "..", "..", "..", "web", "index.html"), "utf8").then((content) => {
fs.readFile(
path.join(import.meta.dirname, "..", "..", "..", "web", "index.html"),
"utf8",
).then((content) => {
res.type("text/html");
res.send(content
.replace(/__CLUSTER_NAME__/g, res.app.locals.controller.config.get("controller.name"))
+5 -5
View File
@@ -1,10 +1,10 @@
import type Controller from "./Controller";
import type BaseConnection from "./BaseConnection";
import type Controller from "./Controller.js";
import type BaseConnection from "./BaseConnection.js";
import * as lib from "@clusterio/lib";
import HostConnection from "./HostConnection";
import ControlConnection from "./ControlConnection";
const { logger } = lib;
import { logger } from "@clusterio/lib";
import HostConnection from "./HostConnection.js";
import ControlConnection from "./ControlConnection.js";
export default class ControllerRouter {
constructor(
+6 -6
View File
@@ -1,12 +1,12 @@
import type Controller from "./Controller";
import type WsServerConnector from "./WsServerConnector";
import type Controller from "./Controller.js";
import type WsServerConnector from "./WsServerConnector.js";
import * as lib from "@clusterio/lib";
const { logger } = lib;
import { logger } from "@clusterio/lib";
import BaseConnection from "./BaseConnection";
import InstanceRecord from "./InstanceRecord";
import HostRecord from "./HostRecord";
import BaseConnection from "./BaseConnection.js";
import InstanceRecord from "./InstanceRecord.js";
import HostRecord from "./HostRecord.js";
/**
+3 -3
View File
@@ -1,8 +1,8 @@
import * as lib from "@clusterio/lib";
import type Controller from "./Controller";
import type HostConnection from "./HostConnection";
import InstanceRecord from "./InstanceRecord";
import type Controller from "./Controller.js";
import type HostConnection from "./HostConnection.js";
import InstanceRecord from "./InstanceRecord.js";
export default class InstanceManager {
private static readonly DefaultFactorioSettings = {
+3 -3
View File
@@ -1,10 +1,10 @@
import {
IUser, Role, SubscribableDatastore,
PermissionError, permissions,
PermissionError, PermissionName, permissions,
} from "@clusterio/lib";
import UserRecord from "./UserRecord";
import UserRecord from "./UserRecord.js";
import { Static } from "@sinclair/typebox";
/**
@@ -126,7 +126,7 @@ export default class User extends UserRecord implements IUser {
this.saveRecord();
}
checkPermission(permission: string) {
checkPermission(permission: PermissionName) {
if (!permissions.has(permission)) {
throw new Error(`permission ${permission} does not exist`);
}
+2 -2
View File
@@ -3,8 +3,8 @@ import jwt from "jsonwebtoken";
import * as lib from "@clusterio/lib";
import User from "./User";
import UserRecord from "./UserRecord";
import User from "./User.js";
import UserRecord from "./UserRecord.js";
/**
* Access layer for users on the controller.
+12 -11
View File
@@ -1,18 +1,18 @@
import type { IncomingMessage } from "http";
import type { Duplex } from "stream";
import type Controller from "./Controller";
import type Controller from "./Controller.js";
import jwt from "jsonwebtoken";
import WebSocket, { WebSocketServer } from "ws";
import * as lib from "@clusterio/lib";
const { logger } = lib;
import { logger } from "@clusterio/lib";
import ControlConnection from "./ControlConnection";
import HostConnection from "./HostConnection";
import WsServerConnector from "./WsServerConnector";
import ControlConnection from "./ControlConnection.js";
import HostConnection from "./HostConnection.js";
import WsServerConnector from "./WsServerConnector.js";
import { version as packageVersion } from "../package.json";
import packageConfig from "../package.json" with { type: "json" };
const wsMessageCounter = new lib.Counter(
"clusterio_controller_websocket_message_total",
@@ -45,12 +45,12 @@ export default class WsServer {
sessionAud: string = `session-${Date.now()}`;
nextSessionId: number = 1;
nextControlId: number = 1;
wss: WebSocket.Server;
wss: WebSocketServer;
constructor(controller: Controller) {
this.controller = controller;
this.wss = new WebSocket.Server({
this.wss = new WebSocketServer({
noServer: true,
path: "/api/socket",
});
@@ -139,7 +139,7 @@ export default class WsServer {
loadedPlugins[name] = plugin.info.version;
}
socket.send(JSON.stringify(new lib.MessageHello(new lib.HelloData(packageVersion, loadedPlugins))));
socket.send(JSON.stringify(new lib.MessageHello(new lib.HelloData(packageConfig.version, loadedPlugins))));
this.attachHandler(socket, req);
}
@@ -287,10 +287,11 @@ ${err.stack}`
logger.info(
`WsServer | registered host ${data.id} using version ${data.version}`
);
if (data.version !== packageVersion) {
if (data.version !== packageConfig.version) {
logger.warn(
`Host ${data.id} connected using version ${data.version} which does not match the ` +
`version of the controller is currently running (${packageVersion}). It may not work as expected.`
`version of the controller is currently running (${packageConfig.version}). ` +
"It may not work as expected."
);
}
+8 -5
View File
@@ -1,10 +1,10 @@
import type WebSocket from "ws";
import { type WebSocket } from "ws";
import events from "events";
import assert from "assert/strict";
import * as lib from "@clusterio/lib";
const { logger } = lib;
import { logger } from "@clusterio/lib";
/**
@@ -49,7 +49,7 @@ export default class WsServerConnector extends lib.WebSocketBaseConnector {
* @param account - account data to provide to control connection
*/
ready(
socket: WebSocket.WebSocket,
socket: WebSocket,
src: lib.Address,
sessionToken: string,
account: lib.AccountDetails | undefined
@@ -80,7 +80,7 @@ export default class WsServerConnector extends lib.WebSocketBaseConnector {
* @param lastSeq - The last message the client received.
*/
continue(
socket: WebSocket.WebSocket,
socket: WebSocket,
lastSeq: number
) {
@@ -106,6 +106,9 @@ export default class WsServerConnector extends lib.WebSocketBaseConnector {
this._state = "connected";
this._attachSocketHandlers();
this._dropSendBufferSeq(lastSeq);
logger.verbose(
`Connector | resuming session with ${this.dst}, resending ${this._sendBuffer.length} buffered messages`
);
for (let message of this._sendBuffer) {
this._socket.send(JSON.stringify(message));
}
@@ -113,7 +116,7 @@ export default class WsServerConnector extends lib.WebSocketBaseConnector {
}
_timedOut() {
logger.verbose("Connector | Connection timed out");
logger.verbose(`Connector | Connection to ${this.dst} timed out`);
this._close();
}
+4 -4
View File
@@ -1,5 +1,5 @@
import type { Application, Request, Response } from "express";
import Controller from "./Controller";
import Controller from "./Controller.js";
import busboy from "busboy";
import crypto from "crypto";
@@ -12,7 +12,7 @@ import nodeStream from "stream";
import util from "util";
import * as lib from "@clusterio/lib";
const { logger } = lib;
import { logger } from "@clusterio/lib";
const finished = util.promisify(nodeStream.finished);
@@ -126,13 +126,13 @@ function getPlugins(req: Request, res: Response) {
let devPlugins = req.app.locals.devPlugins;
if (devPlugins && devPlugins.has(name)) {
let stats = res.locals.webpack.devMiddleware.stats.stats[devPlugins.get(name)!];
web.main = stats.toJson().assetsByChunkName[name];
web.main = stats.toJson().assetsByChunkName[name][0];
} else if (pluginInfo.manifest) {
web.main = pluginInfo.manifest[`${pluginInfo.name}.js`];
if (!web.main) {
web.error = `Missing ${pluginInfo.name}.js entry in manifest.json`;
}
} else {
} else if (lib.pluginNeedsWebBuild(pluginInfo)) {
web.error = "Missing dist/web/manifest.json";
}
if (web.main === "remoteEntry.js") {
File renamed without changes.
+20 -21
View File
@@ -1,20 +1,19 @@
#!/usr/bin/env node
"use strict";
const child_process = require("child_process");
const fs = require("node:fs/promises");
const inquirer = require("inquirer");
const os = require("os");
const path = require("path");
const stream = require("stream");
const util = require("util");
const yargs = require("yargs");
import child_process from "node:child_process";
import fs from "node:fs/promises";
import inquirer from "inquirer";
import os from "node:os";
import path from "node:path";
import stream from "node:stream";
import util from "node:util";
import yargs from "yargs";
const { levels, logger, setLogLevel } = require("./logging");
const { copyPluginTemplates } = require("./template");
import { levels, logger, setLogLevel } from "./logging.js";
import { copyPluginTemplates } from "./template.js";
let dev = false;
const scriptExt = process.platform === "win32" ? ".cmd" : "";
const { escapeArg } = require("./escape_arg");
import { escapeArg } from "./escape_arg.js";
// I hate this, there is a bug with rl.close on windows that was meant to have been fixed in Node14.4
// See nodejs/node#21771 and nodejs/node#30701 for the apparent fix that was applied
@@ -147,21 +146,21 @@ async function execFile(cmd, args) {
async function execController(args) {
if (dev) {
return await execFile("node", [path.join(__dirname, "..", "controller"), ...args]);
return await execFile("node", [path.join(import.meta.dirname, "..", "controller"), ...args]);
}
return await execFile(path.join("node_modules", ".bin", `clusteriocontroller${scriptExt}`), args);
}
async function execHost(args) {
if (dev) {
return await execFile("node", [path.join(__dirname, "..", "host"), ...args]);
return await execFile("node", [path.join(import.meta.dirname, "..", "host"), ...args]);
}
return await execFile(path.join("node_modules", ".bin", `clusteriohost${scriptExt}`), args);
}
async function execCtl(args) {
if (dev) {
return await execFile("node", [path.join(__dirname, "..", "ctl"), ...args]);
return await execFile("node", [path.join(import.meta.dirname, "..", "ctl"), ...args]);
}
return await execFile(path.join("node_modules", ".bin", `clusterioctl${scriptExt}`), args);
}
@@ -235,7 +234,7 @@ async function migrateRename(args) {
if (await pathExists(source) && !await pathExists(destination)) {
await safeOutputFile(destination, JSON.stringify(
renameConfig(JSON.parse(await fs.readFile(source))), null, "\t"
));
), { mode: 0o600 });
logger.info(`Migrated ${source} to ${destination}`);
}
}
@@ -408,7 +407,7 @@ async function installClusterio(mode, plugins) {
for (let plugin of plugins) {
if (!pluginList.has(plugin)) {
let pluginInfo = require(require.resolve(plugin, { paths: [process.cwd()] })).plugin;
let pluginInfo = (await import(plugin)).plugin;
pluginList.set(pluginInfo.name, plugin);
}
}
@@ -750,7 +749,7 @@ async function inquirerMissingArgs(args) {
}
async function main() {
let args = yargs
let yarg = yargs(process.argv.slice(2))
.option("log-level", {
nargs: 1, describe: "Log level to print to stdout", default: "info",
choices: ["none"].concat(Object.keys(levels)), type: "string",
@@ -805,14 +804,14 @@ async function main() {
;
if (process.platform === "linux") {
args = args
yarg = yarg
.option("allow-install-as-root", {
nargs: 0, describe: "(Linux only) Allow installing as root (not recommended)", type: "boolean",
})
;
}
args = args.argv;
const args = yarg.parse();
setLogLevel(args.logLevel === "none" ? -1 : levels[args.logLevel]);
dev = args.dev;
@@ -900,7 +899,7 @@ async function main() {
/* eslint-enable no-console */
}
if (module === require.main) {
if (import.meta.main) {
main().catch(err => {
if (err instanceof InstallError) {
logger.error(err.message);
+1 -7
View File
@@ -1,6 +1,4 @@
"use strict";
function escapeArg(arg) {
export function escapeArg(arg) {
// When shell: true is used execFile does not escaping of arguments, they are
// simply joined by space and then passed to the shell.
@@ -56,7 +54,3 @@ function escapeArg(arg) {
}
return `"${arg.replace(/[$`"\\]/g, "\\$&")}"`;
}
module.exports = {
escapeArg,
};
+7 -12
View File
@@ -1,8 +1,7 @@
/* eslint-disable no-console */
"use strict";
const chalk = require("chalk");
import chalk from "chalk";
const levels = Object.freeze({
export const levels = Object.freeze({
fatal: 0,
error: 1,
warn: 2,
@@ -11,7 +10,7 @@ const levels = Object.freeze({
verbose: 6,
});
const colors = {
export const colors = {
fatal: "inverse red",
error: "bold redBright",
warn: "bold yellowBright",
@@ -21,7 +20,7 @@ const colors = {
};
let maxLogLevel = levels.info;
const logger = {};
export const logger = {};
for (let [name, level] of Object.entries(levels)) {
// eslint-disable-next-line no-loop-func
logger[name] = message => {
@@ -42,10 +41,6 @@ for (let [name, level] of Object.entries(levels)) {
};
}
module.exports = {
colors,
levels,
setLogLevel: (level) => { maxLogLevel = level; },
logger,
};
export function setLogLevel(level) {
maxLogLevel = level;
}
+1
View File
@@ -22,6 +22,7 @@
"access": "public"
},
"type": "module",
"main": "create.js",
"bin": {
"clusteriocreate": "./create.js"
+11 -13
View File
@@ -1,9 +1,8 @@
"use strict";
const { logger } = require("./logging");
const fs = require("node:fs/promises");
const path = require("path");
import { logger } from "./logging.js";
import fs from "node:fs/promises";
import path from "node:path";
const _package = require("./package.json");
import _package from "./package.json" with { type: "json" };
async function copyTemplateFile(src, dst, properties) {
logger.verbose(`Writing ${dst} from template ${src}`);
@@ -79,7 +78,7 @@ async function copyTemplateFile(src, dst, properties) {
}
}
async function copyPluginTemplates(pluginName, templates) {
export async function copyPluginTemplates(pluginName, templates) {
logger.info(`Please wait, coping templates for ${templates.join(", ")}`);
const files = new Map();
const prepare = [];
@@ -96,8 +95,11 @@ async function copyPluginTemplates(pluginName, templates) {
// Get the file extension and path to the templates
const ext = javascriptOnly ? "js" : "ts";
const templatePath = path.resolve(__dirname, javascriptOnly ? "./templates/plugin-js" : "./templates/plugin-ts");
const commonPath = path.resolve(__dirname, "./templates/common");
const templatePath = path.resolve(
import.meta.dirname,
javascriptOnly ? "./templates/plugin-js" : "./templates/plugin-ts"
);
const commonPath = path.resolve(import.meta.dirname, "./templates/common");
// Files included in all templates
files.set(".gitignore", path.join(commonPath, "template.gitignore"));
@@ -119,7 +121,7 @@ async function copyPluginTemplates(pluginName, templates) {
// Files and dependences to support webpack
if (webpack) {
prepare.push("webpack-cli --mode production");
files.set("webpack.config.js", path.join(commonPath, "webpack.config.js"));
files.set("webpack.config.cjs", path.join(commonPath, "webpack.config.cjs"));
if (templates.includes("web")) {
files.set(`web/index.${ext}x`, path.join(templatePath, `web/plugin.${ext}x`));
pluginContexts += 1;
@@ -202,7 +204,3 @@ async function copyPluginTemplates(pluginName, templates) {
await Promise.all(writes);
logger.info("Successfully wrote all template files");
}
module.exports = {
copyPluginTemplates,
};
@@ -2,6 +2,7 @@
"name": "__plugin_name__",
"version": "0.1.0",
"description": "Example Description. Package. Change me in package.json",
"type": "module",
//%if typescript
"main": "dist/node/index.js",
//%endif
@@ -1,19 +1,18 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseControllerPlugin } = require("@clusterio/controller");
import * as lib from "@clusterio/lib";
import { BaseControllerPlugin } from "@clusterio/controller";
//%if multi_context // Messages requires multi context
const {
import {
PluginExampleEvent, PluginExampleRequest,
//%endif
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Messages requires multi context
} = require("./messages");
} from "./messages.js";
//%endif
class ControllerPlugin extends BaseControllerPlugin {
export class ControllerPlugin extends BaseControllerPlugin {
//%if controller & web // Subscribing requires web content and the controller
exampleDatabase;
storageDirty = false;
@@ -79,7 +78,3 @@ class ControllerPlugin extends BaseControllerPlugin {
}
//%endif
}
module.exports = {
ControllerPlugin,
};
+4 -9
View File
@@ -1,8 +1,7 @@
"use strict";
const { BaseCtlPlugin } = require("@clusterio/ctl");
const { CommandTree, Command } = require("@clusterio/lib");
import { BaseCtlPlugin } from "@clusterio/ctl";
import { CommandTree, Command } from "@clusterio/lib";
//%// We do not check for multi context here because it doesn't make sense to have a ctl without messages
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
/* eslint-disable no-console */
const pluginCommands = new CommandTree({
@@ -21,12 +20,8 @@ pluginCommands.add(new Command({
}));
/* eslint-enable no-console */
class CtlPlugin extends BaseCtlPlugin {
export class CtlPlugin extends BaseCtlPlugin {
async addCommands(rootCommand) {
rootCommand.add(pluginCommands);
}
}
module.exports = {
CtlPlugin,
};
+4 -9
View File
@@ -1,11 +1,10 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseHostPlugin } = require("@clusterio/host");
import * as lib from "@clusterio/lib";
import { BaseHostPlugin } from "@clusterio/host";
//%if multi_context
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
//%endif
class HostPlugin extends BaseHostPlugin {
export class HostPlugin extends BaseHostPlugin {
//%if multi_context
async init() {
this.host.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
@@ -35,7 +34,3 @@ class HostPlugin extends BaseHostPlugin {
}
//%endif
}
module.exports = {
HostPlugin,
};
+44 -44
View File
@@ -1,42 +1,9 @@
"use strict";
const lib = require("@clusterio/lib");
import * as lib from "@clusterio/lib";
//%if multi_context
const Messages = require("./messages");
lib.definePermission({
name: "__plugin_name__.example.permission.event",
title: "Example permission event",
description: "Example Description. Event. Change me in index.js",
grantByDefault: true,
});
lib.definePermission({
name: "__plugin_name__.example.permission.request",
title: "Example permission request",
description: "Example Description. Request. Change me in index.js",
grantByDefault: true,
});
//%endif
//%if controller & web // Subscribing requires web content and the controller
lib.definePermission({
name: "__plugin_name__.example.permission.subscribe",
title: "Example permission subscribe",
description: "Example Description. Subscribe. Change me in index.js",
grantByDefault: true,
});
//%endif
//%if web
lib.definePermission({
name: "__plugin_name__.page.view",
title: "Example page view permission",
description: "Example Description. View. Change me in index.js",
grantByDefault: true,
});
import Messages from "./messages.js";
//%endif
const plugin = {
export const plugin = {
name: "__plugin_name__",
title: "__plugin_name__",
description: "Example Description. Plugin. Change me in index.js",
@@ -44,7 +11,7 @@ const plugin = {
//%endif
//%if controller
controllerEntrypoint: "./dist/node/controller",
controllerEntrypoint: "./dist/node/controller.js",
//%endif
//%if controller & config
controllerConfigFields: {
@@ -60,7 +27,7 @@ const plugin = {
//%endif
//%if host
hostEntrypoint: "./dist/node/host",
hostEntrypoint: "./dist/node/host.js",
//%endif
//%if host & config
hostConfigFields: {
@@ -76,7 +43,7 @@ const plugin = {
//%endif
//%if instance | module // Modules load an empty instance plugin
instanceEntrypoint: "./dist/node/instance",
instanceEntrypoint: "./dist/node/instance.js",
//%endif
//%if instance & config
instanceConfigFields: {
@@ -92,7 +59,7 @@ const plugin = {
//%endif
//%if ctl
ctlEntrypoint: "./dist/node/ctl",
ctlEntrypoint: "./dist/node/ctl.js",
//%endif
//%if ctl & config
controlConfigFields: {
@@ -116,6 +83,43 @@ const plugin = {
//%if multi_context // Subscribing requires multi context
],
//%endif
//%if multi_context | web
permissions: [
//%endif
//%if multi_context
{
name: "__plugin_name__.example.permission.event",
title: "Example permission event",
description: "Example Description. Event. Change me in index.js",
grantByDefault: true,
},
{
name: "__plugin_name__.example.permission.request",
title: "Example permission request",
description: "Example Description. Request. Change me in index.js",
grantByDefault: true,
},
//%endif
//%if controller & web // Subscribing requires web content and the controller
{
name: "__plugin_name__.example.permission.subscribe",
title: "Example permission subscribe",
description: "Example Description. Subscribe. Change me in index.js",
grantByDefault: true,
},
//%endif
//%if web
{
name: "__plugin_name__.page.view",
title: "Example page view permission",
description: "Example Description. View. Change me in index.js",
grantByDefault: true,
},
//%endif
//%if multi_context | web
],
//%endif
//%if web // Web content template has an example route which is the plugin name
webEntrypoint: "./web",
@@ -124,7 +128,3 @@ const plugin = {
],
//%endif
};
module.exports = {
plugin,
};
@@ -1,11 +1,10 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseInstancePlugin } = require("@clusterio/host");
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host";
//%if multi_context
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
//%endif
class InstancePlugin extends BaseInstancePlugin {
export class InstancePlugin extends BaseInstancePlugin {
//%if multi_context | module
async init() {
//%if multi_context
@@ -68,7 +67,3 @@ class InstancePlugin extends BaseInstancePlugin {
}
//%endif
}
module.exports = {
InstancePlugin,
};
@@ -1,11 +1,6 @@
"use strict";
const { BaseInstancePlugin } = require("@clusterio/host");
import { BaseInstancePlugin } from "@clusterio/host";
class InstancePlugin extends BaseInstancePlugin {
export class InstancePlugin extends BaseInstancePlugin {
// This class is empty because an instance plugin must be defined for a module to be injected
// This requirement may change in the future to allow for standalone modules
}
module.exports = {
InstancePlugin,
};
@@ -1,8 +1,7 @@
"use strict";
const { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } = require("@clusterio/lib");
const { Type } = require("@sinclair/typebox");
import { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } from "@clusterio/lib";
import { Type } from "@sinclair/typebox";
class PluginExampleEvent {
export class PluginExampleEvent {
static type = "event";
static src = ["host", "control"];
static dst = ["controller", "host", "instance"];
@@ -24,7 +23,7 @@ class PluginExampleEvent {
}
}
class PluginExampleRequest {
export class PluginExampleRequest {
static type = "request";
static src = ["host", "control"];
static dst = ["controller", "host", "instance"];
@@ -52,7 +51,7 @@ class PluginExampleRequest {
}
//%if controller & web // Subscribing requires web content and the controller
class ExampleSubscribableValue {
export class ExampleSubscribableValue {
constructor(id, updatedAtMs, isDeleted) {
this.id = id;
this.updatedAtMs = updatedAtMs;
@@ -70,7 +69,7 @@ class ExampleSubscribableValue {
}
}
class ExampleSubscribableUpdate {
export class ExampleSubscribableUpdate {
static type = "event";
static src = "controller";
static dst = "control";
@@ -90,12 +89,3 @@ class ExampleSubscribableUpdate {
}
}
//%endif
module.exports = {
PluginExampleEvent,
PluginExampleRequest,
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableValue,
ExampleSubscribableUpdate,
//%endif
};
@@ -21,7 +21,7 @@ import {
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Messages requires multi context
} from "../messages";
} from "../messages.js";
//%endif
import * as lib from "@clusterio/lib";
@@ -43,7 +43,7 @@ function MyTemplatePage() {
export class WebPlugin extends BaseWebPlugin {
//%if controller // Subscribing requires web content and the controller
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
subscribableData = new lib.MapSubscriber(ExampleSubscribableUpdate, this.control);
//%endif
async init() {
@@ -57,7 +57,7 @@ export class WebPlugin extends BaseWebPlugin {
permission: "__plugin_name__.example.permission.subscribe",
//%endif
//%if !controller
permission: "__plugin_name__.example.permission.view",
permission: "__plugin_name__.page.view",
//%endif
content: <MyTemplatePage/>,
},
@@ -9,7 +9,7 @@ import {
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Messages requires multi context
} from "./messages";
} from "./messages.js";
//%endif
export class ControllerPlugin extends BaseControllerPlugin {
+1 -1
View File
@@ -1,7 +1,7 @@
import { BaseCtlPlugin, type Control } from "@clusterio/ctl";
import { CommandTree, Command } from "@clusterio/lib";
//%// We do not check for multi context here because it doesn't make sense to have a ctl without messages
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
/* eslint-disable no-console */
const pluginCommands = new CommandTree({
+1 -1
View File
@@ -1,7 +1,7 @@
import * as lib from "@clusterio/lib";
import { BaseHostPlugin } from "@clusterio/host";
//%if multi_context
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
//%endif
export class HostPlugin extends BaseHostPlugin {
+60 -39
View File
@@ -1,40 +1,8 @@
import * as lib from "@clusterio/lib";
//%if multi_context
import * as Messages from "./messages";
lib.definePermission({
name: "__plugin_name__.example.permission.event",
title: "Example permission event",
description: "Example Description. Event. Change me in index.ts",
grantByDefault: true,
});
lib.definePermission({
name: "__plugin_name__.example.permission.request",
title: "Example permission request",
description: "Example Description. Request. Change me in index.ts",
grantByDefault: true,
});
import * as Messages from "./messages.js";
//%endif
//%if controller & web // Subscribing requires web content and the controller
lib.definePermission({
name: "__plugin_name__.example.permission.subscribe",
title: "Example permission subscribe",
description: "Example Description. Subscribe. Change me in index.ts",
grantByDefault: true,
});
//%endif
//%if web
lib.definePermission({
name: "__plugin_name__.page.view",
title: "Example page view permission",
description: "Example Description. View. Change me in index.ts",
grantByDefault: true,
});
//%endif
//%if config
//%if config | multi_context | web
declare module "@clusterio/lib" {
//%endif
@@ -58,7 +26,23 @@ declare module "@clusterio/lib" {
"__plugin_name__.myControlField": string;
}
//%endif
//%if config
//%if multi_context | web
export interface Permissions {
//%endif
//%if multi_context
"__plugin_name__.example.permission.event": never;
"__plugin_name__.example.permission.request": never;
//%endif
//%if controller & web // Subscribing requires web content and the controller
"__plugin_name__.example.permission.subscribe": never;
//%endif
//%if web
"__plugin_name__.page.view": never;
//%endif
//%if multi_context | web
}
//%endif
//%if config | multi_context | web
}
//%endif
@@ -71,7 +55,7 @@ export const plugin: lib.PluginDeclaration = {
//%endif
//%if controller
controllerEntrypoint: "./dist/node/controller",
controllerEntrypoint: "./dist/node/controller.js",
//%endif
//%if controller & config
controllerConfigFields: {
@@ -87,7 +71,7 @@ export const plugin: lib.PluginDeclaration = {
//%endif
//%if host
hostEntrypoint: "./dist/node/host",
hostEntrypoint: "./dist/node/host.js",
//%endif
//%if host & config
hostConfigFields: {
@@ -103,7 +87,7 @@ export const plugin: lib.PluginDeclaration = {
//%endif
//%if instance | module // Modules load an empty instance plugin
instanceEntrypoint: "./dist/node/instance",
instanceEntrypoint: "./dist/node/instance.js",
//%endif
//%if instance & config
instanceConfigFields: {
@@ -119,7 +103,7 @@ export const plugin: lib.PluginDeclaration = {
//%endif
//%if ctl
ctlEntrypoint: "./dist/node/ctl",
ctlEntrypoint: "./dist/node/ctl.js",
//%endif
//%if ctl & config
controlConfigFields: {
@@ -143,6 +127,43 @@ export const plugin: lib.PluginDeclaration = {
//%if multi_context // Subscribing requires multi context
],
//%endif
//%if multi_context | web
permissions: [
//%endif
//%if multi_context
{
name: "__plugin_name__.example.permission.event",
title: "Example permission event",
description: "Example Description. Event. Change me in index.ts",
grantByDefault: true,
},
{
name: "__plugin_name__.example.permission.request",
title: "Example permission request",
description: "Example Description. Request. Change me in index.ts",
grantByDefault: true,
},
//%endif
//%if controller & web // Subscribing requires web content and the controller
{
name: "__plugin_name__.example.permission.subscribe",
title: "Example permission subscribe",
description: "Example Description. Subscribe. Change me in index.ts",
grantByDefault: true,
},
//%endif
//%if web
{
name: "__plugin_name__.page.view",
title: "Example page view permission",
description: "Example Description. View. Change me in index.ts",
grantByDefault: true,
},
//%endif
//%if multi_context | web
],
//%endif
//%if web // Web content template has an example route which is the plugin name
webEntrypoint: "./web",
@@ -1,7 +1,7 @@
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host";
//%if multi_context
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
import { PluginExampleEvent, PluginExampleRequest } from "./messages.js";
//%endif
//%if module
@@ -9,5 +9,5 @@
"noEmitOnError": true,
"outDir": "${configDir}/dist/browser",
},
"include": [ "web/**/*.tsx", "web/**/*.ts", "messages.ts", "package.json" ],
"include": [ "web/**/*.tsx", "web/**/*.ts", "index.ts", "messages.ts", "package.json" ],
}
@@ -21,7 +21,7 @@ import {
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Messages requires multi context
} from "../messages";
} from "../messages.js";
//%endif
import * as lib from "@clusterio/lib";
@@ -43,7 +43,7 @@ function MyTemplatePage() {
export class WebPlugin extends BaseWebPlugin {
//%if controller // Subscribing requires web content and the controller
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
subscribableData = new lib.MapSubscriber(ExampleSubscribableUpdate, this.control);
//%endif
async init() {
@@ -57,7 +57,7 @@ export class WebPlugin extends BaseWebPlugin {
permission: "__plugin_name__.example.permission.subscribe",
//%endif
//%if !controller
permission: "__plugin_name__.example.permission.view",
permission: "__plugin_name__.page.view",
//%endif
content: <MyTemplatePage/>,
},
+7 -6
View File
@@ -7,16 +7,16 @@
import fs from "node:fs/promises";
import yargs, { type Argv } from "yargs";
import path from "path";
import { version } from "./package.json";
import packageConfig from "./package.json" with { type: "json" };
import { strict as assert } from "assert";
// Reduce startup time by lazy compiling schemas.
(global as any).lazySchemaCompilation = true;
import "./src/set_lazy_schema_compliation.js";
import * as lib from "@clusterio/lib";
import { ConsoleTransport, levels, logger } from "@clusterio/lib";
import * as commands from "./src/commands";
import BaseCtlPlugin from "./src/BaseCtlPlugin";
import * as commands from "./src/commands.js";
import BaseCtlPlugin from "./src/BaseCtlPlugin.js";
/**
@@ -37,7 +37,7 @@ class ControlConnector extends lib.WebSocketClientConnector {
new lib.MessageRegisterControl(
new lib.RegisterControlData(
this._token,
version,
packageConfig.version,
)
)
);
@@ -108,6 +108,7 @@ export class Control extends lib.Link {
async function loadPlugins(pluginList: Map<string, string>) {
let pluginInfos = await lib.loadPluginInfos(pluginList);
lib.registerPluginMessages(pluginInfos);
lib.registerPluginPermissions(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
let ctlPlugins = new Map<string, BaseCtlPlugin>();
@@ -377,6 +378,6 @@ ${err.stack}`
});
}
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+4 -4
View File
@@ -1,7 +1,7 @@
import { bootstrap } from "./ctl";
export * from "./ctl";
export { default as BaseCtlPlugin } from "./src/BaseCtlPlugin";
import { bootstrap } from "./ctl.js";
export * from "./ctl.js";
export { default as BaseCtlPlugin } from "./src/BaseCtlPlugin.js";
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+1
View File
@@ -22,6 +22,7 @@
"access": "public"
},
"type": "module",
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build",
+11 -11
View File
@@ -1,17 +1,17 @@
import type { Argv } from "yargs";
import * as lib from "@clusterio/lib";
import type BaseCtlPlugin from "./BaseCtlPlugin";
import { controllerCommands } from "./commands_controller";
import { hostCommands } from "./commands_host";
import { instanceCommands } from "./commands_instance";
import { modPackCommands } from "./commands_mod_pack";
import { modCommands } from "./commands_mod";
import { permissionCommands } from "./commands_permission";
import { roleCommands } from "./commands_role";
import { userCommands } from "./commands_user";
import { logCommands } from "./commands_log";
import { debugCommands } from "./commands_debug";
import type BaseCtlPlugin from "./BaseCtlPlugin.js";
import { controllerCommands } from "./commands_controller.js";
import { hostCommands } from "./commands_host.js";
import { instanceCommands } from "./commands_instance.js";
import { modPackCommands } from "./commands_mod_pack.js";
import { modCommands } from "./commands_mod.js";
import { permissionCommands } from "./commands_permission.js";
import { roleCommands } from "./commands_role.js";
import { userCommands } from "./commands_user.js";
import { logCommands } from "./commands_log.js";
import { debugCommands } from "./commands_debug.js";
export async function registerCommands(ctlPlugins: Map<string, BaseCtlPlugin>, yargs: Argv) {
const rootCommands = new lib.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
+10 -5
View File
@@ -5,9 +5,9 @@ import os from "os";
import child_process from "child_process";
import * as lib from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
@@ -28,9 +28,14 @@ controllerCommands.add(new lib.Command({
controllerCommands.add(new lib.Command({
definition: ["update", "Update the controller", (yargs) => {
yargs.option("restart", { alias: "r", type: "boolean", description: "Restart after update" });
yargs.option("all", { alias: "a", type: "boolean", description: "Also update all plugins" });
}],
handler: async function(args: { restart: boolean }, control: Control) {
await control.send(new lib.ControllerUpdateRequest());
handler: async function(args: { restart: boolean, all: boolean }, control: Control) {
if (args.all) {
await control.send(new lib.UpdateAllRequest());
} else {
await control.send(new lib.ControllerUpdateRequest());
}
if (args.restart) {
await control.send(new lib.ControllerRestartRequest());
} else {
+1 -1
View File
@@ -1,5 +1,5 @@
import * as lib from "@clusterio/lib";
import type { Control } from "../ctl";
import type { Control } from "../ctl.js";
export const debugCommands = new lib.CommandTree({ name: "debug", description: "Debugging utilities" });
debugCommands.add(new lib.Command({
+11 -6
View File
@@ -6,9 +6,9 @@ import child_process from "child_process";
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
@@ -87,7 +87,7 @@ hostCommands.add(new lib.Command({
} else {
logger.info(`Writing ${args.output}`);
try {
await fs.writeFile(args.output, content, { flag: "wx" });
await fs.writeFile(args.output, content, { flag: "wx", mode: 0o600 });
} catch (err: any) {
if (err.code === "EEXIST") {
throw new lib.CommandError(`File ${args.output} already exists`);
@@ -101,10 +101,15 @@ hostCommands.add(new lib.Command({
definition: ["update <host>", "Update the host", (yargs) => {
yargs.positional("host", { describe: "Host to update", type: "string" });
yargs.option("restart", { alias: "r", type: "boolean", description: "Restart after update" });
yargs.option("all", { alias: "a", type: "boolean", description: "Also update all plugins" });
}],
handler: async function(args: { host: string, restart: boolean }, control: Control) {
handler: async function(args: { host: string, restart: boolean, all: boolean }, control: Control) {
let hostId = await lib.resolveHost(control, args.host);
await control.sendTo({ hostId }, new lib.HostUpdateRequest());
if (args.all) {
await control.sendTo({ hostId }, new lib.UpdateAllRequest());
} else {
await control.sendTo({ hostId }, new lib.HostUpdateRequest());
}
if (args.restart) {
await control.sendTo({ hostId }, new lib.HostRestartRequest());
} else {
+3 -3
View File
@@ -9,9 +9,9 @@ import util from "util";
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
import { serializedConfigToString, getEditor, configToKeyVal } from "./config_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
const finished = util.promisify(stream.finished);
+1 -1
View File
@@ -2,7 +2,7 @@ import winston from "winston";
import * as lib from "@clusterio/lib";
import { ConsoleTransport, levels, logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import type { Control } from "../ctl.js";
export const logCommands = new lib.CommandTree({ name: "log", description: "Log inspection" });
logCommands.add(new lib.Command({
+2 -2
View File
@@ -7,8 +7,8 @@ import util from "util";
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
const finished = util.promisify(stream.finished);
+2 -2
View File
@@ -2,8 +2,8 @@ import asTableModule from "as-table";
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
+2 -2
View File
@@ -1,8 +1,8 @@
import asTableModule from "as-table";
import * as lib from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
+2 -2
View File
@@ -2,8 +2,8 @@ import asTableModule from "as-table";
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
+2 -2
View File
@@ -3,8 +3,8 @@ import asTableModule from "as-table";
import path from "path";
import * as lib from "@clusterio/lib";
import type { Control } from "../ctl";
import { print } from "./command_ops";
import type { Control } from "../ctl.js";
import { print } from "./command_ops.js";
const asTable = asTableModule.configure({ delimiter: " | " });
@@ -0,0 +1,3 @@
// This needs to be a separate file due to ECMAScript Modules evaluating code in
// imported modules before any code in the file that imports it.
(global as any).lazySchemaCompilation = true;
+8 -7
View File
@@ -15,7 +15,7 @@
import fs from "node:fs/promises";
import path from "path";
import yargs from "yargs";
import { version } from "./package.json";
import packageConfig from "./package.json" with { type: "json" };
import winston from "winston";
import "winston-daily-rotate-file";
@@ -23,7 +23,7 @@ import "winston-daily-rotate-file";
import * as lib from "@clusterio/lib";
import { ConsoleTransport, levels, logger } from "@clusterio/lib";
import Host from "./src/Host";
import Host from "./src/Host.js";
let host: Host | undefined;
@@ -69,7 +69,7 @@ export class HostConnector extends lib.WebSocketClientConnector {
new lib.MessageRegisterHost(
new lib.RegisterHostData(
this.hostConfig.get("host.controller_token"),
version,
packageConfig.version,
this.hostConfig.get("host.id"),
plugins,
)
@@ -82,7 +82,7 @@ export class HostConnector extends lib.WebSocketClientConnector {
async function startHost() {
// argument parsing
// eslint-disable-next-line node/no-sync
const args = yargs
const args = yargs(process.argv.slice(2))
.scriptName("host")
.usage("$0 <command> [options]")
.option("log-level", {
@@ -145,7 +145,7 @@ async function startHost() {
let command = args._[0];
if (command === "run") {
logger.info(`Starting Clusterio host ${version}`);
logger.info(`Starting Clusterio host ${packageConfig.version}`);
if (args.recovery) {
logger.warn("Host recovery mode enabled. Some features will be disabled.");
}
@@ -163,6 +163,7 @@ async function startHost() {
logger.info("Loading Plugin info");
let pluginInfos = await lib.loadPluginInfos(pluginList);
lib.registerPluginMessages(pluginInfos);
lib.registerPluginPermissions(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
const hostConfigPath = args.config;
@@ -187,7 +188,7 @@ async function startHost() {
}
}
hostConfig.set("host.version", version); // Allows tracking last loaded version
hostConfig.set("host.version", packageConfig.version); // Allows tracking last loaded version
if (command === "config") {
await lib.handleConfigCommand(args, hostConfig, hostConfigLock);
@@ -308,6 +309,6 @@ ${err.stack}`
});
}
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+7 -7
View File
@@ -1,10 +1,10 @@
import { bootstrap } from "./host";
export { default as BaseHostPlugin } from "./src/BaseHostPlugin";
export { default as BaseInstancePlugin } from "./src/BaseInstancePlugin";
export { default as Host, HostRouter } from "./src/Host";
export { default as Instance } from "./src/Instance";
export { default as InstanceConnection } from "./src/InstanceConnection";
import { bootstrap } from "./host.js";
export { default as BaseHostPlugin } from "./src/BaseHostPlugin.js";
export { default as BaseInstancePlugin } from "./src/BaseInstancePlugin.js";
export { default as Host, HostRouter } from "./src/Host.js";
export { default as Instance } from "./src/Instance.js";
export { default as InstanceConnection } from "./src/InstanceConnection.js";
if (module === require.main) {
if (import.meta.main) {
bootstrap();
}
+1
View File
@@ -22,6 +22,7 @@
"access": "public"
},
"type": "module",
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build",
+1 -1
View File
@@ -1,7 +1,7 @@
import type {
CollectorResult, Logger, ParsedFactorioOutput, PlayerEvent, PluginNodeEnvInfo,
} from "@clusterio/lib";
import type Host from "./Host";
import type Host from "./Host.js";
/**
* Base class for host plugins
+2 -2
View File
@@ -1,8 +1,8 @@
import type {
CollectorResult, Logger, ParsedFactorioOutput, PlayerEvent, PluginNodeEnvInfo,
} from "@clusterio/lib";
import type Instance from "./Instance";
import type Host from "./Host";
import type Instance from "./Instance.js";
import type Host from "./Host.js";
/**
* Base class for instance plugins
+20 -9
View File
@@ -5,16 +5,17 @@ import pidusage from "pidusage";
import semver from "semver";
import setBlocking from "set-blocking";
import stream from "stream";
import { fileURLToPath } from "node:url";
import util from "util";
// internal libraries
import * as lib from "@clusterio/lib";
import { logger } from "@clusterio/lib";
import type { HostConnector } from "../host";
import Instance from "./Instance";
import InstanceConnection from "./InstanceConnection";
import BaseHostPlugin from "./BaseHostPlugin";
import type { HostConnector } from "../host.js";
import Instance from "./Instance.js";
import InstanceConnection from "./InstanceConnection.js";
import BaseHostPlugin from "./BaseHostPlugin.js";
const finished = util.promisify(stream.finished);
@@ -403,6 +404,7 @@ export default class Host extends lib.Link {
this.handle(lib.PluginListRequest, this.handlePluginListRequest.bind(this));
this.handle(lib.PluginUpdateRequest, this.handlePluginUpdateRequest.bind(this));
this.handle(lib.PluginInstallRequest, this.handlePluginInstallRequest.bind(this));
this.handle(lib.UpdateAllRequest, this.handleUpdateAllRequest.bind(this));
this.snoopEvent(lib.InstanceAdminlistUpdateEvent, this.handleAdminlistUpdateEvent.bind(this));
this.snoopEvent(lib.InstanceBanlistUpdateEvent, this.handleBanlistUpdateEvent.bind(this));
@@ -859,7 +861,7 @@ export default class Host extends lib.Link {
try {
// First check the clusterio version
const runningVersion = this.config.get("host.version");
const packageJsonPath = require.resolve("@clusterio/host/package.json");
const packageJsonPath = fileURLToPath(import.meta.resolve("@clusterio/host/package.json"));
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
if (runningVersion !== packageJson.version) {
this.config.restartRequired = true;
@@ -876,15 +878,14 @@ export default class Host extends lib.Link {
}
packageName = pluginInfo.npmPackage ?? pluginInfo.name;
const pluginPackageJsonPath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
const pluginPackageJson = JSON.parse(await fs.readFile(pluginPackageJsonPath, "utf8"));
const pluginPackageJson = JSON.parse(await fs.readFile(pluginInfo.packagePath, "utf8"));
if (pluginInfo.version !== pluginPackageJson.version) {
this.config.restartRequired = true;
return true;
}
}
} catch (err: any) {
logger.warn(`Failed to read package json for ${packageName}:\n${err.stack ?? err.message}`);
logger.warn(`Failed to read package.json for ${packageName}:\n${err.stack ?? err.message}`);
}
return false;
@@ -893,7 +894,7 @@ export default class Host extends lib.Link {
async checkRestartDowngrade() {
try {
const runningVersion = this.config.get("host.version");
const packageJsonPath = require.resolve("@clusterio/host/package.json");
const packageJsonPath = fileURLToPath(import.meta.resolve("@clusterio/host/package.json"));
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
const installedVersion = packageJson.version;
@@ -1151,6 +1152,16 @@ export default class Host extends lib.Link {
return await lib.handlePluginInstall(request.pluginPackage);
}
async handleUpdateAllRequest(request: lib.UpdateAllRequest) {
if (!this.config.get("host.allow_remote_updates")) {
throw new lib.RequestError("Remote updates are disabled on this machine");
}
if (!this.config.get("host.allow_plugin_updates")) {
throw new lib.RequestError("Plugin updates are disabled on this machine");
}
return await lib.handleUpdateAll("@clusterio/host", this.pluginInfos);
}
async handlePluginListRequest(request: lib.PluginListRequest) {
return this.pluginInfos.map(pluginInfo => lib.PluginDetails.fromNodeEnvInfo(
pluginInfo,
+26 -6
View File
@@ -9,11 +9,11 @@ const execAsync = util.promisify(exec);
// internal libraries
import * as lib from "@clusterio/lib";
import { FactorioServer } from "./server";
import { SaveModule, patch } from "./patch";
import { exportData } from "./export";
import type Host from "./Host";
import BaseInstancePlugin from "./BaseInstancePlugin";
import { FactorioServer } from "./server.js";
import { SaveModule, patch } from "./patch.js";
import { exportData } from "./export.js";
import type Host from "./Host.js";
import BaseInstancePlugin from "./BaseInstancePlugin.js";
const scriptCommands = [
"/cheat", "/editor",
@@ -796,6 +796,7 @@ end`.replace(/\r?\n/g, " ");
} else {
modPack = await this.sendTo("controller", new lib.ModPackGetRequest(modPackId));
}
this.checkModPackVersion(modPack);
this.activeModPack = modPack;
const mods = await this._host.fetchMods(modPack.mods.values(), modPack.factorioVersion);
@@ -847,6 +848,25 @@ end`.replace(/\r?\n/g, " ");
await fs.writeFile(this.path("mods", "mod-settings.dat"), this.activeModPack.toModSettingsDat());
}
/**
* Check that the mod pack does not target a newer Factorio than the server
*
* The mod pack's Factorio version is written into mod-settings.dat and
* Factorio refuses to read it if it is newer than the game. It then
* loads default settings instead, which can corrupt the save.
*
* @throws {lib.RequestError} if the mod pack version is higher than the server version.
*/
checkModPackVersion(modPack: lib.ModPack) {
const serverVersion = this.server.version;
if (serverVersion && modPack.integerFactorioVersion > lib.integerFullVersion(serverVersion)) {
throw new lib.RequestError(
`Mod pack ${modPack.name} is for Factorio ${modPack.factorioVersion} ` +
`which is newer than the Factorio ${serverVersion} this instance runs`
);
}
}
/**
* Prepare instance for starting
*
@@ -963,7 +983,7 @@ end`.replace(/\r?\n/g, " ");
// Find stand alone modules to load
// XXX for now only the included clusterio module is loaded
let modulesDirectory = path.join(__dirname, "..", "..", "..", "modules");
let modulesDirectory = path.join(import.meta.dirname, "..", "..", "..", "modules");
for (let entry of await fs.readdir(modulesDirectory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (modules.has(entry.name)) {
+2 -2
View File
@@ -1,6 +1,6 @@
import * as lib from "@clusterio/lib";
import type Host from "./Host";
import type Instance from "./Instance";
import type Host from "./Host.js";
import type Instance from "./Instance.js";
export default class InstanceConnection extends lib.Link {
+4 -4
View File
@@ -5,9 +5,9 @@ import { Jimp, JimpMime, type JimpInstance } from "jimp";
import JSZip from "jszip";
import * as lib from "@clusterio/lib";
import * as libBuildMod from "@clusterio/lib/dist/node/build_mod";
import type { FactorioServer } from "./server";
import { definesPrototypesFallback } from "./export_fallback";
import * as libBuildMod from "@clusterio/lib/dist/node/build_mod.js";
import type { FactorioServer } from "./server.js";
import { definesPrototypesFallback } from "./export_fallback.js";
interface SimpleIconSpecification {
icons?: never;
@@ -56,7 +56,7 @@ async function generateExportMod(server: FactorioServer) {
clean: false,
build: true,
pack: true,
sourceDir: path.join(__dirname, "..", "..", "..", "lua", "export"),
sourceDir: path.join(import.meta.dirname, "..", "..", "..", "lua", "export"),
outputDir: server.writePath("mods"),
bumpPatch: false,
factorioVersion: server.version?.replace(/\.\d+$/, ""),
+2 -3
View File
@@ -9,7 +9,7 @@ import { Type, Static } from "@sinclair/typebox";
import * as lib from "@clusterio/lib";
import BaseInstancePlugin from "./BaseInstancePlugin";
import BaseInstancePlugin from "./BaseInstancePlugin.js";
/**
@@ -91,8 +91,7 @@ export class SaveModule {
}
static async fromPlugin(plugin: BaseInstancePlugin) {
let pluginPackagePath = require.resolve(path.posix.join(plugin.info.requirePath, "package.json"));
let moduleDirectory = path.join(path.dirname(pluginPackagePath), "module");
let moduleDirectory = path.join(path.dirname(plugin.info.packagePath), "module");
try {
await fs.access(moduleDirectory);
} catch (err: any) {
+1 -1
View File
@@ -17,7 +17,7 @@ export * from "./src/subscriptions";
export { default as ExponentialBackoff } from "./src/ExponentialBackoff";
export { default as RateLimiter } from "./src/RateLimiter";
export { default as ValueCache } from "./src/ValueCache";
export { default as isDeepStrictEqual } from "./src/is_deep_strict_equal";
export { default as isDeepStrictEqual } from "#is_deep_strict_equal";
import { checkSingletonImport } from "./src/check_singleton_import";
checkSingletonImport(import.meta.url);
+10 -15
View File
@@ -1,11 +1,10 @@
/* eslint-disable no-console */
"use strict";
const fs = require("node:fs/promises");
const JSZip = require("jszip");
const path = require("path");
const stream = require("stream");
const util = require("util");
const yargs = require("yargs");
import fs from "node:fs/promises";
import JSZip from "jszip";
import path from "node:path";
import stream from "node:stream";
import util from "node:util";
import yargs from "yargs";
const finished = util.promisify(stream.finished);
@@ -79,7 +78,7 @@ async function buildMod(args, info, modName) {
}
}
async function build(args) {
export async function build(args) {
let info = JSON.parse(await fs.readFile(path.join(args.sourceDir, "info.json")));
if (args.factorioVersion) {
@@ -121,7 +120,7 @@ async function build(args) {
async function main() {
const args = yargs
const args = yargs(process.argv.slice(2))
.scriptName("build")
.options({
"clean": { describe: "Remove previous builds", type: "boolean", default: false },
@@ -144,16 +143,12 @@ async function main() {
},
})
.strict()
.argv
.parse()
;
await build(args);
}
if (module === require.main) {
if (import.meta.main) {
main().catch(err => { console.log(err); });
}
module.exports = {
build,
};
+39 -39
View File
@@ -2,44 +2,44 @@
* Shared library for Clusterio
* @module lib
*/
export * from "./build_mod";
export * from "./src/api";
export * from "./src/command";
export * from "./src/config";
export * from "./src/data";
export * from "./src/database";
export * from "./src/errors";
export * from "./src/export";
export * from "./src/external";
export * from "./src/factorio";
export * from "./src/file_ops";
export * from "./src/hash";
export * from "./src/helpers";
export * from "./src/ini";
export * from "./src/link";
export * from "./src/logging";
export * from "./src/logging_utils";
export * from "./src/lua_tools";
export * from "./src/permissions";
export * from "./src/plugin";
export * from "./src/plugin_loader";
export * from "./src/prometheus";
export * from "./src/schema";
export * from "./src/shared_commands";
export * from "./src/stream";
export * from "./src/system_collectors";
export * from "./src/zip_ops";
export * from "./src/subscriptions";
export * from "./src/datastore";
export * from "./src/load_plugin_list";
export * from "./src/rce_ops";
export * from "./src/LockFile";
export * from "./build_mod.js";
export * from "./src/api.js";
export * from "./src/command.js";
export * from "./src/config/index.js";
export * from "./src/data/index.js";
export * from "./src/database.js";
export * from "./src/errors.js";
export * from "./src/export.js";
export * from "./src/external/index.js";
export * from "./src/factorio/index.js";
export * from "./src/file_ops.js";
export * from "./src/hash.js";
export * from "./src/helpers.js";
export * from "./src/ini.js";
export * from "./src/link/index.js";
export * from "./src/logging.js";
export * from "./src/logging_utils.js";
export * from "./src/lua_tools.js";
export * from "./src/permissions.js";
export * from "./src/plugin.js";
export * from "./src/plugin_loader.js";
export * from "./src/prometheus.js";
export * from "./src/schema.js";
export * from "./src/shared_commands.js";
export * from "./src/stream.js";
export * from "./src/system_collectors.js";
export * from "./src/zip_ops.js";
export * from "./src/subscriptions.js";
export * from "./src/datastore.js";
export * from "./src/load_plugin_list.js";
export * from "./src/rce_ops.js";
export * from "./src/LockFile.js";
export { default as ExponentialBackoff } from "./src/ExponentialBackoff";
export { default as ModStore } from "./src/ModStore";
export { default as RateLimiter } from "./src/RateLimiter";
export { default as ValueCache } from "./src/ValueCache";
export { default as isDeepStrictEqual } from "./src/is_deep_strict_equal";
export { default as ExponentialBackoff } from "./src/ExponentialBackoff.js";
export { default as ModStore } from "./src/ModStore.js";
export { default as RateLimiter } from "./src/RateLimiter.js";
export { default as ValueCache } from "./src/ValueCache.js";
export { default as isDeepStrictEqual } from "#is_deep_strict_equal";
import { checkSingletonImport } from "./src/check_singleton_import";
checkSingletonImport(__filename);
import { checkSingletonImport } from "./src/check_singleton_import.js";
checkSingletonImport(import.meta.filename);
+11
View File
@@ -22,6 +22,17 @@
"access": "public"
},
"type": "module",
"imports": {
"#WebSocket": {
"node": "./src/link/WebSocket.node.ts",
"import": "./src/link/WebSocket.browser.ts"
},
"#is_deep_strict_equal": {
"node": "./src/is_deep_strict_equal.node.ts",
"import": "./src/is_deep_strict_equal.browser.ts"
}
},
"browser": "browser.ts",
"main": "dist/node/index.js",
"scripts": {
+8 -8
View File
@@ -1,15 +1,14 @@
"use strict";
const fs = require("node:fs");
const path = require("path");
const Ajv = require("ajv");
const standaloneCode = require("ajv/dist/standalone").default;
import fs from "node:fs";
import path from "node:path";
import Ajv from "ajv";
import standaloneCode from "ajv/dist/standalone/index.js";
/* eslint-disable node/no-sync */
// Provide an empty file for message_core to load
fs.writeFileSync(path.join(__dirname, "../dist/node/src/data/message_validate.js"), "");
fs.writeFileSync(path.join(import.meta.dirname, "../dist/node/src/data/message_validate.js"), "export default {};");
const { Message } = require("../dist/node/src/data/messages_core");
const { Message } = await import("../dist/node/src/data/messages_core.js");
const ajv = new Ajv({
allowUnionTypes: true,
@@ -18,13 +17,14 @@ const ajv = new Ajv({
verbose: true,
code: {
source: true,
esm: true,
},
});
const validate = ajv.compile(Message.jsonSchema);
let moduleCode = standaloneCode(ajv, validate);
function write(output) {
fs.writeFileSync(
path.join(__dirname, output),
path.join(import.meta.dirname, output),
`// generated by scripts/compile_validator.js\n// @ts-nocheck\n/* eslint-disable */\n${moduleCode}`
);
}
+4 -4
View File
@@ -2,10 +2,10 @@ import fs from "node:fs/promises";
import path from "path";
import events from "events";
import { Static } from "@sinclair/typebox";
import { logger } from "./logging";
import { downloadFile, safeOutputFile } from "./file_ops";
import { ModPortalReleaseSchema, ModPortalDetailsSchema, ModNameVersionPair } from "./data/messages_mod";
import { ModInfo, ModPack, MajorMinorVersion, ModVersionEquality, SourceVersion } from "./data";
import { logger } from "./logging.js";
import { downloadFile, safeOutputFile } from "./file_ops.js";
import { ModPortalReleaseSchema, ModPortalDetailsSchema, ModNameVersionPair } from "./data/messages_mod.js";
import { ModInfo, ModPack, MajorMinorVersion, ModVersionEquality, SourceVersion } from "./data/index.js";
export interface ModStoreEvents {
/** A stored mod was created, updated or deleted */
+2 -1
View File
@@ -4,7 +4,8 @@ export type PluginWebApi = {
enabled: boolean;
loaded: boolean;
web: {
main: string;
/** Path to the web bundle, not present if the plugin has no web build. */
main?: string;
error?: string;
};
/**
+1 -1
View File
@@ -1,4 +1,4 @@
import { InstallationError } from "./errors";
import { InstallationError } from "./errors.js";
declare global {
// eslint-disable-next-line vars-on-top
var _clusterioLibImportedFrom: string;
+34 -72
View File
@@ -6,9 +6,9 @@
* @author Hornwitser
* @module lib/command
*/
import * as libData from "./data";
import * as libErrors from "./errors";
import type { Link } from "./link";
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, control: any) => Promise<void>;
@@ -146,6 +146,22 @@ export class CommandTree {
}
/**
* 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
*
@@ -157,23 +173,11 @@ export class CommandTree {
* @returns host ID.
*/
export async function resolveHost(client: Link, hostName: string) {
let hostId: number | undefined;
if (/^-?\d+$/.test(hostName)) {
hostId = parseInt(hostName, 10);
} else {
let hosts = await client.sendTo("controller", new libData.HostListRequest());
for (let host of hosts) {
if (host.name === hostName) {
hostId = host.id;
break;
}
}
if (hostId === undefined) {
throw new libErrors.CommandError(`No host named ${hostName}`);
}
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;
}
@@ -188,23 +192,11 @@ export async function resolveHost(client: Link, hostName: string) {
* @returns instance ID.
*/
export async function resolveInstance(client: Link, instanceName: string) {
let instanceId: number | undefined;
if (/^-?\d+$/.test(instanceName)) {
instanceId = parseInt(instanceName, 10);
} else {
let instances = await client.sendTo("controller", new libData.InstanceDetailsListRequest());
for (let instance of instances) {
if (instance.name === instanceName) {
instanceId = instance.id;
break;
}
}
if (instanceId === undefined) {
throw new libErrors.CommandError(`No instance named ${instanceName}`);
}
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;
}
@@ -220,23 +212,11 @@ export async function resolveInstance(client: Link, instanceName: string) {
* @returns mod pack ID.
*/
export async function resolveModPack(client: Link, modPackName: string) {
let modPackId: number | undefined;
if (/^-?\d+/.test(modPackName)) {
modPackId = parseInt(modPackName, 10);
} else {
let modPacks = await client.sendTo("controller", new libData.ModPackListRequest());
for (let modPack of modPacks) {
if (modPack.name === modPackName) {
modPackId = modPack.id;
break;
}
}
if (modPackId === undefined) {
throw new libErrors.CommandError(`No mod pack named ${modPackName}`);
}
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;
}
@@ -251,27 +231,9 @@ export async function resolveModPack(client: Link, modPackName: string) {
* @returns Role info.
*/
export async function retrieveRole(client: Link, roleName: string) {
let roles = await client.sendTo("controller", new libData.RoleListRequest());
let resolvedRole: libData.Role | undefined;
if (/^-?\d+$/.test(roleName)) {
let roleId = parseInt(roleName, 10);
for (let role of roles) {
if (role.id === roleId) {
resolvedRole = role;
break;
}
}
} else {
for (let role of roles) {
if (role.name === roleName) {
resolvedRole = role;
break;
}
}
}
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}`);
}
+8 -8
View File
@@ -3,13 +3,13 @@ import { Type, Static } from "@sinclair/typebox";
import * as fs from "node:fs/promises";
import events from "events";
import isDeepStrictEqual from "../is_deep_strict_equal";
import { basicType } from "../helpers";
import * as libSchema from "../schema";
import { StringEnum } from "../data/composites";
import { safeOutputFile } from "../file_ops";
import * as validators from "./validators";
import { RequestError } from "../errors";
import isDeepStrictEqual from "#is_deep_strict_equal";
import { basicType } from "../helpers.js";
import * as libSchema from "../schema.js";
import { StringEnum } from "../data/composites.js";
import { safeOutputFile } from "../file_ops.js";
import * as validators from "./validators.js";
import { RequestError } from "../errors.js";
const ConfigLocation = StringEnum(["controller", "host", "control"]);
export type ConfigLocation = Static<typeof ConfigLocation>;
@@ -468,7 +468,7 @@ export class Config<
if (!this.filepath) {
throw new Error("Cannot save config which has no filepath");
}
await safeOutputFile(this.filepath, JSON.stringify(this, null, "\t"));
await safeOutputFile(this.filepath, JSON.stringify(this, null, "\t"), { mode: 0o600 });
this.dirty = false;
}
+4 -4
View File
@@ -1,9 +1,9 @@
// Core definitions for the configuration system
import * as classes from "./classes";
import * as validators from "./validators";
import type { PluginNodeEnvInfo, PluginWebpackEnvInfo } from "../plugin";
import * as classes from "./classes.js";
import * as validators from "./validators.js";
import type { PluginNodeEnvInfo, PluginWebpackEnvInfo } from "../plugin.js";
import { Static } from "@sinclair/typebox";
import { isTargetVersion } from "../data/version";
import { isTargetVersion } from "../data/version.js";
type configFromJSON<T> = (...args: Parameters<typeof classes.Config.fromJSON>) => T;
type configFromFile<T> = (...args: Parameters<typeof classes.Config.fromFile>) => Promise<T>;
+2 -2
View File
@@ -3,5 +3,5 @@
* @author Hornwitser
* @module lib/config
*/
export * from "./classes";
export * from "./definitions";
export * from "./classes.js";
export * from "./definitions.js";
+2 -2
View File
@@ -1,6 +1,6 @@
import { Static, Type } from "@sinclair/typebox";
import { StringEnum } from "./composites";
import { TargetVersion, TargetVersionSchema } from "./version";
import { StringEnum } from "./composites.js";
import { TargetVersion, TargetVersionSchema } from "./version.js";
export const InstanceStatus = StringEnum([
"unknown",
+5 -5
View File
@@ -2,16 +2,16 @@ import fs from "node:fs/promises";
import JSZip from "jszip";
import { Type, Static } from "@sinclair/typebox";
import * as libHash from "../hash";
import * as libSchema from "../schema";
import { findRoot } from "../zip_ops";
import { ModRecord } from "./ModPack";
import * as libHash from "../hash.js";
import * as libSchema from "../schema.js";
import { findRoot } from "../zip_ops.js";
import { ModRecord } from "./ModPack.js";
import {
MajorMinorVersion, MajorMinorVersionSchema, normaliseMajorMinorVersion, integerMajorMinorVersion,
SourceVersion, SourceVersionSchema, isSourceVersion, normaliseSourceVersion, integerSourceVersion,
ModVersionEquality,
} from "./version";
} from "./version.js";
type ModDependencyType = "incompatible" | "optional" | "hidden" | "unordered" | "required" | "recommended";
+7 -7
View File
@@ -1,19 +1,19 @@
import zlib from "zlib";
import { Type, Static } from "@sinclair/typebox";
import * as libSchema from "../schema";
import * as libLuaTools from "../lua_tools";
import * as libSchema from "../schema.js";
import * as libLuaTools from "../lua_tools.js";
import ExportManifest from "./ExportManifest";
import type { ExportSettings } from "../export";
import type { Logger } from "../logging";
import ExportManifest from "./ExportManifest.js";
import type { ExportSettings } from "../export.js";
import type { Logger } from "../logging.js";
import ModInfo, { ModDependencyUnsatisfiedReason } from "./ModInfo";
import ModInfo, { ModDependencyUnsatisfiedReason } from "./ModInfo.js";
import {
PartialVersion, PartialVersionSchema, integerPartialVersion,
SourceVersion, SourceVersionSchema, normaliseFullVersion, integerSourceVersion,
} from "./version";
} from "./version.js";
export const ModSettingColor = Type.Object({
+1 -1
View File
@@ -1,5 +1,5 @@
import { Type, Static } from "@sinclair/typebox";
import { compile } from "../schema";
import { compile } from "../schema.js";
/**
* Information about a module stored in its module.json file
+1 -1
View File
@@ -1,5 +1,5 @@
import { Static, Type } from "@sinclair/typebox";
import { permissions as globalPermissions } from "../permissions";
import { permissions as globalPermissions } from "../permissions.js";
interface RoleCollection {
get(id: number): Role;
+3 -3
View File
@@ -1,6 +1,6 @@
import { Static, Type } from "@sinclair/typebox";
import PlayerStats from "./PlayerStats";
import type { PermissionName } from "../permissions.js";
import PlayerStats from "./PlayerStats.js";
/** Represents a user in the cluster */
export default class UserDetails {
@@ -164,5 +164,5 @@ export interface IUser extends UserDetails {
* @throws {Error} If the given permission does not exist.
* @throws {libErrors.PermissionError} if the user does noh have the given permission.
*/
checkPermission(permission: string): void
checkPermission(permission: PermissionName): void
}
+23 -23
View File
@@ -3,26 +3,26 @@
* @module lib/data
* @author Hornwitser
*/
export { default as ExportManifest } from "./ExportManifest";
export { default as ModInfo, ModDependency } from "./ModInfo";
export { ModSettingColor, getInstalledModUpdates, applyModRecordAdvisories } from "./ModPack";
export type { ModSetting, ModRecord, ModRecordAdvisory } from "./ModPack";
export { default as ModPack } from "./ModPack";
export { default as ModuleInfo } from "./ModuleInfo";
export { default as Permission } from "./Permission";
export { default as PlayerStats } from "./PlayerStats";
export { default as Role } from "./Role";
export { default as InstanceDetails, InstanceStatus } from "./InstanceDetails";
export { default as HostDetails } from "./HostDetails";
export { default as UserDetails } from "./UserDetails";
export type { IUser } from "./UserDetails";
export * from "./composites";
export * from "./messages_core";
export * from "./messages_controller";
export * from "./messages_host";
export * from "./messages_instance";
export * from "./messages_mod";
export * from "./messages_user";
export * from "./messages_role";
export * from "./messages_plugin";
export * from "./version";
export { default as ExportManifest } from "./ExportManifest.js";
export { default as ModInfo, ModDependency } from "./ModInfo.js";
export { ModSettingColor, getInstalledModUpdates, applyModRecordAdvisories } from "./ModPack.js";
export type { ModSetting, ModRecord, ModRecordAdvisory } from "./ModPack.js";
export { default as ModPack } from "./ModPack.js";
export { default as ModuleInfo } from "./ModuleInfo.js";
export { default as Permission } from "./Permission.js";
export { default as PlayerStats } from "./PlayerStats.js";
export { default as Role } from "./Role.js";
export { default as InstanceDetails, InstanceStatus } from "./InstanceDetails.js";
export { default as HostDetails } from "./HostDetails.js";
export { default as UserDetails } from "./UserDetails.js";
export type { IUser } from "./UserDetails.js";
export * from "./composites.js";
export * from "./messages_core.js";
export * from "./messages_controller.js";
export * from "./messages_host.js";
export * from "./messages_instance.js";
export * from "./messages_mod.js";
export * from "./messages_user.js";
export * from "./messages_role.js";
export * from "./messages_plugin.js";
export * from "./version.js";
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ValidateFunction } from "ajv";
import type { Message } from "./messages_core";
import type { Message } from "./messages_core.js";
const message_validate: ValidateFunction<Message>;
export default message_validate;
+4 -4
View File
@@ -1,8 +1,8 @@
import { Type, Static } from "@sinclair/typebox";
import { JsonString, StringEnum, StringKey, plainJson } from "./composites";
import { levels } from "../logging";
import { ControllerConfig, HostConfig } from "../config";
import { ExternalFactorioVersionSchema, LatestReleasesSchema } from "../external";
import { JsonString, StringEnum, StringKey, plainJson } from "./composites.js";
import { levels } from "../logging.js";
import { ControllerConfig, HostConfig } from "../config/index.js";
import { ExternalFactorioVersionSchema, LatestReleasesSchema } from "../external/index.js";
export class ControllerStopRequest {
declare ["constructor"]: typeof ControllerStopRequest;
+2 -2
View File
@@ -1,8 +1,8 @@
// Note: Run compile_validator script after making changes to this file.
import { TSchema, Type, Static } from "@sinclair/typebox";
import messageValidate from "./message_validate"; // generated file
import { StringEnum } from "./composites";
import messageValidate from "./message_validate.js"; // generated file
import { StringEnum } from "./composites.js";
export type AddressType = "controller" | "host" | "instance" | "control" | "broadcast";
export type AddressShorthand =
+4 -4
View File
@@ -1,8 +1,8 @@
import { Type, Static } from "@sinclair/typebox";
import { jsonArray, plainJson, StringEnum } from "./composites";
import { CollectorResultSerialized } from "../prometheus";
import { HostConfig } from "../config/definitions";
import HostDetails from "./HostDetails";
import { jsonArray, plainJson, StringEnum } from "./composites.js";
import { CollectorResultSerialized } from "../prometheus.js";
import { HostConfig } from "../config/definitions.js";
import HostDetails from "./HostDetails.js";
export class HostStopRequest {
declare ["constructor"]: typeof HostStopRequest;
+8 -8
View File
@@ -1,12 +1,12 @@
import { Type, Static } from "@sinclair/typebox";
import PlayerStats from "./PlayerStats";
import { JsonString, StringEnum, jsonArray, plainJson } from "./composites";
import { InstanceConfig } from "../config";
import type { IUser } from "./UserDetails";
import type { MessageRequest } from "./messages_core";
import { CollectorResultSerialized } from "../prometheus";
import { TargetVersion, TargetVersionSchema, PartialVersion, PartialVersionSchema } from "./version";
import InstanceDetails, { InstanceStatus } from "./InstanceDetails";
import PlayerStats from "./PlayerStats.js";
import { JsonString, StringEnum, jsonArray, plainJson } from "./composites.js";
import { InstanceConfig } from "../config/index.js";
import type { IUser } from "./UserDetails.js";
import type { MessageRequest } from "./messages_core.js";
import { CollectorResultSerialized } from "../prometheus.js";
import { TargetVersion, TargetVersionSchema, PartialVersion, PartialVersionSchema } from "./version.js";
import InstanceDetails, { InstanceStatus } from "./InstanceDetails.js";
export class InstanceDetailsGetRequest {
declare ["constructor"]: typeof InstanceDetailsGetRequest;
+4 -4
View File
@@ -1,13 +1,13 @@
import { Type, Static } from "@sinclair/typebox";
import ModInfo, { ModDependency } from "./ModInfo";
import ModPack from "./ModPack";
import { JsonString, jsonArray } from "./composites";
import ModInfo, { ModDependency } from "./ModInfo.js";
import ModPack from "./ModPack.js";
import { JsonString, jsonArray } from "./composites.js";
import {
MajorMinorVersion, MajorMinorVersionSchema, normaliseMajorMinorVersion,
SourceVersion, SourceVersionSchema,
ModVersionEquality,
} from "./version";
} from "./version.js";
export class ModPackGetRequest {
+15 -2
View File
@@ -1,6 +1,8 @@
import { Type, Static } from "@sinclair/typebox";
import { jsonArray } from "./composites";
import { PluginNodeEnvInfo } from "../plugin";
import { jsonArray } from "./composites.js";
import { PluginNodeEnvInfo } from "../plugin.js";
import { Address, MessageRequest } from "./messages_core.js";
import type { IUser } from "./UserDetails.js";
/* This is similar to other plugin definitions but with lots removed */
export class PluginDetails {
@@ -91,3 +93,14 @@ export class PluginInstallRequest {
return this.pluginPackage;
}
}
export class UpdateAllRequest {
declare ["constructor"]: typeof UpdateAllRequest;
static type = "request" as const;
static src = "control" as const;
static dst = ["controller", "host"] as const;
static permission(user: IUser, message: MessageRequest) {
user.checkPermission(message.dst.type === Address.host ? "core.host.update" : "core.controller.update");
user.checkPermission("core.plugin.update");
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
import { Type, Static } from "@sinclair/typebox";
import { JsonNumber, jsonArray } from "./composites";
import Permission from "./Permission";
import Role from "./Role";
import { JsonNumber, jsonArray } from "./composites.js";
import Permission from "./Permission.js";
import Role from "./Role.js";
export class PermissionListRequest {
declare ["constructor"]: typeof PermissionListRequest;
+3 -3
View File
@@ -1,7 +1,7 @@
import { Type, Static } from "@sinclair/typebox";
import UserDetails, { IUser } from "./UserDetails";
import { StringEnum, jsonArray, plainJson } from "./composites";
import { MessageRequest } from "./messages_core";
import UserDetails, { IUser } from "./UserDetails.js";
import { StringEnum, jsonArray, plainJson } from "./composites.js";
import { MessageRequest } from "./messages_core.js";
export class UserGetRequest {
declare ["constructor"]: typeof UserGetRequest;
+2 -2
View File
@@ -6,8 +6,8 @@
*/
import fs from "node:fs/promises";
import * as libFileOps from "./file_ops";
import { basicType } from "./helpers";
import * as libFileOps from "./file_ops.js";
import { basicType } from "./helpers.js";
export type ItemCountWithQuality = { [quality: string]: number };
+4 -4
View File
@@ -2,10 +2,10 @@ import path from "path";
import fs from "node:fs/promises";
import { EventEmitter } from "events";
import { safeOutputFile } from "./file_ops";
import { SubscribableValue } from "./subscriptions";
import { logger } from "./logging";
import { ControllerConfig } from "./config";
import { safeOutputFile } from "./file_ops.js";
import { SubscribableValue } from "./subscriptions.js";
import { logger } from "./logging.js";
import { ControllerConfig } from "./config/index.js";
type DatastoreKey = string | number;
type DatastoreValue = string | number | boolean | object;
Loaded 100 of 277 files, more files were not shown because too many files have changed in this diff. Show more