bookworm-smart-assistant/scripts/patches/patch-p1-fast-cache-lib.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

153 lines
4.9 KiB
JavaScript
Raw Permalink 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.

#!/usr/bin/env node
/**
* patch-p1-fast-cache-lib.js
*
* P1.5: 创建 hooks/lib/fast-cache.js
*
* 启动性能优化: 通过 mtime 签名缓存合并多次 readFileSync。
* 对齐 OpenClaw entry.version-fast-path + module.enableCompileCache 思路。
*
* Bookworm 的 hooks 每次冷启动都读 stats-compiled / settings / route-stats /
* SESSION_LOCK / STATE_FILE2 次 statSync + 缓存命中可省 15-30ms/次。
*/
'use strict';
const fs = require('fs');
const path = require('path');
const TARGET = path.join(__dirname, '..', '..', 'hooks', 'lib', 'fast-cache.js');
const SENTINEL = 'P1-FAST-CACHE-V1';
const CONTENT = `'use strict';
/**
* fast-cache.js — 启动期热数据快路径缓存 (${SENTINEL})
*
* 通过 mtime 签名: 所有源文件未变 → 直接返回上次缓存。
* 仅缓存只读字段子集,避免缓存整个大 JSON 文件。
*
* 借鉴: OpenClaw entry.version-fast-path.ts (零模块加载快退出)
*
* Usage:
* const { readFastCache } = require('./lib/fast-cache.js');
* const cache = readFastCache() || {};
* const skillCount = cache.skillCount || 0;
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..', '..');
const CACHE_FILE = path.join(ROOT, 'debug', '.hook-fast-cache.json');
const SOURCES = [
{ file: path.join(ROOT, 'stats-compiled.json'), fields: ['summary', 'version'] },
{ file: path.join(ROOT, 'settings.json'), fields: ['mcpServers'] },
];
function readFastCache() {
try {
const mtimes = SOURCES.map(function(s) {
try { return fs.statSync(s.file).mtimeMs; } catch (_) { return 0; }
});
const sig = mtimes.join(':');
if (fs.existsSync(CACHE_FILE)) {
try {
const cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
if (cache && cache._sig === sig) return cache;
} catch (_) { /* malformed cache, rebuild */ }
}
const rebuilt = { _sig: sig, _builtAt: Date.now() };
for (let i = 0; i < SOURCES.length; i++) {
const { file, fields } = SOURCES[i];
try {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
for (let j = 0; j < fields.length; j++) {
rebuilt[fields[j]] = data[fields[j]];
}
} catch (_) { /* missing file ok */ }
}
rebuilt.mcpCount = Object.keys(rebuilt.mcpServers || {}).length;
rebuilt.skillCount = (rebuilt.summary || {}).skills || 0;
rebuilt.hookCount = (rebuilt.summary || {}).hooksRegistered || (rebuilt.summary || {}).hooks || 0;
rebuilt.agentCount = (rebuilt.summary || {}).agents || 0;
// 异步写回 (不阻塞主流程)
setImmediate(function() {
try {
const cacheDir = path.dirname(CACHE_FILE);
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
const tmp = CACHE_FILE + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(rebuilt));
fs.renameSync(tmp, CACHE_FILE);
} catch (_) { /* best effort */ }
});
return rebuilt;
} catch (_) {
return null;
}
}
function enableCompileCacheBestEffort() {
try {
const mod = require('node:module');
if (mod.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
mod.enableCompileCache();
return true;
}
} catch (_) { /* unsupported */ }
return false;
}
module.exports = {
readFastCache,
enableCompileCacheBestEffort,
__sentinel: '${SENTINEL}',
};
`;
function main() {
const dir = path.dirname(TARGET);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
if (fs.existsSync(TARGET)) {
const cur = fs.readFileSync(TARGET, 'utf8');
if (cur.includes(SENTINEL)) {
process.stdout.write('[SKIP] already deployed\n');
process.exit(0);
}
const ts = new Date().toISOString().replace(/[:.]/g, '-');
fs.copyFileSync(TARGET, TARGET + '.bak.' + ts);
process.stdout.write('[BACKUP] ' + TARGET + '.bak.' + ts + '\n');
}
const tmpPath = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, CONTENT);
try {
delete require.cache[require.resolve(tmpPath)];
const mod = require(tmpPath);
// 自检 1: readFastCache 工作
const c = mod.readFastCache();
if (c === null) throw new Error('readFastCache returned null');
if (typeof c.skillCount !== 'number') throw new Error('skillCount not number');
// 自检 2: enableCompileCache 不抛
const enabled = mod.enableCompileCacheBestEffort();
process.stdout.write(' compile cache: ' + (enabled ? 'ENABLED' : 'unavailable') + '\n');
fs.renameSync(tmpPath, TARGET);
process.stdout.write('[OK] hooks/lib/fast-cache.js deployed\n');
process.stdout.write(' skills=' + c.skillCount + ' hooks=' + c.hookCount + ' mcp=' + c.mcpCount + '\n');
} catch (e) {
fs.unlinkSync(tmpPath);
process.stderr.write('[ERROR] self-test failed: ' + e.message + '\n');
process.exit(1);
}
}
if (require.main === module) main();