@zhin.js/core 1.0.7 → 1.0.9

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +28 -12
  3. package/lib/adapter.d.ts +8 -6
  4. package/lib/adapter.d.ts.map +1 -1
  5. package/lib/adapter.js +13 -7
  6. package/lib/adapter.js.map +1 -1
  7. package/lib/app.d.ts +72 -14
  8. package/lib/app.d.ts.map +1 -1
  9. package/lib/app.js +240 -79
  10. package/lib/app.js.map +1 -1
  11. package/lib/bot.d.ts +10 -8
  12. package/lib/bot.d.ts.map +1 -1
  13. package/lib/config.d.ts +44 -14
  14. package/lib/config.d.ts.map +1 -1
  15. package/lib/config.js +275 -208
  16. package/lib/config.js.map +1 -1
  17. package/lib/index.d.ts +1 -1
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +1 -1
  20. package/lib/index.js.map +1 -1
  21. package/lib/log-transport.js +1 -1
  22. package/lib/log-transport.js.map +1 -1
  23. package/lib/models/system-log.d.ts +2 -2
  24. package/lib/models/system-log.d.ts.map +1 -1
  25. package/lib/models/system-log.js +1 -1
  26. package/lib/models/system-log.js.map +1 -1
  27. package/lib/models/user.d.ts +2 -2
  28. package/lib/models/user.d.ts.map +1 -1
  29. package/lib/models/user.js +1 -1
  30. package/lib/models/user.js.map +1 -1
  31. package/lib/plugin.d.ts +7 -3
  32. package/lib/plugin.d.ts.map +1 -1
  33. package/lib/plugin.js +16 -5
  34. package/lib/plugin.js.map +1 -1
  35. package/lib/prompt.d.ts +1 -1
  36. package/lib/prompt.d.ts.map +1 -1
  37. package/lib/prompt.js +9 -7
  38. package/lib/prompt.js.map +1 -1
  39. package/lib/types.d.ts +6 -5
  40. package/lib/types.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/adapter.ts +18 -11
  43. package/src/app.ts +358 -102
  44. package/src/bot.ts +27 -25
  45. package/src/config.ts +352 -230
  46. package/src/index.ts +1 -1
  47. package/src/log-transport.ts +1 -1
  48. package/src/models/system-log.ts +2 -2
  49. package/src/models/user.ts +2 -2
  50. package/src/plugin.ts +19 -6
  51. package/src/prompt.ts +10 -9
  52. package/src/types.ts +8 -5
  53. package/tests/adapter.test.ts +5 -200
  54. package/tests/app.test.ts +208 -181
  55. package/tests/command.test.ts +2 -2
  56. package/tests/config.test.ts +5 -326
  57. package/tests/cron.test.ts +277 -0
  58. package/tests/jsx.test.ts +300 -0
  59. package/tests/permissions.test.ts +358 -0
  60. package/tests/prompt.test.ts +223 -0
  61. package/tests/schema.test.ts +248 -0
  62. package/lib/schema.d.ts +0 -83
  63. package/lib/schema.d.ts.map +0 -1
  64. package/lib/schema.js +0 -245
  65. package/lib/schema.js.map +0 -1
  66. package/src/schema.ts +0 -273
