azdo-cli 0.22.0-develop.769 → 0.22.0-develop.787

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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +189 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -80,13 +80,16 @@ azdo pr comments --exclude-system --max-chars 500 # human comments only, trun
80
80
  azdo pr comments --thread 148 # a single thread, by id (selector: exit 1 if absent)
81
81
  azdo pr comments --contains '"kind":"review-plan"' # threads holding a literal substring
82
82
  azdo pr status # PR checks (status + branch policies + pipeline builds) + code-comment counts
83
+ azdo pr status --branch feature/x --json # same view for another branch, nothing checked out
84
+ azdo pr status --pr-number 64 # same view for one PR by number, any status
83
85
  azdo pr comment-resolve 17 --pr-number 64 # idempotent: exit 0 even when already resolved
84
86
  azdo pr comment-reopen 17 --pr-number 64
85
87
 
86
- # Write to a PR — new thread, in-place edit, reply
88
+ # Write to a PR — new thread, in-place edit, delete, reply
87
89
  azdo pr comments add --file plan.md --pr-number 64 --dry-run # preview, writes nothing
88
90
  azdo pr comments add --file plan.md --pr-number 64 # NEW thread on the overview
89
91
  azdo pr comments edit 148 --file plan.md --pr-number 64 # rewrite it in place
92
+ azdo pr comments delete 148 --comment-id 3 --pr-number 64 # delete a comment (irreversible; no prompt)
90
93
  azdo pr comments reply 148 "Great suggestion, I'll address it." # human-readable output
91
94
  azdo pr comments reply 148 "Done." --pr-number 64 --json # JSON: { pullRequestId, threadId, commentId, content }
92
95
  azdo pr comment-reply 148 "Done." --pr-number 64 # flat alias, identical behaviour
package/dist/index.js CHANGED
@@ -4105,6 +4105,19 @@ async function updateThreadComment(context, repo, cred, prId, threadId, commentI
4105
4105
  publishedAt: data.publishedDate ?? null
4106
4106
  };
4107
4107
  }
