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