@bdsqqq/lnr-cli 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/src/bench-lnr-overhead.ts +160 -0
- package/src/e2e-mutations.test.ts +378 -0
- package/src/e2e-readonly.test.ts +103 -0
- package/src/generated/doc.ts +270 -0
- package/src/generated/issue.ts +807 -0
- package/src/generated/label.ts +273 -0
- package/src/generated/project.ts +596 -0
- package/src/generated/template.ts +157 -0
- package/src/hand-crafted/issue.ts +27 -0
- package/src/lib/adapters/doc.ts +14 -0
- package/src/lib/adapters/index.ts +4 -0
- package/src/lib/adapters/issue.ts +32 -0
- package/src/lib/adapters/label.ts +20 -0
- package/src/lib/adapters/project.ts +23 -0
- package/src/lib/arktype-config.ts +18 -0
- package/src/lib/command-introspection.ts +97 -0
- package/src/lib/dispatch-effects.test.ts +297 -0
- package/src/lib/error.ts +37 -1
- package/src/lib/operation-spec.test.ts +317 -0
- package/src/lib/operation-spec.ts +11 -0
- package/src/lib/operation-specs.ts +21 -0
- package/src/lib/output.test.ts +3 -1
- package/src/lib/output.ts +1 -296
- package/src/lib/renderers/comments.ts +300 -0
- package/src/lib/renderers/detail.ts +61 -0
- package/src/lib/renderers/index.ts +2 -0
- package/src/router/agent-sessions.ts +253 -0
- package/src/router/auth.ts +6 -5
- package/src/router/config.ts +7 -6
- package/src/router/contract.test.ts +364 -0
- package/src/router/cycles.ts +372 -95
- package/src/router/git-automation-states.ts +355 -0
- package/src/router/git-automation-target-branches.ts +309 -0
- package/src/router/index.ts +26 -8
- package/src/router/initiatives.ts +260 -0
- package/src/router/me.ts +8 -7
- package/src/router/notifications.ts +176 -0
- package/src/router/roadmaps.ts +172 -0
- package/src/router/search.ts +7 -6
- package/src/router/teams.ts +82 -24
- package/src/router/users.ts +126 -0
- package/src/router/views.ts +399 -0
- package/src/router/docs.ts +0 -153
- package/src/router/issues.ts +0 -600
- package/src/router/labels.ts +0 -192
- package/src/router/projects.ts +0 -220
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import type { OperationSpec } from "../lib/operation-spec";
|
|
2
|
+
import "../lib/arktype-config";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
import {
|
|
5
|
+
getClient,
|
|
6
|
+
listGitAutomationStates,
|
|
7
|
+
createGitAutomationState,
|
|
8
|
+
updateGitAutomationState,
|
|
9
|
+
deleteGitAutomationState,
|
|
10
|
+
findTeamByKeyOrName,
|
|
11
|
+
getTeamStates,
|
|
12
|
+
type GitAutomationState,
|
|
13
|
+
type GitAutomationEvent,
|
|
14
|
+
} from "@bdsqqq/lnr-core";
|
|
15
|
+
import { router, procedure } from "./trpc";
|
|
16
|
+
import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
|
|
17
|
+
import {
|
|
18
|
+
outputJson,
|
|
19
|
+
outputQuiet,
|
|
20
|
+
outputTable,
|
|
21
|
+
getOutputFormat,
|
|
22
|
+
type OutputOptions,
|
|
23
|
+
type TableColumn,
|
|
24
|
+
} from "../lib/output";
|
|
25
|
+
|
|
26
|
+
const gitAutomationEvents = ["draft", "merge", "mergeable", "review", "start"] as const;
|
|
27
|
+
|
|
28
|
+
export const listGitAutomationStatesInput = type({
|
|
29
|
+
team: type("string").describe("team key"),
|
|
30
|
+
"json?": type("boolean").describe("output as json"),
|
|
31
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
32
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const gitAutomationStateInput = type({
|
|
36
|
+
idOrEvent: type("string").configure({ positional: true }).describe("automation id, event name, or 'new'"),
|
|
37
|
+
team: type("string").describe("team key"),
|
|
38
|
+
"event?": type("'draft' | 'merge' | 'mergeable' | 'review' | 'start'").describe("git event: draft, merge, mergeable, review, start"),
|
|
39
|
+
"state?": type("string").describe("workflow state name to transition to"),
|
|
40
|
+
"branch?": type("string").describe("target branch ID"),
|
|
41
|
+
"delete?": type("boolean").describe("delete the automation"),
|
|
42
|
+
"json?": type("boolean").describe("output as json"),
|
|
43
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
44
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
type GitAutomationStateCliInput = typeof gitAutomationStateInput.infer;
|
|
48
|
+
|
|
49
|
+
const automationColumns: TableColumn<GitAutomationState>[] = [
|
|
50
|
+
{ header: "EVENT", value: (a) => a.event, width: 12 },
|
|
51
|
+
{ header: "STATE", value: (a) => a.stateName ?? "(no action)", width: 20 },
|
|
52
|
+
{ header: "BRANCH", value: (a) => a.targetBranchPattern ?? "(all)", width: 20 },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const verboseAutomationColumns: TableColumn<GitAutomationState>[] = [
|
|
56
|
+
...automationColumns,
|
|
57
|
+
{ header: "ID", value: (a) => a.id, width: 36 },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const gitAutomationStateOperations = ["create", "read", "update", "delete"] as const;
|
|
61
|
+
type Operation = (typeof gitAutomationStateOperations)[number];
|
|
62
|
+
|
|
63
|
+
export const gitAutomationStateMutationFlags: readonly (keyof GitAutomationStateCliInput)[] = [
|
|
64
|
+
"event", "state", "branch"
|
|
65
|
+
] as const;
|
|
66
|
+
|
|
67
|
+
export function inferOperation(input: GitAutomationStateCliInput): Operation {
|
|
68
|
+
if (input.idOrEvent === "new") return "create";
|
|
69
|
+
if (input.delete) return "delete";
|
|
70
|
+
|
|
71
|
+
for (const flag of gitAutomationStateMutationFlags) {
|
|
72
|
+
if (input[flag] !== undefined) return "update";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return "read";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const gitAutomationStateOperationSpec: OperationSpec<GitAutomationStateCliInput, Operation> = {
|
|
79
|
+
command: "git-automation",
|
|
80
|
+
operations: gitAutomationStateOperations,
|
|
81
|
+
mutationFlags: gitAutomationStateMutationFlags,
|
|
82
|
+
inferOperation,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
async function handleListGitAutomationStates(
|
|
86
|
+
input: typeof listGitAutomationStatesInput.infer
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
try {
|
|
89
|
+
const client = getClient();
|
|
90
|
+
const automations = await listGitAutomationStates(client, input.team);
|
|
91
|
+
|
|
92
|
+
const outputOpts: OutputOptions = {
|
|
93
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
94
|
+
verbose: input.verbose,
|
|
95
|
+
};
|
|
96
|
+
const format = getOutputFormat(outputOpts);
|
|
97
|
+
|
|
98
|
+
if (format === "json") {
|
|
99
|
+
outputJson(automations);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (format === "quiet") {
|
|
104
|
+
outputQuiet(automations.map((a) => a.id));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const columns = input.verbose ? verboseAutomationColumns : automationColumns;
|
|
109
|
+
outputTable(automations, columns, outputOpts);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
handleApiError(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function handleShowGitAutomationState(
|
|
116
|
+
idOrEvent: string,
|
|
117
|
+
input: GitAutomationStateCliInput
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
try {
|
|
120
|
+
const client = getClient();
|
|
121
|
+
|
|
122
|
+
const outputOpts: OutputOptions = {
|
|
123
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
124
|
+
verbose: input.verbose,
|
|
125
|
+
};
|
|
126
|
+
const format = getOutputFormat(outputOpts);
|
|
127
|
+
|
|
128
|
+
const automations = await listGitAutomationStates(client, input.team);
|
|
129
|
+
let automation: GitAutomationState | null = null;
|
|
130
|
+
|
|
131
|
+
if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
|
|
132
|
+
automation = automations.find((a) => a.event === idOrEvent) ?? null;
|
|
133
|
+
} else {
|
|
134
|
+
automation = automations.find((a) => a.id === idOrEvent) ?? null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!automation) {
|
|
138
|
+
exitWithError(
|
|
139
|
+
`git automation "${idOrEvent}" not found`,
|
|
140
|
+
`try: lnr git-automations --team ${input.team}`,
|
|
141
|
+
EXIT_CODES.NOT_FOUND
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (format === "json") {
|
|
146
|
+
outputJson(automation);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (format === "quiet") {
|
|
151
|
+
console.log(automation.id);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`git automation: ${automation.event}`);
|
|
156
|
+
console.log(` state: ${automation.stateName ?? "(no action)"}`);
|
|
157
|
+
console.log(` branch: ${automation.targetBranchPattern ?? "(all branches)"}`);
|
|
158
|
+
if (input.verbose) {
|
|
159
|
+
console.log(` id: ${automation.id}`);
|
|
160
|
+
console.log(` team: ${automation.teamKey ?? automation.teamId}`);
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
handleApiError(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function handleCreateGitAutomationState(
|
|
168
|
+
input: GitAutomationStateCliInput
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
if (!input.event) {
|
|
171
|
+
exitWithError(
|
|
172
|
+
"--event is required",
|
|
173
|
+
"usage: lnr git-automation new --team ENG --event merge --state Done"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const client = getClient();
|
|
179
|
+
|
|
180
|
+
const team = await findTeamByKeyOrName(client, input.team);
|
|
181
|
+
if (!team) {
|
|
182
|
+
exitWithError(`team "${input.team}" not found`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let stateId: string | undefined;
|
|
186
|
+
if (input.state) {
|
|
187
|
+
const states = await getTeamStates(client, team.id);
|
|
188
|
+
const state = states.find(
|
|
189
|
+
(s) => s.name.toLowerCase() === input.state?.toLowerCase()
|
|
190
|
+
);
|
|
191
|
+
if (!state) {
|
|
192
|
+
exitWithError(
|
|
193
|
+
`state "${input.state}" not found in team "${input.team}"`,
|
|
194
|
+
`available states: ${states.map((s) => s.name).join(", ")}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
stateId = state.id;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const automation = await createGitAutomationState(client, {
|
|
201
|
+
teamId: team.id,
|
|
202
|
+
event: input.event,
|
|
203
|
+
stateId,
|
|
204
|
+
targetBranchId: input.branch,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (automation) {
|
|
208
|
+
console.log(`created git automation: ${automation.event} → ${automation.stateName ?? "(no action)"}`);
|
|
209
|
+
} else {
|
|
210
|
+
exitWithError("failed to create git automation");
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
handleApiError(error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function handleUpdateGitAutomationState(
|
|
218
|
+
idOrEvent: string,
|
|
219
|
+
input: GitAutomationStateCliInput
|
|
220
|
+
): Promise<void> {
|
|
221
|
+
try {
|
|
222
|
+
const client = getClient();
|
|
223
|
+
|
|
224
|
+
const automations = await listGitAutomationStates(client, input.team);
|
|
225
|
+
let automation: GitAutomationState | null = null;
|
|
226
|
+
|
|
227
|
+
if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
|
|
228
|
+
automation = automations.find((a) => a.event === idOrEvent) ?? null;
|
|
229
|
+
} else {
|
|
230
|
+
automation = automations.find((a) => a.id === idOrEvent) ?? null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!automation) {
|
|
234
|
+
exitWithError(
|
|
235
|
+
`git automation "${idOrEvent}" not found`,
|
|
236
|
+
`try: lnr git-automations --team ${input.team}`,
|
|
237
|
+
EXIT_CODES.NOT_FOUND
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let stateId: string | undefined;
|
|
242
|
+
if (input.state) {
|
|
243
|
+
const team = await findTeamByKeyOrName(client, input.team);
|
|
244
|
+
if (!team) {
|
|
245
|
+
exitWithError(`team "${input.team}" not found`);
|
|
246
|
+
}
|
|
247
|
+
const states = await getTeamStates(client, team.id);
|
|
248
|
+
const state = states.find(
|
|
249
|
+
(s) => s.name.toLowerCase() === input.state?.toLowerCase()
|
|
250
|
+
);
|
|
251
|
+
if (!state) {
|
|
252
|
+
exitWithError(
|
|
253
|
+
`state "${input.state}" not found in team "${input.team}"`,
|
|
254
|
+
`available states: ${states.map((s) => s.name).join(", ")}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
stateId = state.id;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const success = await updateGitAutomationState(client, automation.id, {
|
|
261
|
+
event: input.event,
|
|
262
|
+
stateId,
|
|
263
|
+
targetBranchId: input.branch,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!success) {
|
|
267
|
+
exitWithError(
|
|
268
|
+
`failed to update git automation "${idOrEvent}"`,
|
|
269
|
+
undefined,
|
|
270
|
+
EXIT_CODES.NOT_FOUND
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(`updated git automation: ${input.event ?? automation.event}`);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
handleApiError(error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function handleDeleteGitAutomationState(
|
|
281
|
+
idOrEvent: string,
|
|
282
|
+
input: GitAutomationStateCliInput
|
|
283
|
+
): Promise<void> {
|
|
284
|
+
try {
|
|
285
|
+
const client = getClient();
|
|
286
|
+
|
|
287
|
+
const automations = await listGitAutomationStates(client, input.team);
|
|
288
|
+
let automation: GitAutomationState | null = null;
|
|
289
|
+
|
|
290
|
+
if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
|
|
291
|
+
automation = automations.find((a) => a.event === idOrEvent) ?? null;
|
|
292
|
+
} else {
|
|
293
|
+
automation = automations.find((a) => a.id === idOrEvent) ?? null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!automation) {
|
|
297
|
+
exitWithError(
|
|
298
|
+
`git automation "${idOrEvent}" not found`,
|
|
299
|
+
`try: lnr git-automations --team ${input.team}`,
|
|
300
|
+
EXIT_CODES.NOT_FOUND
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const success = await deleteGitAutomationState(client, automation.id);
|
|
305
|
+
|
|
306
|
+
if (!success) {
|
|
307
|
+
exitWithError(
|
|
308
|
+
`failed to delete git automation "${idOrEvent}"`,
|
|
309
|
+
undefined,
|
|
310
|
+
EXIT_CODES.NOT_FOUND
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(`deleted git automation: ${automation.event}`);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
handleApiError(error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export const gitAutomationStatesRouter = router({
|
|
321
|
+
"git-automations": procedure
|
|
322
|
+
.meta({
|
|
323
|
+
aliases: { command: ["ga"] },
|
|
324
|
+
description: "list git automation states for a team",
|
|
325
|
+
})
|
|
326
|
+
.input(listGitAutomationStatesInput)
|
|
327
|
+
.query(async ({ input }) => {
|
|
328
|
+
await handleListGitAutomationStates(input);
|
|
329
|
+
}),
|
|
330
|
+
|
|
331
|
+
"git-automation": procedure
|
|
332
|
+
.meta({
|
|
333
|
+
description: "show, create, update, or delete a git automation state",
|
|
334
|
+
})
|
|
335
|
+
.input(gitAutomationStateInput)
|
|
336
|
+
.mutation(async ({ input }) => {
|
|
337
|
+
const operation = inferOperation(input);
|
|
338
|
+
|
|
339
|
+
switch (operation) {
|
|
340
|
+
case "create":
|
|
341
|
+
await handleCreateGitAutomationState(input);
|
|
342
|
+
break;
|
|
343
|
+
case "delete":
|
|
344
|
+
await handleDeleteGitAutomationState(input.idOrEvent, input);
|
|
345
|
+
break;
|
|
346
|
+
case "update":
|
|
347
|
+
await handleUpdateGitAutomationState(input.idOrEvent, input);
|
|
348
|
+
break;
|
|
349
|
+
case "read":
|
|
350
|
+
default:
|
|
351
|
+
await handleShowGitAutomationState(input.idOrEvent, input);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { OperationSpec } from "../lib/operation-spec";
|
|
2
|
+
import "../lib/arktype-config";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
import {
|
|
5
|
+
getClient,
|
|
6
|
+
listGitAutomationTargetBranches,
|
|
7
|
+
createGitAutomationTargetBranch,
|
|
8
|
+
updateGitAutomationTargetBranch,
|
|
9
|
+
deleteGitAutomationTargetBranch,
|
|
10
|
+
findTeamByKeyOrName,
|
|
11
|
+
type GitAutomationTargetBranch,
|
|
12
|
+
} from "@bdsqqq/lnr-core";
|
|
13
|
+
import { router, procedure } from "./trpc";
|
|
14
|
+
import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
|
|
15
|
+
import {
|
|
16
|
+
outputJson,
|
|
17
|
+
outputQuiet,
|
|
18
|
+
outputTable,
|
|
19
|
+
getOutputFormat,
|
|
20
|
+
type OutputOptions,
|
|
21
|
+
type TableColumn,
|
|
22
|
+
} from "../lib/output";
|
|
23
|
+
|
|
24
|
+
export const listGitAutomationTargetBranchesInput = type({
|
|
25
|
+
team: type("string").describe("team key"),
|
|
26
|
+
"json?": type("boolean").describe("output as json"),
|
|
27
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
28
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const gitAutomationTargetBranchInput = type({
|
|
32
|
+
patternOrId: type("string").configure({ positional: true }).describe("branch pattern, id, or 'new'"),
|
|
33
|
+
team: type("string").describe("team key"),
|
|
34
|
+
"pattern?": type("string").describe("branch pattern"),
|
|
35
|
+
"regex?": type("boolean").describe("treat pattern as regex"),
|
|
36
|
+
"delete?": type("boolean").describe("delete the target branch"),
|
|
37
|
+
"json?": type("boolean").describe("output as json"),
|
|
38
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
39
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
type GitAutomationTargetBranchCliInput = typeof gitAutomationTargetBranchInput.infer;
|
|
43
|
+
|
|
44
|
+
const branchColumns: TableColumn<GitAutomationTargetBranch>[] = [
|
|
45
|
+
{ header: "PATTERN", value: (b) => b.branchPattern, width: 30 },
|
|
46
|
+
{ header: "REGEX", value: (b) => (b.isRegex ? "yes" : "no"), width: 8 },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const verboseBranchColumns: TableColumn<GitAutomationTargetBranch>[] = [
|
|
50
|
+
...branchColumns,
|
|
51
|
+
{ header: "ID", value: (b) => b.id, width: 36 },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export const gitAutomationTargetBranchOperations = ["create", "read", "update", "delete"] as const;
|
|
55
|
+
type Operation = (typeof gitAutomationTargetBranchOperations)[number];
|
|
56
|
+
|
|
57
|
+
export const gitAutomationTargetBranchMutationFlags: readonly (keyof GitAutomationTargetBranchCliInput)[] = [
|
|
58
|
+
"pattern", "regex"
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
export function inferOperation(input: GitAutomationTargetBranchCliInput): Operation {
|
|
62
|
+
if (input.patternOrId === "new") return "create";
|
|
63
|
+
if (input.delete) return "delete";
|
|
64
|
+
|
|
65
|
+
for (const flag of gitAutomationTargetBranchMutationFlags) {
|
|
66
|
+
if (input[flag] !== undefined) return "update";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return "read";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const gitAutomationTargetBranchOperationSpec: OperationSpec<GitAutomationTargetBranchCliInput, Operation> = {
|
|
73
|
+
command: "git-branch",
|
|
74
|
+
operations: gitAutomationTargetBranchOperations,
|
|
75
|
+
mutationFlags: gitAutomationTargetBranchMutationFlags,
|
|
76
|
+
inferOperation,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
async function handleListGitAutomationTargetBranches(
|
|
80
|
+
input: typeof listGitAutomationTargetBranchesInput.infer
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
try {
|
|
83
|
+
const client = getClient();
|
|
84
|
+
const branches = await listGitAutomationTargetBranches(client, input.team);
|
|
85
|
+
|
|
86
|
+
const outputOpts: OutputOptions = {
|
|
87
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
88
|
+
verbose: input.verbose,
|
|
89
|
+
};
|
|
90
|
+
const format = getOutputFormat(outputOpts);
|
|
91
|
+
|
|
92
|
+
if (format === "json") {
|
|
93
|
+
outputJson(branches);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (format === "quiet") {
|
|
98
|
+
outputQuiet(branches.map((b) => b.id));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const columns = input.verbose ? verboseBranchColumns : branchColumns;
|
|
103
|
+
outputTable(branches, columns, outputOpts);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
handleApiError(error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function handleShowGitAutomationTargetBranch(
|
|
110
|
+
patternOrId: string,
|
|
111
|
+
input: GitAutomationTargetBranchCliInput
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
try {
|
|
114
|
+
const client = getClient();
|
|
115
|
+
|
|
116
|
+
const outputOpts: OutputOptions = {
|
|
117
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
118
|
+
verbose: input.verbose,
|
|
119
|
+
};
|
|
120
|
+
const format = getOutputFormat(outputOpts);
|
|
121
|
+
|
|
122
|
+
const branches = await listGitAutomationTargetBranches(client, input.team);
|
|
123
|
+
let branch: GitAutomationTargetBranch | null = null;
|
|
124
|
+
|
|
125
|
+
branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
|
|
126
|
+
if (!branch) {
|
|
127
|
+
branch = branches.find((b) => b.id === patternOrId) ?? null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!branch) {
|
|
131
|
+
exitWithError(
|
|
132
|
+
`git automation target branch "${patternOrId}" not found`,
|
|
133
|
+
`try: lnr git-branches --team ${input.team}`,
|
|
134
|
+
EXIT_CODES.NOT_FOUND
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (format === "json") {
|
|
139
|
+
outputJson(branch);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (format === "quiet") {
|
|
144
|
+
console.log(branch.id);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(`git automation target branch: ${branch.branchPattern}`);
|
|
149
|
+
console.log(` regex: ${branch.isRegex ? "yes" : "no"}`);
|
|
150
|
+
if (input.verbose) {
|
|
151
|
+
console.log(` id: ${branch.id}`);
|
|
152
|
+
console.log(` team: ${branch.teamKey ?? branch.teamId}`);
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
handleApiError(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleCreateGitAutomationTargetBranch(
|
|
160
|
+
input: GitAutomationTargetBranchCliInput
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
if (!input.pattern) {
|
|
163
|
+
exitWithError(
|
|
164
|
+
"--pattern is required",
|
|
165
|
+
"usage: lnr git-branch new --team ENG --pattern main"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const client = getClient();
|
|
171
|
+
|
|
172
|
+
const team = await findTeamByKeyOrName(client, input.team);
|
|
173
|
+
if (!team) {
|
|
174
|
+
exitWithError(`team "${input.team}" not found`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const branch = await createGitAutomationTargetBranch(client, {
|
|
178
|
+
teamId: team.id,
|
|
179
|
+
branchPattern: input.pattern,
|
|
180
|
+
isRegex: input.regex,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (branch) {
|
|
184
|
+
console.log(`created git automation target branch: ${branch.branchPattern}`);
|
|
185
|
+
} else {
|
|
186
|
+
exitWithError("failed to create git automation target branch");
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
handleApiError(error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function handleUpdateGitAutomationTargetBranch(
|
|
194
|
+
patternOrId: string,
|
|
195
|
+
input: GitAutomationTargetBranchCliInput
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
try {
|
|
198
|
+
const client = getClient();
|
|
199
|
+
|
|
200
|
+
const branches = await listGitAutomationTargetBranches(client, input.team);
|
|
201
|
+
let branch: GitAutomationTargetBranch | null = null;
|
|
202
|
+
|
|
203
|
+
branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
|
|
204
|
+
if (!branch) {
|
|
205
|
+
branch = branches.find((b) => b.id === patternOrId) ?? null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!branch) {
|
|
209
|
+
exitWithError(
|
|
210
|
+
`git automation target branch "${patternOrId}" not found`,
|
|
211
|
+
`try: lnr git-branches --team ${input.team}`,
|
|
212
|
+
EXIT_CODES.NOT_FOUND
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const success = await updateGitAutomationTargetBranch(client, branch.id, {
|
|
217
|
+
branchPattern: input.pattern,
|
|
218
|
+
isRegex: input.regex,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!success) {
|
|
222
|
+
exitWithError(
|
|
223
|
+
`failed to update git automation target branch "${patternOrId}"`,
|
|
224
|
+
undefined,
|
|
225
|
+
EXIT_CODES.NOT_FOUND
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(`updated git automation target branch: ${input.pattern ?? branch.branchPattern}`);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
handleApiError(error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function handleDeleteGitAutomationTargetBranch(
|
|
236
|
+
patternOrId: string,
|
|
237
|
+
input: GitAutomationTargetBranchCliInput
|
|
238
|
+
): Promise<void> {
|
|
239
|
+
try {
|
|
240
|
+
const client = getClient();
|
|
241
|
+
|
|
242
|
+
const branches = await listGitAutomationTargetBranches(client, input.team);
|
|
243
|
+
let branch: GitAutomationTargetBranch | null = null;
|
|
244
|
+
|
|
245
|
+
branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
|
|
246
|
+
if (!branch) {
|
|
247
|
+
branch = branches.find((b) => b.id === patternOrId) ?? null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!branch) {
|
|
251
|
+
exitWithError(
|
|
252
|
+
`git automation target branch "${patternOrId}" not found`,
|
|
253
|
+
`try: lnr git-branches --team ${input.team}`,
|
|
254
|
+
EXIT_CODES.NOT_FOUND
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const success = await deleteGitAutomationTargetBranch(client, branch.id);
|
|
259
|
+
|
|
260
|
+
if (!success) {
|
|
261
|
+
exitWithError(
|
|
262
|
+
`failed to delete git automation target branch "${patternOrId}"`,
|
|
263
|
+
undefined,
|
|
264
|
+
EXIT_CODES.NOT_FOUND
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.log(`deleted git automation target branch: ${branch.branchPattern}`);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
handleApiError(error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export const gitAutomationTargetBranchesRouter = router({
|
|
275
|
+
"git-branches": procedure
|
|
276
|
+
.meta({
|
|
277
|
+
aliases: { command: ["gb"] },
|
|
278
|
+
description: "list git automation target branches for a team",
|
|
279
|
+
})
|
|
280
|
+
.input(listGitAutomationTargetBranchesInput)
|
|
281
|
+
.query(async ({ input }) => {
|
|
282
|
+
await handleListGitAutomationTargetBranches(input);
|
|
283
|
+
}),
|
|
284
|
+
|
|
285
|
+
"git-branch": procedure
|
|
286
|
+
.meta({
|
|
287
|
+
description: "show, create, update, or delete a git automation target branch",
|
|
288
|
+
})
|
|
289
|
+
.input(gitAutomationTargetBranchInput)
|
|
290
|
+
.mutation(async ({ input }) => {
|
|
291
|
+
const operation = inferOperation(input);
|
|
292
|
+
|
|
293
|
+
switch (operation) {
|
|
294
|
+
case "create":
|
|
295
|
+
await handleCreateGitAutomationTargetBranch(input);
|
|
296
|
+
break;
|
|
297
|
+
case "delete":
|
|
298
|
+
await handleDeleteGitAutomationTargetBranch(input.patternOrId, input);
|
|
299
|
+
break;
|
|
300
|
+
case "update":
|
|
301
|
+
await handleUpdateGitAutomationTargetBranch(input.patternOrId, input);
|
|
302
|
+
break;
|
|
303
|
+
case "read":
|
|
304
|
+
default:
|
|
305
|
+
await handleShowGitAutomationTargetBranch(input.patternOrId, input);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}),
|
|
309
|
+
});
|