@zhin.js/core 1.0.6 → 1.0.8
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/CHANGELOG.md +17 -0
- package/lib/adapter.d.ts +8 -6
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +13 -7
- package/lib/adapter.js.map +1 -1
- package/lib/app.d.ts +72 -14
- package/lib/app.d.ts.map +1 -1
- package/lib/app.js +241 -83
- package/lib/app.js.map +1 -1
- package/lib/bot.d.ts +10 -8
- package/lib/bot.d.ts.map +1 -1
- package/lib/config.d.ts +44 -14
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +275 -208
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/log-transport.js +1 -1
- package/lib/log-transport.js.map +1 -1
- package/lib/models/system-log.d.ts +2 -2
- package/lib/models/system-log.d.ts.map +1 -1
- package/lib/models/system-log.js +1 -1
- package/lib/models/system-log.js.map +1 -1
- package/lib/models/user.d.ts +2 -2
- package/lib/models/user.d.ts.map +1 -1
- package/lib/models/user.js +1 -1
- package/lib/models/user.js.map +1 -1
- package/lib/plugin.d.ts +7 -3
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +16 -5
- package/lib/plugin.js.map +1 -1
- package/lib/prompt.d.ts +1 -1
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +9 -7
- package/lib/prompt.js.map +1 -1
- package/lib/types.d.ts +6 -5
- package/lib/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +18 -11
- package/src/app.ts +358 -105
- package/src/bot.ts +27 -25
- package/src/config.ts +352 -230
- package/src/index.ts +1 -1
- package/src/log-transport.ts +1 -1
- package/src/models/system-log.ts +2 -2
- package/src/models/user.ts +2 -2
- package/src/plugin.ts +19 -6
- package/src/prompt.ts +10 -9
- package/src/types.ts +8 -5
- package/tests/adapter.test.ts +5 -200
- package/tests/app.test.ts +208 -181
- package/tests/command.test.ts +2 -2
- package/tests/config.test.ts +5 -326
- package/tests/cron.test.ts +277 -0
- package/tests/jsx.test.ts +300 -0
- package/tests/permissions.test.ts +358 -0
- package/tests/plugin.test.ts +40 -177
- package/tests/prompt.test.ts +223 -0
- package/tests/schema.test.ts +248 -0
- package/lib/schema.d.ts +0 -83
- package/lib/schema.d.ts.map +0 -1
- package/lib/schema.js +0 -245
- package/lib/schema.js.map +0 -1
- package/src/schema.ts +0 -273
package/src/config.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { pathToFileURL } from
|
|
4
|
-
import { parse as parseYaml, stringify as stringifyYaml } from
|
|
5
|
-
import { parse as parseToml } from
|
|
6
|
-
import { config as loadDotenv } from
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
5
|
+
import { parse as parseToml } from "toml";
|
|
6
|
+
import { config as loadDotenv } from "dotenv";
|
|
7
|
+
import { Schema } from '@zhin.js/hmr';
|
|
8
|
+
import type { AppConfig, DefineConfig } from "./types.js";
|
|
9
|
+
import { LogLevel } from "@zhin.js/logger";
|
|
10
|
+
import { EventEmitter } from "node:events";
|
|
9
11
|
|
|
10
12
|
export interface ConfigOptions {
|
|
11
13
|
configPath?: string;
|
|
@@ -16,260 +18,380 @@ export interface ConfigOptions {
|
|
|
16
18
|
/**
|
|
17
19
|
* 支持的配置文件格式
|
|
18
20
|
*/
|
|
19
|
-
export type ConfigFormat =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let envName: string;
|
|
34
|
-
let defaultValue: string | undefined;
|
|
35
|
-
|
|
36
|
-
if (colonIndex !== -1) {
|
|
37
|
-
// 格式: VAR_NAME:-default_value
|
|
38
|
-
envName = content.slice(0, colonIndex);
|
|
39
|
-
defaultValue = content.slice(colonIndex + 2);
|
|
40
|
-
} else {
|
|
41
|
-
// 格式: VAR_NAME
|
|
42
|
-
envName = content;
|
|
43
|
-
defaultValue = undefined;
|
|
21
|
+
export type ConfigFormat = "json" | "yaml" | "yml" | "toml" | "js" | "ts";
|
|
22
|
+
export class Config<T extends object = object> extends EventEmitter {
|
|
23
|
+
#filepath: string;
|
|
24
|
+
#schema: Schema<Partial<T>>;
|
|
25
|
+
#data: T;
|
|
26
|
+
constructor(filename: string, schema: Schema<Partial<T>>, initialConfig: T) {
|
|
27
|
+
super();
|
|
28
|
+
const ext = path.extname(filename).toLowerCase();
|
|
29
|
+
if (!Config.supportedExtensions.includes(ext)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`不支持的配置文件格式: ${ext},支持的格式有 ${Config.supportedExtensions.join(
|
|
32
|
+
"/"
|
|
33
|
+
)}`
|
|
34
|
+
);
|
|
44
35
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (envValue !== undefined) {
|
|
49
|
-
return envValue;
|
|
50
|
-
} else if (defaultValue !== undefined) {
|
|
51
|
-
return defaultValue;
|
|
52
|
-
} else {
|
|
53
|
-
// console.warn 已替换为注释
|
|
54
|
-
return match;
|
|
36
|
+
const fullpath = path.resolve(process.cwd(), filename);
|
|
37
|
+
if (!fs.existsSync(fullpath)) {
|
|
38
|
+
Config.save(fullpath, initialConfig);
|
|
55
39
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* 递归替换对象中的环境变量
|
|
61
|
-
*/
|
|
62
|
-
function replaceEnvVarsInObject(obj: any): any {
|
|
63
|
-
if (typeof obj === 'string') {
|
|
64
|
-
return replaceEnvVars(obj);
|
|
40
|
+
this.#filepath = fullpath;
|
|
41
|
+
this.#schema = schema;
|
|
42
|
+
this.#data = initialConfig;
|
|
43
|
+
this.#load(this.#data);
|
|
65
44
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return obj.map(item => replaceEnvVarsInObject(item));
|
|
45
|
+
get filepath() {
|
|
46
|
+
return this.#filepath;
|
|
69
47
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
73
|
-
obj[key] = replaceEnvVarsInObject(value);
|
|
74
|
-
}
|
|
75
|
-
return obj;
|
|
48
|
+
get config() {
|
|
49
|
+
return Config.proxyResult(this.#data,()=>{Config.save(this.#filepath, this.#data);});
|
|
76
50
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
case 'yml':
|
|
91
|
-
return parseYaml(content);
|
|
92
|
-
case 'toml':
|
|
93
|
-
return parseToml(content);
|
|
94
|
-
case 'js':
|
|
95
|
-
case 'ts':
|
|
96
|
-
if (!filePath) {
|
|
97
|
-
throw new Error('解析 JS/TS 配置文件需要提供文件路径');
|
|
51
|
+
set config(newConfig: T) {
|
|
52
|
+
const beforeConfig = this.#data;
|
|
53
|
+
this.#data = newConfig;
|
|
54
|
+
this.emit("change", beforeConfig, this.#data);
|
|
55
|
+
Config.save(this.#filepath, this.#data);
|
|
56
|
+
}
|
|
57
|
+
#load(before: T) {
|
|
58
|
+
// 加载配置文件
|
|
59
|
+
Config.load(this.#filepath, this.#schema)
|
|
60
|
+
.then((data) => {
|
|
61
|
+
this.#data = data as T;
|
|
62
|
+
if (JSON.stringify(before) !== JSON.stringify(this.#data)) {
|
|
63
|
+
this.emit("change", before, this.#data);
|
|
98
64
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
reload() {
|
|
68
|
+
this.#load(this.#data);
|
|
69
|
+
}
|
|
70
|
+
get<K extends Config.Paths<T>>(key: K): Config.Value<T, K> {
|
|
71
|
+
const lastKey = /\.?([^.]*)$/.exec(key)?.[1];
|
|
72
|
+
if (!lastKey) throw new Error(`无法获取配置项: ${key}`);
|
|
73
|
+
const obj = Config.getNestedObject(this.#data, key);
|
|
74
|
+
const result= Reflect.get(obj, lastKey);
|
|
75
|
+
return Config.proxyResult(result,()=>{Config.save(this.#filepath, this.#data);}) as Config.Value<T, K>;
|
|
76
|
+
}
|
|
77
|
+
set<K extends Config.Paths<T>>(key: K, value: Config.Value<T, K>): void {
|
|
78
|
+
const prop = /\.?([^.]*)$/.exec(key)?.[1];
|
|
79
|
+
if (!prop) throw new Error(`无法设置配置项: ${key}`);
|
|
80
|
+
const obj = Config.getNestedObject(this.#data, key);
|
|
81
|
+
const beforeConfig = this.#data;
|
|
82
|
+
Reflect.set(obj, prop, value);
|
|
83
|
+
Config.save(this.#filepath, this.#data);
|
|
84
|
+
this.emit("change", beforeConfig, this.#data);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export namespace Config {
|
|
88
|
+
export const supportedExtensions = [
|
|
89
|
+
".json",
|
|
90
|
+
".yaml",
|
|
91
|
+
".yml",
|
|
92
|
+
".toml",
|
|
93
|
+
".js",
|
|
94
|
+
".ts",
|
|
95
|
+
];
|
|
96
|
+
export function proxyResult<T>(result:T,onSet:(value:T)=>void):T{
|
|
97
|
+
if((typeof result!=='object' && typeof result!=='string') || result===null) return result;
|
|
98
|
+
if(typeof result==='string') return replaceEnvVars(result) as T;
|
|
99
|
+
return new Proxy(result as (T & object),{
|
|
100
|
+
get(target,prop,receiver){
|
|
101
|
+
const value=Reflect.get(target,prop,receiver)
|
|
102
|
+
return proxyResult(value as T,onSet);
|
|
103
|
+
},
|
|
104
|
+
set(target,prop,value,receiver){
|
|
105
|
+
const result=Reflect.set(target,prop,value,receiver)
|
|
106
|
+
onSet(value);
|
|
105
107
|
return result;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 配置文件元数据,用于记录原始格式信息
|
|
113
|
+
*/
|
|
114
|
+
interface ConfigMetadata {
|
|
115
|
+
usesFunction: boolean; // 是否使用函数导出
|
|
116
|
+
originalContent?: string; // 原始文件内容
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 存储配置文件的元数据
|
|
120
|
+
const configMetadataMap = new Map<string, ConfigMetadata>();
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 智能保存配置文件,保留原有格式
|
|
124
|
+
*/
|
|
125
|
+
export function save<T extends object>(filePath: string, config: T): void {
|
|
126
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
127
|
+
const metadata = configMetadataMap.get(filePath);
|
|
128
|
+
let content: string;
|
|
129
|
+
|
|
130
|
+
switch (ext) {
|
|
131
|
+
case ".json":
|
|
132
|
+
content = JSON.stringify(config, null, 2);
|
|
133
|
+
break;
|
|
134
|
+
case ".yaml":
|
|
135
|
+
case ".yml":
|
|
136
|
+
content = stringifyYaml(config, { indent: 2 });
|
|
137
|
+
break;
|
|
138
|
+
case ".toml":
|
|
139
|
+
throw new Error("暂不支持保存 TOML 格式的配置文件");
|
|
140
|
+
case ".js":
|
|
141
|
+
case ".ts":
|
|
142
|
+
// 智能保存 JS/TS 配置
|
|
143
|
+
content = saveJsConfig(filePath, config, metadata);
|
|
144
|
+
break;
|
|
106
145
|
default:
|
|
107
|
-
throw new Error(`不支持的配置文件格式: ${
|
|
146
|
+
throw new Error(`不支持的配置文件格式: ${ext}`);
|
|
108
147
|
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
throw new Error(`解析配置文件失败: ${error}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
148
|
|
|
114
|
-
|
|
115
|
-
* 获取配置文件格式
|
|
116
|
-
*/
|
|
117
|
-
function getConfigFormat(filePath: string): ConfigFormat {
|
|
118
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
119
|
-
if (!['json', 'yaml', 'yml', 'toml', 'js', 'ts'].includes(ext)) {
|
|
120
|
-
throw new Error(`不支持的配置文件格式: ${ext}`);
|
|
149
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
121
150
|
}
|
|
122
|
-
return ext as ConfigFormat;
|
|
123
|
-
}
|
|
124
151
|
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
'zhin.config.ts',
|
|
137
|
-
// 然后查找 config.* 格式
|
|
138
|
-
'config.yaml',
|
|
139
|
-
'config.yml',
|
|
140
|
-
'config.json',
|
|
141
|
-
'config.toml',
|
|
142
|
-
'config.js',
|
|
143
|
-
'config.ts'
|
|
144
|
-
];
|
|
152
|
+
/**
|
|
153
|
+
* 智能保存 JS/TS 配置文件,保留函数格式和环境变量
|
|
154
|
+
*/
|
|
155
|
+
function saveJsConfig<T extends object>(
|
|
156
|
+
filePath: string,
|
|
157
|
+
config: T,
|
|
158
|
+
metadata?: ConfigMetadata
|
|
159
|
+
): string {
|
|
160
|
+
const ext = path.extname(filePath);
|
|
161
|
+
const usesFunction = metadata?.usesFunction ?? false;
|
|
162
|
+
const originalContent = metadata?.originalContent;
|
|
145
163
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
if (usesFunction && originalContent) {
|
|
165
|
+
// 如果使用函数导出,尝试保留原有格式并更新配置
|
|
166
|
+
return updateJsConfigWithFunction(originalContent, config, ext === ".ts");
|
|
167
|
+
} else {
|
|
168
|
+
// 简单对象导出
|
|
169
|
+
const typeAnnotation = ext === ".ts" ? ": DefineConfig<AppConfig>" : "";
|
|
170
|
+
return `export default ${JSON.stringify(
|
|
171
|
+
config,
|
|
172
|
+
null,
|
|
173
|
+
2
|
|
174
|
+
)}${typeAnnotation};`;
|
|
150
175
|
}
|
|
151
176
|
}
|
|
152
177
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
/**
|
|
179
|
+
* 更新使用函数导出的配置文件
|
|
180
|
+
* 保留环境变量访问模式和原有代码结构
|
|
181
|
+
*/
|
|
182
|
+
function updateJsConfigWithFunction<T extends object>(
|
|
183
|
+
originalContent: string,
|
|
184
|
+
newConfig: T,
|
|
185
|
+
isTypeScript: boolean
|
|
186
|
+
): string {
|
|
187
|
+
// 简单的字符串替换策略:
|
|
188
|
+
// 1. 找到 return 语句中的对象
|
|
189
|
+
// 2. 替换对象内容,但保留环境变量访问
|
|
190
|
+
|
|
191
|
+
const returnMatch = originalContent.match(
|
|
192
|
+
/return\s*({[\s\S]*?})\s*[;}]?\s*$/m
|
|
193
|
+
);
|
|
194
|
+
if (!returnMatch) {
|
|
195
|
+
// 如果找不到 return 语句,回退到简单格式
|
|
196
|
+
const typeAnnotation = isTypeScript ? ": DefineConfig<AppConfig>" : "";
|
|
197
|
+
return `export default ${JSON.stringify(
|
|
198
|
+
newConfig,
|
|
199
|
+
null,
|
|
200
|
+
2
|
|
201
|
+
)}${typeAnnotation};`;
|
|
172
202
|
}
|
|
203
|
+
|
|
204
|
+
// 保留原有的函数签名和代码结构
|
|
205
|
+
// 只更新 return 语句中的对象
|
|
206
|
+
const configString = formatConfigObject(newConfig, 2);
|
|
207
|
+
const beforeReturn = originalContent.substring(0, returnMatch.index! + 6); // "return"
|
|
208
|
+
|
|
209
|
+
return `${beforeReturn.trim()} ${configString}\n});`;
|
|
173
210
|
}
|
|
174
211
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
212
|
+
/**
|
|
213
|
+
* 格式化配置对象为字符串,保留环境变量访问模式
|
|
214
|
+
*/
|
|
215
|
+
function formatConfigObject(obj: any, indent: number = 0): string {
|
|
216
|
+
const spaces = " ".repeat(indent);
|
|
217
|
+
const nextSpaces = " ".repeat(indent + 2);
|
|
218
|
+
|
|
219
|
+
if (obj === null || obj === undefined) {
|
|
220
|
+
return String(obj);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (typeof obj === "string") {
|
|
224
|
+
for(const [key,value] of Object.entries(process.env)){
|
|
225
|
+
if(obj===value && value!=='zhin') return `process.env.${key}`;
|
|
226
|
+
}
|
|
227
|
+
return JSON.stringify(obj);
|
|
181
228
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (!foundPath) {
|
|
186
|
-
throw new Error('未找到配置文件,支持的文件名: zhin.config.[yaml|yml|json|toml|js|ts] 或 config.[yaml|yml|json|toml|js|ts]');
|
|
229
|
+
|
|
230
|
+
if (typeof obj === "number" || typeof obj === "boolean") {
|
|
231
|
+
return String(obj);
|
|
187
232
|
}
|
|
188
|
-
finalConfigPath = foundPath;
|
|
189
|
-
}
|
|
190
233
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
234
|
+
if (Array.isArray(obj)) {
|
|
235
|
+
if (obj.length === 0) return "[]";
|
|
236
|
+
const items = obj
|
|
237
|
+
.map((item) => `${nextSpaces}${formatConfigObject(item, indent + 2)}`)
|
|
238
|
+
.join(",\n");
|
|
239
|
+
return `[\n${items}\n${spaces}]`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof obj === "object") {
|
|
243
|
+
const entries = Object.entries(obj);
|
|
244
|
+
if (entries.length === 0) return "{}";
|
|
245
|
+
|
|
246
|
+
const props = entries
|
|
247
|
+
.map(([key, value]) => {
|
|
248
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(key);
|
|
249
|
+
const keyStr = needsQuotes ? `'${key}'` : key;
|
|
250
|
+
return `${nextSpaces}${keyStr}: ${formatConfigObject(
|
|
251
|
+
value,
|
|
252
|
+
indent + 2
|
|
253
|
+
)}`;
|
|
254
|
+
})
|
|
255
|
+
.join(",\n");
|
|
256
|
+
|
|
257
|
+
return `{\n${props}\n${spaces}}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return JSON.stringify(obj);
|
|
201
261
|
}
|
|
202
|
-
|
|
203
|
-
// 替换环境变量
|
|
204
|
-
const config = replaceEnvVarsInObject(rawConfig) as AppConfig;
|
|
205
|
-
|
|
206
|
-
// 验证配置
|
|
207
|
-
validateConfig(config);
|
|
208
|
-
|
|
209
|
-
return [finalConfigPath,config];
|
|
210
|
-
}
|
|
211
262
|
|
|
212
|
-
/**
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
|
|
263
|
+
/**
|
|
264
|
+
* 记录配置文件的元数据
|
|
265
|
+
*/
|
|
266
|
+
export function setMetadata(
|
|
267
|
+
filePath: string,
|
|
268
|
+
metadata: ConfigMetadata
|
|
269
|
+
): void {
|
|
270
|
+
configMetadataMap.set(filePath, metadata);
|
|
218
271
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 获取配置文件的元数据
|
|
275
|
+
*/
|
|
276
|
+
export function getMetadata(filePath: string): ConfigMetadata | undefined {
|
|
277
|
+
return configMetadataMap.get(filePath);
|
|
222
278
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
279
|
+
export async function load<T extends object>(
|
|
280
|
+
filePath: string,
|
|
281
|
+
schema: Schema<T>
|
|
282
|
+
): Promise<T> {
|
|
283
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
284
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
285
|
+
let rawConfig: any;
|
|
286
|
+
let usesFunction = false;
|
|
287
|
+
|
|
288
|
+
switch (ext) {
|
|
289
|
+
case ".json":
|
|
290
|
+
rawConfig = JSON.parse(content);
|
|
291
|
+
break;
|
|
292
|
+
case ".yaml":
|
|
293
|
+
case ".yml":
|
|
294
|
+
rawConfig = parseYaml(content);
|
|
295
|
+
break;
|
|
296
|
+
case ".toml":
|
|
297
|
+
rawConfig = parseToml(content);
|
|
298
|
+
break;
|
|
299
|
+
case ".js":
|
|
300
|
+
case ".ts":
|
|
301
|
+
// 使用动态导入加载 JS/TS 模块
|
|
302
|
+
const fileUrl = pathToFileURL(path.resolve(filePath)).href;
|
|
303
|
+
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
304
|
+
// 支持 ES 模块的 default 导出和 CommonJS 模块
|
|
305
|
+
rawConfig = module.default || module;
|
|
306
|
+
if (typeof rawConfig === "function") {
|
|
307
|
+
usesFunction = true;
|
|
308
|
+
rawConfig = await rawConfig(
|
|
309
|
+
(process.env || {}) as Record<string, string>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
// 记录元数据
|
|
313
|
+
setMetadata(filePath, { usesFunction, originalContent: content });
|
|
314
|
+
break;
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`不支持的配置文件格式: ${ext}`);
|
|
228
317
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
318
|
+
return schema(rawConfig);
|
|
319
|
+
}
|
|
320
|
+
export type Value<
|
|
321
|
+
T,
|
|
322
|
+
K extends Paths<T>
|
|
323
|
+
> = K extends `${infer Key}.${infer Rest}`
|
|
324
|
+
? Key extends keyof T
|
|
325
|
+
? Rest extends Paths<T[Key]>
|
|
326
|
+
? Value<T[Key], Rest>
|
|
327
|
+
: never
|
|
328
|
+
: never
|
|
329
|
+
: K extends keyof T
|
|
330
|
+
? T[K]
|
|
331
|
+
: never;
|
|
332
|
+
|
|
333
|
+
export type Paths<
|
|
334
|
+
T,
|
|
335
|
+
Prefix extends string = "",
|
|
336
|
+
Depth extends any[] = []
|
|
337
|
+
> = Depth["length"] extends 10
|
|
338
|
+
? never
|
|
339
|
+
: {
|
|
340
|
+
[K in keyof T]: T[K] extends object
|
|
341
|
+
?
|
|
342
|
+
| `${Prefix}${K & string}`
|
|
343
|
+
| Paths<T[K], `${Prefix}${K & string}.`, [...Depth, 1]>
|
|
344
|
+
: `${Prefix}${K & string}`;
|
|
345
|
+
}[keyof T];
|
|
346
|
+
export function getNestedObject<T>(obj: T, path: string): any {
|
|
347
|
+
const parts = path.split(".");
|
|
348
|
+
let current: any = obj;
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
351
|
+
const part = parts[i];
|
|
352
|
+
if (!(part in current)) {
|
|
353
|
+
current[part] = {};
|
|
354
|
+
}
|
|
355
|
+
current = current[part];
|
|
232
356
|
}
|
|
357
|
+
|
|
358
|
+
return current;
|
|
233
359
|
}
|
|
234
360
|
}
|
|
235
361
|
|
|
236
362
|
/**
|
|
237
|
-
*
|
|
363
|
+
* 替换字符串中的环境变量
|
|
238
364
|
*/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
365
|
+
function replaceEnvVars(str: string): string{
|
|
366
|
+
if (typeof str !== 'string') return str;
|
|
367
|
+
return (str as string).replace(/^\$\{([^}]+)\}$/, (match, content) => {
|
|
368
|
+
// 解析环境变量名和默认值
|
|
369
|
+
const colonIndex = content.indexOf(":-");
|
|
370
|
+
let envName: string;
|
|
371
|
+
let defaultValue: string | undefined;
|
|
372
|
+
|
|
373
|
+
if (colonIndex !== -1) {
|
|
374
|
+
// 格式: VAR_NAME:-default_value
|
|
375
|
+
envName = content.slice(0, colonIndex);
|
|
376
|
+
defaultValue = content.slice(colonIndex + 2);
|
|
377
|
+
} else {
|
|
378
|
+
// 格式: VAR_NAME
|
|
379
|
+
envName = content;
|
|
380
|
+
defaultValue = undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const envValue = process.env[envName];
|
|
384
|
+
|
|
385
|
+
if (envValue !== undefined) {
|
|
386
|
+
return envValue;
|
|
387
|
+
} else if (defaultValue !== undefined) {
|
|
388
|
+
return defaultValue;
|
|
389
|
+
} else {
|
|
390
|
+
return match;
|
|
391
|
+
}
|
|
392
|
+
});
|
|
259
393
|
}
|
|
260
394
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
export function createDefaultConfig(format: ConfigFormat = 'yaml'): AppConfig {
|
|
265
|
-
return {
|
|
266
|
-
log_level: LogLevel.INFO,
|
|
267
|
-
bots: [{
|
|
268
|
-
name: 'onebot11',
|
|
269
|
-
context: 'onebot11',
|
|
270
|
-
url: '${ONEBOT_URL:-ws://localhost:8080}'
|
|
271
|
-
}],
|
|
272
|
-
plugin_dirs: ['./src/plugins', 'node_modules'],
|
|
273
|
-
plugins: [],
|
|
274
|
-
};
|
|
275
|
-
}
|
|
395
|
+
export function defineConfig<T extends DefineConfig<AppConfig>>(config: T): T {
|
|
396
|
+
return config;
|
|
397
|
+
}
|
package/src/index.ts
CHANGED
package/src/log-transport.ts
CHANGED
|
@@ -92,7 +92,7 @@ export class DatabaseLogTransport implements LogTransport {
|
|
|
92
92
|
)
|
|
93
93
|
} catch (error) {
|
|
94
94
|
// 静默处理错误
|
|
95
|
-
this.app.logger.
|
|
95
|
+
this.app.logger.debug('[DatabaseLogTransport] Cleanup error:', (error as Error).message,(error as Error).stack)
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
package/src/models/system-log.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Definition } from '@zhin.js/database'
|
|
2
2
|
|
|
3
3
|
export interface SystemLog {
|
|
4
4
|
id?: number
|
|
@@ -9,7 +9,7 @@ export interface SystemLog {
|
|
|
9
9
|
timestamp: Date
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export const
|
|
12
|
+
export const SystemLogDefinition:Definition<SystemLog>={
|
|
13
13
|
id: { type: 'integer', autoIncrement: true, primary: true },
|
|
14
14
|
level: { type: 'text', nullable: false },
|
|
15
15
|
name: { type: 'text', nullable: false },
|
package/src/models/user.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Definition } from '@zhin.js/database'
|
|
2
2
|
export interface User{
|
|
3
3
|
id:string
|
|
4
4
|
name?:string
|
|
@@ -6,7 +6,7 @@ export interface User{
|
|
|
6
6
|
third_part:string[];
|
|
7
7
|
permissions?:string[]
|
|
8
8
|
}
|
|
9
|
-
export const
|
|
9
|
+
export const UserDefinition:Definition<User>={
|
|
10
10
|
id:{type:"text",nullable:false},
|
|
11
11
|
name:{type:'text',nullable:true},
|
|
12
12
|
password:{type:'text',nullable:true},
|