@yunzai-ng/cli 0.1.0

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 (60) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +34 -0
  3. package/dist/args.d.ts +44 -0
  4. package/dist/args.d.ts.map +1 -0
  5. package/dist/args.js +116 -0
  6. package/dist/args.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +40 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +16 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +181 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/init.d.ts +12 -0
  16. package/dist/commands/init.d.ts.map +1 -0
  17. package/dist/commands/init.js +61 -0
  18. package/dist/commands/init.js.map +1 -0
  19. package/dist/commands/plugin.d.ts +14 -0
  20. package/dist/commands/plugin.d.ts.map +1 -0
  21. package/dist/commands/plugin.js +115 -0
  22. package/dist/commands/plugin.js.map +1 -0
  23. package/dist/commands/start.d.ts +19 -0
  24. package/dist/commands/start.d.ts.map +1 -0
  25. package/dist/commands/start.js +102 -0
  26. package/dist/commands/start.js.map +1 -0
  27. package/dist/help.d.ts +6 -0
  28. package/dist/help.d.ts.map +1 -0
  29. package/dist/help.js +42 -0
  30. package/dist/help.js.map +1 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +17 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/link.d.ts +16 -0
  36. package/dist/link.d.ts.map +1 -0
  37. package/dist/link.js +103 -0
  38. package/dist/link.js.map +1 -0
  39. package/dist/run.d.ts +7 -0
  40. package/dist/run.d.ts.map +1 -0
  41. package/dist/run.js +115 -0
  42. package/dist/run.js.map +1 -0
  43. package/dist/terminal.d.ts +43 -0
  44. package/dist/terminal.d.ts.map +1 -0
  45. package/dist/terminal.js +94 -0
  46. package/dist/terminal.js.map +1 -0
  47. package/package.json +31 -0
  48. package/src/args.test.ts +52 -0
  49. package/src/args.ts +132 -0
  50. package/src/bin.ts +40 -0
  51. package/src/commands/doctor.ts +205 -0
  52. package/src/commands/init.ts +65 -0
  53. package/src/commands/plugin.ts +133 -0
  54. package/src/commands/start.ts +121 -0
  55. package/src/help.ts +42 -0
  56. package/src/index.ts +16 -0
  57. package/src/link.test.ts +53 -0
  58. package/src/link.ts +117 -0
  59. package/src/run.ts +124 -0
  60. package/src/terminal.ts +102 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * 模块职责:`yzng plugin new <名称>` —— 生成可立即运行的插件骨架
