mioku 0.9.2 → 0.9.3
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} +267 -287
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.js +446 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +255 -335
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +237 -322
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +237 -322
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +283 -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,14 +404,11 @@ async function resolveSkillsEntry(pluginPath) {
|
|
|
425
404
|
if (await pathExists(jsPath)) return jsPath;
|
|
426
405
|
return null;
|
|
427
406
|
}
|
|
428
|
-
/**
|
|
429
|
-
* Auto-register plugin help manifests and AI skills
|
|
430
|
-
*/
|
|
431
407
|
async function registerPluginArtifacts(ctx) {
|
|
432
|
-
const enabledPlugins = new Set(
|
|
408
|
+
const enabledPlugins = new Set(mioki.botConfig.plugins ?? []);
|
|
433
409
|
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
|
|
410
|
+
const helpService = ctx.services.help;
|
|
411
|
+
const aiService = ctx.services.ai;
|
|
436
412
|
if (helpService) {
|
|
437
413
|
let helpCount = 0;
|
|
438
414
|
for (const metadata of pluginMetadata) {
|
|
@@ -459,7 +435,7 @@ async function registerPluginArtifacts(ctx) {
|
|
|
459
435
|
skillCount += 1;
|
|
460
436
|
}
|
|
461
437
|
} catch (error) {
|
|
462
|
-
logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error
|
|
438
|
+
logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error}`);
|
|
463
439
|
}
|
|
464
440
|
}
|
|
465
441
|
logger.info(`[plugin-artifacts] Registered ${skillCount} skill(s)`);
|
|
@@ -469,136 +445,97 @@ async function registerPluginArtifacts(ctx) {
|
|
|
469
445
|
//#region src/core/service-config.ts
|
|
470
446
|
const SERVICE_CONFIG_ROOT = "service";
|
|
471
447
|
function resolveConfigDir(serviceName) {
|
|
472
|
-
return
|
|
448
|
+
return path.join(process.cwd(), "config", SERVICE_CONFIG_ROOT, serviceName);
|
|
473
449
|
}
|
|
474
450
|
function resolveConfigPath(serviceName, configName) {
|
|
475
|
-
return
|
|
451
|
+
return path.join(resolveConfigDir(serviceName), `${configName}.json`);
|
|
476
452
|
}
|
|
477
453
|
function ensureDir(serviceName) {
|
|
478
454
|
const dir = resolveConfigDir(serviceName);
|
|
479
|
-
if (!
|
|
455
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
480
456
|
}
|
|
481
|
-
function registerServiceConfig(serviceName, configName, defaults) {
|
|
457
|
+
async function registerServiceConfig(serviceName, configName, defaults) {
|
|
482
458
|
ensureDir(serviceName);
|
|
483
459
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
484
|
-
if (!
|
|
460
|
+
if (!(0, fs.existsSync)(configPath)) await fs_promises.writeFile(configPath, JSON.stringify(defaults, null, 2), "utf-8");
|
|
485
461
|
}
|
|
486
|
-
function getServiceConfig(serviceName, configName) {
|
|
462
|
+
async function getServiceConfig(serviceName, configName) {
|
|
487
463
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
488
464
|
try {
|
|
489
|
-
|
|
490
|
-
} catch {
|
|
491
|
-
|
|
465
|
+
return JSON.parse(await fs_promises.readFile(configPath, "utf-8"));
|
|
466
|
+
} catch (error) {
|
|
467
|
+
if (!(0, fs.existsSync)(configPath)) return {};
|
|
468
|
+
logger.warn(`[service-config] 读取 ${serviceName}/${configName} 失败: ${error}`);
|
|
469
|
+
return {};
|
|
470
|
+
}
|
|
492
471
|
}
|
|
493
|
-
function updateServiceConfig(serviceName, configName, value) {
|
|
472
|
+
async function updateServiceConfig(serviceName, configName, value) {
|
|
494
473
|
ensureDir(serviceName);
|
|
495
|
-
|
|
474
|
+
await fs_promises.writeFile(resolveConfigPath(serviceName, configName), JSON.stringify(value, null, 2), "utf-8");
|
|
496
475
|
}
|
|
497
|
-
function getServiceConfigs(serviceName) {
|
|
476
|
+
async function getServiceConfigs(serviceName) {
|
|
498
477
|
const dir = resolveConfigDir(serviceName);
|
|
499
|
-
if (!
|
|
478
|
+
if (!(0, fs.existsSync)(dir)) return {};
|
|
500
479
|
const result = {};
|
|
501
|
-
const files =
|
|
502
|
-
for (const file of files) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
} catch {}
|
|
480
|
+
const files = (await fs_promises.readdir(dir)).filter((name) => name.endsWith(".json"));
|
|
481
|
+
for (const file of files) try {
|
|
482
|
+
result[path.basename(file, ".json")] = JSON.parse(await fs_promises.readFile(path.join(dir, file), "utf-8"));
|
|
483
|
+
} catch (error) {
|
|
484
|
+
logger.warn(`[service-config] 读取 ${serviceName}/${file} 失败: ${error}`);
|
|
507
485
|
}
|
|
508
486
|
return result;
|
|
509
487
|
}
|
|
510
|
-
function deleteServiceConfig(serviceName, configName) {
|
|
488
|
+
async function deleteServiceConfig(serviceName, configName) {
|
|
511
489
|
const configPath = resolveConfigPath(serviceName, configName);
|
|
512
|
-
if (!
|
|
513
|
-
|
|
490
|
+
if (!(0, fs.existsSync)(configPath)) return false;
|
|
491
|
+
await fs_promises.unlink(configPath);
|
|
514
492
|
return true;
|
|
515
493
|
}
|
|
516
494
|
|
|
517
495
|
//#endregion
|
|
518
|
-
//#region src/
|
|
496
|
+
//#region src/types.ts
|
|
519
497
|
const TOOL_RESULT_FOLLOWUP_KEY = "__miokuFollowup";
|
|
520
498
|
|
|
521
499
|
//#endregion
|
|
522
500
|
//#region src/core/data-paths.ts
|
|
523
|
-
/**
|
|
524
|
-
* Get the data directory for a specific plugin
|
|
525
|
-
* Returns: {cwd}/data/{pluginName}
|
|
526
|
-
*/
|
|
527
501
|
function getPluginDataDir(pluginName) {
|
|
528
|
-
|
|
529
|
-
return dataDir;
|
|
502
|
+
return path.join(process.cwd(), "data", pluginName);
|
|
530
503
|
}
|
|
531
|
-
/**
|
|
532
|
-
* Get the data directory for a service
|
|
533
|
-
* Returns: {cwd}/data/{serviceName}
|
|
534
|
-
*/
|
|
535
504
|
function getServiceDataDir(serviceName) {
|
|
536
|
-
|
|
537
|
-
return dataDir;
|
|
505
|
+
return path.join(process.cwd(), "data", serviceName);
|
|
538
506
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Get the main data directory
|
|
541
|
-
* Returns: {cwd}/data
|
|
542
|
-
*/
|
|
543
507
|
function getDataDir() {
|
|
544
508
|
return path.join(process.cwd(), "data");
|
|
545
509
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Get the config directory for a plugin
|
|
548
|
-
* Returns: {cwd}/config/{pluginName}
|
|
549
|
-
*/
|
|
550
510
|
function getPluginConfigDir(pluginName) {
|
|
551
|
-
|
|
552
|
-
return configDir;
|
|
511
|
+
return path.join(process.cwd(), "config", pluginName);
|
|
553
512
|
}
|
|
554
|
-
/**
|
|
555
|
-
* Get the config directory for a service
|
|
556
|
-
* Returns: {cwd}/config/service/{serviceName}
|
|
557
|
-
*/
|
|
558
513
|
function getServiceConfigDir(serviceName) {
|
|
559
|
-
|
|
560
|
-
return configDir;
|
|
514
|
+
return path.join(process.cwd(), "config", "service", serviceName);
|
|
561
515
|
}
|
|
562
|
-
/**
|
|
563
|
-
* Get the main config directory
|
|
564
|
-
* Returns: {cwd}/config
|
|
565
|
-
*/
|
|
566
516
|
function getConfigDir() {
|
|
567
517
|
return path.join(process.cwd(), "config");
|
|
568
518
|
}
|
|
569
|
-
/**
|
|
570
|
-
* Ensure a directory exists, creating it if necessary
|
|
571
|
-
*/
|
|
572
519
|
function ensureDataDir(pluginName) {
|
|
573
520
|
const dir = getPluginDataDir(pluginName);
|
|
574
|
-
|
|
575
|
-
if (!existsSync$2(dir)) mkdirSync$3(dir, { recursive: true });
|
|
521
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
576
522
|
return dir;
|
|
577
523
|
}
|
|
578
524
|
|
|
579
525
|
//#endregion
|
|
580
526
|
//#region src/core/plugin-runtime-state.ts
|
|
581
|
-
const
|
|
582
|
-
/**
|
|
583
|
-
* Get the runtime state for a plugin
|
|
584
|
-
*/
|
|
527
|
+
const store = getOrCreate("plugin-runtime-state", () => ({}));
|
|
585
528
|
function getPluginRuntimeState(pluginName) {
|
|
586
|
-
if (!
|
|
587
|
-
return
|
|
529
|
+
if (!store[pluginName]) store[pluginName] = {};
|
|
530
|
+
return store[pluginName];
|
|
588
531
|
}
|
|
589
|
-
/**
|
|
590
|
-
* Set/update the runtime state for a plugin
|
|
591
|
-
*/
|
|
592
532
|
function setPluginRuntimeState(pluginName, state) {
|
|
593
|
-
if (!
|
|
594
|
-
Object.assign(
|
|
595
|
-
return
|
|
533
|
+
if (!store[pluginName]) store[pluginName] = {};
|
|
534
|
+
Object.assign(store[pluginName], state);
|
|
535
|
+
return store[pluginName];
|
|
596
536
|
}
|
|
597
|
-
/**
|
|
598
|
-
* Reset the runtime state for a plugin
|
|
599
|
-
*/
|
|
600
537
|
function resetPluginRuntimeState(pluginName) {
|
|
601
|
-
delete
|
|
538
|
+
delete store[pluginName];
|
|
602
539
|
}
|
|
603
540
|
|
|
604
541
|
//#endregion
|
|
@@ -606,48 +543,31 @@ function resetPluginRuntimeState(pluginName) {
|
|
|
606
543
|
function definePlugin(plugin) {
|
|
607
544
|
return plugin;
|
|
608
545
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Start Mioku with plugin and service discovery
|
|
611
|
-
*/
|
|
612
546
|
async function start(options = {}) {
|
|
613
547
|
const { cwd = process.cwd() } = options;
|
|
614
548
|
if (cwd) process.chdir(cwd);
|
|
615
|
-
const { start: startMioki, logger: logger$1, botConfig } = await import("mioki");
|
|
549
|
+
const { start: startMioki, logger: logger$1, botConfig: botConfig$1 } = await import("mioki");
|
|
616
550
|
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
551
|
logger$1.info("こんにちは..");
|
|
624
552
|
logger$1.info("---------------------------------------");
|
|
625
553
|
logger$1.info("---------- Mioku 正在启动 ------------");
|
|
626
554
|
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);
|
|
555
|
+
await bootstrapMioku({
|
|
556
|
+
cwd,
|
|
557
|
+
botConfig: botConfig$1,
|
|
558
|
+
startMioki
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function readVersion() {
|
|
562
|
+
try {
|
|
563
|
+
const here = path.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
564
|
+
const pkgPath = path.join(here, "..", "package.json");
|
|
565
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
|
|
566
|
+
} catch {
|
|
567
|
+
return "0.0.0";
|
|
647
568
|
}
|
|
648
|
-
await startMioki({ cwd });
|
|
649
569
|
}
|
|
650
|
-
const version =
|
|
570
|
+
const version = readVersion();
|
|
651
571
|
|
|
652
572
|
//#endregion
|
|
653
573
|
exports.TOOL_RESULT_FOLLOWUP_KEY = TOOL_RESULT_FOLLOWUP_KEY;
|