bookworm-smart-assistant/scripts/feature-flags.js

138 lines
3.9 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.

#!/usr/bin/env node
/**
* Feature Flags 加载器 (Phase 0)
*
* 提供统一的 feature flag 读取接口,所有 hook/script 统一调用。
*
* 特性:
* - 5 秒进程内缓存
* - 文件缺失/损坏 → 返回空 features所有功能视为关闭
* - Mode 语义: off=完全关闭 | warn=仅记录不阻断 | enforce=完全生效
* - T3 (2026-04-17): warn 模式到达 promoteToEnforceAfter 日期后自动升 enforce
*/
const fs = require('fs');
const path = require('path');
// 路径解析
let flagsPath;
try {
const { PATHS } = require('./paths.config.js');
flagsPath = PATHS.featureFlagsJson;
} catch {
const root = path.resolve(__dirname, '..');
flagsPath = path.join(root, 'feature-flags.json');
}
// ─── 缓存 ────────────────────────────────────────────
const CACHE_TTL = 5000; // 5 秒
let _cache = null;
let _cacheTs = 0;
/**
* 加载 feature-flags.json带缓存
* @returns {Object} features 对象,失败时返回空对象
*/
function loadFlags() {
const now = Date.now();
if (_cache && (now - _cacheTs) < CACHE_TTL) {
return _cache;
}
try {
const raw = fs.readFileSync(flagsPath, 'utf8');
const parsed = JSON.parse(raw);
_cache = parsed.features || {};
_cacheTs = now;
return _cache;
} catch {
// H8: 文件缺失/损坏 → 保持旧缓存(如果存在),而非清空
// 避免安全 flag 被暂时性 I/O 错误关闭
if (!_cache) _cache = {};
// 不更新 _cacheTs下次调用会重试加载
return _cache;
}
}
/**
* 检查功能是否启用
* @param {string} name - feature flag 名称
* @returns {boolean}
*/
function isEnabled(name) {
const flags = loadFlags();
const flag = flags[name];
if (!flag) return false;
return flag.enabled === true;
}
/**
* 获取功能模式
* @param {string} name - feature flag 名称
* @returns {'off'|'warn'|'enforce'}
*
* T3 自动提升规则: mode=warn 且当前日期 >= promoteToEnforceAfter → 返回 'enforce'
* 字段缺失/格式错误时向后兼容返回原 mode.
*/
function getMode(name) {
const flags = loadFlags();
const flag = flags[name];
if (!flag) return 'off';
let mode = flag.mode || 'off';
// T3: 到期自动提升 warn → enforce (2026-04-17)
if (mode === 'warn' && flag.promoteToEnforceAfter) {
const promoteDate = new Date(flag.promoteToEnforceAfter);
if (!isNaN(promoteDate.getTime()) && Date.now() >= promoteDate.getTime()) {
mode = 'enforce';
}
}
return mode;
}
/**
* 获取完整 flag 对象
* @param {string} name - feature flag 名称
* @returns {Object|null}
*/
function getFlag(name) {
const flags = loadFlags();
return flags[name] || null;
}
/**
* 列出所有 flags
* @returns {Object} { name: { enabled, mode, phase }, ... }
*/
function listFlags() {
return loadFlags();
}
/**
* 清除缓存(测试用)
*/
function clearCache() {
_cache = null;
_cacheTs = 0;
}
// ─── 导出 ─────────────────────────────────────────────
module.exports = { isEnabled, getMode, getFlag, listFlags, clearCache };
// CLI: 直接运行时打印所有 flags (附效度模式提示)
if (require.main === module) {
const flags = listFlags();
console.log('=== Bookworm Feature Flags ===');
console.log(`Path: ${flagsPath}`);
console.log(`Today: ${new Date().toISOString().slice(0, 10)}`);
console.log(`Flags: ${Object.keys(flags).length}`);
console.log('');
for (const [name, flag] of Object.entries(flags)) {
const status = flag.enabled ? '✓ ON' : '✗ OFF';
const effective = getMode(name);
const hint = flag.mode !== effective ? ` [T3 auto→${effective}]` : '';
console.log(` ${status} ${name} (mode: ${flag.mode}${hint}, phase: ${flag.phase})`);
}
}