|
| 1 | +import path from 'node:path'; |
| 2 | +import { |
| 3 | + clearConfigCache, |
| 4 | + loadConfig, |
| 5 | + loadConfigWithProvenance, |
| 6 | + resolveUserConfigPath, |
| 7 | +} from '../../infrastructure/config.js'; |
| 8 | +import { |
| 9 | + getUserConfigConsent, |
| 10 | + listUserConfigConsent, |
| 11 | + REGISTRY_PATH, |
| 12 | + setUserConfigConsent, |
| 13 | +} from '../../infrastructure/registry.js'; |
| 14 | +import type { CommandDefinition } from '../types.js'; |
| 15 | + |
| 16 | +export const command: CommandDefinition = { |
| 17 | + name: 'config', |
| 18 | + description: 'Show or manage codegraph configuration (project + user-level global config)', |
| 19 | + options: [ |
| 20 | + ['-j, --json', 'Output as JSON'], |
| 21 | + ['--explain', 'Show per-key provenance (default / user / project / env)'], |
| 22 | + ['--enable-global', 'Record consent to apply the global config to this repo'], |
| 23 | + ['--disable-global', 'Record consent to skip the global config for this repo'], |
| 24 | + ['--list-global', 'List all repos with a recorded consent decision'], |
| 25 | + ], |
| 26 | + execute(_args, opts, ctx) { |
| 27 | + const rootDir = path.resolve('.'); |
| 28 | + |
| 29 | + // ── Consent management ───────────────────────────────────────────── |
| 30 | + |
| 31 | + if (opts.enableGlobal) { |
| 32 | + setUserConfigConsent(rootDir, 'enabled'); |
| 33 | + clearConfigCache(); |
| 34 | + const globalPath = resolveUserConfigPath(); |
| 35 | + if (!globalPath) { |
| 36 | + process.stderr.write( |
| 37 | + `Consent recorded: "enabled" for ${rootDir}\n` + |
| 38 | + `Note: no global config file found. Create one at ~/.config/codegraph/config.json\n`, |
| 39 | + ); |
| 40 | + } else { |
| 41 | + process.stderr.write( |
| 42 | + `Consent recorded: "enabled" for ${rootDir}\n` + `Global config: ${globalPath}\n`, |
| 43 | + ); |
| 44 | + } |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + if (opts.disableGlobal) { |
| 49 | + setUserConfigConsent(rootDir, 'disabled'); |
| 50 | + clearConfigCache(); |
| 51 | + process.stderr.write(`Consent recorded: "disabled" for ${rootDir}\n`); |
| 52 | + return; |
| 53 | + } |
| 54 | + |
| 55 | + if (opts.listGlobal) { |
| 56 | + const entries = listUserConfigConsent(REGISTRY_PATH); |
| 57 | + if (opts.json) { |
| 58 | + process.stdout.write(`${JSON.stringify(entries, null, 2)}\n`); |
| 59 | + return; |
| 60 | + } |
| 61 | + if (entries.length === 0) { |
| 62 | + process.stdout.write('No repos have a recorded global-config consent decision.\n'); |
| 63 | + return; |
| 64 | + } |
| 65 | + process.stdout.write('Global config consent decisions:\n\n'); |
| 66 | + for (const { path: p, decision } of entries) { |
| 67 | + process.stdout.write( |
| 68 | + ` ${decision === 'enabled' ? '✔' : '✘'} ${decision.padEnd(8)} ${p}\n`, |
| 69 | + ); |
| 70 | + } |
| 71 | + return; |
| 72 | + } |
| 73 | + |
| 74 | + // ── Explain mode ─────────────────────────────────────────────────── |
| 75 | + |
| 76 | + if (opts.explain) { |
| 77 | + const { config, provenance, appliedGlobalPath, consentDecision } = loadConfigWithProvenance( |
| 78 | + rootDir, |
| 79 | + { |
| 80 | + userConfig: ctx.program.opts().userConfig, |
| 81 | + }, |
| 82 | + ); |
| 83 | + const globalPath = resolveUserConfigPath(); |
| 84 | + const consent = getUserConfigConsent(rootDir); |
| 85 | + |
| 86 | + if (opts.json) { |
| 87 | + process.stdout.write( |
| 88 | + `${JSON.stringify( |
| 89 | + { |
| 90 | + config, |
| 91 | + provenance, |
| 92 | + appliedGlobalPath, |
| 93 | + globalFilePath: globalPath, |
| 94 | + consentDecision: consentDecision ?? consent ?? 'undecided', |
| 95 | + }, |
| 96 | + null, |
| 97 | + 2, |
| 98 | + )}\n`, |
| 99 | + ); |
| 100 | + return; |
| 101 | + } |
| 102 | + |
| 103 | + // Human-readable explain output |
| 104 | + process.stdout.write('=== Codegraph config provenance ===\n\n'); |
| 105 | + |
| 106 | + const consentStr = consentDecision ?? consent ?? 'undecided'; |
| 107 | + process.stdout.write(`Global config file : ${globalPath ?? '(none found)'}\n`); |
| 108 | + process.stdout.write(`Applied this run : ${appliedGlobalPath ? 'yes' : 'no'}\n`); |
| 109 | + process.stdout.write(`Consent for repo : ${consentStr}\n`); |
| 110 | + process.stdout.write( |
| 111 | + ` (change with \`codegraph config --enable-global\` or \`--disable-global\`)\n`, |
| 112 | + ); |
| 113 | + |
| 114 | + if (!globalPath) { |
| 115 | + process.stdout.write( |
| 116 | + `\nDiscovery hint: create a global config at ~/.config/codegraph/config.json\n` + |
| 117 | + `then run \`codegraph config --enable-global\` in repos where you want it applied.\n`, |
| 118 | + ); |
| 119 | + } else if (!appliedGlobalPath) { |
| 120 | + process.stdout.write( |
| 121 | + `\nDiscovery hint: global config exists but is not applied to this repo.\n` + |
| 122 | + `Run \`codegraph config --enable-global\` to enable it here.\n`, |
| 123 | + ); |
| 124 | + } |
| 125 | + |
| 126 | + process.stdout.write('\n--- Per-key provenance ---\n\n'); |
| 127 | + const provenanceEntries = Object.entries(provenance).sort(([a], [b]) => a.localeCompare(b)); |
| 128 | + for (const [key, source] of provenanceEntries) { |
| 129 | + process.stdout.write(` ${source.padEnd(8)} ${key}\n`); |
| 130 | + } |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + // ── Default: print effective config ──────────────────────────────── |
| 135 | + |
| 136 | + const globalPath = resolveUserConfigPath(); |
| 137 | + const consent = getUserConfigConsent(rootDir); |
| 138 | + const config = loadConfig(rootDir, { userConfig: ctx.program.opts().userConfig }); |
| 139 | + |
| 140 | + // Print effective config — always JSON; discovery hint only in non-JSON mode |
| 141 | + process.stdout.write(`${JSON.stringify(config, null, 2)}\n`); |
| 142 | + |
| 143 | + if (!opts.json && globalPath && !consent) { |
| 144 | + process.stderr.write( |
| 145 | + `\nℹ Global config found at ${globalPath} — not applied to this repo.\n` + |
| 146 | + ` Run \`codegraph config --enable-global\` to opt in, or\n` + |
| 147 | + ` \`codegraph config --disable-global\` to dismiss this notice.\n`, |
| 148 | + ); |
| 149 | + } |
| 150 | + }, |
| 151 | +}; |
0 commit comments