@koishi-ce/koishi 1.0.4 → 1.0.6

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/lib/cli/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
  import { cac } from "cac";
3
3
  import { hyphenate, isInteger } from "@koishi-ce/utils";
4
- import kleur from "kleur";
4
+ import pc from "picocolors";
5
5
  //#region package.json
6
6
  var package_default = {
7
7
  name: "@koishi-ce/koishi",
8
8
  type: "module",
9
9
  description: "Cross-Platform Chatbot Framework Made with Love",
10
- version: "1.0.4",
10
+ version: "1.0.6",
11
11
  main: "lib/index.mjs",
12
12
  module: "lib/index.mjs",
13
13
  types: "lib/index.d.ts",
@@ -59,7 +59,7 @@ var package_default = {
59
59
  "@koishi-ce/plugin-server": "workspace:*",
60
60
  "@koishi-ce/utils": "workspace:*",
61
61
  "cac": "^7.0.0",
62
- "kleur": "^4.1.5"
62
+ "picocolors": "^1.1.1"
63
63
  }
64
64
  };
65
65
  //#endregion
@@ -105,14 +105,14 @@ function createWorker(options) {
105
105
  if (message.type === "start") {
106
106
  config = message.body;
107
107
  timer = config.heartbeatTimeout ? setTimeout(() => {
108
- console.log(kleur.red("daemon: heartbeat timeout"));
108
+ console.log(pc.red("daemon: heartbeat timeout"));
109
109
  child.kill("SIGKILL");
110
110
  }, config.heartbeatTimeout) : void 0;
111
111
  } else if (message.type === "shared") process.env["KOISHI_SHARED"] = message.body;
112
112
  else if (message.type === "heartbeat" && timer && config.heartbeatTimeout) {
113
113
  clearTimeout(timer);
114
114
  timer = setTimeout(() => {
115
- console.log(kleur.red("daemon: heartbeat timeout"));
115
+ console.log(pc.red("daemon: heartbeat timeout"));
116
116
  child.kill("SIGKILL");
117
117
  }, config.heartbeatTimeout);
118
118
  }
@@ -163,7 +163,7 @@ function start_default(cli) {
163
163
  cli.command("start [file]", "start a koishi bot").alias("run").allowUnknownOptions().option("--debug [namespace]", "specify debug namespace").option("--log-level [level]", "specify log level (default: 2)").option("--log-time [format]", "show timestamp in logs").action((file, options) => {
164
164
  const { logLevel, debug, logTime, ...rest } = options;
165
165
  if (logLevel !== void 0 && (!isInteger(logLevel) || logLevel < 0)) {
166
- console.warn(`${kleur.red("error")} log level should be a positive integer.`);
166
+ console.warn(`${pc.red("error")} log level should be a positive integer.`);
167
167
  process.exit(1);
168
168
  }
169
169
  setEnvArg("KOISHI_LOG_TIME", logTime);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@koishi-ce/koishi",
3
3
  "type": "module",
4
4
  "description": "Cross-Platform Chatbot Framework Made with Love",
5
- "version": "1.0.4",
5
+ "version": "1.0.6",
6
6
  "main": "lib/index.mjs",
7
7
  "module": "lib/index.mjs",
8
8
  "types": "lib/index.d.ts",
@@ -61,13 +61,13 @@
61
61
  }
62
62
  },
63
63
  "dependencies": {
64
- "@koishi-ce/core": "^1.0.0",
65
- "@koishi-ce/loader": "^1.0.4",
64
+ "@koishi-ce/core": "^1.0.1",
65
+ "@koishi-ce/loader": "^1.0.5",
66
66
  "@koishi-ce/plugin-http": "^1.0.0",
67
67
  "@koishi-ce/plugin-proxy-agent": "^1.0.0",
68
- "@koishi-ce/plugin-server": "^1.0.0",
68
+ "@koishi-ce/plugin-server": "^1.0.1",
69
69
  "@koishi-ce/utils": "^1.0.0",
70
70
  "cac": "^7.0.0",
71
- "kleur": "^4.1.5"
71
+ "picocolors": "^1.1.1"
72
72
  }
73
73
  }
package/src/cli/start.ts CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { hyphenate, isInteger } from "@koishi-ce/utils";
14
14
  import type { CAC } from "cac";
15
- import kleur from "kleur";
15
+ import pc from "picocolors";
16
16
  import type { Config } from "../worker/daemon.ts";
17
17
 
18
18
  /** 子进程通过 IPC 通道发来的消息类型(并集) */
@@ -20,7 +20,9 @@ type Event = Event.Start | Event.Env | Event.Heartbeat;
20
20
  /** 单个命令行选项的取值形态 */
21
21
  type WorkerOption = boolean | string | string[] | undefined;
22
22
  /** 传递给 worker 的完整选项表;`--` 键对应 cac 收集的透传参数 */
23
- type WorkerOptions = Record<string, WorkerOption> & { "--"?: string[] };
23
+ type WorkerOptions = Record<string, WorkerOption> & {
24
+ "--"?: string[];
25
+ };
24
26
 
25
27
  /** 子进程 IPC 消息的具体结构定义 */
26
28
  namespace Event {
@@ -54,7 +56,9 @@ process.env["KOISHI_SHARED"] = JSON.stringify({
54
56
  * 单字母键转为 `-x`,其余转为 `--kebab-case`。
55
57
  */
56
58
  function toArg(key: string) {
57
- return key.length === 1 ? `-${key}` : `--${hyphenate(key)}`;
59
+ return key.length === 1
60
+ ? `-${key}`
61
+ : `--${hyphenate(key)}`;
58
62
  }
59
63
 
60
64
  /**
@@ -67,21 +71,23 @@ function toArg(key: string) {
67
71
  */
68
72
  function createWorker(options: WorkerOptions) {
69
73
  // 将选项对象还原为 Node/Bun 可识别的 execArgv 数组
70
- const execArgv = Object.entries(options).flatMap<string>(([key, value]) => {
71
- if (key === "--") return [];
72
- key = toArg(key);
73
- if (value === true) {
74
- return [key];
75
- } else if (value === false) {
76
- // 布尔假值转为 --no-xxx 形式
77
- return [`--no-${key.slice(2)}`];
78
- } else if (Array.isArray(value)) {
79
- // 数组值展开为多组 "键 值"
80
- return value.flatMap((value) => [key, value]);
81
- } else {
82
- return [key, String(value)];
83
- }
84
- });
74
+ const execArgv = Object.entries(options).flatMap<string>(
75
+ ([key, value]) => {
76
+ if (key === "--") return [];
77
+ key = toArg(key);
78
+ if (value === true) {
79
+ return [key];
80
+ } else if (value === false) {
81
+ // 布尔假值转为 --no-xxx 形式
82
+ return [`--no-${key.slice(2)}`];
83
+ } else if (Array.isArray(value)) {
84
+ // 数组值展开为多组 "键 值"
85
+ return value.flatMap((value) => [key, value]);
86
+ } else {
87
+ return [key, String(value)];
88
+ }
89
+ },
90
+ );
85
91
  execArgv.push(...(options["--"] ?? []));
86
92
 
87
93
  // worker 入口为构建产物 index.mjs,而非本 TS 源文件
@@ -96,7 +102,9 @@ function createWorker(options: WorkerOptions) {
96
102
  timer = config.heartbeatTimeout
97
103
  ? setTimeout(() => {
98
104
  // eslint-disable-next-line no-console
99
- console.log(kleur.red("daemon: heartbeat timeout"));
105
+ console.log(
106
+ pc.red("daemon: heartbeat timeout"),
107
+ );
100
108
  child.kill("SIGKILL");
101
109
  }, config.heartbeatTimeout)
102
110
  : undefined;
@@ -110,25 +118,28 @@ function createWorker(options: WorkerOptions) {
110
118
  clearTimeout(timer);
111
119
  timer = setTimeout(() => {
112
120
  // eslint-disable-next-line no-console
113
- console.log(kleur.red("daemon: heartbeat timeout"));
121
+ console.log(pc.red("daemon: heartbeat timeout"));
114
122
  child.kill("SIGKILL");
115
123
  }, config.heartbeatTimeout);
116
124
  }
117
125
  };
118
126
 
119
- child = Bun.spawn([process.execPath, worker, ...execArgv], {
120
- ipc: handleMessage,
121
- // Bun.spawn 的 stdio 默认为 ignore,须显式继承输出通道,
122
- // 否则 worker 的全部日志都会被丢弃
123
- stdout: "inherit",
124
- stderr: "inherit",
125
- onExit: (_, code, signal) => {
126
- if (shouldExit(code, signal)) {
127
- process.exit(code ?? 1);
128
- }
129
- createWorker(options);
127
+ child = Bun.spawn(
128
+ [process.execPath, worker, ...execArgv],
129
+ {
130
+ ipc: handleMessage,
131
+ // Bun.spawn 的 stdio 默认为 ignore,须显式继承输出通道,
132
+ // 否则 worker 的全部日志都会被丢弃
133
+ stdout: "inherit",
134
+ stderr: "inherit",
135
+ onExit: (_, code, signal) => {
136
+ if (shouldExit(code, signal)) {
137
+ process.exit(code ?? 1);
138
+ }
139
+ createWorker(options);
140
+ },
130
141
  },
131
- });
142
+ );
132
143
 
133
144
  /**
134
145
  * 判断子进程退出后父进程应跟随退出还是重新拉起。
@@ -136,7 +147,10 @@ function createWorker(options: WorkerOptions) {
136
147
  * 退出码约定:0 表示正常退出;51 表示请求重启(如 loader 的整进程重载);
137
148
  * 52 表示请求退出;收到信号一律视为外部终止,跟随退出。
138
149
  */
139
- function shouldExit(code: number | null, signal: number | null) {
150
+ function shouldExit(
151
+ code: number | null,
152
+ signal: number | null,
153
+ ) {
140
154
  // 尚未收到 start 消息即退出,说明启动失败
141
155
  if (!config) return true;
142
156
 
@@ -176,15 +190,24 @@ export default function (cli: CAC) {
176
190
  .command("start [file]", "start a koishi bot")
177
191
  .alias("run")
178
192
  .allowUnknownOptions()
179
- .option("--debug [namespace]", "specify debug namespace")
180
- .option("--log-level [level]", "specify log level (default: 2)")
193
+ .option(
194
+ "--debug [namespace]",
195
+ "specify debug namespace",
196
+ )
197
+ .option(
198
+ "--log-level [level]",
199
+ "specify log level (default: 2)",
200
+ )
181
201
  .option("--log-time [format]", "show timestamp in logs")
182
202
  .action((file, options) => {
183
203
  const { logLevel, debug, logTime, ...rest } = options;
184
- if (logLevel !== undefined && (!isInteger(logLevel) || logLevel < 0)) {
204
+ if (
205
+ logLevel !== undefined &&
206
+ (!isInteger(logLevel) || logLevel < 0)
207
+ ) {
185
208
  // eslint-disable-next-line no-console
186
209
  console.warn(
187
- `${kleur.red("error")} log level should be a positive integer.`,
210
+ `${pc.red("error")} log level should be a positive integer.`,
188
211
  );
189
212
  process.exit(1);
190
213
  }
@@ -25,8 +25,12 @@ export const Config: Schema<Config> = Schema.object({
25
25
  autoRestart: Schema.boolean()
26
26
  .description("在运行时崩溃自动重启。")
27
27
  .default(true),
28
- heartbeatInterval: Schema.number().description("心跳发送间隔。").default(0),
29
- heartbeatTimeout: Schema.number().description("心跳超时时间。").default(0),
28
+ heartbeatInterval: Schema.number()
29
+ .description("心跳发送间隔。")
30
+ .default(0),
31
+ heartbeatTimeout: Schema.number()
32
+ .description("心跳超时时间。")
33
+ .default(0),
30
34
  })
31
35
  .description("守护设置")
32
36
  .hidden();
@@ -50,7 +54,9 @@ export function apply(ctx: Context, config: Config = {}) {
50
54
  process.send?.({ type: "exit" });
51
55
  }
52
56
  ctx.logger("app").info(`terminated by ${signal}`);
53
- ctx.parallel("exit", signal).finally(() => process.exit());
57
+ ctx
58
+ .parallel("exit", signal)
59
+ .finally(() => process.exit());
54
60
  }
55
61
 
56
62
  ctx.on("ready", () => {
@@ -14,7 +14,13 @@
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  import net from "node:net";
16
16
  import { dirname, resolve } from "node:path";
17
- import { Context, type Dict, Logger, Schema, Time } from "@koishi-ce/core";
17
+ import {
18
+ Context,
19
+ type Dict,
20
+ Logger,
21
+ Schema,
22
+ Time,
23
+ } from "@koishi-ce/core";
18
24
  import Loader, { resolvePlugin } from "@koishi-ce/loader";
19
25
  import * as daemon from "./daemon.ts";
20
26
  import * as logger from "./logger.ts";
@@ -87,14 +93,19 @@ interface ServerPort {
87
93
  }
88
94
 
89
95
  /** 从模块入口路径向上查找最近的 package.json,返回其 name 字段 */
90
- function locatePackageName(filename: string): string | undefined {
96
+ function locatePackageName(
97
+ filename: string,
98
+ ): string | undefined {
91
99
  let dir = dirname(filename);
92
100
  while (true) {
93
101
  const file = resolve(dir, "package.json");
94
102
  if (existsSync(file)) {
95
103
  try {
96
- const manifest = JSON.parse(readFileSync(file, "utf8"));
97
- if (typeof manifest?.name === "string") return manifest.name;
104
+ const manifest = JSON.parse(
105
+ readFileSync(file, "utf8"),
106
+ );
107
+ if (typeof manifest?.name === "string")
108
+ return manifest.name;
98
109
  } catch {}
99
110
  }
100
111
  const parent = dirname(dir);
@@ -107,9 +118,17 @@ function locatePackageName(filename: string): string | undefined {
107
118
  * 遍历插件配置表(含 group 嵌套),收集服务器插件声明的端口区间。
108
119
  * 引用键格式与 loader 一致:首个冒号前为插件名;`$` 开头为元属性。
109
120
  */
110
- function collectServerPorts(plugins: Dict, baseDir: string, out: ServerPort[]) {
121
+ function collectServerPorts(
122
+ plugins: Dict,
123
+ baseDir: string,
124
+ out: ServerPort[],
125
+ ) {
111
126
  for (const [key, source] of Object.entries(plugins)) {
112
- if (key.startsWith("$") || source === null || typeof source !== "object") {
127
+ if (
128
+ key.startsWith("$") ||
129
+ source === null ||
130
+ typeof source !== "object"
131
+ ) {
113
132
  continue;
114
133
  }
115
134
  const [name = ""] = key.split(":", 1);
@@ -118,14 +137,21 @@ function collectServerPorts(plugins: Dict, baseDir: string, out: ServerPort[]) {
118
137
  continue;
119
138
  }
120
139
  try {
121
- const pkgName = locatePackageName(resolvePlugin(name, baseDir));
122
- if (!pkgName || !serverPackages.has(pkgName)) continue;
123
- const { host, port, maxPort } = source as Record<string, unknown>;
140
+ const pkgName = locatePackageName(
141
+ resolvePlugin(name, baseDir),
142
+ );
143
+ if (!pkgName || !serverPackages.has(pkgName))
144
+ continue;
145
+ const { host, port, maxPort } = source as Record<
146
+ string,
147
+ unknown
148
+ >;
124
149
  if (typeof port !== "number") continue;
125
150
  out.push({
126
151
  host: typeof host === "string" ? host : "127.0.0.1",
127
152
  port,
128
- maxPort: typeof maxPort === "number" ? maxPort : port,
153
+ maxPort:
154
+ typeof maxPort === "number" ? maxPort : port,
129
155
  });
130
156
  } catch {}
131
157
  }
@@ -136,7 +162,9 @@ function probePort(port: number, host: string) {
136
162
  return new Promise<boolean>((promiseResolve) => {
137
163
  const server = net.createServer();
138
164
  server.once("error", () => promiseResolve(false));
139
- server.once("listening", () => server.close(() => promiseResolve(true)));
165
+ server.once("listening", () =>
166
+ server.close(() => promiseResolve(true)),
167
+ );
140
168
  server.listen(port, host);
141
169
  });
142
170
  }
@@ -152,14 +180,19 @@ async function checkPorts(plugins: Dict, baseDir: string) {
152
180
  collectServerPorts(plugins, baseDir, ports);
153
181
  for (const { host, port, maxPort } of ports) {
154
182
  let available = false;
155
- for (let current = port; current <= maxPort; current++) {
183
+ for (
184
+ let current = port;
185
+ current <= maxPort;
186
+ current++
187
+ ) {
156
188
  if (await probePort(current, host)) {
157
189
  available = true;
158
190
  break;
159
191
  }
160
192
  }
161
193
  if (available) continue;
162
- const range = port === maxPort ? `${port}` : `${port}-${maxPort}`;
194
+ const range =
195
+ port === maxPort ? `${port}` : `${port}-${maxPort}`;
163
196
  new Logger("app").error(
164
197
  `端口 ${range} 已被占用(可能已有 Koishi 实例在运行),启动中止`,
165
198
  );
@@ -9,7 +9,12 @@
9
9
  * 完成全局 Logger 的等级、时间格式等设定。CLI 传入的环境变量优先级高于配置文件。
10
10
  */
11
11
 
12
- import { Context, defineProperty, Logger, Schema } from "@koishi-ce/core";
12
+ import {
13
+ Context,
14
+ defineProperty,
15
+ Logger,
16
+ Schema,
17
+ } from "@koishi-ce/core";
13
18
 
14
19
  /**
15
20
  * 配置文件形态的日志等级表。
@@ -33,11 +38,15 @@ function normalizeLevels(
33
38
  config: LogLevelConfig,
34
39
  base: number,
35
40
  ): Logger.LevelConfig {
36
- const result: Logger.LevelConfig = { base: config.base ?? base };
41
+ const result: Logger.LevelConfig = {
42
+ base: config.base ?? base,
43
+ };
37
44
  for (const [name, level] of Object.entries(config)) {
38
45
  if (name === "base") continue;
39
46
  result[name] =
40
- typeof level === "number" ? level : normalizeLevels(level, result.base);
47
+ typeof level === "number"
48
+ ? level
49
+ : normalizeLevels(level, result.base);
41
50
  }
42
51
  return result;
43
52
  }
@@ -54,7 +63,9 @@ export interface Config {
54
63
 
55
64
  export const Config: Schema<Config> = Schema.object({
56
65
  levels: Schema.any().description("默认的日志输出等级。"),
57
- showDiff: Schema.boolean().description("标注相邻两次日志输出的时间差。"),
66
+ showDiff: Schema.boolean().description(
67
+ "标注相邻两次日志输出的时间差。",
68
+ ),
58
69
  showTime: Schema.union([Boolean, String])
59
70
  .default(true)
60
71
  .description("输出日志所使用的时间格式。"),
@@ -101,7 +112,10 @@ export function prepare(config: Config = {}) {
101
112
  }
102
113
 
103
114
  /** 递归为所有子命名空间补全 base 等级(未显式设置时继承父级) */
104
- function ensureBaseLevel(config: Logger.LevelConfig, base: number) {
115
+ function ensureBaseLevel(
116
+ config: Logger.LevelConfig,
117
+ base: number,
118
+ ) {
105
119
  config.base ??= base;
106
120
  Object.values(config).forEach((value) => {
107
121
  if (typeof value !== "object") return;
@@ -113,7 +127,9 @@ export function prepare(config: Config = {}) {
113
127
 
114
128
  // KOISHI_DEBUG 指定的各个命名空间一律开启 DEBUG 级输出
115
129
  if (process.env["KOISHI_DEBUG"]) {
116
- for (const name of process.env["KOISHI_DEBUG"].split(",")) {
130
+ for (const name of process.env["KOISHI_DEBUG"].split(
131
+ ",",
132
+ )) {
117
133
  new Logger(name).level = Logger.DEBUG;
118
134
  }
119
135
  }