@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.
- 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 +9 -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
package/src/router/search.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
2
3
|
import { getClient, searchIssues } from "@bdsqqq/lnr-core";
|
|
3
4
|
import { router, procedure } from "./trpc";
|
|
4
5
|
import { handleApiError } from "../lib/error";
|
|
@@ -10,11 +11,11 @@ import {
|
|
|
10
11
|
truncate,
|
|
11
12
|
} from "../lib/output";
|
|
12
13
|
|
|
13
|
-
const searchInput =
|
|
14
|
-
query:
|
|
15
|
-
team:
|
|
16
|
-
json:
|
|
17
|
-
quiet:
|
|
14
|
+
export const searchInput = type({
|
|
15
|
+
query: type("string").configure({ positional: true }).describe("search query"),
|
|
16
|
+
"team?": type("string").describe("filter by team key"),
|
|
17
|
+
"json?": type("boolean").describe("output as json"),
|
|
18
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
export const searchRouter = router({
|
package/src/router/teams.ts
CHANGED
|
@@ -1,50 +1,92 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
2
3
|
import {
|
|
3
4
|
getClient,
|
|
4
5
|
listTeams,
|
|
5
6
|
getTeam,
|
|
6
7
|
getTeamMembers,
|
|
7
8
|
getAvailableTeamKeys,
|
|
9
|
+
type Team,
|
|
10
|
+
type TeamMember,
|
|
8
11
|
} from "@bdsqqq/lnr-core";
|
|
9
12
|
import { router, procedure } from "./trpc";
|
|
10
13
|
import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
|
|
11
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
outputJson,
|
|
16
|
+
outputQuiet,
|
|
17
|
+
outputTable,
|
|
18
|
+
getOutputFormat,
|
|
19
|
+
truncate,
|
|
20
|
+
type OutputOptions,
|
|
21
|
+
type TableColumn,
|
|
22
|
+
} from "../lib/output";
|
|
12
23
|
|
|
13
|
-
const
|
|
14
|
-
json:
|
|
15
|
-
quiet:
|
|
24
|
+
export const listTeamsInput = type({
|
|
25
|
+
"json?": type("boolean").describe("output as json"),
|
|
26
|
+
"quiet?": type("boolean").describe("output keys only"),
|
|
27
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
16
28
|
});
|
|
17
29
|
|
|
18
|
-
const teamInput =
|
|
19
|
-
key:
|
|
20
|
-
members:
|
|
21
|
-
json:
|
|
30
|
+
export const teamInput = type({
|
|
31
|
+
key: type("string").configure({ positional: true }).describe("team key"),
|
|
32
|
+
"members?": type("boolean").describe("list team members"),
|
|
33
|
+
"json?": type("boolean").describe("output as json"),
|
|
34
|
+
"quiet?": type("boolean").describe("output id only"),
|
|
35
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
22
36
|
});
|
|
23
37
|
|
|
38
|
+
const teamColumns: TableColumn<Team>[] = [
|
|
39
|
+
{ header: "KEY", value: (t) => t.key, width: 8 },
|
|
40
|
+
{ header: "NAME", value: (t) => t.name, width: 24 },
|
|
41
|
+
{ header: "DESCRIPTION", value: (t) => truncate(t.description ?? "-", 40), width: 40 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const verboseTeamColumns: TableColumn<Team>[] = [
|
|
45
|
+
...teamColumns,
|
|
46
|
+
{ header: "PRIVATE", value: (t) => (t.private ? "yes" : "no"), width: 8 },
|
|
47
|
+
{ header: "TIMEZONE", value: (t) => t.timezone ?? "-", width: 20 },
|
|
48
|
+
{ header: "ID", value: (t) => t.id, width: 36 },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const memberColumns: TableColumn<TeamMember>[] = [
|
|
52
|
+
{ header: "NAME", value: (m) => m.name, width: 24 },
|
|
53
|
+
{ header: "EMAIL", value: (m) => m.email ?? "-", width: 32 },
|
|
54
|
+
{ header: "ACTIVE", value: (m) => (m.active ? "yes" : "no"), width: 8 },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const verboseMemberColumns: TableColumn<TeamMember>[] = [
|
|
58
|
+
...memberColumns,
|
|
59
|
+
{ header: "DISPLAY NAME", value: (m) => m.displayName ?? "-", width: 24 },
|
|
60
|
+
{ header: "ID", value: (m) => m.id, width: 36 },
|
|
61
|
+
];
|
|
62
|
+
|
|
24
63
|
export const teamsRouter = router({
|
|
25
64
|
teams: procedure
|
|
26
65
|
.meta({ aliases: { command: ["t"] }, description: "list teams" })
|
|
27
|
-
.input(
|
|
66
|
+
.input(listTeamsInput)
|
|
28
67
|
.query(async ({ input }) => {
|
|
29
68
|
try {
|
|
30
69
|
const client = getClient();
|
|
31
70
|
const teams = await listTeams(client);
|
|
32
71
|
|
|
33
|
-
|
|
72
|
+
const outputOpts: OutputOptions = {
|
|
73
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
74
|
+
verbose: input.verbose,
|
|
75
|
+
};
|
|
76
|
+
const format = getOutputFormat(outputOpts);
|
|
77
|
+
|
|
78
|
+
if (format === "json") {
|
|
34
79
|
outputJson(teams);
|
|
35
80
|
return;
|
|
36
81
|
}
|
|
37
82
|
|
|
38
|
-
if (
|
|
83
|
+
if (format === "quiet") {
|
|
39
84
|
outputQuiet(teams.map((t) => t.key));
|
|
40
85
|
return;
|
|
41
86
|
}
|
|
42
87
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{ header: "NAME", value: (t) => t.name, width: 24 },
|
|
46
|
-
{ header: "DESCRIPTION", value: (t) => t.description ?? "-", width: 40 },
|
|
47
|
-
]);
|
|
88
|
+
const columns = input.verbose ? verboseTeamColumns : teamColumns;
|
|
89
|
+
outputTable(teams, columns, outputOpts);
|
|
48
90
|
} catch (error) {
|
|
49
91
|
handleApiError(error);
|
|
50
92
|
}
|
|
@@ -67,34 +109,50 @@ export const teamsRouter = router({
|
|
|
67
109
|
);
|
|
68
110
|
}
|
|
69
111
|
|
|
112
|
+
const outputOpts: OutputOptions = {
|
|
113
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
114
|
+
verbose: input.verbose,
|
|
115
|
+
};
|
|
116
|
+
const format = getOutputFormat(outputOpts);
|
|
117
|
+
|
|
70
118
|
if (input.members) {
|
|
71
119
|
const members = await getTeamMembers(client, input.key);
|
|
72
120
|
|
|
73
|
-
if (
|
|
121
|
+
if (format === "json") {
|
|
74
122
|
outputJson(members);
|
|
75
123
|
return;
|
|
76
124
|
}
|
|
77
125
|
|
|
126
|
+
if (format === "quiet") {
|
|
127
|
+
outputQuiet(members.map((m) => m.id));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
78
131
|
console.log(`${team.name} (${team.key}) members:\n`);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{ header: "EMAIL", value: (m) => m.email ?? "-", width: 32 },
|
|
82
|
-
{ header: "ACTIVE", value: (m) => (m.active ? "yes" : "no"), width: 8 },
|
|
83
|
-
]);
|
|
132
|
+
const cols = input.verbose ? verboseMemberColumns : memberColumns;
|
|
133
|
+
outputTable(members, cols, outputOpts);
|
|
84
134
|
return;
|
|
85
135
|
}
|
|
86
136
|
|
|
87
|
-
if (
|
|
137
|
+
if (format === "json") {
|
|
88
138
|
outputJson(team);
|
|
89
139
|
return;
|
|
90
140
|
}
|
|
91
141
|
|
|
142
|
+
if (format === "quiet") {
|
|
143
|
+
console.log(team.id);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
92
147
|
console.log(`${team.name} (${team.key})`);
|
|
93
148
|
if (team.description) {
|
|
94
149
|
console.log(team.description);
|
|
95
150
|
}
|
|
96
151
|
console.log(`timezone: ${team.timezone ?? "-"}`);
|
|
97
152
|
console.log(`private: ${team.private ? "yes" : "no"}`);
|
|
153
|
+
if (input.verbose) {
|
|
154
|
+
console.log(`id: ${team.id}`);
|
|
155
|
+
}
|
|
98
156
|
} catch (error) {
|
|
99
157
|
handleApiError(error);
|
|
100
158
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
import {
|
|
4
|
+
getClient,
|
|
5
|
+
listUsers,
|
|
6
|
+
findUserByNameOrEmail,
|
|
7
|
+
type User,
|
|
8
|
+
} from "@bdsqqq/lnr-core";
|
|
9
|
+
import { router, procedure } from "./trpc";
|
|
10
|
+
import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
|
|
11
|
+
import {
|
|
12
|
+
outputJson,
|
|
13
|
+
outputQuiet,
|
|
14
|
+
outputTable,
|
|
15
|
+
getOutputFormat,
|
|
16
|
+
type OutputOptions,
|
|
17
|
+
type TableColumn,
|
|
18
|
+
} from "../lib/output";
|
|
19
|
+
|
|
20
|
+
export const listUsersInput = type({
|
|
21
|
+
"json?": type("boolean").describe("output as json"),
|
|
22
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
23
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const userInput = type({
|
|
27
|
+
nameOrEmail: type("string").configure({ positional: true }).describe("user name or email"),
|
|
28
|
+
"json?": type("boolean").describe("output as json"),
|
|
29
|
+
"quiet?": type("boolean").describe("output id only"),
|
|
30
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const userColumns: TableColumn<User>[] = [
|
|
34
|
+
{ header: "NAME", value: (u) => u.name, width: 24 },
|
|
35
|
+
{ header: "EMAIL", value: (u) => u.email ?? "-", width: 32 },
|
|
36
|
+
{ header: "ACTIVE", value: (u) => (u.active ? "yes" : "no"), width: 8 },
|
|
37
|
+
{ header: "ADMIN", value: (u) => (u.admin ? "yes" : "no"), width: 8 },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const verboseUserColumns: TableColumn<User>[] = [
|
|
41
|
+
...userColumns,
|
|
42
|
+
{ header: "DISPLAY NAME", value: (u) => u.displayName ?? "-", width: 24 },
|
|
43
|
+
{ header: "ID", value: (u) => u.id, width: 36 },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export const usersRouter = router({
|
|
47
|
+
users: procedure
|
|
48
|
+
.meta({ aliases: { command: ["u"] }, description: "list users" })
|
|
49
|
+
.input(listUsersInput)
|
|
50
|
+
.query(async ({ input }) => {
|
|
51
|
+
try {
|
|
52
|
+
const client = getClient();
|
|
53
|
+
const users = await listUsers(client);
|
|
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
|
+
if (format === "json") {
|
|
62
|
+
outputJson(users);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (format === "quiet") {
|
|
67
|
+
outputQuiet(users.map((u) => u.id));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const columns = input.verbose ? verboseUserColumns : userColumns;
|
|
72
|
+
outputTable(users, columns, outputOpts);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
handleApiError(error);
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
user: procedure
|
|
79
|
+
.meta({ description: "show user details" })
|
|
80
|
+
.input(userInput)
|
|
81
|
+
.query(async ({ input }) => {
|
|
82
|
+
try {
|
|
83
|
+
const client = getClient();
|
|
84
|
+
const user = await findUserByNameOrEmail(client, input.nameOrEmail);
|
|
85
|
+
|
|
86
|
+
if (!user) {
|
|
87
|
+
exitWithError(
|
|
88
|
+
`user "${input.nameOrEmail}" not found`,
|
|
89
|
+
"try: lnr users",
|
|
90
|
+
EXIT_CODES.NOT_FOUND
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const outputOpts: OutputOptions = {
|
|
95
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
96
|
+
verbose: input.verbose,
|
|
97
|
+
};
|
|
98
|
+
const format = getOutputFormat(outputOpts);
|
|
99
|
+
|
|
100
|
+
if (format === "json") {
|
|
101
|
+
outputJson(user);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (format === "quiet") {
|
|
106
|
+
console.log(user.id);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`${user.name}`);
|
|
111
|
+
if (user.email) {
|
|
112
|
+
console.log(`email: ${user.email}`);
|
|
113
|
+
}
|
|
114
|
+
if (user.displayName) {
|
|
115
|
+
console.log(`display name: ${user.displayName}`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`active: ${user.active ? "yes" : "no"}`);
|
|
118
|
+
console.log(`admin: ${user.admin ? "yes" : "no"}`);
|
|
119
|
+
if (input.verbose) {
|
|
120
|
+
console.log(`id: ${user.id}`);
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
handleApiError(error);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
});
|