返回 last30days-skill
runtime-features.js
根目录 / skills / last30days / scripts / lib / vendor / bird-search / lib / runtime-features.js
1 import { existsSync, readFileSync } from 'node:fs';
2 import { mkdir, writeFile } from 'node:fs/promises';
3 import { homedir } from 'node:os';
4 import path from 'node:path';
5 // biome-ignore lint/correctness/useImportExtensions: JSON module import doesn't use .js extension.
6 import defaultOverrides from './features.json' with { type: 'json' };
7 const DEFAULT_CACHE_FILENAME = 'features.json';
8 let cachedOverrides = null;
9 function normalizeFeatureMap(value) {
10 if (!value || typeof value !== 'object' || Array.isArray(value)) {
11 return {};
12 }
13 const result = {};
14 for (const [key, entry] of Object.entries(value)) {
15 if (typeof entry === 'boolean') {
16 result[key] = entry;
17 }
18 }
19 return result;
20 }
21 function normalizeOverrides(value) {
22 if (!value || typeof value !== 'object' || Array.isArray(value)) {
23 return { global: {}, sets: {} };
24 }
25 const record = value;
26 const global = normalizeFeatureMap(record.global);
27 const sets = {};
28 const rawSets = record.sets && typeof record.sets === 'object' && !Array.isArray(record.sets)
29 ? record.sets
30 : {};
31 for (const [setName, setValue] of Object.entries(rawSets)) {
32 const normalized = normalizeFeatureMap(setValue);
33 if (Object.keys(normalized).length > 0) {
34 sets[setName] = normalized;
35 }
36 }
37 return { global, sets };
38 }
39 function mergeOverrides(base, next) {
40 const sets = { ...base.sets };
41 for (const [setName, overrides] of Object.entries(next.sets)) {
42 const existing = sets[setName];
43 sets[setName] = existing ? { ...existing, ...overrides } : { ...overrides };
44 }
45 return {
46 global: { ...base.global, ...next.global },
47 sets,
48 };
49 }
50 function toFeatureOverrides(overrides) {
51 const result = {};
52 if (Object.keys(overrides.global).length > 0) {
53 result.global = overrides.global;
54 }
55 const setEntries = Object.entries(overrides.sets).filter(([, value]) => Object.keys(value).length > 0);
56 if (setEntries.length > 0) {
57 result.sets = Object.fromEntries(setEntries);
58 }
59 return result;
60 }
61 function resolveFeaturesCachePath() {
62 const override = process.env.BIRD_FEATURES_CACHE ?? process.env.BIRD_FEATURES_PATH;
63 if (override && override.trim().length > 0) {
64 return path.resolve(override.trim());
65 }
66 return path.join(homedir(), '.config', 'bird', DEFAULT_CACHE_FILENAME);
67 }
68 function readOverridesFromFile(cachePath) {
69 if (!existsSync(cachePath)) {
70 return null;
71 }
72 try {
73 const raw = readFileSync(cachePath, 'utf8');
74 return normalizeOverrides(JSON.parse(raw));
75 }
76 catch {
77 return null;
78 }
79 }
80 function readOverridesFromEnv() {
81 const raw = process.env.BIRD_FEATURES_JSON;
82 if (!raw || raw.trim().length === 0) {
83 return null;
84 }
85 try {
86 return normalizeOverrides(JSON.parse(raw));
87 }
88 catch {
89 return null;
90 }
91 }
92 function writeOverridesToDisk(cachePath, overrides) {
93 const payload = toFeatureOverrides(overrides);
94 return mkdir(path.dirname(cachePath), { recursive: true }).then(() => writeFile(cachePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'));
95 }
96 export function loadFeatureOverrides() {
97 if (cachedOverrides) {
98 return cachedOverrides;
99 }
100 const base = normalizeOverrides(defaultOverrides);
101 const fromFile = readOverridesFromFile(resolveFeaturesCachePath());
102 const fromEnv = readOverridesFromEnv();
103 let merged = base;
104 if (fromFile) {
105 merged = mergeOverrides(merged, fromFile);
106 }
107 if (fromEnv) {
108 merged = mergeOverrides(merged, fromEnv);
109 }
110 cachedOverrides = merged;
111 return merged;
112 }
113 export function getFeatureOverridesSnapshot() {
114 const overrides = toFeatureOverrides(loadFeatureOverrides());
115 return {
116 cachePath: resolveFeaturesCachePath(),
117 overrides,
118 };
119 }
120 export function applyFeatureOverrides(setName, base) {
121 const overrides = loadFeatureOverrides();
122 const globalOverrides = overrides.global;
123 const setOverrides = overrides.sets[setName];
124 if (Object.keys(globalOverrides).length === 0 && (!setOverrides || Object.keys(setOverrides).length === 0)) {
125 return base;
126 }
127 if (setOverrides) {
128 return {
129 ...base,
130 ...globalOverrides,
131 ...setOverrides,
132 };
133 }
134 return {
135 ...base,
136 ...globalOverrides,
137 };
138 }
139 export async function refreshFeatureOverridesCache() {
140 const cachePath = resolveFeaturesCachePath();
141 const base = normalizeOverrides(defaultOverrides);
142 const fromFile = readOverridesFromFile(cachePath);
143 const merged = mergeOverrides(base, fromFile ?? { global: {}, sets: {} });
144 await writeOverridesToDisk(cachePath, merged);
145 cachedOverrides = null;
146 return { cachePath, overrides: toFeatureOverrides(merged) };
147 }
148 export function clearFeatureOverridesCache() {
149 cachedOverrides = null;
150 }
151 //# sourceMappingURL=runtime-features.js.map
151 lines JAVASCRIPT