Buckets:
| export * as ProjectV2 from "./project" | |
| export * as Project from "./project" | |
| import { Context, Effect, Layer, Schema } from "effect" | |
| import path from "path" | |
| import { AbsolutePath } from "./schema" | |
| import { FSUtil } from "./fs-util" | |
| import { Git } from "./git" | |
| import { LayerNode } from "./effect/layer-node" | |
| import { Hash } from "./util/hash" | |
| import { ProjectDirectories } from "./project/directories" | |
| import { ProjectSchema } from "./project/schema" | |
| export const ID = ProjectSchema.ID | |
| export type ID = ProjectSchema.ID | |
| export const Vcs = ProjectSchema.Vcs | |
| export type Vcs = ProjectSchema.Vcs | |
| export class Info extends Schema.Class<Info>("Project.Info")({ | |
| id: ID, | |
| }) {} | |
| export const DirectoriesInput = ProjectDirectories.ListInput | |
| export type DirectoriesInput = typeof DirectoriesInput.Type | |
| export const Directories = ProjectDirectories.ListOutput | |
| export type Directories = typeof Directories.Type | |
| export interface Resolved { | |
| readonly previous?: ID | |
| readonly id: ID | |
| readonly directory: AbsolutePath | |
| readonly vcs?: Vcs | |
| } | |
| export interface Interface { | |
| readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories> | |
| readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved> | |
| /** | |
| * Temporary bridge method for writing the resolved project ID to the repo-local cache. | |
| * | |
| * This exists while the old opencode project service and this core project | |
| * service work together: core resolves the ID, while the old service still owns | |
| * database migration and persistence. The old service should call this after it | |
| * finishes migrating from `resolve().previous` to `resolve().id`; once project | |
| * persistence moves into core, this separate bridge method can go away. | |
| */ | |
| readonly commit: (input: { store: AbsolutePath; id: ID }) => Effect.Effect<void> | |
| } | |
| export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectV2") {} | |
| export const layer = Layer.effect( | |
| Service, | |
| Effect.gen(function* () { | |
| const fs = yield* FSUtil.Service | |
| const git = yield* Git.Service | |
| const projectDirectories = yield* ProjectDirectories.Service | |
| const directories = Effect.fn("Project.directories")(function* (input: DirectoriesInput) { | |
| return yield* projectDirectories.list(input.projectID) | |
| }) | |
| const cached = Effect.fnUntraced(function* (dir: string) { | |
| return yield* fs.readFileString(path.join(dir, "opencode")).pipe( | |
| Effect.map((value) => value.trim()), | |
| Effect.map((value) => (value ? ID.make(value) : undefined)), | |
| Effect.catch(() => Effect.succeed(undefined)), | |
| ) | |
| }) | |
| const remote = Effect.fnUntraced(function* (repo: Git.Repo) { | |
| const origin = yield* git.remote(repo) | |
| if (!origin) return undefined | |
| const normalized = url(origin) | |
| if (!normalized) return undefined | |
| return ID.make(Hash.fast(`git-remote:${normalized}`)) | |
| }) | |
| function url(input: string) { | |
| const value = input.trim() | |
| if (!value) return undefined | |
| try { | |
| const parsed = new URL(value) | |
| if (parsed.protocol === "file:") return undefined | |
| return parts(parsed.hostname, parsed.pathname) | |
| } catch { | |
| const scp = value.match(/^([^@/:]+@)?([^/:]+):(.+)$/) | |
| if (scp) return parts(scp[2], scp[3]) | |
| return undefined | |
| } | |
| } | |
| function parts(host: string, name: string) { | |
| const pathname = name | |
| .replace(/^\/+/, "") | |
| .replace(/\.git\/?$/, "") | |
| .replace(/\/+$/, "") | |
| if (!host || !pathname) return undefined | |
| return `${host.toLowerCase()}/${pathname}` | |
| } | |
| const root = Effect.fnUntraced(function* (repo: Git.Repo) { | |
| const root = (yield* git.roots(repo))[0] | |
| return root ? ID.make(root) : undefined | |
| }) | |
| const resolve = Effect.fn("Project.resolve")(function* (input: AbsolutePath) { | |
| const repo = yield* git.find(input) | |
| if (!repo) return { id: ID.global, directory: AbsolutePath.make(path.parse(input).root), vcs: undefined } | |
| const previous = yield* cached(repo.store) | |
| const id = (yield* remote(repo)) ?? previous ?? (yield* root(repo)) | |
| return { | |
| previous, | |
| id: id ?? ID.global, | |
| directory: repo.directory, | |
| vcs: { type: "git" as const, store: repo.store }, | |
| } | |
| }) | |
| const commit = Effect.fn("Project.commit")(function* (input: { store: AbsolutePath; id: ID }) { | |
| yield* fs.writeFileString(path.join(input.store, "opencode"), input.id).pipe(Effect.ignore) | |
| }) | |
| return Service.of({ directories, resolve, commit }) | |
| }), | |
| ) | |
| export const defaultLayer = layer.pipe( | |
| Layer.provide(FSUtil.defaultLayer), | |
| Layer.provide(Git.defaultLayer), | |
| Layer.provideMerge(ProjectDirectories.defaultLayer), | |
| ) | |
| export const node = LayerNode.make(layer, [FSUtil.node, Git.node, ProjectDirectories.node]) | |
Xet Storage Details
- Size:
- 4.82 kB
- Xet hash:
- 0d5f7e90acb527329f04d3190e8beafd7b4d2bf8ad44fb0249811f5d5b17511b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.