azdo-cli 0.5.0-017-pr-comments-threads.244 → 0.5.0-017-pr-comments-threads.248

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 +9 -2
  2. package/dist/index.js +143 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ Azure DevOps CLI focused on work item read/write workflows.
13
13
  - Create or update work items from markdown documents (`upsert`)
14
14
  - Read and post work item comments (`comments`)
15
15
  - Read/write rich-text fields as markdown (`get-md-field`, `set-md-field`)
16
- - Check branch pull request status, open PRs to `develop`, and review active comments (`pr`)
16
+ - Check branch pull request status, open PRs to `develop`, list PR comment threads for any PR (`--pr-number`), and resolve/reopen threads from the CLI (`pr`)
17
17
  - Persist org/project/default fields in local config (`config`)
18
18
  - List all fields of a work item (`list-fields`)
19
19
  - Store a PAT per Azure DevOps organization in the OS credential store via `azdo auth` (or use `AZDO_PAT`). Inspect with `azdo auth status`, remove with `azdo auth logout`. See [docs/authentication.md](docs/authentication.md).
@@ -40,9 +40,16 @@ azdo set-state 12345 "Active"
40
40
  # Create a work item from markdown
41
41
  azdo upsert --type "User Story" --content $'---\nTitle: Improve markdown import UX\nState: New\n---'
42
42
 
43
- # Read and post comments
43
+ # Read and post work item comments
44
44
  azdo comments list 12345
45
45
  azdo comments add 12345 "Investigating the root cause now."
