diff --git a/src/__tests__/globalConfig-defaults.test.ts b/src/__tests__/globalConfig-defaults.test.ts index 9bb3c8b..de544f0 100644 --- a/src/__tests__/globalConfig-defaults.test.ts +++ b/src/__tests__/globalConfig-defaults.test.ts @@ -580,184 +580,6 @@ describe('loadGlobalConfig', () => { }); }); - describe('deprecated migration: observability → logging', () => { - it('should migrate observability.provider_events to logging.providerEvents', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'observability:', - ' provider_events: true', - ].join('\n'), - 'utf-8', - ); - - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - try { - const config = loadGlobalConfig(); - expect(config.logging?.providerEvents).toBe(true); - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining('observability'), - ); - } finally { - warnSpy.mockRestore(); - } - }); - - it('should not overwrite explicit logging.provider_events with observability value', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'logging:', - ' provider_events: false', - 'observability:', - ' provider_events: true', - ].join('\n'), - 'utf-8', - ); - - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - try { - const config = loadGlobalConfig(); - expect(config.logging?.providerEvents).toBe(false); - } finally { - warnSpy.mockRestore(); - } - }); - - it('should emit deprecation warning when observability is present', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'observability:', - ' provider_events: false', - ].join('\n'), - 'utf-8', - ); - - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - try { - loadGlobalConfig(); - expect(warnSpy).toHaveBeenCalledTimes(1); - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining('Deprecated'), - ); - } finally { - warnSpy.mockRestore(); - } - }); - }); - - describe('deprecated migration: log_level → logging.level', () => { - it('should migrate log_level to logging.level', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'log_level: warn', - ].join('\n'), - 'utf-8', - ); - - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - try { - const config = loadGlobalConfig(); - expect(config.logging?.level).toBe('warn'); - expect(warnSpy).toHaveBeenCalled(); - } finally { - warnSpy.mockRestore(); - } - }); - - it('should prefer logging.level over legacy log_level', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'logging:', - ' level: info', - 'log_level: warn', - ].join('\n'), - 'utf-8', - ); - - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - try { - const config = loadGlobalConfig(); - expect(config.logging?.level).toBe('info'); - } finally { - warnSpy.mockRestore(); - } - }); - }); - - describe('logging.level → logLevel fallback', () => { - it('should use logging.level as logLevel fallback when legacy log_level is absent', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'logging:', - ' level: warn', - ].join('\n'), - 'utf-8', - ); - - invalidateGlobalConfigCache(); - const fallback = loadGlobalMigratedProjectLocalFallback(); - expect(fallback.logLevel).toBe('warn'); - }); - - it('should prefer logging.level over legacy log_level', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'log_level: debug', - 'logging:', - ' level: warn', - ].join('\n'), - 'utf-8', - ); - - invalidateGlobalConfigCache(); - const fallback = loadGlobalMigratedProjectLocalFallback(); - expect(fallback.logLevel).toBe('warn'); - }); - - it('should fall back to legacy log_level when logging.level is absent', () => { - const taktDir = join(testHomeDir, '.takt'); - mkdirSync(taktDir, { recursive: true }); - writeFileSync( - getGlobalConfigPath(), - [ - 'language: en', - 'log_level: debug', - ].join('\n'), - 'utf-8', - ); - - invalidateGlobalConfigCache(); - const fallback = loadGlobalMigratedProjectLocalFallback(); - expect(fallback.logLevel).toBe('debug'); - }); - }); it('should save and reload notification_sound_events config', () => { const taktDir = join(testHomeDir, '.takt'); diff --git a/src/__tests__/globalConfigLegacyMigration.test.ts b/src/__tests__/globalConfigLegacyMigration.test.ts deleted file mode 100644 index 41c593a..0000000 --- a/src/__tests__/globalConfigLegacyMigration.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { migrateDeprecatedGlobalConfigKeys } from '../infra/config/global/globalConfigLegacyMigration.js'; - -describe('migrateDeprecatedGlobalConfigKeys', () => { - it('should return migrated config without mutating input object', () => { - const rawConfig: Record = { - log_level: 'warn', - observability: { - provider_events: true, - }, - }; - - const originalSnapshot = JSON.parse(JSON.stringify(rawConfig)) as Record; - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - try { - const migrated = migrateDeprecatedGlobalConfigKeys(rawConfig); - expect(migrated.migratedLogLevel).toBe('warn'); - expect(migrated.migratedConfig).toEqual({ - logging: { - level: 'warn', - provider_events: true, - }, - }); - expect(rawConfig).toEqual(originalSnapshot); - } finally { - warnSpy.mockRestore(); - } - }); -}); diff --git a/src/infra/config/global/globalConfigCore.ts b/src/infra/config/global/globalConfigCore.ts index d530823..d795e79 100644 --- a/src/infra/config/global/globalConfigCore.ts +++ b/src/infra/config/global/globalConfigCore.ts @@ -19,10 +19,7 @@ import { removeMigratedProjectLocalKeys, type GlobalMigratedProjectLocalFallback, } from './globalMigratedProjectLocalFallback.js'; -import { - sanitizeConfigValue, - migrateDeprecatedGlobalConfigKeys, -} from './globalConfigLegacyMigration.js'; +import { sanitizeConfigValue } from './globalConfigLegacyMigration.js'; import { serializeGlobalConfig } from './globalConfigSerializer.js'; export { validateCliPath } from './cliPathValidator.js'; @@ -81,12 +78,8 @@ export class GlobalConfigManager { } applyGlobalConfigEnvOverrides(rawConfig); - const { migratedConfig, migratedLogLevel } = migrateDeprecatedGlobalConfigKeys(rawConfig); - const migratedProjectLocalFallback = extractMigratedProjectLocalFallback({ - ...migratedConfig, - ...(migratedLogLevel !== undefined ? { log_level: migratedLogLevel } : {}), - }); - const schemaInput = { ...migratedConfig }; + const migratedProjectLocalFallback = extractMigratedProjectLocalFallback(rawConfig); + const schemaInput = { ...rawConfig }; removeMigratedProjectLocalKeys(schemaInput); const parsed = GlobalConfigSchema.parse(schemaInput); diff --git a/src/infra/config/global/globalConfigLegacyMigration.ts b/src/infra/config/global/globalConfigLegacyMigration.ts index 192d100..daa00f1 100644 --- a/src/infra/config/global/globalConfigLegacyMigration.ts +++ b/src/infra/config/global/globalConfigLegacyMigration.ts @@ -1,10 +1,3 @@ -function getRecord(value: unknown): Record | undefined { - if (typeof value !== 'object' || value === null || Array.isArray(value)) { - return undefined; - } - return value as Record; -} - const FORBIDDEN_CONFIG_KEYS = new Set(['__proto__', 'prototype', 'constructor']); export function sanitizeConfigValue(value: unknown, path: string): unknown { @@ -12,10 +5,10 @@ export function sanitizeConfigValue(value: unknown, path: string): unknown { return value.map((item, index) => sanitizeConfigValue(item, `${path}[${index}]`)); } - const record = getRecord(value); - if (!record) { + if (typeof value !== 'object' || value === null) { return value; } + const record = value as Record; const sanitized: Record = {}; for (const [key, nestedValue] of Object.entries(record)) { @@ -27,73 +20,3 @@ export function sanitizeConfigValue(value: unknown, path: string): unknown { return sanitized; } -type LegacyGlobalConfigMigrationResult = { - migratedConfig: Record; - migratedLogLevel?: string; -}; - -export function migrateDeprecatedGlobalConfigKeys(rawConfig: Record): LegacyGlobalConfigMigrationResult { - const migratedConfig: Record = { ...rawConfig }; - const hasLegacyLogLevel = Object.prototype.hasOwnProperty.call(rawConfig, 'log_level'); - const legacyLogLevel = rawConfig.log_level; - const hasLegacyObservability = Object.prototype.hasOwnProperty.call(rawConfig, 'observability'); - const observability = getRecord(rawConfig.observability); - const initialLogging = getRecord(rawConfig.logging); - let migratedLogging = initialLogging ? { ...initialLogging } : undefined; - - if (hasLegacyObservability) { - console.warn('Deprecated: "observability" is deprecated. Use "logging" instead.'); - if (observability) { - const observabilityProviderEvents = observability.provider_events; - if (observabilityProviderEvents !== undefined) { - const hasExplicitProviderEvents = migratedLogging - ? Object.prototype.hasOwnProperty.call(migratedLogging, 'provider_events') - : false; - if (!hasExplicitProviderEvents) { - migratedLogging = { - ...(migratedLogging ?? {}), - provider_events: observabilityProviderEvents, - }; - } - } - } - } - - if (hasLegacyLogLevel) { - console.warn('Deprecated: "log_level" is deprecated. Use "logging.level" instead.'); - } - - const resolvedLoggingLevel = migratedLogging?.level; - const migratedLogLevel = typeof resolvedLoggingLevel === 'string' - ? resolvedLoggingLevel - : hasLegacyLogLevel && typeof legacyLogLevel === 'string' - ? legacyLogLevel - : undefined; - - if (migratedLogLevel !== undefined) { - const hasExplicitLevel = migratedLogging - ? Object.prototype.hasOwnProperty.call(migratedLogging, 'level') - : false; - if (!hasExplicitLevel) { - migratedLogging = { - ...(migratedLogging ?? {}), - level: migratedLogLevel, - }; - } - } - if (migratedLogging) { - migratedConfig.logging = migratedLogging; - } - - if (hasLegacyObservability) { - delete migratedConfig.observability; - } - if (hasLegacyLogLevel) { - delete migratedConfig.log_level; - } - - return { - migratedConfig, - migratedLogLevel, - }; -}