/** * lib/appsettings.ts — Resolve the generated SmartStack app's DB connection string * from appsettings*.json, for skill CLIs (run via `npx tsx`, with no access to the * CLI's own `src/lib`). Mirrors the proven parse logic of `src/lib/appsettings.ts`. */ import { existsSync, readFileSync } from 'node:fs' import { join } from 'node:path' export interface ParsedConnection { server: string database: string useWindowsAuth: boolean user: string | null password: string | null trustServerCertificate: boolean raw: string } function readJsonSafe(filePath: string): Record | null { try { if (!existsSync(filePath)) return null return JSON.parse(readFileSync(filePath, 'utf-8')) as Record } catch { return null } } /** * Resolve `ConnectionStrings:DefaultConnection` from an API folder, preferring the * gitignored `appsettings.Local.json` (where `ss dev` writes the real credentials), * then `appsettings.Development.json`, then the base `appsettings.json`. */ export function resolveConnectionString(apiDir: string): { value: string | null; source: string | null } { const candidates = ['appsettings.Local.json', 'appsettings.Development.json', 'appsettings.json'] for (const file of candidates) { const json = readJsonSafe(join(apiDir, file)) const cs = json?.ConnectionStrings?.DefaultConnection if (typeof cs === 'string' && cs.length > 0) return { value: cs, source: file } } return { value: null, source: null } } /** Parse a SQL Server connection string into its components. */ export function parseConnectionString(connStr: string): ParsedConnection { const parts: Record = {} for (const segment of connStr.split(';')) { const eq = segment.indexOf('=') if (eq < 0) continue const key = segment.slice(0, eq).trim().toLowerCase() const value = segment.slice(eq + 1).trim() if (key && value) parts[key] = value } const integrated = (parts['integrated security'] ?? parts['trusted_connection'] ?? '').toLowerCase() const trust = (parts['trustservercertificate'] ?? '').toLowerCase() return { server: parts['server'] ?? parts['data source'] ?? 'localhost', database: parts['database'] ?? parts['initial catalog'] ?? 'SmartStack', useWindowsAuth: integrated === 'true' || integrated === 'sspi' || integrated === 'yes', user: parts['user id'] ?? parts['uid'] ?? null, password: parts['password'] ?? parts['pwd'] ?? null, trustServerCertificate: trust === 'true' || trust === 'yes', raw: connStr, } }