4108
+ async function deleteThreadComment(context, repo, cred, prId, threadId, commentId) {
4109
+ const url = new URL(
4110
+ `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/threads/${threadId}/comments/${commentId}`
4111
+ );
4112
+ url.searchParams.set("api-version", "7.1");
4113
+ const response = await fetchWithErrors(url.toString(), {
4114
+ method: "DELETE",
4115
+ headers: authHeaders(cred)
4116
+ });
4117
+ if (!response.ok) {
4118
+ throw httpError(response);
4119
+ }
4120
+ }
4108
4121
  async function postThreadComment(context, repo, cred, prId, threadId, content) {
4109
4122
  const response = await fetchWithErrors(buildThreadCommentUrl(context, repo, prId, threadId).toString(), {
4110
4123
  method: "POST",
@@ -4368,7 +4381,7 @@ function parsePositivePrNumber(raw) {
4368
4381
  return null;
4369
4382
  }
4370
4383
  const n = Number.parseInt(raw, 10);
4371
- return Number.isFinite(n) && n > 0 ? n : null;
4384
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
4372
4385
  }
4373
4386
  function prNumberHelp(branchStatus = "active") {
4374
4387
  const matched = branchStatus === "abandoned" ? "abandoned" : "open";
@@ -4678,16 +4691,59 @@ async function resolvePrCommandContext(options, resolveOpts = {}) {
4678
4691
  pat: credential
4679
4692
  };
4680
4693
  }
4694
+ var STATUS_PR_NUMBER_HELP = "show only the pull request with this numeric id, instead of the current branch's pull requests; mutually exclusive with --branch";
4695
+ var STATUS_BRANCH_HELP = "show the pull requests whose source branch is this one (with or without the refs/heads/ prefix), instead of the current branch's; no checkout needed. Mutually exclusive with --pr-number";
4696
+ function parseStatusTarget(options) {
4697
+ if (options.prNumber !== void 0 && options.branch !== void 0) {
4698
+ writeError("Cannot specify both --pr-number and --branch.");
4699
+ return null;
4700
+ }
4701
+ if (options.branch !== void 0) {
4702
+ const branch = options.branch.trim().replace(/^refs\/heads\//, "");
4703
+ if (branch === "") {
4704
+ writeError("--branch must not be empty.");
4705
+ return null;
4706
+ }
4707
+ return { kind: "branch", branch };
4708
+ }
4709
+ const prId = parseTargetPrNumber(options);
4710
+ if (prId === "invalid") {
4711
+ return null;
4712
+ }
4713
+ return prId === "none" ? { kind: "current" } : { kind: "pr", prId };
4714
+ }
4715
+ async function findStatusPullRequests(resolved, target) {
4716
+ if (target.kind === "pr") {
4717
+ const pullRequest = await fetchTargetById(resolved, target.prId);
4718
+ return pullRequest === null ? null : { branch: formatBranchName(pullRequest.sourceRefName), pullRequests: [pullRequest] };
4719
+ }
4720
+ const branch = target.kind === "branch" ? target.branch : resolved.branch;
4721
+ const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
4722
+ if (target.kind === "branch" && pullRequests.length === 0) {
4723
+ writeError(
4724
+ `No pull requests found for branch ${branch} in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`
4725
+ );
4726
+ return null;
4727
+ }
4728
+ return { branch, pullRequests };
4729
+ }
4681
4730
  function createPrStatusCommand() {
4682
4731
  const command = new Command12("status");
4683
- withCommonPrOptions(command).description("Check pull requests for the current branch").option("--json", "output JSON").action(async (options) => {
4732
+ withCommonPrOptions(configureUnwrappedHelp(command)).description("Check pull requests for the current branch, another branch, or a specific pull request").option("--pr-number <id>", STATUS_PR_NUMBER_HELP).option("--branch <name>", STATUS_BRANCH_HELP).option("--json", "output JSON").action(async (options) => {
4684
4733
  validateOrgProjectPair(options);
4734
+ const target = parseStatusTarget(options);
4735
+ if (target === null) {
4736
+ return;
4737
+ }
4685
4738
  let context;
4686
4739
  try {
4687
- const resolved = await resolvePrCommandContext(options);
4740
+ const resolved = await resolvePrCommandContext(options, { requireBranch: target.kind === "current" });
4688
4741
  context = resolved.context;
4689
- const branch = resolved.branch;
4690
- const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
4742
+ const found = await findStatusPullRequests(resolved, target);
4743
+ if (found === null) {
4744
+ return;
4745
+ }
4746
+ const { branch, pullRequests } = found;
4691
4747
  let projectId = null;
4692
4748
  try {
4693
4749
  projectId = await resolveProjectId(resolved.context, resolved.pat);
@@ -5186,6 +5242,7 @@ function createPrCommentsCommand() {
5186
5242
  command.addCommand(createPrCommentsReplyCommand());
5187
5243
  command.addCommand(createPrCommentsAddCommand());
5188
5244
  command.addCommand(createPrCommentsEditCommand());
5245
+ command.addCommand(createPrCommentsDeleteCommand());
5189
5246
  return command;
5190
5247
  }
5191
5248
  function parseTargetPrNumber(options) {
@@ -5504,7 +5561,26 @@ function createPrCommentAddCommand() {
5504
5561
  'Post a new comment thread on the pull request overview (alias of "azdo pr comments add")'
5505
5562
  );
5506
5563
  }
5507
- async function fetchThreadForEdit(target) {
5564
+ function parseExplicitCommentId(options) {
5565
+ if (options.commentId === void 0) {
5566
+ return null;
5567
+ }
5568
+ const parsed = parsePositivePrNumber(options.commentId);
5569
+ if (parsed === null) {
5570
+ writeError(`Invalid --comment-id "${options.commentId}"; expected a positive integer.`);
5571
+ return "invalid";
5572
+ }
5573
+ return parsed;
5574
+ }
5575
+ function findCommentInThread(thread, commentId, target) {
5576
+ const match = thread.comments.find((comment) => comment.id === commentId);
5577
+ if (match === void 0) {
5578
+ writeError(`Comment #${commentId} not found in thread #${target.threadId} on pull request #${target.pullRequest.id}.`, EXIT_NOT_FOUND);
5579
+ return null;
5580
+ }
5581
+ return match;
5582
+ }
5583
+ async function fetchTargetThread(target) {
5508
5584
  try {
5509
5585
  return await getPullRequestThread(
5510
5586
  target.context,
@@ -5523,12 +5599,7 @@ async function fetchThreadForEdit(target) {
5523
5599
  }
5524
5600
  function selectEditableComment(thread, explicitCommentId, target) {
5525
5601
  if (explicitCommentId !== null) {
5526
- const match = thread.comments.find((comment) => comment.id === explicitCommentId);
5527
- if (match === void 0) {
5528
- writeError(`Comment #${explicitCommentId} not found in thread #${target.threadId} on pull request #${target.pullRequest.id}.`, EXIT_NOT_FOUND);
5529
- return null;
5530
- }
5531
- return match;
5602
+ return findCommentInThread(thread, explicitCommentId, target);
5532
5603
  }
5533
5604
  const first = [...thread.comments].sort((a, b) => a.id - b.id)[0];
5534
5605
  if (first === void 0) {
@@ -5563,13 +5634,9 @@ async function runCommentEdit(threadIdRaw, text, options) {
5563
5634
  if (body === null) {
5564
5635
  return;
5565
5636
  }
5566
- let explicitCommentId = null;
5567
- if (options.commentId !== void 0) {
5568
- explicitCommentId = parsePositivePrNumber(options.commentId);
5569
- if (explicitCommentId === null) {
5570
- writeError(`Invalid --comment-id "${options.commentId}"; expected a positive integer.`);
5571
- return;
5572
- }
5637
+ const explicitCommentId = parseExplicitCommentId(options);
5638
+ if (explicitCommentId === "invalid") {
5639
+ return;
5573
5640
  }
5574
5641
  const target = await resolveThreadTarget(threadIdRaw, options, {
5575
5642
  onContextResolved: (resolved) => {
@@ -5579,7 +5646,7 @@ async function runCommentEdit(threadIdRaw, text, options) {
5579
5646
  if (target === null) {
5580
5647
  return;
5581
5648
  }
5582
- const thread = await fetchThreadForEdit(target);
5649
+ const thread = await fetchTargetThread(target);
5583
5650
  if (thread === null) {
5584
5651
  return;
5585
5652
  }
@@ -5641,6 +5708,107 @@ function createPrCommentEditCommand() {
5641
5708
  'Edit an existing pull request comment in place (alias of "azdo pr comments edit")'
5642
5709
  );
5643
5710
  }
5711
+ function selectDeletableComment(thread, explicitCommentId, target) {
5712
+ if (explicitCommentId !== null) {
5713
+ return findCommentInThread(thread, explicitCommentId, target);
5714
+ }
5715
+ const comments = [...thread.comments].sort((a, b) => a.id - b.id);
5716
+ if (comments.length === 1) {
5717
+ return comments[0];
5718
+ }
5719
+ if (comments.length === 0) {
5720
+ writeError(`Thread #${target.threadId} on pull request #${target.pullRequest.id} has no comment to delete.`, EXIT_NOT_FOUND);
5721
+ return null;
5722
+ }
5723
+ const candidates = comments.map((comment) => `#${comment.id} (${comment.author ?? "Unknown"})`).join(", ");
5724
+ writeError(
5725
+ `Thread #${target.threadId} on pull request #${target.pullRequest.id} holds ${comments.length} comments; pass --comment-id to choose one: ${candidates}.`
5726
+ );
5727
+ return null;
5728
+ }
5729
+ function reportDeleteResult(result, json, comment) {
5730
+ if (json) {
5731
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
5732
+ `);
5733
+ return;
5734
+ }
5735
+ if (result.dryRun) {
5736
+ process.stdout.write(
5737
+ `Dry run: would delete comment #${result.commentId} by ${comment.author ?? "Unknown"} (${comment.content.length} chars) from thread #${result.threadId} on pull request #${result.pullRequestId}.
5738
+ `
5739
+ );
5740
+ return;
5741
+ }
5742
+ process.stdout.write(
5743
+ `Comment #${result.commentId} deleted from thread #${result.threadId} on pull request #${result.pullRequestId}.
5744
+ `
5745
+ );
5746
+ }
5747
+ async function runCommentDelete(threadIdRaw, options) {
5748
+ let context;
5749
+ try {
5750
+ const explicitCommentId = parseExplicitCommentId(options);
5751
+ if (explicitCommentId === "invalid") {
5752
+ return;
5753
+ }
5754
+ const target = await resolveThreadTarget(threadIdRaw, options, {
5755
+ onContextResolved: (resolved) => {
5756
+ context = resolved;
5757
+ }
5758
+ });
5759
+ if (target === null) {
5760
+ return;
5761
+ }
5762
+ const thread = await fetchTargetThread(target);
5763
+ if (thread === null) {
5764
+ return;
5765
+ }
5766
+ const comment = selectDeletableComment(thread, explicitCommentId, target);
5767
+ if (comment === null) {
5768
+ return;
5769
+ }
5770
+ const dryRun = options.dryRun === true;
5771
+ if (!dryRun) {
5772
+ await deleteThreadComment(
5773
+ target.context,
5774
+ target.repo,
5775
+ target.pat,
5776
+ target.pullRequest.id,
5777
+ target.threadId,
5778
+ comment.id
5779
+ );
5780
+ }
5781
+ reportDeleteResult(
5782
+ {
5783
+ pullRequestId: target.pullRequest.id,
5784
+ threadId: target.threadId,
5785
+ commentId: comment.id,
5786
+ deleted: !dryRun,
5787
+ dryRun
5788
+ },
5789
+ options.json === true,
5790
+ comment
5791
+ );
5792
+ } catch (err) {
5793
+ handlePrCommandError(err, context, "write");
5794
+ }
5795
+ }
5796
+ function buildCommentDeleteCommand(name, description) {
5797
+ const command = new Command12(name);
5798
+ withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("<threadId>", "numeric id of the thread holding the comment").option("--comment-id <N>", "numeric id of the comment to delete; may be omitted only when the thread holds a single comment").option("--dry-run", "resolve the target comment and print what would be deleted, without deleting anything").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, _options, command2) => {
5799
+ await runCommentDelete(threadIdRaw, mergedPrOptions(command2));
5800
+ });
5801
+ return command;
5802
+ }
5803
+ function createPrCommentsDeleteCommand() {
5804
+ return buildCommentDeleteCommand("delete", "Delete a pull request comment (no confirmation prompt; irreversible)");
5805
+ }
5806
+ function createPrCommentDeleteCommand() {
5807
+ return buildCommentDeleteCommand(
5808
+ "comment-delete",
5809
+ 'Delete a pull request comment (alias of "azdo pr comments delete"; no confirmation prompt; irreversible)'
5810
+ );
5811
+ }
5644
5812
  var LIST_STATUS_VALUES = ["active", "completed", "abandoned", "all"];
5645
5813
  var DEFAULT_LIST_TOP = 25;
5646
5814
  function formatPullRequestListEntry(pullRequest) {
@@ -5906,6 +6074,7 @@ function createPrCommand() {
5906
6074
  command.addCommand(createPrWorkItemsCommand());
5907
6075
  command.addCommand(createPrReviewersCommand());
5908
6076
  command.addCommand(createPrCommentEditCommand());
6077
+ command.addCommand(createPrCommentDeleteCommand());
5909
6078
  return command;
5910
6079
  }
5911
6080
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.22.0-develop.769",
3
+ "version": "0.22.0-develop.787",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {