refactor: 共有ノーマライザを configNormalizers.ts に抽出
globalConfig.ts と projectConfig.ts に重複していた normalizeProviderProfiles / denormalizeProviderProfiles / normalizePieceOverrides / denormalizePieceOverrides を configNormalizers.ts に集約した。
This commit is contained in:
parent
ecf0b02684
commit
8aa79d909c
79
src/infra/config/configNormalizers.ts
Normal file
79
src/infra/config/configNormalizers.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Shared normalizer/denormalizer functions for config snake_case <-> camelCase conversion.
|
||||||
|
*
|
||||||
|
* Used by both globalConfig.ts and projectConfig.ts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ProviderPermissionProfiles } from '../../core/models/provider-profiles.js';
|
||||||
|
import type { PieceOverrides } from '../../core/models/persisted-global-config.js';
|
||||||
|
|
||||||
|
export function normalizeProviderProfiles(
|
||||||
|
raw: Record<string, { default_permission_mode: unknown; movement_permission_overrides?: Record<string, unknown> }> | undefined,
|
||||||
|
): ProviderPermissionProfiles | undefined {
|
||||||
|
if (!raw) return undefined;
|
||||||
|
|
||||||
|
const entries = Object.entries(raw).map(([provider, profile]) => [provider, {
|
||||||
|
defaultPermissionMode: profile.default_permission_mode,
|
||||||
|
movementPermissionOverrides: profile.movement_permission_overrides,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return Object.fromEntries(entries) as ProviderPermissionProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function denormalizeProviderProfiles(
|
||||||
|
profiles: ProviderPermissionProfiles | undefined,
|
||||||
|
): Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }> | undefined {
|
||||||
|
if (!profiles) return undefined;
|
||||||
|
const entries = Object.entries(profiles);
|
||||||
|
if (entries.length === 0) return undefined;
|
||||||
|
|
||||||
|
return Object.fromEntries(entries.map(([provider, profile]) => [provider, {
|
||||||
|
default_permission_mode: profile.defaultPermissionMode,
|
||||||
|
...(profile.movementPermissionOverrides
|
||||||
|
? { movement_permission_overrides: profile.movementPermissionOverrides }
|
||||||
|
: {}),
|
||||||
|
}])) as Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePieceOverrides(
|
||||||
|
raw: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined,
|
||||||
|
): PieceOverrides | undefined {
|
||||||
|
if (!raw) return undefined;
|
||||||
|
return {
|
||||||
|
qualityGates: raw.quality_gates,
|
||||||
|
qualityGatesEditOnly: raw.quality_gates_edit_only,
|
||||||
|
movements: raw.movements
|
||||||
|
? Object.fromEntries(
|
||||||
|
Object.entries(raw.movements).map(([name, override]) => [
|
||||||
|
name,
|
||||||
|
{ qualityGates: override.quality_gates },
|
||||||
|
])
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function denormalizePieceOverrides(
|
||||||
|
overrides: PieceOverrides | undefined,
|
||||||
|
): { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined {
|
||||||
|
if (!overrides) return undefined;
|
||||||
|
const result: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } = {};
|
||||||
|
if (overrides.qualityGates !== undefined) {
|
||||||
|
result.quality_gates = overrides.qualityGates;
|
||||||
|
}
|
||||||
|
if (overrides.qualityGatesEditOnly !== undefined) {
|
||||||
|
result.quality_gates_edit_only = overrides.qualityGatesEditOnly;
|
||||||
|
}
|
||||||
|
if (overrides.movements) {
|
||||||
|
result.movements = Object.fromEntries(
|
||||||
|
Object.entries(overrides.movements).map(([name, override]) => {
|
||||||
|
const movementOverride: { quality_gates?: string[] } = {};
|
||||||
|
if (override.qualityGates !== undefined) {
|
||||||
|
movementOverride.quality_gates = override.qualityGates;
|
||||||
|
}
|
||||||
|
return [name, movementOverride];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Object.keys(result).length > 0 ? result : undefined;
|
||||||
|
}
|
||||||
@ -10,12 +10,17 @@ import { isAbsolute } from 'node:path';
|
|||||||
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
||||||
import { GlobalConfigSchema } from '../../../core/models/index.js';
|
import { GlobalConfigSchema } from '../../../core/models/index.js';
|
||||||
import type { Language } from '../../../core/models/index.js';
|
import type { Language } from '../../../core/models/index.js';
|
||||||
import type { PersistedGlobalConfig, PersonaProviderEntry, PieceOverrides } from '../../../core/models/persisted-global-config.js';
|
import type { PersistedGlobalConfig, PersonaProviderEntry } from '../../../core/models/persisted-global-config.js';
|
||||||
import type { ProviderPermissionProfiles } from '../../../core/models/provider-profiles.js';
|
|
||||||
import {
|
import {
|
||||||
normalizeConfigProviderReference,
|
normalizeConfigProviderReference,
|
||||||
type ConfigProviderReference,
|
type ConfigProviderReference,
|
||||||
} from '../providerReference.js';
|
} from '../providerReference.js';
|
||||||
|
import {
|
||||||
|
normalizeProviderProfiles,
|
||||||
|
denormalizeProviderProfiles,
|
||||||
|
normalizePieceOverrides,
|
||||||
|
denormalizePieceOverrides,
|
||||||
|
} from '../configNormalizers.js';
|
||||||
import { getGlobalConfigPath } from '../paths.js';
|
import { getGlobalConfigPath } from '../paths.js';
|
||||||
import { DEFAULT_LANGUAGE } from '../../../shared/constants.js';
|
import { DEFAULT_LANGUAGE } from '../../../shared/constants.js';
|
||||||
import { parseProviderModel } from '../../../shared/utils/providerModel.js';
|
import { parseProviderModel } from '../../../shared/utils/providerModel.js';
|
||||||
@ -112,79 +117,6 @@ function normalizePersonaProviders(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeProviderProfiles(
|
|
||||||
raw: Record<string, { default_permission_mode: unknown; movement_permission_overrides?: Record<string, unknown> }> | undefined,
|
|
||||||
): ProviderPermissionProfiles | undefined {
|
|
||||||
if (!raw) return undefined;
|
|
||||||
|
|
||||||
const entries = Object.entries(raw).map(([provider, profile]) => [provider, {
|
|
||||||
defaultPermissionMode: profile.default_permission_mode,
|
|
||||||
movementPermissionOverrides: profile.movement_permission_overrides,
|
|
||||||
}]);
|
|
||||||
|
|
||||||
return Object.fromEntries(entries) as ProviderPermissionProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function denormalizeProviderProfiles(
|
|
||||||
profiles: ProviderPermissionProfiles | undefined,
|
|
||||||
): Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }> | undefined {
|
|
||||||
if (!profiles) return undefined;
|
|
||||||
const entries = Object.entries(profiles);
|
|
||||||
if (entries.length === 0) return undefined;
|
|
||||||
|
|
||||||
return Object.fromEntries(entries.map(([provider, profile]) => [provider, {
|
|
||||||
default_permission_mode: profile.defaultPermissionMode,
|
|
||||||
...(profile.movementPermissionOverrides
|
|
||||||
? { movement_permission_overrides: profile.movementPermissionOverrides }
|
|
||||||
: {}),
|
|
||||||
}])) as Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Normalize piece_overrides from snake_case (YAML) to camelCase (internal) */
|
|
||||||
function normalizePieceOverrides(
|
|
||||||
raw: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined,
|
|
||||||
): PieceOverrides | undefined {
|
|
||||||
if (!raw) return undefined;
|
|
||||||
return {
|
|
||||||
qualityGates: raw.quality_gates,
|
|
||||||
qualityGatesEditOnly: raw.quality_gates_edit_only,
|
|
||||||
movements: raw.movements
|
|
||||||
? Object.fromEntries(
|
|
||||||
Object.entries(raw.movements).map(([name, override]) => [
|
|
||||||
name,
|
|
||||||
{ qualityGates: override.quality_gates },
|
|
||||||
])
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Denormalize piece_overrides from camelCase (internal) to snake_case (YAML) */
|
|
||||||
function denormalizePieceOverrides(
|
|
||||||
overrides: PieceOverrides | undefined,
|
|
||||||
): { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined {
|
|
||||||
if (!overrides) return undefined;
|
|
||||||
const result: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } = {};
|
|
||||||
if (overrides.qualityGates !== undefined) {
|
|
||||||
result.quality_gates = overrides.qualityGates;
|
|
||||||
}
|
|
||||||
if (overrides.qualityGatesEditOnly !== undefined) {
|
|
||||||
result.quality_gates_edit_only = overrides.qualityGatesEditOnly;
|
|
||||||
}
|
|
||||||
if (overrides.movements) {
|
|
||||||
result.movements = Object.fromEntries(
|
|
||||||
Object.entries(overrides.movements).map(([name, override]) => {
|
|
||||||
const movementOverride: { quality_gates?: string[] } = {};
|
|
||||||
if (override.qualityGates !== undefined) {
|
|
||||||
movementOverride.quality_gates = override.qualityGates;
|
|
||||||
}
|
|
||||||
return [name, movementOverride];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Object.keys(result).length > 0 ? result : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages global configuration loading and caching.
|
* Manages global configuration loading and caching.
|
||||||
* Singleton — use GlobalConfigManager.getInstance().
|
* Singleton — use GlobalConfigManager.getInstance().
|
||||||
|
|||||||
@ -10,13 +10,18 @@ import { parse, stringify } from 'yaml';
|
|||||||
import { ProjectConfigSchema } from '../../../core/models/index.js';
|
import { ProjectConfigSchema } from '../../../core/models/index.js';
|
||||||
import { copyProjectResourcesToDir } from '../../resources/index.js';
|
import { copyProjectResourcesToDir } from '../../resources/index.js';
|
||||||
import type { ProjectLocalConfig } from '../types.js';
|
import type { ProjectLocalConfig } from '../types.js';
|
||||||
import type { ProviderPermissionProfiles } from '../../../core/models/provider-profiles.js';
|
import type { AnalyticsConfig, SubmoduleSelection } from '../../../core/models/persisted-global-config.js';
|
||||||
import type { AnalyticsConfig, PieceOverrides, SubmoduleSelection } from '../../../core/models/persisted-global-config.js';
|
|
||||||
import { applyProjectConfigEnvOverrides } from '../env/config-env-overrides.js';
|
import { applyProjectConfigEnvOverrides } from '../env/config-env-overrides.js';
|
||||||
import {
|
import {
|
||||||
normalizeConfigProviderReference,
|
normalizeConfigProviderReference,
|
||||||
type ConfigProviderReference,
|
type ConfigProviderReference,
|
||||||
} from '../providerReference.js';
|
} from '../providerReference.js';
|
||||||
|
import {
|
||||||
|
normalizeProviderProfiles,
|
||||||
|
denormalizeProviderProfiles,
|
||||||
|
normalizePieceOverrides,
|
||||||
|
denormalizePieceOverrides,
|
||||||
|
} from '../configNormalizers.js';
|
||||||
import { invalidateResolvedConfigCache } from '../resolutionCache.js';
|
import { invalidateResolvedConfigCache } from '../resolutionCache.js';
|
||||||
|
|
||||||
export type { ProjectLocalConfig } from '../types.js';
|
export type { ProjectLocalConfig } from '../types.js';
|
||||||
@ -86,28 +91,6 @@ function getConfigPath(projectDir: string): string {
|
|||||||
return join(getConfigDir(projectDir), 'config.yaml');
|
return join(getConfigDir(projectDir), 'config.yaml');
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeProviderProfiles(raw: Record<string, { default_permission_mode: unknown; movement_permission_overrides?: Record<string, unknown> }> | undefined): ProviderPermissionProfiles | undefined {
|
|
||||||
if (!raw) return undefined;
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(raw).map(([provider, profile]) => [provider, {
|
|
||||||
defaultPermissionMode: profile.default_permission_mode,
|
|
||||||
movementPermissionOverrides: profile.movement_permission_overrides,
|
|
||||||
}]),
|
|
||||||
) as ProviderPermissionProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function denormalizeProviderProfiles(profiles: ProviderPermissionProfiles | undefined): Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }> | undefined {
|
|
||||||
if (!profiles) return undefined;
|
|
||||||
const entries = Object.entries(profiles);
|
|
||||||
if (entries.length === 0) return undefined;
|
|
||||||
return Object.fromEntries(entries.map(([provider, profile]) => [provider, {
|
|
||||||
default_permission_mode: profile.defaultPermissionMode,
|
|
||||||
...(profile.movementPermissionOverrides
|
|
||||||
? { movement_permission_overrides: profile.movementPermissionOverrides }
|
|
||||||
: {}),
|
|
||||||
}])) as Record<string, { default_permission_mode: string; movement_permission_overrides?: Record<string, string> }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeAnalytics(raw: Record<string, unknown> | undefined): AnalyticsConfig | undefined {
|
function normalizeAnalytics(raw: Record<string, unknown> | undefined): AnalyticsConfig | undefined {
|
||||||
if (!raw) return undefined;
|
if (!raw) return undefined;
|
||||||
const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : undefined;
|
const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : undefined;
|
||||||
@ -133,51 +116,6 @@ function denormalizeAnalytics(config: AnalyticsConfig | undefined): Record<strin
|
|||||||
return Object.keys(raw).length > 0 ? raw : undefined;
|
return Object.keys(raw).length > 0 ? raw : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Normalize piece_overrides from snake_case (YAML) to camelCase (internal) */
|
|
||||||
function normalizePieceOverrides(
|
|
||||||
raw: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined,
|
|
||||||
): PieceOverrides | undefined {
|
|
||||||
if (!raw) return undefined;
|
|
||||||
return {
|
|
||||||
qualityGates: raw.quality_gates,
|
|
||||||
qualityGatesEditOnly: raw.quality_gates_edit_only,
|
|
||||||
movements: raw.movements
|
|
||||||
? Object.fromEntries(
|
|
||||||
Object.entries(raw.movements).map(([name, override]) => [
|
|
||||||
name,
|
|
||||||
{ qualityGates: override.quality_gates },
|
|
||||||
])
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Denormalize piece_overrides from camelCase (internal) to snake_case (YAML) */
|
|
||||||
function denormalizePieceOverrides(
|
|
||||||
overrides: PieceOverrides | undefined,
|
|
||||||
): { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } | undefined {
|
|
||||||
if (!overrides) return undefined;
|
|
||||||
const result: { quality_gates?: string[]; quality_gates_edit_only?: boolean; movements?: Record<string, { quality_gates?: string[] }> } = {};
|
|
||||||
if (overrides.qualityGates !== undefined) {
|
|
||||||
result.quality_gates = overrides.qualityGates;
|
|
||||||
}
|
|
||||||
if (overrides.qualityGatesEditOnly !== undefined) {
|
|
||||||
result.quality_gates_edit_only = overrides.qualityGatesEditOnly;
|
|
||||||
}
|
|
||||||
if (overrides.movements) {
|
|
||||||
result.movements = Object.fromEntries(
|
|
||||||
Object.entries(overrides.movements).map(([name, override]) => {
|
|
||||||
const movementOverride: { quality_gates?: string[] } = {};
|
|
||||||
if (override.qualityGates !== undefined) {
|
|
||||||
movementOverride.quality_gates = override.qualityGates;
|
|
||||||
}
|
|
||||||
return [name, movementOverride];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Object.keys(result).length > 0 ? result : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load project configuration from .takt/config.yaml
|
* Load project configuration from .takt/config.yaml
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user