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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +111 -30
  3. 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 Command11 } from "commander";
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/pr.ts
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 Command10("status");
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
- context = resolveContext(options);
1631
- const repo = detectRepoName();
1632
- const branch = getCurrentBranch();
1633
- const credential = await resolvePat();
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 Command10("open");
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
- context = resolveContext(options);
1669
- const repo = detectRepoName();
1670
- const branch = getCurrentBranch();
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 credential = await resolvePat();
1675
- const result = await openPullRequest(context, repo, credential.pat, branch, title, description);
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 Command10("comments");
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
- context = resolveContext(options);
1709
- const repo = detectRepoName();
1710
- const branch = getCurrentBranch();
1711
- const credential = await resolvePat();
1712
- const pullRequests = await listPullRequests(context, repo, credential.pat, branch, { status: "active" });
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, credential.pat, pullRequest.id);
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 Command10("pr");
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 Command11();
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.100",
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",