Files
ccs/src/web-server/file-watcher.ts
T
Tam Nhu Tran 4f6e61739c refactor(config): adopt config-loader-facade across the codebase
Issue #1161. Sweeps 127 files to import from
src/config/config-loader-facade.ts instead of unified-config-loader or
utils/config-manager directly.

WRITE callers (32 files): replaced raw saveUnifiedConfig /
mutateUnifiedConfig / updateUnifiedConfig calls with the facade's
cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This
fixes a latent stale-cache window where direct writes through the
underlying loader bypassed the facade's memoization.

READ callers (95 files): mechanical import-path migration only —
function names unchanged because the facade re-exports them. No
behavior change.

Also updated:
- tests/unit/utils/browser/browser-setup.test.ts (DI interface rename)
- src/management/checks/image-analysis-check.ts (dynamic import rename)
- src/web-server/health-service.ts (dynamic require rename)
- src/ccs.ts (path prefix fix from sweep script)

After sweep: zero raw write callers remain outside src/config/. Direct
imports of config-manager remain only for symbols not in the facade
(getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite
passes 1824/1824.

Out of scope: switching loadOrCreateUnifiedConfig() callers to
getCachedConfig() — needs per-callsite cache-safety analysis. Tracked
as follow-up.

Refs #1161
2026-05-03 01:42:53 -04:00

80 lines
1.9 KiB
TypeScript

/**
* File Watcher (Phase 04)
*
* Watches ~/.ccs/ directory for config file changes using chokidar.
* Broadcasts changes to WebSocket clients for real-time sync.
*/
import chokidar, { FSWatcher } from 'chokidar';
import * as path from 'path';
import { getCcsDir } from '../config/config-loader-facade';
export interface FileChangeEvent {
type: 'config-changed' | 'settings-changed' | 'profiles-changed' | 'proxy-status-changed';
path: string;
timestamp: number;
}
export type FileChangeCallback = (event: FileChangeEvent) => void;
export function createFileWatcher(onChange: FileChangeCallback): FSWatcher {
const ccsDir = getCcsDir();
const watcher = chokidar.watch(
[
path.join(ccsDir, 'config.json'), // Legacy config
path.join(ccsDir, 'config.yaml'), // Unified config
path.join(ccsDir, '*.settings.json'),
path.join(ccsDir, 'profiles.json'),
path.join(ccsDir, 'cliproxy', 'sessions.json'), // Proxy session tracking
],
{
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 50,
},
}
);
watcher.on('change', (filePath) => {
const basename = path.basename(filePath);
let type: FileChangeEvent['type'];
if (basename === 'config.json' || basename === 'config.yaml') {
type = 'config-changed';
} else if (basename === 'profiles.json') {
type = 'profiles-changed';
} else if (basename === 'sessions.json') {
type = 'proxy-status-changed';
} else {
type = 'settings-changed';
}
onChange({
type,
path: filePath,
timestamp: Date.now(),
});
});
watcher.on('add', (filePath) => {
onChange({
type: 'config-changed',
path: filePath,
timestamp: Date.now(),
});
});
watcher.on('unlink', (filePath) => {
onChange({
type: 'config-changed',
path: filePath,
timestamp: Date.now(),
});
});
return watcher;
}