@bdsqqq/lnr-cli 1.6.0 → 2.0.1

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.
Files changed (47) hide show
  1. package/package.json +2 -3
  2. package/src/bench-lnr-overhead.ts +160 -0
  3. package/src/e2e-mutations.test.ts +378 -0
  4. package/src/e2e-readonly.test.ts +103 -0
  5. package/src/generated/doc.ts +270 -0
  6. package/src/generated/issue.ts +807 -0
  7. package/src/generated/label.ts +273 -0
  8. package/src/generated/project.ts +596 -0
  9. package/src/generated/template.ts +157 -0
  10. package/src/hand-crafted/issue.ts +27 -0
  11. package/src/lib/adapters/doc.ts +14 -0
  12. package/src/lib/adapters/index.ts +4 -0
  13. package/src/lib/adapters/issue.ts +32 -0
  14. package/src/lib/adapters/label.ts +20 -0
  15. package/src/lib/adapters/project.ts +23 -0
  16. package/src/lib/arktype-config.ts +18 -0
  17. package/src/lib/command-introspection.ts +97 -0
  18. package/src/lib/dispatch-effects.test.ts +297 -0
  19. package/src/lib/error.ts +37 -1
  20. package/src/lib/operation-spec.test.ts +317 -0
  21. package/src/lib/operation-spec.ts +11 -0
  22. package/src/lib/operation-specs.ts +21 -0
  23. package/src/lib/output.test.ts +3 -1
  24. package/src/lib/output.ts +1 -296
  25. package/src/lib/renderers/comments.ts +300 -0
  26. package/src/lib/renderers/detail.ts +61 -0
  27. package/src/lib/renderers/index.ts +2 -0
  28. package/src/router/agent-sessions.ts +253 -0
  29. package/src/router/auth.ts +9 -5
  30. package/src/router/config.ts +7 -6
  31. package/src/router/contract.test.ts +364 -0
  32. package/src/router/cycles.ts +372 -95
  33. package/src/router/git-automation-states.ts +355 -0
  34. package/src/router/git-automation-target-branches.ts +309 -0
  35. package/src/router/index.ts +26 -8
  36. package/src/router/initiatives.ts +260 -0
  37. package/src/router/me.ts +8 -7
  38. package/src/router/notifications.ts +176 -0
  39. package/src/router/roadmaps.ts +172 -0
  40. package/src/router/search.ts +7 -6
  41. package/src/router/teams.ts +82 -24
  42. package/src/router/users.ts +126 -0
  43. package/src/router/views.ts +399 -0
  44. package/src/router/docs.ts +0 -153
  45. package/src/router/issues.ts +0 -606
  46. package/src/router/labels.ts +0 -192
  47. package/src/router/projects.ts +0 -220
