azdo-cli 0.2.0-develop.104 → 0.2.0-develop.133
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 +19 -5
- package/dist/index.js +41 -9
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -26,6 +26,20 @@ Azure DevOps CLI focused on work item read/write workflows.
|
|
|
26
26
|
npm install -g azdo-cli
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Utility Scripts
|
|
30
|
+
|
|
31
|
+
The repository also includes a helper script for syncing local `.env` entries into GitHub Actions secrets for the current repository:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
./scripts/sync-env-to-gh-secrets.zsh
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It walks upward from the current directory until it finds a `.env`, then sets each valid `KEY=VALUE` entry with `gh secret set`. You can also limit the sync to selected keys:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
./scripts/sync-env-to-gh-secrets.zsh FOO BAR
|
|
41
|
+
```
|
|
42
|
+
|
|
29
43
|
## Authentication and Context Resolution
|
|
30
44
|
|
|
31
45
|
PAT resolution order:
|
|
@@ -88,8 +102,6 @@ azdo get-item 12345 --fields "System.Tags,Microsoft.VSTS.Common.Priority"
|
|
|
88
102
|
# Convert rich text fields to markdown
|
|
89
103
|
azdo get-item 12345 --markdown
|
|
90
104
|
|
|
91
|
-
# Disable markdown even if config is on
|
|
92
|
-
azdo get-item 12345 --no-markdown
|
|
93
105
|
```
|
|
94
106
|
|
|
95
107
|
```bash
|
|
@@ -107,7 +119,7 @@ azdo set-field 12345 System.Title "Updated title"
|
|
|
107
119
|
### List Fields
|
|
108
120
|
|
|
109
121
|
```bash
|
|
110
|
-
# List all fields
|
|
122
|
+
# List all fields with values (rich text fields preview first 5 lines)
|
|
111
123
|
azdo list-fields 12345
|
|
112
124
|
|
|
113
125
|
# JSON output
|
|
@@ -118,7 +130,7 @@ azdo list-fields 12345 --json
|
|
|
118
130
|
|
|
119
131
|
The `get-item` command can convert HTML rich-text fields to readable markdown. Resolution order:
|
|
120
132
|
|
|
121
|
-
1. `--markdown`
|
|
133
|
+
1. `--markdown` flag enables markdown for the current call
|
|
122
134
|
2. Config setting: `azdo config set markdown true`
|
|
123
135
|
3. Default: off (HTML stripped to plain text)
|
|
124
136
|
|
|
@@ -155,7 +167,7 @@ azdo pr comments
|
|
|
155
167
|
|
|
156
168
|
`azdo pr status`
|
|
157
169
|
|
|
158
|
-
- Lists
|
|
170
|
+
- Lists pull requests for the current branch
|
|
159
171
|
- Prints `No pull requests found for branch <branch>.` when no PRs exist
|
|
160
172
|
- Supports `--json` for machine-readable output
|
|
161
173
|
|
|
@@ -288,6 +300,8 @@ These commands support `--json` for machine-readable output:
|
|
|
288
300
|
- `assign`
|
|
289
301
|
- `set-field`
|
|
290
302
|
- `set-md-field`
|
|
303
|
+
- `upsert`
|
|
304
|
+
- `pr status|open|comments`
|
|
291
305
|
- `config set|get|list|unset`
|
|
292
306
|
|
|
293
307
|
## Development
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,15 @@ async function fetchWithErrors(url, init) {
|
|
|
39
39
|
}
|
|
40
40
|
if (response.status === 401) throw new Error("AUTH_FAILED");
|
|
41
41
|
if (response.status === 403) throw new Error("PERMISSION_DENIED");
|
|
42
|
-
if (response.status === 404)
|
|
42
|
+
if (response.status === 404) {
|
|
43
|
+
let detail = "";
|
|
44
|
+
try {
|
|
45
|
+
const body = await response.text();
|
|
46
|
+
detail = ` | url=${url} | body=${body}`;
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`NOT_FOUND${detail}`);
|
|
50
|
+
}
|
|
43
51
|
return response;
|
|
44
52
|
}
|
|
45
53
|
async function readResponseMessage(response) {
|
|
@@ -64,9 +72,20 @@ function stringifyFieldValue(value) {
|
|
|
64
72
|
function buildExtraFields(fields, requested) {
|
|
65
73
|
const result = {};
|
|
66
74
|
for (const name of requested) {
|
|
67
|
-
|
|
75
|
+
let val = fields[name];
|
|
76
|
+
let resolvedName = name;
|
|
77
|
+
if (val === void 0) {
|
|
78
|
+
const nameSuffix = name.split(".").pop().toLowerCase();
|
|
79
|
+
const match = Object.keys(fields).find(
|
|
80
|
+
(k) => k.split(".").pop().toLowerCase() === nameSuffix
|
|
81
|
+
);
|
|
82
|
+
if (match !== void 0) {
|
|
83
|
+
val = fields[match];
|
|
84
|
+
resolvedName = match;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
68
87
|
if (val !== void 0 && val !== null) {
|
|
69
|
-
result[
|
|
88
|
+
result[resolvedName] = stringifyFieldValue(val);
|
|
70
89
|
}
|
|
71
90
|
}
|
|
72
91
|
return Object.keys(result).length > 0 ? result : null;
|
|
@@ -622,7 +641,7 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
|
|
|
622
641
|
`Error: Access denied. Your PAT may lack ${scope} permissions for project "${context?.project}".
|
|
623
642
|
`
|
|
624
643
|
);
|
|
625
|
-
} else if (msg
|
|
644
|
+
} else if (msg.startsWith("NOT_FOUND")) {
|
|
626
645
|
process.stderr.write(
|
|
627
646
|
`Error: Work item ${id} not found in ${context?.org}/${context?.project}.
|
|
628
647
|
`
|
|
@@ -1407,7 +1426,7 @@ function buildUpsertResult(action, writeResult, fields) {
|
|
|
1407
1426
|
};
|
|
1408
1427
|
}
|
|
1409
1428
|
function isUpdateWriteError(err) {
|
|
1410
|
-
return err.message === "AUTH_FAILED" || err.message === "PERMISSION_DENIED" || err.message
|
|
1429
|
+
return err.message === "AUTH_FAILED" || err.message === "PERMISSION_DENIED" || err.message.startsWith("NOT_FOUND") || err.message === "NETWORK_ERROR" || err.message.startsWith("BAD_REQUEST:") || err.message.startsWith("UPDATE_REJECTED:");
|
|
1411
1430
|
}
|
|
1412
1431
|
function isCreateWriteError(err) {
|
|
1413
1432
|
return err.message === "AUTH_FAILED" || err.message === "PERMISSION_DENIED" || err.message === "NETWORK_ERROR" || err.message.startsWith("BAD_REQUEST:") || err.message.startsWith("HTTP_");
|
|
@@ -1477,6 +1496,14 @@ function stringifyValue(value) {
|
|
|
1477
1496
|
if (typeof value === "object") return JSON.stringify(value);
|
|
1478
1497
|
return String(value);
|
|
1479
1498
|
}
|
|
1499
|
+
function formatRichValue(raw) {
|
|
1500
|
+
const md = htmlToMarkdown(raw);
|
|
1501
|
+
const lines = md.split("\n").filter((l) => l.trim() !== "");
|
|
1502
|
+
const preview = lines.slice(0, 5);
|
|
1503
|
+
const suffix = lines.length > 5 ? `
|
|
1504
|
+
\u2026 (${lines.length - 5} more lines)` : "";
|
|
1505
|
+
return preview.join("\n ") + suffix;
|
|
1506
|
+
}
|
|
1480
1507
|
function formatFieldList(fields) {
|
|
1481
1508
|
const entries = Object.entries(fields).sort(([a], [b]) => a.localeCompare(b));
|
|
1482
1509
|
const maxKeyLen = Math.min(
|
|
@@ -1484,9 +1511,14 @@ function formatFieldList(fields) {
|
|
|
1484
1511
|
50
|
|
1485
1512
|
);
|
|
1486
1513
|
return entries.map(([key, value]) => {
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1514
|
+
const raw = stringifyValue(value);
|
|
1515
|
+
if (raw === "") return `${key.padEnd(maxKeyLen + 2)}(empty)`;
|
|
1516
|
+
if (typeof value === "string" && isHtml(value)) {
|
|
1517
|
+
const preview = formatRichValue(value);
|
|
1518
|
+
return `${key.padEnd(maxKeyLen + 2)}[rich text]
|
|
1519
|
+
${preview}`;
|
|
1520
|
+
}
|
|
1521
|
+
return `${key.padEnd(maxKeyLen + 2)}${raw}`;
|
|
1490
1522
|
}).join("\n");
|
|
1491
1523
|
}
|
|
1492
1524
|
function createListFieldsCommand() {
|
|
@@ -1660,7 +1692,7 @@ function handlePrCommandError(err, context, mode = "read") {
|
|
|
1660
1692
|
if (error.message === "NETWORK_ERROR") {
|
|
1661
1693
|
writeError("Could not connect to Azure DevOps. Check your network connection.");
|
|
1662
1694
|
}
|
|
1663
|
-
if (error.message
|
|
1695
|
+
if (error.message.startsWith("NOT_FOUND")) {
|
|
1664
1696
|
writeError(`Azure DevOps repository not found in ${context?.org}/${context?.project}.`);
|
|
1665
1697
|
}
|
|
1666
1698
|
if (error.message.startsWith("HTTP_")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azdo-cli",
|
|
3
|
-
"version": "0.2.0-develop.
|
|
3
|
+
"version": "0.2.0-develop.133",
|
|
4
4
|
"description": "Azure DevOps CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
16
|
"format": "prettier --check src/",
|
|
17
17
|
"test": "npm run build && vitest run tests/unit",
|
|
18
|
-
"test:integration": "npm run build && vitest run tests/integration"
|
|
18
|
+
"test:integration": "npm run build && vitest run tests/integration",
|
|
19
|
+
"test:integration:full": "bash scripts/setup-keyring.sh && npm run test:integration"
|
|
19
20
|
},
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|