-
-
Notifications
You must be signed in to change notification settings - Fork 443
refactor(config): establish typed configuration seam #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,26 +7,31 @@ import { | |
| } from "node:fs"; | ||
| import { homedir } from "node:os"; | ||
| import { join, resolve } from "node:path"; | ||
| import * as z from "zod/v4"; | ||
| import { expandHomePath } from "./roots.js"; | ||
| import type { StoredSubagentsConfig } from "./local-agent-config.js"; | ||
|
|
||
| export interface DevspaceUserConfig { | ||
| host?: string; | ||
| port?: number; | ||
| allowedRoots?: string[]; | ||
| publicBaseUrl?: string | null; | ||
| allowedHosts?: string[]; | ||
| stateDir?: string; | ||
| worktreeRoot?: string; | ||
| artifactsEnabled?: boolean; | ||
| artifactMaxFileBytes?: number; | ||
| agentDir?: string; | ||
| subagents?: StoredSubagentsConfig; | ||
| } | ||
| import { storedSubagentsConfigSchema } from "./local-agent-config.js"; | ||
|
|
||
| export interface DevspaceAuthConfig { | ||
| ownerToken?: string; | ||
| } | ||
| const devspaceUserConfigSchema = z.object({ | ||
| host: z.string().optional(), | ||
| port: z.number().optional(), | ||
| allowedRoots: z.array(z.string()).optional(), | ||
| publicBaseUrl: z.string().nullable().optional(), | ||
| allowedHosts: z.array(z.string()).optional(), | ||
| stateDir: z.string().optional(), | ||
| worktreeRoot: z.string().optional(), | ||
| artifactsEnabled: z.boolean().optional(), | ||
| artifactMaxFileBytes: z.number().optional(), | ||
| agentDir: z.string().optional(), | ||
| subagents: storedSubagentsConfigSchema.optional(), | ||
| }); | ||
|
|
||
| const devspaceAuthConfigSchema = z.object({ | ||
| ownerToken: z.string().optional(), | ||
| }); | ||
|
|
||
| export type DevspaceUserConfig = z.infer<typeof devspaceUserConfigSchema>; | ||
|
|
||
| export type DevspaceAuthConfig = z.infer<typeof devspaceAuthConfigSchema>; | ||
|
|
||
| export interface DevspaceFiles { | ||
| dir: string; | ||
|
|
@@ -36,6 +41,7 @@ export interface DevspaceFiles { | |
| authExists: boolean; | ||
| config: DevspaceUserConfig; | ||
| auth: DevspaceAuthConfig; | ||
| configDocument: Record<string, unknown>; | ||
| } | ||
|
|
||
| export function devspaceConfigDir(env: NodeJS.ProcessEnv = process.env): string { | ||
|
|
@@ -65,24 +71,29 @@ export function loadDevspaceFiles(env: NodeJS.ProcessEnv = process.env): Devspac | |
| const configExists = existsSync(configPath); | ||
| const authExists = existsSync(authPath); | ||
|
|
||
| const configDocument = configExists ? readJsonObject(configPath) : {}; | ||
| const authDocument = authExists ? readJsonObject(authPath) : {}; | ||
|
|
||
| return { | ||
| dir, | ||
| configPath, | ||
| authPath, | ||
| configExists, | ||
| authExists, | ||
| config: configExists ? readJsonFile<DevspaceUserConfig>(configPath) : {}, | ||
| auth: authExists ? readJsonFile<DevspaceAuthConfig>(authPath) : {}, | ||
| config: parseDocument(devspaceUserConfigSchema, configDocument, configPath), | ||
| auth: parseDocument(devspaceAuthConfigSchema, authDocument, authPath), | ||
|
Comment on lines
+83
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an existing Knowledge Base Used: Configuration and onboarding flow |
||
| configDocument, | ||
| }; | ||
| } | ||
|
|
||
| export function writeDevspaceConfig( | ||
| config: DevspaceUserConfig, | ||
| env: NodeJS.ProcessEnv = process.env, | ||
| existingDocument: Record<string, unknown> = {}, | ||
| ): string { | ||
| const filePath = devspaceConfigPath(env); | ||
| mkdirSync(devspaceConfigDir(env), { recursive: true }); | ||
| writeJsonFile(filePath, config, 0o600); | ||
| writeJsonFile(filePath, { ...existingDocument, ...config }, 0o600); | ||
| return filePath; | ||
| } | ||
|
|
||
|
|
@@ -100,15 +111,30 @@ export function generateOwnerToken(): string { | |
| return randomBytes(32).toString("base64url"); | ||
| } | ||
|
|
||
| function readJsonFile<T>(filePath: string): T { | ||
| function readJsonObject(filePath: string): Record<string, unknown> { | ||
| try { | ||
| return JSON.parse(readFileSync(filePath, "utf8")) as T; | ||
| const parsed: unknown = JSON.parse(readFileSync(filePath, "utf8")); | ||
| if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { | ||
| throw new Error("expected a JSON object"); | ||
| } | ||
| return parsed as Record<string, unknown>; | ||
| } catch (error) { | ||
| const reason = error instanceof Error ? error.message : String(error); | ||
| throw new Error(`Unable to read ${filePath}: ${reason}`); | ||
| } | ||
| } | ||
|
|
||
| function parseDocument<T>( | ||
| schema: z.ZodType<T>, | ||
| document: Record<string, unknown>, | ||
| filePath: string, | ||
| ): T { | ||
| const result = schema.safeParse(document); | ||
| if (result.success) return result.data; | ||
|
|
||
| throw new Error(`Invalid ${filePath}: ${z.prettifyError(result.error)}`); | ||
| } | ||
|
|
||
| function writeJsonFile(filePath: string, value: unknown, mode: number): void { | ||
| writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", { mode }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Update the subagent caller for the new
parseBooleansignature.parseBooleannow requiresname, butsrc/local-agent-config.ts:53still callsparseBoolean(env.DEVSPACE_SUBAGENTS)with one argument. This causes a TypeScript arity error, or produces an error containingundefinedwithout type checking. Pass"DEVSPACE_SUBAGENTS"and keep the helper export/import contract consistent.As per coding guidelines, cross-cutting configuration changes must keep the subagent configuration contract synchronized.
🤖 Prompt for AI Agents
Source: Coding guidelines