azdo-cli 0.2.0-008-pull-request-handling.100 → 0.2.0-008-pull-request-handling.102
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 +13 -0
- package/dist/index.js +111 -30
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Azure DevOps CLI focused on work item read/write workflows.
|
|
|
17
17
|
- Set rich-text fields as markdown from inline text, file, or stdin (`set-md-field`)
|
|
18
18
|
- Check branch pull request status, open PRs to `develop`, and review active comments (`pr`)
|
|
19
19
|
- Persist org/project/default fields in local config (`config`)
|
|
20
|
+
- List all fields of a work item (`list-fields`)
|
|
20
21
|
- Store PAT in OS credential store (or use `AZDO_PAT`)
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
@@ -65,6 +66,7 @@ azdo upsert --content $'---\nTitle: Improve markdown import UX\nState: New\n---'
|
|
|
65
66
|
| `azdo upsert [id]` | Create or update a Task from markdown | `--content`, `--file`, `--json`, `--org`, `--project` |
|
|
66
67
|
| `azdo get-md-field <id> <field>` | Get field as markdown | `--org`, `--project` |
|
|
67
68
|
| `azdo set-md-field <id> <field> [content]` | Set markdown field | `--file`, `--json`, `--org`, `--project` |
|
|
69
|
+
| `azdo list-fields <id>` | List all fields of a work item | `--json`, `--org`, `--project` |
|
|
68
70
|
| `azdo pr <subcommand>` | Manage pull requests for the current branch | `status`, `open`, `comments`, `--json`, `--org`, `--project` |
|
|
69
71
|
| `azdo config <subcommand>` | Manage saved settings | `set`, `get`, `list`, `unset`, `wizard`, `--json` |
|
|
70
72
|
| `azdo clear-pat` | Remove stored PAT | none |
|
|
@@ -102,6 +104,16 @@ azdo assign 12345 --unassign
|
|
|
102
104
|
azdo set-field 12345 System.Title "Updated title"
|
|
103
105
|
```
|
|
104
106
|
|
|
107
|
+
### List Fields
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# List all fields of a work item
|
|
111
|
+
azdo list-fields 12345
|
|
112
|
+
|
|
113
|
+
# JSON output
|
|
114
|
+
azdo list-fields 12345 --json
|
|
115
|
+
```
|
|
116
|
+
|
|
105
117
|
### Markdown Display
|
|
106
118
|
|
|
107
119
|
The `get-item` command can convert HTML rich-text fields to readable markdown. Resolution order:
|
|
@@ -271,6 +283,7 @@ azdo clear-pat
|
|
|
271
283
|
## JSON Output
|
|
272
284
|
|
|
273
285
|
These commands support `--json` for machine-readable output:
|
|
286
|
+
- `list-fields`
|
|
274
287
|
- `set-state`
|
|
275
288
|
- `assign`
|
|
276
289
|
- `set-field`
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command12 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -92,6 +92,25 @@ async function readWriteResponse(response, errorCode) {
|
|
|
92
92
|
fields: data.fields
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
async function getWorkItemFields(context, id, pat) {
|
|
96
|
+
const url = new URL(
|
|
97
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitems/${id}`
|
|
98
|
+
);
|
|
99
|
+
url.searchParams.set("api-version", "7.1");
|
|
100
|
+
url.searchParams.set("$expand", "all");
|
|
101
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(pat) });
|
|
102
|
+
if (response.status === 400) {
|
|
103
|
+
const serverMessage = await readResponseMessage(response);
|
|
104
|
+
if (serverMessage) {
|
|
105
|
+
throw new Error(`BAD_REQUEST: ${serverMessage}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`HTTP_${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
return data.fields;
|
|
113
|
+
}
|
|
95
114
|
async function getWorkItem(context, id, pat, extraFields) {
|
|
96
115
|
const url = new URL(
|
|
97
116
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitems/${id}`
|
|
@@ -1451,8 +1470,54 @@ function createUpsertCommand() {
|
|
|
1451
1470
|
return command;
|
|
1452
1471
|
}
|
|
1453
1472
|
|
|
1454
|
-
// src/commands/
|
|
1473
|
+
// src/commands/list-fields.ts
|
|
1455
1474
|
import { Command as Command10 } from "commander";
|
|
1475
|
+
function stringifyValue(value) {
|
|
1476
|
+
if (value === null || value === void 0) return "";
|
|
1477
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
1478
|
+
return String(value);
|
|
1479
|
+
}
|
|
1480
|
+
function formatFieldList(fields) {
|
|
1481
|
+
const entries = Object.entries(fields).sort(([a], [b]) => a.localeCompare(b));
|
|
1482
|
+
const maxKeyLen = Math.min(
|
|
1483
|
+
Math.max(...entries.map(([k]) => k.length)),
|
|
1484
|
+
50
|
|
1485
|
+
);
|
|
1486
|
+
return entries.map(([key, value]) => {
|
|
1487
|
+
const display = stringifyValue(value);
|
|
1488
|
+
const truncated = display.length > 120 ? display.slice(0, 117) + "..." : display;
|
|
1489
|
+
return `${key.padEnd(maxKeyLen + 2)}${truncated}`;
|
|
1490
|
+
}).join("\n");
|
|
1491
|
+
}
|
|
1492
|
+
function createListFieldsCommand() {
|
|
1493
|
+
const command = new Command10("list-fields");
|
|
1494
|
+
command.description("List all fields of an Azure DevOps work item").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output result as JSON").action(
|
|
1495
|
+
async (idStr, options) => {
|
|
1496
|
+
const id = parseWorkItemId(idStr);
|
|
1497
|
+
validateOrgProjectPair(options);
|
|
1498
|
+
let context;
|
|
1499
|
+
try {
|
|
1500
|
+
context = resolveContext(options);
|
|
1501
|
+
const credential = await resolvePat();
|
|
1502
|
+
const fields = await getWorkItemFields(context, id, credential.pat);
|
|
1503
|
+
if (options.json) {
|
|
1504
|
+
process.stdout.write(JSON.stringify({ id, fields }, null, 2) + "\n");
|
|
1505
|
+
} else {
|
|
1506
|
+
process.stdout.write(`Work Item ${id} \u2014 ${Object.keys(fields).length} fields
|
|
1507
|
+
|
|
1508
|
+
`);
|
|
1509
|
+
process.stdout.write(formatFieldList(fields) + "\n");
|
|
1510
|
+
}
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
handleCommandError(err, id, context, "read");
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
return command;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/commands/pr.ts
|
|
1520
|
+
import { Command as Command11 } from "commander";
|
|
1456
1521
|
|
|
1457
1522
|
// src/services/pr-client.ts
|
|
1458
1523
|
function buildPullRequestsUrl(context, repo, sourceBranch, opts) {
|
|
@@ -1613,25 +1678,35 @@ function formatPullRequestBlock(pullRequest) {
|
|
|
1613
1678
|
function formatThreads(prId, title, threads) {
|
|
1614
1679
|
const lines = [`Active comments for pull request #${prId}: ${title}`];
|
|
1615
1680
|
for (const thread of threads) {
|
|
1616
|
-
lines.push("");
|
|
1617
|
-
lines.push(`Thread #${thread.id} [${thread.status}] ${thread.threadContext ?? "(general)"}`);
|
|
1681
|
+
lines.push("", `Thread #${thread.id} [${thread.status}] ${thread.threadContext ?? "(general)"}`);
|
|
1618
1682
|
for (const comment of thread.comments) {
|
|
1619
1683
|
lines.push(` ${comment.author ?? "Unknown"}: ${comment.content}`);
|
|
1620
1684
|
}
|
|
1621
1685
|
}
|
|
1622
1686
|
return lines.join("\n");
|
|
1623
1687
|
}
|
|
1688
|
+
async function resolvePrCommandContext(options) {
|
|
1689
|
+
const context = resolveContext(options);
|
|
1690
|
+
const repo = detectRepoName();
|
|
1691
|
+
const branch = getCurrentBranch();
|
|
1692
|
+
const credential = await resolvePat();
|
|
1693
|
+
return {
|
|
1694
|
+
context,
|
|
1695
|
+
repo,
|
|
1696
|
+
branch,
|
|
1697
|
+
pat: credential.pat
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1624
1700
|
function createPrStatusCommand() {
|
|
1625
|
-
const command = new
|
|
1701
|
+
const command = new Command11("status");
|
|
1626
1702
|
command.description("Check pull requests for the current branch").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (options) => {
|
|
1627
1703
|
validateOrgProjectPair(options);
|
|
1628
1704
|
let context;
|
|
1629
1705
|
try {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
const
|
|
1633
|
-
const
|
|
1634
|
-
const pullRequests = await listPullRequests(context, repo, credential.pat, branch);
|
|
1706
|
+
const resolved = await resolvePrCommandContext(options);
|
|
1707
|
+
context = resolved.context;
|
|
1708
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch);
|
|
1709
|
+
const { branch, repo } = resolved;
|
|
1635
1710
|
const result = { branch, repository: repo, pullRequests };
|
|
1636
1711
|
if (options.json) {
|
|
1637
1712
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
@@ -1652,7 +1727,7 @@ function createPrStatusCommand() {
|
|
|
1652
1727
|
return command;
|
|
1653
1728
|
}
|
|
1654
1729
|
function createPrOpenCommand() {
|
|
1655
|
-
const command = new
|
|
1730
|
+
const command = new Command11("open");
|
|
1656
1731
|
command.description("Open a pull request from the current branch to develop").option("--title <title>", "pull request title").option("--description <description>", "pull request description").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (options) => {
|
|
1657
1732
|
validateOrgProjectPair(options);
|
|
1658
1733
|
const title = options.title?.trim();
|
|
@@ -1665,14 +1740,19 @@ function createPrOpenCommand() {
|
|
|
1665
1740
|
}
|
|
1666
1741
|
let context;
|
|
1667
1742
|
try {
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
if (branch === "develop") {
|
|
1743
|
+
const resolved = await resolvePrCommandContext(options);
|
|
1744
|
+
context = resolved.context;
|
|
1745
|
+
if (resolved.branch === "develop") {
|
|
1672
1746
|
writeError("Pull request creation requires a source branch other than develop.");
|
|
1673
1747
|
}
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1748
|
+
const result = await openPullRequest(
|
|
1749
|
+
resolved.context,
|
|
1750
|
+
resolved.repo,
|
|
1751
|
+
resolved.pat,
|
|
1752
|
+
resolved.branch,
|
|
1753
|
+
title,
|
|
1754
|
+
description
|
|
1755
|
+
);
|
|
1676
1756
|
if (options.json) {
|
|
1677
1757
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1678
1758
|
`);
|
|
@@ -1685,7 +1765,7 @@ ${result.pullRequest.url}
|
|
|
1685
1765
|
return;
|
|
1686
1766
|
}
|
|
1687
1767
|
process.stdout.write(
|
|
1688
|
-
`Active pull request already exists for ${branch} -> develop: #${result.pullRequest.id}
|
|
1768
|
+
`Active pull request already exists for ${resolved.branch} -> develop: #${result.pullRequest.id}
|
|
1689
1769
|
${result.pullRequest.url}
|
|
1690
1770
|
`
|
|
1691
1771
|
);
|
|
@@ -1700,26 +1780,26 @@ ${result.pullRequest.url}
|
|
|
1700
1780
|
return command;
|
|
1701
1781
|
}
|
|
1702
1782
|
function createPrCommentsCommand() {
|
|
1703
|
-
const command = new
|
|
1783
|
+
const command = new Command11("comments");
|
|
1704
1784
|
command.description("List active pull request comments for the current branch").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (options) => {
|
|
1705
1785
|
validateOrgProjectPair(options);
|
|
1706
1786
|
let context;
|
|
1707
1787
|
try {
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
1788
|
+
const resolved = await resolvePrCommandContext(options);
|
|
1789
|
+
context = resolved.context;
|
|
1790
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
|
|
1791
|
+
status: "active"
|
|
1792
|
+
});
|
|
1713
1793
|
if (pullRequests.length === 0) {
|
|
1714
|
-
writeError(`No active pull request found for branch ${branch}.`);
|
|
1794
|
+
writeError(`No active pull request found for branch ${resolved.branch}.`);
|
|
1715
1795
|
}
|
|
1716
1796
|
if (pullRequests.length > 1) {
|
|
1717
1797
|
const ids = pullRequests.map((pullRequest2) => `#${pullRequest2.id}`).join(", ");
|
|
1718
|
-
writeError(`Multiple active pull requests found for branch ${branch}: ${ids}. Use pr status to review them.`);
|
|
1798
|
+
writeError(`Multiple active pull requests found for branch ${resolved.branch}: ${ids}. Use pr status to review them.`);
|
|
1719
1799
|
}
|
|
1720
1800
|
const pullRequest = pullRequests[0];
|
|
1721
|
-
const threads = await getPullRequestThreads(context, repo,
|
|
1722
|
-
const result = { branch, pullRequest, threads };
|
|
1801
|
+
const threads = await getPullRequestThreads(resolved.context, resolved.repo, resolved.pat, pullRequest.id);
|
|
1802
|
+
const result = { branch: resolved.branch, pullRequest, threads };
|
|
1723
1803
|
if (options.json) {
|
|
1724
1804
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1725
1805
|
`);
|
|
@@ -1739,7 +1819,7 @@ function createPrCommentsCommand() {
|
|
|
1739
1819
|
return command;
|
|
1740
1820
|
}
|
|
1741
1821
|
function createPrCommand() {
|
|
1742
|
-
const command = new
|
|
1822
|
+
const command = new Command11("pr");
|
|
1743
1823
|
command.description("Manage Azure DevOps pull requests");
|
|
1744
1824
|
command.addCommand(createPrStatusCommand());
|
|
1745
1825
|
command.addCommand(createPrOpenCommand());
|
|
@@ -1748,7 +1828,7 @@ function createPrCommand() {
|
|
|
1748
1828
|
}
|
|
1749
1829
|
|
|
1750
1830
|
// src/index.ts
|
|
1751
|
-
var program = new
|
|
1831
|
+
var program = new Command12();
|
|
1752
1832
|
program.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
1753
1833
|
program.addCommand(createGetItemCommand());
|
|
1754
1834
|
program.addCommand(createClearPatCommand());
|
|
@@ -1759,6 +1839,7 @@ program.addCommand(createSetFieldCommand());
|
|
|
1759
1839
|
program.addCommand(createGetMdFieldCommand());
|
|
1760
1840
|
program.addCommand(createSetMdFieldCommand());
|
|
1761
1841
|
program.addCommand(createUpsertCommand());
|
|
1842
|
+
program.addCommand(createListFieldsCommand());
|
|
1762
1843
|
program.addCommand(createPrCommand());
|
|
1763
1844
|
program.showHelpAfterError();
|
|
1764
1845
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azdo-cli",
|
|
3
|
-
"version": "0.2.0-008-pull-request-handling.
|
|
3
|
+
"version": "0.2.0-008-pull-request-handling.102",
|
|
4
4
|
"description": "Azure DevOps CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"lint": "eslint src/",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
16
|
"format": "prettier --check src/",
|
|
17
|
-
"test": "npm run build && vitest run"
|
|
17
|
+
"test": "npm run build && vitest run tests/unit",
|
|
18
|
+
"test:integration": "npm run build && vitest run tests/integration"
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|