4 Commits
Author SHA1 Message Date
saopig1andClaude Opus 5 a804b2edc1 feat(setup)!: require Node 22.12+ and drop Node 20 (#152)
better-sqlite3 stopped publishing prebuilt binaries for Node 20's ABI
(115) in 12.10.0 - upstream, not a mirror gap:

    12.8.0 / 12.9.0   115 127 131 137 141
    12.10.0+              127 137 141 147

`better-sqlite3: ^12.8.0` resolves well past that, so every Node 20
install 404'd on the CDN, fell through to the source build, and demanded
Python plus a C++ toolchain before the bot could start at all. package.json
went on claiming `^20.19.0` worked, and the README went on recommending
Node 20 as one of two blessed versions. It was not a supported
configuration in any meaningful sense - it was a trap.

So say so up front: engines, both setup scripts, and the Docker images now
require Node 22.12+ (or 24+, which still needs a source build for opus).
The version gate in setup.bat / setup.sh is kept byte-identical to the
engines range, as before.

Also copy scripts/lib/console-log.mjs into the production image. The
previous commit had check-native.mjs import it, and the Dockerfile copies
check-native.mjs in on its own for `docker exec ... npm start` - without
its dependency that preflight now dies with ERR_MODULE_NOT_FOUND.

BREAKING CHANGE: Node 20 is no longer supported. Node 22.12 LTS or newer
is required; setup refuses to run on anything older.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 18:27:22 +08:00
saopig1andClaude Opus 5 5e9ae49f52 fix(setup): stop a failed console write from aborting setup (#152)
setup.bat runs `chcp 65001` and shows binary progress on stderr. On some
Windows consoles - the reporter's Windows Server 2012 R2 above all - that
code page cannot render non-ASCII text and the OS fails the write with
EIO. process.stderr is an ordinary stream, so the EIO arrived as an
'error' event, and with no listener attached Node rethrew it as an
uncaught exception:

    Error: write EIO { errno: -4070, code: 'EIO', syscall: 'write' }
        at log (scripts/download-binaries.mjs:84:18)
        at ensureFfmpeg (scripts/download-binaries.mjs:451:5)

Those two frames pin it exactly: line 84 is `process.stderr.write`, and
line 451 is the first log line of the whole run that contains Chinese.
The three lines before it are pure ASCII and printed fine. Nothing was
wrong with the download it was announcing - setup killed itself inside
its own progress logging and reported the native modules as unusable.

scripts/lib/console-log.mjs now wraps both streams: it listens for
'error' so the failure can never be fatal, then degrades that stream
rather than dying - first to an ASCII rendering that keeps the English
half of each bilingual line, then silent if the stream is really gone.
The streams degrade independently, so a console that gives up costs
setup.log nothing: that stdout is a redirected file. check-native.mjs
gets the same treatment, since the console that cannot print its Chinese
is exactly the one a user needs its English from.

Also report a 404 honestly. better-sqlite3 dropped its Node 20 (ABI 115)
prebuilds in 12.10.0 and @discordjs/opus 0.10.0 has none for Node 24, so
users on those majors fall through to the source build and are told to
install Python and a C++ toolchain - when switching Node major takes two
minutes. Nothing in the output said so, and the README recommended
Node 20 as if it still worked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 17:59:31 +08:00
saopig1andClaude Opus 5 03ffd09d36 fix(setup): 按 Node ABI 校验并自动修复原生模块
换过 Node 大版本之后安装就废了,而且安装脚本还会报告成功。原生模块只能在
编译它的那个 Node ABI 上加载(Node 20 = 115、22 = 127、24 = 137),而
better-sqlite3 的 .node 放在与 ABI 无关的固定路径下,旧的 download-binaries
只检查「文件存在且大于 500KB」,于是给 Node 24 编译的 1.9MB 文件在 Node 22
下原样保留,跳过重新下载,机器人启动时死在 NODE_MODULE_VERSION 上。
(@discordjs/opus 的目录名里带 ABI,反而歪打正着没这个问题。)

download-binaries.mjs 现在不看文件大小,而是在子进程里真的把每个包 load 一遍
——子进程是必须的,Windows 上父进程加载过的 .node 会一直被映射,系统随后拒绝
删除或覆盖它。注意 better-sqlite3 的 addon 是在 Database 构造函数里惰性加载的,
所以光 require 这个包探测不出问题,得真的开一个内存库。

失败就按当前 ABI 重新安装,整个替换过程是先把旧文件挪到 node_modules/
.tsmusicbot-backup、下载解压到暂存目录、原子 rename 就位、再探测一次,任何
一步失败都把原文件还原回去——删掉不匹配的二进制却下载不下来,比原来的版本
更糟。备份特意放在包的 build/ 之外,因为源码编译回退会调 node-gyp 把 build/
清空。被中断(比如下载到一半 Ctrl+C)遗留的备份,下一次运行会自动认领回来。

其他一并修掉的问题:
- 版本号原本硬编码 12.8.0,实际锁的是 12.11.1,一旦真的触发下载就会 404;
  改为从 node_modules 里读。
- 三个模块原本用 Promise.all 并发。源码编译走的是 execSync,会把事件循环整个
  卡住几分钟,而 download() 的 120 秒超时是挂在同一个循环上的 socket 静默计时
  器——循环一恢复,还在传输中的连接就会被判超时。这不是小概率竞态:npmmirror
  上没有 ABI 137 的 opus,也没有 ABI 115 的 better-sqlite3,所以在 Node 24 和
  Node 20 上必定有一个模块在 100ms 内 404 并开始编译,而 ffmpeg 的 80MB 下载
  正在进行。ffmpeg 是可选模块,于是它被误杀后只记一条 WARN,脚本照样 exit 0,
  setup 打印「Setup Complete」,用户装完却没有 ffmpeg,放什么都放不出来。
  改成严格串行执行。
- 必需模块(opus / better-sqlite3)失败才返回非零;ffmpeg 有系统 ffmpeg 兜底,
  只警告。setup.bat 里原本形同虚设的 FAILED 标志接上了,必需模块失败会中止安装,
  不再是「装完才发现」。
- 4b 步骤原本把全部输出重定向进 setup.log,用户盯着不动的窗口以为卡死;现在
  进度走 stderr 实时显示,完整记录仍进日志。

新增 scripts/check-native.mjs:启动前预检,直接说清楚哪个模块对不上、分别是哪
个 ABI、怎么修,而不是抛一串 NODE_MODULE_VERSION 堆栈。scripts\start.bat、根目
录 start.bat(现在改为委托给前者,并且会先切到项目目录)和 npm start 的 prestart
都会跑它。Docker 运行镜像也补上这个文件,否则容器里执行 npm start 会因为找不到
脚本而失败。

Node 版本要求改为按依赖的真实下限判断(@honeybbq/teamspeak-client 要 >=20.19、
@sansenjian/qq-music-api 要 >=20.17/22.9,21 和 23 被 better-sqlite3 与 vitest
排除),package.json 补上对应的 engines;比 20/22 LTS 更新的大版本不阻止,只提
示可能要源码编译。README 相应更新,并补一条 NODE_MODULE_VERSION 的常见问题。

注意:批处理里新增的行全部保持纯 ASCII —— cmd.exe 在括号块里遇到多字节 UTF-8
会算错文件偏移,开始吃掉后续行的 echo 前缀,中文提示一律交给 Node 脚本输出。

Closes #140

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:09:15 +08:00
XuVIIJay f87aaaf8c3 feat(scripts): optimize setup/start scripts for China network 2026-05-15 20:42:51 +08:00