Spaces:
Runtime error
Runtime error
Aryan Mishra
feat: initial commit - Multilingual ABSA project setup with 6 phases, 36 requirements
a6b96c2 | ; | |
| /** | |
| * Configuration Module β legacy-key normalization, defaults merge, and explicit | |
| * on-disk migration. Pure normalization primitives consumed by config-loader.cjs | |
| * and config-schema.cjs. `loadConfig` was extracted to config-loader.cjs per | |
| * ADR-857 phase 2e (#885) and removed from this module per #893. | |
| * | |
| * ADR-457 build-at-publish: the hand-written bin/lib/configuration.cjs collapsed | |
| * to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour | |
| * from the prior hand-written .cjs; only types are added. | |
| */ | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.DYNAMIC_KEY_PATTERNS = exports.RUNTIME_STATE_KEYS = exports.VALID_CONFIG_KEYS = exports.CONFIG_DEFAULTS = void 0; | |
| exports.normalizeLegacyKeys = normalizeLegacyKeys; | |
| exports.mergeDefaults = mergeDefaults; | |
| exports.migrateOnDisk = migrateOnDisk; | |
| const node_fs_1 = require("node:fs"); | |
| const node_path_1 = require("node:path"); | |
| // In .cts (CommonJS output) files, `require` is available as a global. | |
| const _require = require; | |
| // βββ Manifest requires βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function loadConfigurationManifest(fileName) { | |
| const candidates = [ | |
| // Installed runtime layout: gsd-core/bin/shared/*.manifest.json | |
| (0, node_path_1.join)(__dirname, '..', 'shared', fileName), | |
| ]; | |
| let lastErr = null; | |
| for (const candidate of candidates) { | |
| try { | |
| return _require(candidate); | |
| } | |
| catch (err) { | |
| const e = err; | |
| const isMissingCandidate = e && e.code === 'MODULE_NOT_FOUND' && String(e.message || '').includes(candidate); | |
| if (!isMissingCandidate) | |
| throw err; | |
| lastErr = e; | |
| } | |
| } | |
| throw new Error(`${fileName} not found. Tried:\n${candidates.map((p) => ` ${p}`).join('\n')}\nLast error: ${lastErr?.message}`); | |
| } | |
| const CONFIG_DEFAULTS = loadConfigurationManifest('config-defaults.manifest.json'); | |
| exports.CONFIG_DEFAULTS = CONFIG_DEFAULTS; | |
| const SCHEMA_MANIFEST = loadConfigurationManifest('config-schema.manifest.json'); | |
| const VALID_CONFIG_KEYS = new Set(SCHEMA_MANIFEST.validKeys); | |
| exports.VALID_CONFIG_KEYS = VALID_CONFIG_KEYS; | |
| const RUNTIME_STATE_KEYS = new Set(SCHEMA_MANIFEST.runtimeStateKeys); | |
| exports.RUNTIME_STATE_KEYS = RUNTIME_STATE_KEYS; | |
| const DYNAMIC_KEY_PATTERNS = SCHEMA_MANIFEST.dynamicKeyPatterns.map((p) => { | |
| const pattern = new RegExp(p.source); | |
| return { | |
| ...p, | |
| test: (key) => { | |
| pattern.lastIndex = 0; | |
| return pattern.test(key); | |
| }, | |
| }; | |
| }); | |
| exports.DYNAMIC_KEY_PATTERNS = DYNAMIC_KEY_PATTERNS; | |
| // βββ Depth β Granularity mapping βββββββββββββββββββββββββββββββββββββββββββββ | |
| const DEPTH_TO_GRANULARITY = { | |
| quick: 'coarse', | |
| standard: 'standard', | |
| comprehensive: 'fine', | |
| }; | |
| // βββ Internal helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function planningDir(cwd, workstream) { | |
| if (!workstream) | |
| return (0, node_path_1.join)(cwd, '.planning'); | |
| return (0, node_path_1.join)(cwd, '.planning', 'workstreams', workstream); | |
| } | |
| function detectSubRepos(cwd) { | |
| const results = []; | |
| try { | |
| const entries = (0, node_fs_1.readdirSync)(cwd, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| if (!entry.isDirectory()) | |
| continue; | |
| if (entry.name.startsWith('.') || entry.name === 'node_modules') | |
| continue; | |
| const gitPath = (0, node_path_1.join)(cwd, entry.name, '.git'); | |
| try { | |
| if ((0, node_fs_1.existsSync)(gitPath)) { | |
| results.push(entry.name); | |
| } | |
| } | |
| catch { /* ignore */ } | |
| } | |
| } | |
| catch { /* ignore */ } | |
| return results.sort(); | |
| } | |
| function deepMergeConfig(base, overlay) { | |
| const result = { ...base }; | |
| for (const key of Object.keys(overlay)) { | |
| const ov = overlay[key]; | |
| if (ov !== null && ov !== undefined && typeof ov === 'object' && !Array.isArray(ov)) { | |
| const bv = base[key]; | |
| if (bv !== null && bv !== undefined && typeof bv === 'object' && !Array.isArray(bv)) { | |
| result[key] = deepMergeConfig(bv, ov); | |
| } | |
| else { | |
| result[key] = deepMergeConfig({}, ov); | |
| } | |
| } | |
| else { | |
| result[key] = ov; | |
| } | |
| } | |
| return result; | |
| } | |
| // βββ Exported functions βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function normalizeLegacyKeys(parsed) { | |
| const result = { ...parsed }; | |
| const normalizations = []; | |
| // 1. branching_strategy β git.branching_strategy | |
| if (Object.prototype.hasOwnProperty.call(result, 'branching_strategy')) { | |
| const value = result['branching_strategy']; | |
| const git = (result['git'] ?? {}); | |
| if (git['branching_strategy'] === undefined) { | |
| result['git'] = { ...git, branching_strategy: value }; | |
| } | |
| else { | |
| // canonical nested wins β just delete the stale top-level | |
| result['git'] = { ...git }; | |
| } | |
| delete result['branching_strategy']; | |
| normalizations.push({ from: 'branching_strategy', to: 'git.branching_strategy', value }); | |
| } | |
| // 2. top-level sub_repos β planning.sub_repos | |
| if (Object.prototype.hasOwnProperty.call(result, 'sub_repos')) { | |
| const value = result['sub_repos']; | |
| const planning = (result['planning'] ?? {}); | |
| if (planning['sub_repos'] === undefined) { | |
| result['planning'] = { ...planning, sub_repos: value }; | |
| } | |
| else { | |
| // canonical nested wins β just drop the stale top-level | |
| result['planning'] = { ...planning }; | |
| } | |
| delete result['sub_repos']; | |
| normalizations.push({ from: 'sub_repos', to: 'planning.sub_repos', value }); | |
| } | |
| // 3. multiRepo: true β marker (filesystem detection deferred to migrateOnDisk / caller) | |
| if (result['multiRepo'] === true) { | |
| delete result['multiRepo']; | |
| normalizations.push({ from: 'multiRepo', to: 'planning.sub_repos', value: true, requiresFilesystem: true }); | |
| } | |
| // 4. top-level depth β granularity | |
| if (Object.prototype.hasOwnProperty.call(result, 'depth') && !Object.prototype.hasOwnProperty.call(result, 'granularity')) { | |
| const rawDepth = result['depth']; | |
| const mapped = DEPTH_TO_GRANULARITY[rawDepth] ?? rawDepth; | |
| result['granularity'] = mapped; | |
| delete result['depth']; | |
| normalizations.push({ from: 'depth', to: 'granularity', value: mapped }); | |
| } | |
| return { parsed: result, normalizations }; | |
| } | |
| function mergeDefaults(parsed) { | |
| // Start with a deep clone of defaults, then overlay parsed | |
| const defaults = structuredClone(CONFIG_DEFAULTS); | |
| return deepMergeConfig(defaults, parsed); | |
| } | |
| function migrateOnDisk(cwd, workstream) { | |
| const configPath = (0, node_path_1.join)(planningDir(cwd, workstream), 'config.json'); | |
| let raw; | |
| try { | |
| raw = (0, node_fs_1.readFileSync)(configPath, 'utf-8'); | |
| } | |
| catch { | |
| // File missing β nothing to migrate | |
| return { migrated: false, normalizations: [], wrote: null }; | |
| } | |
| const trimmed = raw.trim(); | |
| if (trimmed === '') { | |
| return { migrated: false, normalizations: [], wrote: null }; | |
| } | |
| let parsed; | |
| try { | |
| parsed = JSON.parse(trimmed); | |
| } | |
| catch { | |
| // Malformed β can't migrate | |
| return { migrated: false, normalizations: [], wrote: null }; | |
| } | |
| const { parsed: normalized, normalizations } = normalizeLegacyKeys(parsed); | |
| if (normalizations.length === 0) { | |
| return { migrated: false, normalizations: [], wrote: null }; | |
| } | |
| // Resolve multiRepo filesystem detection | |
| const result = { ...normalized }; | |
| for (const norm of normalizations) { | |
| if (norm.requiresFilesystem) { | |
| const detected = detectSubRepos(cwd); | |
| if (detected.length > 0) { | |
| const planning = (result['planning'] ?? {}); | |
| result['planning'] = { ...planning, sub_repos: detected, commit_docs: false }; | |
| } | |
| } | |
| } | |
| try { | |
| (0, node_fs_1.writeFileSync)(configPath, JSON.stringify(result, null, 2)); | |
| } | |
| catch (err) { | |
| const msg = err instanceof Error ? err.message : String(err); | |
| throw new Error(`Failed to write migrated config at ${configPath}: ${msg}`); | |
| } | |
| return { migrated: true, normalizations, wrote: configPath }; | |
| } | |