@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.
@@ -1,4 +1,4 @@
1
- import type { Command } from "commander";
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 { handleApiError, exitWithError, EXIT_CODES } from "../lib/error";
10
- import {
11
- getOutputFormat,
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
- interface TeamShowOptions extends OutputOptions {
24
- members?: boolean;
25
- json?: boolean;
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
- export function registerTeamsCommand(program: Command): void {
29
- program
30
- .command("teams")
31
- .alias("t")
32
- .description("list teams")
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 (format === "json") {
33
+ if (input.json) {
43
34
  outputJson(teams);
44
35
  return;
45
36
  }
46
37
 
47
- if (format === "quiet") {
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 (options.members) {
84
- const members = await getTeamMembers(client, key);
70
+ if (input.members) {
71
+ const members = await getTeamMembers(client, input.key);
85
72
 
86
- if (format === "json") {
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 (format === "json") {
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
+ });
@@ -0,0 +1,7 @@
1
+ import { initTRPC } from "@trpc/server";
2
+ import type { TrpcCliMeta } from "trpc-cli";
3
+
4
+ export const t = initTRPC.meta<TrpcCliMeta>().create();
5
+
6
+ export const router = t.router;
7
+ export const procedure = t.procedure;
@@ -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
- }
@@ -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
- }