@@ -1,192 +0,0 @@
1
- import { z } from "zod";
2
- import {
3
- getClient,
4
- listLabels,
5
- getLabel,
6
- createLabel,
7
- updateLabel,
8
- deleteLabel,
9
- findTeamByKeyOrName,
10
- getAvailableTeamKeys,
11
- } from "@bdsqqq/lnr-core";
12
- import { router, procedure } from "./trpc";
13
- import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
14
- import {
15
- outputJson,
16
- outputQuiet,
17
- outputTable,
18
- getOutputFormat,
19
- truncate,
20
- type OutputOptions,
21
- } from "../lib/output";
22
-
23
- const listLabelsInput = z.object({
24
- team: z.string().optional().describe("filter by team key"),
25
- json: z.boolean().optional().describe("output as json"),
26
- quiet: z.boolean().optional().describe("output ids only"),
27
- verbose: z.boolean().optional().describe("show all columns"),
28
- });
29
-
30
- const labelInput = z.object({
31
- id: z.string().meta({ positional: true }).describe("label id or 'new'"),
32
- name: z.string().optional().describe("label name (required for new)"),
33
- color: z.string().optional().describe("hex color code"),
34
- description: z.string().optional().describe("label description"),
35
- team: z.string().optional().describe("team key (required for new)"),
36
- delete: z.boolean().optional().describe("delete the label"),
37
- json: z.boolean().optional().describe("output as json"),
38
- });
39
-
40
- export const labelsRouter = router({
41
- labels: procedure
42
- .meta({
43
- description: "list labels",
44
- })
45
- .input(listLabelsInput)
46
- .query(async ({ input }) => {
47
- try {
48
- const client = getClient();
49
-
50
- const outputOpts: OutputOptions = {
51
- format: input.json ? "json" : input.quiet ? "quiet" : undefined,
52
- verbose: input.verbose,
53
- };
54
- const format = getOutputFormat(outputOpts);
55
-
56
- let teamId: string | undefined;
57
- if (input.team) {
58
- const team = await findTeamByKeyOrName(client, input.team);
59
- if (!team) {
60
- const available = await getAvailableTeamKeys(client);
61
- exitWithError(
62
- `team not found: ${input.team}`,
63
- `available teams: ${available.join(", ")}`,
64
- EXIT_CODES.NOT_FOUND
65
- );
66
- }
67
- teamId = team.id;
68
- }
69
-
70
- const labels = await listLabels(client, teamId);
71
-
72
- if (format === "json") {
73
- outputJson(labels);
74
- return;
75
- }
76
-
77
- if (format === "quiet") {
78
- outputQuiet(labels.map((l) => l.id));
79
- return;
80
- }
81
-
82
- outputTable(
83
- labels,
84
- [
85
- { header: "ID", value: (l) => l.id.slice(0, 8), width: 10 },
86
- { header: "NAME", value: (l) => truncate(l.name, 30), width: 30 },
87
- { header: "COLOR", value: (l) => l.color ?? "-", width: 10 },
88
- { header: "DESCRIPTION", value: (l) => truncate(l.description ?? "-", 40), width: 40 },
89
- ],
90
- outputOpts
91
- );
92
- } catch (error) {
93
- handleApiError(error);
94
- }
95
- }),
96
-
97
- label: procedure
98
- .meta({
99
- description: "show label details, create with 'new', update, or delete with --delete",
100
- })
101
- .input(labelInput)
102
- .query(async ({ input }) => {
103
- try {
104
- const client = getClient();
105
-
106
- if (input.id === "new") {
107
- if (!input.name) {
108
- exitWithError("--name is required", "usage: lnr label new --name \"...\" --team <key>");
109
- }
110
- if (!input.team) {
111
- exitWithError("--team is required", "usage: lnr label new --name \"...\" --team <key>");
112
- }
113
-
114
- const team = await findTeamByKeyOrName(client, input.team);
115
- if (!team) {
116
- const available = (await getAvailableTeamKeys(client)).join(", ");
117
- exitWithError(
118
- `team "${input.team}" not found`,
119
- `available teams: ${available}`,
120
- EXIT_CODES.NOT_FOUND
121
- );
122
- }
123
-
124
- const label = await createLabel(client, {
125
- name: input.name,
126
- teamId: team.id,
127
- color: input.color,
128
- description: input.description,
129
- });
130
-
131
- if (label) {
132
- console.log(`created label: ${label.name}`);
133
- } else {
134
- exitWithError("failed to create label");
135
- }
136
- return;
137
- }
138
-
139
- if (input.delete) {
140
- const success = await deleteLabel(client, input.id);
141
-
142
- if (!success) {
143
- exitWithError(`label "${input.id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
144
- }
145
-
146
- console.log(`deleted label: ${input.id}`);
147
- return;
148
- }
149
-
150
- if (input.name || input.color || input.description) {
151
- const success = await updateLabel(client, input.id, {
152
- name: input.name,
153
- color: input.color,
154
- description: input.description,
155
- });
156
-
157
- if (!success) {
158
- exitWithError(`label "${input.id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
159
- }
160
-
161
- console.log(`updated label: ${input.id}`);
162
- return;
163
- }
164
-
165
- const outputOpts: OutputOptions = {
166
- format: input.json ? "json" : undefined,
167
- };
168
- const format = getOutputFormat(outputOpts);
169
-
170
- const label = await getLabel(client, input.id);
171
-
172
- if (!label) {
173
- exitWithError(`label "${input.id}" not found`, undefined, EXIT_CODES.NOT_FOUND);
174
- }
175
-
176
- if (format === "json") {
177
- outputJson(label);
178
- return;
179
- }
180
-
181
- console.log(`${label.name}`);
182
- if (label.description) {
183
- console.log(` ${truncate(label.description, 80)}`);
184
- }
185
- console.log();
186
- console.log(`id: ${label.id}`);
187
- console.log(`color: ${label.color ?? "-"}`);
188
- } catch (error) {
189
- handleApiError(error);
190
- }
191
- }),
192
- });
@@ -1,220 +0,0 @@
1
- import { z } from "zod";
2
- import {
3
- getClient,
4
- listProjects,
5
- getProject,
6
- getProjectIssues,
7
- createProject,
8
- deleteProject,
9
- findTeamByKeyOrName,
10
- getAvailableTeamKeys,
11
- } from "@bdsqqq/lnr-core";
12
- import { router, procedure } from "./trpc";
13
- import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
14
- import {
15
- outputJson,
16
- outputQuiet,
17
- outputTable,
18
- getOutputFormat,
19
- formatDate,
20
- truncate,
21
- type OutputOptions,
22
- } from "../lib/output";
23
-
24
- const listProjectsInput = z.object({
25
- team: z.string().optional().describe("filter by team key"),
26
- status: z.string().optional().describe("filter by status (planned, started, completed, etc)"),
27
- json: z.boolean().optional().describe("output as json"),
28
- quiet: z.boolean().optional().describe("output ids only"),
29
- verbose: z.boolean().optional().describe("show all columns"),
30
- });
31
-
32
- const projectInput = z.object({
33
- name: z.string().meta({ positional: true }).describe("project name or 'new'"),
34
- issues: z.boolean().optional().describe("list issues in project"),
35
- json: z.boolean().optional().describe("output as json"),
36
- quiet: z.boolean().optional().describe("output ids only"),
37
- verbose: z.boolean().optional().describe("show all columns"),
38
- delete: z.boolean().optional().describe("delete the project"),
39
- projectName: z.string().optional().describe("project name (required for new)"),
40
- team: z.string().optional().describe("team key to associate project with"),
41
- description: z.string().optional().describe("project description"),
42
- });
43
-
44
- export const projectsRouter = router({
45
- projects: procedure
46
- .meta({
47
- aliases: { command: ["p"] },
48
- description: "list projects",
49
- })
50
- .input(listProjectsInput)
51
- .query(async ({ input }) => {
52
- try {
53
- const client = getClient();
54
-
55
- const outputOpts: OutputOptions = {
56
- format: input.json ? "json" : input.quiet ? "quiet" : undefined,
57
- verbose: input.verbose,
58
- };
59
- const format = getOutputFormat(outputOpts);
60
-
61
- const projects = await listProjects(client, {
62
- team: input.team,
63
- status: input.status,
64
- });
65
-
66
- if (format === "json") {
67
- outputJson(projects);
68
- return;
69
- }
70
-
71
- if (format === "quiet") {
72
- outputQuiet(projects.map((p) => p.id));
73
- return;
74
- }
75
-
76
- outputTable(
77
- projects,
78
- [
79
- { header: "NAME", value: (p) => truncate(p.name, 30), width: 30 },
80
- { header: "STATE", value: (p) => p.state ?? "-", width: 12 },
81
- {
82
- header: "PROGRESS",
83
- value: (p) => `${Math.round((p.progress ?? 0) * 100)}%`,
84
- width: 10,
85
- },
86
- { header: "TARGET", value: (p) => formatDate(p.targetDate), width: 12 },
87
- ],
88
- outputOpts
89
- );
90
- } catch (error) {
91
- handleApiError(error);
92
- }
93
- }),
94
-
95
- project: procedure
96
- .meta({
97
- description: "show project details, create with 'new', or delete with --delete",
98
- })
99
- .input(projectInput)
100
- .query(async ({ input }) => {
101
- if (input.name === "new") {
102
- if (!input.projectName) {
103
- exitWithError("--projectName is required", "usage: lnr project new --projectName \"...\"");
104
- }
105
-
106
- try {
107
- const client = getClient();
108
- let teamIds: string[] = [];
109
-
110
- if (input.team) {
111
- const team = await findTeamByKeyOrName(client, input.team);
112
- if (!team) {
113
- const available = (await getAvailableTeamKeys(client)).join(", ");
114
- exitWithError(
115
- `team "${input.team}" not found`,
116
- `available teams: ${available}`,
117
- EXIT_CODES.NOT_FOUND
118
- );
119
- }
120
- teamIds = [team.id];
121
- }
122
-
123
- const project = await createProject(client, {
124
- name: input.projectName,
125
- description: input.description,
126
- teamIds,
127
- });
128
-
129
- if (project) {
130
- console.log(`created project: ${project.name}`);
131
- } else {
132
- console.log("created project");
133
- }
134
- } catch (error) {
135
- handleApiError(error);
136
- }
137
- return;
138
- }
139
-
140
- if (input.delete) {
141
- try {
142
- const client = getClient();
143
- const success = await deleteProject(client, input.name);
144
-
145
- if (!success) {
146
- exitWithError(`project "${input.name}" not found`, undefined, EXIT_CODES.NOT_FOUND);
147
- }
148
-
149
- console.log(`deleted project: ${input.name}`);
150
- } catch (error) {
151
- handleApiError(error);
152
- }
153
- return;
154
- }
155
-
156
- try {
157
- const client = getClient();
158
-
159
- const outputOpts: OutputOptions = {
160
- format: input.json ? "json" : input.quiet ? "quiet" : undefined,
161
- verbose: input.verbose,
162
- };
163
- const format = getOutputFormat(outputOpts);
164
-
165
- const project = await getProject(client, input.name);
166
-
167
- if (!project) {
168
- exitWithError(`project "${input.name}" not found`, undefined, EXIT_CODES.NOT_FOUND);
169
- }
170
-
171
- if (input.issues) {
172
- const issues = await getProjectIssues(client, input.name);
173
-
174
- if (format === "json") {
175
- outputJson(issues);
176
- return;
177
- }
178
-
179
- if (format === "quiet") {
180
- outputQuiet(issues.map((i) => i.identifier));
181
- return;
182
- }
183
-
184
- outputTable(
185
- issues,
186
- [
187
- { header: "ID", value: (i) => i.identifier, width: 12 },
188
- { header: "TITLE", value: (i) => truncate(i.title, 50), width: 50 },
189
- { header: "CREATED", value: (i) => formatDate(i.createdAt), width: 12 },
190
- ],
191
- outputOpts
192
- );
193
- return;
194
- }
195
-
196
- if (format === "json") {
197
- outputJson(project);
198
- return;
199
- }
200
-
201
- if (format === "quiet") {
202
- console.log(project.id);
203
- return;
204
- }
205
-
206
- console.log(`${project.name}`);
207
- if (project.description) {
208
- console.log(` ${truncate(project.description, 80)}`);
209
- }
210
- console.log();
211
- console.log(`state: ${project.state ?? "-"}`);
212
- console.log(`progress: ${Math.round((project.progress ?? 0) * 100)}%`);
213
- console.log(`target: ${formatDate(project.targetDate)}`);
214
- console.log(`started: ${formatDate(project.startDate)}`);
215
- console.log(`created: ${formatDate(project.createdAt)}`);
216
- } catch (error) {
217
- handleApiError(error);
218
- }
219
- }),
220
- });