mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(config-facade): mtime-based staleness detection for cache
getCachedConfig() now checks config file mtime on each call. If external code writes via unified-config-loader directly, the facade detects the file change and re-reads from disk automatically. Resolves PR-Agent "Stale Cache" finding.
This commit is contained in:
@@ -208,5 +208,27 @@ describe('config-loader-facade', () => {
|
||||
expect(config.profiles).toBeDefined();
|
||||
expect(config.cliproxy).toBeDefined();
|
||||
});
|
||||
|
||||
it('auto-invalidates when config file is modified externally', async () => {
|
||||
const facade = await importFacade();
|
||||
|
||||
// Prime the cache
|
||||
const first = facade.getCachedConfig();
|
||||
expect(first.version).toBeDefined();
|
||||
|
||||
// Modify config file directly on disk (simulating external code
|
||||
// bypassing the facade)
|
||||
const configPath = path.join(tempHome, '.ccs', 'config.yaml');
|
||||
// Touch the file to update mtime (wait briefly for mtime resolution)
|
||||
const content = fs.readFileSync(configPath, 'utf8');
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
fs.writeFileSync(configPath, content + '# touched\n', 'utf8');
|
||||
|
||||
// getCachedConfig should detect the mtime change and re-read
|
||||
const second = facade.getCachedConfig();
|
||||
expect(first).not.toBe(second);
|
||||
// Content should still be valid (re-read from disk)
|
||||
expect(second.version).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,34 +52,42 @@ export type { GeminiWebSearchInfo } from './unified-config-loader';
|
||||
export { loadSettings, loadConfigSafe, readConfig, getCcsDir } from '../utils/config-manager';
|
||||
|
||||
// Internal imports for memoization wrappers
|
||||
import { statSync } from 'fs';
|
||||
import type { UnifiedConfig } from './unified-config-types';
|
||||
import {
|
||||
loadOrCreateUnifiedConfig as _loadOrCreateUnifiedConfig,
|
||||
saveUnifiedConfig as _saveUnifiedConfig,
|
||||
mutateUnifiedConfig as _mutateUnifiedConfig,
|
||||
updateUnifiedConfig as _updateUnifiedConfig,
|
||||
getConfigYamlPath as _getConfigYamlPath,
|
||||
} from './unified-config-loader';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Memoization cache
|
||||
// Memoization cache with mtime-based staleness detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let _configCache: UnifiedConfig | null = null;
|
||||
let _cacheMtimeMs: number = 0;
|
||||
|
||||
function getConfigFileMtime(): number {
|
||||
try {
|
||||
return statSync(_getConfigYamlPath()).mtimeMs;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unified config with in-memory caching.
|
||||
* First call reads from disk; subsequent calls return a deep copy.
|
||||
* Returns a copy to prevent callers from silently mutating the cache.
|
||||
*
|
||||
* NOTE: `loadOrCreateUnifiedConfig` (re-exported above) is NOT cached.
|
||||
* Use `getCachedConfig()` for cached reads, or the direct import for uncached.
|
||||
*
|
||||
* Call invalidateConfigCache() or use mutateConfig()/updateConfig()
|
||||
* to force a re-read from disk.
|
||||
* Checks file mtime on each call — if the config file was modified
|
||||
* externally (e.g. by code importing unified-config-loader directly),
|
||||
* the cache is automatically invalidated and re-read from disk.
|
||||
*/
|
||||
export function getCachedConfig(): UnifiedConfig {
|
||||
if (!_configCache) {
|
||||
const currentMtime = getConfigFileMtime();
|
||||
if (!_configCache || currentMtime > _cacheMtimeMs) {
|
||||
_configCache = _loadOrCreateUnifiedConfig();
|
||||
_cacheMtimeMs = getConfigFileMtime();
|
||||
}
|
||||
return structuredClone(_configCache);
|
||||
}
|
||||
@@ -90,6 +98,7 @@ export function getCachedConfig(): UnifiedConfig {
|
||||
*/
|
||||
export function invalidateConfigCache(): void {
|
||||
_configCache = null;
|
||||
_cacheMtimeMs = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user