refactor(core): consolidate filesystem services (#30447)
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
import { Plugin } from "../plugin"
|
||||
import { Format } from "../format"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { File } from "../file"
|
||||
import { Snapshot } from "../snapshot"
|
||||
import * as Project from "./project"
|
||||
import * as Vcs from "./vcs"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Config } from "@/config/config"
|
||||
@@ -23,8 +21,6 @@ export const layer = Layer.effect(
|
||||
// InstanceStore imports only the lightweight tag from bootstrap-service.ts,
|
||||
// so it can depend on bootstrap without importing this implementation graph.
|
||||
const config = yield* Config.Service
|
||||
const file = yield* File.Service
|
||||
const fileWatcher = yield* FileWatcher.Service
|
||||
const format = yield* Format.Service
|
||||
const lsp = yield* LSP.Service
|
||||
const plugin = yield* Plugin.Service
|
||||
@@ -44,7 +40,7 @@ export const layer = Layer.effect(
|
||||
// Each service self-manages its own slow work via Effect.forkScoped against
|
||||
// its per-instance state scope. We just await materialization here.
|
||||
yield* Effect.forEach(
|
||||
[reference, lsp, shareNext, format, file, fileWatcher, vcs, snapshot, project],
|
||||
[reference, lsp, shareNext, format, vcs, snapshot, project],
|
||||
(s) => s.init().pipe(Effect.catchCause((cause) => Effect.logWarning("init failed", { cause }))),
|
||||
{ concurrency: "unbounded", discard: true },
|
||||
).pipe(Effect.withSpan("InstanceBootstrap.init"))
|
||||
@@ -57,8 +53,6 @@ export const layer = Layer.effect(
|
||||
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
||||
Layer.provide([
|
||||
Config.defaultLayer,
|
||||
File.defaultLayer,
|
||||
FileWatcher.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LocalContext } from "@/util/local-context"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import type * as Project from "./project"
|
||||
|
||||
export interface InstanceContext {
|
||||
@@ -16,9 +16,9 @@ export const context = LocalContext.create<InstanceContext>("instance")
|
||||
* Paths within the worktree but outside the working directory should not trigger external_directory permission.
|
||||
*/
|
||||
export function containsPath(filepath: string, ctx: InstanceContext): boolean {
|
||||
if (AppFileSystem.contains(ctx.directory, filepath)) return true
|
||||
if (FSUtil.contains(ctx.directory, filepath)) return true
|
||||
// Non-git projects set worktree to "/" which would match ANY absolute path.
|
||||
// Skip worktree check in this case to preserve external_directory permissions.
|
||||
if (ctx.worktree === "/") return false
|
||||
return AppFileSystem.contains(ctx.worktree, filepath)
|
||||
return FSUtil.contains(ctx.worktree, filepath)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
|
||||
import { type InstanceContext } from "./instance-context"
|
||||
import { InstanceBootstrap } from "./bootstrap-service"
|
||||
@@ -104,7 +104,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
})
|
||||
|
||||
const load = (input: LoadInput): Effect.Effect<InstanceContext> => {
|
||||
const directory = AppFileSystem.resolve(input.directory)
|
||||
const directory = FSUtil.resolve(input.directory)
|
||||
return Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
const existing = cache.get(directory)
|
||||
@@ -122,7 +122,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
}
|
||||
|
||||
const reload = (input: LoadInput): Effect.Effect<InstanceContext> => {
|
||||
const directory = AppFileSystem.resolve(input.directory)
|
||||
const directory = FSUtil.resolve(input.directory)
|
||||
return Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
const previous = cache.get(directory)
|
||||
@@ -153,7 +153,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
})
|
||||
|
||||
const disposeDirectory = Effect.fn("InstanceStore.disposeDirectory")(function* (input: string) {
|
||||
const directory = AppFileSystem.resolve(input)
|
||||
const directory = FSUtil.resolve(input)
|
||||
const entry = cache.get(directory)
|
||||
if (!entry) return
|
||||
const exit = yield* Deferred.await(entry.deferred).pipe(Effect.exit)
|
||||
|
||||
@@ -6,12 +6,12 @@ import { WorkspaceTable } from "@opencode-ai/core/control-plane/workspace.sql"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { which } from "../util/which"
|
||||
import { which } from "@opencode-ai/core/util/which"
|
||||
import { Command } from "@/command"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Effect, Layer, Scope, Context, Stream, Types, Schema } from "effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
@@ -135,7 +135,7 @@ type GitResult = { code: number; text: string; stderr: string }
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const proc = yield* AppProcess.Service
|
||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||
const projectV2 = yield* ProjectV2.Service
|
||||
@@ -326,7 +326,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const buffer = yield* fs.readFile(shortest).pipe(Effect.orDie)
|
||||
const base64 = Buffer.from(buffer).toString("base64")
|
||||
const mime = AppFileSystem.mimeType(shortest)
|
||||
const mime = FSUtil.mimeType(shortest)
|
||||
const url = `data:${mime};base64,${base64}`
|
||||
yield* update({ projectID: input.id, icon: { url } }).pipe(
|
||||
Effect.catchTag("Project.NotFoundError", () => Effect.void),
|
||||
@@ -468,7 +468,7 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(AppFileSystem.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Effect, Layer, Context, Schema, Stream, Scope } from "effect"
|
||||
import { formatPatch, structuredPatch } from "diff"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { Watcher } from "@opencode-ai/core/filesystem/watcher"
|
||||
import { Git } from "@/git"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
@@ -328,9 +328,9 @@ export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Serv
|
||||
log.info("initialized", { branch: value.current, default_branch: value.root?.name })
|
||||
|
||||
const unsubscribe = yield* events.listen((event) => {
|
||||
if (event.type !== FileWatcher.Event.Updated.type || event.location?.directory !== ctx.directory)
|
||||
if (event.type !== Watcher.Event.Updated.type || event.location?.directory !== ctx.directory)
|
||||
return Effect.void
|
||||
const data = event.data as EventV2.Data<typeof FileWatcher.Event.Updated>
|
||||
const data = event.data as EventV2.Data<typeof Watcher.Event.Updated>
|
||||
if (!data.file.endsWith("HEAD")) return Effect.void
|
||||
return Effect.gen(function* () {
|
||||
const next = yield* get()
|
||||
|
||||
Reference in New Issue
Block a user