UNPKG

@koishijs/loader

Version:
413 lines (411 loc) 14.1 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 __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 __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/shared.ts var shared_exports = {}; __export(shared_exports, { Loader: () => Loader, default: () => shared_default, unwrapExports: () => unwrapExports }); module.exports = __toCommonJS(shared_exports); var import_core = require("@koishijs/core"); var import_fs = require("fs"); var yaml = __toESM(require("js-yaml")); var path = __toESM(require("path")); function unwrapExports(module2) { return module2?.default || module2; } __name(unwrapExports, "unwrapExports"); function separate(source, isGroup = false) { const config = {}, meta = {}; for (const [key, value] of Object.entries(source || {})) { if (key.startsWith("$")) { meta[key] = value; } else { config[key] = value; } } return [isGroup ? source : config, meta]; } __name(separate, "separate"); var kUpdate = Symbol("update"); var group = { name: "group", reusable: true, apply(ctx, plugins) { ctx.scope[Loader.kRecord] ||= /* @__PURE__ */ Object.create(null); for (const name in plugins || {}) { if (name.startsWith("~") || name.startsWith("$")) continue; ctx.loader.reload(ctx, name, plugins[name]); } ctx.accept((neo) => { const old = ctx.scope.config; for (const key in { ...old, ...neo }) { if (key.startsWith("~") || key.startsWith("$")) continue; const fork = ctx.scope[Loader.kRecord][key]; if (!fork) { ctx.loader.reload(ctx, key, neo[key]); } else if (!(key in neo)) { ctx.loader.unload(ctx, key); } else { ctx.loader.reload(ctx, key, neo[key] || {}); } } }, { passive: true }); } }; function insertKey(object, temp, rest) { for (const key of rest) { temp[key] = object[key]; delete object[key]; } Object.assign(object, temp); } __name(insertKey, "insertKey"); function rename(object, old, neo, value) { const keys = Object.keys(object); const index = keys.findIndex((key) => key === old || key === "~" + old); const rest = index < 0 ? [] : keys.slice(index + 1); const temp = { [neo]: value }; delete object[old]; delete object["~" + old]; insertKey(object, temp, rest); } __name(rename, "rename"); var writable = { ".json": "application/json", ".yaml": "application/yaml", ".yml": "application/yaml" }; var Loader = class _Loader { static { __name(this, "Loader"); } static kRecord = Symbol.for("koishi.loader.record"); static exitCode = 51; static extensions = new Set(Object.keys(writable)); // process baseDir = process.cwd(); envData = process.env.KOISHI_SHARED ? JSON.parse(process.env.KOISHI_SHARED) : { startTime: Date.now() }; params = { env: process.env }; app; config; entry; suspend = false; writable = false; mime; filename; envFiles; names = /* @__PURE__ */ new Set(); cache = /* @__PURE__ */ Object.create(null); prolog = []; store = /* @__PURE__ */ new WeakMap(); _writeTask; _writeSlient = true; constructor() { import_core.Logger.targets.push({ colors: 3, record: /* @__PURE__ */ __name((record) => { this.prolog.push(record); this.prolog = this.prolog.slice(-1e3); }, "record") }); } async init(filename) { if (filename) { filename = path.resolve(this.baseDir, filename); const stats = await import_fs.promises.stat(filename); if (stats.isFile()) { this.filename = filename; this.baseDir = path.dirname(filename); const extname2 = path.extname(filename); this.mime = writable[extname2]; if (!_Loader.extensions.has(extname2)) { throw new Error(`extension "${extname2}" not supported`); } } else { this.baseDir = filename; await this.findConfig(); } } else { await this.findConfig(); } if (this.mime) { try { await import_fs.promises.access(this.filename, import_fs.constants.W_OK); this.writable = true; } catch { } } this.envFiles = [ path.resolve(this.baseDir, ".env"), path.resolve(this.baseDir, ".env.local") ]; } async findConfig() { const files = await import_fs.promises.readdir(this.baseDir); for (const basename of ["koishi.config", "koishi"]) { for (const extname2 of _Loader.extensions) { if (files.includes(basename + extname2)) { this.mime = writable[extname2]; this.filename = path.resolve(this.baseDir, basename + extname2); return; } } } throw new Error("config file not found"); } migrateEntry(name, config) { if (name !== "group") return; const backup = { ...config }; for (const key in backup) delete config[key]; for (let key in backup) { if (key.startsWith("$")) { config[key] = backup[key]; continue; } const [prefix] = key.split(":", 1); const name2 = prefix.replace(/^~/, ""); const value = this.migrateEntry(name2, backup[key]) ?? backup[key]; let ident = key.slice(prefix.length + 1); if (!ident || this.names.has(ident)) { ident = Math.random().toString(36).slice(2, 8); key = `${prefix}:${ident}`; } this.names.add(ident); config[key] = value; } } async migrate() { this.migrateEntry("group", this.config.plugins); } async readConfig(initial = false) { if (this.mime === "application/yaml") { this.config = yaml.load(await import_fs.promises.readFile(this.filename, "utf8")); } else if (this.mime === "application/json") { this.config = JSON.parse(await import_fs.promises.readFile(this.filename, "utf8")); } else { const module2 = require(this.filename); this.config = module2.default || module2; } if (initial) await this.migrate(); if (this.writable) await this.writeConfig(true); return new import_core.Context.Config(this.interpolate(this.config)); } async _writeConfig(silent = false) { this.suspend = true; if (!this.writable) { throw new Error(`cannot overwrite readonly config`); } if (this.mime === "application/yaml") { await import_fs.promises.writeFile(this.filename + ".tmp", yaml.dump(this.config)); } else if (this.mime === "application/json") { await import_fs.promises.writeFile(this.filename + ".tmp", JSON.stringify(this.config, null, 2)); } await import_fs.promises.rename(this.filename + ".tmp", this.filename); if (!silent) this.app.emit("config"); } writeConfig(silent = false) { this._writeSlient &&= silent; if (this._writeTask) return this._writeTask; return this._writeTask = new Promise((resolve2, reject) => { setTimeout(() => { this._writeSlient = true; this._writeTask = void 0; this._writeConfig(silent).then(resolve2, reject); }, 0); }); } interpolate(source) { if (typeof source === "string") { return (0, import_core.interpolate)(source, this.params, /\$\{\{(.+?)\}\}/g); } else if (!source || typeof source !== "object") { return source; } else if (Array.isArray(source)) { return source.map((item) => this.interpolate(item)); } else { return (0, import_core.valueMap)(source, (item) => this.interpolate(item)); } } async resolve(name) { const plugin = unwrapExports(await this.import(name)); if (plugin) this.store.set(this.app.registry.resolve(plugin), name); return plugin; } keyFor(plugin) { const name = this.store.get(this.app.registry.resolve(plugin)); if (name) return name.replace(/(koishi-|^@koishijs\/)plugin-/, ""); } replace(oldKey, newKey) { oldKey = this.app.registry.resolve(oldKey); newKey = this.app.registry.resolve(newKey); const name = this.store.get(oldKey); if (!name) return; this.store.set(newKey, name); this.store.delete(oldKey); } async forkPlugin(name, config, parent) { const plugin = await this.resolve(name); if (!plugin) return; return parent.plugin(plugin, this.interpolate(config)); } isTruthyLike(expr) { if ((0, import_core.isNullable)(expr)) return true; return !!this.interpolate(`\${{ ${expr} }}`); } logUpdate(type, parent, key) { this.app.logger("loader").info("%s plugin %c", type, key); } async reload(parent, key, source) { let fork = parent.scope[_Loader.kRecord][key]; const name = key.split(":", 1)[0]; const [config, meta] = separate(source, name === "group"); if (fork) { if (!this.isTruthyLike(meta.$if)) { this.unload(parent, key); return; } fork[kUpdate] = true; fork.update(config); } else { if (!this.isTruthyLike(meta.$if)) return; this.logUpdate("apply", parent, key); const ctx = parent.extend(); if (name === "group") { fork = ctx.plugin(group, config); } else { fork = await this.forkPlugin(name, config, ctx); } if (!fork) return; fork.key = key.slice(name.length + 1); parent.scope[_Loader.kRecord][key] = fork; } const filter = this.interpolate(meta.$filter); fork.parent.filter = (session) => { return parent.filter(session) && ((0, import_core.isNullable)(filter) || session.resolve(filter)); }; return fork; } unload(ctx, key) { const fork = ctx.scope[_Loader.kRecord][key]; if (fork) fork.dispose(); } getRefName(fork) { const record = fork.parent.scope[_Loader.kRecord]; if (!record) return; for (const name in record) { if (record[name] !== fork) continue; return name; } } /** @deprecated */ resolvePlugin(name) { return this.resolve(name); } /** @deprecated */ reloadPlugin(ctx, key, source) { return this.reload(ctx, key, source); } /** @deprecated */ unloadPlugin(ctx, key) { return this.unload(ctx, key); } paths(scope) { if (scope === scope.parent.scope) return []; if (scope.runtime === scope) { return [].concat(...scope.runtime.children.map((child) => this.paths(child))); } if (scope.key) return [scope.key]; return this.paths(scope.parent.scope); } async createApp() { new import_core.Logger("app").info("%C", `Koishi/${import_core.version}`); const app = this.app = new import_core.Context(this.interpolate(this.config)); app.provide("loader", this, true); app.provide("baseDir", this.baseDir, true); app.scope[_Loader.kRecord] = /* @__PURE__ */ Object.create(null); const fork = await this.reload(app, "group:entry", this.config.plugins); this.entry = fork.ctx; app.accept((config) => { app.koishi.config = config; }); app.accept(["plugins"], (config) => { this.reload(app, "group:entry", config.plugins); }, { passive: true }); app.on("dispose", () => { this.fullReload(); }); app.on("internal/fork", (fork2) => { if (fork2.uid || !fork2.parent.scope[_Loader.kRecord]) return; const key = Object.keys(fork2.parent.scope[_Loader.kRecord]).find((key2) => { return fork2.parent.scope[_Loader.kRecord][key2] === fork2; }); if (!key) return; this.logUpdate("unload", fork2.parent, key); delete fork2.parent.scope[_Loader.kRecord][key]; if (!app.registry.has(fork2.runtime.plugin)) return; rename(fork2.parent.scope.config, key, "~" + key, fork2.parent.scope.config[key]); this.writeConfig(); }); app.on("internal/update", (fork2) => { const key = this.getRefName(fork2); if (key) this.logUpdate("reload", fork2.parent, key); }); app.on("internal/before-update", (fork2, config) => { if (fork2[kUpdate]) return delete fork2[kUpdate]; const name = this.getRefName(fork2); if (!name) return; const { schema } = fork2.runtime; fork2.parent.scope.config[name] = { ...separate(fork2.parent.scope.config[name])[1], ...schema ? schema.simplify(config) : config }; this.writeConfig(); }); if (this.envData.message) { const { sid, channelId, guildId, content } = this.envData.message; this.envData.message = null; const dispose = app.on("bot-status-updated", (bot) => { if (bot.sid !== sid || bot.status !== import_core.Universal.Status.ONLINE) return; dispose(); bot.sendMessage(channelId, content, guildId); }); } return app; } }; var shared_default = Loader; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Loader, unwrapExports }); //# sourceMappingURL=shared.js.map