dsh-disk-cleaner 1.6.6 → 1.6.7

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <!-- badges -->
6
6
  | | |
7
7
  | --- | --- |
8
- | **version** | `1.6.6` |
8
+ | **version** | `1.6.7` |
9
9
  | **DSH** | `0.2.1-rc.5`(开发与实测版本;用到 `plugins.bundle.config`、`settings.section`) |
10
10
  | **license** | [MIT](LICENSE) |
11
11
  | **Node** | `>= 22`(仅运行冒烟测试需要) |
@@ -166,16 +166,16 @@ These are enforced in `lib/catalog.js` and must not be relaxed:
166
166
 
167
167
  Listed in the curated [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) catalog, which powers **dshmarket → Discover** ([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970), merged): search **dsh-disk-cleaner** there and install it in one click. The direct installs below do the same thing by hand.
168
168
 
169
- Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.6`:
169
+ Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.7`:
170
170
 
171
171
  ```bash
172
172
  npm i dsh-disk-cleaner
173
173
  ```
174
174
 
175
- A prebuilt tarball is attached to the [v1.6.6 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.6); installing from it skips DSH's build-approval step:
175
+ A prebuilt tarball is attached to the [v1.6.7 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.7); installing from it skips DSH's build-approval step:
176
176
 
177
177
  ```bash
178
- dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.6/dsh-disk-cleaner-1.6.6.tgz
178
+ dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.7/dsh-disk-cleaner-1.6.7.tgz
179
179
  ```
180
180
 
181
181
  Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
@@ -183,7 +183,7 @@ Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
183
183
  ```json
184
184
  {
185
185
  "dependencies": {
186
- "dsh-disk-cleaner": "^1.6.6"
186
+ "dsh-disk-cleaner": "^1.6.7"
187
187
  },
188
188
  "dsh": {
189
189
  "profile": {
@@ -258,10 +258,11 @@ What the smoke test deliberately does **not** cover: anything that walks a real
258
258
  ```
259
259
  ├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
260
260
  ├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
261
- ├─ lib/index.js (482) Host: HTTP route + disk_cleaner tool, same run() for both; reveal
262
- ├─ lib/catalog.js (1222) The 32 targets, measurePath / purgePath / resolveTargets / drives
261
+ ├─ lib/index.js (506) Host: HTTP route + disk_cleaner tool, same run() for both; reveal
262
+ ├─ lib/catalog.js (1242) The 32 targets, measurePath / purgePath / resolveTargets / drives
263
263
  ├─ lib/audit.js (365) Read-only assessment, 10-minute cache, areaPaths, exposes no deletion
264
264
  ├─ lib/plan.js (150) Pure: measurements → ordered steps
265
+ ├─ lib/history.js (71) Append-only run log in $DSH_HOME/dsh-disk-cleaner; never throws
265
266
  ├─ lib/client.js (1808) Client half: slots, panel, preferences, zh/en dictionaries, CSS
266
267
  ├─ locale/{en,zh}.json Display metadata (meta.title / meta.description)
267
268
  ├─ icon.svg Plugin icon
@@ -274,7 +275,8 @@ Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode
274
275
 
275
276
  ### What's new
276
277
 
277
- - **1.6.6** — The results the plugin already computed are now shown. A **Dry run** button next to *Clean* asks the exact same question without `confirm`, so you see what a delete would free before anything goes; the result banner then survives the rescan that follows it (it used to be erased a line later). Plan steps name targets in words instead of printing `npm-cache, chrome-cache, pip-cache`, an empty plan says "nothing worth doing" instead of rendering a hole, and route errors arrive as a stable `code` the panel translates, so a Chinese user never reads `origin rejected` or `unknown target: x`. Every real clean now appends one line to `$DSH_HOME/dsh-disk-cleaner/history.jsonl`, and a **Clean history** panel reads it back with a *Copy as CSV* button. Under the hood: scan walks four targets at a time instead of all 32 at once, `SystemRoot` decides where the Windows targets live instead of a hardcoded `C:`, a request without an `Origin` header is refused, and a refused directory removal is counted separately from a legitimately full one.
278
+ - **1.6.7** — Fixes a regression 1.6.6 shipped: refusing every request without an `Origin` header locked the plugin's own panel out of its API ("请求来源不被接受"), because the desktop window posts from `dsh-app://app`, not from `http://127.0.0.1:<port>`. The check now accepts the desktop scheme and a header the desktop forwarder stripped, but only when the request already arrived on a loopback address; a plain web page still cannot reach a loopback port without sending an `Origin`, so the door stays shut. No other behaviour changed.
279
+ - **1.6.6** — The results the plugin already computed are now shown. A **Dry run** button next to *Clean* asks the exact same question without `confirm`, so you see what a delete would free before anything goes; the result banner then survives the rescan that follows it (it used to be erased a line later). Plan steps name targets in words instead of printing `npm-cache, chrome-cache, pip-cache`, an empty plan says "nothing worth doing" instead of rendering a hole, and route errors arrive as a stable `code` the panel translates, so a Chinese user never reads `origin rejected` or `unknown target: x`. Every real clean now appends one line to `$DSH_HOME/dsh-disk-cleaner/history.jsonl`, and a **Clean history** panel reads it back with a *Copy as CSV* button. Under the hood: scan walks four targets at a time instead of all 32 at once, `SystemRoot` decides where the Windows targets live instead of a hardcoded `C:`, a request without an `Origin` header is refused unless it arrived on loopback (see 1.6.7), and a refused directory removal is counted separately from a legitimately full one.
278
280
  - **1.6.5** — Documentation only: the install section now points at the Release it names (the prebuilt-tarball link is labelled v1.6.5 and resolves to the v1.6.5 tag), and the catalog paragraph says the awesome-dsh-plugin entry is merged rather than pending review. No code change.
