1import type { ProjectId, ProjectScript, ServerSettings } from "@t3tools/contracts";
2
3type 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 */
17export 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
29export 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
41interface ProjectScriptRuntimeEnvInput {
42 project: {
43 cwd: string;
44 };
45 worktreePath?: string | null;
46 extraEnv?: Record<string, string>;
47}
48
49export function projectScriptCwd(input: {
50 project: {
51 cwd: string;
52 };
53 worktreePath?: string | null;
54}): string {
55 return input.worktreePath ?? input.project.cwd;
56}
57
58export 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
73export 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)". */
78export 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
86export function settleProjectScript(scripts: readonly ProjectScript[]): ProjectScript | null {
87 return scripts.find((script) => script.runOnSettle === true) ?? null;
88}