refactor: deprecated config マイグレーション処理を削除
log_level / observability の後方互換マイグレーションを完全削除。 logging キーに一本化し、レガシー変換コード・テストを除去。
This commit is contained in:
parent
cb3bc5e45e
commit
1cfae9f53b
@ -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', () => {
|
it('should save and reload notification_sound_events config', () => {
|
||||||
const taktDir = join(testHomeDir, '.takt');
|
const taktDir = join(testHomeDir, '.takt');
|
||||||
|
|||||||
@ -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<string, unknown> = {
|
|
||||||
log_level: 'warn',
|
|
||||||
observability: {
|
|
||||||
provider_events: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const originalSnapshot = JSON.parse(JSON.stringify(rawConfig)) as Record<string, unknown>;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -19,10 +19,7 @@ import {
|
|||||||
removeMigratedProjectLocalKeys,
|
removeMigratedProjectLocalKeys,
|
||||||
type GlobalMigratedProjectLocalFallback,
|
type GlobalMigratedProjectLocalFallback,
|
||||||
} from './globalMigratedProjectLocalFallback.js';
|
} from './globalMigratedProjectLocalFallback.js';
|
||||||
import {
|
import { sanitizeConfigValue } from './globalConfigLegacyMigration.js';
|
||||||
sanitizeConfigValue,
|
|
||||||
migrateDeprecatedGlobalConfigKeys,
|
|
||||||
} from './globalConfigLegacyMigration.js';
|
|
||||||
import { serializeGlobalConfig } from './globalConfigSerializer.js';
|
import { serializeGlobalConfig } from './globalConfigSerializer.js';
|
||||||
export { validateCliPath } from './cliPathValidator.js';
|
export { validateCliPath } from './cliPathValidator.js';
|
||||||
|
|
||||||
@ -81,12 +78,8 @@ export class GlobalConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyGlobalConfigEnvOverrides(rawConfig);
|
applyGlobalConfigEnvOverrides(rawConfig);
|
||||||
const { migratedConfig, migratedLogLevel } = migrateDeprecatedGlobalConfigKeys(rawConfig);
|
const migratedProjectLocalFallback = extractMigratedProjectLocalFallback(rawConfig);
|
||||||
const migratedProjectLocalFallback = extractMigratedProjectLocalFallback({
|
const schemaInput = { ...rawConfig };
|
||||||
...migratedConfig,
|
|
||||||
...(migratedLogLevel !== undefined ? { log_level: migratedLogLevel } : {}),
|
|
||||||
});
|
|
||||||
const schemaInput = { ...migratedConfig };
|
|
||||||
removeMigratedProjectLocalKeys(schemaInput);
|
removeMigratedProjectLocalKeys(schemaInput);
|
||||||
|
|
||||||
const parsed = GlobalConfigSchema.parse(schemaInput);
|
const parsed = GlobalConfigSchema.parse(schemaInput);
|
||||||
|
|||||||
@ -1,10 +1,3 @@
|
|||||||
function getRecord(value: unknown): Record<string, unknown> | undefined {
|
|
||||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return value as Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FORBIDDEN_CONFIG_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
const FORBIDDEN_CONFIG_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
||||||
|
|
||||||
export function sanitizeConfigValue(value: unknown, path: string): unknown {
|
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}]`));
|
return value.map((item, index) => sanitizeConfigValue(item, `${path}[${index}]`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = getRecord(value);
|
if (typeof value !== 'object' || value === null) {
|
||||||
if (!record) {
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
const record = value as Record<string, unknown>;
|
||||||
|
|
||||||
const sanitized: Record<string, unknown> = {};
|
const sanitized: Record<string, unknown> = {};
|
||||||
for (const [key, nestedValue] of Object.entries(record)) {
|
for (const [key, nestedValue] of Object.entries(record)) {
|
||||||
@ -27,73 +20,3 @@ export function sanitizeConfigValue(value: unknown, path: string): unknown {
|
|||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LegacyGlobalConfigMigrationResult = {
|
|
||||||
migratedConfig: Record<string, unknown>;
|
|
||||||
migratedLogLevel?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function migrateDeprecatedGlobalConfigKeys(rawConfig: Record<string, unknown>): LegacyGlobalConfigMigrationResult {
|
|
||||||
const migratedConfig: Record<string, unknown> = { ...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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user