Files
SenMeshNet/scripts/migratePortableLocalStorage.cjs
zhaolei 2e71e03d71
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
feat: 巡检增强 / 拓扑大屏 / 远程 HTTP / portable 绿色打包
巡检模块:巡检调度器改为轮询驱动,新增每日报告存储、告警规则独立
Store、设备 Store 解耦,_executeSnapshot 补齐 DEBUG 声明,巡检中心
与告警中心页增强。

拓扑大屏:MonitoringTopologyView 扩展分组/布局控制,
useMonitoringData 重构数据流。

远程 HTTP:新增 electron/remoteHttpServer.cjs,preload/api.cjs 暴露
IPC,registerBridges 注册 netcatty:http:open 桥。

portable 绿色打包:
- electron-builder.config.cjs 恢复原始白名单(61MB app.asar),
  收集器自动追踪运行时依赖;
- portableData.cjs 重写为 .portable 标记检测;
- scripts/migratePortableLocalStorage.cjs 离线脚本把 Local Storage
  的 http://localhost:5173 origin 批量迁移为 app://netcatty;
- scripts/scanWorkspaceDeps.cjs 扫描 workspace 运行时依赖。
2026-10-19 21:11:57 +08:00

100 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 离线迁移脚本:把 dev Local Storage leveldb 里所有
* http://localhost:5173\x00\x01 前缀的 key
* 替换成
* app://netcatty\x00\x01
*
* 运行时机:绿色便携版打包完、给用户之前执行一次。
* 之后 Electron 启动时直接就能读正确 origin 的数据。
*
* 用法:node scripts/migratePortableLocalStorage.cjs <portableUserDataPath>
* 例:node scripts/migratePortableLocalStorage.cjs release-green\win-unpacked\data
*
* 需要:level 包(npm i --no-save level)
*/
const fs = require("node:fs");
const path = require("node:path");
const { Level } = require("level");
const DEV_ORIGIN = "http://localhost:5173";
const PKG_ORIGIN = "app://netcatty";
const KEY_SEP = Buffer.from([0x00, 0x01]);
// Chromium Local Storage leveldb key 格式:
// _<origin>\x00\x01<actualKey>
// 注意:origin 前有个下划线前缀!
const DEV_ORIGIN_FULL = "_" + DEV_ORIGIN;
const PKG_ORIGIN_FULL = "_" + PKG_ORIGIN;
function findSep(keyBuf, from) {
for (let i = from; i < keyBuf.length - 1; i++) {
if (keyBuf[i] === 0x00 && keyBuf[i + 1] === 0x01) return i;
}
return -1;
}
async function main() {
const dataDir = process.argv[2];
if (!dataDir) { console.error("usage: node migratePortableLocalStorage.cjs <portableUserDataPath>"); process.exit(2); }
const lsDir = path.join(path.resolve(dataDir), "Local Storage", "leveldb");
if (!fs.existsSync(lsDir)) { console.error("leveldb dir not found:", lsDir); process.exit(2); }
// 先看看 origin 统计
const db = new Level(lsDir, { keyEncoding: "buffer", valueEncoding: "buffer", createIfMissing: false });
const counts = new Map(); // origin -> count
const ops = [];
let scanned = 0, migrated = 0;
for await (const [keyBuf, valBuf] of db.iterator()) {
scanned++;
const sepIdx = findSep(keyBuf, 0);
if (sepIdx < 0) continue;
const origin = keyBuf.subarray(0, sepIdx).toString("utf-8");
counts.set(origin, (counts.get(origin) || 0) + 1);
if (origin === DEV_ORIGIN_FULL) {
// 新 key:_app://netcatty + \x00\x01 + actualKey
const newKey = Buffer.concat([Buffer.from(PKG_ORIGIN_FULL, "utf-8"), KEY_SEP, keyBuf.subarray(sepIdx + 2)]);
ops.push({ type: "put", key: newKey, value: valBuf });
ops.push({ type: "del", key: keyBuf });
migrated++;
}
}
await db.close();
console.log(`扫描 ${scanned} 个 key,origin 分布:`);
for (const [o, c] of counts) console.log(` ${o || "(无 origin)"} → ${c}`);
if (migrated === 0) {
console.log("\n没有 http://localhost:5173 前缀的 key 需要迁移。跳过。");
return;
}
// 备份当前 leveldb
const bak = lsDir + ".bak_" + new Date().toISOString().replace(/[:.]/g, "-");
fs.cpSync(lsDir, bak, { recursive: true });
console.log(`\n已备份 leveldb → ${bak}`);
// 执行迁移
const db2 = new Level(lsDir, { keyEncoding: "buffer", valueEncoding: "buffer", createIfMissing: false });
await db2.batch(ops);
await db2.close();
console.log(`迁移完成:${migrated} 个 key 从 ${DEV_ORIGIN} → ${PKG_ORIGIN}`);
// 验证
const db3 = new Level(lsDir, { keyEncoding: "buffer", valueEncoding: "buffer", createIfMissing: false });
const after = new Map();
for await (const [k] of db3.iterator()) {
const sep = findSep(k, 0);
if (sep >= 0) {
const o = k.subarray(0, sep).toString("utf-8");
after.set(o, (after.get(o) || 0) + 1);
}
}
await db3.close();
console.log("\n迁移后 origin 分布:");
for (const [o, c] of after) console.log(` ${o || "(无)"} → ${c}`);
}
main().catch(e => { console.error("FATAL:", e.message); process.exit(1); });