refactor: split config lsp and formatter schemas (#22986)

This commit is contained in:
Dax
2026-04-16 21:35:26 -04:00
committed by GitHub
parent 6405e3a7b1
commit 326471a25c
6 changed files with 63 additions and 51 deletions

View File

@@ -14,6 +14,7 @@
- Use Bun APIs when possible, like `Bun.file()` - Use Bun APIs when possible, like `Bun.file()`
- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream - Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
Reduce total variable count by inlining when a value is only used once. Reduce total variable count by inlining when a value is only used once.

View File

@@ -23,6 +23,10 @@ See `specs/effect/migration.md` for the compact pattern reference and examples.
- Use `Effect.callback` for callback-based APIs. - Use `Effect.callback` for callback-based APIs.
- Prefer `DateTime.nowAsDate` over `new Date(yield* Clock.currentTimeMillis)` when you need a `Date`. - Prefer `DateTime.nowAsDate` over `new Date(yield* Clock.currentTimeMillis)` when you need a `Date`.
## Module conventions
- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
## Schemas and errors ## Schemas and errors
- Use `Schema.Class` for multi-field data. - Use `Schema.Class` for multi-field data.

View File

@@ -12,7 +12,6 @@ import { Auth } from "../auth"
import { Env } from "../env" import { Env } from "../env"
import { applyEdits, modify } from "jsonc-parser" import { applyEdits, modify } from "jsonc-parser"
import { Instance, type InstanceContext } from "../project/instance" import { Instance, type InstanceContext } from "../project/instance"
import * as LSPServer from "../lsp/server"
import { InstallationLocal, InstallationVersion } from "@/installation/version" import { InstallationLocal, InstallationVersion } from "@/installation/version"
import { existsSync } from "fs" import { existsSync } from "fs"
import { GlobalBus } from "@/bus/global" import { GlobalBus } from "@/bus/global"
@@ -37,6 +36,8 @@ import { ConfigPermission } from "./permission"
import { ConfigProvider } from "./provider" import { ConfigProvider } from "./provider"
import { ConfigSkills } from "./skills" import { ConfigSkills } from "./skills"
import { ConfigPaths } from "./paths" import { ConfigPaths } from "./paths"
import { ConfigFormatter } from "./formatter"
import { ConfigLSP } from "./lsp"
const log = Log.create({ service: "config" }) const log = Log.create({ service: "config" })
@@ -186,56 +187,8 @@ export const Info = z
) )
.optional() .optional()
.describe("MCP (Model Context Protocol) server configurations"), .describe("MCP (Model Context Protocol) server configurations"),
formatter: z formatter: ConfigFormatter.Info.optional(),
.union([ lsp: ConfigLSP.Info.optional(),
z.literal(false),
z.record(
z.string(),
z.object({
disabled: z.boolean().optional(),
command: z.array(z.string()).optional(),
environment: z.record(z.string(), z.string()).optional(),
extensions: z.array(z.string()).optional(),
}),
),
])
.optional(),
lsp: z
.union([
z.literal(false),
z.record(
z.string(),
z.union([
z.object({
disabled: z.literal(true),
}),
z.object({
command: z.array(z.string()),
extensions: z.array(z.string()).optional(),
disabled: z.boolean().optional(),
env: z.record(z.string(), z.string()).optional(),
initialization: z.record(z.string(), z.any()).optional(),
}),
]),
),
])
.optional()
.refine(
(data) => {
if (!data) return true
if (typeof data === "boolean") return true
const serverIds = new Set(Object.values(LSPServer).map((s) => s.id))
return Object.entries(data).every(([id, config]) => {
if (config.disabled) return true
if (serverIds.has(id)) return true
return Boolean(config.extensions)
})
},
{
error: "For custom LSP servers, 'extensions' array is required.",
},
),
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"), instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
layout: Layout.optional().describe("@deprecated Always uses stretch layout."), layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
permission: ConfigPermission.Info.optional(), permission: ConfigPermission.Info.optional(),

View File

@@ -0,0 +1,13 @@
export * as ConfigFormatter from "./formatter"
import z from "zod"
export const Entry = z.object({
disabled: z.boolean().optional(),
command: z.array(z.string()).optional(),
environment: z.record(z.string(), z.string()).optional(),
extensions: z.array(z.string()).optional(),
})
export const Info = z.union([z.literal(false), z.record(z.string(), Entry)])
export type Info = z.infer<typeof Info>

View File

@@ -2,6 +2,8 @@ export * as Config from "./config"
export * as ConfigAgent from "./agent" export * as ConfigAgent from "./agent"
export * as ConfigCommand from "./command" export * as ConfigCommand from "./command"
export * as ConfigError from "./error" export * as ConfigError from "./error"
export * as ConfigFormatter from "./formatter"
export * as ConfigLSP from "./lsp"
export * as ConfigVariable from "./variable" export * as ConfigVariable from "./variable"
export { ConfigManaged } from "./managed" export { ConfigManaged } from "./managed"
export * as ConfigMarkdown from "./markdown" export * as ConfigMarkdown from "./markdown"

View File

@@ -0,0 +1,39 @@
export * as ConfigLSP from "./lsp"
import z from "zod"
import * as LSPServer from "../lsp/server"
export const Disabled = z.object({
disabled: z.literal(true),
})
export const Entry = z.union([
Disabled,
z.object({
command: z.array(z.string()),
extensions: z.array(z.string()).optional(),
disabled: z.boolean().optional(),
env: z.record(z.string(), z.string()).optional(),
initialization: z.record(z.string(), z.any()).optional(),
}),
])
export const Info = z
.union([z.literal(false), z.record(z.string(), Entry)])
.refine(
(data) => {
if (typeof data === "boolean") return true
const serverIds = new Set(Object.values(LSPServer).map((server) => server.id))
return Object.entries(data).every(([id, config]) => {
if (config.disabled) return true
if (serverIds.has(id)) return true
return Boolean(config.extensions)
})
},
{
error: "For custom LSP servers, 'extensions' array is required.",
},
)
export type Info = z.infer<typeof Info>