mioku 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli.cjs → cli/index.cjs} +297 -286
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.js +477 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +259 -335
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +238 -322
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +238 -322
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +287 -363
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js +0 -466
- package/dist/cli.js.map +0 -1
- /package/dist/{cli.d.cts → cli/index.d.cts} +0 -0
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
3
|
-
const node_path = require_chunk.__toESM(require("node:path"));
|
|
4
|
-
const fs_promises = require_chunk.__toESM(require("fs/promises"));
|
|
5
|
-
const path = require_chunk.__toESM(require("path"));
|
|
6
2
|
const fs = require_chunk.__toESM(require("fs"));
|
|
3
|
+
const path = require_chunk.__toESM(require("path"));
|
|
4
|
+
const url = require_chunk.__toESM(require("url"));
|
|
5
|
+
const fs_promises = require_chunk.__toESM(require("fs/promises"));
|
|
6
|
+
const mioki = require_chunk.__toESM(require("mioki"));
|
|
7
7
|
|
|
8
8
|
//#region src/core/logger.ts
|
|
9
9
|
const fallbackLogger = {
|
|
@@ -26,9 +26,8 @@ const logger = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region src/core/
|
|
30
|
-
|
|
31
|
-
async function pathExists$3(filePath) {
|
|
29
|
+
//#region src/core/module-scanner.ts
|
|
30
|
+
async function pathExists(filePath) {
|
|
32
31
|
try {
|
|
33
32
|
await fs_promises.access(filePath);
|
|
34
33
|
return true;
|
|
@@ -36,6 +35,66 @@ async function pathExists$3(filePath) {
|
|
|
36
35
|
return false;
|
|
37
36
|
}
|
|
38
37
|
}
|
|
38
|
+
async function resolveRealpath(p) {
|
|
39
|
+
try {
|
|
40
|
+
return await fs_promises.realpath(p);
|
|
41
|
+
} catch {
|
|
42
|
+
return p;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function toImportPath(filePath) {
|
|
46
|
+
if (process.platform === "win32") return "file:///" + filePath.replace(/\\/g, "/");
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
async function scanLocalDir(dir) {
|
|
50
|
+
const results = [];
|
|
51
|
+
if (!await pathExists(dir)) return results;
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = await fs_promises.readdir(dir, { withFileTypes: true });
|
|
55
|
+
} catch {
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const entryPath = path.join(dir, entry.name);
|
|
60
|
+
try {
|
|
61
|
+
const stat = await fs_promises.stat(entryPath);
|
|
62
|
+
if (!stat.isDirectory()) continue;
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
results.push({
|
|
67
|
+
name: entry.name,
|
|
68
|
+
path: await resolveRealpath(entryPath)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
async function scanNodeModules(prefix) {
|
|
74
|
+
const results = [];
|
|
75
|
+
const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
|
|
76
|
+
if (!await pathExists(nodeModulesPath)) return results;
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
|
|
80
|
+
} catch {
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (!entry.name.startsWith(prefix)) continue;
|
|
85
|
+
const name = entry.name.slice(prefix.length);
|
|
86
|
+
const fullPath = path.join(nodeModulesPath, entry.name);
|
|
87
|
+
results.push({
|
|
88
|
+
name,
|
|
89
|
+
path: await resolveRealpath(fullPath)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/core/plugin-linker.ts
|
|
97
|
+
const DEFAULT_RUNTIME_PLUGINS_DIR = ".mioku/plugins";
|
|
39
98
|
async function removeIfBrokenSymlink(entryPath) {
|
|
40
99
|
let stat;
|
|
41
100
|
try {
|
|
@@ -54,8 +113,7 @@ async function removeIfBrokenSymlink(entryPath) {
|
|
|
54
113
|
}
|
|
55
114
|
}
|
|
56
115
|
function relativeSymlinkTarget(linkPath, targetPath) {
|
|
57
|
-
|
|
58
|
-
return relativePath || ".";
|
|
116
|
+
return path.relative(path.dirname(linkPath), targetPath) || ".";
|
|
59
117
|
}
|
|
60
118
|
async function ensurePluginLink(runtimePluginsDir, metadata) {
|
|
61
119
|
const linkPath = path.join(runtimePluginsDir, metadata.name);
|
|
@@ -80,7 +138,7 @@ async function ensurePluginLink(runtimePluginsDir, metadata) {
|
|
|
80
138
|
logger.warn(`[plugin-linker] ${linkPath} exists and is not a symlink, skip linking ${metadata.name}`);
|
|
81
139
|
return false;
|
|
82
140
|
}
|
|
83
|
-
if (!await pathExists
|
|
141
|
+
if (!await pathExists(targetPath)) {
|
|
84
142
|
logger.warn(`[plugin-linker] Plugin target missing, skip linking ${metadata.name}: ${targetPath}`);
|
|
85
143
|
return false;
|
|
86
144
|
}
|
|
@@ -90,12 +148,11 @@ async function ensurePluginLink(runtimePluginsDir, metadata) {
|
|
|
90
148
|
}
|
|
91
149
|
async function prepareRuntimePluginLinks(plugins, runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR)) {
|
|
92
150
|
await fs_promises.mkdir(runtimePluginsDir, { recursive: true });
|
|
93
|
-
const discoveredNames = new Set(plugins.map((
|
|
151
|
+
const discoveredNames = new Set(plugins.map((p) => p.name));
|
|
94
152
|
const entries = await fs_promises.readdir(runtimePluginsDir, { withFileTypes: true });
|
|
95
153
|
for (const entry of entries) {
|
|
96
154
|
const entryPath = path.join(runtimePluginsDir, entry.name);
|
|
97
|
-
|
|
98
|
-
if (symlinkState !== "ok") continue;
|
|
155
|
+
if (await removeIfBrokenSymlink(entryPath) !== "ok") continue;
|
|
99
156
|
if (entry.isSymbolicLink() && !discoveredNames.has(entry.name)) {
|
|
100
157
|
await fs_promises.rm(entryPath, { force: true });
|
|
101
158
|
logger.info(`[plugin-linker] Removed stale plugin link: ${entry.name}`);
|
|
@@ -107,279 +164,166 @@ async function prepareRuntimePluginLinks(plugins, runtimePluginsDir = path.resol
|
|
|
107
164
|
}
|
|
108
165
|
|
|
109
166
|
//#endregion
|
|
110
|
-
//#region src/core/
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
167
|
+
//#region src/core/registry.ts
|
|
168
|
+
const STORE_SYMBOL = Symbol.for("mioku.globalStore");
|
|
169
|
+
function store$1() {
|
|
170
|
+
const g = globalThis;
|
|
171
|
+
if (!g[STORE_SYMBOL]) g[STORE_SYMBOL] = {};
|
|
172
|
+
return g[STORE_SYMBOL];
|
|
173
|
+
}
|
|
174
|
+
function getOrCreate(key, factory) {
|
|
175
|
+
const s = store$1();
|
|
176
|
+
if (s[key] === void 0) s[key] = factory();
|
|
177
|
+
return s[key];
|
|
119
178
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*/
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/core/plugin-manager.ts
|
|
182
|
+
const PLUGIN_PREFIX = "mioku-plugin-";
|
|
125
183
|
var PluginManager = class PluginManager {
|
|
126
|
-
|
|
184
|
+
plugins = new Map();
|
|
127
185
|
static getInstance() {
|
|
128
|
-
|
|
129
|
-
if (!g[PLUGIN_MANAGER_SYMBOL]) g[PLUGIN_MANAGER_SYMBOL] = new PluginManager();
|
|
130
|
-
return g[PLUGIN_MANAGER_SYMBOL];
|
|
186
|
+
return getOrCreate("plugin-manager", () => new PluginManager());
|
|
131
187
|
}
|
|
132
188
|
async discoverPlugins(miokuConfig = {}) {
|
|
133
|
-
const
|
|
134
|
-
const pluginsDir =
|
|
135
|
-
this.
|
|
136
|
-
if (!await pathExists
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
const nodeModulesPlugins = await this.discoverFromNodeModules();
|
|
143
|
-
discovered.push(...nodeModulesPlugins);
|
|
144
|
-
logger.info(`O.o 发现了 ${this.pluginMetadata.size} 个插件`);
|
|
145
|
-
return Array.from(this.pluginMetadata.values());
|
|
146
|
-
}
|
|
147
|
-
async discoverFromDir(pluginsDir) {
|
|
148
|
-
const discovered = [];
|
|
149
|
-
try {
|
|
150
|
-
const entries = await fs_promises.readdir(pluginsDir, { withFileTypes: true });
|
|
151
|
-
for (const entry of entries) {
|
|
152
|
-
const pluginPath = path.join(pluginsDir, entry.name);
|
|
153
|
-
const metadataPath = await this.resolveDirectoryPath(pluginPath);
|
|
154
|
-
if (!metadataPath) continue;
|
|
155
|
-
const metadata = await this.loadPluginMetadata(entry.name, pluginPath);
|
|
156
|
-
if (metadata) {
|
|
157
|
-
discovered.push(metadata);
|
|
158
|
-
this.pluginMetadata.set(metadata.name, metadata);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
} catch (error) {
|
|
162
|
-
logger.error(`扫描插件目录失败: ${error}`);
|
|
189
|
+
const configuredDir = miokuConfig.plugins_dir;
|
|
190
|
+
const pluginsDir = configuredDir && configuredDir !== DEFAULT_RUNTIME_PLUGINS_DIR ? path.resolve(process.cwd(), configuredDir) : path.resolve(process.cwd(), "plugins");
|
|
191
|
+
this.plugins.clear();
|
|
192
|
+
if (!await pathExists(pluginsDir)) (0, fs.mkdirSync)(pluginsDir, { recursive: true });
|
|
193
|
+
const local = await scanLocalDir(pluginsDir);
|
|
194
|
+
for (const { name, path: p } of local) {
|
|
195
|
+
const metadata = await this.loadPluginMetadata(name, p);
|
|
196
|
+
if (metadata) this.plugins.set(metadata.name, metadata);
|
|
163
197
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
|
|
169
|
-
if (!await pathExists$2(nodeModulesPath)) return discovered;
|
|
170
|
-
try {
|
|
171
|
-
const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
if (!entry.name.startsWith("mioku-plugin-")) continue;
|
|
174
|
-
const pluginName = entry.name.replace(/^mioku-plugin-/, "");
|
|
175
|
-
const pluginPath = path.join(nodeModulesPath, entry.name);
|
|
176
|
-
const metadata = await this.loadPluginMetadata(pluginName, pluginPath);
|
|
177
|
-
if (metadata) {
|
|
178
|
-
discovered.push(metadata);
|
|
179
|
-
this.pluginMetadata.set(metadata.name, metadata);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
} catch (error) {
|
|
183
|
-
logger.debug(`扫描 node_modules 插件失败: ${error}`);
|
|
184
|
-
}
|
|
185
|
-
return discovered;
|
|
186
|
-
}
|
|
187
|
-
async resolveDirectoryPath(entryPath) {
|
|
188
|
-
try {
|
|
189
|
-
const stat = await fs_promises.stat(entryPath);
|
|
190
|
-
return stat.isDirectory() ? entryPath : null;
|
|
191
|
-
} catch {
|
|
192
|
-
return null;
|
|
198
|
+
const external = await scanNodeModules(PLUGIN_PREFIX);
|
|
199
|
+
for (const { name, path: p } of external) {
|
|
200
|
+
const metadata = await this.loadPluginMetadata(name, p);
|
|
201
|
+
if (metadata) this.plugins.set(metadata.name, metadata);
|
|
193
202
|
}
|
|
203
|
+
logger.info(`O.o 发现了 ${this.plugins.size} 个插件`);
|
|
204
|
+
return [...this.plugins.values()];
|
|
194
205
|
}
|
|
195
206
|
async loadPluginMetadata(name, pluginPath) {
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
resolvedPath = await fs_promises.realpath(pluginPath);
|
|
199
|
-
} catch {}
|
|
200
|
-
const packageJsonPath = path.join(resolvedPath, "package.json");
|
|
207
|
+
const resolvedPath = await resolveRealpath(pluginPath);
|
|
201
208
|
let packageJson = null;
|
|
202
209
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
packageJson = JSON.parse(await fs_promises.readFile(path.join(resolvedPath, "package.json"), "utf-8"));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.warn(`[plugin-manager] 读取 ${name} 的 package.json 失败: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
const config = packageJson?.mioku ?? {};
|
|
215
|
+
return {
|
|
207
216
|
name,
|
|
208
|
-
version: packageJson?.version
|
|
217
|
+
version: packageJson?.version ?? "0.0.0",
|
|
209
218
|
description: packageJson?.description,
|
|
210
219
|
path: resolvedPath,
|
|
211
|
-
packageJson,
|
|
212
|
-
config
|
|
220
|
+
packageJson: packageJson ?? {},
|
|
221
|
+
config
|
|
213
222
|
};
|
|
214
|
-
return metadata;
|
|
215
223
|
}
|
|
216
224
|
collectRequiredServices() {
|
|
217
225
|
const services = new Set();
|
|
218
|
-
for (const metadata of this.
|
|
226
|
+
for (const metadata of this.plugins.values()) for (const service of metadata.config.services ?? []) services.add(service);
|
|
219
227
|
return services;
|
|
220
228
|
}
|
|
221
229
|
getPluginMetadata(name) {
|
|
222
|
-
return this.
|
|
230
|
+
return this.plugins.get(name);
|
|
223
231
|
}
|
|
224
232
|
getAllMetadata() {
|
|
225
|
-
return
|
|
233
|
+
return [...this.plugins.values()];
|
|
226
234
|
}
|
|
227
235
|
reset() {
|
|
228
|
-
this.
|
|
236
|
+
this.plugins.clear();
|
|
229
237
|
}
|
|
230
238
|
};
|
|
231
239
|
var plugin_manager_default = PluginManager.getInstance();
|
|
232
240
|
|
|
233
241
|
//#endregion
|
|
234
242
|
//#region src/core/service-manager.ts
|
|
235
|
-
const
|
|
236
|
-
async function pathExists$1(filePath) {
|
|
237
|
-
try {
|
|
238
|
-
await fs_promises.access(filePath);
|
|
239
|
-
return true;
|
|
240
|
-
} catch {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* 服务管理器
|
|
246
|
-
*/
|
|
243
|
+
const SERVICE_PREFIX = "mioku-service-";
|
|
247
244
|
var ServiceManager = class ServiceManager {
|
|
248
245
|
services = new Map();
|
|
249
246
|
serviceMetadata = new Map();
|
|
250
|
-
servicesDir = "services";
|
|
251
247
|
static getInstance() {
|
|
252
|
-
|
|
253
|
-
if (!g[SERVICE_MANAGER_SYMBOL]) g[SERVICE_MANAGER_SYMBOL] = new ServiceManager();
|
|
254
|
-
return g[SERVICE_MANAGER_SYMBOL];
|
|
248
|
+
return getOrCreate("service-manager", () => new ServiceManager());
|
|
255
249
|
}
|
|
256
250
|
async discoverServices(miokuConfig = {}) {
|
|
257
|
-
|
|
258
|
-
else this.servicesDir = path.resolve(process.cwd(), "services");
|
|
259
|
-
const discovered = [];
|
|
251
|
+
const servicesDir = miokuConfig.services_dir ? path.resolve(process.cwd(), miokuConfig.services_dir) : path.resolve(process.cwd(), "services");
|
|
260
252
|
this.serviceMetadata.clear();
|
|
261
|
-
if ((0, fs.existsSync)(
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
logger.info(`o.O 发现了 ${this.serviceMetadata.size} 个服务`);
|
|
267
|
-
return Array.from(this.serviceMetadata.values());
|
|
268
|
-
}
|
|
269
|
-
async discoverFromDir(servicesDir) {
|
|
270
|
-
const discovered = [];
|
|
271
|
-
try {
|
|
272
|
-
const entries = await fs_promises.readdir(servicesDir, { withFileTypes: true });
|
|
273
|
-
for (const entry of entries) {
|
|
274
|
-
if (!entry.isDirectory()) continue;
|
|
275
|
-
const servicePath = path.join(servicesDir, entry.name);
|
|
276
|
-
const metadata = await this.loadServiceMetadata(entry.name, servicePath);
|
|
277
|
-
if (metadata) {
|
|
278
|
-
discovered.push(metadata);
|
|
279
|
-
this.serviceMetadata.set(entry.name, metadata);
|
|
280
|
-
}
|
|
253
|
+
if ((0, fs.existsSync)(servicesDir)) {
|
|
254
|
+
const local = await scanLocalDir(servicesDir);
|
|
255
|
+
for (const { name, path: p } of local) {
|
|
256
|
+
const metadata = await this.loadServiceMetadata(name, p);
|
|
257
|
+
if (metadata) this.serviceMetadata.set(name, metadata);
|
|
281
258
|
}
|
|
282
|
-
}
|
|
283
|
-
|
|
259
|
+
} else (0, fs.mkdirSync)(servicesDir, { recursive: true });
|
|
260
|
+
const external = await scanNodeModules(SERVICE_PREFIX);
|
|
261
|
+
for (const { name, path: p } of external) {
|
|
262
|
+
const metadata = await this.loadServiceMetadata(name, p);
|
|
263
|
+
if (metadata) this.serviceMetadata.set(name, metadata);
|
|
284
264
|
}
|
|
285
|
-
|
|
265
|
+
logger.info(`o.O 发现了 ${this.serviceMetadata.size} 个服务`);
|
|
266
|
+
return [...this.serviceMetadata.values()];
|
|
286
267
|
}
|
|
287
268
|
async loadServiceMetadata(name, servicePath) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
resolvedPath = await fs_promises.realpath(servicePath);
|
|
291
|
-
} catch {}
|
|
292
|
-
const packageJsonPath = path.join(resolvedPath, "package.json");
|
|
293
|
-
if (!await pathExists$1(packageJsonPath)) return null;
|
|
269
|
+
const packageJsonPath = path.join(servicePath, "package.json");
|
|
270
|
+
if (!await pathExists(packageJsonPath)) return null;
|
|
294
271
|
try {
|
|
295
272
|
const packageJson = JSON.parse(await fs_promises.readFile(packageJsonPath, "utf-8"));
|
|
296
|
-
|
|
273
|
+
return {
|
|
297
274
|
name,
|
|
298
|
-
version: packageJson.version
|
|
275
|
+
version: packageJson.version ?? "0.0.0",
|
|
299
276
|
description: packageJson.description,
|
|
300
|
-
path:
|
|
277
|
+
path: servicePath,
|
|
301
278
|
packageJson
|
|
302
279
|
};
|
|
303
|
-
return metadata;
|
|
304
280
|
} catch (error) {
|
|
305
|
-
logger.
|
|
281
|
+
logger.warn(`[service-manager] 解析服务 ${name} 失败: ${error}`);
|
|
306
282
|
return null;
|
|
307
283
|
}
|
|
308
284
|
}
|
|
309
|
-
|
|
310
|
-
* Load built-in services from the package
|
|
311
|
-
*/
|
|
312
|
-
async loadBuiltinServices() {
|
|
313
|
-
await this.discoverFromNodeModules();
|
|
314
|
-
}
|
|
315
|
-
async discoverFromNodeModules() {
|
|
316
|
-
const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
|
|
317
|
-
if (!await pathExists$1(nodeModulesPath)) return;
|
|
318
|
-
try {
|
|
319
|
-
const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
|
|
320
|
-
for (const entry of entries) {
|
|
321
|
-
if (!entry.name.startsWith("mioku-service-")) continue;
|
|
322
|
-
const serviceName = entry.name.replace(/^mioku-service-/, "");
|
|
323
|
-
const servicePath = path.join(nodeModulesPath, entry.name);
|
|
324
|
-
const metadata = await this.loadServiceMetadata(serviceName, servicePath);
|
|
325
|
-
if (metadata) this.serviceMetadata.set(serviceName, metadata);
|
|
326
|
-
}
|
|
327
|
-
} catch (error) {
|
|
328
|
-
logger.debug(`扫描 node_modules 服务失败: ${error}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
async checkMissingServices(requiredServices) {
|
|
285
|
+
async checkMissingServices(required) {
|
|
332
286
|
const missing = [];
|
|
333
|
-
for (const
|
|
287
|
+
for (const name of required) if (!this.serviceMetadata.has(name)) missing.push(name);
|
|
334
288
|
return missing;
|
|
335
289
|
}
|
|
336
290
|
async loadAllServices(ctx) {
|
|
337
|
-
const
|
|
338
|
-
logger.info(`O.o 准备加载 ${
|
|
339
|
-
for (const metadata of
|
|
291
|
+
const all = [...this.serviceMetadata.values()];
|
|
292
|
+
logger.info(`O.o 准备加载 ${all.length} 个服务...`);
|
|
293
|
+
for (const metadata of all) await this.loadService(metadata, ctx);
|
|
340
294
|
}
|
|
341
295
|
async loadService(metadata, ctx) {
|
|
342
296
|
try {
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (!entryPoint || !indexExists && !indexJsExists) {
|
|
349
|
-
logger.error(`服务 ${metadata.name} 入口丢失`);
|
|
297
|
+
const tsEntry = path.join(metadata.path, "index.ts");
|
|
298
|
+
const jsEntry = path.join(metadata.path, "index.js");
|
|
299
|
+
const entry = await pathExists(tsEntry) ? tsEntry : await pathExists(jsEntry) ? jsEntry : null;
|
|
300
|
+
if (!entry) {
|
|
301
|
+
logger.error(`[service-manager] 服务 ${metadata.name} 入口丢失`);
|
|
350
302
|
return false;
|
|
351
303
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
await service.init();
|
|
358
|
-
if (service.api) {
|
|
359
|
-
if (!ctx.services) ctx.services = {};
|
|
360
|
-
ctx.services[metadata.name] = service.api;
|
|
304
|
+
const serviceModule = await import(toImportPath(entry));
|
|
305
|
+
const service = serviceModule.default ?? serviceModule;
|
|
306
|
+
if (!service || typeof service.init !== "function") {
|
|
307
|
+
logger.warn(`[service-manager] 服务 ${metadata.name} 无效:缺少 init()`);
|
|
308
|
+
return false;
|
|
361
309
|
}
|
|
310
|
+
await service.init();
|
|
311
|
+
if (service.api) ctx.services[metadata.name] = service.api;
|
|
362
312
|
this.services.set(metadata.name, service);
|
|
363
313
|
return true;
|
|
364
314
|
} catch (error) {
|
|
365
|
-
logger.error(
|
|
315
|
+
logger.error(`[service-manager] 加载服务 ${metadata.name} 失败: ${error}`);
|
|
366
316
|
return false;
|
|
367
317
|
}
|
|
368
318
|
}
|
|
369
|
-
/**
|
|
370
|
-
* Register a builtin service directly
|
|
371
|
-
*/
|
|
372
319
|
registerBuiltinService(name, service) {
|
|
373
320
|
this.services.set(name, service);
|
|
374
321
|
}
|
|
375
|
-
/**
|
|
376
|
-
* Get a loaded service
|
|
377
|
-
*/
|
|
378
322
|
getService(name) {
|
|
379
323
|
return this.services.get(name);
|
|
380
324
|
}
|
|
381
325
|
async disposeAll() {
|
|
382
|
-
for (const [
|
|
326
|
+
for (const [, service] of this.services) await service.dispose?.();
|
|
383
327
|
this.services.clear();
|
|
384
328
|
}
|
|
385
329
|
reset() {
|
|
@@ -390,21 +334,56 @@ var ServiceManager = class ServiceManager {
|
|
|
390
334
|
var service_manager_default = ServiceManager.getInstance();
|
|
391
335
|
|
|
392
336
|
//#endregion
|
|
393
|
-
//#region src/core/
|
|
394
|
-
async function
|
|
337
|
+
//#region src/core/bootstrap.ts
|
|
338
|
+
async function readMiokuConfig() {
|
|
339
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
395
340
|
try {
|
|
396
|
-
await fs_promises.
|
|
397
|
-
return
|
|
341
|
+
const pkg = JSON.parse(await fs_promises.readFile(packageJsonPath, "utf-8"));
|
|
342
|
+
return pkg.mioki ?? {};
|
|
398
343
|
} catch {
|
|
399
|
-
return
|
|
344
|
+
return {};
|
|
400
345
|
}
|
|
401
346
|
}
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
|
|
347
|
+
function ensureRuntimeDirectories() {
|
|
348
|
+
for (const dir of [
|
|
349
|
+
"data",
|
|
350
|
+
"config",
|
|
351
|
+
"temp"
|
|
352
|
+
]) if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
353
|
+
}
|
|
354
|
+
async function discoverAndLinkPlugins(miokuConfig) {
|
|
355
|
+
logger.info("O.o Miku 正在翻找插件..");
|
|
356
|
+
const discovered = await plugin_manager_default.discoverPlugins(miokuConfig);
|
|
357
|
+
logger.info(`O.o 共发现 ${discovered.length} 个插件: ${discovered.map((p) => p.name).join(", ")}`);
|
|
358
|
+
const runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR);
|
|
359
|
+
return prepareRuntimePluginLinks(discovered, runtimePluginsDir);
|
|
405
360
|
}
|
|
361
|
+
async function discoverAndValidateServices(miokuConfig) {
|
|
362
|
+
logger.info("o.O Miku 正在翻找服务..");
|
|
363
|
+
await service_manager_default.discoverServices(miokuConfig);
|
|
364
|
+
const requiredServices = plugin_manager_default.collectRequiredServices();
|
|
365
|
+
const missing = await service_manager_default.checkMissingServices(requiredServices);
|
|
366
|
+
if (missing.length > 0) logger.warn(`发现缺失服务: ${missing.join(", ")}`);
|
|
367
|
+
}
|
|
368
|
+
function applyPluginAllowlist(miokuConfig, botConfig$1, linkedPluginNames) {
|
|
369
|
+
botConfig$1.plugins_dir = DEFAULT_RUNTIME_PLUGINS_DIR;
|
|
370
|
+
if (miokuConfig.plugins !== void 0) return;
|
|
371
|
+
for (const name of linkedPluginNames) if (!botConfig$1.plugins.includes(name)) botConfig$1.plugins.push(name);
|
|
372
|
+
}
|
|
373
|
+
async function bootstrapMioku(deps) {
|
|
374
|
+
const { cwd, botConfig: botConfig$1, startMioki } = deps;
|
|
375
|
+
const miokuConfig = await readMiokuConfig();
|
|
376
|
+
ensureRuntimeDirectories();
|
|
377
|
+
const linkedPluginNames = await discoverAndLinkPlugins(miokuConfig);
|
|
378
|
+
await discoverAndValidateServices(miokuConfig);
|
|
379
|
+
applyPluginAllowlist(miokuConfig, botConfig$1, linkedPluginNames);
|
|
380
|
+
await startMioki({ cwd });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/core/plugin-artifact-registry.ts
|
|
406
385
|
function isAISkill(value) {
|
|
407
|
-
return value && typeof value === "object" && typeof value.name === "string" && Array.isArray(value.tools);
|
|
386
|
+
return !!value && typeof value === "object" && typeof value.name === "string" && Array.isArray(value.tools);
|
|
408
387
|
}
|
|
409
388
|
function extractSkills(moduleExports) {
|
|
410
389
|
const candidates = [
|
|
@@ -425,18 +404,19 @@ async function resolveSkillsEntry(pluginPath) {
|
|
|
425
404
|
if (await pathExists(jsPath)) return jsPath;
|
|
426
405
|
return null;
|
|
427
406
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
407
|
+
function hasPublicKeyword(keywords) {
|
|
408
|
+
return Array.isArray(keywords) && keywords.includes("mioku");
|
|
409
|
+
}
|
|
431
410
|
async function registerPluginArtifacts(ctx) {
|
|
432
|
-
const enabledPlugins = new Set(
|
|
411
|
+
const enabledPlugins = new Set(mioki.botConfig.plugins ?? []);
|
|
433
412
|
const pluginMetadata = plugin_manager_default.getAllMetadata().filter((metadata) => enabledPlugins.size > 0 ? enabledPlugins.has(metadata.name) : true);
|
|
434
|
-
const helpService = ctx.services
|
|
435
|
-
const aiService = ctx.services
|
|
413
|
+
const helpService = ctx.services.help;
|
|
414
|
+
const aiService = ctx.services.ai;
|
|
436
415
|
if (helpService) {
|
|
437
416
|
let helpCount = 0;
|
|
438
417
|
for (const metadata of pluginMetadata) {
|
|
439
418
|
if (!metadata.config.help) continue;
|
|
419
|
+
if (!hasPublicKeyword(metadata.packageJson?.keywords)) continue;
|
|
440
420
|
helpService.registerHelp(metadata.name, metadata.config.help);
|
|
441
421
|
helpCount += 1;
|
|
442
422
|
}
|
|
@@ -459,7 +439,7 @@ async function registerPluginArtifacts(ctx) {
|
|
|
459
439
|
skillCount += 1;
|
|
460
440
|
}
|
|
461
441
|
} catch (error) {
|
|
462
|
-
logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error
|
|
442
|
+
logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error}`);
|
|
463
443
|
}
|
|
464
444
|
}
|
|
465
445
|
logger.info(`[plugin-artifacts] Registered ${skillCount} skill(s)`);
|
|
@@ -469,136 +449,97 @@ async function registerPluginArtifacts(ctx) {
|
|
|
469
449
|
//#region src/core/service-config.ts
|
|
470
450
|
const SERVICE_CONFIG_ROOT = "service";
|
|
471
451
|
function resolveConfigDir(serviceName) {
|
|
472
|
-
return
|
|
452
|
+
return path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
|
|
473
453
|
}
|
|
474
454
|
function resolveConfigPath(serviceName, configName) {
|
|
475
|
-
return
|
|
455
|
+
return path.join(resolveConfigDir(serviceName), `${configName}.json`);
|
|
476
456
|
}
|
|
477
457
|
function ensureDir(serviceName) {
|
|
478
458
|
const dir = resolveConfigDir(serviceName);
|
|
479
|
-
if (!
|
|
459
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
480
460
|
}
|
|
481
|
-
function registerServiceConfig(serviceName, configName, defaults) {
|
|
461
|
+
async function registerServiceConfig(serviceName, configName, defaults) {
|
|
482
462
|
ensureDir(serviceName);
|
|
483
463
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
484
|
-
if (!
|
|
464
|
+
if (!(0, fs.existsSync)(configPath)) await fs_promises.writeFile(configPath, JSON.stringify(defaults, null, 2), "utf-8");
|
|
485
465
|
}
|
|
486
|
-
function getServiceConfig(serviceName, configName) {
|
|
466
|
+
async function getServiceConfig(serviceName, configName) {
|
|
487
467
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
488
468
|
try {
|
|
489
|
-
|
|
490
|
-
} catch {
|
|
491
|
-
|
|
469
|
+
return JSON.parse(await fs_promises.readFile(configPath, "utf-8"));
|
|
470
|
+
} catch (error) {
|
|
471
|
+
if (!(0, fs.existsSync)(configPath)) return {};
|
|
472
|
+
logger.warn(`[service-config] 读取 ${serviceName}/${configName} 失败: ${error}`);
|
|
473
|
+
return {};
|
|
474
|
+
}
|
|
492
475
|
}
|
|
493
|
-
function updateServiceConfig(serviceName, configName, value) {
|
|
476
|
+
async function updateServiceConfig(serviceName, configName, value) {
|
|
494
477
|
ensureDir(serviceName);
|
|
495
|
-
|
|
478
|
+
await fs_promises.writeFile(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
|
|
496
479
|
}
|
|
497
|
-
function getServiceConfigs(serviceName) {
|
|
480
|
+
async function getServiceConfigs(serviceName) {
|
|
498
481
|
const dir = resolveConfigDir(serviceName);
|
|
499
|
-
if (!
|
|
482
|
+
if (!(0, fs.existsSync)(dir)) return {};
|
|
500
483
|
const result = {};
|
|
501
|
-
const files =
|
|
502
|
-
for (const file of files) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
} catch {}
|
|
484
|
+
const files = (await fs_promises.readdir(dir)).filter((name) => name.endsWith(".json"));
|
|
485
|
+
for (const file of files) try {
|
|
486
|
+
result[path.basename(file, ".json")] = JSON.parse(await fs_promises.readFile(path.join(dir, file), "utf-8"));
|
|
487
|
+
} catch (error) {
|
|
488
|
+
logger.warn(`[service-config] 读取 ${serviceName}/${file} 失败: ${error}`);
|
|
507
489
|
}
|
|
508
490
|
return result;
|
|
509
491
|
}
|
|
510
|
-
function deleteServiceConfig(serviceName, configName) {
|
|
492
|
+
async function deleteServiceConfig(serviceName, configName) {
|
|
511
493
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
512
|
-
if (!
|
|
513
|
-
|
|
494
|
+
if (!(0, fs.existsSync)(configPath)) return false;
|
|
495
|
+
await fs_promises.unlink(configPath);
|
|
514
496
|
return true;
|
|
515
497
|
}
|
|
516
498
|
|
|
517
499
|
//#endregion
|
|
518
|
-
//#region src/
|
|
500
|
+
//#region src/types.ts
|
|
519
501
|
const TOOL_RESULT_FOLLOWUP_KEY = "__miokuFollowup";
|
|
520
502
|
|
|
521
503
|
//#endregion
|
|
522
504
|
//#region src/core/data-paths.ts
|
|
523
|
-
/**
|
|
524
|
-
* Get the data directory for a specific plugin
|
|
525
|
-
* Returns: {cwd}/data/{pluginName}
|
|
526
|
-
*/
|
|
527
505
|
function getPluginDataDir(pluginName) {
|
|
528
|
-
|
|
529
|
-
return dataDir;
|
|
506
|
+
return path.join(process.cwd(), "data", pluginName);
|
|
530
507
|
}
|
|
531
|
-
/**
|
|
532
|
-
* Get the data directory for a service
|
|
533
|
-
* Returns: {cwd}/data/{serviceName}
|
|
534
|
-
*/
|
|
535
508
|
function getServiceDataDir(serviceName) {
|
|
536
|
-
|
|
537
|
-
return dataDir;
|
|
509
|
+
return path.join(process.cwd(), "data", serviceName);
|
|
538
510
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Get the main data directory
|
|
541
|
-
* Returns: {cwd}/data
|
|
542
|
-
*/
|
|
543
511
|
function getDataDir() {
|
|
544
512
|
return path.join(process.cwd(), "data");
|
|
545
513
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Get the config directory for a plugin
|
|
548
|
-
* Returns: {cwd}/config/{pluginName}
|
|
549
|
-
*/
|
|
550
514
|
function getPluginConfigDir(pluginName) {
|
|
551
|
-
|
|
552
|
-
return configDir;
|
|
515
|
+
return path.join(process.cwd(), "config", pluginName);
|
|
553
516
|
}
|
|
554
|
-
/**
|
|
555
|
-
* Get the config directory for a service
|
|
556
|
-
* Returns: {cwd}/config/service/{serviceName}
|
|
557
|
-
*/
|
|
558
517
|
function getServiceConfigDir(serviceName) {
|
|
559
|
-
|
|
560
|
-
return configDir;
|
|
518
|
+
return path.join(process.cwd(), "config", "service", serviceName);
|
|
561
519
|
}
|
|
562
|
-
/**
|
|
563
|
-
* Get the main config directory
|
|
564
|
-
* Returns: {cwd}/config
|
|
565
|
-
*/
|
|
566
520
|
function getConfigDir() {
|
|
567
521
|
return path.join(process.cwd(), "config");
|
|
568
522
|
}
|
|
569
|
-
/**
|
|
570
|
-
* Ensure a directory exists, creating it if necessary
|
|
571
|
-
*/
|
|
572
523
|
function ensureDataDir(pluginName) {
|
|
573
524
|
const dir = getPluginDataDir(pluginName);
|
|
574
|
-
|
|
575
|
-
if (!existsSync$2(dir)) mkdirSync$3(dir, { recursive: true });
|
|
525
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
576
526
|
return dir;
|
|
577
527
|
}
|
|
578
528
|
|
|
579
529
|
//#endregion
|
|
580
530
|
//#region src/core/plugin-runtime-state.ts
|
|
581
|
-
const
|
|
582
|
-
/**
|
|
583
|
-
* Get the runtime state for a plugin
|
|
584
|
-
*/
|
|
531
|
+
const store = getOrCreate("plugin-runtime-state", () => ({}));
|
|
585
532
|
function getPluginRuntimeState(pluginName) {
|
|
586
|
-
if (!
|
|
587
|
-
return
|
|
533
|
+
if (!store[pluginName]) store[pluginName] = {};
|
|
534
|
+
return store[pluginName];
|
|
588
535
|
}
|
|
589
|
-
/**
|
|
590
|
-
* Set/update the runtime state for a plugin
|
|
591
|
-
*/
|
|
592
536
|
function setPluginRuntimeState(pluginName, state) {
|
|
593
|
-
if (!
|
|
594
|
-
Object.assign(
|
|
595
|
-
return
|
|
537
|
+
if (!store[pluginName]) store[pluginName] = {};
|
|
538
|
+
Object.assign(store[pluginName], state);
|
|
539
|
+
return store[pluginName];
|
|
596
540
|
}
|
|
597
|
-
/**
|
|
598
|
-
* Reset the runtime state for a plugin
|
|
599
|
-
*/
|
|
600
541
|
function resetPluginRuntimeState(pluginName) {
|
|
601
|
-
delete
|
|
542
|
+
delete store[pluginName];
|
|
602
543
|
}
|
|
603
544
|
|
|
604
545
|
//#endregion
|
|
@@ -606,48 +547,31 @@ function resetPluginRuntimeState(pluginName) {
|
|
|
606
547
|
function definePlugin(plugin) {
|
|
607
548
|
return plugin;
|
|
608
549
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Start Mioku with plugin and service discovery
|
|
611
|
-
*/
|
|
612
550
|
async function start(options = {}) {
|
|
613
551
|
const { cwd = process.cwd() } = options;
|
|
614
552
|
if (cwd) process.chdir(cwd);
|
|
615
|
-
const { start: startMioki, logger: logger$1, botConfig } = await import("mioki");
|
|
553
|
+
const { start: startMioki, logger: logger$1, botConfig: botConfig$1 } = await import("mioki");
|
|
616
554
|
setMiokuLogger(logger$1);
|
|
617
|
-
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
618
|
-
let miokuConfig = {};
|
|
619
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
620
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
621
|
-
miokuConfig = pkg.mioki || {};
|
|
622
|
-
}
|
|
623
555
|
logger$1.info("こんにちは..");
|
|
624
556
|
logger$1.info("---------------------------------------");
|
|
625
557
|
logger$1.info("---------- Mioku 正在启动 ------------");
|
|
626
558
|
logger$1.info("---------------------------------------");
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
await service_manager_default.discoverServices(miokuConfig);
|
|
641
|
-
const requiredServices = plugin_manager_default.collectRequiredServices();
|
|
642
|
-
const missingServices = await service_manager_default.checkMissingServices(requiredServices);
|
|
643
|
-
if (missingServices.length > 0) logger$1.warn(`发现缺失服务: ${missingServices.join(", ")}`);
|
|
644
|
-
const userSpecifiedPlugins = miokuConfig.plugins !== void 0;
|
|
645
|
-
if (!userSpecifiedPlugins) {
|
|
646
|
-
for (const name of linkedPluginNames) if (!botConfig.plugins.includes(name)) botConfig.plugins.push(name);
|
|
559
|
+
await bootstrapMioku({
|
|
560
|
+
cwd,
|
|
561
|
+
botConfig: botConfig$1,
|
|
562
|
+
startMioki
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
function readVersion() {
|
|
566
|
+
try {
|
|
567
|
+
const here = path.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
568
|
+
const pkgPath = path.join(here, "..", "package.json");
|
|
569
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
|
|
570
|
+
} catch {
|
|
571
|
+
return "0.0.0";
|
|
647
572
|
}
|
|
648
|
-
await startMioki({ cwd });
|
|
649
573
|
}
|
|
650
|
-
const version =
|
|
574
|
+
const version = readVersion();
|
|
651
575
|
|
|
652
576
|
//#endregion
|
|
653
577
|
exports.TOOL_RESULT_FOLLOWUP_KEY = TOOL_RESULT_FOLLOWUP_KEY;
|