@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bdsqqq/lnr-cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "cli for linear issue tracking",
5
5
  "type": "module",
6
6
  "private": false,
@@ -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
- if (input.comment) {
232
- await addComment(client, issue.id, input.comment);
233
- console.log(`commented on ${identifier}`);
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
- if (input.editComment) {
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
- if (input.replyTo) {
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
- if (input.deleteComment) {
256
- await deleteComment(client, input.deleteComment);
257
- console.log(`deleted comment ${input.deleteComment.slice(0, 8)}`);
258
- return;
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
- if (input.archive) {
262
- await archiveIssue(client, issue.id);
263
- console.log(`archived ${identifier}`);
264
- return;
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}`);