feat(api): publish v2 reference
This commit is contained in:
parent
e6e951b252
commit
eed23d8ee9
40 changed files with 26473 additions and 147 deletions
|
|
@ -3,8 +3,10 @@ export * as Project from "./project"
|
|||
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { asc, desc } from "drizzle-orm"
|
||||
import path from "path"
|
||||
import { AbsolutePath } from "./schema"
|
||||
import { Database } from "./database/database"
|
||||
import { FSUtil } from "./fs-util"
|
||||
import { Git } from "./git"
|
||||
import { AppProcess } from "./process"
|
||||
|
|
@ -12,6 +14,7 @@ import { makeGlobalNode } from "./effect/app-node"
|
|||
import { Hash } from "./util/hash"
|
||||
import { ProjectDirectories } from "./project/directories"
|
||||
import { ProjectSchema } from "./project/schema"
|
||||
import { ProjectTable } from "./project/sql"
|
||||
|
||||
export const ID = ProjectSchema.ID
|
||||
export type ID = ProjectSchema.ID
|
||||
|
|
@ -25,9 +28,8 @@ export type Current = ProjectSchema.Current
|
|||
export const Directory = ProjectSchema.Directory
|
||||
export type Directory = ProjectSchema.Directory
|
||||
|
||||
export class Info extends Schema.Class<Info>("Project.Info")({
|
||||
id: ID,
|
||||
}) {}
|
||||
export const Info = ProjectSchema.Info
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
|
||||
export const DirectoriesInput = ProjectSchema.DirectoriesInput
|
||||
export type DirectoriesInput = typeof DirectoriesInput.Type
|
||||
|
|
@ -54,6 +56,7 @@ export const root = Effect.fn("Project.root")(function* (
|
|||
})
|
||||
|
||||
export interface Interface {
|
||||
readonly list: () => Effect.Effect<ReadonlyArray<Info>>
|
||||
readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories>
|
||||
readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>
|
||||
/**
|
||||
|
|
@ -70,14 +73,50 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectV2") {}
|
||||
|
||||
function fromRow(row: typeof ProjectTable.$inferSelect): Info {
|
||||
const icon =
|
||||
row.icon_url || row.icon_url_override || row.icon_color
|
||||
? {
|
||||
url: row.icon_url ?? undefined,
|
||||
override: row.icon_url_override ?? undefined,
|
||||
color: row.icon_color ?? undefined,
|
||||
}
|
||||
: undefined
|
||||
return {
|
||||
id: row.id,
|
||||
worktree: row.worktree,
|
||||
vcs: row.vcs ?? undefined,
|
||||
name: row.name ?? undefined,
|
||||
icon,
|
||||
commands: row.commands ?? undefined,
|
||||
time: {
|
||||
created: row.time_created,
|
||||
updated: row.time_updated,
|
||||
initialized: row.time_initialized ?? undefined,
|
||||
},
|
||||
sandboxes: row.sandboxes,
|
||||
}
|
||||
}
|
||||
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const git = yield* Git.Service
|
||||
const proc = yield* AppProcess.Service
|
||||
const db = (yield* Database.Service).db
|
||||
const projectDirectories = yield* ProjectDirectories.Service
|
||||
|
||||
const list = Effect.fn("Project.list")(function* () {
|
||||
const rows = yield* db
|
||||
.select()
|
||||
.from(ProjectTable)
|
||||
.orderBy(desc(ProjectTable.time_updated), asc(ProjectTable.id))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
return rows.map(fromRow)
|
||||
})
|
||||
|
||||
const directories = Effect.fn("Project.directories")(function* (input: DirectoriesInput) {
|
||||
return yield* projectDirectories.list(input.projectID)
|
||||
})
|
||||
|
|
@ -190,12 +229,12 @@ const layer = Layer.effect(
|
|||
yield* fs.writeFileString(path.join(input.store, "opencode"), input.id).pipe(Effect.ignore)
|
||||
})
|
||||
|
||||
return Service.of({ directories, resolve, commit })
|
||||
return Service.of({ list, directories, resolve, commit })
|
||||
}),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [FSUtil.node, Git.node, AppProcess.node, ProjectDirectories.node],
|
||||
deps: [Database.node, FSUtil.node, Git.node, AppProcess.node, ProjectDirectories.node],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ export type Current = typeof Current.Type
|
|||
export const Directory = Project.Directory
|
||||
export type Directory = typeof Directory.Type
|
||||
|
||||
export const Info = Project.Info
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
|
||||
export const DirectoriesInput = Project.DirectoriesInput
|
||||
export type DirectoriesInput = typeof DirectoriesInput.Type
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { ProjectSchema } from "./schema"
|
|||
export const ProjectTable = sqliteTable("project", {
|
||||
id: text().$type<ProjectSchema.ID>().primaryKey(),
|
||||
worktree: absoluteColumn().notNull(),
|
||||
vcs: text(),
|
||||
vcs: text().$type<"git" | "hg">(),
|
||||
name: text(),
|
||||
icon_url: text(),
|
||||
icon_url_override: text(),
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ describe("node build", () => {
|
|||
Effect.sync(() => {
|
||||
acquisitions++
|
||||
return Project.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
directories: () => Effect.succeed([]),
|
||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory }),
|
||||
commit: () => Effect.void,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const ref = { directory: AbsolutePath.make("/repo/packages/app"), workspaceID }
|
|||
const projectLayer = Layer.succeed(
|
||||
Project.Service,
|
||||
Project.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
directories: () => Effect.succeed([]),
|
||||
resolve: () =>
|
||||
Effect.succeed({
|
||||
|
|
|
|||
|
|
@ -2,15 +2,71 @@ import { describe, expect } from "bun:test"
|
|||
import { $ } from "bun"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(AppNodeBuilder.build(ProjectV2.node))
|
||||
const it = testEffect(
|
||||
Layer.merge(AppNodeBuilder.build(ProjectV2.node), AppNodeBuilder.build(Database.node)),
|
||||
)
|
||||
|
||||
describe("ProjectV2.list", () => {
|
||||
it.effect("returns complete projects ordered by recent update", () =>
|
||||
Effect.gen(function* () {
|
||||
const db = (yield* Database.Service).db
|
||||
const project = yield* ProjectV2.Service
|
||||
yield* db
|
||||
.insert(ProjectTable)
|
||||
.values([
|
||||
{
|
||||
id: ProjectV2.ID.make("older"),
|
||||
worktree: abs("/older"),
|
||||
vcs: "git",
|
||||
name: "Older",
|
||||
icon_color: "#000000",
|
||||
commands: { start: "bun dev" },
|
||||
sandboxes: [abs("/older/sandbox")],
|
||||
time_created: 1,
|
||||
time_updated: 1,
|
||||
},
|
||||
{
|
||||
id: ProjectV2.ID.make("newer"),
|
||||
worktree: abs("/newer"),
|
||||
sandboxes: [],
|
||||
time_created: 2,
|
||||
time_updated: 2,
|
||||
time_initialized: 3,
|
||||
},
|
||||
])
|
||||
.run()
|
||||
|
||||
expect(yield* project.list()).toEqual([
|
||||
{
|
||||
id: ProjectV2.ID.make("newer"),
|
||||
worktree: abs("/newer"),
|
||||
time: { created: 2, updated: 2, initialized: 3 },
|
||||
sandboxes: [],
|
||||
},
|
||||
{
|
||||
id: ProjectV2.ID.make("older"),
|
||||
worktree: abs("/older"),
|
||||
vcs: "git",
|
||||
name: "Older",
|
||||
icon: { color: "#000000" },
|
||||
commands: { start: "bun dev" },
|
||||
time: { created: 1, updated: 1 },
|
||||
sandboxes: [abs("/older/sandbox")],
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
function remoteID(remote: string) {
|
||||
return ProjectV2.ID.make(Hash.fast(`git-remote:${remote}`))
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const model = Model.make({
|
|||
const projects = Layer.succeed(
|
||||
ProjectV2.Service,
|
||||
ProjectV2.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
||||
directories: () => Effect.succeed([]),
|
||||
commit: () => Effect.void,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { tmpdir } from "./fixture/tmpdir"
|
|||
const projects = Layer.succeed(
|
||||
ProjectV2.Service,
|
||||
ProjectV2.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
||||
directories: () => Effect.succeed([]),
|
||||
commit: () => Effect.void,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const readToolNode = makeLocationNode({
|
|||
const projects = Layer.succeed(
|
||||
ProjectV2.Service,
|
||||
ProjectV2.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
||||
directories: () => Effect.succeed([]),
|
||||
commit: () => Effect.void,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { testEffect } from "./lib/effect"
|
|||
const projects = Layer.succeed(
|
||||
ProjectV2.Service,
|
||||
ProjectV2.Service.of({
|
||||
list: () => Effect.succeed([]),
|
||||
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
||||
directories: () => Effect.succeed([]),
|
||||
commit: () => Effect.void,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue