()
| 82 | * Initialize file watching |
| 83 | */ |
| 84 | export async function initialize(): Promise<void> { |
| 85 | if (getIsRemoteMode()) return |
| 86 | if (initialized || disposed) return |
| 87 | initialized = true |
| 88 | |
| 89 | // Start MDM poll for registry/plist changes (independent of filesystem watching) |
| 90 | startMdmPoll() |
| 91 | |
| 92 | // Register cleanup to properly dispose during graceful shutdown |
| 93 | registerCleanup(dispose) |
| 94 | |
| 95 | const { dirs, settingsFiles, dropInDir } = await getWatchTargets() |
| 96 | if (disposed) return // dispose() ran during the await |
| 97 | if (dirs.length === 0) return |
| 98 | |
| 99 | logForDebugging( |
| 100 | `Watching for changes in setting files ${[...settingsFiles].join(', ')}...${dropInDir ? ` and drop-in directory ${dropInDir}` : ''}`, |
| 101 | ) |
| 102 | |
| 103 | watcher = chokidar.watch(dirs, { |
| 104 | persistent: true, |
| 105 | ignoreInitial: true, |
| 106 | depth: 0, // Only watch immediate children, not subdirectories |
| 107 | awaitWriteFinish: { |
| 108 | stabilityThreshold: |
| 109 | testOverrides?.stabilityThreshold ?? FILE_STABILITY_THRESHOLD_MS, |
| 110 | pollInterval: |
| 111 | testOverrides?.pollInterval ?? FILE_STABILITY_POLL_INTERVAL_MS, |
| 112 | }, |
| 113 | ignored: (path, stats) => { |
| 114 | // Ignore special file types (sockets, FIFOs, devices) - they cannot be watched |
| 115 | // and will error with EOPNOTSUPP on macOS. |
| 116 | if (stats && !stats.isFile() && !stats.isDirectory()) return true |
| 117 | // Ignore .git directories |
| 118 | if (path.split(platformPath.sep).some(dir => dir === '.git')) return true |
| 119 | // Allow directories (chokidar needs them for directory-level watching) |
| 120 | // and paths without stats (chokidar's initial check before stat) |
| 121 | if (!stats || stats.isDirectory()) return false |
| 122 | // Only watch known settings files, ignore everything else in the directory |
| 123 | // Note: chokidar normalizes paths to forward slashes on Windows, so we |
| 124 | // normalize back to native format for comparison |
| 125 | const normalized = platformPath.normalize(path) |
| 126 | if (settingsFiles.has(normalized)) return false |
| 127 | // Also accept .json files inside the managed-settings.d/ drop-in directory |
| 128 | if ( |
| 129 | dropInDir && |
| 130 | normalized.startsWith(dropInDir + platformPath.sep) && |
| 131 | normalized.endsWith('.json') |
| 132 | ) { |
| 133 | return false |
| 134 | } |
| 135 | return true |
| 136 | }, |
| 137 | // Additional options for stability |
| 138 | ignorePermissionErrors: true, |
| 139 | usePolling: false, // Use native file system events |
| 140 | atomic: true, // Handle atomic writes better |
| 141 | }) |
nothing calls this directly
no test coverage detected