host-config-export.ts
116 lines · scripts/host-config-export.ts at 3f79d81
1#!/usr/bin/env -S bun --no-env-file --config=/dev/null
2/**
3 * Standalone query CLI for host configs (list / get / detect / validate).
4 *
5 * NOT yet wired into ./setup — setup still hand-rolls its host lists (a
6 * known drift source; driving setup from this CLI is queued follow-up work).
7 * Behavior is pinned by test/host-config.test.ts.
8 *
9 * Usage: bun run scripts/host-config-export.ts <command> [args]
10 *
11 * Commands:
12 * list Print all host names, one per line
13 * get <host> <field> Print a single config field value
14 * detect Print names of hosts whose CLI binary is on PATH
15 * validate Validate all configs, exit 1 on error
16 *
17 * All output is shell-safe (single-quoted values, no eval needed).
18 */
20import { ALL_HOST_CONFIGS, getHostConfig, ALL_HOST_NAMES } from '../hosts/index';
21import { validateAllConfigs } from './host-config';
22import { RESOLVERS } from './resolvers';
23import { execSync } from 'child_process';
26function shellEscape(s: string): string {
27 return "'" + s.replace(/'/g, "'\\''") + "'";
28}
30const [command, ...args] = process.argv.slice(2);
32switch (command) {
33 case 'list':
34 for (const name of ALL_HOST_NAMES) {
35 console.log(name);
36 }
37 break;
39 case 'get': {
40 const [hostName, field] = args;
41 if (!hostName || !field) {
42 console.error('Usage: host-config-export.ts get <host> <field>');
43 process.exit(1);
44 }
45 const config = getHostConfig(hostName);
46 const value = (config as any)[field];
47 if (value === undefined) {
48 console.error(`Unknown field: ${field}`);
49 process.exit(1);
50 }
51 if (typeof value === 'string') {
52 console.log(value);
53 } else if (typeof value === 'boolean') {
54 console.log(value ? '1' : '0');
55 } else if (Array.isArray(value)) {
56 for (const item of value) {
57 console.log(typeof item === 'string' ? item : JSON.stringify(item));
58 }
59 } else {
60 console.log(JSON.stringify(value));
61 }
62 break;
63 }
65 case 'detect': {
66 for (const config of ALL_HOST_CONFIGS) {
67 const commands = [config.cliCommand, ...(config.cliAliases || [])];
68 for (const cmd of commands) {
69 try {
70 execSync(`command -v ${shellEscape(cmd)}`, { stdio: 'pipe' });
71 console.log(config.name);
72 break; // Found this host, move to next
73 } catch {
74 // Binary not found, try next alias
75 }
76 }
77 }
78 break;
79 }
81 case 'validate': {
82 const errors = validateAllConfigs(ALL_HOST_CONFIGS, new Set(Object.keys(RESOLVERS)));
83 if (errors.length > 0) {
84 for (const error of errors) {
85 console.error(`ERROR: ${error}`);
86 }
87 process.exit(1);
88 }
89 console.log(`All ${ALL_HOST_CONFIGS.length} configs valid`);
90 break;
91 }
93 case 'symlinks': {
94 const [hostName] = args;
95 if (!hostName) {
96 console.error('Usage: host-config-export.ts symlinks <host>');
97 process.exit(1);
98 }
99 const config = getHostConfig(hostName);
100 for (const link of config.runtimeRoot.globalSymlinks) {
101 console.log(link);
102 }
103 if (config.runtimeRoot.globalFiles) {
104 for (const [dir, files] of Object.entries(config.runtimeRoot.globalFiles)) {
105 for (const file of files) {
106 console.log(`${dir}/${file}`);
107 }
108 }
109 }
110 break;
111 }
113 default:
114 console.error('Usage: host-config-export.ts <list|get|detect|validate|symlinks> [args]');
115 process.exit(1);
116}