279
281
  - **1.6.4** — The panel now paints itself with [dsh-design-baseline](https://github.com/stallenlu/dsh-design-baseline): the stylesheet is inlined into the client bundle, the panel root carries `dsb-root`, and every colour resolves through a `--dsb-*` token instead of a host `--dsw-alias-*` name. That means it follows the host's light and dark themes (including `color-scheme`, so native `<select>` menus and scrollbars match), and colour is spent only on data that has state — the primary button and the active chip are now inverted neutrals rather than brand blue. No behaviour changes.
280
282
  - **1.6.3** — Safety and honesty rather than new features. The Recycle Bin is no longer filed under `safe`: emptying it cannot be undone, so it is never preselected, never picked by "select all", and carries its own `irreversible` note instead of the generic "rebuilt; next start is slower". The confirm dialog now names each target and the total instead of asking a generic "delete these?". A target that names no environment root is bounded by the volume it sits on rather than skipping the boundary check. `measure` and `purge` now count entries in the same unit, and a walk that hits the 2,000,000-entry cap says so (`truncated`) instead of claiming exact totals. A directory removal refused for a reason other than "not empty" is counted as `blocked` instead of being swallowed, and a clean that left files behind reports a warning rather than a green success. The orphan target is named for what it actually measures (the QQGuild folder).
@@ -438,16 +440,16 @@ Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode
438
440
 
439
441
  已收录进精选目录 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin),它是 **dshmarket → 发现** 的数据源([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970),已合并):在 dshmarket 里搜索 **dsh-disk-cleaner** 即可一键安装。下面的直接安装方式就是手动做同一件事。
440
442
 
441
- 已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.6`。
443
+ 已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.7`。
442
444
 
443
445
  ```bash
444
446
  npm i dsh-disk-cleaner
445
447
  ```
446
448
 
447
- [v1.6.6 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.6) 还附带一个预构建 tarball,从它安装可跳过 DSH 的构建授权步骤:
449
+ [v1.6.7 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.7) 还附带一个预构建 tarball,从它安装可跳过 DSH 的构建授权步骤:
448
450
 
449
451
  ```bash
450
- dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.6/dsh-disk-cleaner-1.6.6.tgz
452
+ dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.7/dsh-disk-cleaner-1.6.7.tgz
451
453
  ```
452
454
 
453
455
  然后在 profile 里启用 —— 在 `$DSH_PROFILE_DIR/package.json`:
@@ -455,7 +457,7 @@ dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner
455
457
  ```json
456
458
  {
457
459
  "dependencies": {
458
- "dsh-disk-cleaner": "^1.6.6"
460
+ "dsh-disk-cleaner": "^1.6.7"
459
461
  },
460
462
  "dsh": {
461
463
  "profile": {
@@ -530,10 +532,11 @@ node --check lib/client.js # 以及 lib/index.js、lib/catalog.js、li
530
532
  ```
531
533
  ├─ package.json dsh.bundle.patch + dsh.client { platform: "web" }
532
534
  ├─ cordis.patch.yml - insert: id: dsh-disk-cleaner
533
- ├─ lib/index.js (482) Host:HTTP 路由 + disk_cleaner 工具,共用同一个 run(),含 reveal
534
- ├─ lib/catalog.js (1222) 32 个目标,measurePath / purgePath / resolveTargets / drives
535
+ ├─ lib/index.js (506) Host:HTTP 路由 + disk_cleaner 工具,共用同一个 run(),含 reveal
536
+ ├─ lib/catalog.js (1242) 32 个目标,measurePath / purgePath / resolveTargets / drives
535
537
  ├─ lib/audit.js (365) 只读评估,10 分钟缓存,带 areaPaths,不暴露任何删除
