Skip to content
Source
Sign inSign up
Open History to see the last commit

settingsScope.ts

150 lines4.9 KB

1import type { EnvironmentId } from "@t3tools/contracts";
2​
3import type {
4 SidebarProjectGroupMember,
5 SidebarProjectSnapshot,
6} from "../../sidebarProjectGrouping";
7import type { EnvironmentPresentation } from "../../state/environments";
8​
9export interface SettingsScopeSearch {
10 project?: string | undefined;
11 machine?: string | undefined;
12 checkout?: string | undefined;
14​
15type ScopeTargets = {
16 label: string;
17 members: readonly SidebarProjectGroupMember[];
18 environmentIds: readonly EnvironmentId[];
19};
20​
21export type ResolvedSettingsScope = ScopeTargets &
22 (
23 | { kind: "all" }
24 | { kind: "environment"; environmentId: EnvironmentId }
25 | {
26 kind: "project";
27 group: SidebarProjectSnapshot;
28 environmentId: EnvironmentId | null;
29 }
30 | {
31 kind: "checkout";
32 group: SidebarProjectSnapshot;
33 checkout: SidebarProjectGroupMember;
34 environmentId: EnvironmentId;
35 }
36 | {
37 kind: "unavailable";
38 reason: "project-required" | "project-missing" | "environment-missing" | "checkout-missing";
39 message: string;
40 }
41 );
42​
43/** Stale IDs remain visible to the resolver so a removed target reads as unavailable, not as "all". */
44export function validateSettingsScopeSearch(raw: Record<string, unknown>): SettingsScopeSearch {
45 const stringValue = (value: unknown) =>
46 typeof value === "string" && value.trim().length > 0 ? value : undefined;
47 const project = stringValue(raw.project);
48 const machine = stringValue(raw.machine);
49 const checkout = stringValue(raw.checkout);
50 return {
51 ...(project === undefined ? {} : { project }),
52 ...(machine === undefined ? {} : { machine }),
53 ...(checkout === undefined ? {} : { checkout }),
54 };
56​
57/** An unavailable selection never broadens a subsequent write. */
58export function resolveSettingsScope(
59 search: SettingsScopeSearch,
60 groups: readonly SidebarProjectSnapshot[],
61 environments: readonly Pick<EnvironmentPresentation, "environmentId" | "label">[],
62): ResolvedSettingsScope {
63 const unavailable = (
64 reason: Extract<ResolvedSettingsScope, { kind: "unavailable" }>["reason"],
65 message: string,
66 ): ResolvedSettingsScope => ({
67 kind: "unavailable",
68 reason,
69 label: "Unavailable selection",
70 message,
71 members: [],
72 environmentIds: [],
73 });
74​
75 if (search.checkout && !search.project) {
76 return unavailable("project-required", "Select a project to choose one of its checkouts.");
77 }
78​
79 const environment = environments.find((candidate) => candidate.environmentId === search.machine);
80 if (search.machine && !environment) {
81 return unavailable("environment-missing", "This environment is no longer available.");
82 }
83​
84 if (search.project) {
85 const group = groups.find((candidate) => candidate.projectKey === search.project);
86 if (!group) return unavailable("project-missing", "This project is no longer available.");
87 const members = group.memberProjects.filter(
88 (member) =>
89 (search.machine === undefined || member.environmentId === search.machine) &&
90 (search.checkout === undefined || member.physicalProjectKey === search.checkout),
91 );
92 if (members.length === 0) {
93 return unavailable(
94 "checkout-missing",
95 search.checkout
96 ? "This checkout is no longer available in the selected project and environment."
97 : "This project has no checkout on this environment.",
98 );
99 }
100 if (search.checkout) {
101 const checkout = members[0]!;
102 const checkoutEnvironment = environments.find(
103 (candidate) => candidate.environmentId === checkout.environmentId,
104 );
105 if (!checkoutEnvironment) {
106 return unavailable(
107 "environment-missing",
108 "This checkout's environment is no longer available.",
109 );
110 }
111 const sharesEnvironment = group.memberProjects.some(
112 (member) =>
113 member.environmentId === checkout.environmentId &&
114 member.physicalProjectKey !== checkout.physicalProjectKey,
115 );
116 return {
117 kind: "checkout",
118 group,
119 checkout,
120 environmentId: checkout.environmentId,
121 label: `${group.displayName} / ${checkoutEnvironment.label}${sharesEnvironment ? ` · ${checkout.workspaceRoot}` : ""}`,
122 members,
123 environmentIds: [checkout.environmentId],
124 };
125 }
126 return {
127 kind: "project",
128 group,
129 environmentId: environment?.environmentId ?? null,
130 label: `${group.displayName} / ${environment?.label ?? "All checkouts"}`,
131 members,
132 environmentIds: [...new Set(members.map((member) => member.environmentId))],
133 };
134 }
135 if (environment) {
136 return {
137 kind: "environment",
138 environmentId: environment.environmentId,
139 label: environment.label,
140 members: [],
141 environmentIds: [environment.environmentId],
142 };
143 }
144 return {
145 kind: "all",
146 label: "All environments",
147 members: [],
148 environmentIds: environments.map((candidate) => candidate.environmentId),
149 };