package/src/config.ts CHANGED
@@ -1,11 +1,13 @@
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 type {AppConfig, DefineConfig} from './types.js';
8
- import { LogLevel } from '@zhin.js/logger';
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 = 'json' | 'yaml' | 'yml' | 'toml' | 'js' | 'ts';
20
-
21
- /**
22
- * 环境变量替换正则表达式,支持默认值语法: ${VAR_NAME:-default_value}
23
- */
24
- const ENV_VAR_REGEX = /\$\{([^}]+)\}/g;
25
-
26
- /**
27
- * 替换字符串中的环境变量
28
- */
29
- function replaceEnvVars(str: string): string {
30
- return str.replace(ENV_VAR_REGEX, (match, content) => {
31
- // 解析环境变量名和默认值
32
- const colonIndex = content.indexOf(':-');
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
- const envValue = process.env[envName];
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
- if (Array.isArray(obj)) {
68
- return obj.map(item => replaceEnvVarsInObject(item));
45
+ get filepath() {
46
+ return this.#filepath;
69
47
  }
70
-
71
- if (obj && typeof obj === 'object') {
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
- return obj;
79
- }
80
-
81
- /**
82
- * 根据文件扩展名解析配置文件
83
- */
84
- async function parseConfigFile(content: string, format: ConfigFormat, filePath?: string): Promise<any> {
85
- try {
86
- switch (format) {
87
- case 'json':
88
- return JSON.parse(content);
89
- case 'yaml':
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
- // 使用动态导入加载 JS/TS 模块
100
- const fileUrl = pathToFileURL(path.resolve(filePath)).href;
101
- const module = await import(fileUrl);
102
- // 支持 ES 模块的 default 导出和 CommonJS 模块
103
- const result = module.default || module;
104
- if(typeof result === 'function') return await result((process.env||{}) as Record<string,string>);
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(`不支持的配置文件格式: ${format}`);
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 findConfigFile(cwd: string = process.cwd()): string | null {
129
- const configNames = [
130
- // 优先查找 zhin.config.* 格式
131
- 'zhin.config.yaml',
132
- 'zhin.config.yml',
133
- 'zhin.config.json',
134
- 'zhin.config.toml',
135
- 'zhin.config.ts',
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
- for (const name of configNames) {
147
- const filePath = path.join(cwd, name);
148
- if (fs.existsSync(filePath)) {
149
- return filePath;
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
- return null;
154
- }
155
- export function defineConfig<T extends DefineConfig<AppConfig>>(config: T): T {
156
- return config;
157
- }
158
- /**
159
- * 加载配置文件
160
- */
161
- export async function loadConfig(options: ConfigOptions = {}): Promise<[string,AppConfig]> {
162
- const { configPath, envPath, envOverride = true } = options;
163
-
164
- // 加载环境变量
165
- if (envPath) {
166
- loadDotenv({ path: envPath, override: envOverride });
167
- } else {
168
- // 尝试加载默认的 .env 文件
169
- const defaultEnvPath = path.join(process.cwd(), '.env');
170
- if (fs.existsSync(defaultEnvPath)) {
171
- loadDotenv({ path: defaultEnvPath, override: envOverride });
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
- let finalConfigPath: string;
177
-
178
- if (configPath) {
179
- if (!fs.existsSync(configPath)) {
180
- throw new Error(`配置文件不存在: ${configPath}`);
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
- finalConfigPath = configPath;
183
- } else {
184
- const foundPath = findConfigFile();
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
- const format = getConfigFormat(finalConfigPath);
193
- let rawConfig: any;
194
-
195
- if (format === 'js' || format === 'ts') {
196
- // JS/TS 文件不需要读取内容,直接解析
197
- rawConfig = await parseConfigFile('', format, finalConfigPath);
198
- } else {
199
- const content = fs.readFileSync(finalConfigPath, 'utf-8');
200
- rawConfig = await parseConfigFile(content, format, finalConfigPath);
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 validateConfig(config: any): void {
216
- if (!config) {
217
- throw new Error('配置文件不能为空');
263
+ /**
264
+ * 记录配置文件的元数据
265
+ */
266
+ export function setMetadata(
267
+ filePath: string,
268
+ metadata: ConfigMetadata
269
+ ): void {
270
+ configMetadataMap.set(filePath, metadata);
218
271
  }
219
-
220
- if (!config.bots || !Array.isArray(config.bots)) {
221
- throw new Error('配置文件必须包含 bots 数组');
272
+
273
+ /**
274
+ * 获取配置文件的元数据
275
+ */
276
+ export function getMetadata(filePath: string): ConfigMetadata | undefined {
277
+ return configMetadataMap.get(filePath);
222
278
  }
223
-
224
-
225
- for (const [index, bot] of config.bots.entries()) {
226
- if (!bot.name) {
227
- throw new Error(`机器人 ${index} 缺少 name 字段`);
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
- if (!bot.context) {
231
- throw new Error(`机器人 ${bot.name} 缺少 context 字段`);
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
- export function saveConfig(config: AppConfig, filePath: string): void {
240
- const format = getConfigFormat(filePath);
241
- let content: string;
242
-
243
- switch (format) {
244
- case 'json':
245
- content = JSON.stringify(config, null, 2);
246
- break;
247
- case 'yaml':
248
- case 'yml':
249
- content = stringifyYaml(config, { indent: 2 });
250
- break;
251
- case 'toml':
252
- // toml 库没有 stringify 方法,我们需要手动实现或使用其他库
253
- throw new Error('暂不支持保存 TOML 格式的配置文件');
254
- default:
255
- throw new Error(`不支持的配置文件格式: ${format}`);
256
- }
257
-
258
- fs.writeFileSync(filePath, content, 'utf-8');
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
@@ -15,4 +15,4 @@ export * from './cron.js'
15
15
  export * from '@zhin.js/database'
16
16
  export * from '@zhin.js/logger'
17
17
 
18
- export { Dependency } from '@zhin.js/hmr'
18
+ export { Dependency, Schema } from '@zhin.js/hmr'
@@ -92,7 +92,7 @@ export class DatabaseLogTransport implements LogTransport {
92
92
  )
93
93
  } catch (error) {
94
94
  // 静默处理错误
95
- this.app.logger.error('[DatabaseLogTransport] Cleanup error:', (error as Error).message)
95
+ this.app.logger.debug('[DatabaseLogTransport] Cleanup error:', (error as Error).message,(error as Error).stack)
96
96
  }
97
97
  }
98
98
 
@@ -1,4 +1,4 @@
1
- import { Schema } from '@zhin.js/database'
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 SystemLogSchema:Schema<SystemLog>={
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 },
@@ -1,4 +1,4 @@
1
- import { Schema } from '@zhin.js/database'
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 UserSchema:Schema<User>={
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},