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.
- package/README.md +3 -1
- package/dist/index.js +159 -12
- 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(
|
|
2946
|
-
|
|
2947
|
-
|
|
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 =
|
|
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 (
|
|
3081
|
-
|
|
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
|
`);
|