@yottameta/yotta-memory 0.22.4 → 0.22.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/CHANGELOG.md +18 -0
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/USER_GUIDE.md +2 -1
- package/assets/view.html +33 -6
- package/bin/install.js +12 -1
- package/bin/yotta-memory.js +414 -146
- package/install.sh +10 -1
- package/package.json +1 -1
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## v0.22.6 (2026-10-09)
|
|
2
|
+
|
|
3
|
+
- **并发写入加固**:索引更新改为跨进程互斥(`.index.lock`,mkdir 原子语义 + 陈旧锁自动回收)+ 原子落盘(临时文件 + rename);序号分配 / 记忆落盘 / 索引更新同处一个临界区,多智能体(多宿主、多 MCP 进程)同时写入更稳。
|
|
4
|
+
- **索引一致性维护**:以记忆文件为唯一真源,读取路径与用户平台在后台做轻量一致性校准(自动对齐,无需用户操作;不修改任何记忆文件)。
|
|
5
|
+
- **管理台搜索增强**:搜索支持按编号(文件名)与归属智能体查找(原支持标题 / 内容 / 标签)。
|
|
6
|
+
- **管理台 CLI 页补全配置键**:高级 / CLI 页新增 39 个配置键速查(`config set` 单一真源,含 `memory_home` / `backup_*` / `maintain_*` / `consolidate_*` / `scale_*` 等),可直接搜索与复制。
|
|
7
|
+
- **doctor**:新增索引一致性检查(条数 = 文件数)。
|
|
8
|
+
- **安装器洁净度**:顶层跳过并清理 `*.tgz` 与 `test-fixtures/`(与 npm 包白名单一致),安装副本不再携带源码侧残留。
|
|
9
|
+
- 新增 `test/index-consistency.test.js` 5 项(一致性检测与校准、幂等、6 进程并发 120 条零丢失、跨进程锁互斥、显示与实际对账 + 编号 / 归属搜索)。
|
|
10
|
+
- 新增 `test/password-stdin-bom.test.js`(Windows PowerShell 管道 BOM)与 `test/installer-hygiene.test.js`(薄片洁净度 + 残留清理)。
|
|
11
|
+
|
|
12
|
+
## v0.22.5 (2026-10-07)
|
|
13
|
+
|
|
14
|
+
- **修复 Electron 宿主(YottaCode shim)下开机自启拉起 GUI 而非服务**:宿主运行时为 Electron 时(`process.versions.electron`;`process.execPath` 指向 Electron 可执行文件),此前生成的任务 / 兜底 / 定时配置不带 `ELECTRON_RUN_AS_NODE=1`,开机拉起的是桌面程序本体、8787 无监听(新机首例实测)。现在自动检测并统一注入:Windows 主任务与每日备份任务(cmd /c + `set "ELECTRON_RUN_AS_NODE=1"`)、Windows Startup 兜底 .cmd(set 前缀)、Linux systemd 单元 / crontab @reboot / 备份定时器(`Environment=` / 行内前缀)、macOS 备份 LaunchAgent(EnvironmentVariables)。
|
|
15
|
+
- `lan status` 新增告警:任务或 Startup 兜底命令指向 Electron 可执行文件且缺少 `ELECTRON_RUN_AS_NODE` 时,提示重新 `lan enable` 修复。
|
|
16
|
+
- 非 Electron 宿主行为不变(纯 Node 下生成的命令与旧版一致)。
|
|
17
|
+
- `test/lan-windows-autostart.test.js` 新增 8 项(Electron / 非 Electron 双态:任务 XML / 兜底 cmd / 备份任务 / Linux 单元与 crontab / macOS plist / status 告警两例);全量 `npm test` 352/352。
|
|
18
|
+
|
|
1
19
|
## v0.22.4 (2026-10-07)
|
|
2
20
|
|
|
3
21
|
- **修复 Windows `lan enable` 开机自启三缺陷(实测复现)**:此前计划任务用 schtasks 默认交互式主体——① 在登录会话运行会弹控制台窗口;② `--onstart` 开机触发时无登录会话,任务起不来且不补跑(实测 LastTaskResult 267011);③ schtasks 默认带电池限制与 72 小时执行时限。本版改为 **S4U 主体 + 任务 XML 注册**(零依赖):后台静默运行不弹窗口,开机 / 登录触发均可用;默认去掉电池限制与 72h 时限(`ExecutionTimeLimit=PT0S` 不限时);`--onstart` 在 Windows 真正生效(BootTrigger)。任务注册成功时自动清理旧 Startup 兜底文件,避免双份自启。
|
package/README.zh-CN.md
CHANGED
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
|
|
175
175
|
- **记忆库即引擎**:`serve` 把记忆目录挂成 MCP 服务,目录随盘走;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体。
|
|
176
176
|
- **双模式可并存**:本地 `serve --stdio` 零进程(客户端按需拉起);局域网 streamable HTTP(默认 `0.0.0.0:8787`)+ 每智能体 token 鉴权。
|
|
177
|
-
- **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(v0.22.4 起 S4U 主体后台静默、不弹窗口;默认登录自启,`--onstart` 开机即启、无登录会话也可启动,计划任务注册需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002
|
|
177
|
+
- **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(v0.22.4 起 S4U 主体后台静默、不弹窗口;默认登录自启,`--onstart` 开机即启、无登录会话也可启动,计划任务注册需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);**v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,任务不再拉起 GUI**;Linux 优先 **systemd 用户单元**(`systemctl --user`,登录自启;`--onstart` 附加 `loginctl enable-linger` 开机即启),systemd 不可用时自动降级**用户 crontab @reboot**(v0.6.4);`lan disable` 移除,`lan status` 查询。
|
|
178
178
|
- **安全边界**:管理动作(init / config / token / lan / serve)不进 MCP,token 不远程暴露;远程智能体只能读写记忆,且路径限记忆库内(export/import 的 out/src 必须落在库内)、distill 不支持 `--model`(仅本地 CLI),不能改配置、不能管 token。
|
|
179
179
|
- 完整操作步骤见上文「局域网多机共享」章节与 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
180
180
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-memory
|
|
3
3
|
description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照、基线探针、恢复演练、记忆库安全扫描、scan、命中打点、容量水位、--capacity、--propose、上下文压缩审计、context --audit
|
|
4
|
-
version: 0.22.
|
|
4
|
+
version: 0.22.6
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -333,7 +333,7 @@ yotta-memory doctor --json
|
|
|
333
333
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 每智能体访问 token:生成 / 列出 / 吊销(登记 `<记忆库>/.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
334
334
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] [--tools core|full]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id + X-Agent-Key 鉴权;工具分组默认 full)|
|
|
335
335
|
| `yotta-memory runtime list / install <tarball|版本> [--from-current] [--force] / use <版本> [--restart] / rollback [--restart] / status` | 运行时稳定入口(runtime.json + versions + current;安装 / 切换 / 回滚 / 查看漂移;`--restart` 尝试重启受管 server,失败自动切回旧版本);漂移诊断用 `doctor --runtime` |
|
|
336
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:任务计划程序,S4U 主体后台静默——v0.22.4 起不弹窗口,`--onstart` 无登录会话也可启动;任务 XML 注册,去电池限制与 72h 时限;非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
336
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:任务计划程序,S4U 主体后台静默——v0.22.4 起不弹窗口,`--onstart` 无登录会话也可启动;任务 XML 注册,去电池限制与 72h 时限;非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,不再拉起 GUI;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
337
337
|
| `yotta-memory feedback <文件|主题> --useful|--useless [--reason <原因>] [--undo]` | 显式使用反馈(v0.8.0 自我学习闭环:useful → weight×1.2 / useless → weight×0.8,confidence / feedback_net 同步演化;`--undo` 回滚最近一次;审计写 `.archive/feedback-<日期>.jsonl`)|
|
|
338
338
|
| `yotta-memory maintain [--dry-run] [--apply] [--purge] [--threshold N] [--age N] [--dedup] [--dedup --apply] [--merge A,B] [--rules]` | 记忆自组织(v0.8.0 自我进化 + v0.10.0 自动合并):默认 dry-run 预览;`--apply` 执行归档(immutable / BOUND 豁免;私密归档入 `.archive/private/<owner>/<type>/`),`--purge` 才真删遗忘候选;`--dedup` 查重并给**置信度分档**(≥0.85 高置信 / 0.65–0.85 建议手动 / 其余忽略),`--dedup --apply` 自动合并同归属高置信组(写批次审计可回滚;与归档互斥,不误归档);`--merge A,B` 手动合并两条;`--rules` 只读输出重复踩坑 → 建议升级为规则的清单(阈值 `maintain_rule_min_hits`,默认 3;只给建议不写 BOUND);审计写 `.archive/audit-<日期>.jsonl`)|
|
|
339
339
|
| `yotta-memory consolidate [--min-age N] [--min-idle N] [--max-utility N] [--min-group N] [--period N] [--type T] [--model <cmd>] [--apply] [--undo <batch>] [--batches]` | 周期摘要压缩(v0.10.0 压缩遗忘 + v0.18.0 提案闸门 + v0.18.1 日期 / 标记:把超龄 + 长期闲置 + 低效用的同主题旧记忆归纳成**带溯源**的周期摘要并留在活跃区,原文整体进 `.archive/`;摘要正文按条目 `created` 把「昨天 / 上周 / 本月 / 今年」等绝对化,归档副本追加一行巩固标记;默认 dry-run;immutable / BOUND 豁免,活跃 / 高效用记忆不动;`--apply` 执行并写批次审计,`--undo <batch>` 剥离标记并回滚(幂等),`--batches` 查近期批次;`--model` 仅本地 CLI)|
|
package/USER_GUIDE.md
CHANGED
|
@@ -335,7 +335,8 @@ yotta-memory init --dir /srv/yotta-memory # 新库:初始化(
|
|
|
335
335
|
|
|
336
336
|
```bash
|
|
337
337
|
# Windows:内置命令(优先计划任务——v0.22.4 起 S4U 主体后台静默、不弹窗口;
|
|
338
|
-
# 非管理员自动降级用户级 Startup 静默自启;v0.6.3
|
|
338
|
+
# 非管理员自动降级用户级 Startup 静默自启;v0.6.3 起启动脚本自愈——启动文件被清理也会在开机时自动重建;
|
|
339
|
+
# v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,不再拉起 GUI)
|
|
339
340
|
yotta-memory lan enable # 登录后自动启动(默认)
|
|
340
341
|
yotta-memory lan enable --onstart # 开机即启、无登录会话也可启动(计划任务注册需管理员)
|
|
341
342
|
yotta-memory lan status # 查看状态
|
package/assets/view.html
CHANGED
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
|
|
251
251
|
<section class="view" id="view-memories">
|
|
252
252
|
<div class="card toolbar">
|
|
253
|
-
<label class="search" style="min-width:280px;flex:1"><svg class="sv" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input id="memSearch" placeholder="搜索记忆(标题 / 内容 /
|
|
253
|
+
<label class="search" style="min-width:280px;flex:1"><svg class="sv" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input id="memSearch" placeholder="搜索记忆(标题 / 内容 / 标签 / 编号)"></label>
|
|
254
254
|
<div class="chips" id="typeChips">
|
|
255
255
|
<button class="chip active" data-type="all">全部</button>
|
|
256
256
|
<button class="chip" data-type="FACT">FACT</button>
|
|
@@ -258,6 +258,7 @@
|
|
|
258
258
|
<button class="chip" data-type="BOUND">BOUND</button>
|
|
259
259
|
<button class="chip" data-type="COMMIT">COMMIT</button>
|
|
260
260
|
</div>
|
|
261
|
+
<select id="ownerFilter" title="按智能体筛选" style="width:auto;min-width:140px"><option value="">全部智能体</option></select>
|
|
261
262
|
<div class="spacer"></div>
|
|
262
263
|
<button class="btn" id="btnArchived">已藏</button>
|
|
263
264
|
<button class="btn" id="btnTrash">最近删除</button>
|
|
@@ -329,7 +330,7 @@
|
|
|
329
330
|
(function () {
|
|
330
331
|
'use strict';
|
|
331
332
|
var $ = function (s) { return document.querySelector(s); };
|
|
332
|
-
var state = { view: 'overview', entries: [], count: 0, offset: 0, limit: 20, query: '', type: 'all', owners: [], overview: null, current: null, help: null };
|
|
333
|
+
var state = { view: 'overview', entries: [], count: 0, offset: 0, limit: 20, query: '', type: 'all', owner: '', owners: [], overview: null, current: null, help: null };
|
|
333
334
|
var typeNames = { FACT: 'FACT 事实', PREF: 'PREF 偏好', BOUND: 'BOUND 边界', COMMIT: 'COMMIT 承诺' };
|
|
334
335
|
|
|
335
336
|
function esc(s) {
|
|
@@ -437,14 +438,24 @@
|
|
|
437
438
|
$('#ovBytes').textContent = fmtBytes(d.bytes);
|
|
438
439
|
$('#accBackup').textContent = d.backupDir || '未配置';
|
|
439
440
|
var colors = { FACT: 'var(--gold)', PREF: '#b79cdd', BOUND: '#8fb4de', COMMIT: 'var(--ok)' };
|
|
441
|
+
var byType = d.byType || {};
|
|
442
|
+
var typeCounts = { all: d.total || 0, FACT: byType.FACT || 0, PREF: byType.PREF || 0, BOUND: byType.BOUND || 0, COMMIT: byType.COMMIT || 0 };
|
|
440
443
|
$('#typeBars').innerHTML = ['FACT', 'PREF', 'BOUND', 'COMMIT'].map(function (t) {
|
|
441
|
-
|
|
442
|
-
return '<div class="bar"><span class="dot" style="background:' + colors[t] + '"></span><span class="faint">' + typeNames[t] + '</span><b>' + n + '</b></div>';
|
|
444
|
+
return '<div class="bar" data-type="' + t + '" style="cursor:pointer" title="查看 ' + typeNames[t] + '"><span class="dot" style="background:' + colors[t] + '"></span><span class="faint">' + typeNames[t] + '</span><b>' + typeCounts[t] + '</b></div>';
|
|
443
445
|
}).join('');
|
|
446
|
+
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) {
|
|
447
|
+
c.textContent = (c.dataset.type === 'all' ? '全部' : c.dataset.type) + ' ' + (typeCounts[c.dataset.type] || 0);
|
|
448
|
+
});
|
|
444
449
|
}
|
|
445
450
|
async function loadOwners() {
|
|
446
451
|
var d = await api('/api/owners');
|
|
447
452
|
state.owners = (d && d.owners) || [];
|
|
453
|
+
var sel = $('#ownerFilter');
|
|
454
|
+
if (sel) {
|
|
455
|
+
sel.innerHTML = '<option value="">全部智能体</option>' + state.owners.map(function (o) {
|
|
456
|
+
return '<option value="' + esc(o.owner) + '"' + (o.owner === state.owner ? ' selected' : '') + '>' + esc(o.owner) + '</option>';
|
|
457
|
+
}).join('');
|
|
458
|
+
}
|
|
448
459
|
var box = $('#ownerRows');
|
|
449
460
|
if (!state.owners.length) { box.innerHTML = '<tr><td colspan="5" class="faint">' + esc((d && d.hint) || '当前没有 owner,请先在 CLI 执行 yotta-memory iam <id>。') + '</td></tr>'; return; }
|
|
450
461
|
box.innerHTML = state.owners.map(function (o) {
|
|
@@ -456,7 +467,7 @@
|
|
|
456
467
|
}).join('');
|
|
457
468
|
}
|
|
458
469
|
async function loadEntries() {
|
|
459
|
-
var d = await api('/api/entries', { query: state.query, offset: state.offset, limit: state.limit, type: state.type === 'all' ? '' : state.type });
|
|
470
|
+
var d = await api('/api/entries', { query: state.query, offset: state.offset, limit: state.limit, type: state.type === 'all' ? '' : state.type, owner: state.owner || '' });
|
|
460
471
|
if (d.error) { toast(errText(d, '加载失败')); return; }
|
|
461
472
|
state.entries = d.entries || [];
|
|
462
473
|
state.count = d.count || 0;
|
|
@@ -669,6 +680,15 @@
|
|
|
669
680
|
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) { c.classList.toggle('active', c === b); });
|
|
670
681
|
loadEntries();
|
|
671
682
|
});
|
|
683
|
+
$('#typeBars').addEventListener('click', function (e) {
|
|
684
|
+
var bar = e.target.closest('.bar[data-type]');
|
|
685
|
+
if (!bar) return;
|
|
686
|
+
state.type = bar.dataset.type; state.offset = 0;
|
|
687
|
+
Array.prototype.forEach.call(document.querySelectorAll('#typeChips .chip'), function (c) { c.classList.toggle('active', c.dataset.type === state.type); });
|
|
688
|
+
switchView('memories');
|
|
689
|
+
loadEntries();
|
|
690
|
+
});
|
|
691
|
+
$('#ownerFilter').addEventListener('change', function () { state.owner = this.value; state.offset = 0; loadEntries(); });
|
|
672
692
|
$('#prevPage').addEventListener('click', function () { if (state.offset > 0) { state.offset -= state.limit; loadEntries(); } });
|
|
673
693
|
$('#nextPage').addEventListener('click', function () { state.offset += state.limit; loadEntries(); });
|
|
674
694
|
|
|
@@ -803,7 +823,7 @@
|
|
|
803
823
|
}
|
|
804
824
|
function renderFull() {
|
|
805
825
|
if (!state.help) return;
|
|
806
|
-
|
|
826
|
+
var groupsHtml = (state.help.groups || []).map(function (g) {
|
|
807
827
|
var cmds = (g.commands || []).map(function (c) {
|
|
808
828
|
var subs = (c.subcommands || []).map(function (s) {
|
|
809
829
|
return '<div class="help-sub"><div class="cmd-head"><code class="cmd-name">' + esc(c.name + ' ' + s.name) + '</code></div>' +
|
|
@@ -817,6 +837,13 @@
|
|
|
817
837
|
}).join('');
|
|
818
838
|
return '<div class="quick-group"><h4>' + esc(g.group) + '</h4>' + cmds + '</div>';
|
|
819
839
|
}).join('');
|
|
840
|
+
var keys = state.help.configKeys || [];
|
|
841
|
+
var keysHtml = keys.length ? '<div class="quick-group" id="cliConfigKeys"><h4>配置键(yotta-memory config set <键> <值>)</h4><div class="quick-list">' +
|
|
842
|
+
keys.map(function (k) {
|
|
843
|
+
return '<div class="cmd-item"><div class="cmd-head"><code class="cmd-name">' + esc(k) + '</code></div>' +
|
|
844
|
+
codeLine('yotta-memory config set ' + k + ' <值>') + '</div>';
|
|
845
|
+
}).join('') + '</div></div>' : '';
|
|
846
|
+
$('#cliFull').innerHTML = groupsHtml + keysHtml;
|
|
820
847
|
}
|
|
821
848
|
async function ensureHelp() {
|
|
822
849
|
if (state.help) { renderQuick(); return; }
|
package/bin/install.js
CHANGED
|
@@ -27,7 +27,7 @@ const PKG_ROOT = path.join(__dirname, '..');
|
|
|
27
27
|
const TOP_SKIP = [
|
|
28
28
|
'package.json', 'package-lock.json', 'bin', 'lib', 'test',
|
|
29
29
|
'.github', '.git', '.gitignore', '.npmignore', '.gitattributes',
|
|
30
|
-
'.yotta', '.tmp', 'install.sh', 'node_modules',
|
|
30
|
+
'.yotta', '.tmp', 'install.sh', 'node_modules', 'test-fixtures',
|
|
31
31
|
];
|
|
32
32
|
const TOP_SKIP_SET = new Set(TOP_SKIP);
|
|
33
33
|
// 缓存 / 编译产物:任意层级跳过,安装时同样清理。
|
|
@@ -190,6 +190,7 @@ function copyDir(src, dst, topLevel) {
|
|
|
190
190
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
191
191
|
// 只跳过安装包顶层开发文件;template/ 等嵌套同名载荷必须保留。
|
|
192
192
|
if (topLevel && TOP_SKIP_SET.has(entry.name)) continue;
|
|
193
|
+
if (topLevel && entry.isFile() && /\.tgz$/i.test(entry.name)) continue;
|
|
193
194
|
if (shouldSkipCache(entry.name, entry.isFile())) continue;
|
|
194
195
|
const s = path.join(src, entry.name);
|
|
195
196
|
const d = path.join(dst, entry.name);
|
|
@@ -219,6 +220,16 @@ function cleanResidue(target) {
|
|
|
219
220
|
fs.rmSync(p, { recursive: true, force: true });
|
|
220
221
|
removed.push(name);
|
|
221
222
|
}
|
|
223
|
+
try {
|
|
224
|
+
for (const name of fs.readdirSync(target)) {
|
|
225
|
+
if (!/\.tgz$/i.test(name)) continue;
|
|
226
|
+
const p = path.join(target, name);
|
|
227
|
+
const entry = fs.lstatSync(p);
|
|
228
|
+
if (entry.isSymbolicLink() || !entry.isFile()) continue;
|
|
229
|
+
fs.rmSync(p, { force: true });
|
|
230
|
+
removed.push(name);
|
|
231
|
+
}
|
|
232
|
+
} catch (_) { /* ignore */ }
|
|
222
233
|
const walk = (dir) => {
|
|
223
234
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
224
235
|
const p = path.join(dir, entry.name);
|