| 1 | import type { ProjectId, ProjectScript, ServerSettings } from "@t3tools/contracts"; |
| 2 | |
| 3 | type ProjectScriptSettings = Pick< |
| 4 | ServerSettings, |
| 5 | | "defaultProjectScripts" |
| 6 | | "projectScriptOverrides" |
| 7 | | "projectSettingsOverrides" |
| 8 | | "projectSettingsFolded" |
| 9 | >; |
| 10 | |
| 11 | /** |
| 12 | * The project's override wins, then environment defaults. Until the legacy |
| 13 | * fields have been folded into `projectSettingsOverrides`, the old map (null |
| 14 | * there meant "reset to machine defaults") and the aggregate's own scripts |
| 15 | * still count, so a server that has not run the fold yet behaves as before. |
| 16 | */ |
| 17 | export function resolveProjectScripts( |
| 18 | settings: ProjectScriptSettings, |
| 19 | project: { id: ProjectId; scripts: readonly ProjectScript[] }, |
| 20 | ): readonly ProjectScript[] { |
| 21 | const override = settings.projectSettingsOverrides[project.id]?.defaultProjectScripts; |
| 22 | if (override !== undefined) return override; |
| 23 | if (settings.projectSettingsFolded) return settings.defaultProjectScripts; |
| 24 | const legacy = settings.projectScriptOverrides[project.id]; |
| 25 | if (legacy === null) return settings.defaultProjectScripts; |
| 26 | return legacy ?? (project.scripts.length > 0 ? project.scripts : settings.defaultProjectScripts); |
| 27 | } |
| 28 | |
| 29 | export function projectScriptsInheritDefaults( |
| 30 | settings: ProjectScriptSettings, |
| 31 | project: { id: ProjectId; scripts: readonly ProjectScript[] }, |
| 32 | ): boolean { |
| 33 | if (settings.projectSettingsOverrides[project.id]?.defaultProjectScripts !== undefined) { |
| 34 | return false; |
| 35 | } |
| 36 | if (settings.projectSettingsFolded) return true; |
| 37 | const legacy = settings.projectScriptOverrides[project.id]; |
| 38 | return legacy === null || (legacy === undefined && project.scripts.length === 0); |
| 39 | } |
| 40 | |
| 41 | interface ProjectScriptRuntimeEnvInput { |
| 42 | project: { |
| 43 | cwd: string; |
| 44 | }; |
| 45 | worktreePath?: string | null; |
| 46 | extraEnv?: Record<string, string>; |
| 47 | } |
| 48 | |
| 49 | export function projectScriptCwd(input: { |
| 50 | project: { |
| 51 | cwd: string; |
| 52 | }; |
| 53 | worktreePath?: string | null; |
| 54 | }): string { |
| 55 | return input.worktreePath ?? input.project.cwd; |
| 56 | } |
| 57 | |
| 58 | export function projectScriptRuntimeEnv( |
| 59 | input: ProjectScriptRuntimeEnvInput, |
| 60 | ): Record<string, string> { |
| 61 | const env: Record<string, string> = { |
| 62 | T3CODE_PROJECT_ROOT: input.project.cwd, |
| 63 | }; |
| 64 | if (input.worktreePath) { |
| 65 | env.T3CODE_WORKTREE_PATH = input.worktreePath; |
| 66 | } |
| 67 | if (input.extraEnv) { |
| 68 | return { ...env, ...input.extraEnv }; |
| 69 | } |
| 70 | return env; |
| 71 | } |
| 72 | |
| 73 | export function setupProjectScript(scripts: readonly ProjectScript[]): ProjectScript | null { |
| 74 | return scripts.find((script) => script.runOnWorktreeCreate) ?? null; |
| 75 | } |
| 76 | |
| 77 | /** Menu label naming the lifecycle roles a script runs in, e.g. "Clean (on settle)". */ |
| 78 | export function projectScriptMenuLabel(script: ProjectScript): string { |
| 79 | const roles = [ |
| 80 | ...(script.runOnWorktreeCreate ? ["setup"] : []), |
| 81 | ...(script.runOnSettle ? ["on settle"] : []), |
| 82 | ]; |
| 83 | return roles.length === 0 ? script.name : `${script.name} (${roles.join(", ")})`; |
| 84 | } |
| 85 | |
| 86 | export function settleProjectScript(scripts: readonly ProjectScript[]): ProjectScript | null { |
| 87 | return scripts.find((script) => script.runOnSettle === true) ?? null; |
| 88 | } |