Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/opencode/src/cli/cmd/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const ImportCommand = cmd({
await bootstrap(process.cwd(), async () => {
let exportData:
| {
info: Session.Info
info: SDKSession
messages: Array<{
info: Message
parts: Part[]
Expand Down Expand Up @@ -152,7 +152,7 @@ export const ImportCommand = cmd({
return
}

const row = { ...Session.toRow(exportData.info), project_id: Instance.project.id }
const row = Session.toRow({ ...exportData.info, projectID: Instance.project.id })
Database.use((db) =>
db
.insert(SessionTable)
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/control-plane/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import z from "zod"
import { Identifier } from "@/id/id"
import { ProjectID } from "@/project/schema"

export const WorkspaceInfo = z.object({
id: Identifier.schema("workspace"),
Expand All @@ -8,7 +9,7 @@ export const WorkspaceInfo = z.object({
name: z.string().nullable(),
directory: z.string().nullable(),
extra: z.unknown().nullable(),
projectID: z.string(),
projectID: ProjectID.zod,
})
export type WorkspaceInfo = z.infer<typeof WorkspaceInfo>

Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/control-plane/workspace.sql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { ProjectID } from "../project/schema"

export const WorkspaceTable = sqliteTable("workspace", {
id: text().primaryKey(),
Expand All @@ -9,6 +10,7 @@ export const WorkspaceTable = sqliteTable("workspace", {
directory: text(),
extra: text({ mode: "json" }),
project_id: text()
.$type<ProjectID>()
.notNull()
.references(() => ProjectTable.id, { onDelete: "cascade" }),
})
3 changes: 2 additions & 1 deletion packages/opencode/src/control-plane/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Project } from "@/project/project"
import { BusEvent } from "@/bus/bus-event"
import { GlobalBus } from "@/bus/global"
import { Log } from "@/util/log"
import { ProjectID } from "@/project/schema"
import { WorkspaceTable } from "./workspace.sql"
import { getAdaptor } from "./adaptors"
import { WorkspaceInfo } from "./types"
Expand Down Expand Up @@ -48,7 +49,7 @@ export namespace Workspace {
id: Identifier.schema("workspace").optional(),
type: Info.shape.type,
branch: Info.shape.branch,
projectID: Info.shape.projectID,
projectID: ProjectID.zod,
extra: Info.shape.extra,
})

Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/permission/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Database, eq } from "@/storage/db"
import { PermissionTable } from "@/session/session.sql"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { ProjectID } from "@/project/schema"
import { Wildcard } from "@/util/wildcard"
import os from "os"
import z from "zod"
Expand Down Expand Up @@ -90,7 +91,7 @@ export namespace PermissionNext {
export type Reply = z.infer<typeof Reply>

export const Approval = z.object({
projectID: z.string(),
projectID: ProjectID.zod,
patterns: z.string().array(),
})

Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/project/project.sql.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
import { Timestamps } from "../storage/schema.sql"
import type { ProjectID } from "./schema"

export const ProjectTable = sqliteTable("project", {
id: text().primaryKey(),
id: text().$type<ProjectID>().primaryKey(),
worktree: text().notNull(),
vcs: text(),
name: text(),
Expand Down
43 changes: 23 additions & 20 deletions packages/opencode/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { existsSync } from "fs"
import { git } from "../util/git"
import { Glob } from "../util/glob"
import { which } from "../util/which"
import { ProjectID } from "./schema"

export namespace Project {
const log = Log.create({ service: "project" })
Expand All @@ -33,7 +34,7 @@ export namespace Project {

export const Info = z
.object({
id: z.string(),
id: ProjectID.zod,
worktree: z.string(),
vcs: z.literal("git").optional(),
name: z.string().optional(),
Expand Down Expand Up @@ -73,7 +74,7 @@ export namespace Project {
? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined }
: undefined
return {
id: row.id,
id: ProjectID.make(row.id),
worktree: row.worktree,
vcs: row.vcs ? Info.shape.vcs.parse(row.vcs) : undefined,
name: row.name ?? undefined,
Expand All @@ -91,6 +92,7 @@ export namespace Project {
function readCachedId(dir: string) {
return Filesystem.readText(path.join(dir, "opencode"))
.then((x) => x.trim())
.then(ProjectID.make)
.catch(() => undefined)
}

Expand All @@ -111,7 +113,7 @@ export namespace Project {

if (!gitBinary) {
return {
id: id ?? "global",
id: id ?? ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand All @@ -130,7 +132,7 @@ export namespace Project {

if (!worktree) {
return {
id: id ?? "global",
id: id ?? ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand Down Expand Up @@ -160,22 +162,22 @@ export namespace Project {

if (!roots) {
return {
id: "global",
id: ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
}
}

id = roots[0]
id = roots[0] ? ProjectID.make(roots[0]) : undefined
if (id) {
await Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined)
}
}

if (!id) {
return {
id: "global",
id: ProjectID.global,
worktree: sandbox,
sandbox,
vcs: "git",
Expand Down Expand Up @@ -208,7 +210,7 @@ export namespace Project {
}

return {
id: "global",
id: ProjectID.global,
worktree: "/",
sandbox: "/",
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand All @@ -228,7 +230,7 @@ export namespace Project {
updated: Date.now(),
},
}
if (data.id !== "global") {
if (data.id !== ProjectID.global) {
await migrateFromGlobal(data.id, data.worktree)
}
return fresh
Expand Down Expand Up @@ -308,12 +310,12 @@ export namespace Project {
return
}

async function migrateFromGlobal(id: string, worktree: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, "global")).get())
async function migrateFromGlobal(id: ProjectID, worktree: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, ProjectID.global)).get())
if (!row) return

const sessions = Database.use((db) =>
db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(),
db.select().from(SessionTable).where(eq(SessionTable.project_id, ProjectID.global)).all(),
)
if (sessions.length === 0) return

Expand All @@ -323,14 +325,14 @@ export namespace Project {
// Skip sessions that belong to a different directory
if (row.directory && row.directory !== worktree) return

log.info("migrating session", { sessionID: row.id, from: "global", to: id })
log.info("migrating session", { sessionID: row.id, from: ProjectID.global, to: id })
Database.use((db) => db.update(SessionTable).set({ project_id: id }).where(eq(SessionTable.id, row.id)).run())
}).catch((error) => {
log.error("failed to migrate sessions from global to project", { error, projectId: id })
})
}

export function setInitialized(id: string) {
export function setInitialized(id: ProjectID) {
Database.use((db) =>
db
.update(ProjectTable)
Expand All @@ -352,7 +354,7 @@ export namespace Project {
)
}

export function get(id: string): Info | undefined {
export function get(id: ProjectID): Info | undefined {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) return undefined
return fromRow(row)
Expand All @@ -375,12 +377,13 @@ export namespace Project {

export const update = fn(
z.object({
projectID: z.string(),
projectID: ProjectID.zod,
name: z.string().optional(),
icon: Info.shape.icon.optional(),
commands: Info.shape.commands.optional(),
}),
async (input) => {
const id = ProjectID.make(input.projectID)
const result = Database.use((db) =>
db
.update(ProjectTable)
Expand All @@ -391,7 +394,7 @@ export namespace Project {
commands: input.commands,
time_updated: Date.now(),
})
.where(eq(ProjectTable.id, input.projectID))
.where(eq(ProjectTable.id, id))
.returning()
.get(),
)
Expand All @@ -407,7 +410,7 @@ export namespace Project {
},
)

export async function sandboxes(id: string) {
export async function sandboxes(id: ProjectID) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) return []
const data = fromRow(row)
Expand All @@ -419,7 +422,7 @@ export namespace Project {
return valid
}

export async function addSandbox(id: string, directory: string) {
export async function addSandbox(id: ProjectID, directory: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) throw new Error(`Project not found: ${id}`)
const sandboxes = [...row.sandboxes]
Expand All @@ -443,7 +446,7 @@ export namespace Project {
return data
}

export async function removeSandbox(id: string, directory: string) {
export async function removeSandbox(id: ProjectID, directory: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) throw new Error(`Project not found: ${id}`)
const sandboxes = row.sandboxes.filter((s) => s !== directory)
Expand Down
16 changes: 16 additions & 0 deletions packages/opencode/src/project/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Schema } from "effect"
import z from "zod"

import { withStatics } from "@/util/schema"

const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectId"))

export type ProjectID = typeof projectIdSchema.Type

export const ProjectID = projectIdSchema.pipe(
withStatics((schema: typeof projectIdSchema) => ({
global: schema.makeUnsafe("global"),
make: (id: string) => schema.makeUnsafe(id),
zod: z.string().pipe(z.custom<ProjectID>()),
})),
)
3 changes: 2 additions & 1 deletion packages/opencode/src/server/routes/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolver } from "hono-openapi"
import { Instance } from "../../project/instance"
import { Project } from "../../project/project"
import z from "zod"
import { ProjectID } from "../../project/schema"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { InstanceBootstrap } from "../../project/bootstrap"
Expand Down Expand Up @@ -105,7 +106,7 @@ export const ProjectRoutes = lazy(() =>
...errors(400, 404),
},
}),
validator("param", z.object({ projectID: z.string() })),
validator("param", z.object({ projectID: ProjectID.zod })),
validator("json", Project.update.schema.omit({ projectID: true })),
async (c) => {
const projectID = c.req.valid("param").projectID
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { fn } from "@/util/fn"
import { Command } from "../command"
import { Snapshot } from "@/snapshot"
import { WorkspaceContext } from "../control-plane/workspace-context"
import { ProjectID } from "../project/schema"

import type { Provider } from "@/provider/provider"
import { PermissionNext } from "@/permission/next"
Expand Down Expand Up @@ -120,7 +121,7 @@ export namespace Session {
.object({
id: Identifier.schema("session"),
slug: z.string(),
projectID: z.string(),
projectID: ProjectID.zod,
workspaceID: z.string().optional(),
directory: z.string(),
parentID: Identifier.schema("session").optional(),
Expand Down Expand Up @@ -162,7 +163,7 @@ export namespace Session {

export const ProjectInfo = z
.object({
id: z.string(),
id: ProjectID.zod,
name: z.string().optional(),
worktree: z.string(),
})
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/session.sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2"
import type { Snapshot } from "../snapshot"
import type { PermissionNext } from "../permission/next"
import type { ProjectID } from "../project/schema"
import { Timestamps } from "../storage/schema.sql"

type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID">
Expand All @@ -13,6 +14,7 @@ export const SessionTable = sqliteTable(
{
id: text().primaryKey(),
project_id: text()
.$type<ProjectID>()
.notNull()
.references(() => ProjectTable.id, { onDelete: "cascade" }),
workspace_id: text(),
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/worktree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { InstanceBootstrap } from "../project/bootstrap"
import { Project } from "../project/project"
import { Database, eq } from "../storage/db"
import { ProjectTable } from "../project/project.sql"
import type { ProjectID } from "../project/schema"
import { fn } from "../util/fn"
import { Log } from "../util/log"
import { Process } from "../util/process"
Expand Down Expand Up @@ -310,7 +311,7 @@ export namespace Worktree {
return false
}

async function runStartScripts(directory: string, input: { projectID: string; extra?: string }) {
async function runStartScripts(directory: string, input: { projectID: ProjectID; extra?: string }) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, input.projectID)).get())
const project = row ? Project.fromRow(row) : undefined
const startup = project?.commands?.start?.trim() ?? ""
Expand All @@ -322,7 +323,7 @@ export namespace Worktree {
return true
}

function queueStartScripts(directory: string, input: { projectID: string; extra?: string }) {
function queueStartScripts(directory: string, input: { projectID: ProjectID; extra?: string }) {
setTimeout(() => {
const start = async () => {
await runStartScripts(directory, input)
Expand Down
Loading
Loading