536
538
  ├─ lib/plan.js (150) 纯函数:测量结果 → 有序步骤
539
+ ├─ lib/history.js (71) 只追加的运行日志,落在 $DSH_HOME/dsh-disk-cleaner;永不抛错
537
540
  ├─ lib/client.js (1808) Client 半面:slot、面板、偏好、zh/en 字典、CSS
538
541
  ├─ locale/{en,zh}.json 展示元数据(meta.title / meta.description)
539
542
  ├─ icon.svg 插件图标
@@ -546,7 +549,8 @@ Client 半面注册的 slot:`sidebar.panellist`(order 50,仅设置页模
546
549
 
547
550
  ### 更新记录
548
551
 
549
- - **1.6.6** — 把插件本来就算出来的结果真正显示出来。*清理* 旁边多了 **预演** 按钮:发同一个请求但不带 `confirm`,删之前就能看到会释放多少;清理结果横幅不再被紧随其后的重新扫描抹掉(以前下一行就被清空)。计划步骤改用目标名称,不再直出 `npm-cache, chrome-cache, pip-cache`;没有可做的事时说明「现在没有值得动手的地方」,而不是留一片空白;接口错误改带稳定 `code`,由面板翻成中文,不会再出现 `origin rejected` 或 `unknown target: x`。每次真正清理都会往 `$DSH_HOME/dsh-disk-cleaner/history.jsonl` 追加一行,新增的 **清理记录** 面板可读回并按 *复制为 CSV* 导出。底层:扫描从「32 个目标同时跑」改为每次 4 个,Windows 目标路径改由 `SystemRoot` 决定而不再硬编码 `C:`,缺少 `Origin` 头的请求会被拒绝,目录删除被拒与非空目录被跳过分别计数。
552
+ - **1.6.7** — 修掉 1.6.6 带进来的一个回归:把「没有 Origin 头的请求」一律拒绝,连插件自己的面板都进不了接口(界面报「请求来源不被接受」)——桌面窗口发请求时 Origin 是 `dsh-app://app`,不是 `http://127.0.0.1:<端口>`。现在的判定接受桌面协议,也接受被桌面转发层剥掉的头,但前提是请求本身已经到达 loopback 地址;普通网页不发 Origin 就到不了 loopback 端口,所以门仍然是关的。其余行为没变。
553
+ - **1.6.6** — 把插件本来就算出来的结果真正显示出来。*清理* 旁边多了 **预演** 按钮:发同一个请求但不带 `confirm`,删之前就能看到会释放多少;清理结果横幅不再被紧随其后的重新扫描抹掉(以前下一行就被清空)。计划步骤改用目标名称,不再直出 `npm-cache, chrome-cache, pip-cache`;没有可做的事时说明「现在没有值得动手的地方」,而不是留一片空白;接口错误改带稳定 `code`,由面板翻成中文,不会再出现 `origin rejected` 或 `unknown target: x`。每次真正清理都会往 `$DSH_HOME/dsh-disk-cleaner/history.jsonl` 追加一行,新增的 **清理记录** 面板可读回并按 *复制为 CSV* 导出。底层:扫描从「32 个目标同时跑」改为每次 4 个,Windows 目标路径改由 `SystemRoot` 决定而不再硬编码 `C:`,缺少 `Origin` 头的请求会被拒绝(这项判定在 1.6.7 收紧到只放行 loopback,见下),目录删除被拒与非空目录被跳过分别计数。
550
554
  - **1.6.5** — 仅文档修正:安装段现在指向它所写的那个 Release(预构建 tarball 的链接标为 v1.6.5,且指向 v1.6.5 的 tag);目录段落也从「审核中」改为「已合并」。代码无变化。
551
555
  - **1.6.4** — 面板改用 [dsh-design-baseline](https://github.com/stallenlu/dsh-design-baseline) 上色:样式表内联进 client bundle,根节点带 `dsb-root`,所有颜色都走 `--dsb-*` 令牌而不是宿主的 `--dsw-alias-*` 名字。因此它跟随宿主的明暗主题(含 `color-scheme`,原生 `<select>` 弹层与滚动条也跟着变),并且颜色只花在有状态的数据上——主按钮与选中项改成中性反色,不再是品牌蓝。行为没有变化。
552
556
  - **1.6.3** — 改的是安全与诚实,不是新功能。回收站不再归入「安全」:清空不可恢复,所以它永不被预选、不被「全选」勾中,并单独带 `irreversible` 注意码,而不是笼统的「重建,下次启动慢一些」。确认框现在逐条列出目标名与合计字节,不再只问一句「删除选中缓存目录里的文件?」。没有声明根目录的目标,改为按所在卷做边界校验,而不是直接跳过检查。`measure` 与 `purge` 现在用同一单位计数,走查触到 200 万条上限时会如实标 `truncated`,不再声称精确总量。目录删除失败若原因不是「非空」会计入 `blocked`(此前被静默吞掉),清理后仍有残留的目标会报警告而不是绿色成功。orphan 目标按它实际测量的东西重新命名(QQ 频道目录)。
package/lib/index.js CHANGED
@@ -41,17 +41,41 @@ const sendJson = (res, status, value) => {
41
41
  res.end(body);
42
42
  };
43
43
 
44
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
45
+
46
+ /**
47
+ * The desktop app renders the panel under its own privileged protocol (`dsh-app://app`), so a
48
+ * fetch from inside that window carries an Origin no `http://` comparison can ever match. A web
49
+ * page cannot forge this: Origin is set by the user agent, and only the harness serves that
50
+ * scheme. Everything is still gated on a loopback Host below.
51
+ */
52
+ const APP_SCHEME = 'dsh-app://';
53
+
54
+ /** `host` is `name` or `name:port`; the port is not ours to compare. */
55
+ function hostName(host) {
56
+ if (host.startsWith('[')) {
57
+ const end = host.indexOf(']');
58
+ return end === -1 ? host : host.slice(0, end + 1);
59
+ }
60
+ const colon = host.lastIndexOf(':');
61
+ return colon === -1 ? host : host.slice(0, colon);
62
+ }
63
+
44
64
  /**
45
- * A same-origin page is the only caller that can reach a state-changing route.
65
+ * Only the panel may drive a state-changing route.
46
66
  *
47
- * An absent Origin used to pass, which let any local process drive clean/set-key by simply
48
- * not sending the header. Browsers always send it on a cross-context POST, so requiring it
49
- * costs the panel nothing and shuts the door; a missing Host is treated the same way.
67
+ * A missing Origin is allowed solely when the request already arrived on a loopback address: the
68
+ * desktop forwarder can strip the header, and refusing that locks the panel out of its own API.
69
+ * A web page cannot reach a loopback port without sending an Origin, so this does not open the
70
+ * door it looks like it opens. Any other absent-Origin caller must first reach loopback anyway.
50
71
  */
51
72
  const sameOrigin = (req) => {
52
73
  const origin = req.headers.origin;
53
74
  const host = req.headers.host;
54
- if (typeof origin !== 'string' || typeof host !== 'string') return false;
75
+ if (typeof host !== 'string' || host === '') return false;
76
+ const onLoopback = LOOPBACK_HOSTS.has(hostName(host));
77
+ if (typeof origin !== 'string' || origin === 'null') return onLoopback;
78
+ if (origin.startsWith(APP_SCHEME)) return onLoopback;
55
79
  return origin === `http://${host}` || origin === `https://${host}`;
56
80
  };
57
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-disk-cleaner",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "type": "module",
5
5
  "description": "Disk Cleaner for DSH: scan known-safe cache locations, see what they occupy, and reclaim the space from a panel or from the agent.",
6
6
  "keywords": [
package/smoke.mjs CHANGED
@@ -413,7 +413,7 @@ check(
413
413
  source.includes('QQ') ,
414
414
  'it only ever measures the QQGuild folder',
415
415
  );
416
- check('the version badge and package agree', pkg.version === '1.6.6');
416
+ check('the version badge and package agree', pkg.version === '1.6.7');
417
417
 
418
418
  check(
419
419
  'a dry run is one click away and never sends confirm',
@@ -509,9 +509,13 @@ check(
509
509
  );
510
510
 
511
511
  check(
512
- 'a request without an Origin is refused',
513
- /if \(typeof origin !== 'string' \|\| typeof host !== 'string'\) return false;/.test(index),
514
- 'any local process could otherwise drive a destructive route',
512
+ 'a request without an Origin is refused unless it arrived on loopback',
513
+ /const LOOPBACK_HOSTS = new Set\(/.test(index) &&
514
+ /const APP_SCHEME = 'dsh-app:\/\/';/.test(index) &&
515
+ /if \(typeof origin !== 'string' \|\| origin === 'null'\) return onLoopback;/.test(index) &&
516
+ /if \(origin\.startsWith\(APP_SCHEME\)\) return onLoopback;/.test(index) &&
517
+ !/if \(typeof origin !== 'string' \|\| typeof host !== 'string'\) return false;/.test(index),
518
+ 'the desktop shell posts from dsh-app://, and a stripped Origin can still be the panel itself',
515
519
  );
516
520
 
517
521
  check(