feat(app): add workspace startup script to projects
This commit is contained in:
@@ -25,6 +25,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
||||
name: defaultName(),
|
||||
color: props.project.icon?.color || "pink",
|
||||
iconUrl: props.project.icon?.override || "",
|
||||
startup: props.project.commands?.start ?? "",
|
||||
saving: false,
|
||||
})
|
||||
|
||||
@@ -69,15 +70,18 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
||||
|
||||
async function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
if (!props.project.id) return
|
||||
|
||||
setStore("saving", true)
|
||||
const name = store.name.trim() === folderName() ? "" : store.name.trim()
|
||||
const start = store.startup.trim()
|
||||
await globalSDK.client.project.update({
|
||||
projectID: props.project.id,
|
||||
directory: props.project.worktree,
|
||||
name,
|
||||
icon: { color: store.color, override: store.iconUrl },
|
||||
commands: { start },
|
||||
})
|
||||
setStore("saving", false)
|
||||
dialog.close()
|
||||
@@ -215,6 +219,17 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<TextField
|
||||
multiline
|
||||
label={language.t("dialog.project.edit.worktree.startup")}
|
||||
description={language.t("dialog.project.edit.worktree.startup.description")}
|
||||
placeholder={language.t("dialog.project.edit.worktree.startup.placeholder")}
|
||||
value={store.startup}
|
||||
onChange={(v) => setStore("startup", v)}
|
||||
spellcheck={false}
|
||||
class="max-h-40 w-full font-mono text-xs no-scrollbar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
|
||||
@@ -257,6 +257,9 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Recommended: 128x128px",
|
||||
"dialog.project.edit.color": "Color",
|
||||
"dialog.project.edit.color.select": "Select {{color}} color",
|
||||
"dialog.project.edit.worktree.startup": "Workspace startup script",
|
||||
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
|
||||
|
||||
"context.breakdown.title": "Context Breakdown",
|
||||
"context.breakdown.note": 'Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.',
|
||||
|
||||
@@ -29,6 +29,11 @@ export namespace Project {
|
||||
color: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
commands: z
|
||||
.object({
|
||||
start: z.string().optional().describe("Startup script to run when creating a new workspace (worktree)"),
|
||||
})
|
||||
.optional(),
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
updated: z.number(),
|
||||
@@ -287,6 +292,7 @@ export namespace Project {
|
||||
projectID: z.string(),
|
||||
name: z.string().optional(),
|
||||
icon: Info.shape.icon.optional(),
|
||||
commands: Info.shape.commands.optional(),
|
||||
}),
|
||||
async (input) => {
|
||||
const result = await Storage.update<Info>(["project", input.projectID], (draft) => {
|
||||
@@ -299,6 +305,16 @@ export namespace Project {
|
||||
if (input.icon.override !== undefined) draft.icon.override = input.icon.override || undefined
|
||||
if (input.icon.color !== undefined) draft.icon.color = input.icon.color
|
||||
}
|
||||
|
||||
if (input.commands?.start !== undefined) {
|
||||
const start = input.commands.start || undefined
|
||||
draft.commands = {
|
||||
...(draft.commands ?? {}),
|
||||
}
|
||||
draft.commands.start = start
|
||||
if (!draft.commands.start) draft.commands = undefined
|
||||
}
|
||||
|
||||
draft.time.updated = Date.now()
|
||||
})
|
||||
GlobalBus.emit("event", {
|
||||
|
||||
@@ -90,7 +90,7 @@ export const ExperimentalRoutes = lazy(() =>
|
||||
"/worktree",
|
||||
describeRoute({
|
||||
summary: "Create worktree",
|
||||
description: "Create a new git worktree for the current project.",
|
||||
description: "Create a new git worktree for the current project and run any configured startup scripts.",
|
||||
operationId: "worktree.create",
|
||||
responses: {
|
||||
200: {
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ProjectRoutes = lazy(() =>
|
||||
"/:projectID",
|
||||
describeRoute({
|
||||
summary: "Update project",
|
||||
description: "Update project properties such as name, icon and color.",
|
||||
description: "Update project properties such as name, icon, and commands.",
|
||||
operationId: "project.update",
|
||||
responses: {
|
||||
200: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NamedError } from "@opencode-ai/util/error"
|
||||
import { Global } from "../global"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Project } from "../project/project"
|
||||
import { Storage } from "../storage/storage"
|
||||
import { fn } from "../util/fn"
|
||||
import { Config } from "@/config/config"
|
||||
|
||||
@@ -25,7 +26,10 @@ export namespace Worktree {
|
||||
export const CreateInput = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
startCommand: z.string().optional(),
|
||||
startCommand: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Additional startup script to run after the project's start command"),
|
||||
})
|
||||
.meta({
|
||||
ref: "WorktreeCreateInput",
|
||||
@@ -238,12 +242,23 @@ export namespace Worktree {
|
||||
throw new CreateFailedError({ message: errorText(created) || "Failed to create git worktree" })
|
||||
}
|
||||
|
||||
const cmd = input?.startCommand?.trim()
|
||||
if (!cmd) return info
|
||||
const project = await Storage.read<Project.Info>(["project", Instance.project.id]).catch(() => Instance.project)
|
||||
const startup = project.commands?.start?.trim()
|
||||
if (startup) {
|
||||
const ran = await runStartCommand(info.directory, startup)
|
||||
if (ran.exitCode !== 0) {
|
||||
throw new StartCommandFailedError({
|
||||
message: errorText(ran) || "Project start command failed",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const ran = await runStartCommand(info.directory, cmd)
|
||||
if (ran.exitCode !== 0) {
|
||||
throw new StartCommandFailedError({ message: errorText(ran) || "Worktree start command failed" })
|
||||
const extra = input?.startCommand?.trim()
|
||||
if (extra) {
|
||||
const ran = await runStartCommand(info.directory, extra)
|
||||
if (ran.exitCode !== 0) {
|
||||
throw new StartCommandFailedError({ message: errorText(ran) || "Worktree start command failed" })
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
@@ -293,7 +293,7 @@ export class Project extends HeyApiClient {
|
||||
/**
|
||||
* Update project
|
||||
*
|
||||
* Update project properties such as name, icon and color.
|
||||
* Update project properties such as name, icon, and commands.
|
||||
*/
|
||||
public update<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
@@ -305,6 +305,12 @@ export class Project extends HeyApiClient {
|
||||
override?: string
|
||||
color?: string
|
||||
}
|
||||
commands?: {
|
||||
/**
|
||||
* Startup script to run when creating a new workspace (worktree)
|
||||
*/
|
||||
start?: string
|
||||
}
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -317,6 +323,7 @@ export class Project extends HeyApiClient {
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "body", key: "name" },
|
||||
{ in: "body", key: "icon" },
|
||||
{ in: "body", key: "commands" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -718,7 +725,7 @@ export class Worktree extends HeyApiClient {
|
||||
/**
|
||||
* Create worktree
|
||||
*
|
||||
* Create a new git worktree for the current project.
|
||||
* Create a new git worktree for the current project and run any configured startup scripts.
|
||||
*/
|
||||
public create<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
|
||||
@@ -28,6 +28,12 @@ export type Project = {
|
||||
override?: string
|
||||
color?: string
|
||||
}
|
||||
commands?: {
|
||||
/**
|
||||
* Startup script to run when creating a new workspace (worktree)
|
||||
*/
|
||||
start?: string
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
@@ -1906,6 +1912,9 @@ export type Worktree = {
|
||||
|
||||
export type WorktreeCreateInput = {
|
||||
name?: string
|
||||
/**
|
||||
* Additional startup script to run after the project's start command
|
||||
*/
|
||||
startCommand?: string
|
||||
}
|
||||
|
||||
@@ -2233,6 +2242,12 @@ export type ProjectUpdateData = {
|
||||
override?: string
|
||||
color?: string
|
||||
}
|
||||
commands?: {
|
||||
/**
|
||||
* Startup script to run when creating a new workspace (worktree)
|
||||
*/
|
||||
start?: string
|
||||
}
|
||||
}
|
||||
path: {
|
||||
projectID: string
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user