@bdsqqq/lnr-cli 1.3.0 → 1.4.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 +1 -1
- package/src/router/issues.ts +70 -49
package/package.json
CHANGED
package/src/router/issues.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
createReaction,
|
|
22
22
|
deleteReaction,
|
|
23
23
|
createIssueRelation,
|
|
24
|
+
getProject,
|
|
24
25
|
type Issue,
|
|
25
26
|
type ListIssuesFilter,
|
|
26
27
|
type Comment,
|
|
@@ -65,6 +66,7 @@ const issueInput = z.object({
|
|
|
65
66
|
team: z.string().optional().describe("team key (required for new)"),
|
|
66
67
|
title: z.string().optional().describe("issue title (required for new)"),
|
|
67
68
|
description: z.string().optional().describe("issue description"),
|
|
69
|
+
project: z.string().optional().describe("project name to add issue to"),
|
|
68
70
|
comments: z.boolean().optional().describe("list comments on issue"),
|
|
69
71
|
editComment: z.string().optional().describe("comment id to edit (requires --text)"),
|
|
70
72
|
text: z.string().optional().describe("text for --edit-comment or --reply-to"),
|
|
@@ -228,61 +230,27 @@ async function handleUpdateIssue(
|
|
|
228
230
|
exitWithError(`issue ${identifier} not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return;
|
|
233
|
+
// Upfront validation: required flags
|
|
234
|
+
if (input.editComment && !input.text) {
|
|
235
|
+
exitWithError("--text is required with --edit-comment");
|
|
235
236
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (!input.text) {
|
|
239
|
-
exitWithError("--text is required with --edit-comment");
|
|
240
|
-
}
|
|
241
|
-
await updateComment(client, input.editComment, input.text);
|
|
242
|
-
console.log(`updated comment ${input.editComment.slice(0, 8)}`);
|
|
243
|
-
return;
|
|
237
|
+
if (input.replyTo && !input.text) {
|
|
238
|
+
exitWithError("--text is required with --reply-to");
|
|
244
239
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!input.text) {
|
|
248
|
-
exitWithError("--text is required with --reply-to");
|
|
249
|
-
}
|
|
250
|
-
await replyToComment(client, issue.id, input.replyTo, input.text);
|
|
251
|
-
console.log(`replied to comment ${input.replyTo.slice(0, 8)}`);
|
|
252
|
-
return;
|
|
240
|
+
if (input.react && !input.emoji) {
|
|
241
|
+
exitWithError("--emoji is required with --react");
|
|
253
242
|
}
|
|
254
243
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
244
|
+
// Upfront validation: mutual exclusivity for comment operations
|
|
245
|
+
const commentOpCount = [input.comment, input.editComment, input.replyTo, input.deleteComment].filter(Boolean).length;
|
|
246
|
+
if (commentOpCount > 1) {
|
|
247
|
+
exitWithError("only one comment operation allowed per invocation", "use --comment, --edit-comment, --reply-to, or --delete-comment separately");
|
|
259
248
|
}
|
|
260
249
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (input.react) {
|
|
268
|
-
if (!input.emoji) {
|
|
269
|
-
exitWithError("--emoji is required with --react");
|
|
270
|
-
}
|
|
271
|
-
const success = await createReaction(client, input.react, input.emoji);
|
|
272
|
-
if (!success) {
|
|
273
|
-
exitWithError(`failed to add reaction to comment ${input.react.slice(0, 8)}`);
|
|
274
|
-
}
|
|
275
|
-
console.log(`added reaction ${input.emoji} to comment ${input.react.slice(0, 8)}`);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (input.unreact) {
|
|
280
|
-
const success = await deleteReaction(client, input.unreact);
|
|
281
|
-
if (!success) {
|
|
282
|
-
exitWithError(`reaction ${input.unreact.slice(0, 8)} not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
283
|
-
}
|
|
284
|
-
console.log(`removed reaction ${input.unreact.slice(0, 8)}`);
|
|
285
|
-
return;
|
|
250
|
+
// Upfront validation: mutual exclusivity for reaction operations
|
|
251
|
+
const reactionOpCount = [input.react, input.unreact].filter(Boolean).length;
|
|
252
|
+
if (reactionOpCount > 1) {
|
|
253
|
+
exitWithError("only one reaction operation allowed per invocation", "use --react or --unreact separately");
|
|
286
254
|
}
|
|
287
255
|
|
|
288
256
|
const updatePayload: Record<string, unknown> = {};
|
|
@@ -421,6 +389,50 @@ async function handleUpdateIssue(
|
|
|
421
389
|
}
|
|
422
390
|
console.log(`${identifier} now relates to ${input.relatesTo}`);
|
|
423
391
|
}
|
|
392
|
+
|
|
393
|
+
// Comment operations (mutually exclusive, validated above)
|
|
394
|
+
if (input.comment) {
|
|
395
|
+
await addComment(client, issue.id, input.comment);
|
|
396
|
+
console.log(`commented on ${identifier}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (input.editComment) {
|
|
400
|
+
await updateComment(client, input.editComment, input.text!);
|
|
401
|
+
console.log(`updated comment ${input.editComment.slice(0, 8)}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (input.replyTo) {
|
|
405
|
+
await replyToComment(client, issue.id, input.replyTo, input.text!);
|
|
406
|
+
console.log(`replied to comment ${input.replyTo.slice(0, 8)}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (input.deleteComment) {
|
|
410
|
+
await deleteComment(client, input.deleteComment);
|
|
411
|
+
console.log(`deleted comment ${input.deleteComment.slice(0, 8)}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Reaction operations (mutually exclusive, validated above)
|
|
415
|
+
if (input.react) {
|
|
416
|
+
const success = await createReaction(client, input.react, input.emoji!);
|
|
417
|
+
if (!success) {
|
|
418
|
+
exitWithError(`failed to add reaction to comment ${input.react.slice(0, 8)}`);
|
|
419
|
+
}
|
|
420
|
+
console.log(`added reaction ${input.emoji} to comment ${input.react.slice(0, 8)}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (input.unreact) {
|
|
424
|
+
const success = await deleteReaction(client, input.unreact);
|
|
425
|
+
if (!success) {
|
|
426
|
+
exitWithError(`reaction ${input.unreact.slice(0, 8)} not found`, undefined, EXIT_CODES.NOT_FOUND);
|
|
427
|
+
}
|
|
428
|
+
console.log(`removed reaction ${input.unreact.slice(0, 8)}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Archive last (restricts further edits)
|
|
432
|
+
if (input.archive) {
|
|
433
|
+
await archiveIssue(client, issue.id);
|
|
434
|
+
console.log(`archived ${identifier}`);
|
|
435
|
+
}
|
|
424
436
|
} catch (error) {
|
|
425
437
|
handleApiError(error);
|
|
426
438
|
}
|
|
@@ -452,6 +464,7 @@ async function handleCreateIssue(input: IssueInput): Promise<void> {
|
|
|
452
464
|
priority?: number;
|
|
453
465
|
labelIds?: string[];
|
|
454
466
|
parentId?: string;
|
|
467
|
+
projectId?: string;
|
|
455
468
|
} = {
|
|
456
469
|
teamId: team.id,
|
|
457
470
|
title: input.title,
|
|
@@ -499,6 +512,14 @@ async function handleCreateIssue(input: IssueInput): Promise<void> {
|
|
|
499
512
|
createPayload.parentId = parentIssue.id;
|
|
500
513
|
}
|
|
501
514
|
|
|
515
|
+
if (input.project) {
|
|
516
|
+
const project = await getProject(client, input.project);
|
|
517
|
+
if (!project) {
|
|
518
|
+
exitWithError(`project "${input.project}" not found`);
|
|
519
|
+
}
|
|
520
|
+
createPayload.projectId = project.id;
|
|
521
|
+
}
|
|
522
|
+
|
|
502
523
|
const issue = await createIssue(client, createPayload);
|
|
503
524
|
if (issue) {
|
|
504
525
|
console.log(`created ${issue.identifier}: ${issue.title}`);
|