@xiaoyuyu6420/dsh-backup 0.12.0 → 0.12.2

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/index.js CHANGED
@@ -633,7 +633,10 @@ export function apply(ctx, pluginConfig) {
633
633
  const toFwd = (p) => (p.includes('\\') ? p.split('\\').join('/') : p);
634
634
  const homeRaw = env?.get('HOME')?.value || env?.get('USERPROFILE')?.value;
635
635
  const home = homeRaw ? toFwd(homeRaw.replace(/\/+$/, '')) : undefined;
636
- const dshHome = env?.get('DSH_HOME')?.value || (home ? `${home}/.dsh` : undefined);
636
+ // DSH_HOME 与 HOME 一样去尾斜杠(#88):'D:/dsh_data/' 会让 base 变空串、
637
+ // parent 退化为源目录本身,tar 位置参数成空串,产出 0 字节坏归档。
638
+ const dshHomeRaw = env?.get('DSH_HOME')?.value || (home ? `${home}/.dsh` : undefined);
639
+ const dshHome = dshHomeRaw ? toFwd(dshHomeRaw).replace(/\/+$/, '') || '/' : undefined;
637
640
  if (!home || !dshHome) throw new Error('无法解析 HOME/USERPROFILE 或 DSH_HOME(launchEnvironment 缺失)');
638
641
  const cfg = resolvedConfig();
639
642
  const raw = typeof cfg.destination === 'string' && cfg.destination.trim()
@@ -643,6 +646,12 @@ export function apply(ctx, pluginConfig) {
643
646
  return { home, dshHome: toFwd(dshHome), root };
644
647
  }
645
648
 
649
+ // #88:parent 由字符串截取求得,DSH_HOME 直接挂在盘符根时退化为 'D:'——
650
+ // bsdtar/GNU tar 都把裸盘符解析为"该盘符当前目录",-C D: 必然失败并留下
651
+ // 空壳归档。tar 的 -C 一律传补过分隔符的值;fs 侧继续用原 parent
652
+ // ('D:' + '/' + name 仍是合法路径,renameBeside 等不受影响)。
653
+ const parentTar = (p) => (p.endsWith('/') ? p : `${p}/`);
654
+
646
655
  function defaultKeep() {
647
656
  const cfg = resolvedConfig();
648
657
  const k = Number(cfg.keep);
@@ -1008,7 +1017,14 @@ export function apply(ctx, pluginConfig) {
1008
1017
  const redacted = skipVault ? await existingSensitive(dshHome) : await refreshVault(dshHome, home, signal);
1009
1018
  const tar = await ctx.subprocess.resolveExecutable('tar');
1010
1019
  const redactFlags = isTyped ? [] : redactExcludeFlags(base);
1011
- await spawnRun([tar, '--exclude=*node_modules*', '--exclude=.system', ...quarantineFlags, ...redactFlags, ...extraExcludes(), '-czf', name, '-C', parent, ...(isTyped ? typePaths : [base])], root, signal);
1020
+ try {
1021
+ await spawnRun([tar, '--exclude=*node_modules*', '--exclude=.system', ...quarantineFlags, ...redactFlags, ...extraExcludes(), '-czf', name, '-C', parentTar(parent), ...(isTyped ? typePaths : [base])], root, signal);
1022
+ } catch (err) {
1023
+ // tar 失败前已创建 -f 目标——空壳归档留在备份目录会被面板当正常备份
1024
+ // 列出来(#88:29 字节静默空包)。删掉空壳再抛,让失败可见。
1025
+ await rm(out, { force: true }).catch(() => {});
1026
+ throw err;
1027
+ }
1012
1028
 
1013
1029
  const shaText = await sha256File(out, home, signal);
1014
1030
  await writeOwned(`${out}.sha256`, `${shaText} ${out}\n`);
@@ -1683,7 +1699,7 @@ export function apply(ctx, pluginConfig) {
1683
1699
  }
1684
1700
  try {
1685
1701
  const tar = await ctx.subprocess.resolveExecutable('tar');
1686
- await spawnRun([tar, '-xzf', picked.name, '-C', parent, ...fileTargets], root, signal);
1702
+ await spawnRun([tar, '-xzf', picked.name, '-C', parentTar(parent), ...fileTargets], root, signal);
1687
1703
  } catch (err) {
1688
1704
  // 对齐 #28 / doctorRepair 回滚:从留档还原每个被覆盖项;连还原都失败
1689
1705
  // 时把留档路径交代清楚,绝不静默停在半截状态。
@@ -1751,7 +1767,7 @@ export function apply(ctx, pluginConfig) {
1751
1767
  }
1752
1768
  try {
1753
1769
  const tar = await ctx.subprocess.resolveExecutable('tar');
1754
- await spawnRun([tar, '-xzf', picked.name, '-C', parent], root, signal);
1770
+ await spawnRun([tar, '-xzf', picked.name, '-C', parentTar(parent)], root, signal);
1755
1771
  } catch (err) {
1756
1772
  // 挪旁之后解压失败会让 .dsh 处于空位——这是恢复流程里用户最容易慌
1757
1773
  // 的时刻。尽力自动回滚:清掉半截解压产物、把原数据改回原名;连回滚
@@ -2420,7 +2436,7 @@ export function apply(ctx, pluginConfig) {
2420
2436
  // 交代清楚,绝不静默停在最糟的中间态。
2421
2437
  try {
2422
2438
  const tar = await ctx.subprocess.resolveExecutable('tar');
2423
- await spawnRun([tar, '-xzf', picked.name, '-C', parent, ...targets.map((t) => t.entry)], root, signal);
2439
+ await spawnRun([tar, '-xzf', picked.name, '-C', parentTar(parent), ...targets.map((t) => t.entry)], root, signal);
2424
2440
  } catch (err) {
2425
2441
  const restored = new Set();
2426
2442
  for (let i = 0; i < targets.length; i++) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xiaoyuyu6420/dsh-backup",
3
- "version": "0.12.0",
4
- "description": "Backup, restore, download and GitHub-sync DeepSeek Harness user data (~/.dsh): /backup, scheduled auto-backup that survives restarts, sha256 checksums, integrity verify, rotation, credential redaction with a local vault (plaintext never leaves the machine), cross-machine restore preflight with github pull, and a visual Settings panel. Cross-platform (macOS/Linux/Windows). 一键备份与恢复 DSH 数据:定时自动备份、完整性校验、凭据默认脱敏(明文只存本机 vault)、跨机恢复预检与 github pull 拉取,附 Settings 可视面板。",
3
+ "version": "0.12.2",
4
+ "description": "一键备份与恢复 DeepSeek Harness(dsh)的全部用户数据(~/.dsh):定时自动备份、升级快照、会话日志体检修复、迁移预检、宿主起不来也能用的救援通道、凭据脱敏、GitHub 同步跨机迁移,附可视化面板。 One command to back up & restore all of ~/.dsh: /backup, scheduled auto-backup, upgrade snapshots, session doctor & repair, migrate precheck, out-of-process rescue console, credential redaction, GitHub sync.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "main": "lib/index.js",
@@ -16,7 +16,7 @@
16
16
  "rescue",
17
17
  "cordis.patch.yml",
18
18
  "README.md",
19
- "README.zh.md",
19
+ "README.en.md",
20
20
  "LICENSE"
21
21
  ],
22
22
  "bin": {
package/rescue/rescue.mjs CHANGED
@@ -84,6 +84,10 @@ function run(argv, cwd) {
84
84
  return r.stdout.toString('utf8');
85
85
  }
86
86
 
87
+ // #88:同 lib/index.js——parent 由字符串截取求得,DSH_HOME 挂盘符根时退化
88
+ // 为 'D:',裸盘符作 tar -C 会失败。-C 一律传补过分隔符的值。
89
+ const parentTar = (p) => (p.endsWith('/') ? p : `${p}/`);
90
+
87
91
  async function sha256File(absPath) {
88
92
  const info = await fs.stat(absPath);
89
93
  if (info.size > HASH_MAX_BYTES) throw new Error(`文件 ${Math.floor(info.size / 1048576)}MB 超过哈希上限`);
@@ -420,7 +424,7 @@ async function doctorRepair(root, dshHome, selector) {
420
424
  kept.push(keep);
421
425
  }
422
426
  try {
423
- run(['tar', '-xzf', picked.name, '-C', parent, ...targets.map((t) => t.entry)], root);
427
+ run(['tar', '-xzf', picked.name, '-C', parentTar(parent), ...targets.map((t) => t.entry)], root);
424
428
  } catch (err) {
425
429
  const reason = err && err.message ? err.message : String(err);
426
430
  const restored = new Set();
@@ -493,7 +497,13 @@ async function restoreArchive(root, selector, apply) {
493
497
  const sensitiveNow = new Set(SENSITIVE_DEFAULTS);
494
498
  if (Array.isArray(redactedFiles)) for (const rel of redactedFiles) sensitiveNow.add(rel);
495
499
  const redactFlags = [...sensitiveNow].flatMap((rel) => [`--exclude=${base}/${rel}`, `--exclude=*/${rel}`]);
496
- run(['tar', '--exclude=*node_modules*', '--exclude=.system', ...redactFlags, '-czf', snapshotName, '-C', parent, base], root);
500
+ try {
501
+ run(['tar', '--exclude=*node_modules*', '--exclude=.system', ...redactFlags, '-czf', snapshotName, '-C', parentTar(parent), base], root);
502
+ } catch (err) {
503
+ // tar 失败留下的空壳快照不能留在恢复点列表里冒充可用备份(#88)
504
+ await fs.rm(`${root}/${snapshotName}`, { force: true }).catch(() => {});
505
+ throw err;
506
+ }
497
507
  // 清掉上一次的 pre-restore 快照(只保留本次),防累积
498
508
  for (const name of await fs.readdir(root)) {
499
509
  if (name.startsWith('dsh-pre-restore-') && name !== snapshotName) {
@@ -510,7 +520,7 @@ async function restoreArchive(root, selector, apply) {
510
520
  aside = `${parent}/${asideName}`;
511
521
  }
512
522
  try {
513
- run(['tar', '-xzf', picked.name, '-C', parent], root);
523
+ run(['tar', '-xzf', picked.name, '-C', parentTar(parent)], root);
514
524
  } catch (err) {
515
525
  const reason = err && err.message ? err.message : String(err);
516
526
  let rolledBack = false;
package/README.zh.md DELETED
@@ -1,160 +0,0 @@
1
- # dsh-backup
2
-
3
- [![dsh-plugin](https://img.shields.io/badge/ecosystem-dsh--plugin-8b5cf6)](https://github.com/topics/dsh-plugin)
4
- [![npm](https://img.shields.io/npm/v/@xiaoyuyu6420/dsh-backup)](https://www.npmjs.com/package/@xiaoyuyu6420/dsh-backup)
5
- [![Downloads](https://img.shields.io/npm/dw/@xiaoyuyu6420/dsh-backup)](https://www.npmjs.com/package/@xiaoyuyu6420/dsh-backup)
6
- [![Publish to npm](https://github.com/xiaoyuyu6420/dsh-backup/actions/workflows/publish.yml/badge.svg)](https://github.com/xiaoyuyu6420/dsh-backup/actions/workflows/publish.yml)
7
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
- [![Listed on DSH Directory](https://dsh.directory/badges/listed.svg)](https://dsh.directory/plugins/xiaoyuyu6420/dsh-backup)
9
-
10
- [English](README.md) | 简体中文
11
-
12
- **你所有的 DSH 工作数据都在一个目录里:`~/.dsh`。一次升级失败、一次误删、一次换电脑——没有备份,会话、设置、技能全没了。dsh-backup 用一条命令把它们找回来。**
13
-
14
- ```sh
15
- dsh plugin --profile web add @xiaoyuyu6420/dsh-backup # 安装
16
- # 重启 dsh web,然后输入:
17
- /backup # → 一份带校验的备份落在 ~/Desktop/dsh-backups/
18
- ```
19
-
20
- v0.9.0 全新安装的真实输出:
21
-
22
- ```text
23
- 备份完成: dsh-20260826-195150036.tar.gz
24
- sha256: 8f9ae6322ef782d21554981cf4547220d5bb3e64d7964a883317415ad54e3cbb
25
- 轮换删除 0 份(保留 7 份)
26
- ```
27
-
28
- 不想敲命令也行:`dsh web` → 设置 → 插件 → 备份 里有可视化面板——列备份、校验、恢复、删除、改设置,全都立即生效不用重启。
29
-
30
- ![备份面板](docs/assets/panel-backups.png)
31
-
32
- ## 它替你挡掉哪些事
33
-
34
- | 你担心的 | dsh-backup 做的事 |
35
- |---|---|
36
- | 「升级把环境搞坏了」 | 宿主版本一变就自动拍 `dsh-pre-upgrade-` 快照——放心试新版,坏了随时回滚 |
37
- | 「我误删 / 改坏了东西」 | `/backup restore latest --dry-run` 先预览要恢复什么再动手;恢复失败自动回滚并给出结果回执 |
38
- | 「DSH 根本起不来了」 | 每次备份都会往备份目录放一个零依赖的**救援控制台**(`dsh-rescue` / `rescue.mjs`,或双击「点我恢复」)——不依赖 DSH 的网页版恢复界面 |
39
- | 「API Key 会被传到云上吗」 | 凭据默认脱敏,不进备份包;明文只存本机 vault,永不离开这台机器 |
40
- | 「会话日志损坏了」 | `/backup doctor` 体检并从备份定点修复;损坏文件先隔离再入档,防止轮换把好副本也带走 |
41
- | 「换新电脑了」 | GitHub 同步:`/backup github pull` 拉回云端备份,`restore --sync-deps` 顺手重装插件 |
42
- | 「备份悄悄坏了没人知道」 | 每份归档带 sha256,`/backup verify all` 随时体检;每日/每周分级保留,留得住有用的历史 |
43
- | 「我会忘记备份」 | `/backup auto 12`——每 12 小时自动跑,重启不中断,旧副本自动轮换(默认保留 7 份) |
44
-
45
- ![备份设置](docs/assets/panel-settings.png)
46
-
47
- ## 安装
48
-
49
- 要求:macOS / Linux / Windows 10+(自带 `tar`),DSH `0.1.1-rc.2`+(0.1.1 / 0.1.2 / 0.1.5 列车均已实测,最新验证至 `0.1.5-rc.2`)。
50
-
51
- ```sh
52
- dsh plugin --profile web add @xiaoyuyu6420/dsh-backup
53
- # 或者直接从 GitHub 安装:
54
- dsh plugin --profile web add github:xiaoyuyu6420/dsh-backup
55
- ```
56
-
57
- 装完重启 `dsh web` 后插件才生效。
58
-
59
- > 安装时可能刷出 `✕ missing peer @deepseek-ai/...` 警告,属预期现象:这些
60
- > peer 包由 DSH 宿主在运行时提供,不装在 profile 目录里。只要命令以
61
- > `Done` 结尾就是装成功了。
62
-
63
- ## 快速上手
64
-
65
- 1. 按上面装好插件,重启 `dsh web`
66
- 2. 输入 `/backup`
67
- 3. 搞定 —— 备份出现在 `~/Desktop/dsh-backups/`,文件名带时间戳,旁边一份 `.sha256`
68
-
69
- 想定时自动跑?`/backup auto 12`(每 12 小时一次;`off` 关闭,`status` 看状态)。
70
-
71
- ## 命令速查
72
-
73
- | 场景 | 命令 |
74
- |---|---|
75
- | 立即备份 | `/backup` |
76
- | 分类型备份(只备份选中类型) | `/backup --types skills,sessions`(可选:`credentials`·`mcp`·`skills`·`sessions`·`settings`·`profiles`;`--only` 同义) |
77
- | 定时备份(重启不中断) | `/backup auto 12` · `off` · `status` |
78
- | 恢复(先预览) | `/backup restore latest --dry-run` |
79
- | 正式恢复 | `/backup restore latest` |
80
- | 分类型恢复(merge,不动其他类型) | `/backup restore <归档> --types skills` |
81
- | 列出备份 | `/backup list` |
82
- | 校验完整性 | `/backup verify [前缀\|all]` |
83
- | 会话日志体检/修复 | `/backup doctor` · `--repair [前缀\|latest]` |
84
- | **DSH 起不来时自救** | 双击备份目录里的「点我恢复」,或 `dsh-rescue` / `node rescue.mjs` |
85
- | 删除 / 保留策略 | `/backup delete <前缀\|latest>` · `/backup --keep N`(默认 7) |
86
-
87
- ## 分类型备份
88
-
89
- 只想备份/恢复某几类数据?`--types`(或 `--only`)按内容类型子集操作,可选类型:`credentials`(凭据/api key)、`mcp`(MCP 配置)、`skills`(技能)、`sessions`(会话)、`settings`(设置)、`profiles`(插件 profiles)。
90
-
91
- - **备份**:`/backup --types skills,sessions` 生成 `dsh-t-` 前缀的子集归档,与全量备份分开轮换、互不挤占保留份数
92
- - **恢复**:`/backup restore <归档名> --types skills` 只把 skills 合并回现有 `~/.dsh`(先 `--dry-run` 预览;覆盖的现有文件自动留档 `.pre-merge-*`),其他类型完全不碰
93
- - **凭据类型特殊**:`--types credentials` 会把 api key **明文**打进归档(默认全量备份是脱敏的)——这类归档绝不进 GitHub 同步,仅建议本机保存或手动拷到新机
94
- - **安全护栏**:分类型归档不带 `--types` 直接整包恢复会被拒绝(防止误覆盖丢数据);rescue 救援通道同样不列、不整包恢复它们
95
- - 面板(Settings → 插件 → 备份)同样支持:备份按钮下方勾选类型;分类型归档在独立分区展示与恢复
96
-
97
- ## 换新电脑
98
-
99
- 前提:旧电脑配过 GitHub 同步([配置方法](docs/advanced.zh.md#github-同步可选))。
100
-
101
- 1. 新电脑装好插件,配置里填同一个 `githubRepo`
102
- 2. `/backup github pull` —— 拉回云端备份
103
- 3. `/backup restore latest --sync-deps` —— 恢复并重装插件依赖
104
- 4. 重启 `dsh`
105
-
106
- ## 常见问题
107
-
108
- **API Key / 密码会进备份包吗?**
109
- 不会。已知的凭据文件打包前会脱敏,明文留在本机 vault 里不离开这台机器;恢复时自动还原。
110
-
111
- **到底备份了什么?**
112
- `~/.dsh` 下的全部——会话、设置、技能、插件配置——减去你配置的排除模式和 `node_modules`。
113
-
114
- **我把 `~/.dsh` 搞坏了,`dsh` 都启动不了,还有救吗?**
115
- 有——这正是救援通道的用途。每次备份都会往备份目录写 `rescue.mjs` 和双击启动器(macOS `.command` / Windows `.bat` / Linux `.sh`)。它只用普通 Node 就能跑,不需要 DSH,起一个本地网页让你浏览和恢复备份。
116
-
117
- **支持 Windows 吗?**
118
- 支持——Windows 10+(用系统自带 `tar`),救援启动器是 `.bat` 文件。
119
-
120
- **备份默认放哪?**
121
- `~/Desktop/dsh-backups/`——在面板(设置 → 插件 → 备份)里随时可改,立即生效不用重启。
122
-
123
- ## 反馈
124
-
125
- 用过吗?哪里坏了、缺什么、喜欢什么,都欢迎说——反馈直接决定路线图:
126
-
127
- - 💬 [分享反馈(GitHub Discussions)](https://github.com/xiaoyuyu6420/dsh-backup/discussions)
128
- - 🐛 [报告问题](https://github.com/xiaoyuyu6420/dsh-backup/issues)
129
-
130
- ## 更新日志
131
-
132
- <details>
133
- <summary>最近的版本</summary>
134
-
135
- - **0.12.0** —— 迁移安全网第一弹,对准官方社区集中爆发的「升级后老会话打不开」(#6151/#6297/#6355):① `/backup migrate-check` 迁移预检——升级前静态扫描全部代会话日志(v0/v1/v2/v3),预测哪些会话升完会打不开、挂在哪条规则(descriptor v2、permission/preset origin、冻结清单外事件类型、自定义来源 kind、seq 漂移、文件名代际不一致……规则烤入自宿主 0.1.5-rc.2,摘要如实标注覆盖边界),并探测文件系统硬链接支持(exFAT 上宿主发布会失败的 #6358 场景);② 凭据哨兵——旧扁平布局的 `.credentials.yaml` 会被宿主原子替换成 version:1(不可逆),检测到即先自动存底 vault 再让宿主动它。③ GitHub token 面板直配——设置面板(或 `/backup github token <token>`)直接粘贴保存,存本机备份目录 `github.token`(0600,不进归档、不进 GitHub 同步),优先于环境变量,跨机恢复后重填。另:新增 `engines.dsh` 声明(插件市场的兼容卡片读取它)。
136
- - **0.11.3** —— 跟进 dsh `0.1.5` 列车:peerDependencies 追加 `^0.1.5-rc.1`(0.11.2 在 0.1.5 宿主上会报 peer 警告)。兼容实测:宿主 `0.1.5-rc.2` 真机 e2e 32/32;六个 node 侧 peer 包 rc.1↔rc.2 **逐字节相同**(tarball diff),client 包列车未动——本次升列车零适配面;跨列车原地升级 e2e(rc.1 宿主 + 0.11.2 → rc.2 宿主 + 0.11.3)14/14,设置与归档无损。顺带修了升级 e2e 脚本自身的一个坑:新版期望版本从写死改为从 tarball 动态推导,换版本对不再改脚本。
137
- - **0.11.2** —— doctor 行级 SessionHeader 校验对齐宿主 `isHeaderLine`(闭环 0.11.0 已知遗留):补 `version`/`createdAt`/`delegationDepth` 类型与非负安全整数三连(含 `-0` 拒绝)、`seedLength`/`origin`/`agentPreset` 可选分支、退役字段 `sandboxMode`/`approvalPolicy`——宿主拒载的 header 不再被误报健康。对照双列车宿主编译产物逐字段验证(rc.1 为严格超集),独立复核结论 ALIGN;测试套新增 11 种坏 header 负样本 + 修复往返。
138
- - **0.11.1** —— 适配 dsh `0.1.2-rc.1`:跟随 `@deepseek-ai/dsh-settings` 移除 `settingsNamespace`(改用普通 `dsh-backup` 命名空间注册——取值完全一致,原地更新后设置、备份与旧归档全部无损),支持 Web 强制鉴权(303 + HttpOnly cookie),peer 范围放宽为 `^0.1.1-rc.2 || ^0.1.2-rc.1`。双列车真宿主 e2e 各 32/32 通过,另做了 0.11.0 → 0.11.1 原地升级测试(设置保留、旧归档可恢复)。
139
- - **0.11.0** —— doctor 容器契约校验:首帧必须解出「恰好一行 header、单个换行结尾」(字节精确,对齐宿主读端)。单帧重写、首帧多余空行、缺行尾、skippable 帧现在都会判损坏(此前报健康但宿主拒载);救援台同步。来自 deepseek-harness 官方讨论区 #1047 的社区审计。
140
- - **0.10.0** —— 分类型备份:只备份需要的部分(`/backup --types skills,sessions`),按类型 merge 恢复(`/backup restore <归档> --types skills`);类型归档独立轮换不挤全量配额。含凭据的类型归档永不进 GitHub 同步,跨机护栏不变。
141
- - **0.9.1** —— 面板新增反馈入口;README 重做。六路 agent UX 深审后的加固:恢复确认按钮补主题色兜底重新可见、修复恢复快照时自毁目标快照的 bug、双击救援启动器自动探测 node、报错文案全面改为"人话 + 下一步"。
142
- - **0.9.0** —— `/backup doctor` 会话日志体检与定点修复;宿主起不来也能用的进程外救援通道(备份目录里的 dsh-rescue / 双击启动器);恢复失败自动回滚与结果导向回执;智能备份:宿主升级前自动快照、损坏会话先隔离再入档、每日/每周分级保留。
143
- - **0.8.0** —— 面板里直接改备份设置(目录、保留份数、排除模式),立即生效写入 settings.yaml 不用重启;两处同时改有冲突提示,不会静默覆盖。
144
- - **0.7.x** —— 凭据脱敏(本机 vault)、跨机恢复。
145
-
146
- </details>
147
-
148
- ## 更多
149
-
150
- 定时备份策略、敏感文件脱敏、GitHub 同步、恢复保护机制、配置参考、故障排查、开发说明——都在[进阶文档](docs/advanced.zh.md)。跨运行时兼容性说明:[compatibility.md](docs/compatibility.md)。
151
-
152
- ## 致谢
153
-
154
- - [@beastrobin](https://github.com/beastrobin) —— #1 中对保留方法名的根因分析,直接促成 v0.5.1 修复
155
- - [@mlosun](https://github.com/mlosun) —— #2 中详尽的复现与根因报告
156
- - [@Choi-Peng](https://github.com/Choi-Peng) —— #5 中协助把受影响用户指引到修复版本
157
-
158
- ## 许可证
159
-
160
- MIT