UNPKG

@koishijs/plugin-console

Version:

Web User Interface for Koishi

296 lines (293 loc) 12.7 kB
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