feat: 巡检增强 / 拓扑大屏 / 远程 HTTP / portable 绿色打包
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
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
巡检模块:巡检调度器改为轮询驱动,新增每日报告存储、告警规则独立 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 运行时依赖。
This commit is contained in:
99
scripts/migratePortableLocalStorage.cjs
Normal file
99
scripts/migratePortableLocalStorage.cjs
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 离线迁移脚本:把 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); });
|
||||
48
scripts/scanWorkspaceDeps.cjs
Normal file
48
scripts/scanWorkspaceDeps.cjs
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 扫描根 package.json + 所有 workspace 子包的 dependencies/optionalDependencies,
|
||||
* 找出 workspace 子包引用但根 package.json 没声明的运行时依赖(hoist 到根 node_modules 了)。
|
||||
*
|
||||
* 这些包 electron-builder 收集器看不到(它只读根 package.json),
|
||||
* 会导致打包后 MODULE_NOT_FOUND。需要加到根 package.json 里。
|
||||
*/
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..");
|
||||
const rootPkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf-8"));
|
||||
|
||||
// workspace 配置
|
||||
let globs = rootPkg.workspace || rootPkg.pnpm?.packages || ["packages/*"];
|
||||
if (!Array.isArray(globs)) globs = ["packages/*"];
|
||||
|
||||
const rootRuntime = { ...rootPkg.dependencies, ...rootPkg.optionalDependencies };
|
||||
|
||||
const subRuntime = {}; // 子包独有的运行时依赖 → 版本
|
||||
|
||||
for (const g of globs) {
|
||||
const dir = path.join(rootDir, g.replace("/*", ""));
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const e of entries) {
|
||||
if (!e.isDirectory()) continue;
|
||||
const pkgPath = path.join(dir, e.name, "package.json");
|
||||
if (!fs.existsSync(pkgPath)) continue;
|
||||
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
||||
const deps = { ...pkgJson.dependencies, ...pkgJson.optionalDependencies };
|
||||
for (const [name, version] of Object.entries(deps)) {
|
||||
if (!rootRuntime[name] && !subRuntime[name]) {
|
||||
subRuntime[name] = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const missingRootDeps = Object.keys(subRuntime);
|
||||
console.log("子包独有的运行时依赖(根 package.json 没声明):");
|
||||
console.log(" 数量:", missingRootDeps.length);
|
||||
console.log(" 列表:", missingRootDeps.join(", "));
|
||||
console.log("");
|
||||
console.log("根 package.json 已有运行时依赖:", Object.keys(rootRuntime).length);
|
||||
console.log("");
|
||||
console.log("建议加到根 package.json.dependencies 的条目:");
|
||||
console.log(JSON.stringify(subRuntime, null, 2));
|
||||
Reference in New Issue
Block a user