@bdsqqq/lnr-cli 1.1.1 → 1.2.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 +7 -3
- package/src/cli.ts +22 -24
- package/src/lib/output.test.ts +181 -0
- package/src/lib/output.ts +317 -1
- package/src/{commands → router}/auth.ts +22 -15
- package/src/router/config.ts +71 -0
- package/src/router/cycles.ts +131 -0
- package/src/router/docs.ts +153 -0
- package/src/router/index.ts +28 -0
- package/src/router/issues.ts +558 -0
- package/src/router/labels.ts +192 -0
- package/src/{commands → router}/me.ts +47 -29
- package/src/router/projects.ts +220 -0
- package/src/{commands → router}/search.ts +20 -19
- package/src/{commands → router}/teams.ts +33 -46
- package/src/router/trpc.ts +7 -0
- package/src/commands/config.ts +0 -64
- package/src/commands/cycles.ts +0 -134
- package/src/commands/issues.ts +0 -390
- package/src/commands/projects.ts +0 -214
|
@@ -0,0 +1,192 @@
|
|
|
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,47 +1,43 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
getClient,
|
|
4
4
|
getViewer,
|
|
5
5
|
getMyIssues,
|
|
6
6
|
getMyCreatedIssues,
|
|
7
|
+
getMyActivity,
|
|
7
8
|
} from "@bdsqqq/lnr-core";
|
|
8
|
-
import {
|
|
9
|
+
import { router, procedure } from "./trpc";
|
|
9
10
|
import {
|
|
10
|
-
getOutputFormat,
|
|
11
11
|
outputJson,
|
|
12
12
|
outputQuiet,
|
|
13
13
|
outputTable,
|
|
14
14
|
formatPriority,
|
|
15
|
+
formatDate,
|
|
15
16
|
truncate,
|
|
16
|
-
type OutputOptions,
|
|
17
17
|
} from "../lib/output";
|
|
18
|
+
import { handleApiError } from "../lib/error";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
issues
|
|
21
|
-
created
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export function registerMeCommand(program: Command): void {
|
|
27
|
-
program
|
|
28
|
-
.command("me")
|
|
29
|
-
.description("show current user info")
|
|
30
|
-
.option("--issues", "show my assigned issues")
|
|
31
|
-
.option("--created", "show issues i created")
|
|
32
|
-
.option("--json", "output as json")
|
|
33
|
-
.option("--quiet", "output ids only")
|
|
34
|
-
.action(async (options: MeOptions) => {
|
|
35
|
-
const format = options.json
|
|
36
|
-
? "json"
|
|
37
|
-
: options.quiet
|
|
38
|
-
? "quiet"
|
|
39
|
-
: getOutputFormat(options);
|
|
20
|
+
const meInput = z.object({
|
|
21
|
+
issues: z.boolean().optional().describe("list issues assigned to me"),
|
|
22
|
+
created: z.boolean().optional().describe("list issues created by me"),
|
|
23
|
+
activity: z.boolean().optional().describe("show recent activity"),
|
|
24
|
+
json: z.boolean().optional().describe("output as json"),
|
|
25
|
+
quiet: z.boolean().optional().describe("output ids only"),
|
|
26
|
+
});
|
|
40
27
|
|
|
28
|
+
export const meRouter = router({
|
|
29
|
+
me: procedure
|
|
30
|
+
.meta({
|
|
31
|
+
description: "show current user info",
|
|
32
|
+
})
|
|
33
|
+
.input(meInput)
|
|
34
|
+
.query(async ({ input }) => {
|
|
41
35
|
try {
|
|
42
36
|
const client = getClient();
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
const format = input.json ? "json" : input.quiet ? "quiet" : "table";
|
|
39
|
+
|
|
40
|
+
if (input.issues) {
|
|
45
41
|
const issues = await getMyIssues(client);
|
|
46
42
|
|
|
47
43
|
if (format === "json") {
|
|
@@ -63,7 +59,7 @@ export function registerMeCommand(program: Command): void {
|
|
|
63
59
|
return;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
if (
|
|
62
|
+
if (input.created) {
|
|
67
63
|
const issues = await getMyCreatedIssues(client);
|
|
68
64
|
|
|
69
65
|
if (format === "json") {
|
|
@@ -85,6 +81,28 @@ export function registerMeCommand(program: Command): void {
|
|
|
85
81
|
return;
|
|
86
82
|
}
|
|
87
83
|
|
|
84
|
+
if (input.activity) {
|
|
85
|
+
const activity = await getMyActivity(client);
|
|
86
|
+
|
|
87
|
+
if (format === "json") {
|
|
88
|
+
outputJson(activity);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (format === "quiet") {
|
|
93
|
+
outputQuiet(activity.map((a) => a.identifier));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
outputTable(activity, [
|
|
98
|
+
{ header: "ID", value: (a) => a.identifier, width: 12 },
|
|
99
|
+
{ header: "TITLE", value: (a) => truncate(a.title, 40), width: 40 },
|
|
100
|
+
{ header: "STATE", value: (a) => a.state ?? "-", width: 16 },
|
|
101
|
+
{ header: "UPDATED", value: (a) => formatDate(a.updatedAt), width: 12 },
|
|
102
|
+
]);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
const viewer = await getViewer(client);
|
|
89
107
|
|
|
90
108
|
if (format === "json") {
|
|
@@ -106,5 +124,5 @@ export function registerMeCommand(program: Command): void {
|
|
|
106
124
|
} catch (error) {
|
|
107
125
|
handleApiError(error);
|
|
108
126
|
}
|
|
109
|
-
})
|
|
110
|
-
}
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
import { getClient, searchIssues } from "@bdsqqq/lnr-core";
|
|
3
|
+
import { router, procedure } from "./trpc";
|
|
3
4
|
import { handleApiError } from "../lib/error";
|
|
4
5
|
import {
|
|
5
6
|
getOutputFormat,
|
|
@@ -7,28 +8,28 @@ import {
|
|
|
7
8
|
outputQuiet,
|
|
8
9
|
outputTable,
|
|
9
10
|
truncate,
|
|
10
|
-
type OutputOptions,
|
|
11
11
|
} from "../lib/output";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const searchInput = z.object({
|
|
14
|
+
query: z.string().meta({ positional: true }).describe("search query"),
|
|
15
|
+
team: z.string().optional().describe("filter by team key"),
|
|
16
|
+
json: z.boolean().optional().describe("output as json"),
|
|
17
|
+
quiet: z.boolean().optional().describe("output ids only"),
|
|
18
|
+
});
|
|
18
19
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
const format =
|
|
20
|
+
export const searchRouter = router({
|
|
21
|
+
search: procedure
|
|
22
|
+
.meta({
|
|
23
|
+
description: "search issues",
|
|
24
|
+
aliases: { command: ["s"] },
|
|
25
|
+
})
|
|
26
|
+
.input(searchInput)
|
|
27
|
+
.query(async ({ input }) => {
|
|
28
|
+
const format = input.json ? "json" : input.quiet ? "quiet" : getOutputFormat({});
|
|
28
29
|
|
|
29
30
|
try {
|
|
30
31
|
const client = getClient();
|
|
31
|
-
const issues = await searchIssues(client, query, { team:
|
|
32
|
+
const issues = await searchIssues(client, input.query, { team: input.team });
|
|
32
33
|
|
|
33
34
|
if (format === "json") {
|
|
34
35
|
outputJson(issues);
|
|
@@ -48,5 +49,5 @@ export function registerSearchCommand(program: Command): void {
|
|
|
48
49
|
} catch (error) {
|
|
49
50
|
handleApiError(error);
|
|
50
51
|
}
|
|
51
|
-
})
|
|
52
|
-
}
|
|
52
|
+
}),
|
|
53
|
+
});
|