@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
getClient,
|
|
4
4
|
listTeams,
|
|
@@ -6,45 +6,36 @@ import {
|
|
|
6
6
|
getTeamMembers,
|
|
7
7
|
getAvailableTeamKeys,
|
|
8
8
|
} from "@bdsqqq/lnr-core";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
outputJson,
|
|
13
|
-
outputQuiet,
|
|
14
|
-
outputTable,
|
|
15
|
-
type OutputOptions,
|
|
16
|
-
} from "../lib/output";
|
|
17
|
-
|
|
18
|
-
interface TeamListOptions extends OutputOptions {
|
|
19
|
-
json?: boolean;
|
|
20
|
-
quiet?: boolean;
|
|
21
|
-
}
|
|
9
|
+
import { router, procedure } from "./trpc";
|
|
10
|
+
import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
|
|
11
|
+
import { outputJson, outputQuiet, outputTable } from "../lib/output";
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
13
|
+
const teamsInput = z.object({
|
|
14
|
+
json: z.boolean().optional().describe("output as json"),
|
|
15
|
+
quiet: z.boolean().optional().describe("output keys only"),
|
|
16
|
+
});
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.option("--json", "output as json")
|
|
34
|
-
.option("--quiet", "output ids only")
|
|
35
|
-
.action(async (options: TeamListOptions) => {
|
|
36
|
-
const format = options.json ? "json" : options.quiet ? "quiet" : getOutputFormat(options);
|
|
18
|
+
const teamInput = z.object({
|
|
19
|
+
key: z.string().meta({ positional: true }).describe("team key"),
|
|
20
|
+
members: z.boolean().optional().describe("list team members"),
|
|
21
|
+
json: z.boolean().optional().describe("output as json"),
|
|
22
|
+
});
|
|
37
23
|
|
|
24
|
+
export const teamsRouter = router({
|
|
25
|
+
teams: procedure
|
|
26
|
+
.meta({ aliases: { command: ["t"] }, description: "list teams" })
|
|
27
|
+
.input(teamsInput)
|
|
28
|
+
.query(async ({ input }) => {
|
|
38
29
|
try {
|
|
39
30
|
const client = getClient();
|
|
40
31
|
const teams = await listTeams(client);
|
|
41
32
|
|
|
42
|
-
if (
|
|
33
|
+
if (input.json) {
|
|
43
34
|
outputJson(teams);
|
|
44
35
|
return;
|
|
45
36
|
}
|
|
46
37
|
|
|
47
|
-
if (
|
|
38
|
+
if (input.quiet) {
|
|
48
39
|
outputQuiet(teams.map((t) => t.key));
|
|
49
40
|
return;
|
|
50
41
|
}
|
|
@@ -57,33 +48,29 @@ export function registerTeamsCommand(program: Command): void {
|
|
|
57
48
|
} catch (error) {
|
|
58
49
|
handleApiError(error);
|
|
59
50
|
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
program
|
|
63
|
-
.command("team <key>")
|
|
64
|
-
.description("show team details")
|
|
65
|
-
.option("--members", "show team members")
|
|
66
|
-
.option("--json", "output as json")
|
|
67
|
-
.action(async (key: string, options: TeamShowOptions) => {
|
|
68
|
-
const format = options.json ? "json" : getOutputFormat(options);
|
|
51
|
+
}),
|
|
69
52
|
|
|
53
|
+
team: procedure
|
|
54
|
+
.meta({ description: "show team details" })
|
|
55
|
+
.input(teamInput)
|
|
56
|
+
.query(async ({ input }) => {
|
|
70
57
|
try {
|
|
71
58
|
const client = getClient();
|
|
72
|
-
const team = await getTeam(client, key);
|
|
59
|
+
const team = await getTeam(client, input.key);
|
|
73
60
|
|
|
74
61
|
if (!team) {
|
|
75
62
|
const availableKeys = (await getAvailableTeamKeys(client)).join(", ");
|
|
76
63
|
exitWithError(
|
|
77
|
-
`team "${key}" not found`,
|
|
64
|
+
`team "${input.key}" not found`,
|
|
78
65
|
`available teams: ${availableKeys}`,
|
|
79
66
|
EXIT_CODES.NOT_FOUND
|
|
80
67
|
);
|
|
81
68
|
}
|
|
82
69
|
|
|
83
|
-
if (
|
|
84
|
-
const members = await getTeamMembers(client, key);
|
|
70
|
+
if (input.members) {
|
|
71
|
+
const members = await getTeamMembers(client, input.key);
|
|
85
72
|
|
|
86
|
-
if (
|
|
73
|
+
if (input.json) {
|
|
87
74
|
outputJson(members);
|
|
88
75
|
return;
|
|
89
76
|
}
|
|
@@ -97,7 +84,7 @@ export function registerTeamsCommand(program: Command): void {
|
|
|
97
84
|
return;
|
|
98
85
|
}
|
|
99
86
|
|
|
100
|
-
if (
|
|
87
|
+
if (input.json) {
|
|
101
88
|
outputJson(team);
|
|
102
89
|
return;
|
|
103
90
|
}
|
|
@@ -111,5 +98,5 @@ export function registerTeamsCommand(program: Command): void {
|
|
|
111
98
|
} catch (error) {
|
|
112
99
|
handleApiError(error);
|
|
113
100
|
}
|
|
114
|
-
})
|
|
115
|
-
}
|
|
101
|
+
}),
|
|
102
|
+
});
|
package/src/commands/config.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
loadConfig,
|
|
4
|
-
getConfigValue,
|
|
5
|
-
setConfigValue,
|
|
6
|
-
type Config,
|
|
7
|
-
} from "@bdsqqq/lnr-core";
|
|
8
|
-
import { exitWithError } from "../lib/error";
|
|
9
|
-
|
|
10
|
-
const VALID_KEYS: (keyof Config)[] = ["api_key", "default_team", "output_format"];
|
|
11
|
-
|
|
12
|
-
export function registerConfigCommand(program: Command): void {
|
|
13
|
-
const configCmd = program
|
|
14
|
-
.command("config")
|
|
15
|
-
.description("view and manage configuration");
|
|
16
|
-
|
|
17
|
-
configCmd
|
|
18
|
-
.command("get <key>")
|
|
19
|
-
.description("get a config value")
|
|
20
|
-
.action((key: string) => {
|
|
21
|
-
if (!VALID_KEYS.includes(key as keyof Config)) {
|
|
22
|
-
exitWithError(`unknown config key: ${key}`, `valid keys: ${VALID_KEYS.join(", ")}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const value = getConfigValue(key as keyof Config);
|
|
26
|
-
if (value === undefined) {
|
|
27
|
-
console.log("(not set)");
|
|
28
|
-
} else {
|
|
29
|
-
console.log(value);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
configCmd
|
|
34
|
-
.command("set <key> <value>")
|
|
35
|
-
.description("set a config value")
|
|
36
|
-
.action((key: string, value: string) => {
|
|
37
|
-
if (!VALID_KEYS.includes(key as keyof Config)) {
|
|
38
|
-
exitWithError(`unknown config key: ${key}`, `valid keys: ${VALID_KEYS.join(", ")}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (key === "output_format" && !["table", "json", "quiet"].includes(value)) {
|
|
42
|
-
exitWithError(`invalid output_format: ${value}`, "valid values: table, json, quiet");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
setConfigValue(key as keyof Config, value as Config[keyof Config]);
|
|
46
|
-
console.log(`${key} = ${value}`);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
configCmd.action(() => {
|
|
50
|
-
const config = loadConfig();
|
|
51
|
-
if (Object.keys(config).length === 0) {
|
|
52
|
-
console.log("(no configuration set)");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for (const [key, value] of Object.entries(config)) {
|
|
57
|
-
if (key === "api_key" && value) {
|
|
58
|
-
console.log(`${key} = ${(value as string).slice(0, 10)}...`);
|
|
59
|
-
} else {
|
|
60
|
-
console.log(`${key} = ${value}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
package/src/commands/cycles.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
getClient,
|
|
4
|
-
listCycles,
|
|
5
|
-
getCurrentCycle,
|
|
6
|
-
getCycleIssues,
|
|
7
|
-
} from "@bdsqqq/lnr-core";
|
|
8
|
-
import { handleApiError, exitWithError } from "../lib/error";
|
|
9
|
-
import {
|
|
10
|
-
outputJson,
|
|
11
|
-
outputQuiet,
|
|
12
|
-
outputTable,
|
|
13
|
-
getOutputFormat,
|
|
14
|
-
formatDate,
|
|
15
|
-
truncate,
|
|
16
|
-
type OutputOptions,
|
|
17
|
-
} from "../lib/output";
|
|
18
|
-
|
|
19
|
-
interface CyclesOptions extends OutputOptions {
|
|
20
|
-
team?: string;
|
|
21
|
-
json?: boolean;
|
|
22
|
-
quiet?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface CycleOptions extends OutputOptions {
|
|
26
|
-
current?: boolean;
|
|
27
|
-
team?: string;
|
|
28
|
-
issues?: boolean;
|
|
29
|
-
json?: boolean;
|
|
30
|
-
quiet?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function registerCyclesCommand(program: Command): void {
|
|
34
|
-
program
|
|
35
|
-
.command("cycles")
|
|
36
|
-
.description("list cycles for a team")
|
|
37
|
-
.requiredOption("--team <key>", "team key (required)")
|
|
38
|
-
.option("--json", "output as json")
|
|
39
|
-
.option("--quiet", "output ids only")
|
|
40
|
-
.option("--verbose", "show detailed output")
|
|
41
|
-
.action(async (options: CyclesOptions) => {
|
|
42
|
-
try {
|
|
43
|
-
const client = getClient();
|
|
44
|
-
const cycles = await listCycles(client, options.team!);
|
|
45
|
-
|
|
46
|
-
if (cycles.length === 0) {
|
|
47
|
-
exitWithError(`team "${options.team}" not found`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const format = options.json ? "json" : options.quiet ? "quiet" : getOutputFormat(options);
|
|
51
|
-
|
|
52
|
-
if (format === "json") {
|
|
53
|
-
outputJson(cycles);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (format === "quiet") {
|
|
58
|
-
outputQuiet(cycles.map((c) => c.id));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
outputTable(cycles, [
|
|
63
|
-
{ header: "#", value: (c) => String(c.number), width: 4 },
|
|
64
|
-
{ header: "NAME", value: (c) => c.name ?? `Cycle ${c.number}`, width: 20 },
|
|
65
|
-
{ header: "START", value: (c) => formatDate(c.startsAt), width: 12 },
|
|
66
|
-
{ header: "END", value: (c) => formatDate(c.endsAt), width: 12 },
|
|
67
|
-
], options);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
handleApiError(error);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
program
|
|
74
|
-
.command("cycle")
|
|
75
|
-
.description("show cycle details")
|
|
76
|
-
.option("--current", "show current active cycle")
|
|
77
|
-
.requiredOption("--team <key>", "team key (required)")
|
|
78
|
-
.option("--issues", "list issues in the cycle")
|
|
79
|
-
.option("--json", "output as json")
|
|
80
|
-
.option("--quiet", "output ids only")
|
|
81
|
-
.option("--verbose", "show detailed output")
|
|
82
|
-
.action(async (options: CycleOptions) => {
|
|
83
|
-
if (!options.current) {
|
|
84
|
-
exitWithError("cycle identifier required", "use --current to show active cycle");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const client = getClient();
|
|
89
|
-
const cycle = await getCurrentCycle(client, options.team!);
|
|
90
|
-
|
|
91
|
-
if (!cycle) {
|
|
92
|
-
exitWithError("no active cycle", `team "${options.team}" has no current cycle`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const format = options.json ? "json" : options.quiet ? "quiet" : getOutputFormat(options);
|
|
96
|
-
|
|
97
|
-
if (options.issues) {
|
|
98
|
-
const issues = await getCycleIssues(client, options.team!);
|
|
99
|
-
|
|
100
|
-
if (format === "json") {
|
|
101
|
-
outputJson(issues);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (format === "quiet") {
|
|
106
|
-
outputQuiet(issues.map((i) => i.identifier));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
outputTable(issues, [
|
|
111
|
-
{ header: "ID", value: (i) => i.identifier, width: 10 },
|
|
112
|
-
{ header: "TITLE", value: (i) => truncate(i.title, 50), width: 50 },
|
|
113
|
-
], options);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (format === "json") {
|
|
118
|
-
outputJson(cycle);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (format === "quiet") {
|
|
123
|
-
console.log(cycle.id);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log(`cycle ${cycle.number}: ${cycle.name ?? `Cycle ${cycle.number}`}`);
|
|
128
|
-
console.log(` start: ${formatDate(cycle.startsAt)}`);
|
|
129
|
-
console.log(` end: ${formatDate(cycle.endsAt)}`);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
handleApiError(error);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|