azdo-cli 0.10.0-develop.233 → 0.10.0-develop.260
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 +9 -2
- package/dist/index.js +237 -31
- 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
|
|
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
|
@@ -2340,7 +2340,7 @@ function mapPullRequest(repo, pullRequest) {
|
|
|
2340
2340
|
targetRefName: pullRequest.targetRefName,
|
|
2341
2341
|
status: pullRequest.status,
|
|
2342
2342
|
createdBy: pullRequest.createdBy?.displayName ?? null,
|
|
2343
|
-
url: pullRequest._links
|
|
2343
|
+
url: pullRequest._links?.web?.href ?? null
|
|
2344
2344
|
};
|
|
2345
2345
|
}
|
|
2346
2346
|
function mapPullRequestCheckName(status) {
|
|
@@ -2385,9 +2385,6 @@ function mapComment(comment) {
|
|
|
2385
2385
|
};
|
|
2386
2386
|
}
|
|
2387
2387
|
function mapThread(thread) {
|
|
2388
|
-
if (thread.status !== "active" && thread.status !== "pending") {
|
|
2389
|
-
return null;
|
|
2390
|
-
}
|
|
2391
2388
|
const comments = thread.comments.map(mapComment).filter((comment) => comment !== null);
|
|
2392
2389
|
if (comments.length === 0) {
|
|
2393
2390
|
return null;
|
|
@@ -2399,12 +2396,49 @@ function mapThread(thread) {
|
|
|
2399
2396
|
comments
|
|
2400
2397
|
};
|
|
2401
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
|
+
}
|
|
2407
|
+
var RESOLVED_THREAD_STATUSES = /* @__PURE__ */ new Set(["fixed", "wontFix", "closed", "byDesign"]);
|
|
2408
|
+
function isThreadResolved(status) {
|
|
2409
|
+
return RESOLVED_THREAD_STATUSES.has(status);
|
|
2410
|
+
}
|
|
2402
2411
|
async function readJsonResponse(response) {
|
|
2403
2412
|
if (!response.ok) {
|
|
2404
2413
|
throw new Error(`HTTP_${response.status}`);
|
|
2405
2414
|
}
|
|
2406
2415
|
return response.json();
|
|
2407
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
|
+
}
|
|
2433
|
+
async function getPullRequestById(context, repo, pat, prId) {
|
|
2434
|
+
const url = new URL(
|
|
2435
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}`
|
|
2436
|
+
);
|
|
2437
|
+
url.searchParams.set("api-version", "7.1");
|
|
2438
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(pat) });
|
|
2439
|
+
const data = await readJsonResponse(response);
|
|
2440
|
+
return mapPullRequest(repo, data);
|
|
2441
|
+
}
|
|
2408
2442
|
async function listPullRequests(context, repo, pat, sourceBranch, opts) {
|
|
2409
2443
|
const response = await fetchWithErrors(
|
|
2410
2444
|
buildPullRequestsUrl(context, repo, sourceBranch, opts).toString(),
|
|
@@ -2474,31 +2508,43 @@ async function getPullRequestThreads(context, repo, pat, prId) {
|
|
|
2474
2508
|
}
|
|
2475
2509
|
|
|
2476
2510
|
// src/commands/pr.ts
|
|
2511
|
+
function parsePositivePrNumber(raw) {
|
|
2512
|
+
if (!/^\d+$/.test(raw)) {
|
|
2513
|
+
return null;
|
|
2514
|
+
}
|
|
2515
|
+
const n = Number.parseInt(raw, 10);
|
|
2516
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
2517
|
+
}
|
|
2477
2518
|
function formatBranchName(refName) {
|
|
2478
2519
|
return refName.startsWith("refs/heads/") ? refName.slice("refs/heads/".length) : refName;
|
|
2479
2520
|
}
|
|
2480
2521
|
function writeError(message) {
|
|
2481
2522
|
process.stderr.write(`Error: ${message}
|
|
2482
2523
|
`);
|
|
2483
|
-
process.
|
|
2524
|
+
process.exitCode = 1;
|
|
2484
2525
|
}
|
|
2485
2526
|
function handlePrCommandError(err, context, mode = "read") {
|
|
2486
2527
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2487
2528
|
if (error.message === "AUTH_FAILED") {
|
|
2488
2529
|
const scopeLabel = mode === "write" ? "Code (Read & Write)" : "Code (Read)";
|
|
2489
2530
|
writeError(`Authentication failed. Check that your PAT is valid and has the "${scopeLabel}" scope.`);
|
|
2531
|
+
return;
|
|
2490
2532
|
}
|
|
2491
2533
|
if (error.message === "PERMISSION_DENIED") {
|
|
2492
2534
|
writeError(`Access denied. Your PAT may lack ${mode} permissions for project "${context?.project}".`);
|
|
2535
|
+
return;
|
|
2493
2536
|
}
|
|
2494
2537
|
if (error.message === "NETWORK_ERROR") {
|
|
2495
2538
|
writeError("Could not connect to Azure DevOps. Check your network connection.");
|
|
2539
|
+
return;
|
|
2496
2540
|
}
|
|
2497
2541
|
if (error.message.startsWith("NOT_FOUND")) {
|
|
2498
2542
|
writeError(`Azure DevOps repository not found in ${context?.org}/${context?.project}.`);
|
|
2543
|
+
return;
|
|
2499
2544
|
}
|
|
2500
2545
|
if (error.message.startsWith("HTTP_")) {
|
|
2501
2546
|
writeError(`Azure DevOps request failed with ${error.message}.`);
|
|
2547
|
+
return;
|
|
2502
2548
|
}
|
|
2503
2549
|
writeError(error.message);
|
|
2504
2550
|
}
|
|
@@ -2519,24 +2565,28 @@ function formatPullRequestBlock(pullRequest) {
|
|
|
2519
2565
|
return [
|
|
2520
2566
|
`#${pullRequest.id} [${pullRequest.status}] ${pullRequest.title}`,
|
|
2521
2567
|
`${formatBranchName(pullRequest.sourceRefName)} -> ${formatBranchName(pullRequest.targetRefName)}`,
|
|
2522
|
-
pullRequest.url,
|
|
2568
|
+
pullRequest.url ?? "\u2014",
|
|
2523
2569
|
...formatPullRequestChecks(pullRequest.checks)
|
|
2524
2570
|
].join("\n");
|
|
2525
2571
|
}
|
|
2572
|
+
function threadStatusLabel(status) {
|
|
2573
|
+
return isThreadResolved(status) ? "resolved" : status;
|
|
2574
|
+
}
|
|
2526
2575
|
function formatThreads(prId, title, threads) {
|
|
2527
|
-
const lines = [`
|
|
2576
|
+
const lines = [`Comment threads for pull request #${prId}: ${title}`];
|
|
2528
2577
|
for (const thread of threads) {
|
|
2529
|
-
lines.push("", `Thread #${thread.id} [${thread.status}] ${thread.threadContext ?? "(general)"}`);
|
|
2578
|
+
lines.push("", `Thread #${thread.id} [${threadStatusLabel(thread.status)}] ${thread.threadContext ?? "(general)"}`);
|
|
2530
2579
|
for (const comment of thread.comments) {
|
|
2531
2580
|
lines.push(` ${comment.author ?? "Unknown"}: ${comment.content}`);
|
|
2532
2581
|
}
|
|
2533
2582
|
}
|
|
2534
2583
|
return lines.join("\n");
|
|
2535
2584
|
}
|
|
2536
|
-
async function resolvePrCommandContext(options) {
|
|
2585
|
+
async function resolvePrCommandContext(options, resolveOpts = {}) {
|
|
2586
|
+
const requireBranch = resolveOpts.requireBranch ?? true;
|
|
2537
2587
|
const context = resolveContext(options);
|
|
2538
2588
|
const repo = detectRepoName();
|
|
2539
|
-
const branch = getCurrentBranch();
|
|
2589
|
+
const branch = requireBranch ? getCurrentBranch() : null;
|
|
2540
2590
|
const credential = await requirePat(context.org);
|
|
2541
2591
|
return {
|
|
2542
2592
|
context,
|
|
@@ -2553,15 +2603,15 @@ function createPrStatusCommand() {
|
|
|
2553
2603
|
try {
|
|
2554
2604
|
const resolved = await resolvePrCommandContext(options);
|
|
2555
2605
|
context = resolved.context;
|
|
2556
|
-
const
|
|
2606
|
+
const branch = resolved.branch;
|
|
2607
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
|
|
2557
2608
|
const pullRequestsWithChecks = await Promise.all(
|
|
2558
2609
|
pullRequests.map(async (pullRequest) => ({
|
|
2559
2610
|
...pullRequest,
|
|
2560
2611
|
checks: await getPullRequestChecks(resolved.context, resolved.repo, resolved.pat, pullRequest.id)
|
|
2561
2612
|
}))
|
|
2562
2613
|
);
|
|
2563
|
-
const { branch, repo
|
|
2564
|
-
const result = { branch, repository: repo, pullRequests: pullRequestsWithChecks };
|
|
2614
|
+
const result = { branch, repository: resolved.repo, pullRequests: pullRequestsWithChecks };
|
|
2565
2615
|
if (options.json) {
|
|
2566
2616
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
2567
2617
|
`);
|
|
@@ -2587,10 +2637,12 @@ function createPrOpenCommand() {
|
|
|
2587
2637
|
const title = options.title?.trim();
|
|
2588
2638
|
if (!title) {
|
|
2589
2639
|
writeError("--title is required for pull request creation.");
|
|
2640
|
+
return;
|
|
2590
2641
|
}
|
|
2591
2642
|
const description = options.description?.trim();
|
|
2592
2643
|
if (!description) {
|
|
2593
2644
|
writeError("--description is required for pull request creation.");
|
|
2645
|
+
return;
|
|
2594
2646
|
}
|
|
2595
2647
|
let context;
|
|
2596
2648
|
try {
|
|
@@ -2598,12 +2650,14 @@ function createPrOpenCommand() {
|
|
|
2598
2650
|
context = resolved.context;
|
|
2599
2651
|
if (resolved.branch === "develop") {
|
|
2600
2652
|
writeError("Pull request creation requires a source branch other than develop.");
|
|
2653
|
+
return;
|
|
2601
2654
|
}
|
|
2655
|
+
const openBranch = resolved.branch;
|
|
2602
2656
|
const result = await openPullRequest(
|
|
2603
2657
|
resolved.context,
|
|
2604
2658
|
resolved.repo,
|
|
2605
2659
|
resolved.pat,
|
|
2606
|
-
|
|
2660
|
+
openBranch,
|
|
2607
2661
|
title,
|
|
2608
2662
|
description
|
|
2609
2663
|
);
|
|
@@ -2614,19 +2668,20 @@ function createPrOpenCommand() {
|
|
|
2614
2668
|
}
|
|
2615
2669
|
if (result.created) {
|
|
2616
2670
|
process.stdout.write(`Created pull request #${result.pullRequest.id}: ${result.pullRequest.title}
|
|
2617
|
-
${result.pullRequest.url}
|
|
2671
|
+
${result.pullRequest.url ?? "\u2014"}
|
|
2618
2672
|
`);
|
|
2619
2673
|
return;
|
|
2620
2674
|
}
|
|
2621
2675
|
process.stdout.write(
|
|
2622
2676
|
`Active pull request already exists for ${resolved.branch} -> develop: #${result.pullRequest.id}
|
|
2623
|
-
${result.pullRequest.url}
|
|
2677
|
+
${result.pullRequest.url ?? "\u2014"}
|
|
2624
2678
|
`
|
|
2625
2679
|
);
|
|
2626
2680
|
} catch (err) {
|
|
2627
2681
|
if (err instanceof Error && err.message.startsWith("AMBIGUOUS_PRS:")) {
|
|
2628
2682
|
const ids = err.message.replace("AMBIGUOUS_PRS:", "").split(",").map((id) => `#${id}`).join(", ");
|
|
2629
2683
|
writeError(`Multiple active pull requests already exist for this branch targeting develop: ${ids}. Use pr status to review them.`);
|
|
2684
|
+
return;
|
|
2630
2685
|
}
|
|
2631
2686
|
handlePrCommandError(err, context, "write");
|
|
2632
2687
|
}
|
|
@@ -2635,33 +2690,65 @@ ${result.pullRequest.url}
|
|
|
2635
2690
|
}
|
|
2636
2691
|
function createPrCommentsCommand() {
|
|
2637
2692
|
const command = new Command12("comments");
|
|
2638
|
-
command.description("List
|
|
2693
|
+
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>", "target the pull request with this numeric id, instead of the current branch's PR").option("--hide-resolved", "hide threads whose status is resolved / won't fix / closed / by design").option("--json", "output JSON").action(async (options) => {
|
|
2639
2694
|
validateOrgProjectPair(options);
|
|
2640
2695
|
let context;
|
|
2696
|
+
let explicitPrId = null;
|
|
2697
|
+
if (options.prNumber !== void 0) {
|
|
2698
|
+
explicitPrId = parsePositivePrNumber(options.prNumber);
|
|
2699
|
+
if (explicitPrId === null) {
|
|
2700
|
+
writeError(`Invalid --pr-number "${options.prNumber}"; expected a positive integer.`);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2641
2704
|
try {
|
|
2642
|
-
const resolved = await resolvePrCommandContext(options);
|
|
2705
|
+
const resolved = await resolvePrCommandContext(options, { requireBranch: explicitPrId === null });
|
|
2643
2706
|
context = resolved.context;
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2707
|
+
let pullRequest;
|
|
2708
|
+
let branchLabel;
|
|
2709
|
+
if (explicitPrId !== null) {
|
|
2710
|
+
try {
|
|
2711
|
+
pullRequest = await getPullRequestById(resolved.context, resolved.repo, resolved.pat, explicitPrId);
|
|
2712
|
+
} catch (err) {
|
|
2713
|
+
if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
|
|
2714
|
+
writeError(`Pull request #${explicitPrId} not found in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
throw err;
|
|
2718
|
+
}
|
|
2719
|
+
branchLabel = resolved.branch ?? pullRequest.sourceRefName;
|
|
2720
|
+
} else {
|
|
2721
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
|
|
2722
|
+
status: "active"
|
|
2723
|
+
});
|
|
2724
|
+
if (pullRequests.length === 0) {
|
|
2725
|
+
writeError(`No active pull request found for branch ${resolved.branch}.`);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
if (pullRequests.length > 1) {
|
|
2729
|
+
const ids = pullRequests.map((pr) => `#${pr.id}`).join(", ");
|
|
2730
|
+
writeError(`Multiple active pull requests found for branch ${resolved.branch}: ${ids}. Use pr status to review them.`);
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
pullRequest = pullRequests[0];
|
|
2734
|
+
branchLabel = resolved.branch;
|
|
2653
2735
|
}
|
|
2654
|
-
const
|
|
2655
|
-
const threads =
|
|
2656
|
-
const result = { branch:
|
|
2736
|
+
const allThreads = await getPullRequestThreads(resolved.context, resolved.repo, resolved.pat, pullRequest.id);
|
|
2737
|
+
const threads = options.hideResolved ? allThreads.filter((thread) => !isThreadResolved(thread.status)) : allThreads;
|
|
2738
|
+
const result = { branch: branchLabel, pullRequest, threads };
|
|
2657
2739
|
if (options.json) {
|
|
2658
2740
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
2659
2741
|
`);
|
|
2660
2742
|
return;
|
|
2661
2743
|
}
|
|
2662
2744
|
if (threads.length === 0) {
|
|
2663
|
-
|
|
2745
|
+
if (options.hideResolved && allThreads.length > 0) {
|
|
2746
|
+
process.stdout.write(`Pull request #${pullRequest.id} has no unresolved comment threads (${allThreads.length} resolved thread${allThreads.length === 1 ? "" : "s"} hidden by --hide-resolved).
|
|
2747
|
+
`);
|
|
2748
|
+
} else {
|
|
2749
|
+
process.stdout.write(`Pull request #${pullRequest.id} has no comment threads.
|
|
2664
2750
|
`);
|
|
2751
|
+
}
|
|
2665
2752
|
return;
|
|
2666
2753
|
}
|
|
2667
2754
|
process.stdout.write(`${formatThreads(pullRequest.id, pullRequest.title, threads)}
|
|
@@ -2672,12 +2759,131 @@ function createPrCommentsCommand() {
|
|
|
2672
2759
|
});
|
|
2673
2760
|
return command;
|
|
2674
2761
|
}
|
|
2762
|
+
async function resolveThreadTarget(threadIdRaw, options) {
|
|
2763
|
+
validateOrgProjectPair(options);
|
|
2764
|
+
const threadId = parsePositivePrNumber(threadIdRaw);
|
|
2765
|
+
if (threadId === null) {
|
|
2766
|
+
writeError(`Invalid thread id "${threadIdRaw}"; expected a positive integer.`);
|
|
2767
|
+
return null;
|
|
2768
|
+
}
|
|
2769
|
+
let explicitPrId = null;
|
|
2770
|
+
if (options.prNumber !== void 0) {
|
|
2771
|
+
explicitPrId = parsePositivePrNumber(options.prNumber);
|
|
2772
|
+
if (explicitPrId === null) {
|
|
2773
|
+
writeError(`Invalid --pr-number "${options.prNumber}"; expected a positive integer.`);
|
|
2774
|
+
return null;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
const resolved = await resolvePrCommandContext(options, { requireBranch: explicitPrId === null });
|
|
2778
|
+
let pullRequest;
|
|
2779
|
+
if (explicitPrId !== null) {
|
|
2780
|
+
try {
|
|
2781
|
+
pullRequest = await getPullRequestById(resolved.context, resolved.repo, resolved.pat, explicitPrId);
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
|
|
2784
|
+
writeError(`Pull request #${explicitPrId} not found in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`);
|
|
2785
|
+
return null;
|
|
2786
|
+
}
|
|
2787
|
+
throw err;
|
|
2788
|
+
}
|
|
2789
|
+
} else {
|
|
2790
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
|
|
2791
|
+
status: "active"
|
|
2792
|
+
});
|
|
2793
|
+
if (pullRequests.length === 0) {
|
|
2794
|
+
writeError(`No active pull request found for branch ${resolved.branch}.`);
|
|
2795
|
+
return null;
|
|
2796
|
+
}
|
|
2797
|
+
if (pullRequests.length > 1) {
|
|
2798
|
+
const ids = pullRequests.map((pr) => `#${pr.id}`).join(", ");
|
|
2799
|
+
writeError(`Multiple active pull requests found for branch ${resolved.branch}: ${ids}. Use pr status to review them.`);
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
pullRequest = pullRequests[0];
|
|
2803
|
+
}
|
|
2804
|
+
return { context: resolved.context, repo: resolved.repo, pat: resolved.pat, pullRequest, threadId };
|
|
2805
|
+
}
|
|
2806
|
+
async function runThreadStateChange(threadIdRaw, options, direction) {
|
|
2807
|
+
let context;
|
|
2808
|
+
try {
|
|
2809
|
+
const target = await resolveThreadTarget(threadIdRaw, options);
|
|
2810
|
+
if (target === null) {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
context = target.context;
|
|
2814
|
+
const threads = await getPullRequestThreads(target.context, target.repo, target.pat, target.pullRequest.id);
|
|
2815
|
+
const thread = threads.find((t) => t.id === target.threadId);
|
|
2816
|
+
if (!thread) {
|
|
2817
|
+
writeError(`Thread #${target.threadId} not found on pull request #${target.pullRequest.id}.`);
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
const alreadyInTargetState = direction === "resolve" ? isThreadResolved(thread.status) : !isThreadResolved(thread.status);
|
|
2821
|
+
const targetStatus = direction === "resolve" ? "fixed" : "active";
|
|
2822
|
+
if (alreadyInTargetState) {
|
|
2823
|
+
const humanLabel = direction === "resolve" ? "resolved" : "active";
|
|
2824
|
+
const noopResult = {
|
|
2825
|
+
pullRequestId: target.pullRequest.id,
|
|
2826
|
+
threadId: target.threadId,
|
|
2827
|
+
status: thread.status,
|
|
2828
|
+
noop: true
|
|
2829
|
+
};
|
|
2830
|
+
if (options.json) {
|
|
2831
|
+
process.stdout.write(`${JSON.stringify(noopResult, null, 2)}
|
|
2832
|
+
`);
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
process.stdout.write(`Thread #${target.threadId} is already ${humanLabel} on pull request #${target.pullRequest.id} (current status: ${thread.status}).
|
|
2836
|
+
`);
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const updated = await patchThreadStatus(
|
|
2840
|
+
target.context,
|
|
2841
|
+
target.repo,
|
|
2842
|
+
target.pat,
|
|
2843
|
+
target.pullRequest.id,
|
|
2844
|
+
target.threadId,
|
|
2845
|
+
targetStatus
|
|
2846
|
+
);
|
|
2847
|
+
const result = {
|
|
2848
|
+
pullRequestId: target.pullRequest.id,
|
|
2849
|
+
threadId: target.threadId,
|
|
2850
|
+
status: targetStatus,
|
|
2851
|
+
noop: false
|
|
2852
|
+
};
|
|
2853
|
+
if (options.json) {
|
|
2854
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
2855
|
+
`);
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
const verb = direction === "resolve" ? "resolved" : "reopened";
|
|
2859
|
+
process.stdout.write(`Thread #${target.threadId} ${verb} on pull request #${target.pullRequest.id} (status: ${updated.status}).
|
|
2860
|
+
`);
|
|
2861
|
+
} catch (err) {
|
|
2862
|
+
handlePrCommandError(err, context, "write");
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
function createPrCommentResolveCommand() {
|
|
2866
|
+
const command = new Command12("comment-resolve");
|
|
2867
|
+
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) => {
|
|
2868
|
+
await runThreadStateChange(threadIdRaw, options, "resolve");
|
|
2869
|
+
});
|
|
2870
|
+
return command;
|
|
2871
|
+
}
|
|
2872
|
+
function createPrCommentReopenCommand() {
|
|
2873
|
+
const command = new Command12("comment-reopen");
|
|
2874
|
+
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) => {
|
|
2875
|
+
await runThreadStateChange(threadIdRaw, options, "reopen");
|
|
2876
|
+
});
|
|
2877
|
+
return command;
|
|
2878
|
+
}
|
|
2675
2879
|
function createPrCommand() {
|
|
2676
2880
|
const command = new Command12("pr");
|
|
2677
2881
|
command.description("Manage Azure DevOps pull requests");
|
|
2678
2882
|
command.addCommand(createPrStatusCommand());
|
|
2679
2883
|
command.addCommand(createPrOpenCommand());
|
|
2680
2884
|
command.addCommand(createPrCommentsCommand());
|
|
2885
|
+
command.addCommand(createPrCommentResolveCommand());
|
|
2886
|
+
command.addCommand(createPrCommentReopenCommand());
|
|
2681
2887
|
return command;
|
|
2682
2888
|
}
|
|
2683
2889
|
|