@bdsqqq/lnr-cli 1.6.0 → 2.0.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 +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 +6 -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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
import {
|
|
4
|
+
getClient,
|
|
5
|
+
listAgentSessions,
|
|
6
|
+
getAgentSession,
|
|
7
|
+
updateAgentSession,
|
|
8
|
+
getAgentSessionActivities,
|
|
9
|
+
type AgentSession,
|
|
10
|
+
type AgentActivity,
|
|
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
|
+
type OutputOptions,
|
|
20
|
+
type TableColumn,
|
|
21
|
+
} from "../lib/output";
|
|
22
|
+
|
|
23
|
+
export const listAgentSessionsInput = type({
|
|
24
|
+
"json?": type("boolean").describe("output as json"),
|
|
25
|
+
"quiet?": type("boolean").describe("output ids only"),
|
|
26
|
+
"verbose?": type("boolean").describe("show all columns"),
|
|
27
|
+
"status?": type("string").describe("filter by status (active, pending, complete, error, stale)"),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const agentSessionInput = type({
|
|
31
|
+
id: type("string").configure({ positional: true }).describe("agent session id"),
|
|
32
|
+
"json?": type("boolean").describe("output as json"),
|
|
33
|
+
"quiet?": type("boolean").describe("output id only"),
|
|
34
|
+
"verbose?": type("boolean").describe("show all fields"),
|
|
35
|
+
"externalLink?": type("string").describe("set external link url"),
|
|
36
|
+
"activities?": type("boolean").describe("show session activities"),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const sessionColumns: TableColumn<AgentSession>[] = [
|
|
40
|
+
{ header: "STATUS", value: (s) => s.status, width: 14 },
|
|
41
|
+
{ header: "TYPE", value: (s) => s.type, width: 14 },
|
|
42
|
+
{ header: "ISSUE", value: (s) => s.issueIdentifier ?? "-", width: 12 },
|
|
43
|
+
{ header: "CREATOR", value: (s) => s.creatorName ?? "-", width: 16 },
|
|
44
|
+
{
|
|
45
|
+
header: "DATE",
|
|
46
|
+
value: (s) => s.createdAt.toISOString().split("T")[0] ?? "",
|
|
47
|
+
width: 12,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const verboseSessionColumns: TableColumn<AgentSession>[] = [
|
|
52
|
+
...sessionColumns,
|
|
53
|
+
{ header: "APP USER", value: (s) => s.appUserName ?? "-", width: 16 },
|
|
54
|
+
{
|
|
55
|
+
header: "STARTED",
|
|
56
|
+
value: (s) =>
|
|
57
|
+
s.startedAt ? s.startedAt.toISOString().split("T")[0] ?? "-" : "-",
|
|
58
|
+
width: 12,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
header: "ENDED",
|
|
62
|
+
value: (s) =>
|
|
63
|
+
s.endedAt ? s.endedAt.toISOString().split("T")[0] ?? "-" : "-",
|
|
64
|
+
width: 12,
|
|
65
|
+
},
|
|
66
|
+
{ header: "ID", value: (s) => s.id, width: 36 },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const activityColumns: TableColumn<AgentActivity>[] = [
|
|
70
|
+
{ header: "TYPE", value: (a) => a.type, width: 12 },
|
|
71
|
+
{
|
|
72
|
+
header: "CONTENT",
|
|
73
|
+
value: (a) => {
|
|
74
|
+
if (a.content.action) {
|
|
75
|
+
return `${a.content.action}: ${a.content.parameter ?? ""}`.slice(0, 40);
|
|
76
|
+
}
|
|
77
|
+
return (a.content.body ?? "").slice(0, 40);
|
|
78
|
+
},
|
|
79
|
+
width: 42,
|
|
80
|
+
},
|
|
81
|
+
{ header: "USER", value: (a) => a.userName ?? "-", width: 16 },
|
|
82
|
+
{
|
|
83
|
+
header: "DATE",
|
|
84
|
+
value: (a) => a.createdAt.toISOString().split("T")[0] ?? "",
|
|
85
|
+
width: 12,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const verboseActivityColumns: TableColumn<AgentActivity>[] = [
|
|
90
|
+
...activityColumns,
|
|
91
|
+
{ header: "SIGNAL", value: (a) => a.signal ?? "-", width: 10 },
|
|
92
|
+
{ header: "EPHEMERAL", value: (a) => (a.ephemeral ? "yes" : "no"), width: 10 },
|
|
93
|
+
{ header: "ID", value: (a) => a.id, width: 36 },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export const agentSessionsRouter = router({
|
|
97
|
+
"agent-sessions": procedure
|
|
98
|
+
.meta({
|
|
99
|
+
aliases: { command: ["as"] },
|
|
100
|
+
description: "list agent sessions (experimental)",
|
|
101
|
+
})
|
|
102
|
+
.input(listAgentSessionsInput)
|
|
103
|
+
.query(async ({ input }) => {
|
|
104
|
+
try {
|
|
105
|
+
const client = getClient();
|
|
106
|
+
const sessions = await listAgentSessions(client, {
|
|
107
|
+
status: input.status,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const outputOpts: OutputOptions = {
|
|
111
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
112
|
+
verbose: input.verbose,
|
|
113
|
+
};
|
|
114
|
+
const format = getOutputFormat(outputOpts);
|
|
115
|
+
|
|
116
|
+
if (format === "json") {
|
|
117
|
+
outputJson(sessions);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (format === "quiet") {
|
|
122
|
+
outputQuiet(sessions.map((s) => s.id));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const columns = input.verbose ? verboseSessionColumns : sessionColumns;
|
|
127
|
+
outputTable(sessions, columns, outputOpts);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
handleApiError(error);
|
|
130
|
+
}
|
|
131
|
+
}),
|
|
132
|
+
|
|
133
|
+
"agent-session": procedure
|
|
134
|
+
.meta({ description: "show agent session details (experimental)" })
|
|
135
|
+
.input(agentSessionInput)
|
|
136
|
+
.mutation(async ({ input }) => {
|
|
137
|
+
try {
|
|
138
|
+
const client = getClient();
|
|
139
|
+
|
|
140
|
+
if (input.externalLink !== undefined) {
|
|
141
|
+
const success = await updateAgentSession(client, input.id, {
|
|
142
|
+
externalLink: input.externalLink,
|
|
143
|
+
});
|
|
144
|
+
if (!success) {
|
|
145
|
+
exitWithError(
|
|
146
|
+
`failed to update agent session "${input.id}"`,
|
|
147
|
+
"check the session id and permissions",
|
|
148
|
+
EXIT_CODES.GENERAL_ERROR
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
console.log("updated");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (input.activities) {
|
|
156
|
+
const activities = await getAgentSessionActivities(client, input.id);
|
|
157
|
+
|
|
158
|
+
const outputOpts: OutputOptions = {
|
|
159
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
160
|
+
verbose: input.verbose,
|
|
161
|
+
};
|
|
162
|
+
const format = getOutputFormat(outputOpts);
|
|
163
|
+
|
|
164
|
+
if (format === "json") {
|
|
165
|
+
outputJson(activities);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (format === "quiet") {
|
|
170
|
+
outputQuiet(activities.map((a) => a.id));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const columns = input.verbose
|
|
175
|
+
? verboseActivityColumns
|
|
176
|
+
: activityColumns;
|
|
177
|
+
outputTable(activities, columns, outputOpts);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const session = await getAgentSession(client, input.id);
|
|
182
|
+
|
|
183
|
+
if (!session) {
|
|
184
|
+
exitWithError(
|
|
185
|
+
`agent session "${input.id}" not found`,
|
|
186
|
+
"try: lnr agent-sessions",
|
|
187
|
+
EXIT_CODES.NOT_FOUND
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const outputOpts: OutputOptions = {
|
|
192
|
+
format: input.json ? "json" : input.quiet ? "quiet" : undefined,
|
|
193
|
+
verbose: input.verbose,
|
|
194
|
+
};
|
|
195
|
+
const format = getOutputFormat(outputOpts);
|
|
196
|
+
|
|
197
|
+
if (format === "json") {
|
|
198
|
+
outputJson(session);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (format === "quiet") {
|
|
203
|
+
console.log(session.id);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`status: ${session.status}`);
|
|
208
|
+
console.log(`type: ${session.type}`);
|
|
209
|
+
console.log(`created: ${session.createdAt.toISOString()}`);
|
|
210
|
+
if (session.issueIdentifier) {
|
|
211
|
+
console.log(`issue: ${session.issueIdentifier}`);
|
|
212
|
+
}
|
|
213
|
+
if (session.creatorName) {
|
|
214
|
+
console.log(`creator: ${session.creatorName}`);
|
|
215
|
+
}
|
|
216
|
+
if (session.appUserName) {
|
|
217
|
+
console.log(`app user: ${session.appUserName}`);
|
|
218
|
+
}
|
|
219
|
+
if (session.summary) {
|
|
220
|
+
console.log(`summary: ${session.summary}`);
|
|
221
|
+
}
|
|
222
|
+
if (session.externalLink) {
|
|
223
|
+
console.log(`external link: ${session.externalLink}`);
|
|
224
|
+
}
|
|
225
|
+
if (session.startedAt) {
|
|
226
|
+
console.log(`started: ${session.startedAt.toISOString()}`);
|
|
227
|
+
}
|
|
228
|
+
if (session.endedAt) {
|
|
229
|
+
console.log(`ended: ${session.endedAt.toISOString()}`);
|
|
230
|
+
}
|
|
231
|
+
if (input.verbose) {
|
|
232
|
+
console.log(`id: ${session.id}`);
|
|
233
|
+
if (session.issueId) {
|
|
234
|
+
console.log(`issue id: ${session.issueId}`);
|
|
235
|
+
}
|
|
236
|
+
if (session.commentId) {
|
|
237
|
+
console.log(`comment id: ${session.commentId}`);
|
|
238
|
+
}
|
|
239
|
+
if (session.dismissedAt) {
|
|
240
|
+
console.log(`dismissed: ${session.dismissedAt.toISOString()}`);
|
|
241
|
+
}
|
|
242
|
+
if (session.archivedAt) {
|
|
243
|
+
console.log(`archived: ${session.archivedAt.toISOString()}`);
|
|
244
|
+
}
|
|
245
|
+
if (session.plan) {
|
|
246
|
+
console.log(`plan: ${JSON.stringify(session.plan)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
handleApiError(error);
|
|
251
|
+
}
|
|
252
|
+
}),
|
|
253
|
+
});
|
package/src/router/auth.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
2
3
|
import {
|
|
3
4
|
setApiKey,
|
|
4
5
|
clearApiKey,
|
|
@@ -9,10 +10,10 @@ import {
|
|
|
9
10
|
import { router, procedure } from "./trpc";
|
|
10
11
|
import { exitWithError, EXIT_CODES } from "../lib/error";
|
|
11
12
|
|
|
12
|
-
const authInput =
|
|
13
|
-
apiKey:
|
|
14
|
-
whoami:
|
|
15
|
-
logout:
|
|
13
|
+
export const authInput = type({
|
|
14
|
+
"apiKey?": type("string").configure({ positional: true }).describe("Linear API key"),
|
|
15
|
+
"whoami?": type("boolean").describe("show current authenticated user"),
|
|
16
|
+
"logout?": type("boolean").describe("clear stored credentials"),
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
export const authRouter = router({
|
package/src/router/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../lib/arktype-config";
|
|
2
|
+
import { type } from "arktype";
|
|
2
3
|
import {
|
|
3
4
|
loadConfig,
|
|
4
5
|
getConfigValue,
|
|
@@ -8,13 +9,13 @@ import {
|
|
|
8
9
|
import { router, procedure } from "./trpc";
|
|
9
10
|
import { exitWithError } from "../lib/error";
|
|
10
11
|
|
|
11
|
-
const getInput =
|
|
12
|
-
key:
|
|
12
|
+
export const getInput = type({
|
|
13
|
+
key: type("'api_key' | 'default_team' | 'output_format'").configure({ positional: true }).describe("config key to get"),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
const setInput =
|
|
16
|
-
key:
|
|
17
|
-
value:
|
|
16
|
+
export const setInput = type({
|
|
17
|
+
key: type("'api_key' | 'default_team' | 'output_format'").configure({ positional: true }).describe("config key to set"),
|
|
18
|
+
value: type("string").configure({ positional: true }).describe("value to set"),
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
export const configRouter = router({
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contract tests: CLI input → core function parameters
|
|
3
|
+
*
|
|
4
|
+
* these tests verify the transformation from CLI flags to API request payloads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect } from "bun:test";
|
|
8
|
+
import { type } from "arktype";
|
|
9
|
+
|
|
10
|
+
// import schemas directly from router files
|
|
11
|
+
import { listIssuesInput, issueInput } from "../generated/issue";
|
|
12
|
+
import { listProjectsInput, projectInput } from "../generated/project";
|
|
13
|
+
import { listTeamsInput, teamInput } from "../router/teams";
|
|
14
|
+
import { listCyclesInput, cycleInput } from "../router/cycles";
|
|
15
|
+
import { listDocsInput, docInput } from "../generated/doc";
|
|
16
|
+
import { listLabelsInput, labelInput } from "../generated/label";
|
|
17
|
+
import { meInput } from "../router/me";
|
|
18
|
+
import { searchInput } from "../router/search";
|
|
19
|
+
import { authInput } from "../router/auth";
|
|
20
|
+
import { getInput as configGetInput, setInput as configSetInput } from "../router/config";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* unified validation helper that works with both zod and arktype schemas.
|
|
24
|
+
* provides consistent safeParse-like interface during migration.
|
|
25
|
+
*/
|
|
26
|
+
function safeParse<T>(schema: unknown, data: unknown): { success: boolean; data?: T; error?: unknown } {
|
|
27
|
+
// zod schema check
|
|
28
|
+
if (typeof schema === "object" && schema !== null && "safeParse" in schema) {
|
|
29
|
+
const zodResult = (schema as { safeParse: (d: unknown) => { success: boolean; data?: T; error?: unknown } }).safeParse(data);
|
|
30
|
+
return zodResult;
|
|
31
|
+
}
|
|
32
|
+
// arktype schema (callable)
|
|
33
|
+
if (typeof schema === "function") {
|
|
34
|
+
const result = (schema as (d: unknown) => unknown)(data);
|
|
35
|
+
if (result instanceof type.errors) {
|
|
36
|
+
return { success: false, error: result };
|
|
37
|
+
}
|
|
38
|
+
return { success: true, data: result as T };
|
|
39
|
+
}
|
|
40
|
+
throw new Error("unknown schema type");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// operation inference - mirrors router logic
|
|
44
|
+
function inferOperation(command: string, input: Record<string, unknown>): "create" | "update" | "delete" | "show" {
|
|
45
|
+
const positionalNewCommands = ["issue", "project", "doc", "label"];
|
|
46
|
+
if (positionalNewCommands.includes(command)) {
|
|
47
|
+
const positional = input.idOrNew ?? input.name ?? input.id;
|
|
48
|
+
if (positional === "new") return "create";
|
|
49
|
+
}
|
|
50
|
+
if (input.delete || input.archive) return "delete";
|
|
51
|
+
const mutationFlags = [
|
|
52
|
+
"state", "assignee", "priority", "label", "comment",
|
|
53
|
+
"editComment", "replyTo", "deleteComment", "react", "unreact",
|
|
54
|
+
"parent", "blocks", "blockedBy", "relatesTo", "title",
|
|
55
|
+
"content", "projectName"
|
|
56
|
+
];
|
|
57
|
+
for (const flag of mutationFlags) {
|
|
58
|
+
if (input[flag] !== undefined) return "update";
|
|
59
|
+
}
|
|
60
|
+
return "show";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("projects", () => {
|
|
64
|
+
describe("projects", () => {
|
|
65
|
+
test("valid input parses", () => {
|
|
66
|
+
const result = safeParse(listProjectsInput, {});
|
|
67
|
+
expect(result.success).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("infers SHOW with no mutation flags", () => {
|
|
71
|
+
expect(inferOperation("projects", {})).toBe("show");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("project", () => {
|
|
77
|
+
test("valid input parses", () => {
|
|
78
|
+
const result = safeParse(projectInput, {"name":"test-value"});
|
|
79
|
+
expect(result.success).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("rejects missing required flags", () => {
|
|
83
|
+
const result = safeParse(projectInput, {});
|
|
84
|
+
expect(result.success).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("infers CREATE when name='new'", () => {
|
|
88
|
+
expect(inferOperation("project", { name: "new" })).toBe("create");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("infers DELETE when --delete", () => {
|
|
92
|
+
expect(inferOperation("project", {"name":"test-value","delete":true})).toBe("delete");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("infers SHOW with no mutation flags", () => {
|
|
96
|
+
expect(inferOperation("project", {"name":"test-value"})).toBe("show");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("search", () => {
|
|
104
|
+
describe("search", () => {
|
|
105
|
+
test("valid input parses", () => {
|
|
106
|
+
const result = safeParse(searchInput, {"query":"test-value"});
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("rejects missing required flags", () => {
|
|
111
|
+
const result = safeParse(searchInput, {});
|
|
112
|
+
expect(result.success).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("cycles", () => {
|
|
120
|
+
describe("cycles", () => {
|
|
121
|
+
test("valid input parses", () => {
|
|
122
|
+
const result = safeParse(listCyclesInput, {"team":"test-value"});
|
|
123
|
+
expect(result.success).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("rejects missing required flags", () => {
|
|
127
|
+
const result = safeParse(listCyclesInput, {});
|
|
128
|
+
expect(result.success).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("cycle", () => {
|
|
134
|
+
test("valid input parses", () => {
|
|
135
|
+
const result = safeParse(cycleInput, {"nameOrNumber":"1","team":"test-value"});
|
|
136
|
+
expect(result.success).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("rejects missing required flags", () => {
|
|
140
|
+
const result = safeParse(cycleInput, {});
|
|
141
|
+
expect(result.success).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("infers CREATE when nameOrNumber='new'", () => {
|
|
145
|
+
const result = safeParse(cycleInput, {"nameOrNumber":"new","team":"ENG"});
|
|
146
|
+
expect(result.success).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("infers DELETE when --delete", () => {
|
|
150
|
+
const result = safeParse(cycleInput, {"nameOrNumber":"1","team":"ENG","delete":true});
|
|
151
|
+
expect(result.success).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("infers SHOW with no mutation flags", () => {
|
|
155
|
+
const result = safeParse(cycleInput, {"nameOrNumber":"Sprint 1","team":"ENG"});
|
|
156
|
+
expect(result.success).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("docs", () => {
|
|
164
|
+
describe("docs", () => {
|
|
165
|
+
test("valid input parses", () => {
|
|
166
|
+
const result = safeParse(listDocsInput, {});
|
|
167
|
+
expect(result.success).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("infers SHOW with no mutation flags", () => {
|
|
171
|
+
expect(inferOperation("docs", {})).toBe("show");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("doc", () => {
|
|
177
|
+
test("valid input parses", () => {
|
|
178
|
+
const result = safeParse(docInput, {"id":"test-value"});
|
|
179
|
+
expect(result.success).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("rejects missing required flags", () => {
|
|
183
|
+
const result = safeParse(docInput, {});
|
|
184
|
+
expect(result.success).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("infers CREATE when id='new'", () => {
|
|
188
|
+
expect(inferOperation("doc", { id: "new" })).toBe("create");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("infers DELETE when --delete", () => {
|
|
192
|
+
expect(inferOperation("doc", {"id":"test-value","delete":true})).toBe("delete");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("infers SHOW with no mutation flags", () => {
|
|
196
|
+
expect(inferOperation("doc", {"id":"test-value"})).toBe("show");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("teams", () => {
|
|
204
|
+
describe("teams", () => {
|
|
205
|
+
test("valid input parses", () => {
|
|
206
|
+
const result = safeParse(listTeamsInput, {});
|
|
207
|
+
expect(result.success).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("team", () => {
|
|
213
|
+
test("valid input parses", () => {
|
|
214
|
+
const result = safeParse(teamInput, {"key":"test-value"});
|
|
215
|
+
expect(result.success).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("rejects missing required flags", () => {
|
|
219
|
+
const result = safeParse(teamInput, {});
|
|
220
|
+
expect(result.success).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("config", () => {
|
|
228
|
+
describe("get", () => {
|
|
229
|
+
test("valid input parses", () => {
|
|
230
|
+
const result = safeParse(configGetInput, {"key":"api_key"});
|
|
231
|
+
expect(result.success).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("rejects missing required flags", () => {
|
|
235
|
+
const result = safeParse(configGetInput, {});
|
|
236
|
+
expect(result.success).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("set", () => {
|
|
242
|
+
test("valid input parses", () => {
|
|
243
|
+
const result = safeParse(configSetInput, {"key":"api_key","value":"test-value"});
|
|
244
|
+
expect(result.success).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("rejects missing required flags", () => {
|
|
248
|
+
const result = safeParse(configSetInput, {});
|
|
249
|
+
expect(result.success).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("me", () => {
|
|
257
|
+
describe("me", () => {
|
|
258
|
+
test("valid input parses", () => {
|
|
259
|
+
const result = safeParse(meInput, {});
|
|
260
|
+
expect(result.success).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("labels", () => {
|
|
268
|
+
describe("labels", () => {
|
|
269
|
+
test("valid input parses", () => {
|
|
270
|
+
const result = safeParse(listLabelsInput, {});
|
|
271
|
+
expect(result.success).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("infers SHOW with no mutation flags", () => {
|
|
275
|
+
expect(inferOperation("labels", {})).toBe("show");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("label", () => {
|
|
281
|
+
test("valid input parses", () => {
|
|
282
|
+
const result = safeParse(labelInput, {"id":"test-value"});
|
|
283
|
+
expect(result.success).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("rejects missing required flags", () => {
|
|
287
|
+
const result = safeParse(labelInput, {});
|
|
288
|
+
expect(result.success).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("infers CREATE when id='new'", () => {
|
|
292
|
+
expect(inferOperation("label", { id: "new" })).toBe("create");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("infers DELETE when --delete", () => {
|
|
296
|
+
expect(inferOperation("label", {"id":"test-value","delete":true})).toBe("delete");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("infers SHOW with no mutation flags", () => {
|
|
300
|
+
expect(inferOperation("label", {"id":"test-value"})).toBe("show");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("auth", () => {
|
|
308
|
+
describe("auth", () => {
|
|
309
|
+
test("valid input parses", () => {
|
|
310
|
+
const result = safeParse(authInput, {});
|
|
311
|
+
expect(result.success).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("issues", () => {
|
|
319
|
+
describe("issues", () => {
|
|
320
|
+
test("valid input parses", () => {
|
|
321
|
+
const result = safeParse(listIssuesInput, {});
|
|
322
|
+
expect(result.success).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("infers UPDATE when --state", () => {
|
|
326
|
+
expect(inferOperation("issues", {"state":"test"})).toBe("update");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("infers SHOW with no mutation flags", () => {
|
|
330
|
+
expect(inferOperation("issues", {})).toBe("show");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("issue", () => {
|
|
336
|
+
test("valid input parses", () => {
|
|
337
|
+
const result = safeParse(issueInput, {"idOrNew":"test-value"});
|
|
338
|
+
expect(result.success).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("rejects missing required flags", () => {
|
|
342
|
+
const result = safeParse(issueInput, {});
|
|
343
|
+
expect(result.success).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("infers CREATE when idOrNew='new'", () => {
|
|
347
|
+
expect(inferOperation("issue", { idOrNew: "new" })).toBe("create");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("infers DELETE when --archive", () => {
|
|
351
|
+
expect(inferOperation("issue", {"idOrNew":"test-value","archive":true})).toBe("delete");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("infers UPDATE when --state", () => {
|
|
355
|
+
expect(inferOperation("issue", {"idOrNew":"test-value","state":"test"})).toBe("update");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("infers SHOW with no mutation flags", () => {
|
|
359
|
+
expect(inferOperation("issue", {"idOrNew":"test-value"})).toBe("show");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
});
|