46
+
47
+ # PR comment threads — list, filter, target by number, resolve or reopen
48
+ azdo pr comments # active-branch PR
49
+ azdo pr comments --pr-number 64 # any PR by number (skips branch lookup)
50
+ azdo pr comments --pr-number 64 --hide-resolved
51
+ azdo pr comment-resolve 17 --pr-number 64 # idempotent: exit 0 even when already resolved
52
+ azdo pr comment-reopen 17 --pr-number 64
46
53
  ```
47
54
 
48
55
  ## Documentation
package/dist/index.js CHANGED
@@ -2396,6 +2396,14 @@ function mapThread(thread) {
2396
2396
  comments
2397
2397
  };
2398
2398
  }
2399
+ function toActiveCommentThread(thread) {
2400
+ return {
2401
+ id: thread.id,
2402
+ status: thread.status,
2403
+ threadContext: thread.threadContext?.filePath ?? null,
2404
+ comments: thread.comments.map(mapComment).filter((comment) => comment !== null)
2405
+ };
2406
+ }
2399
2407
  var RESOLVED_THREAD_STATUSES = /* @__PURE__ */ new Set(["fixed", "wontFix", "closed", "byDesign"]);
2400
2408
  function isThreadResolved(status) {
2401
2409
  return RESOLVED_THREAD_STATUSES.has(status);
@@ -2406,6 +2414,22 @@ async function readJsonResponse(response) {
2406
2414
  }
2407
2415
  return response.json();
2408
2416
  }
2417
+ async function patchThreadStatus(context, repo, pat, prId, threadId, status) {
2418
+ const url = new URL(
2419
+ `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/threads/${threadId}`
2420
+ );
2421
+ url.searchParams.set("api-version", "7.1");
2422
+ const response = await fetchWithErrors(url.toString(), {
2423
+ method: "PATCH",
2424
+ headers: {
2425
+ ...authHeaders(pat),
2426
+ "Content-Type": "application/json"
2427
+ },
2428
+ body: JSON.stringify({ status })
2429
+ });
2430
+ const data = await readJsonResponse(response);
2431
+ return toActiveCommentThread(data);
2432
+ }
2409
2433
  async function getPullRequestById(context, repo, pat, prId) {
2410
2434
  const url = new URL(
2411
2435
  `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}`
@@ -2739,12 +2763,131 @@ function createPrCommentsCommand() {
2739
2763
  });
2740
2764
  return command;
2741
2765
  }
2766
+ async function resolveThreadTarget(threadIdRaw, options) {
2767
+ validateOrgProjectPair(options);
2768
+ const threadId = parsePositivePrNumber(threadIdRaw);
2769
+ if (threadId === null) {
2770
+ writeError(`Invalid thread id "${threadIdRaw}"; expected a positive integer.`);
2771
+ return null;
2772
+ }
2773
+ let explicitPrId = null;
2774
+ if (options.prNumber !== void 0) {
2775
+ explicitPrId = parsePositivePrNumber(options.prNumber);
2776
+ if (explicitPrId === null) {
2777
+ writeError(`Invalid --pr-number "${options.prNumber}"; expected a positive integer.`);
2778
+ return null;
2779
+ }
2780
+ }
2781
+ const resolved = await resolvePrCommandContext(options, { requireBranch: explicitPrId === null });
2782
+ let pullRequest;
2783
+ if (explicitPrId !== null) {
2784
+ try {
2785
+ pullRequest = await getPullRequestById(resolved.context, resolved.repo, resolved.pat, explicitPrId);
2786
+ } catch (err) {
2787
+ if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
2788
+ writeError(`Pull request #${explicitPrId} not found in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`);
2789
+ return null;
2790
+ }
2791
+ throw err;
2792
+ }
2793
+ } else {
2794
+ const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
2795
+ status: "active"
2796
+ });
2797
+ if (pullRequests.length === 0) {
2798
+ writeError(`No active pull request found for branch ${resolved.branch}.`);
2799
+ return null;
2800
+ }
2801
+ if (pullRequests.length > 1) {
2802
+ const ids = pullRequests.map((pr) => `#${pr.id}`).join(", ");
2803
+ writeError(`Multiple active pull requests found for branch ${resolved.branch}: ${ids}. Use pr status to review them.`);
2804
+ return null;
2805
+ }
2806
+ pullRequest = pullRequests[0];
2807
+ }
2808
+ return { context: resolved.context, repo: resolved.repo, pat: resolved.pat, pullRequest, threadId };
2809
+ }
2810
+ async function runThreadStateChange(threadIdRaw, options, direction) {
2811
+ let context;
2812
+ try {
2813
+ const target = await resolveThreadTarget(threadIdRaw, options);
2814
+ if (target === null) {
2815
+ return;
2816
+ }
2817
+ context = target.context;
2818
+ const threads = await getPullRequestThreads(target.context, target.repo, target.pat, target.pullRequest.id);
2819
+ const thread = threads.find((t) => t.id === target.threadId);
2820
+ if (!thread) {
2821
+ writeError(`Thread #${target.threadId} not found on pull request #${target.pullRequest.id}.`);
2822
+ return;
2823
+ }
2824
+ const alreadyInTargetState = direction === "resolve" ? isThreadResolved(thread.status) : !isThreadResolved(thread.status);
2825
+ const targetStatus = direction === "resolve" ? "fixed" : "active";
2826
+ if (alreadyInTargetState) {
2827
+ const humanLabel = direction === "resolve" ? "resolved" : "active";
2828
+ const noopResult = {
2829
+ pullRequestId: target.pullRequest.id,
2830
+ threadId: target.threadId,
2831
+ status: targetStatus,
2832
+ noop: true
2833
+ };
2834
+ if (options.json) {
2835
+ process.stdout.write(`${JSON.stringify(noopResult, null, 2)}
2836
+ `);
2837
+ return;
2838
+ }
2839
+ process.stdout.write(`Thread #${target.threadId} is already ${humanLabel} on pull request #${target.pullRequest.id}.
2840
+ `);
2841
+ return;
2842
+ }
2843
+ const updated = await patchThreadStatus(
2844
+ target.context,
2845
+ target.repo,
2846
+ target.pat,
2847
+ target.pullRequest.id,
2848
+ target.threadId,
2849
+ targetStatus
2850
+ );
2851
+ const result = {
2852
+ pullRequestId: target.pullRequest.id,
2853
+ threadId: target.threadId,
2854
+ status: targetStatus,
2855
+ noop: false
2856
+ };
2857
+ if (options.json) {
2858
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
2859
+ `);
2860
+ return;
2861
+ }
2862
+ const verb = direction === "resolve" ? "resolved" : "reopened";
2863
+ process.stdout.write(`Thread #${target.threadId} ${verb} on pull request #${target.pullRequest.id} (status: ${updated.status}).
2864
+ `);
2865
+ } catch (err) {
2866
+ handlePrCommandError(err, context, "write");
2867
+ }
2868
+ }
2869
+ function createPrCommentResolveCommand() {
2870
+ const command = new Command12("comment-resolve");
2871
+ command.description("Mark a pull request comment thread as resolved").argument("<threadId>", "numeric id of the thread to resolve").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--pr-number <N>", "target the pull request with this numeric id, instead of the current branch's PR").option("--json", "output JSON").action(async (threadIdRaw, options) => {
2872
+ await runThreadStateChange(threadIdRaw, options, "resolve");
2873
+ });
2874
+ return command;
2875
+ }
2876
+ function createPrCommentReopenCommand() {
2877
+ const command = new Command12("comment-reopen");
2878
+ command.description("Reopen (set to active) a previously resolved pull request comment thread").argument("<threadId>", "numeric id of the thread to reopen").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--pr-number <N>", "target the pull request with this numeric id, instead of the current branch's PR").option("--json", "output JSON").action(async (threadIdRaw, options) => {
2879
+ await runThreadStateChange(threadIdRaw, options, "reopen");
2880
+ });
2881
+ return command;
2882
+ }
2742
2883
  function createPrCommand() {
2743
2884
  const command = new Command12("pr");
2744
2885
  command.description("Manage Azure DevOps pull requests");
2745
2886
  command.addCommand(createPrStatusCommand());
2746
2887
  command.addCommand(createPrOpenCommand());
2747
2888
  command.addCommand(createPrCommentsCommand());
2889
+ command.addCommand(createPrCommentResolveCommand());
2890
+ command.addCommand(createPrCommentReopenCommand());
2748
2891
  return command;
2749
2892
  }
2750
2893
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.5.0-017-pr-comments-threads.244",
3
+ "version": "0.5.0-017-pr-comments-threads.248",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {