feat: Lifecycle hooks (#219)
* flatten lifecycle hooks into RepoSettings string fields replace the separate LifecycleHook model with setupScript and postCheckoutScript string fields directly on RepoSettings. move the UI into the Agent settings section alongside environment variables and custom instructions. delete the standalone lifecycle-hooks API route, component, and schema since the existing settings PATCH endpoint handles the new fields automatically. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: pass env to lifecycle hook spawn so scripts can use package managers Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
3a7145db1a
commit
1d59fd3d21
@@ -0,0 +1,37 @@
|
||||
import { LIFECYCLE_HOOK_TIMEOUT_MS } from "../lifecycle.ts";
|
||||
import { log } from "./cli.ts";
|
||||
import { spawn } from "./subprocess.ts";
|
||||
|
||||
export interface ExecuteLifecycleHookParams {
|
||||
event: string;
|
||||
script: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a lifecycle hook script if one is configured.
|
||||
* runs the script in a bash shell with a timeout.
|
||||
*/
|
||||
export async function executeLifecycleHook(params: ExecuteLifecycleHookParams): Promise<void> {
|
||||
if (!params.script) return;
|
||||
|
||||
log.info(`» executing ${params.event} lifecycle hook...`);
|
||||
|
||||
const result = await spawn({
|
||||
cmd: "bash",
|
||||
args: ["-c", params.script],
|
||||
env: process.env,
|
||||
timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
|
||||
activityTimeout: 0,
|
||||
onStdout: (chunk) => process.stdout.write(chunk),
|
||||
onStderr: (chunk) => process.stderr.write(chunk),
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
const output = result.stderr || result.stdout;
|
||||
throw new Error(
|
||||
`lifecycle hook '${params.event}' failed with exit code ${result.exitCode}:\n${output}`
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`» ${params.event} lifecycle hook completed successfully`);
|
||||
}
|
||||
+12
-1
@@ -11,6 +11,8 @@ export interface Mode {
|
||||
export interface RepoSettings {
|
||||
defaultAgent: AgentName | null;
|
||||
modes: Mode[];
|
||||
setupScript: string | null;
|
||||
postCheckoutScript: string | null;
|
||||
repoInstructions: string;
|
||||
web: ToolPermission;
|
||||
search: ToolPermission;
|
||||
@@ -26,6 +28,8 @@ export interface RunContext {
|
||||
const defaultSettings: RepoSettings = {
|
||||
defaultAgent: null,
|
||||
modes: [],
|
||||
setupScript: null,
|
||||
postCheckoutScript: null,
|
||||
repoInstructions: "",
|
||||
web: "enabled",
|
||||
search: "enabled",
|
||||
@@ -81,7 +85,14 @@ export async function fetchRunContext(params: {
|
||||
}
|
||||
|
||||
return {
|
||||
settings: data.settings ?? defaultSettings,
|
||||
settings: {
|
||||
...defaultSettings,
|
||||
...data.settings,
|
||||
// ensure arrays are never undefined (API may omit new fields for existing repos)
|
||||
modes: data.settings?.modes ?? [],
|
||||
setupScript: data.settings?.setupScript ?? null,
|
||||
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
|
||||
},
|
||||
apiToken: data.apiToken,
|
||||
};
|
||||
} catch {
|
||||
|
||||
+8
-12
@@ -45,15 +45,19 @@ export function setupTestRepo(options: SetupOptions): void {
|
||||
}
|
||||
}
|
||||
|
||||
interface SetupGitParams {
|
||||
export interface GitContext {
|
||||
gitToken: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
event: PayloadEvent;
|
||||
octokit: OctokitWithPlugins;
|
||||
toolState: ToolState;
|
||||
// restricted bash mode: disables git hooks to prevent token exfiltration
|
||||
restricted: boolean;
|
||||
postCheckoutScript: string | null;
|
||||
}
|
||||
|
||||
export interface SetupGitParams extends GitContext {
|
||||
event: PayloadEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,15 +153,7 @@ export async function setupGit(params: SetupGitParams): Promise<void> {
|
||||
// PR event: checkout PR branch using shared helper
|
||||
const prNumber = params.event.issue_number;
|
||||
|
||||
// use shared checkout helper (handles fork remotes, push config, etc.)
|
||||
// use shared checkout helper (handles fork remotes, push config, post-checkout hook)
|
||||
// this updates toolState.pushUrl for fork PRs and sets toolState.issueNumber
|
||||
await checkoutPrBranch({
|
||||
octokit: params.octokit,
|
||||
owner: params.owner,
|
||||
name: params.name,
|
||||
gitToken: params.gitToken,
|
||||
pullNumber: prNumber,
|
||||
toolState: params.toolState,
|
||||
restricted: params.restricted,
|
||||
});
|
||||
await checkoutPrBranch(prNumber, params);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user