3
+ * 依赖方向:仅使用 node:fs / node:path 与内核的 `resolvePaths`
4
+ * 生命周期:一次性,执行完毕即退出
5
+ * 注意事项:**生成的是 JavaScript,而非 TypeScript。** 骨架的目的在于使开发者在极短时间内
6
+ * 观察到 `#ping` 得到回复;TS 插件额外需要一份 tsconfig、一次编译,以及
7
+ * "修改源码后未生效"一类的排查,而这些均发生于其确认框架可用之前。
8
+ * 文件末尾以注释给出了改用 TS 的方式(编译至 dist/index.js)。
9
+ *
10
+ * 绝不覆盖已存在的目录 —— 误操作重复执行 `plugin new` 不应清除已编写的代码。
11
+ */
12
+ import { mkdir, writeFile } from "node:fs/promises";
13
+ import { join } from "node:path";
14
+ import { resolvePaths } from "@yunzai-ng/core";
15
+ import { bold, cyan, dim, green, print, printErr, red } from "../terminal.js";
16
+ /** 插件名合法字符,与内核 `definePlugin` 的校验保持一致 */
17
+ const NAME_RE = /^[a-zA-Z][\w.-]*$/;
18
+ /**
19
+ * 生成骨架源码
20
+ * @param name 插件名
21
+ * @returns index.js 内容
22
+ */
23
+ function scaffold(name) {
24
+ return `import { definePlugin, s } from "@yunzai-ng/core"
25
+
26
+ /**
27
+ * ${name}
28
+ *
29
+ * 本文件为 \`yzng plugin new\` 生成的骨架。整个插件即一个 setup 函数:
30
+ * 内核将各项能力经由 ctx 注入,插件无需(亦无法取得)任何全局变量。
31
+ */
32
+ export default definePlugin({
33
+ name: "${name}",
34
+ version: "0.1.0",
35
+ description: "Yunzai NG 插件骨架",
36
+
37
+ // 声明配置 schema 即自动获得 config/${name}.yaml 以及面板中的一张表单,
38
+ // 无需自行编写文件读写代码,亦无需为面板编写任何前端代码
39
+ configSchema: s.object({
40
+ greeting: s.string().default("pong").title("回复内容")
41
+ }),
42
+
43
+ setup(ctx) {
44
+ ctx
45
+ .command("#ping")
46
+ .desc("检测插件是否正常运行")
47
+ .action(async e => {
48
+ await e.reply(ctx.config.get().greeting)
49
+ })
50
+
51
+ // 定时任务:注册即受内核托管,插件卸载时内核自动停止它
52
+ // ctx.cron("0 0 8 * * *", () => ctx.logger.info("每日任务已执行"))
53
+
54
+ // 需要保存状态时使用 ctx.kv,其已绑定以插件名隔离的命名空间
55
+ // await ctx.kv.set("上次运行时间", Date.now())
56
+ }
57
+ })
58
+
59
+ // 改用 TypeScript:将源码置于 src/,编译至 dist/index.js,
60
+ // 并在 package.json 中声明 "main": "dist/index.js" —— 内核优先采用该入口
61
+ `;
62
+ }
63
+ /**
64
+ * 生成 package.json
65
+ * @param name 插件名
66
+ * @returns 文件内容
67
+ */
68
+ function manifest(name) {
69
+ return `${JSON.stringify({
70
+ name,
71
+ version: "0.1.0",
72
+ description: `Yunzai NG 插件:${name}`,
73
+ type: "module",
74
+ main: "index.js"
75
+ }, null, 2)}\n`;
76
+ }
77
+ /**
78
+ * 生成插件骨架
79
+ * @param opts 参数
80
+ * @returns 进程退出码
81
+ */
82
+ export async function runPluginNew(opts) {
83
+ if (!NAME_RE.test(opts.name)) {
84
+ printErr(red(`插件名 ${opts.name} 不合法:需以字母开头,仅含字母、数字与 . - _`));
85
+ printErr(dim("该名称同时作为配置文件名与存储命名空间,因此约束严于目录名"));
86
+ return 1;
87
+ }
88
+ const paths = resolvePaths({ home: opts.home });
89
+ const dir = join(paths.plugins, opts.name);
90
+ try {
91
+ // recursive: false —— 目录已存在时抛出 EEXIST,此即所需的保护行为
92
+ await mkdir(paths.plugins, { recursive: true });
93
+ await mkdir(dir);
94
+ }
95
+ catch (err) {
96
+ const code = err.code;
97
+ if (code === "EEXIST") {
98
+ printErr(red(`${dir} 已存在`));
99
+ printErr(dim("请改用其他名称,或先移走原有目录 —— 本命令不覆盖任何已有文件"));
100
+ return 1;
101
+ }
102
+ printErr(red(`创建目录失败:${err instanceof Error ? err.message : String(err)}`));
103
+ return 1;
104
+ }
105
+ await writeFile(join(dir, "index.js"), scaffold(opts.name), "utf8");
106
+ await writeFile(join(dir, "package.json"), manifest(opts.name), "utf8");
107
+ print();
108
+ print(` ${bold(green("已生成"))} ${cyan(dir)}`);
109
+ print();
110
+ print(` ${dim("下一步")} ${cyan("yzng dev")} ${dim("随后向机器人发送")} ${cyan("#ping")}`);
111
+ print(` ${dim("修改代码后在面板的插件页执行重载,无需重启整个进程")}`);
112
+ print();
113
+ return 0;
114
+ }
115
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/commands/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { LogLevel } from "@yunzai-ng/types";
2
+ /** 启动参数 */
3
+ export interface StartOptions {
4
+ /** 应用主目录 */
5
+ readonly home?: string | undefined;
6
+ /** 额外插件目录 */
7
+ readonly extraDirs?: readonly string[] | undefined;
8
+ /** 启动期日志级别 */
9
+ readonly logLevel?: LogLevel | undefined;
10
+ /** 是否输出至控制台 */
11
+ readonly console?: boolean | undefined;
12
+ }
13
+ /**
14
+ * 启动机器人
15
+ * @param opts 启动参数
16
+ * @returns 进程退出码
17
+ */
18
+ export declare function runStart(opts?: StartOptions): Promise<number>;
19
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAOhD,WAAW;AACX,MAAM,WAAW,YAAY;IAC3B,YAAY;IACZ,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,aAAa;IACb,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAClD,cAAc;IACd,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IACxC,eAAe;IACf,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACvC;AAwCD;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CvE"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 模块职责:`yzng start` / `yzng dev` —— 装配内核、启动、接管信号、输出入口信息
3
+ * 依赖方向:依赖 @yunzai-ng/core 的公开入口;不涉及其他命令
4
+ * 生命周期:进程的整个生命周期。返回时进程即将退出
5
+ * 注意事项:**必须自行持有事件循环。** `app.start()` 之后,进程能否继续存活取决于
6
+ * 是否存在活跃 handle:面板启用时存在监听 socket,账号连接时存在 TCP 连接。
7
+ * 但"面板关闭且未配置任何账号"正是全新安装后的缺省状态,此时 Node 会判定
8
+ * 无待处理工作而直接退出 —— 使用者所见的现象是 `yzng start` 输出横幅后即
9
+ * 返回终端提示符,且退出码为 0,无法据此判断属于异常终止还是既定行为。
10
+ * 因此此处以一个长周期定时器兜底,并交由 `app.own()` 在停机时清除。
11
+ */
12
+ import { createApp, resolvePaths } from "@yunzai-ng/core";
13
+ import { linkFramework } from "../link.js";
14
+ import { bold, cyan, dim, green, print, printErr, printRows, red, yellow } from "../terminal.js";
15
+ /** 兜底定时器的间隔:取值足够大以避免产生可测量的开销,同时不超出 32 位范围 */
16
+ const KEEPALIVE_MS = 0x7fffffff;
17
+ /**
18
+ * 输出启动横幅
19
+ *
20
+ * 刻意经由 stdout 而非 logger:这几行给出的是面板访问位置,使用者在配置出错、
21
+ * 日志级别被调整为 warn 时最需要看到,而那恰是 logger 不再输出的情形。
22
+ * @param app 已启动的应用
23
+ */
24
+ function printBanner(app) {
25
+ const settings = app.settings;
26
+ const rows = [
27
+ ["版本", app.version],
28
+ ["主目录", cyan(app.paths.home)],
29
+ ["环境", `${app.platform.os}/${app.platform.arch}${app.platform.isTermux ? " · Termux" : ""} · Node ${app.platform.nodeVersion}`]
30
+ ];
31
+ if (settings.server.enable) {
32
+ const host = settings.server.host === "0.0.0.0" ? "127.0.0.1" : settings.server.host;
33
+ rows.push(["面板", cyan(`http://${host}:${settings.server.port}/`)]);
34
+ // 令牌完整输出而非掩码:此处为本机终端,使用者此刻正需将其复制至浏览器。
35
+ // 掩码只会迫使其转往 config/yunzai.yaml 查阅,安全性并无变化,却增加一道步骤
36
+ if (settings.server.token)
37
+ rows.push(["令牌", settings.server.token]);
38
+ }
39
+ else {
40
+ rows.push(["面板", dim("已在配置中关闭(server.enable)")]);
41
+ }
42
+ const online = app.subsystems.bots.online().length;
43
+ const accounts = app.subsystems.accounts.list().length;
44
+ rows.push(["账号", accounts === 0 ? yellow("尚未配置,请在面板中添加") : `${online}/${accounts} 在线`]);
45
+ print();
46
+ print(` ${bold(green("Yunzai NG"))} ${dim("已启动")}`);
47
+ print();
48
+ printRows(rows);
49
+ print();
50
+ print(dim(" Ctrl+C 停机(再次按下则强制退出)"));
51
+ print();
52
+ }
53
+ /**
54
+ * 启动机器人
55
+ * @param opts 启动参数
56
+ * @returns 进程退出码
57
+ */
58
+ export async function runStart(opts = {}) {
59
+ // 建立链接须早于 createApp:插件在 app.start() 中被 import,此时 Node 的解析
60
+ // 已经开始,届时再补建链接已无效果
61
+ const home = resolvePaths({ home: opts.home }).home;
62
+ const link = await linkFramework(home);
63
+ let app;
64
+ try {
65
+ app = await createApp({
66
+ home,
67
+ logLevel: opts.logLevel,
68
+ console: opts.console,
69
+ extraDirs: opts.extraDirs === undefined ? undefined : [...opts.extraDirs]
70
+ });
71
+ }
72
+ catch (err) {
73
+ // 装配失败(目录无法建立、KV 驱动全部无法打开)时尚无可用的 logger,只能直接写入终端
74
+ printErr(red(`启动失败:${err instanceof Error ? err.message : String(err)}`));
75
+ printErr(dim("常见原因:主目录无写入权限、磁盘空间不足,或上一实例仍占用数据目录"));
76
+ return 1;
77
+ }
78
+ // 信号接管须在 start() 之前装配:插件加载可能耗时数秒,其间按下 Ctrl+C
79
+ // 亦应能够优雅停机,而不是残留一个半初始化状态的数据目录
80
+ app.handleSignals();
81
+ // 链接结果延至此处报告:createApp 之前尚无 logger,而该事项的重要程度不足以直接写入终端
82
+ if (link.linked.length > 0)
83
+ app.logger.debug(`已将框架包链接至主目录:${link.linked.join("、")}`);
84
+ for (const [name, reason] of link.failed) {
85
+ app.logger.warn(`${name} 未能链接至主目录,${home} 下的第三方插件将无法 import 该包:${reason}`);
86
+ }
87
+ const keepalive = setInterval(() => undefined, KEEPALIVE_MS);
88
+ app.own(() => clearInterval(keepalive));
89
+ try {
90
+ await app.start();
91
+ }
92
+ catch (err) {
93
+ app.logger.fatal(`启动失败:${err instanceof Error ? err.message : String(err)}`);
94
+ await app.stop();
95
+ return 1;
96
+ }
97
+ printBanner(app);
98
+ // 不返回:进程由 handleSignals() 中的 process.exit 结束。
99
+ // 该 promise 永不兑现是刻意的 —— 参见文件头
100
+ return new Promise(() => undefined);
101
+ }
102
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAY,MAAM,iBAAiB,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEhG,6CAA6C;AAC7C,MAAM,YAAY,GAAG,UAAU,CAAA;AAc/B;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,GAAQ;IAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAA;IAC7B,MAAM,IAAI,GAAuB;QAC/B,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC;QACnB,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;KAChI,CAAA;IAED,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAA;QACpF,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAA;QAClE,sCAAsC;QACtC,kDAAkD;QAClD,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACrE,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,CAAA;IAClD,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAA;IACtD,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,QAAQ,KAAK,CAAC,CAAC,CAAA;IAEvF,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACpD,KAAK,EAAE,CAAA;IACP,SAAS,CAAC,IAAI,CAAC,CAAA;IACf,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAA;IACpC,KAAK,EAAE,CAAA;AACT,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB,EAAE;IACpD,0DAA0D;IAC1D,mBAAmB;IACnB,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAA;IACnD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAA;IAEtC,IAAI,GAAQ,CAAA;IACZ,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC;YACpB,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;SAC1E,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iDAAiD;QACjD,QAAQ,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QACzE,QAAQ,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAA;QAClD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,6CAA6C;IAC7C,8BAA8B;IAC9B,GAAG,CAAC,aAAa,EAAE,CAAA;IAEnB,sDAAsD;IACtD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACpF,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,aAAa,IAAI,yBAAyB,MAAM,EAAE,CAAC,CAAA;IAC5E,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAA;IAEvC,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC5E,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAChB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,WAAW,CAAC,GAAG,CAAC,CAAA;IAChB,8CAA8C;IAC9C,8BAA8B;IAC9B,OAAO,IAAI,OAAO,CAAS,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;AAC7C,CAAC"}
package/dist/help.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 输出帮助文本
3
+ * @param version CLI 版本
4
+ */
5
+ export declare function printHelp(version: string): void;
6
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA2B/C"}
package/dist/help.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 模块职责:`yzng --help` 的文案
3
+ * 依赖方向:仅依赖终端输出
4
+ * 生命周期:纯函数
5
+ * 注意事项:帮助文案手写而非由命令表自动生成。自动生成的帮助仅能罗列选项,
6
+ * 而初次使用者真正需要的是"当前应执行哪一条命令"—— 那属于顺序信息,
7
+ * 选项表格无法表达。
8
+ */
9
+ import { bold, cyan, dim, print } from "./terminal.js";
10
+ /**
11
+ * 输出帮助文本
12
+ * @param version CLI 版本
13
+ */
14
+ export function printHelp(version) {
15
+ print();
16
+ print(` ${bold("Yunzai NG")} ${dim(version)} ${dim("面向 QQ 机器人的可插拔运行时")}`);
17
+ print();
18
+ print(` ${bold("用法")} ${cyan("yzng")} <命令> [选项]`);
19
+ print();
20
+ print(` ${bold("命令")}`);
21
+ print(` ${cyan("init")} 生成主目录与缺省配置,不启动运行时`);
22
+ print(` ${cyan("start")} 启动机器人与面板服务`);
23
+ print(` ${cyan("dev")} 同 start,但日志级别为 debug`);
24
+ print(` ${cyan("doctor")} 环境自检:运行环境、目录、原生依赖、端口占用`);
25
+ print(` ${cyan("plugin new <名称>")} 在插件目录中生成可直接运行的插件骨架`);
26
+ print();
27
+ print(` ${bold("选项")}`);
28
+ print(` ${cyan("-c, --home <目录>")} 指定主目录(亦可通过环境变量 YZNG_HOME 指定)`);
29
+ print(` ${cyan("-d, --debug")} 将启动期日志级别设为 debug`);
30
+ print(` ${cyan(" --no-console")} 仅写入日志文件,不输出至终端(以服务方式运行时使用)`);
31
+ print(` ${cyan(" --plugins <目录>")} 追加一个插件目录,多个目录以逗号分隔`);
32
+ print(` ${cyan(" --port <端口>")} 仅 doctor:指定探测的端口`);
33
+ print(` ${cyan("-v, --version")} 输出版本号`);
34
+ print(` ${cyan("-h, --help")} 输出本帮助`);
35
+ print();
36
+ print(` ${bold("首次使用")}`);
37
+ print(` ${dim("1.")} ${cyan("yzng init")} ${dim("确认各类文件的存放位置")}`);
38
+ print(` ${dim("2.")} ${cyan("yzng start")} ${dim("终端将输出面板地址与访问令牌")}`);
39
+ print(` ${dim("3.")} ${dim("在面板中安装适配器插件并添加账号 —— 账号登录同为插件,不在内核之内")}`);
40
+ print();
41
+ }
42
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAEtD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAA;IAC3E,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACnD,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxB,KAAK,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,iCAAiC,CAAC,CAAA;IAC3D,KAAK,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAA;IACpD,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA;IAC9D,KAAK,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,oCAAoC,CAAC,CAAA;IAChE,KAAK,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAA;IAC3D,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxB,KAAK,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,gCAAgC,CAAC,CAAA;IACrE,KAAK,CAAC,OAAO,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAA;IAC1D,KAAK,CAAC,OAAO,IAAI,CAAC,kBAAkB,CAAC,8BAA8B,CAAC,CAAA;IACpE,KAAK,CAAC,OAAO,IAAI,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAA;IAC7D,KAAK,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,CAAA;IACzD,KAAK,CAAC,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAA;IAC/C,KAAK,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,CAAA;IAC/C,KAAK,EAAE,CAAA;IACP,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC1B,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;IACvE,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;IAC1E,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAA;IACvE,KAAK,EAAE,CAAA;AACT,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 模块职责:`@yunzai-ng/cli` 的入口,导出可复用的命令实现
3
+ * 依赖方向:仅作转发
4
+ * 生命周期:无
5
+ * 注意事项:`bin.ts` **不在**此处导出 —— 其具有顶层副作用(注册进程监听器、执行命令、
6
+ * 设置退出码),被 import 一次即等同于执行一次。以库的方式调用请使用 `run()`。
7
+ */
8
+ export * from "./args.js";
9
+ export * from "./help.js";
10
+ export * from "./link.js";
11
+ export * from "./run.js";
12
+ export * from "./terminal.js";
13
+ export * from "./commands/start.js";
14
+ export * from "./commands/init.js";
15
+ export * from "./commands/doctor.js";
16
+ export * from "./commands/plugin.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 模块职责:`@yunzai-ng/cli` 的入口,导出可复用的命令实现
3
+ * 依赖方向:仅作转发
4
+ * 生命周期:无
5
+ * 注意事项:`bin.ts` **不在**此处导出 —— 其具有顶层副作用(注册进程监听器、执行命令、
6
+ * 设置退出码),被 import 一次即等同于执行一次。以库的方式调用请使用 `run()`。
7
+ */
8
+ export * from "./args.js";
9
+ export * from "./help.js";
10
+ export * from "./link.js";
11
+ export * from "./run.js";
12
+ export * from "./terminal.js";
13
+ export * from "./commands/start.js";
14
+ export * from "./commands/init.js";
15
+ export * from "./commands/doctor.js";
16
+ export * from "./commands/plugin.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA"}
package/dist/link.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /** 链接结果 */
2
+ export interface LinkReport {
3
+ /** 本次新建或修复的包名 */
4
+ readonly linked: readonly string[];
5
+ /** 已存在、无需处理的包名 */
6
+ readonly kept: readonly string[];
7
+ /** 失败的包名与原因 */
8
+ readonly failed: readonly (readonly [string, string])[];
9
+ }
10
+ /**
11
+ * 在主目录中建立框架包链接
12
+ * @param home 主目录
13
+ * @returns 链接结果
14
+ */
15
+ export declare function linkFramework(home: string): Promise<LinkReport>;
16
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAkCA,WAAW;AACX,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,kBAAkB;IAClB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,eAAe;IACf,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAA;CACxD;AAmCD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkCrE"}
package/dist/link.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * 模块职责:将框架包链接至主目录,使 `<home>/plugins` 下的第三方插件可以 import 内核
3
+ * 依赖方向:仅使用 node:fs / node:path / node:module
4
+ * 生命周期:`init` 与 `start` 各调用一次,幂等
5
+ * 注意事项:**此项并非可选的便利功能,而是插件能否加载的前提。** 插件位于使用者的主目录
6
+ * (例如 `%LOCALAPPDATA%\yunzai-ng\plugins\我的插件\`),而框架安装于其他位置;
7
+ * Node 解析 `import "@yunzai-ng/core"` 时仅会自插件目录逐级向上查找
8
+ * `node_modules`,始终无法到达框架所在目录 —— 其结果是每个插件均以
9
+ * `ERR_MODULE_NOT_FOUND` 失败,而报错信息完全不指示应对方式。
10
+ * 在 `<home>/node_modules/@yunzai-ng/` 下建立两个链接即可解决,pnpm 自身
11
+ * 亦采用同一做法。
12
+ *
13
+ * Windows 上使用 junction 而非 symlink:目录 symlink 需要开发者模式或
14
+ * 管理员权限,junction 无此要求,而对 Node 的解析行为两者等价。
15
+ *
16
+ * 已存在的链接一律不作改动,仅替换**指向已消失目标**的那一类(框架被移动或
17
+ * 重装后会出现该情形)。绝不覆盖真实目录:开发时主目录可能即为仓库本身,
18
+ * 其中的 `node_modules/@yunzai-ng` 由 pnpm 建立,改动它将破坏开发环境。
19
+ */
20
+ import { lstat, mkdir, readlink, rm, symlink } from "node:fs/promises";
21
+ import { dirname, join, resolve } from "node:path";
22
+ import { createRequire } from "node:module";
23
+ import { existsSync } from "node:fs";
24
+ /**
25
+ * 需要暴露给插件的框架包
26
+ *
27
+ * 三者与分层门禁中插件可依赖的包集合(scripts/check-layering.mjs 的 plugins 规则,
28
+ * 现已移交各插件仓库的 eslint)保持一致:**门禁允许 import 的包,此处必须链接**,
29
+ * 否则插件在开发时类型检查通过,装到使用者主目录后却以 ERR_MODULE_NOT_FOUND 失败。
30
+ * `jsx` 曾因不在此列而具备该缺陷。
31
+ */
32
+ const FRAMEWORK_PACKAGES = ["@yunzai-ng/core", "@yunzai-ng/types", "@yunzai-ng/jsx"];
33
+ /**
34
+ * 定位一个已安装包的目录
35
+ *
36
+ * 解析 `package.json` 而非包主入口:`exports` 字段可将主入口指向 `dist/` 内的深层路径,
37
+ * 对其取 dirname 会得到 `dist` 而非包根。所有框架包均显式导出了
38
+ * `"./package.json"`,正是为使此类工具能够查询其包根位置。
39
+ * @param name 包名
40
+ * @returns 包根目录;解析不到时 undefined
41
+ */
42
+ function packageDir(name) {
43
+ try {
44
+ return dirname(createRequire(import.meta.url).resolve(`${name}/package.json`));
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /**
51
+ * 判断一个已存在的路径是否需要重建
52
+ * @param path 链接路径
53
+ * @returns 需要重建时 true
54
+ */
55
+ async function isBrokenLink(path) {
56
+ const stat = await lstat(path);
57
+ if (!stat.isSymbolicLink())
58
+ return false;
59
+ try {
60
+ const target = await readlink(path);
61
+ return !existsSync(resolve(dirname(path), target));
62
+ }
63
+ catch {
64
+ return true;
65
+ }
66
+ }
67
+ /**
68
+ * 在主目录中建立框架包链接
69
+ * @param home 主目录
70
+ * @returns 链接结果
71
+ */
72
+ export async function linkFramework(home) {
73
+ const scopeDir = join(home, "node_modules", "@yunzai-ng");
74
+ const linked = [];
75
+ const kept = [];
76
+ const failed = [];
77
+ await mkdir(scopeDir, { recursive: true });
78
+ for (const name of FRAMEWORK_PACKAGES) {
79
+ const short = name.slice("@yunzai-ng/".length);
80
+ const link = join(scopeDir, short);
81
+ const target = packageDir(name);
82
+ if (target === undefined) {
83
+ failed.push([name, "CLI 的模块图中不存在该包,框架安装可能不完整"]);
84
+ continue;
85
+ }
86
+ try {
87
+ if (existsSync(link)) {
88
+ if (!(await isBrokenLink(link))) {
89
+ kept.push(name);
90
+ continue;
91
+ }
92
+ await rm(link, { recursive: true, force: true });
93
+ }
94
+ await symlink(target, link, "junction");
95
+ linked.push(name);
96
+ }
97
+ catch (err) {
98
+ failed.push([name, err instanceof Error ? err.message : String(err)]);
99
+ }
100
+ }
101
+ return { linked, kept, failed };
102
+ }
103
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.js","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"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"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 执行一次命令
3
+ * @param argv `process.argv.slice(2)`
4
+ * @returns 退出码
5
+ */
6
+ export declare function run(argv: readonly string[]): Promise<number>;
7
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAqDA;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiElE"}
package/dist/run.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * 模块职责:命令分发 —— 将解析后的参数转为一次命令执行,返回退出码
3
+ * 依赖方向:依赖 args / help / commands/*;不涉及 process.exit(该职责属于 bin.ts)
4
+ * 生命周期:一次调用
5
+ * 注意事项:**返回退出码而非自行退出。** 由此 `run()` 可被测试直接调用并断言
6
+ * 返回值;将 `process.exit` 置于函数深处的程序无法编写测试 —— 一经调用
7
+ * 即会终止测试进程自身。
8
+ *
9
+ * 未知命令返回 1 并输出帮助,而非静默无响应:命令名拼写错误是最常见的
10
+ * 使用者错误,此时"无任何反馈"是最差的处理方式。
11
+ */
12
+ import { createRequire } from "node:module";
13
+ import { flagBoolean, flagString, parseArgs } from "./args.js";
14
+ import { printHelp } from "./help.js";
15
+ import { cyan, dim, print, printErr, red } from "./terminal.js";
16
+ import { runStart } from "./commands/start.js";
17
+ import { runInit } from "./commands/init.js";
18
+ import { runDoctor } from "./commands/doctor.js";
19
+ import { runPluginNew } from "./commands/plugin.js";
20
+ /**
21
+ * 读取自身版本号
22
+ *
23
+ * 自 package.json 读取而非硬编码常量:硬编码的版本号必然与发布时的实际版本脱节,
24
+ * 而版本号有误会使使用者提交的 issue 全部指向错误的代码。
25
+ * @returns 版本号
26
+ */
27
+ function readVersion() {
28
+ try {
29
+ const require = createRequire(import.meta.url);
30
+ const pkg = require("../package.json");
31
+ return pkg.version ?? "0.0.0";
32
+ }
33
+ catch {
34
+ return "0.0.0";
35
+ }
36
+ }
37
+ /**
38
+ * 将 `--plugins a,b` 拆分为目录数组
39
+ * @param args 解析结果
40
+ * @returns 目录数组;未提供时 undefined
41
+ */
42
+ function pluginDirs(args) {
43
+ const raw = flagString(args, "plugins");
44
+ if (raw === undefined)
45
+ return undefined;
46
+ const dirs = raw
47
+ .split(",")
48
+ .map(s => s.trim())
49
+ .filter(s => s !== "");
50
+ return dirs.length > 0 ? dirs : undefined;
51
+ }
52
+ /**
53
+ * 执行一次命令
54
+ * @param argv `process.argv.slice(2)`
55
+ * @returns 退出码
56
+ */
57
+ export async function run(argv) {
58
+ const args = parseArgs(argv);
59
+ const version = readVersion();
60
+ if (flagBoolean(args, "version")) {
61
+ print(version);
62
+ return 0;
63
+ }
64
+ if (args.command === "" || args.command === "help" || flagBoolean(args, "help")) {
65
+ printHelp(version);
66
+ // 显式请求帮助属于成功;未提供任何参数亦视为成功 —— 仅输入命令名以查看用法是正常行为
67
+ return 0;
68
+ }
69
+ const home = flagString(args, "home");
70
+ const debug = flagBoolean(args, "debug");
71
+ switch (args.command) {
72
+ case "init":
73
+ return runInit({ home });
74
+ case "start":
75
+ return runStart({
76
+ home,
77
+ extraDirs: pluginDirs(args),
78
+ logLevel: debug ? "debug" : undefined,
79
+ console: flagBoolean(args, "console", true)
80
+ });
81
+ case "dev":
82
+ // dev 与 start 的唯一区别在于缺省日志级别。刻意不提供文件监听自动重载:
83
+ // Node 的 ESM 模块缓存无法真正清除(见 plugin/host.ts 第 4 条注意事项),
84
+ // 提供名义上的热重载会使"修改未生效"成为常态性困惑
85
+ return runStart({
86
+ home,
87
+ extraDirs: pluginDirs(args),
88
+ logLevel: "debug",
89
+ console: flagBoolean(args, "console", true)
90
+ });
91
+ case "doctor": {
92
+ const port = flagString(args, "port");
93
+ const parsed = port === undefined ? undefined : Number.parseInt(port, 10);
94
+ return runDoctor({
95
+ home,
96
+ host: flagString(args, "host"),
97
+ port: parsed !== undefined && Number.isInteger(parsed) ? parsed : undefined
98
+ });
99
+ }
100
+ case "plugin": {
101
+ const sub = args.positional[0] ?? "";
102
+ const name = args.positional[1] ?? "";
103
+ if (sub !== "new" || name === "") {
104
+ printErr(red("用法:yzng plugin new <名称>"));
105
+ return 1;
106
+ }
107
+ return runPluginNew({ name, home });
108
+ }
109
+ default:
110
+ printErr(red(`未知命令:${args.command}`));
111
+ printErr(dim(`可用命令见 ${cyan("yzng --help")}`));
112
+ return 1;
113
+ }
114
+ }
115
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}