153 lines
4.9 KiB
JavaScript
153 lines
4.9 KiB
JavaScript
|
|
#!/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_FILE,2 次 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();
|