@bdsqqq/lnr-cli 1.6.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 -606
- package/src/router/labels.ts +0 -192
- package/src/router/projects.ts +0 -220
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE - DO NOT EDIT
|
|
3
|
+
* Regenerate with: bun run packages/codegen/generate-commands.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import "../lib/arktype-config";
|
|
7
|
+
import { type } from "arktype";
|
|
8
|
+
import {
|
|
9
|
+
getClient,
|
|
10
|
+
listLabels,
|
|
11
|
+
getLabel,
|
|
12
|
+
createLabel,
|
|
13
|
+
updateLabel,
|
|
14
|
+
deleteLabel,
|
|
15
|
+
resolveTeamByKey,
|
|
16
|
+
createSubscription,
|
|
17
|
+
deleteSubscription,
|
|
18
|
+
findUserSubscription,
|
|
19
|
+
type Label,
|
|
20
|
+
} from "@bdsqqq/lnr-core";
|
|
21
|
+
import { router, procedure } from "../router/trpc";
|
|
22
|
+
import { handleApiError, exitWithError, EXIT_CODES } from "../lib/error";
|
|
23
|
+
import type { OperationSpec } from "../lib/operation-spec";
|
|
24
|
+
import {
|
|
25
|
+
outputJson,
|
|
26
|
+
outputQuiet,
|
|
27
|
+
outputTable,
|
|
28
|
+
getOutputFormat,
|
|
29
|
+
truncate,
|
|
30
|
+
type OutputOptions,
|
|
31
|
+
type TableColumn,
|
|
32
|
+
} from "../lib/output";
|
|
33
|
+
import { outputDetail } from "../lib/renderers/detail";
|
|
34
|
+
import { labelToDetail } from "../lib/adapters";
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export const listLabelsInput = type({
|
|
38
|
+
"team?": type("string").describe("filter by team key"),
|
|
39
|
+
"json?": type("boolean").describe("output as json"),
|
|
40
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
41
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const labelInput = type({
|
|
45
|
+
id: type("string").configure({ positional: true }).describe("label id or 'new'"),
|
|
46
|
+
"json?": type("boolean").describe("output as json"),
|
|
47
|
+
"delete?": type("boolean").describe("delete the label"),
|
|
48
|
+
"team?": type("string").describe("team key (required for new)"),
|
|
49
|
+
"name?": type("string").describe("label name (required for new)"),
|
|
50
|
+
"description?": type("string").describe("label description"),
|
|
51
|
+
"color?": type("string").describe("hex color code"),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
type LabelInput = typeof labelInput.infer;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const labelColumns: TableColumn<Label>[] = [
|
|
59
|
+
{ header: "ID", value: (l) => l.id.slice(0, 8), width: 10 },
|
|
60
|
+
{ header: "NAME", value: (l) => truncate(l.name, 30), width: 30 },
|
|
61
|
+
{ header: "COLOR", value: (l) => l.color ?? "-", width: 10 },
|
|
62
|
+
{ header: "DESCRIPTION", value: (l) => truncate(l.description ?? "-", 40), width: 40 },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export const labelOperations = ["create", "read", "update", "delete"] as const;
|
|
66
|
+
type Operation = (typeof labelOperations)[number];
|
|
67
|
+
|
|
68
|
+
export const labelMutationFlags: readonly (keyof LabelInput)[] = [
|
|
69
|
+
"name", "color", "description"
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export function inferOperation(input: LabelInput): Operation {
|
|
73
|
+
if (input.id === "new") return "create";
|
|
74
|
+
if (input.delete) return "delete";
|
|
75
|
+
|
|
76
|
+
for (const flag of labelMutationFlags) {
|
|
77
|
+
if (input[flag] !== undefined) return "update";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return "read";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const labelOperationSpec: OperationSpec<LabelInput, Operation> = {
|
|
84
|
+
command: "label",
|
|
85
|
+
operations: labelOperations,
|
|
86
|
+
mutationFlags: labelMutationFlags,
|
|
87
|
+
inferOperation,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
async function handleListLabels(
|
|
91
|
+
input: typeof listLabelsInput.infer
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
try {
|
|
94
|
+
const client = getClient();
|
|
95
|
+
|
|
96
|
+
const outputOpts: OutputOptions = {
|
|
97
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
98
|
+
verbose: input.verbose,
|
|
99
|
+
};
|
|
100
|
+
const format = getOutputFormat(outputOpts);
|
|
101
|
+
|
|
102
|
+
let teamId: string | undefined;
|
|
103
|
+
if (input.team) {
|
|
104
|
+
teamId = await resolveTeamByKey(client, input.team);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const labels = await listLabels(client, teamId);
|
|
108
|
+
|
|
109
|
+
if (format === "json") {
|
|
110
|
+
outputJson(labels);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (format === "quiet") {
|
|
115
|
+
outputQuiet(labels.map((l) => l.id));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
outputTable(labels, labelColumns, outputOpts);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
handleApiError(error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function handleShowLabel(
|
|
126
|
+
id: string,
|
|
127
|
+
input: LabelInput
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
try {
|
|
130
|
+
const client = getClient();
|
|
131
|
+
|
|
132
|
+
const outputOpts: OutputOptions = {
|
|
133
|
+
format: input.json ? "json" : undefined,
|
|
134
|
+
};
|
|
135
|
+
const format = getOutputFormat(outputOpts);
|
|
136
|
+
|
|
137
|
+
const label = await getLabel(client, id);
|
|
138
|
+
|
|
139
|
+
if (!label) {
|
|
140
|
+
exitWithError(`label "${id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (format === "json") {
|
|
144
|
+
outputJson(label);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
outputDetail(labelToDetail(label));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
handleApiError(error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function handleUpdateLabel(
|
|
155
|
+
id: string,
|
|
156
|
+
input: LabelInput
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
try {
|
|
159
|
+
const client = getClient();
|
|
160
|
+
|
|
161
|
+
const updatePayload: {
|
|
162
|
+
name?: string;
|
|
163
|
+
color?: string;
|
|
164
|
+
description?: string;
|
|
165
|
+
} = {};
|
|
166
|
+
|
|
167
|
+
if (input.name !== undefined) updatePayload.name = input.name;
|
|
168
|
+
if (input.color !== undefined) updatePayload.color = input.color;
|
|
169
|
+
if (input.description !== undefined) updatePayload.description = input.description;
|
|
170
|
+
|
|
171
|
+
if (Object.keys(updatePayload).length > 0) {
|
|
172
|
+
const success = await updateLabel(client, id, updatePayload);
|
|
173
|
+
if (!success) {
|
|
174
|
+
exitWithError(`label "${id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
175
|
+
}
|
|
176
|
+
console.log(`updated label: ${id}`);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
handleApiError(error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleCreateLabel(input: LabelInput): Promise<void> {
|
|
184
|
+
if (!input.name) {
|
|
185
|
+
exitWithError("--name is required", 'usage: lnr label new --name "..." --team <key>');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!input.team) {
|
|
189
|
+
exitWithError("--team is required", 'usage: lnr label new --name "..." --team <key>');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const client = getClient();
|
|
194
|
+
|
|
195
|
+
const teamId = await resolveTeamByKey(client, input.team);
|
|
196
|
+
|
|
197
|
+
const label = await createLabel(client, {
|
|
198
|
+
name: input.name,
|
|
199
|
+
teamId,
|
|
200
|
+
color: input.color,
|
|
201
|
+
description: input.description,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (label) {
|
|
205
|
+
console.log(`created label: ${label.name}`);
|
|
206
|
+
} else {
|
|
207
|
+
exitWithError("failed to create label");
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
handleApiError(error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function handleDeleteLabel(
|
|
215
|
+
id: string,
|
|
216
|
+
_input: LabelInput
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
try {
|
|
219
|
+
const client = getClient();
|
|
220
|
+
const success = await deleteLabel(client, id);
|
|
221
|
+
|
|
222
|
+
if (!success) {
|
|
223
|
+
exitWithError(`label "${id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(`deleted label: ${id}`);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
handleApiError(error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
export const generatedLabelsRouter = router({
|
|
237
|
+
labels: procedure
|
|
238
|
+
.meta({
|
|
239
|
+
description: "list labels",
|
|
240
|
+
|
|
241
|
+
})
|
|
242
|
+
.input(listLabelsInput)
|
|
243
|
+
.query(async ({ input }) => {
|
|
244
|
+
await handleListLabels(input);
|
|
245
|
+
}),
|
|
246
|
+
|
|
247
|
+
label: procedure
|
|
248
|
+
.meta({
|
|
249
|
+
description: "show or update a label, or create with 'new'",
|
|
250
|
+
})
|
|
251
|
+
.input(labelInput)
|
|
252
|
+
.mutation(async ({ input }) => {
|
|
253
|
+
const operation = inferOperation(input);
|
|
254
|
+
|
|
255
|
+
switch (operation) {
|
|
256
|
+
case "create":
|
|
257
|
+
await handleCreateLabel(input);
|
|
258
|
+
break;
|
|
259
|
+
case "delete":
|
|
260
|
+
await handleDeleteLabel(input.id, input);
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
case "update":
|
|
264
|
+
await handleUpdateLabel(input.id, input);
|
|
265
|
+
break;
|
|
266
|
+
case "read":
|
|
267
|
+
default:
|
|
268
|
+
await handleShowLabel(input.id, input);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
|
|
273
|
+
});
|