97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
/**
|
|
* Leaf state module for the remote-managed-settings sync cache.
|
|
*
|
|
* Split from syncCache.ts to break the settings.ts → syncCache.ts → auth.ts →
|
|
* settings.ts cycle. auth.ts sits inside the large settings SCC; importing it
|
|
* from settings.ts's own dependency chain pulls hundreds of modules into the
|
|
* eagerly-evaluated SCC at startup.
|
|
*
|
|
* This module imports only leaves (path, envUtils, file, json, types,
|
|
* settings/settingsCache — also a leaf, only type-imports validation). settings.ts
|
|
* reads the cache from here. syncCache.ts keeps isRemoteManagedSettingsEligible
|
|
* (the auth-touching part) and re-exports everything from here for callers that
|
|
* don't care about the cycle.
|
|
*
|
|
* Eligibility is a tri-state here: undefined (not yet determined — return
|
|
* null), false (ineligible — return null), true (proceed). managedEnv.ts
|
|
* calls isRemoteManagedSettingsEligible() just before the policySettings
|
|
* read — after userSettings/flagSettings env vars are applied, so the check
|
|
* sees config-provided CLAUDE_CODE_USE_BEDROCK/ANTHROPIC_BASE_URL. That call
|
|
* computes once and mirrors the result here via setEligibility(). Every
|
|
* subsequent read hits the cached bool instead of re-running the auth chain.
|
|
*/
|
|
|
|
import { join } from 'path'
|
|
import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
|
|
import { readFileSync } from '../../utils/fileRead.js'
|
|
import { stripBOM } from '../../utils/jsonRead.js'
|
|
import { resetSettingsCache } from '../../utils/settings/settingsCache.js'
|
|
import type { SettingsJson } from '../../utils/settings/types.js'
|
|
import { jsonParse } from '../../utils/slowOperations.js'
|
|
|
|
const SETTINGS_FILENAME = 'remote-settings.json'
|
|
|
|
let sessionCache: SettingsJson | null = null
|
|
let eligible: boolean | undefined
|
|
|
|
export function setSessionCache(value: SettingsJson | null): void {
|
|
sessionCache = value
|
|
}
|
|
|
|
export function resetSyncCache(): void {
|
|
sessionCache = null
|
|
eligible = undefined
|
|
}
|
|
|
|
export function setEligibility(v: boolean): boolean {
|
|
eligible = v
|
|
return v
|
|
}
|
|
|
|
export function getSettingsPath(): string {
|
|
return join(getClaudeConfigHomeDir(), SETTINGS_FILENAME)
|
|
}
|
|
|
|
// sync IO — settings pipeline is sync. fileRead and jsonRead are leaves;
|
|
// file.ts and json.ts both sit in the settings SCC.
|
|
function loadSettings(): SettingsJson | null {
|
|
try {
|
|
const content = readFileSync(getSettingsPath())
|
|
const data: unknown = jsonParse(stripBOM(content))
|
|
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
return null
|
|
}
|
|
return data as SettingsJson
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function getRemoteManagedSettingsSyncFromCache(): SettingsJson | null {
|
|
if (eligible !== true) return null
|
|
if (sessionCache) return sessionCache
|
|
const cachedSettings = loadSettings()
|
|
if (cachedSettings) {
|
|
sessionCache = cachedSettings
|
|
// Remote settings just became available for the first time. Any merged
|
|
// getSettings_DEPRECATED() result cached before this moment is missing
|
|
// the policySettings layer (the `eligible !== true` guard above returned
|
|
// null). Flush so the next merged read re-merges with this layer visible.
|
|
//
|
|
// Fires at most once: subsequent calls hit `if (sessionCache)` above.
|
|
// When called from loadSettingsFromDisk() (settings.ts:546), the merged
|
|
// cache is still null (setSessionSettingsCache runs at :732 after
|
|
// loadSettingsFromDisk returns) — no-op. The async-fetch arm (index.ts
|
|
// setSessionCache + notifyChange) already handles its own reset.
|
|
//
|
|
// gh-23085: isBridgeEnabled() at main.tsx Commander-definition time
|
|
// (before preAction → init() → isRemoteManagedSettingsEligible()) reached
|
|
// getSettings_DEPRECATED() at auth.ts:115. The try/catch in bridgeEnabled
|
|
// swallowed the later getGlobalConfig() throw, but the merged settings
|
|
// cache was already poisoned. See managedSettingsHeadless.int.test.ts.
|
|
resetSettingsCache()
|
|
return cachedSettings
|
|
}
|
|
return null
|
|
}
|