From ce4e47a2e3456924b9a8306d63ab2241772d02f5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 15 Apr 2026 22:16:01 -0400 Subject: [PATCH] feat: unwrap uformat namespace to flat exports + barrel (#22703) --- packages/opencode/src/format/format.ts | 192 ++++++++++++++++++++++++ packages/opencode/src/format/index.ts | 195 +------------------------ 2 files changed, 193 insertions(+), 194 deletions(-) create mode 100644 packages/opencode/src/format/format.ts diff --git a/packages/opencode/src/format/format.ts b/packages/opencode/src/format/format.ts new file mode 100644 index 0000000000..6df00d3db3 --- /dev/null +++ b/packages/opencode/src/format/format.ts @@ -0,0 +1,192 @@ +import { Effect, Layer, Context } from "effect" +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" +import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { InstanceState } from "@/effect/instance-state" +import path from "path" +import { mergeDeep } from "remeda" +import z from "zod" +import { Config } from "../config" +import { Log } from "../util/log" +import * as Formatter from "./formatter" + +const log = Log.create({ service: "format" }) + +export const Status = z + .object({ + name: z.string(), + extensions: z.string().array(), + enabled: z.boolean(), + }) + .meta({ + ref: "FormatterStatus", + }) +export type Status = z.infer + +export interface Interface { + readonly init: () => Effect.Effect + readonly status: () => Effect.Effect + readonly file: (filepath: string) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/Format") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const config = yield* Config.Service + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + + const state = yield* InstanceState.make( + Effect.fn("Format.state")(function* (_ctx) { + const commands: Record = {} + const formatters: Record = {} + + const cfg = yield* config.get() + + if (cfg.formatter !== false) { + for (const item of Object.values(Formatter)) { + formatters[item.name] = item + } + for (const [name, item] of Object.entries(cfg.formatter ?? {})) { + // Ruff and uv are both the same formatter, so disabling either should disable both. + if (["ruff", "uv"].includes(name) && (cfg.formatter?.ruff?.disabled || cfg.formatter?.uv?.disabled)) { + // TODO combine formatters so shared backends like Ruff/uv don't need linked disable handling here. + delete formatters.ruff + delete formatters.uv + continue + } + if (item.disabled) { + delete formatters[name] + continue + } + const info = mergeDeep(formatters[name] ?? {}, { + extensions: [], + ...item, + }) + + formatters[name] = { + ...info, + name, + enabled: async () => info.command ?? false, + } + } + } else { + log.info("all formatters are disabled") + } + + async function getCommand(item: Formatter.Info) { + let cmd = commands[item.name] + if (cmd === false || cmd === undefined) { + cmd = await item.enabled() + commands[item.name] = cmd + } + return cmd + } + + async function isEnabled(item: Formatter.Info) { + const cmd = await getCommand(item) + return cmd !== false + } + + async function getFormatter(ext: string) { + const matching = Object.values(formatters).filter((item) => item.extensions.includes(ext)) + const checks = await Promise.all( + matching.map(async (item) => { + log.info("checking", { name: item.name, ext }) + const cmd = await getCommand(item) + if (cmd) { + log.info("enabled", { name: item.name, ext }) + } + return { + item, + cmd, + } + }), + ) + return checks.filter((x) => x.cmd).map((x) => ({ item: x.item, cmd: x.cmd! })) + } + + function formatFile(filepath: string) { + return Effect.gen(function* () { + log.info("formatting", { file: filepath }) + const ext = path.extname(filepath) + + for (const { item, cmd } of yield* Effect.promise(() => getFormatter(ext))) { + if (cmd === false) continue + log.info("running", { command: cmd }) + const replaced = cmd.map((x) => x.replace("$FILE", filepath)) + const dir = yield* InstanceState.directory + const code = yield* spawner + .spawn( + ChildProcess.make(replaced[0]!, replaced.slice(1), { + cwd: dir, + env: item.environment, + extendEnv: true, + }), + ) + .pipe( + Effect.flatMap((handle) => handle.exitCode), + Effect.scoped, + Effect.catch(() => + Effect.sync(() => { + log.error("failed to format file", { + error: "spawn failed", + command: cmd, + ...item.environment, + file: filepath, + }) + return ChildProcessSpawner.ExitCode(1) + }), + ), + ) + if (code !== 0) { + log.error("failed", { + command: cmd, + ...item.environment, + }) + } + } + }) + } + + log.info("init") + + return { + formatters, + isEnabled, + formatFile, + } + }), + ) + + const init = Effect.fn("Format.init")(function* () { + yield* InstanceState.get(state) + }) + + const status = Effect.fn("Format.status")(function* () { + const { formatters, isEnabled } = yield* InstanceState.get(state) + const result: Status[] = [] + for (const formatter of Object.values(formatters)) { + const isOn = yield* Effect.promise(() => isEnabled(formatter)) + result.push({ + name: formatter.name, + extensions: formatter.extensions, + enabled: isOn, + }) + } + return result + }) + + const file = Effect.fn("Format.file")(function* (filepath: string) { + const { formatFile } = yield* InstanceState.get(state) + yield* formatFile(filepath) + }) + + return Service.of({ init, status, file }) + }), +) + +export const defaultLayer = layer.pipe( + Layer.provide(Config.defaultLayer), + Layer.provide(CrossSpawnSpawner.defaultLayer), +) diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index d65ed2944e..435c517ac7 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,194 +1 @@ -import { Effect, Layer, Context } from "effect" -import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" -import { InstanceState } from "@/effect/instance-state" -import path from "path" -import { mergeDeep } from "remeda" -import z from "zod" -import { Config } from "../config" -import { Log } from "../util/log" -import * as Formatter from "./formatter" - -export namespace Format { - const log = Log.create({ service: "format" }) - - export const Status = z - .object({ - name: z.string(), - extensions: z.string().array(), - enabled: z.boolean(), - }) - .meta({ - ref: "FormatterStatus", - }) - export type Status = z.infer - - export interface Interface { - readonly init: () => Effect.Effect - readonly status: () => Effect.Effect - readonly file: (filepath: string) => Effect.Effect - } - - export class Service extends Context.Service()("@opencode/Format") {} - - export const layer = Layer.effect( - Service, - Effect.gen(function* () { - const config = yield* Config.Service - const spawner = yield* ChildProcessSpawner.ChildProcessSpawner - - const state = yield* InstanceState.make( - Effect.fn("Format.state")(function* (_ctx) { - const commands: Record = {} - const formatters: Record = {} - - const cfg = yield* config.get() - - if (cfg.formatter !== false) { - for (const item of Object.values(Formatter)) { - formatters[item.name] = item - } - for (const [name, item] of Object.entries(cfg.formatter ?? {})) { - // Ruff and uv are both the same formatter, so disabling either should disable both. - if (["ruff", "uv"].includes(name) && (cfg.formatter?.ruff?.disabled || cfg.formatter?.uv?.disabled)) { - // TODO combine formatters so shared backends like Ruff/uv don't need linked disable handling here. - delete formatters.ruff - delete formatters.uv - continue - } - if (item.disabled) { - delete formatters[name] - continue - } - const info = mergeDeep(formatters[name] ?? {}, { - extensions: [], - ...item, - }) - - formatters[name] = { - ...info, - name, - enabled: async () => info.command ?? false, - } - } - } else { - log.info("all formatters are disabled") - } - - async function getCommand(item: Formatter.Info) { - let cmd = commands[item.name] - if (cmd === false || cmd === undefined) { - cmd = await item.enabled() - commands[item.name] = cmd - } - return cmd - } - - async function isEnabled(item: Formatter.Info) { - const cmd = await getCommand(item) - return cmd !== false - } - - async function getFormatter(ext: string) { - const matching = Object.values(formatters).filter((item) => item.extensions.includes(ext)) - const checks = await Promise.all( - matching.map(async (item) => { - log.info("checking", { name: item.name, ext }) - const cmd = await getCommand(item) - if (cmd) { - log.info("enabled", { name: item.name, ext }) - } - return { - item, - cmd, - } - }), - ) - return checks.filter((x) => x.cmd).map((x) => ({ item: x.item, cmd: x.cmd! })) - } - - function formatFile(filepath: string) { - return Effect.gen(function* () { - log.info("formatting", { file: filepath }) - const ext = path.extname(filepath) - - for (const { item, cmd } of yield* Effect.promise(() => getFormatter(ext))) { - if (cmd === false) continue - log.info("running", { command: cmd }) - const replaced = cmd.map((x) => x.replace("$FILE", filepath)) - const dir = yield* InstanceState.directory - const code = yield* spawner - .spawn( - ChildProcess.make(replaced[0]!, replaced.slice(1), { - cwd: dir, - env: item.environment, - extendEnv: true, - }), - ) - .pipe( - Effect.flatMap((handle) => handle.exitCode), - Effect.scoped, - Effect.catch(() => - Effect.sync(() => { - log.error("failed to format file", { - error: "spawn failed", - command: cmd, - ...item.environment, - file: filepath, - }) - return ChildProcessSpawner.ExitCode(1) - }), - ), - ) - if (code !== 0) { - log.error("failed", { - command: cmd, - ...item.environment, - }) - } - } - }) - } - - log.info("init") - - return { - formatters, - isEnabled, - formatFile, - } - }), - ) - - const init = Effect.fn("Format.init")(function* () { - yield* InstanceState.get(state) - }) - - const status = Effect.fn("Format.status")(function* () { - const { formatters, isEnabled } = yield* InstanceState.get(state) - const result: Status[] = [] - for (const formatter of Object.values(formatters)) { - const isOn = yield* Effect.promise(() => isEnabled(formatter)) - result.push({ - name: formatter.name, - extensions: formatter.extensions, - enabled: isOn, - }) - } - return result - }) - - const file = Effect.fn("Format.file")(function* (filepath: string) { - const { formatFile } = yield* InstanceState.get(state) - yield* formatFile(filepath) - }) - - return Service.of({ init, status, file }) - }), - ) - - export const defaultLayer = layer.pipe( - Layer.provide(Config.defaultLayer), - Layer.provide(CrossSpawnSpawner.defaultLayer), - ) -} +export * as Format from "./format"