mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Merge branch 'master' into feature/plugin-install
This commit is contained in:
277 files changed
+2557
-2079
No files matched your search
@@ -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();
|
||||
}
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"private": true,
|
||||
"files": [],
|
||||
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"watch": "tsc --build --watch",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
"type": "module",
|
||||
"main": "dist/node/index.js",
|
||||
"scripts": {
|
||||
"prepare": "tsc --build && webpack-cli --mode production",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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"))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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,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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
"type": "module",
|
||||
"main": "create.js",
|
||||
"bin": {
|
||||
"clusteriocreate": "./create.js"
|
||||
|
||||
+11
-13
@@ -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
|
||||
|
||||
File renamed without changes.
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,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,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 {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
"type": "module",
|
||||
"main": "dist/node/index.js",
|
||||
"scripts": {
|
||||
"prepare": "tsc --build",
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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,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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,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: " | " });
|
||||
|
||||
|
||||
@@ -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,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: " | " });
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
"type": "module",
|
||||
"main": "dist/node/index.js",
|
||||
"scripts": {
|
||||
"prepare": "tsc --build",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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+$/, ""),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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": {
|
||||
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,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
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
* @author Hornwitser
|
||||
* @module lib/config
|
||||
*/
|
||||
export * from "./classes";
|
||||
export * from "./definitions";
|
||||
export * from "./classes.js";
|
||||
export * from "./definitions.js";
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,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,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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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
Reference in new issue
Block a user