@koishijs/plugin-console
Version:
Web User Interface for Koishi
296 lines (293 loc) • 12.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/node/locales/zh-CN.yml
var require_zh_CN = __commonJS({
"src/node/locales/zh-CN.yml"(exports2, module2) {
module2.exports = { root: "前端页面的根目录。", uiPath: "前端页面呈现的路径。", apiPath: "后端 API 服务的路径。", selfUrl: "Koishi 服务暴露在公网的地址。", open: "在应用启动后自动在浏览器中打开控制台。", head: { $desc: "自定义页面头部信息。", $inner: { tag: { $desc: "标签名。", $inner: ["title", "link", "meta", "script", "style", "自定义"] }, attrs: "标签属性。", content: "标签内容。" } }, heartbeat: { interval: "心跳发送间隔 (单位毫秒)。", timeout: "心跳超时时间 (单位毫秒)。" }, devMode: "启用调试模式 (仅供开发者使用)。", cacheDir: "调试服务器缓存目录。" };
}
});
// src/node/index.ts
var node_exports = {};
__export(node_exports, {
default: () => node_default
});
module.exports = __toCommonJS(node_exports);
var import_koishi = require("koishi");
var import_console = require("@koishijs/console");
var import_path = require("path");
var import_fs = require("fs");
var import_open = __toESM(require("open"));
var import_module = require("module");
var import_url = require("url");
__reExport(node_exports, require("@koishijs/console"), module.exports);
var import_meta = {};
var NodeConsole = class extends import_console.Console {
constructor(ctx, config) {
super(ctx);
this.ctx = ctx;
this.config = config;
this.layer = ctx.server.ws(config.apiPath, (socket, request) => {
this.accept(socket, request);
});
ctx.on("console/connection", () => {
const loader = ctx.get("loader");
if (!loader) return;
loader.envData.clientCount = this.layer.clients.size;
});
const base = import_meta.url || (0, import_url.pathToFileURL)(__filename).href;
const require2 = (0, import_module.createRequire)(base);
this.root = config.devMode ? (0, import_path.resolve)(require2.resolve("@koishijs/client/package.json"), "../app") : (0, import_url.fileURLToPath)(new URL("../../dist", base));
}
static {
__name(this, "NodeConsole");
}
static inject = { required: ["server"], optional: ["console"] };
// static inject = ['server']
// workaround for edge case (collision with @koishijs/plugin-config)
_config;
vite;
root;
layer;
// @ts-ignore FIXME
get config() {
return this._config;
}
set config(value) {
this._config = value;
}
createGlobal() {
const global = {};
const { devMode, uiPath, apiPath, selfUrl, heartbeat } = this.config;
global.devMode = devMode;
global.uiPath = uiPath;
global.heartbeat = heartbeat;
global.endpoint = selfUrl + apiPath;
const proxy = this.ctx.get("server.proxy");
if (proxy) global.proxyBase = proxy.config.path + "/";
return global;
}
async start() {
if (this.config.devMode) await this.createVite();
this.serveAssets();
this.ctx.on("server/ready", () => {
let { host, port } = this.ctx.server;
if (["0.0.0.0", "::"].includes(host)) host = "127.0.0.1";
const target = `http://${host}:${port}${this.config.uiPath}`;
if (this.config.open && !this.ctx.get("loader")?.envData.clientCount && !process.env.KOISHI_AGENT) {
(0, import_open.default)(target);
}
this.ctx.logger.info("webui is available at %c", target);
});
}
getFiles(files) {
if (typeof files === "string" || Array.isArray(files)) return files;
if (!this.config.devMode) return files.prod;
if (!(0, import_fs.existsSync)(files.dev)) return files.prod;
return files.dev;
}
resolveEntry(files, key) {
const { devMode, uiPath } = this.config;
const filenames = [];
for (const local of (0, import_koishi.makeArray)(this.getFiles(files))) {
const filename = devMode ? "/vite/@fs/" + local : uiPath + "/@plugin-" + key;
if ((0, import_path.extname)(local)) {
filenames.push(filename);
} else {
filenames.push(filename + "/index.js");
if ((0, import_fs.existsSync)(local + "/style.css")) {
filenames.push(filename + "/style.css");
}
}
}
return filenames;
}
serveAssets() {
const { uiPath } = this.config;
this.ctx.server.get(uiPath + "(.*)", async (ctx, next) => {
await next();
if (ctx.body || ctx.response.body) return;
if (ctx.path === uiPath && !uiPath.endsWith("/")) {
return ctx.redirect(ctx.path + "/");
}
const name = ctx.path.slice(uiPath.length).replace(/^\/+/, "");
const sendFile = /* @__PURE__ */ __name((filename2) => {
ctx.type = (0, import_path.extname)(filename2);
return ctx.body = (0, import_fs.createReadStream)(filename2);
}, "sendFile");
if (name.startsWith("@plugin-")) {
const [key] = name.slice(8).split("/", 1);
if (this.entries[key]) {
const files = (0, import_koishi.makeArray)(this.getFiles(this.entries[key].files));
const filename2 = files[0] + name.slice(8 + key.length);
ctx.type = (0, import_path.extname)(filename2);
if (this.config.devMode || ctx.type !== "application/javascript") {
return sendFile(filename2);
}
const source = await import_fs.promises.readFile(filename2, "utf8");
return ctx.body = await this.transformImport(source);
} else {
return ctx.status = 404;
}
}
const filename = (0, import_path.resolve)(this.root, name);
if (!filename.startsWith(this.root) && !filename.includes("node_modules")) {
return ctx.status = 403;
}
const stats = await import_fs.promises.stat(filename).catch(import_koishi.noop);
if (stats?.isFile()) return sendFile(filename);
const template = await import_fs.promises.readFile((0, import_path.resolve)(this.root, "index.html"), "utf8");
ctx.type = "html";
ctx.body = await this.transformHtml(template);
});
}
async transformImport(source) {
let output = "";
let cap;
while (cap = /((?:^|;)import\b[^'"]+\bfrom\s*)(['"])([^'"]+)\2;/m.exec(source)) {
const [stmt, left, quote, path] = cap;
output += source.slice(0, cap.index) + left + quote + ({
"vue": "../vue.js",
"vue-router": "../vue-router.js",
"@vueuse/core": "../vueuse.js",
"@koishijs/client": "../client.js"
}[path] ?? path) + quote + ";";
source = source.slice(cap.index + stmt.length);
}
return output + source;
}
async transformHtml(template) {
const { uiPath, head = [] } = this.config;
if (this.vite) {
template = await this.vite.transformIndexHtml(uiPath, template);
} else {
template = template.replace(/(href|src)="(?=\/)/g, (_, $1) => `${$1}="${uiPath}`);
}
let headInjection = `<script>KOISHI_CONFIG = ${JSON.stringify(this.createGlobal())}</script>`;
for (const { tag, attrs = {}, content } of head) {
const attrString = Object.entries(attrs).map(([key, value]) => ` ${key}="${import_koishi.h.escape(value ?? "", true)}"`).join("");
headInjection += `<${tag}${attrString}>${content ?? ""}</${tag}>`;
}
return template.replace("<title>", headInjection + "<title>");
}
async createVite() {
const { cacheDir, dev } = this.config;
const { createServer } = require("@koishijs/client/lib");
this.vite = await createServer(this.ctx.baseDir, {
cacheDir: (0, import_path.resolve)(this.ctx.baseDir, cacheDir),
server: {
fs: dev.fs
}
});
this.ctx.server.all("/vite(.*)", (ctx) => new Promise((resolve2) => {
this.vite.middlewares(ctx.req, ctx.res, resolve2);
}));
this.ctx.on("dispose", () => this.vite.close());
}
stop() {
this.layer.close();
}
};
((NodeConsole2) => {
NodeConsole2.Dev = import_koishi.Schema.object({
fs: import_koishi.Schema.object({
strict: import_koishi.Schema.boolean().default(true),
allow: import_koishi.Schema.array(String).default(null),
deny: import_koishi.Schema.array(String).default(null)
}).hidden()
});
NodeConsole2.Head = import_koishi.Schema.intersect([
import_koishi.Schema.object({
tag: import_koishi.Schema.union([
"title",
"link",
"meta",
"script",
"style",
import_koishi.Schema.string()
]).required()
}),
import_koishi.Schema.union([
import_koishi.Schema.object({
tag: import_koishi.Schema.const("title").required(),
content: import_koishi.Schema.string().role("textarea")
}),
import_koishi.Schema.object({
tag: import_koishi.Schema.const("link").required(),
attrs: import_koishi.Schema.dict(import_koishi.Schema.string()).role("table")
}),
import_koishi.Schema.object({
tag: import_koishi.Schema.const("meta").required(),
attrs: import_koishi.Schema.dict(import_koishi.Schema.string()).role("table")
}),
import_koishi.Schema.object({
tag: import_koishi.Schema.const("script").required(),
attrs: import_koishi.Schema.dict(import_koishi.Schema.string()).role("table"),
content: import_koishi.Schema.string().role("textarea")
}),
import_koishi.Schema.object({
tag: import_koishi.Schema.const("style").required(),
attrs: import_koishi.Schema.dict(import_koishi.Schema.string()).role("table"),
content: import_koishi.Schema.string().role("textarea")
}),
import_koishi.Schema.object({
tag: import_koishi.Schema.string().required(),
attrs: import_koishi.Schema.dict(import_koishi.Schema.string()).role("table"),
content: import_koishi.Schema.string().role("textarea")
})
])
]);
NodeConsole2.Config = import_koishi.Schema.intersect([
import_koishi.Schema.object({
uiPath: import_koishi.Schema.string().default(""),
apiPath: import_koishi.Schema.string().default("/status"),
selfUrl: import_koishi.Schema.string().role("link").default(""),
open: import_koishi.Schema.boolean(),
head: import_koishi.Schema.array(NodeConsole2.Head),
heartbeat: import_koishi.Schema.object({
interval: import_koishi.Schema.number().default(import_koishi.Time.second * 30),
timeout: import_koishi.Schema.number().default(import_koishi.Time.minute)
}),
devMode: import_koishi.Schema.boolean().default(process.env.NODE_ENV === "development").hidden(),
cacheDir: import_koishi.Schema.string().default("cache/vite").hidden(),
dev: NodeConsole2.Dev
})
]).i18n({
"zh-CN": require_zh_CN()
});
})(NodeConsole || (NodeConsole = {}));
var node_default = NodeConsole;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
...require("@koishijs/console")
});
//# sourceMappingURL=index.js.map