azdo-cli 0.10.0-develop.386 → 0.10.0-develop.394

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 +3 -1
  2. package/dist/index.js +159 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,7 +56,9 @@ azdo comments add 12345 "Investigating the root cause now."
56
56
  # PR comment threads — list, filter, target by number, resolve or reopen
57
57
  azdo pr comments # active-branch PR
58
58
  azdo pr comments --pr-number 64 # any PR by number (skips branch lookup)
59
- azdo pr comments --pr-number 64 --hide-resolved
59
+ azdo pr comments --pr-number 64 --hide-resolved # or --exclude-resolved (alias)
60
+ azdo pr comments --code-related-only # only file/line-anchored threads
61
+ azdo pr status # PR checks (status + branch policies) + code-comment counts
60
62
  azdo pr comment-resolve 17 --pr-number 64 # idempotent: exit 0 even when already resolved
61
63
  azdo pr comment-reopen 17 --pr-number 64
62
64
  ```
package/dist/index.js CHANGED
@@ -2652,6 +2652,21 @@ function buildPullRequestStatusesUrl(context, repo, prId) {
2652
2652
  url.searchParams.set("api-version", "7.1");
2653
2653
  return url;
2654
2654
  }
2655
+ function buildProjectUrl(context) {
2656
+ const url = new URL(
2657
+ `https://dev.azure.com/${encodeURIComponent(context.org)}/_apis/projects/${encodeURIComponent(context.project)}`
2658
+ );
2659
+ url.searchParams.set("api-version", "7.1");
2660
+ return url;
2661
+ }
2662
+ function buildPolicyEvaluationsUrl(context, projectId, prId) {
2663
+ const url = new URL(
2664
+ `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/policy/evaluations`
2665
+ );
2666
+ url.searchParams.set("api-version", "7.1");
2667
+ url.searchParams.set("artifactId", `vstfs:///CodeReview/CodeReviewId/${projectId}/${prId}`);
2668
+ return url;
2669
+ }
2655
2670
  function mapPullRequest(repo, pullRequest) {
2656
2671
  return {
2657
2672
  id: pullRequest.pullRequestId,
@@ -2690,7 +2705,49 @@ function mapPullRequestCheck(status2) {
2690
2705
  targetUrl: status2.targetUrl ?? null,
2691
2706
  createdBy: status2.createdBy?.displayName ?? null,
2692
2707
  createdAt: status2.creationDate ?? null,
2693
- updatedAt: status2.updatedDate ?? null
2708
+ updatedAt: status2.updatedDate ?? null,
2709
+ source: "status"
2710
+ };
2711
+ }
2712
+ function mapPolicyEvaluationState(status2) {
2713
+ switch (status2) {
2714
+ case "approved":
2715
+ return "succeeded";
2716
+ case "rejected":
2717
+ return "failed";
2718
+ case "running":
2719
+ case "queued":
2720
+ return "pending";
2721
+ case "notApplicable":
2722
+ case "notSet":
2723
+ case void 0:
2724
+ return null;
2725
+ default:
2726
+ return status2;
2727
+ }
2728
+ }
2729
+ function mapPolicyEvaluationName(evaluation) {
2730
+ const display = evaluation.configuration?.settings?.displayName?.trim() || evaluation.configuration?.type?.displayName?.trim();
2731
+ if (display) {
2732
+ return display;
2733
+ }
2734
+ return `Policy ${evaluation.configuration?.id ?? evaluation.evaluationId ?? "?"}`;
2735
+ }
2736
+ function mapPolicyEvaluationCheck(evaluation) {
2737
+ const state = mapPolicyEvaluationState(evaluation.status);
2738
+ if (state === null) {
2739
+ return null;
2740
+ }
2741
+ return {
2742
+ id: evaluation.configuration?.id ?? 0,
2743
+ state,
2744
+ name: mapPolicyEvaluationName(evaluation),
2745
+ description: null,
2746
+ targetUrl: null,
2747
+ createdBy: null,
2748
+ createdAt: null,
2749
+ updatedAt: null,
2750
+ source: "policy"
2694
2751
  };
2695
2752
  }
2696
2753
  function mapComment(comment) {
@@ -2776,6 +2833,21 @@ async function getPullRequestChecks(context, repo, cred, prId) {
2776
2833
  const data = await readJsonResponse(response);
2777
2834
  return data.value.map(mapPullRequestCheck).filter((check) => check !== null);
2778
2835
  }
2836
+ async function resolveProjectId(context, cred) {
2837
+ const response = await fetchWithErrors(buildProjectUrl(context).toString(), {
2838
+ headers: authHeaders(cred)
2839
+ });
2840
+ const data = await readJsonResponse(response);
2841
+ return data.id;
2842
+ }
2843
+ async function getPullRequestPolicyEvaluations(context, cred, projectId, prId) {
2844
+ const response = await fetchWithErrors(
2845
+ buildPolicyEvaluationsUrl(context, projectId, prId).toString(),
2846
+ { headers: authHeaders(cred) }
2847
+ );
2848
+ const data = await readJsonResponse(response);
2849
+ return data.value.map(mapPolicyEvaluationCheck).filter((check) => check !== null);
2850
+ }
2779
2851
  async function openPullRequest(context, repo, cred, sourceBranch, title, description) {
2780
2852
  const existing = await listPullRequests(context, repo, cred, sourceBranch, {
2781
2853
  status: "active",
@@ -2884,7 +2956,10 @@ function handlePrCommandError(err, context, mode = "read") {
2884
2956
  }
2885
2957
  writeError(error.message);
2886
2958
  }
2887
- function formatPullRequestChecks(checks) {
2959
+ function formatPullRequestChecks(checks, checksError) {
2960
+ if (checksError) {
2961
+ return [`Checks: unable to retrieve (${checksError})`];
2962
+ }
2888
2963
  if (checks.length === 0) {
2889
2964
  return ["Checks: none reported by Azure DevOps"];
2890
2965
  }
@@ -2897,14 +2972,68 @@ function formatPullRequestChecks(checks) {
2897
2972
  }
2898
2973
  return lines;
2899
2974
  }
2975
+ function countCodeComments(threads) {
2976
+ let open = 0;
2977
+ let closed = 0;
2978
+ for (const thread of threads) {
2979
+ if (thread.threadContext === null) {
2980
+ continue;
2981
+ }
2982
+ if (isThreadResolved(thread.status)) {
2983
+ closed += 1;
2984
+ } else {
2985
+ open += 1;
2986
+ }
2987
+ }
2988
+ return { open, closed };
2989
+ }
2990
+ function formatCodeCommentCounts(counts) {
2991
+ return `Code comments: ${counts.open} open, ${counts.closed} closed`;
2992
+ }
2900
2993
  function formatPullRequestBlock(pullRequest) {
2901
2994
  return [
2902
2995
  `#${pullRequest.id} [${pullRequest.status}] ${pullRequest.title}`,
2903
2996
  `${formatBranchName(pullRequest.sourceRefName)} -> ${formatBranchName(pullRequest.targetRefName)}`,
2904
2997
  pullRequest.url ?? "\u2014",
2905
- ...formatPullRequestChecks(pullRequest.checks)
2998
+ ...formatPullRequestChecks(pullRequest.checks, pullRequest.checksError),
2999
+ formatCodeCommentCounts(pullRequest.codeCommentCounts)
2906
3000
  ].join("\n");
2907
3001
  }
3002
+ async function buildPullRequestStatusEntry(context, repo, cred, pullRequest, projectId) {
3003
+ let statusChecks = [];
3004
+ let statusOk = true;
3005
+ try {
3006
+ statusChecks = await getPullRequestChecks(context, repo, cred, pullRequest.id);
3007
+ } catch {
3008
+ statusOk = false;
3009
+ }
3010
+ let policyChecks = [];
3011
+ let policyOk = true;
3012
+ if (projectId === null) {
3013
+ policyOk = false;
3014
+ } else {
3015
+ try {
3016
+ policyChecks = await getPullRequestPolicyEvaluations(context, cred, projectId, pullRequest.id);
3017
+ } catch {
3018
+ policyOk = false;
3019
+ }
3020
+ }
3021
+ let codeCommentCounts;
3022
+ try {
3023
+ const threads = await getPullRequestThreads(context, repo, cred, pullRequest.id);
3024
+ codeCommentCounts = countCodeComments(threads);
3025
+ } catch {
3026
+ codeCommentCounts = { open: 0, closed: 0 };
3027
+ }
3028
+ const checks = [...statusChecks, ...policyChecks];
3029
+ const checksError = checks.length === 0 && (!statusOk || !policyOk) ? "Azure DevOps request failed" : null;
3030
+ return {
3031
+ ...pullRequest,
3032
+ checks,
3033
+ codeCommentCounts,
3034
+ checksError
3035
+ };
3036
+ }
2908
3037
  function threadStatusLabel(status2) {
2909
3038
  return isThreadResolved(status2) ? "resolved" : status2;
2910
3039
  }
@@ -2941,11 +3070,16 @@ function createPrStatusCommand() {
2941
3070
  context = resolved.context;
2942
3071
  const branch = resolved.branch;
2943
3072
  const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
3073
+ let projectId = null;
3074
+ try {
3075
+ projectId = await resolveProjectId(resolved.context, resolved.pat);
3076
+ } catch {
3077
+ projectId = null;
3078
+ }
2944
3079
  const pullRequestsWithChecks = await Promise.all(
2945
- pullRequests.map(async (pullRequest) => ({
2946
- ...pullRequest,
2947
- checks: await getPullRequestChecks(resolved.context, resolved.repo, resolved.pat, pullRequest.id)
2948
- }))
3080
+ pullRequests.map(
3081
+ async (pullRequest) => buildPullRequestStatusEntry(resolved.context, resolved.repo, resolved.pat, pullRequest, projectId)
3082
+ )
2949
3083
  );
2950
3084
  const result = { branch, repository: resolved.repo, pullRequests: pullRequestsWithChecks };
2951
3085
  if (options.json) {
@@ -3026,7 +3160,7 @@ ${result.pullRequest.url ?? "\u2014"}
3026
3160
  }
3027
3161
  function createPrCommentsCommand() {
3028
3162
  const command = new Command12("comments");
3029
- configureUnwrappedHelp(command).description("List pull request comment threads for the current branch").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--pr-number <N>", PR_NUMBER_HELP).option("--hide-resolved", "hide threads whose status is resolved / won't fix / closed / by design").option("--json", "output JSON").action(async (options) => {
3163
+ configureUnwrappedHelp(command).description("List pull request comment threads for the current branch").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--pr-number <N>", PR_NUMBER_HELP).option("--hide-resolved", "hide threads whose status is resolved / won't fix / closed / by design").option("--exclude-resolved", "alias of --hide-resolved: exclude resolved / won't fix / closed / by design threads").option("--code-related-only", "show only threads anchored to a file/line; omit general discussion threads").option("--json", "output JSON").action(async (options) => {
3030
3164
  validateOrgProjectPair(options);
3031
3165
  let context;
3032
3166
  let explicitPrId = null;
@@ -3068,8 +3202,12 @@ function createPrCommentsCommand() {
3068
3202
  pullRequest = pullRequests[0];
3069
3203
  branchLabel = resolved.branch;
3070
3204
  }
3205
+ const hideResolved = options.hideResolved === true || options.excludeResolved === true;
3206
+ const codeRelatedOnly = options.codeRelatedOnly === true;
3071
3207
  const allThreads = await getPullRequestThreads(resolved.context, resolved.repo, resolved.pat, pullRequest.id);
3072
- const threads = options.hideResolved ? allThreads.filter((thread) => !isThreadResolved(thread.status)) : allThreads;
3208
+ const threads = allThreads.filter(
3209
+ (thread) => (!hideResolved || !isThreadResolved(thread.status)) && (!codeRelatedOnly || thread.threadContext !== null)
3210
+ );
3073
3211
  const result = { branch: branchLabel, pullRequest, threads };
3074
3212
  if (options.json) {
3075
3213
  process.stdout.write(`${JSON.stringify(result, null, 2)}
@@ -3077,9 +3215,18 @@ function createPrCommentsCommand() {
3077
3215
  return;
3078
3216
  }
3079
3217
  if (threads.length === 0) {
3080
- if (options.hideResolved && allThreads.length > 0) {
3081
- process.stdout.write(`Pull request #${pullRequest.id} has no unresolved comment threads (${allThreads.length} resolved thread${allThreads.length === 1 ? "" : "s"} hidden by --hide-resolved).
3082
- `);
3218
+ if (allThreads.length > 0 && (hideResolved || codeRelatedOnly)) {
3219
+ const filters = [];
3220
+ if (codeRelatedOnly) {
3221
+ filters.push("code-related");
3222
+ }
3223
+ if (hideResolved) {
3224
+ filters.push("unresolved");
3225
+ }
3226
+ process.stdout.write(
3227
+ `Pull request #${pullRequest.id} has no ${filters.join(" ")} comment threads (filtered from ${allThreads.length} thread${allThreads.length === 1 ? "" : "s"}).
3228
+ `
3229
+ );
3083
3230
  } else {
3084
3231
  process.stdout.write(`Pull request #${pullRequest.id} has no comment threads.
3085
3232
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.10.0-develop.386",
3
+ "version": "0.10.0-develop.394",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {