azdo-cli 0.22.0-develop.793 → 0.22.0-develop.807
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 +7 -0
- package/dist/index.js +524 -118
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Azure DevOps CLI focused on work item read/write workflows.
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Retrieve work items with readable output (`get-item`)
|
|
12
|
+
- List work items by state, tag, assignee or title (`list-items`)
|
|
12
13
|
- Update work item state, assignee, or any field (`set-state`, `assign`, `set-field`)
|
|
13
14
|
- Create or update work items from markdown documents (`upsert`)
|
|
14
15
|
- Read and post work item comments (`comments`)
|
|
@@ -48,6 +49,9 @@ azdo get-item 12345
|
|
|
48
49
|
# Download images embedded in a work item's rich-text fields (opt-in)
|
|
49
50
|
azdo get-item 12345 --download-images # saved to the system temp dir
|
|
50
51
|
azdo get-item 12345 --resize-images 1024 --images-path ./img # cap width at 1024px, save as PNG
|
|
52
|
+
|
|
53
|
+
# List work items (--state, --tag, --assigned-to [@me], --title-contains, --top, --json)
|
|
54
|
+
azdo list-items --state Active --tag ready --json
|
|
51
55
|
azdo get-md-field 12345 System.Description --download-images # same flags on get-md-field
|
|
52
56
|
|
|
53
57
|
# Update state
|
|
@@ -135,6 +139,9 @@ azdo pipeline get-runs 12 --branch develop --limit 1
|
|
|
135
139
|
azdo pipeline wait 3456 # blocks; exit 0 success / non-zero failure / 124 timeout
|
|
136
140
|
azdo pipeline get-run-detail 3456 # errors, failing tests, per-stage status
|
|
137
141
|
azdo pipeline start 12 --branch develop --parameter env=staging
|
|
142
|
+
azdo pipeline artifacts 3456 # list a run's build artifacts (name, type, size)
|
|
143
|
+
azdo pipeline artifact-download 3456 reports --path ./out # extract into ./out (--all for every artifact, --force to overwrite)
|
|
144
|
+
azdo pipeline logs 3456 --step "Trivy" --head 50 --no-progress # also --tail/--grep; --no-progress drops CR redraws
|
|
138
145
|
|
|
139
146
|
# Work item relations — types, add, remove, list
|
|
140
147
|
azdo relations types # list all relation types (Child, Parent, Related, ...)
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
} from "./chunk-MHJ6OBX3.js";
|
|
43
43
|
|
|
44
44
|
// src/program.ts
|
|
45
|
-
import { Command as
|
|
45
|
+
import { Command as Command20 } from "commander";
|
|
46
46
|
|
|
47
47
|
// src/version.ts
|
|
48
48
|
import { readFileSync } from "fs";
|
|
@@ -249,8 +249,9 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
|
|
|
249
249
|
);
|
|
250
250
|
writeErrorDetail(msg, "PERMISSION_DENIED");
|
|
251
251
|
} else if (msg.startsWith("NOT_FOUND")) {
|
|
252
|
+
const subject = id === void 0 ? "Project or resource" : `Work item ${id}`;
|
|
252
253
|
process.stderr.write(
|
|
253
|
-
`Error:
|
|
254
|
+
`Error: ${subject} not found in ${context?.org}/${context?.project}.
|
|
254
255
|
`
|
|
255
256
|
);
|
|
256
257
|
} else if (msg === "NETWORK_ERROR") {
|
|
@@ -988,6 +989,107 @@ async function createAttachment(context, fileName, content, cred) {
|
|
|
988
989
|
}
|
|
989
990
|
return await response.json();
|
|
990
991
|
}
|
|
992
|
+
var LIST_SYSTEM_FIELDS = [
|
|
993
|
+
"System.Title",
|
|
994
|
+
"System.State",
|
|
995
|
+
"System.WorkItemType",
|
|
996
|
+
"System.AssignedTo",
|
|
997
|
+
"System.Description",
|
|
998
|
+
"System.Tags",
|
|
999
|
+
"System.TeamProject"
|
|
1000
|
+
];
|
|
1001
|
+
var LIST_PROCESS_FIELDS = [
|
|
1002
|
+
"Microsoft.VSTS.Common.AcceptanceCriteria",
|
|
1003
|
+
"Microsoft.VSTS.TCM.ReproSteps"
|
|
1004
|
+
];
|
|
1005
|
+
var WORKITEMS_BATCH_LIMIT = 200;
|
|
1006
|
+
function wiqlLiteral(value) {
|
|
1007
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
1008
|
+
}
|
|
1009
|
+
function buildListWiql(filter) {
|
|
1010
|
+
const clauses = ["[System.TeamProject] = @project"];
|
|
1011
|
+
if (filter.state) clauses.push(`[System.State] = ${wiqlLiteral(filter.state)}`);
|
|
1012
|
+
if (filter.tag) clauses.push(`[System.Tags] CONTAINS ${wiqlLiteral(filter.tag)}`);
|
|
1013
|
+
if (filter.assignedTo) {
|
|
1014
|
+
clauses.push(
|
|
1015
|
+
filter.assignedTo.toLowerCase() === "@me" ? "[System.AssignedTo] = @Me" : `[System.AssignedTo] = ${wiqlLiteral(filter.assignedTo)}`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
if (filter.titleContains) clauses.push(`[System.Title] CONTAINS ${wiqlLiteral(filter.titleContains)}`);
|
|
1019
|
+
return `SELECT [System.Id] FROM WorkItems WHERE ${clauses.join(" AND ")} ORDER BY [System.ChangedDate] DESC`;
|
|
1020
|
+
}
|
|
1021
|
+
function parseTags(raw) {
|
|
1022
|
+
if (typeof raw !== "string") return [];
|
|
1023
|
+
return raw.split(";").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
1024
|
+
}
|
|
1025
|
+
async function runWiql(context, cred, wiql, top) {
|
|
1026
|
+
const url = new URL(
|
|
1027
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/wiql`
|
|
1028
|
+
);
|
|
1029
|
+
url.searchParams.set("api-version", "7.1");
|
|
1030
|
+
url.searchParams.set("$top", String(top));
|
|
1031
|
+
const response = await fetchWithErrors(url.toString(), {
|
|
1032
|
+
method: "POST",
|
|
1033
|
+
headers: { ...authHeaders(cred), "Content-Type": "application/json" },
|
|
1034
|
+
body: JSON.stringify({ query: wiql })
|
|
1035
|
+
});
|
|
1036
|
+
if (response.status === 400) {
|
|
1037
|
+
const serverMessage = await readResponseMessage(response);
|
|
1038
|
+
if (serverMessage) throw new Error(`BAD_REQUEST: ${serverMessage}`);
|
|
1039
|
+
}
|
|
1040
|
+
if (!response.ok) throw httpError(response);
|
|
1041
|
+
const data = await response.json();
|
|
1042
|
+
return (data.workItems ?? []).map((w) => w.id);
|
|
1043
|
+
}
|
|
1044
|
+
async function postWorkItemsBatch(context, cred, ids, fields) {
|
|
1045
|
+
const url = new URL(
|
|
1046
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitemsbatch`
|
|
1047
|
+
);
|
|
1048
|
+
url.searchParams.set("api-version", "7.1");
|
|
1049
|
+
return fetchWithErrors(url.toString(), {
|
|
1050
|
+
method: "POST",
|
|
1051
|
+
headers: { ...authHeaders(cred), "Content-Type": "application/json" },
|
|
1052
|
+
body: JSON.stringify({ ids, fields })
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
async function readWorkItemsBatch(context, cred, ids) {
|
|
1056
|
+
let response = await postWorkItemsBatch(context, cred, ids, [...LIST_SYSTEM_FIELDS, ...LIST_PROCESS_FIELDS]);
|
|
1057
|
+
if (response.status === 400) {
|
|
1058
|
+
const serverMessage = await readResponseMessage(response);
|
|
1059
|
+
if (!serverMessage?.includes("TF51535")) {
|
|
1060
|
+
throw serverMessage ? new Error(`BAD_REQUEST: ${serverMessage}`) : httpError(response);
|
|
1061
|
+
}
|
|
1062
|
+
response = await postWorkItemsBatch(context, cred, ids, LIST_SYSTEM_FIELDS);
|
|
1063
|
+
}
|
|
1064
|
+
if (!response.ok) throw httpError(response);
|
|
1065
|
+
return (await response.json()).value ?? [];
|
|
1066
|
+
}
|
|
1067
|
+
async function queryWorkItems(context, cred, filter) {
|
|
1068
|
+
const ids = await runWiql(context, cred, buildListWiql(filter), filter.top);
|
|
1069
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1070
|
+
const chunks = [];
|
|
1071
|
+
for (let i = 0; i < ids.length; i += WORKITEMS_BATCH_LIMIT) {
|
|
1072
|
+
chunks.push(ids.slice(i, i + WORKITEMS_BATCH_LIMIT));
|
|
1073
|
+
}
|
|
1074
|
+
const batches = await Promise.all(chunks.map((chunk) => readWorkItemsBatch(context, cred, chunk)));
|
|
1075
|
+
for (const batch of batches) {
|
|
1076
|
+
for (const item of batch) {
|
|
1077
|
+
const teamProject = item.fields["System.TeamProject"];
|
|
1078
|
+
const project = typeof teamProject === "string" ? teamProject : context.project;
|
|
1079
|
+
byId.set(item.id, {
|
|
1080
|
+
id: item.id,
|
|
1081
|
+
title: item.fields["System.Title"],
|
|
1082
|
+
description: buildCombinedDescription(item.fields),
|
|
1083
|
+
url: `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(project)}/_workitems/edit/${item.id}`,
|
|
1084
|
+
state: item.fields["System.State"],
|
|
1085
|
+
type: item.fields["System.WorkItemType"],
|
|
1086
|
+
tags: parseTags(item.fields["System.Tags"]),
|
|
1087
|
+
assignedTo: item.fields["System.AssignedTo"]?.displayName ?? null
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return ids.flatMap((id) => byId.get(id) ?? []);
|
|
1092
|
+
}
|
|
991
1093
|
|
|
992
1094
|
// src/services/auth.ts
|
|
993
1095
|
import { createInterface } from "readline";
|
|
@@ -1957,10 +2059,69 @@ function createGetItemCommand() {
|
|
|
1957
2059
|
return command;
|
|
1958
2060
|
}
|
|
1959
2061
|
|
|
1960
|
-
// src/commands/
|
|
2062
|
+
// src/commands/list-items.ts
|
|
1961
2063
|
import { Command as Command2 } from "commander";
|
|
2064
|
+
var DEFAULT_TOP = 50;
|
|
2065
|
+
var MAX_TOP = 1e3;
|
|
2066
|
+
function parseTop(raw) {
|
|
2067
|
+
if (raw === void 0) return DEFAULT_TOP;
|
|
2068
|
+
const top = Number(raw);
|
|
2069
|
+
if (!/^\d+$/.test(raw) || top < 1 || top > MAX_TOP) {
|
|
2070
|
+
process.stderr.write(`Error: --top must be an integer between 1 and ${MAX_TOP}. Got: "${raw}"
|
|
2071
|
+
`);
|
|
2072
|
+
process.exit(1);
|
|
2073
|
+
}
|
|
2074
|
+
return top;
|
|
2075
|
+
}
|
|
2076
|
+
function toJsonRow(item) {
|
|
2077
|
+
return {
|
|
2078
|
+
id: item.id,
|
|
2079
|
+
title: item.title,
|
|
2080
|
+
description: item.description ? toMarkdown(item.description) : "",
|
|
2081
|
+
url: item.url,
|
|
2082
|
+
state: item.state,
|
|
2083
|
+
tags: item.tags,
|
|
2084
|
+
assignedTo: item.assignedTo
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function formatItemLine(item) {
|
|
2088
|
+
const tags = item.tags.length > 0 ? ` [${item.tags.join(", ")}]` : "";
|
|
2089
|
+
return `${item.id} ${item.state} ${item.assignedTo ?? "Unassigned"} ${item.title}${tags}`;
|
|
2090
|
+
}
|
|
2091
|
+
function createListItemsCommand() {
|
|
2092
|
+
const command = new Command2("list-items");
|
|
2093
|
+
command.description("List work items filtered by state, tag, assignee and title (one WIQL query, then batched reads of 200 items)").option("--state <state>", 'only items in this state (e.g. "Active")').option("--tag <tag>", "only items carrying this tag").option("--assigned-to <user>", "only items assigned to this user (display name, email, or @me)").option("--title-contains <text>", "only items whose title contains this text").option("--top <n>", `maximum number of items, newest change first (default ${DEFAULT_TOP}, max ${MAX_TOP})`).option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output an array of { id, title, description (markdown), url, state, tags, assignedTo }").action(async (options) => {
|
|
2094
|
+
validateOrgProjectPair(options);
|
|
2095
|
+
const top = parseTop(options.top);
|
|
2096
|
+
let context;
|
|
2097
|
+
try {
|
|
2098
|
+
context = resolveContext(options);
|
|
2099
|
+
const credential = await requireAuthCredential(context.org);
|
|
2100
|
+
const items = await queryWorkItems(context, credential, {
|
|
2101
|
+
state: options.state,
|
|
2102
|
+
tag: options.tag,
|
|
2103
|
+
assignedTo: options.assignedTo,
|
|
2104
|
+
titleContains: options.titleContains,
|
|
2105
|
+
top
|
|
2106
|
+
});
|
|
2107
|
+
if (options.json) {
|
|
2108
|
+
process.stdout.write(JSON.stringify(items.map(toJsonRow)) + "\n");
|
|
2109
|
+
} else if (items.length === 0) {
|
|
2110
|
+
process.stdout.write("No work items found.\n");
|
|
2111
|
+
} else {
|
|
2112
|
+
process.stdout.write(items.map(formatItemLine).join("\n") + "\n");
|
|
2113
|
+
}
|
|
2114
|
+
} catch (err) {
|
|
2115
|
+
handleCommandError(err, void 0, context, "read");
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
return command;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// src/commands/clear-pat.ts
|
|
2122
|
+
import { Command as Command3 } from "commander";
|
|
1962
2123
|
function createClearPatCommand() {
|
|
1963
|
-
const command = new
|
|
2124
|
+
const command = new Command3("clear-pat");
|
|
1964
2125
|
command.description("Remove a stored Azure DevOps PAT (deprecated: use `azdo auth logout`)").option("--org <name>", "Azure DevOps organization (overrides auto-detect / config)").action(async (options) => {
|
|
1965
2126
|
process.stderr.write("`azdo clear-pat` is deprecated; use `azdo auth logout [--org <name>]` instead.\n");
|
|
1966
2127
|
const resolved = resolveOrg({ org: options.org });
|
|
@@ -1983,7 +2144,7 @@ function createClearPatCommand() {
|
|
|
1983
2144
|
}
|
|
1984
2145
|
|
|
1985
2146
|
// src/commands/auth.ts
|
|
1986
|
-
import { Command as
|
|
2147
|
+
import { Command as Command4 } from "commander";
|
|
1987
2148
|
|
|
1988
2149
|
// src/services/auth-diagnostics.ts
|
|
1989
2150
|
async function runConnectivityTest(org, cred) {
|
|
@@ -2481,7 +2642,7 @@ async function handleToken(orgFromGlobal) {
|
|
|
2481
2642
|
`);
|
|
2482
2643
|
}
|
|
2483
2644
|
function createAuthCommand() {
|
|
2484
|
-
const command = new
|
|
2645
|
+
const command = new Command4("auth");
|
|
2485
2646
|
command.description(
|
|
2486
2647
|
"Manage Azure DevOps authentication. Use `azdo auth login` for OAuth (default); the bare `azdo auth` form preserves the legacy PAT-prompt path for back-compat."
|
|
2487
2648
|
);
|
|
@@ -2619,7 +2780,7 @@ not be resolved, 4 OS credential store unavailable. stdout stays empty on all.
|
|
|
2619
2780
|
}
|
|
2620
2781
|
|
|
2621
2782
|
// src/commands/config.ts
|
|
2622
|
-
import { Command as
|
|
2783
|
+
import { Command as Command5 } from "commander";
|
|
2623
2784
|
import { createInterface as createInterface2 } from "readline";
|
|
2624
2785
|
|
|
2625
2786
|
// src/services/dpapi-copy.ts
|
|
@@ -2810,7 +2971,7 @@ function renderSettingsHelp(settings = SETTINGS) {
|
|
|
2810
2971
|
var KEY_ARGUMENT_HELP = `setting key (${SETTINGS.map((s) => s.key).join(", ")})`;
|
|
2811
2972
|
var KEY_HELP_POINTER = "\nRun `azdo config --help` for each setting's meaning, accepted values and scope.\n";
|
|
2812
2973
|
function createConfigCommand() {
|
|
2813
|
-
const config = new
|
|
2974
|
+
const config = new Command5("config");
|
|
2814
2975
|
config.description("Manage CLI settings");
|
|
2815
2976
|
config.addHelpText("after", `
|
|
2816
2977
|
${renderSettingsHelp()}`);
|
|
@@ -2830,7 +2991,7 @@ ${renderSettingsHelp()}`);
|
|
|
2830
2991
|
"Run `azdo auth diagnose` to see which credential is in use."
|
|
2831
2992
|
].join("\n")
|
|
2832
2993
|
);
|
|
2833
|
-
const set = new
|
|
2994
|
+
const set = new Command5("set");
|
|
2834
2995
|
set.description("Set a configuration value").argument("<key>", KEY_ARGUMENT_HELP).argument("<value>", "setting value").option("--org <org>", "set value in an org-scoped configuration").option("--json", "output in JSON format").option("--copy-credentials", "with credentialStore dpapi: copy Credential Manager credentials without asking").option("--no-copy-credentials", "with credentialStore dpapi: do not offer to copy Credential Manager credentials").addHelpText("after", KEY_HELP_POINTER).action((key, value, options) => {
|
|
2835
2996
|
try {
|
|
2836
2997
|
if (options.org) {
|
|
@@ -2864,7 +3025,7 @@ ${renderSettingsHelp()}`);
|
|
|
2864
3025
|
}
|
|
2865
3026
|
writeJson();
|
|
2866
3027
|
});
|
|
2867
|
-
const get = new
|
|
3028
|
+
const get = new Command5("get");
|
|
2868
3029
|
get.description("Get a configuration value").argument("<key>", KEY_ARGUMENT_HELP).option("--org <org>", "read from an org-scoped configuration").option("--json", "output in JSON format").addHelpText("after", KEY_HELP_POINTER).action((key, options) => {
|
|
2869
3030
|
try {
|
|
2870
3031
|
const value = options.org ? getOrgScopedValue(options.org, key) : getConfigValue(key);
|
|
@@ -2887,7 +3048,7 @@ ${renderSettingsHelp()}`);
|
|
|
2887
3048
|
process.exit(1);
|
|
2888
3049
|
}
|
|
2889
3050
|
});
|
|
2890
|
-
const list = new
|
|
3051
|
+
const list = new Command5("list");
|
|
2891
3052
|
list.description("List all configuration values").option("--json", "output in JSON format").action((options) => {
|
|
2892
3053
|
const cfg = loadConfig();
|
|
2893
3054
|
if (options.json) {
|
|
@@ -2897,7 +3058,7 @@ ${renderSettingsHelp()}`);
|
|
|
2897
3058
|
}
|
|
2898
3059
|
writeConfigList(cfg);
|
|
2899
3060
|
});
|
|
2900
|
-
const unset = new
|
|
3061
|
+
const unset = new Command5("unset");
|
|
2901
3062
|
unset.description("Remove a configuration value").argument("<key>", KEY_ARGUMENT_HELP).option("--org <org>", "remove from an org-scoped configuration").option("--json", "output in JSON format").addHelpText("after", KEY_HELP_POINTER).action((key, options) => {
|
|
2902
3063
|
try {
|
|
2903
3064
|
if (options.org) {
|
|
@@ -2919,7 +3080,7 @@ ${renderSettingsHelp()}`);
|
|
|
2919
3080
|
process.exit(1);
|
|
2920
3081
|
}
|
|
2921
3082
|
});
|
|
2922
|
-
const wizard = new
|
|
3083
|
+
const wizard = new Command5("wizard");
|
|
2923
3084
|
wizard.description("Interactive wizard to configure all settings").action(async () => {
|
|
2924
3085
|
if (!process.stdin.isTTY) {
|
|
2925
3086
|
process.stderr.write(
|
|
@@ -2942,7 +3103,7 @@ ${renderSettingsHelp()}`);
|
|
|
2942
3103
|
rl.close();
|
|
2943
3104
|
process.stderr.write("Configuration complete!\n");
|
|
2944
3105
|
});
|
|
2945
|
-
const orgCopy = new
|
|
3106
|
+
const orgCopy = new Command5("org-copy");
|
|
2946
3107
|
orgCopy.description('Copy settings from one scope to another (use "default" as source to copy top-level settings)').argument("<from>", 'source scope name or "default"').argument("<to>", "destination org name").option("--force", "overwrite existing values on collision").action((from, to, options) => {
|
|
2947
3108
|
try {
|
|
2948
3109
|
copyOrgScope(from, to, options.force ?? false);
|
|
@@ -2955,7 +3116,7 @@ ${renderSettingsHelp()}`);
|
|
|
2955
3116
|
process.exit(1);
|
|
2956
3117
|
}
|
|
2957
3118
|
});
|
|
2958
|
-
const orgMove = new
|
|
3119
|
+
const orgMove = new Command5("org-move");
|
|
2959
3120
|
orgMove.description("Move settings from one org scope to another").argument("<from>", "source org name").argument("<to>", "destination org name").option("--force", "overwrite existing values on collision").action((from, to, options) => {
|
|
2960
3121
|
try {
|
|
2961
3122
|
moveOrgScope(from, to, options.force ?? false);
|
|
@@ -2968,7 +3129,7 @@ ${renderSettingsHelp()}`);
|
|
|
2968
3129
|
process.exit(1);
|
|
2969
3130
|
}
|
|
2970
3131
|
});
|
|
2971
|
-
const orgDelete = new
|
|
3132
|
+
const orgDelete = new Command5("org-delete");
|
|
2972
3133
|
orgDelete.description("Delete an org-scoped configuration").argument("<name>", "org name").action((name) => {
|
|
2973
3134
|
try {
|
|
2974
3135
|
deleteOrgScope(name);
|
|
@@ -2993,9 +3154,9 @@ ${renderSettingsHelp()}`);
|
|
|
2993
3154
|
}
|
|
2994
3155
|
|
|
2995
3156
|
// src/commands/set-state.ts
|
|
2996
|
-
import { Command as
|
|
3157
|
+
import { Command as Command6 } from "commander";
|
|
2997
3158
|
function createSetStateCommand() {
|
|
2998
|
-
const command = new
|
|
3159
|
+
const command = new Command6("set-state");
|
|
2999
3160
|
command.description("Change the state of a work item").argument("<id>", "work item ID").argument("<state>", 'target state (e.g., "Active", "Closed")').option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output result as JSON").action(
|
|
3000
3161
|
async (idStr, state, options) => {
|
|
3001
3162
|
const id = parseWorkItemId(idStr);
|
|
@@ -3031,9 +3192,9 @@ function createSetStateCommand() {
|
|
|
3031
3192
|
}
|
|
3032
3193
|
|
|
3033
3194
|
// src/commands/assign.ts
|
|
3034
|
-
import { Command as
|
|
3195
|
+
import { Command as Command7 } from "commander";
|
|
3035
3196
|
function createAssignCommand() {
|
|
3036
|
-
const command = new
|
|
3197
|
+
const command = new Command7("assign");
|
|
3037
3198
|
command.description("Assign a work item to a user, or unassign it").argument("<id>", "work item ID").argument("[name]", "user display name or email").option("--unassign", "clear the Assigned To field").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output result as JSON").action(
|
|
3038
3199
|
async (idStr, name, options) => {
|
|
3039
3200
|
const id = parseWorkItemId(idStr);
|
|
@@ -3083,9 +3244,9 @@ function createAssignCommand() {
|
|
|
3083
3244
|
}
|
|
3084
3245
|
|
|
3085
3246
|
// src/commands/set-field.ts
|
|
3086
|
-
import { Command as
|
|
3247
|
+
import { Command as Command8 } from "commander";
|
|
3087
3248
|
function createSetFieldCommand() {
|
|
3088
|
-
const command = new
|
|
3249
|
+
const command = new Command8("set-field");
|
|
3089
3250
|
command.description("Set any work item field by its reference name").argument("<id>", "work item ID").argument("<field>", "field reference name (e.g., System.Title)").argument("<value>", "new value for the field").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output result as JSON").action(
|
|
3090
3251
|
async (idStr, field, value, options) => {
|
|
3091
3252
|
const id = parseWorkItemId(idStr);
|
|
@@ -3121,9 +3282,9 @@ function createSetFieldCommand() {
|
|
|
3121
3282
|
}
|
|
3122
3283
|
|
|
3123
3284
|
// src/commands/get-md-field.ts
|
|
3124
|
-
import { Command as
|
|
3285
|
+
import { Command as Command9 } from "commander";
|
|
3125
3286
|
function createGetMdFieldCommand() {
|
|
3126
|
-
const command = new
|
|
3287
|
+
const command = new Command9("get-md-field");
|
|
3127
3288
|
command.description("Get a work item field value, converting HTML to markdown").argument("<id>", "work item ID").argument("<field>", "field reference name (e.g., System.Description)").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project");
|
|
3128
3289
|
addImageDownloadOptions(command);
|
|
3129
3290
|
command.action(
|
|
@@ -3158,7 +3319,7 @@ function createGetMdFieldCommand() {
|
|
|
3158
3319
|
|
|
3159
3320
|
// src/commands/set-md-field.ts
|
|
3160
3321
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
3161
|
-
import { Command as
|
|
3322
|
+
import { Command as Command10 } from "commander";
|
|
3162
3323
|
function fail(message) {
|
|
3163
3324
|
process.stderr.write(`Error: ${message}
|
|
3164
3325
|
`);
|
|
@@ -3220,7 +3381,7 @@ function formatOutput(result, options, field) {
|
|
|
3220
3381
|
}
|
|
3221
3382
|
}
|
|
3222
3383
|
function createSetMdFieldCommand() {
|
|
3223
|
-
const command = new
|
|
3384
|
+
const command = new Command10("set-md-field");
|
|
3224
3385
|
command.description("Set a work item field with markdown content").argument("<id>", "work item ID").argument("<field>", "field reference name (e.g., System.Description)").argument("[content]", "markdown content to set").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output result as JSON").option("--file <path>", "read markdown content from file").action(
|
|
3225
3386
|
async (idStr, field, inlineContent, options) => {
|
|
3226
3387
|
const id = parseWorkItemId(idStr);
|
|
@@ -3247,7 +3408,7 @@ function createSetMdFieldCommand() {
|
|
|
3247
3408
|
|
|
3248
3409
|
// src/commands/upsert.ts
|
|
3249
3410
|
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
3250
|
-
import { Command as
|
|
3411
|
+
import { Command as Command11 } from "commander";
|
|
3251
3412
|
|
|
3252
3413
|
// src/services/task-document.ts
|
|
3253
3414
|
var FIELD_ALIASES = /* @__PURE__ */ new Map([
|
|
@@ -3532,7 +3693,7 @@ function handleUpsertError(err, id, context) {
|
|
|
3532
3693
|
process.exit(1);
|
|
3533
3694
|
}
|
|
3534
3695
|
function createUpsertCommand() {
|
|
3535
|
-
const command = new
|
|
3696
|
+
const command = new Command11("upsert");
|
|
3536
3697
|
command.description("Create or update a work item from a markdown document").argument("[id]", "work item ID to update; omit to create a new work item").option("--content <markdown>", "task document content").option("--file <path>", "read task document from file").option("--type <workItemType>", "create mode work item type (defaults to Task)").option("--json", "output result as JSON").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").action(async (idStr, options) => {
|
|
3537
3698
|
validateOrgProjectPair(options);
|
|
3538
3699
|
const id = idStr === void 0 ? void 0 : parseWorkItemId(idStr);
|
|
@@ -3573,7 +3734,7 @@ function createUpsertCommand() {
|
|
|
3573
3734
|
}
|
|
3574
3735
|
|
|
3575
3736
|
// src/commands/list-fields.ts
|
|
3576
|
-
import { Command as
|
|
3737
|
+
import { Command as Command12 } from "commander";
|
|
3577
3738
|
function stringifyValue(value) {
|
|
3578
3739
|
if (value === null || value === void 0) return "";
|
|
3579
3740
|
if (typeof value === "string") return value;
|
|
@@ -3607,7 +3768,7 @@ function formatFieldList(fields) {
|
|
|
3607
3768
|
}).join("\n");
|
|
3608
3769
|
}
|
|
3609
3770
|
function createListFieldsCommand() {
|
|
3610
|
-
const command = new
|
|
3771
|
+
const command = new Command12("list-fields");
|
|
3611
3772
|
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(
|
|
3612
3773
|
async (idStr, options) => {
|
|
3613
3774
|
const id = parseWorkItemId(idStr);
|
|
@@ -3635,7 +3796,7 @@ function createListFieldsCommand() {
|
|
|
3635
3796
|
|
|
3636
3797
|
// src/commands/pr.ts
|
|
3637
3798
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3638
|
-
import { Command as
|
|
3799
|
+
import { Command as Command13 } from "commander";
|
|
3639
3800
|
|
|
3640
3801
|
// src/services/pr-client.ts
|
|
3641
3802
|
function buildPullRequestsUrl(context, repo, sourceBranch, opts) {
|
|
@@ -4340,11 +4501,11 @@ function branchTemplateSegments(branch) {
|
|
|
4340
4501
|
}
|
|
4341
4502
|
return segments;
|
|
4342
4503
|
}
|
|
4343
|
-
async function fetchRepositoryItemContent(context, repo, cred,
|
|
4504
|
+
async function fetchRepositoryItemContent(context, repo, cred, path5, branch) {
|
|
4344
4505
|
const url = new URL(
|
|
4345
4506
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/items`
|
|
4346
4507
|
);
|
|
4347
|
-
url.searchParams.set("path",
|
|
4508
|
+
url.searchParams.set("path", path5);
|
|
4348
4509
|
url.searchParams.set("versionDescriptor.version", branch);
|
|
4349
4510
|
url.searchParams.set("versionDescriptor.versionType", "branch");
|
|
4350
4511
|
url.searchParams.set("includeContent", "true");
|
|
@@ -4387,10 +4548,10 @@ function defaultTemplateCandidates() {
|
|
|
4387
4548
|
return paths;
|
|
4388
4549
|
}
|
|
4389
4550
|
async function firstMatchingTemplate(context, repo, cred, defaultBranch, candidates, kind) {
|
|
4390
|
-
for (const
|
|
4391
|
-
const content = await fetchRepositoryItemContent(context, repo, cred,
|
|
4551
|
+
for (const path5 of candidates) {
|
|
4552
|
+
const content = await fetchRepositoryItemContent(context, repo, cred, path5, defaultBranch);
|
|
4392
4553
|
if (content !== null) {
|
|
4393
|
-
return { path:
|
|
4554
|
+
return { path: path5, content, kind };
|
|
4394
4555
|
}
|
|
4395
4556
|
}
|
|
4396
4557
|
return null;
|
|
@@ -4763,7 +4924,7 @@ async function findStatusPullRequests(resolved, target) {
|
|
|
4763
4924
|
return { branch, pullRequests };
|
|
4764
4925
|
}
|
|
4765
4926
|
function createPrStatusCommand() {
|
|
4766
|
-
const command = new
|
|
4927
|
+
const command = new Command13("status");
|
|
4767
4928
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("Check pull requests for the current branch, another branch, or a specific pull request").option("--pr-number <id>", STATUS_PR_NUMBER_HELP).option("--branch <name>", STATUS_BRANCH_HELP).option("--json", "output JSON").action(async (options) => {
|
|
4768
4929
|
validateOrgProjectPair(options);
|
|
4769
4930
|
const target = parseStatusTarget(options);
|
|
@@ -4850,7 +5011,7 @@ function resolveOpenDescription(inline, file) {
|
|
|
4850
5011
|
return trimmedInline;
|
|
4851
5012
|
}
|
|
4852
5013
|
function createPrOpenCommand() {
|
|
4853
|
-
const command = new
|
|
5014
|
+
const command = new Command13("open");
|
|
4854
5015
|
withCommonPrOptions(command).description("Open a pull request from the current branch to develop").option("--title <title>", "pull request title").option(
|
|
4855
5016
|
"--description <description>",
|
|
4856
5017
|
"pull request description; when omitted, a repository-defined pull request template is used if one exists (prepended by this text when both are present)"
|
|
@@ -5018,7 +5179,7 @@ ${updated.url ?? "\u2014"}
|
|
|
5018
5179
|
}
|
|
5019
5180
|
}
|
|
5020
5181
|
function createPrUpdateCommand() {
|
|
5021
|
-
const command = new
|
|
5182
|
+
const command = new Command13("update");
|
|
5022
5183
|
withCommonPrOptions(configureUnwrappedHelp(command)).alias("edit").description("Update a pull request's title and/or description").option("--pr-number <N>", PR_NUMBER_HELP).option("--title <title>", "new pull request title").option("--title-file <path>", 'read the new title from a UTF-8 file; "-" reads standard input').option(
|
|
5023
5184
|
"--description <description>",
|
|
5024
5185
|
'new pull request description; REPLACES the existing description literally \u2014 no repository pull request template is looked up or prepended, unlike "azdo pr open"'
|
|
@@ -5106,7 +5267,7 @@ ${updated.url ?? "\u2014"}
|
|
|
5106
5267
|
}
|
|
5107
5268
|
}
|
|
5108
5269
|
function createPrAbandonCommand() {
|
|
5109
|
-
const command = new
|
|
5270
|
+
const command = new Command13("abandon");
|
|
5110
5271
|
withCommonPrOptions(configureUnwrappedHelp(command)).alias("close").description(
|
|
5111
5272
|
'Abandon a pull request (status: abandoned) \u2014 reversible with "azdo pr reactivate". This does NOT complete or merge the pull request; nothing is deleted and no confirmation is asked.'
|
|
5112
5273
|
).option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (_options, command2) => {
|
|
@@ -5115,7 +5276,7 @@ function createPrAbandonCommand() {
|
|
|
5115
5276
|
return command;
|
|
5116
5277
|
}
|
|
5117
5278
|
function createPrReactivateCommand() {
|
|
5118
|
-
const command = new
|
|
5279
|
+
const command = new Command13("reactivate");
|
|
5119
5280
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(
|
|
5120
5281
|
"Reactivate an abandoned pull request (status: active). When --pr-number is omitted the current branch's single ABANDONED pull request is used, not the active one."
|
|
5121
5282
|
).option("--pr-number <N>", PR_NUMBER_HELP_ABANDONED).option("--json", "output JSON").action(async (_options, command2) => {
|
|
@@ -5221,7 +5382,7 @@ function describeEmptyThreads(pullRequestId, allThreadCount, active) {
|
|
|
5221
5382
|
`;
|
|
5222
5383
|
}
|
|
5223
5384
|
function createPrCommentsCommand() {
|
|
5224
|
-
const command = new
|
|
5385
|
+
const command = new Command13("comments");
|
|
5225
5386
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("List pull request comment threads for the current branch").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("--exclude-system", "omit Azure DevOps system comments (branch updates, reviewer votes, build events)").option("--max-chars <N>", "truncate each comment body to N characters (0 = no limit, the default)").option("--thread <id>", "show only the thread with this numeric id; fails when the pull request has no such thread").option("--contains <text>", "show only threads holding a comment that contains this literal, case-sensitive substring (matched before --max-chars truncates)").option("--json", "output JSON").action(async (options) => {
|
|
5226
5387
|
validateOrgProjectPair(options);
|
|
5227
5388
|
const parsed = parseCommentsOptions(options);
|
|
@@ -5417,14 +5578,14 @@ async function runThreadStateChange(threadIdRaw, options, direction) {
|
|
|
5417
5578
|
}
|
|
5418
5579
|
}
|
|
5419
5580
|
function createPrCommentResolveCommand() {
|
|
5420
|
-
const command = new
|
|
5581
|
+
const command = new Command13("comment-resolve");
|
|
5421
5582
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("Mark a pull request comment thread as resolved").argument("<threadId>", "numeric id of the thread to resolve").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, options) => {
|
|
5422
5583
|
await runThreadStateChange(threadIdRaw, options, "resolve");
|
|
5423
5584
|
});
|
|
5424
5585
|
return command;
|
|
5425
5586
|
}
|
|
5426
5587
|
function createPrCommentReopenCommand() {
|
|
5427
|
-
const command = new
|
|
5588
|
+
const command = new Command13("comment-reopen");
|
|
5428
5589
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("Reopen (set to active) a previously resolved pull request comment thread").argument("<threadId>", "numeric id of the thread to reopen").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, options) => {
|
|
5429
5590
|
await runThreadStateChange(threadIdRaw, options, "reopen");
|
|
5430
5591
|
});
|
|
@@ -5476,7 +5637,7 @@ async function runCommentReply(threadIdRaw, text, options) {
|
|
|
5476
5637
|
}
|
|
5477
5638
|
}
|
|
5478
5639
|
function buildCommentReplyCommand(name, description) {
|
|
5479
|
-
const command = new
|
|
5640
|
+
const command = new Command13(name);
|
|
5480
5641
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("<threadId>", "numeric id of the thread to reply to").argument("[text]", "text of the reply; omit when using --file").option("--file <path>", "read the reply body from a UTF-8 file instead of the inline argument").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, text, _options, command2) => {
|
|
5481
5642
|
await runCommentReply(threadIdRaw, text, mergedPrOptions(command2));
|
|
5482
5643
|
});
|
|
@@ -5578,7 +5739,7 @@ ${body}
|
|
|
5578
5739
|
}
|
|
5579
5740
|
}
|
|
5580
5741
|
function buildCommentAddCommand(name, description) {
|
|
5581
|
-
const command = new
|
|
5742
|
+
const command = new Command13(name);
|
|
5582
5743
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("[text]", "body of the new comment; omit when using --file").option("--file <path>", "read the comment body from a UTF-8 file instead of the inline argument").option(
|
|
5583
5744
|
"--status <status>",
|
|
5584
5745
|
`thread status (${CREATABLE_THREAD_STATUSES.join(" | ")}); omit for a plain, non-resolvable comment`
|
|
@@ -5728,7 +5889,7 @@ async function runCommentEdit(threadIdRaw, text, options) {
|
|
|
5728
5889
|
}
|
|
5729
5890
|
}
|
|
5730
5891
|
function buildCommentEditCommand(name, description) {
|
|
5731
|
-
const command = new
|
|
5892
|
+
const command = new Command13(name);
|
|
5732
5893
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("<threadId>", "numeric id of the thread holding the comment").argument("[text]", "new comment body; omit when using --file").option("--comment-id <N>", "numeric id of the comment to edit; defaults to the thread's first comment").option("--file <path>", "read the new body from a UTF-8 file instead of the inline argument").option("--dry-run", "print the replacement body plus the current/new lengths, without writing anything (--json also returns previousContent)").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, text, _options, command2) => {
|
|
5733
5894
|
await runCommentEdit(threadIdRaw, text, mergedPrOptions(command2));
|
|
5734
5895
|
});
|
|
@@ -5829,7 +5990,7 @@ async function runCommentDelete(threadIdRaw, options) {
|
|
|
5829
5990
|
}
|
|
5830
5991
|
}
|
|
5831
5992
|
function buildCommentDeleteCommand(name, description) {
|
|
5832
|
-
const command = new
|
|
5993
|
+
const command = new Command13(name);
|
|
5833
5994
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("<threadId>", "numeric id of the thread holding the comment").option("--comment-id <N>", "numeric id of the comment to delete; may be omitted only when the thread holds a single comment").option("--dry-run", "resolve the target comment and print what would be deleted, without deleting anything").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (threadIdRaw, _options, command2) => {
|
|
5834
5995
|
await runCommentDelete(threadIdRaw, mergedPrOptions(command2));
|
|
5835
5996
|
});
|
|
@@ -5895,7 +6056,7 @@ async function attachWorkItemIds(context, repo, cred, pullRequests) {
|
|
|
5895
6056
|
return result;
|
|
5896
6057
|
}
|
|
5897
6058
|
function createPrListCommand() {
|
|
5898
|
-
const command = new
|
|
6059
|
+
const command = new Command13("list");
|
|
5899
6060
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("List pull requests in the repository, optionally filtered by source branch").option("--branch <name>", "only pull requests whose source branch is this one (with or without the refs/heads/ prefix)").option("--status <status>", `pull request status filter (${LIST_STATUS_VALUES.join(" | ")})`, "active").option("--top <N>", `maximum number of pull requests to return (default ${DEFAULT_LIST_TOP})`).option("--work-items", "also return the ids of each pull request's linked work items (one extra API call per pull request)").option("--json", "output JSON").action(async (options) => {
|
|
5900
6061
|
validateOrgProjectPair(options);
|
|
5901
6062
|
const parsed = parseListOptions(options);
|
|
@@ -5993,14 +6154,14 @@ async function runWorkItemLinkChange(workItemIdRaw, options, direction) {
|
|
|
5993
6154
|
}
|
|
5994
6155
|
}
|
|
5995
6156
|
function buildWorkItemLinkCommand(name, description, direction) {
|
|
5996
|
-
const command = new
|
|
6157
|
+
const command = new Command13(name);
|
|
5997
6158
|
withCommonPrOptions(configureUnwrappedHelp(command)).description(description).argument("<workItemId>", "numeric id of the work item").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (workItemIdRaw, _options, command2) => {
|
|
5998
6159
|
await runWorkItemLinkChange(workItemIdRaw, mergedPrOptions(command2), direction);
|
|
5999
6160
|
});
|
|
6000
6161
|
return command;
|
|
6001
6162
|
}
|
|
6002
6163
|
function createPrWorkItemsCommand() {
|
|
6003
|
-
const command = new
|
|
6164
|
+
const command = new Command13("work-items");
|
|
6004
6165
|
command.description("Manage work items linked to a pull request");
|
|
6005
6166
|
command.addCommand(buildWorkItemLinkCommand("link", "Link a work item to the pull request", "link"));
|
|
6006
6167
|
command.addCommand(buildWorkItemLinkCommand("unlink", "Unlink a work item from the pull request", "unlink"));
|
|
@@ -6145,19 +6306,19 @@ async function runReviewerList(options) {
|
|
|
6145
6306
|
}
|
|
6146
6307
|
}
|
|
6147
6308
|
function createPrReviewersCommand() {
|
|
6148
|
-
const command = new
|
|
6309
|
+
const command = new Command13("reviewers");
|
|
6149
6310
|
command.description("Manage pull request reviewers");
|
|
6150
|
-
const list = new
|
|
6311
|
+
const list = new Command13("list");
|
|
6151
6312
|
withCommonPrOptions(configureUnwrappedHelp(list)).description("List the pull request reviewers with their votes (approved, waiting-for-author, \u2026) and required flag").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (_options, command2) => {
|
|
6152
6313
|
await runReviewerList(mergedPrOptions(command2));
|
|
6153
6314
|
});
|
|
6154
6315
|
command.addCommand(list);
|
|
6155
|
-
const add = new
|
|
6316
|
+
const add = new Command13("add");
|
|
6156
6317
|
withCommonPrOptions(configureUnwrappedHelp(add)).description("Add a reviewer to the pull request (optional by default)").argument("<reviewer>", "reviewer email or unique name").option("--required", "mark the reviewer as required instead of optional").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (reviewer, _options, command2) => {
|
|
6157
6318
|
await runReviewerAdd(reviewer, mergedPrOptions(command2));
|
|
6158
6319
|
});
|
|
6159
6320
|
command.addCommand(add);
|
|
6160
|
-
const remove = new
|
|
6321
|
+
const remove = new Command13("remove");
|
|
6161
6322
|
withCommonPrOptions(configureUnwrappedHelp(remove)).description("Remove a reviewer from the pull request").argument("<reviewer>", "reviewer email or unique name").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (reviewer, _options, command2) => {
|
|
6162
6323
|
await runReviewerRemove(reviewer, mergedPrOptions(command2));
|
|
6163
6324
|
});
|
|
@@ -6165,7 +6326,7 @@ function createPrReviewersCommand() {
|
|
|
6165
6326
|
return command;
|
|
6166
6327
|
}
|
|
6167
6328
|
function createPrCommand() {
|
|
6168
|
-
const command = new
|
|
6329
|
+
const command = new Command13("pr");
|
|
6169
6330
|
command.description("Manage Azure DevOps pull requests");
|
|
6170
6331
|
command.addCommand(createPrListCommand());
|
|
6171
6332
|
command.addCommand(createPrStatusCommand());
|
|
@@ -6186,7 +6347,8 @@ function createPrCommand() {
|
|
|
6186
6347
|
}
|
|
6187
6348
|
|
|
6188
6349
|
// src/commands/pipeline.ts
|
|
6189
|
-
import { Command as
|
|
6350
|
+
import { Command as Command14 } from "commander";
|
|
6351
|
+
import path3 from "path";
|
|
6190
6352
|
|
|
6191
6353
|
// src/services/pipeline-client.ts
|
|
6192
6354
|
var API_VERSION = "7.1";
|
|
@@ -6310,6 +6472,9 @@ async function getBuildStatus(context, cred, buildId) {
|
|
|
6310
6472
|
const build = await getBuild(context, cred, buildId);
|
|
6311
6473
|
return { state: mapBuildState(build.status), result: mapRunResult(build.result) };
|
|
6312
6474
|
}
|
|
6475
|
+
function recordErrors(record) {
|
|
6476
|
+
return (record.issues ?? []).filter((issue) => issue.type === "error" && issue.message).map((issue) => ({ message: issue.message ?? "", source: record.name ?? null }));
|
|
6477
|
+
}
|
|
6313
6478
|
async function getBuildTimeline(context, cred, buildId) {
|
|
6314
6479
|
const url = withApiVersion(
|
|
6315
6480
|
new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}/timeline`)
|
|
@@ -6321,14 +6486,20 @@ async function getBuildTimeline(context, cred, buildId) {
|
|
|
6321
6486
|
const stages = [];
|
|
6322
6487
|
const jobs = [];
|
|
6323
6488
|
const logSteps = /* @__PURE__ */ new Map();
|
|
6489
|
+
const logRecords = /* @__PURE__ */ new Map();
|
|
6490
|
+
const namesById = /* @__PURE__ */ new Map();
|
|
6324
6491
|
for (const record of records) {
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
}
|
|
6492
|
+
if (record.id && record.name) namesById.set(record.id, record.name);
|
|
6493
|
+
}
|
|
6494
|
+
for (const record of records) {
|
|
6495
|
+
errors.push(...recordErrors(record));
|
|
6330
6496
|
if (record.name && record.log?.id !== void 0) {
|
|
6331
6497
|
logSteps.set(record.log.id, record.name);
|
|
6498
|
+
logRecords.set(record.log.id, {
|
|
6499
|
+
name: record.name,
|
|
6500
|
+
type: record.type ?? null,
|
|
6501
|
+
parent: record.parentId && namesById.get(record.parentId) || null
|
|
6502
|
+
});
|
|
6332
6503
|
}
|
|
6333
6504
|
if (!record.name) continue;
|
|
6334
6505
|
const status2 = {
|
|
@@ -6348,7 +6519,7 @@ async function getBuildTimeline(context, cred, buildId) {
|
|
|
6348
6519
|
if (b.startTime === void 0) return -1;
|
|
6349
6520
|
return a.startTime < b.startTime ? -1 : 1;
|
|
6350
6521
|
});
|
|
6351
|
-
return { errors, stages, jobs: jobs.map((j) => j.status), logSteps };
|
|
6522
|
+
return { errors, stages, jobs: jobs.map((j) => j.status), logSteps, logRecords };
|
|
6352
6523
|
}
|
|
6353
6524
|
async function listTestRuns(context, cred, buildId) {
|
|
6354
6525
|
const url = withApiVersion(new URL(`${orgProjectBase(context)}/_apis/test/runs`));
|
|
@@ -6457,16 +6628,18 @@ async function getRunLogs(context, cred, buildId) {
|
|
|
6457
6628
|
);
|
|
6458
6629
|
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
6459
6630
|
const data = await readJsonResponse2(response);
|
|
6460
|
-
let
|
|
6631
|
+
let logRecords = /* @__PURE__ */ new Map();
|
|
6461
6632
|
try {
|
|
6462
|
-
|
|
6633
|
+
logRecords = (await getBuildTimeline(context, cred, buildId)).logRecords;
|
|
6463
6634
|
} catch {
|
|
6464
6635
|
}
|
|
6465
6636
|
return data.value.map((log) => ({
|
|
6466
6637
|
id: log.id,
|
|
6467
6638
|
createdOn: log.createdOn ?? null,
|
|
6468
6639
|
lineCount: log.lineCount ?? null,
|
|
6469
|
-
step:
|
|
6640
|
+
step: logRecords.get(log.id)?.name ?? null,
|
|
6641
|
+
type: logRecords.get(log.id)?.type ?? null,
|
|
6642
|
+
parent: logRecords.get(log.id)?.parent ?? null
|
|
6470
6643
|
}));
|
|
6471
6644
|
}
|
|
6472
6645
|
async function getRunLog(context, cred, buildId, logId) {
|
|
@@ -6479,6 +6652,108 @@ async function getRunLog(context, cred, buildId, logId) {
|
|
|
6479
6652
|
}
|
|
6480
6653
|
return response.text();
|
|
6481
6654
|
}
|
|
6655
|
+
async function listBuildArtifacts(context, cred, buildId) {
|
|
6656
|
+
const url = withApiVersion(
|
|
6657
|
+
new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}/artifacts`)
|
|
6658
|
+
);
|
|
6659
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
6660
|
+
const data = await readJsonResponse2(response);
|
|
6661
|
+
return data.value.map((artifact) => {
|
|
6662
|
+
const size = Number.parseInt(artifact.resource?.properties?.artifactsize ?? "", 10);
|
|
6663
|
+
return {
|
|
6664
|
+
id: artifact.id,
|
|
6665
|
+
name: artifact.name,
|
|
6666
|
+
type: artifact.resource?.type ?? null,
|
|
6667
|
+
sizeBytes: Number.isFinite(size) ? size : null,
|
|
6668
|
+
downloadUrl: artifact.resource?.downloadUrl ?? null
|
|
6669
|
+
};
|
|
6670
|
+
});
|
|
6671
|
+
}
|
|
6672
|
+
async function downloadArtifactZip(cred, artifact, onProgress) {
|
|
6673
|
+
if (!artifact.downloadUrl) {
|
|
6674
|
+
throw new Error(`Artifact "${artifact.name}" (${artifact.type ?? "unknown type"}) has no download URL.`);
|
|
6675
|
+
}
|
|
6676
|
+
const url = new URL(artifact.downloadUrl);
|
|
6677
|
+
url.searchParams.set("$format", "zip");
|
|
6678
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
6679
|
+
if (!response.ok) {
|
|
6680
|
+
throw httpError(response);
|
|
6681
|
+
}
|
|
6682
|
+
if (!response.body) {
|
|
6683
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
6684
|
+
}
|
|
6685
|
+
const chunks = [];
|
|
6686
|
+
let received = 0;
|
|
6687
|
+
for await (const chunk of response.body) {
|
|
6688
|
+
chunks.push(chunk);
|
|
6689
|
+
received += chunk.length;
|
|
6690
|
+
onProgress?.(received);
|
|
6691
|
+
}
|
|
6692
|
+
return Buffer.concat(chunks);
|
|
6693
|
+
}
|
|
6694
|
+
|
|
6695
|
+
// src/services/artifact-extract.ts
|
|
6696
|
+
import { existsSync as existsSync6, lstatSync, mkdirSync, realpathSync, writeFileSync } from "fs";
|
|
6697
|
+
import path2 from "path";
|
|
6698
|
+
import { unzipSync } from "fflate";
|
|
6699
|
+
function entryTarget(entry, artifactName) {
|
|
6700
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
6701
|
+
if (normalized.endsWith("/")) return null;
|
|
6702
|
+
const prefix = `${artifactName}/`;
|
|
6703
|
+
const relative = normalized.startsWith(prefix) ? normalized.slice(prefix.length) : normalized;
|
|
6704
|
+
const segments = relative.split("/");
|
|
6705
|
+
if (relative.startsWith("/") || /^[A-Za-z]:/.test(relative) || segments.includes("..")) {
|
|
6706
|
+
throw new Error(`Refusing to extract "${entry}": path escapes the destination folder.`);
|
|
6707
|
+
}
|
|
6708
|
+
return relative;
|
|
6709
|
+
}
|
|
6710
|
+
function assertNoSymlinkEscape(root, relative) {
|
|
6711
|
+
let current = root;
|
|
6712
|
+
for (const segment of relative.split("/")) {
|
|
6713
|
+
current = path2.join(current, segment);
|
|
6714
|
+
let isLink;
|
|
6715
|
+
try {
|
|
6716
|
+
isLink = lstatSync(current).isSymbolicLink();
|
|
6717
|
+
} catch {
|
|
6718
|
+
return;
|
|
6719
|
+
}
|
|
6720
|
+
if (isLink) {
|
|
6721
|
+
throw new Error(`Refusing to extract through symbolic link "${current}".`);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
function extractArtifactZip(zip, destination, options) {
|
|
6726
|
+
const entries = unzipSync(zip);
|
|
6727
|
+
const targets = [];
|
|
6728
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6729
|
+
for (const [entry, data] of Object.entries(entries)) {
|
|
6730
|
+
const relative = entryTarget(entry, options.artifactName);
|
|
6731
|
+
if (relative === null) continue;
|
|
6732
|
+
const key = path2.posix.normalize(relative);
|
|
6733
|
+
if (seen.has(key)) {
|
|
6734
|
+
throw new Error(`Refusing to extract: several archive entries resolve to "${key}".`);
|
|
6735
|
+
}
|
|
6736
|
+
seen.add(key);
|
|
6737
|
+
targets.push({ relative: key, data });
|
|
6738
|
+
}
|
|
6739
|
+
if (!options.force) {
|
|
6740
|
+
const existing = targets.find((t) => existsSync6(path2.join(destination, t.relative)));
|
|
6741
|
+
if (existing) {
|
|
6742
|
+
throw new Error(
|
|
6743
|
+
`"${path2.join(destination, existing.relative)}" already exists. Use --force to overwrite.`
|
|
6744
|
+
);
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
mkdirSync(destination, { recursive: true });
|
|
6748
|
+
const root = realpathSync(destination);
|
|
6749
|
+
for (const { relative } of targets) assertNoSymlinkEscape(root, relative);
|
|
6750
|
+
for (const { relative, data } of targets) {
|
|
6751
|
+
const file = path2.join(root, relative);
|
|
6752
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
6753
|
+
writeFileSync(file, data);
|
|
6754
|
+
}
|
|
6755
|
+
return targets.map((t) => t.relative);
|
|
6756
|
+
}
|
|
6482
6757
|
|
|
6483
6758
|
// src/commands/pipeline.ts
|
|
6484
6759
|
var EXIT_FAILED = 1;
|
|
@@ -6561,7 +6836,7 @@ function formatTable(rows, rightAlign = /* @__PURE__ */ new Set()) {
|
|
|
6561
6836
|
).join("\n");
|
|
6562
6837
|
}
|
|
6563
6838
|
function createPipelineListCommand() {
|
|
6564
|
-
const command = new
|
|
6839
|
+
const command = new Command14("list");
|
|
6565
6840
|
command.description("List Azure DevOps pipeline definitions").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--filter <name>", "filter definitions by name (case-insensitive substring)").option("--json", "output JSON").action(async (options) => {
|
|
6566
6841
|
validateOrgProjectPair(options);
|
|
6567
6842
|
let context;
|
|
@@ -6633,7 +6908,7 @@ function parseGetRunsInputs(defIdRaw, options) {
|
|
|
6633
6908
|
return { defId, limit: limit ?? 10, prNumber };
|
|
6634
6909
|
}
|
|
6635
6910
|
function createPipelineGetRunsCommand() {
|
|
6636
|
-
const command = new
|
|
6911
|
+
const command = new Command14("get-runs");
|
|
6637
6912
|
command.description("List recent runs for a pipeline definition (newest first)").argument("[def_id]", "pipeline definition id (optional with --commit or --pr)").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--limit <n>", "maximum number of runs to show (default 10)").option("--branch <branch>", "only show runs for this source branch").option("--commit <sha>", "only show runs that built this commit (full or abbreviated SHA)").option("--pr <number>", "only show runs for this pull request").option("--json", "output JSON").action(async (defIdRaw, options) => {
|
|
6638
6913
|
validateOrgProjectPair(options);
|
|
6639
6914
|
const inputs = parseGetRunsInputs(defIdRaw, options);
|
|
@@ -6688,7 +6963,7 @@ function applyWaitExitCode(result) {
|
|
|
6688
6963
|
}
|
|
6689
6964
|
}
|
|
6690
6965
|
function createPipelineWaitCommand() {
|
|
6691
|
-
const command = new
|
|
6966
|
+
const command = new Command14("wait");
|
|
6692
6967
|
command.description("Wait for a pipeline run to finish; exit code reflects the result (0 success, non-zero otherwise)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--timeout <seconds>", "maximum seconds to wait (default 1800)").option("--poll-interval <seconds>", "seconds between status checks (default 5)").option("--json", "output JSON").action(async (runIdRaw, options) => {
|
|
6693
6968
|
validateOrgProjectPair(options);
|
|
6694
6969
|
const runId = parsePositiveId(runIdRaw);
|
|
@@ -6815,19 +7090,26 @@ function formatRunDetail(detail) {
|
|
|
6815
7090
|
...testRows(detail)
|
|
6816
7091
|
].join("\n");
|
|
6817
7092
|
}
|
|
7093
|
+
async function withRunContext(runIdRaw, options, body) {
|
|
7094
|
+
validateOrgProjectPair(options);
|
|
7095
|
+
const runId = parsePositiveId(runIdRaw);
|
|
7096
|
+
if (runId === null) {
|
|
7097
|
+
writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
|
|
7098
|
+
return;
|
|
7099
|
+
}
|
|
7100
|
+
let context;
|
|
7101
|
+
try {
|
|
7102
|
+
const resolved = await resolvePipelineContext(options);
|
|
7103
|
+
context = resolved.context;
|
|
7104
|
+
await body(runId, resolved);
|
|
7105
|
+
} catch (err) {
|
|
7106
|
+
handlePipelineError(err, context);
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
6818
7109
|
function createPipelineGetRunDetailCommand() {
|
|
6819
|
-
const command = new
|
|
6820
|
-
command.description("Show a detailed summary of a single pipeline run (errors, failing tests, stages)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(
|
|
6821
|
-
|
|
6822
|
-
const runId = parsePositiveId(runIdRaw);
|
|
6823
|
-
if (runId === null) {
|
|
6824
|
-
writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
|
|
6825
|
-
return;
|
|
6826
|
-
}
|
|
6827
|
-
let context;
|
|
6828
|
-
try {
|
|
6829
|
-
const resolved = await resolvePipelineContext(options);
|
|
6830
|
-
context = resolved.context;
|
|
7110
|
+
const command = new Command14("get-run-detail");
|
|
7111
|
+
command.description("Show a detailed summary of a single pipeline run (errors, failing tests, stages)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(
|
|
7112
|
+
(runIdRaw, options) => withRunContext(runIdRaw, options, async (runId, resolved) => {
|
|
6831
7113
|
const detail = await getRunDetail(resolved.context, resolved.cred, runId);
|
|
6832
7114
|
if (options.json) {
|
|
6833
7115
|
process.stdout.write(`${JSON.stringify(detail, null, 2)}
|
|
@@ -6836,10 +7118,8 @@ function createPipelineGetRunDetailCommand() {
|
|
|
6836
7118
|
}
|
|
6837
7119
|
process.stdout.write(`${formatRunDetail(detail)}
|
|
6838
7120
|
`);
|
|
6839
|
-
}
|
|
6840
|
-
|
|
6841
|
-
}
|
|
6842
|
-
});
|
|
7121
|
+
})
|
|
7122
|
+
);
|
|
6843
7123
|
return command;
|
|
6844
7124
|
}
|
|
6845
7125
|
function grepWithContext(lines, grep, context) {
|
|
@@ -6862,17 +7142,29 @@ function grepWithContext(lines, grep, context) {
|
|
|
6862
7142
|
}
|
|
6863
7143
|
return selected;
|
|
6864
7144
|
}
|
|
6865
|
-
function
|
|
7145
|
+
function collapseProgress(line) {
|
|
7146
|
+
let end = line.length;
|
|
7147
|
+
while (end > 0 && line[end - 1] === "\r") end--;
|
|
7148
|
+
return line.slice(line.lastIndexOf("\r", end - 1) + 1, end);
|
|
7149
|
+
}
|
|
7150
|
+
function filterLogLines(content, filters) {
|
|
7151
|
+
const { grep, tail, head, contextLines: context } = filters;
|
|
6866
7152
|
let lines = content.split("\n");
|
|
6867
7153
|
if (lines.at(-1) === "") {
|
|
6868
7154
|
lines.pop();
|
|
6869
7155
|
}
|
|
7156
|
+
if (filters.noProgress) {
|
|
7157
|
+
lines = lines.map(collapseProgress);
|
|
7158
|
+
}
|
|
6870
7159
|
if (grep) {
|
|
6871
7160
|
lines = context > 0 ? grepWithContext(lines, grep, context) : lines.filter((line) => grep.test(line));
|
|
6872
7161
|
}
|
|
6873
7162
|
if (tail !== void 0 && lines.length > tail) {
|
|
6874
7163
|
lines = lines.slice(-tail);
|
|
6875
7164
|
}
|
|
7165
|
+
if (head !== void 0 && lines.length > head) {
|
|
7166
|
+
lines = lines.slice(0, head);
|
|
7167
|
+
}
|
|
6876
7168
|
return lines;
|
|
6877
7169
|
}
|
|
6878
7170
|
function parseLogFilters(options) {
|
|
@@ -6881,9 +7173,14 @@ function parseLogFilters(options) {
|
|
|
6881
7173
|
return null;
|
|
6882
7174
|
}
|
|
6883
7175
|
const selectsSingleLog = options.logId !== void 0 || options.step !== void 0;
|
|
6884
|
-
const
|
|
7176
|
+
const noProgress = options.progress === false;
|
|
7177
|
+
const slices = options.tail !== void 0 || options.head !== void 0 || options.grep !== void 0 || options.context !== void 0 || noProgress;
|
|
6885
7178
|
if (slices && !selectsSingleLog) {
|
|
6886
|
-
writeError2("--tail, --grep, and --
|
|
7179
|
+
writeError2("--tail, --head, --grep, --context, and --no-progress require --log-id or --step.");
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
7182
|
+
if (options.head !== void 0 && options.tail !== void 0) {
|
|
7183
|
+
writeError2("Use either --head or --tail, not both.");
|
|
6887
7184
|
return null;
|
|
6888
7185
|
}
|
|
6889
7186
|
if (options.context !== void 0 && options.grep === void 0) {
|
|
@@ -6892,6 +7189,8 @@ function parseLogFilters(options) {
|
|
|
6892
7189
|
}
|
|
6893
7190
|
const tail = parseOptionalCount(options.tail, "--tail");
|
|
6894
7191
|
if (tail === null) return null;
|
|
7192
|
+
const head = parseOptionalCount(options.head, "--head");
|
|
7193
|
+
if (head === null) return null;
|
|
6895
7194
|
const contextLines = parseOptionalCount(options.context, "--context");
|
|
6896
7195
|
if (contextLines === null) return null;
|
|
6897
7196
|
let grep;
|
|
@@ -6903,7 +7202,13 @@ function parseLogFilters(options) {
|
|
|
6903
7202
|
return null;
|
|
6904
7203
|
}
|
|
6905
7204
|
}
|
|
6906
|
-
return { tail, contextLines: contextLines ?? 0, grep };
|
|
7205
|
+
return { tail, head, noProgress, contextLines: contextLines ?? 0, grep };
|
|
7206
|
+
}
|
|
7207
|
+
function describeLog(log) {
|
|
7208
|
+
const kind = log.type ?? "";
|
|
7209
|
+
const where = log.parent ? ` in ${log.parent}` : "";
|
|
7210
|
+
const detail = `${kind}${where}`.trim();
|
|
7211
|
+
return detail ? `${log.step} [${detail}]` : String(log.step);
|
|
6907
7212
|
}
|
|
6908
7213
|
function chooseStepLog(logs, step, runId) {
|
|
6909
7214
|
const needle = step.toLowerCase();
|
|
@@ -6915,7 +7220,7 @@ function chooseStepLog(logs, step, runId) {
|
|
|
6915
7220
|
return null;
|
|
6916
7221
|
}
|
|
6917
7222
|
if (chosen.length > 1) {
|
|
6918
|
-
const candidates = chosen.map((l) => `${l.id} (${l
|
|
7223
|
+
const candidates = chosen.map((l) => `${l.id} (${describeLog(l)})`).join(", ");
|
|
6919
7224
|
writeError2(`Step "${step}" matches multiple logs: ${candidates}. Be more specific or use --log-id.`);
|
|
6920
7225
|
return null;
|
|
6921
7226
|
}
|
|
@@ -6937,8 +7242,8 @@ async function resolveRequestedLogId(resolved, runId, options) {
|
|
|
6937
7242
|
return chooseStepLog(allLogs, options.step, runId);
|
|
6938
7243
|
}
|
|
6939
7244
|
function printSingleLog(content, filters) {
|
|
6940
|
-
if (filters.grep !== void 0 || filters.tail !== void 0) {
|
|
6941
|
-
const lines = filterLogLines(content, filters
|
|
7245
|
+
if (filters.grep !== void 0 || filters.tail !== void 0 || filters.head !== void 0 || filters.noProgress) {
|
|
7246
|
+
const lines = filterLogLines(content, filters);
|
|
6942
7247
|
if (lines.length > 0) {
|
|
6943
7248
|
process.stdout.write(`${lines.join("\n")}
|
|
6944
7249
|
`);
|
|
@@ -6949,8 +7254,8 @@ function printSingleLog(content, filters) {
|
|
|
6949
7254
|
`);
|
|
6950
7255
|
}
|
|
6951
7256
|
function createPipelineLogsCommand() {
|
|
6952
|
-
const command = new
|
|
6953
|
-
command.description("List a pipeline run's logs, or print a specific log with --log-id").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--log-id <id>", "print the content of this log id").option("--step <name>", "print the log of the step/job matching this name (case-insensitive substring)").option("--tail <n>", "with --log-id/--step, print only the last N lines").option("--grep <pattern>", "with --log-id/--step, print only lines matching this regular expression").option("--context <n>", "with --grep, also print N lines around each match (grep -C)").option("--json", "output JSON").action(async (runIdRaw, options) => {
|
|
7257
|
+
const command = new Command14("logs");
|
|
7258
|
+
command.description("List a pipeline run's logs, or print a specific log with --log-id").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--log-id <id>", "print the content of this log id").option("--step <name>", "print the log of the step/job matching this name (case-insensitive substring)").option("--tail <n>", "with --log-id/--step, print only the last N lines").option("--head <n>", "with --log-id/--step, print only the first N lines").option("--grep <pattern>", "with --log-id/--step, print only lines matching this regular expression").option("--context <n>", "with --grep, also print N lines around each match (grep -C)").option("--no-progress", "with --log-id/--step, collapse carriage-return progress redraws to their final state").option("--json", "output JSON").action(async (runIdRaw, options) => {
|
|
6954
7259
|
validateOrgProjectPair(options);
|
|
6955
7260
|
const runId = parsePositiveId(runIdRaw);
|
|
6956
7261
|
if (runId === null) {
|
|
@@ -6989,7 +7294,9 @@ function createPipelineLogsCommand() {
|
|
|
6989
7294
|
String(l.id),
|
|
6990
7295
|
l.createdOn ?? "\u2014",
|
|
6991
7296
|
l.lineCount == null ? "" : `${l.lineCount} lines`,
|
|
6992
|
-
l.
|
|
7297
|
+
l.type ?? "",
|
|
7298
|
+
l.step ?? "",
|
|
7299
|
+
l.parent ? `(in ${l.parent})` : ""
|
|
6993
7300
|
]);
|
|
6994
7301
|
process.stdout.write(`${formatTable(rows, /* @__PURE__ */ new Set([0]))}
|
|
6995
7302
|
`);
|
|
@@ -6999,8 +7306,104 @@ function createPipelineLogsCommand() {
|
|
|
6999
7306
|
});
|
|
7000
7307
|
return command;
|
|
7001
7308
|
}
|
|
7309
|
+
function formatSize(bytes) {
|
|
7310
|
+
if (bytes === null) return "";
|
|
7311
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
7312
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
7313
|
+
let value = bytes / 1024;
|
|
7314
|
+
let unit = 0;
|
|
7315
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
7316
|
+
value /= 1024;
|
|
7317
|
+
unit++;
|
|
7318
|
+
}
|
|
7319
|
+
return `${value.toFixed(1)} ${units[unit]}`;
|
|
7320
|
+
}
|
|
7321
|
+
function createPipelineArtifactsCommand() {
|
|
7322
|
+
const command = new Command14("artifacts");
|
|
7323
|
+
command.description("List the build artifacts published by a pipeline run").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(
|
|
7324
|
+
(runIdRaw, options) => withRunContext(runIdRaw, options, async (runId, resolved) => {
|
|
7325
|
+
const artifacts = await listBuildArtifacts(resolved.context, resolved.cred, runId);
|
|
7326
|
+
if (options.json) {
|
|
7327
|
+
process.stdout.write(`${JSON.stringify(artifacts, null, 2)}
|
|
7328
|
+
`);
|
|
7329
|
+
return;
|
|
7330
|
+
}
|
|
7331
|
+
if (artifacts.length === 0) {
|
|
7332
|
+
process.stdout.write(`No artifacts found for run ${runId}.
|
|
7333
|
+
`);
|
|
7334
|
+
return;
|
|
7335
|
+
}
|
|
7336
|
+
const rows = artifacts.map((a) => [a.name, a.type ?? "", formatSize(a.sizeBytes)]);
|
|
7337
|
+
process.stdout.write(`${formatTable(rows)}
|
|
7338
|
+
`);
|
|
7339
|
+
})
|
|
7340
|
+
);
|
|
7341
|
+
return command;
|
|
7342
|
+
}
|
|
7343
|
+
function selectArtifacts(artifacts, name, all, runId) {
|
|
7344
|
+
const available = artifacts.map((a) => a.name).join(", ") || "(none)";
|
|
7345
|
+
if (all) return artifacts;
|
|
7346
|
+
if (name === void 0) {
|
|
7347
|
+
writeError2(`Give an artifact name or --all. Available artifacts in run ${runId}: ${available}.`);
|
|
7348
|
+
return null;
|
|
7349
|
+
}
|
|
7350
|
+
const match = artifacts.find((a) => a.name === name);
|
|
7351
|
+
if (!match) {
|
|
7352
|
+
writeError2(`Run ${runId} has no artifact "${name}". Available artifacts: ${available}.`);
|
|
7353
|
+
return null;
|
|
7354
|
+
}
|
|
7355
|
+
return [match];
|
|
7356
|
+
}
|
|
7357
|
+
function createPipelineArtifactDownloadCommand() {
|
|
7358
|
+
const command = new Command14("artifact-download");
|
|
7359
|
+
command.description("Download a run's artifact (or --all) and extract it into a folder; no zip is left behind").argument("<run_id>", "pipeline run id").argument("[name]", "artifact name (see `pipeline artifacts`)").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--path <dir>", "destination folder (default ./<name>; with --all, <dir>/<name> per artifact)").option("--all", "download every artifact of the run").option("--force", "overwrite files that already exist").option("--progress", "print byte-progress lines on stderr").option("--json", "output JSON").action(async (runIdRaw, name, options) => {
|
|
7360
|
+
validateOrgProjectPair(options);
|
|
7361
|
+
const runId = parsePositiveId(runIdRaw);
|
|
7362
|
+
if (runId === null) {
|
|
7363
|
+
writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
|
|
7364
|
+
return;
|
|
7365
|
+
}
|
|
7366
|
+
if (options.all && name !== void 0) {
|
|
7367
|
+
writeError2("Use either an artifact name or --all, not both.");
|
|
7368
|
+
return;
|
|
7369
|
+
}
|
|
7370
|
+
let context;
|
|
7371
|
+
try {
|
|
7372
|
+
const resolved = await resolvePipelineContext(options);
|
|
7373
|
+
context = resolved.context;
|
|
7374
|
+
const artifacts = await listBuildArtifacts(resolved.context, resolved.cred, runId);
|
|
7375
|
+
const selected = selectArtifacts(artifacts, name, options.all === true, runId);
|
|
7376
|
+
if (selected === null) return;
|
|
7377
|
+
const results = await Promise.all(
|
|
7378
|
+
selected.map(async (artifact) => {
|
|
7379
|
+
const destination = path3.resolve(
|
|
7380
|
+
options.all ? path3.join(options.path ?? ".", artifact.name) : options.path ?? artifact.name
|
|
7381
|
+
);
|
|
7382
|
+
const onProgress = options.progress ? (bytes) => process.stderr.write(`${artifact.name}: ${bytes} bytes
|
|
7383
|
+
`) : void 0;
|
|
7384
|
+
const zip = await downloadArtifactZip(resolved.cred, artifact, onProgress);
|
|
7385
|
+
const files = extractArtifactZip(zip, destination, {
|
|
7386
|
+
artifactName: artifact.name,
|
|
7387
|
+
force: options.force === true
|
|
7388
|
+
});
|
|
7389
|
+
return { name: artifact.name, path: destination, files: files.length };
|
|
7390
|
+
})
|
|
7391
|
+
);
|
|
7392
|
+
if (options.json) {
|
|
7393
|
+
process.stdout.write(`${JSON.stringify(results, null, 2)}
|
|
7394
|
+
`);
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
process.stdout.write(`${results.map((r) => r.path).join("\n")}
|
|
7398
|
+
`);
|
|
7399
|
+
} catch (err) {
|
|
7400
|
+
handlePipelineError(err, context);
|
|
7401
|
+
}
|
|
7402
|
+
});
|
|
7403
|
+
return command;
|
|
7404
|
+
}
|
|
7002
7405
|
function createPipelineTestsCommand() {
|
|
7003
|
-
const command = new
|
|
7406
|
+
const command = new Command14("tests");
|
|
7004
7407
|
command.description("Show a run's test results: summary plus failing tests by name (no log grepping needed)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--failed", "list only the failing tests").option("--json", "output JSON").action(async (runIdRaw, options) => {
|
|
7005
7408
|
validateOrgProjectPair(options);
|
|
7006
7409
|
const runId = parsePositiveId(runIdRaw);
|
|
@@ -7057,7 +7460,7 @@ function collectParameter(value, previous) {
|
|
|
7057
7460
|
return previous.concat([value]);
|
|
7058
7461
|
}
|
|
7059
7462
|
function createPipelineStartCommand() {
|
|
7060
|
-
const command = new
|
|
7463
|
+
const command = new Command14("start");
|
|
7061
7464
|
command.description("Queue a new run of a pipeline definition").argument("<def_id>", "pipeline definition id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--branch <branch>", "branch to run against (default: pipeline default branch)").option("--parameter <key=value>", "template parameter (repeatable)", collectParameter, []).option("--json", "output JSON").action(async (defIdRaw, options) => {
|
|
7062
7465
|
validateOrgProjectPair(options);
|
|
7063
7466
|
const defId = parsePositiveId(defIdRaw);
|
|
@@ -7093,20 +7496,22 @@ ${result.webUrl ?? "\u2014"}
|
|
|
7093
7496
|
return command;
|
|
7094
7497
|
}
|
|
7095
7498
|
function createPipelineCommand() {
|
|
7096
|
-
const command = new
|
|
7499
|
+
const command = new Command14("pipeline");
|
|
7097
7500
|
command.description("Manage Azure DevOps pipelines");
|
|
7098
7501
|
command.addCommand(createPipelineListCommand());
|
|
7099
7502
|
command.addCommand(createPipelineGetRunsCommand());
|
|
7100
7503
|
command.addCommand(createPipelineWaitCommand());
|
|
7101
7504
|
command.addCommand(createPipelineGetRunDetailCommand());
|
|
7102
7505
|
command.addCommand(createPipelineLogsCommand());
|
|
7506
|
+
command.addCommand(createPipelineArtifactsCommand());
|
|
7507
|
+
command.addCommand(createPipelineArtifactDownloadCommand());
|
|
7103
7508
|
command.addCommand(createPipelineTestsCommand());
|
|
7104
7509
|
command.addCommand(createPipelineStartCommand());
|
|
7105
7510
|
return command;
|
|
7106
7511
|
}
|
|
7107
7512
|
|
|
7108
7513
|
// src/commands/comments.ts
|
|
7109
|
-
import { Command as
|
|
7514
|
+
import { Command as Command15 } from "commander";
|
|
7110
7515
|
function writeError3(message) {
|
|
7111
7516
|
process.stderr.write(`Error: ${message}
|
|
7112
7517
|
`);
|
|
@@ -7126,7 +7531,7 @@ function formatComments(result, convertMarkdown) {
|
|
|
7126
7531
|
return lines.join("\n");
|
|
7127
7532
|
}
|
|
7128
7533
|
function createCommentsListCommand() {
|
|
7129
|
-
const command = new
|
|
7534
|
+
const command = new Command15("list");
|
|
7130
7535
|
command.description("List visible comments for a work item").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "convert HTML comment bodies to markdown").action(async (idStr, options) => {
|
|
7131
7536
|
validateOrgProjectPair(options);
|
|
7132
7537
|
const id = parseWorkItemId(idStr);
|
|
@@ -7154,7 +7559,7 @@ function createCommentsListCommand() {
|
|
|
7154
7559
|
return command;
|
|
7155
7560
|
}
|
|
7156
7561
|
function createCommentsAddCommand() {
|
|
7157
|
-
const command = new
|
|
7562
|
+
const command = new Command15("add");
|
|
7158
7563
|
command.description("Add a comment to a work item").argument("<id>", "work item ID").argument("<text>", "comment text").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "post comment as markdown").action(async (idStr, text, options) => {
|
|
7159
7564
|
validateOrgProjectPair(options);
|
|
7160
7565
|
const id = parseWorkItemId(idStr);
|
|
@@ -7181,7 +7586,7 @@ function createCommentsAddCommand() {
|
|
|
7181
7586
|
return command;
|
|
7182
7587
|
}
|
|
7183
7588
|
function createCommentsCommand() {
|
|
7184
|
-
const command = new
|
|
7589
|
+
const command = new Command15("comments");
|
|
7185
7590
|
command.description("Manage Azure DevOps work item comments");
|
|
7186
7591
|
command.addCommand(createCommentsListCommand());
|
|
7187
7592
|
command.addCommand(createCommentsAddCommand());
|
|
@@ -7189,12 +7594,12 @@ function createCommentsCommand() {
|
|
|
7189
7594
|
}
|
|
7190
7595
|
|
|
7191
7596
|
// src/commands/download-attachment.ts
|
|
7192
|
-
import { Command as
|
|
7597
|
+
import { Command as Command16 } from "commander";
|
|
7193
7598
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
7194
|
-
import { existsSync as
|
|
7599
|
+
import { existsSync as existsSync7 } from "fs";
|
|
7195
7600
|
import { join as join3 } from "path";
|
|
7196
7601
|
function createDownloadAttachmentCommand() {
|
|
7197
|
-
const command = new
|
|
7602
|
+
const command = new Command16("download-attachment");
|
|
7198
7603
|
command.description("Download an attachment from an Azure DevOps work item").argument("<id>", "work item ID").argument("<filename>", "name of the attachment to download").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--output <dir>", "target directory for the downloaded file").action(
|
|
7199
7604
|
async (idStr, filename, options) => {
|
|
7200
7605
|
const id = parseWorkItemId(idStr);
|
|
@@ -7204,7 +7609,7 @@ function createDownloadAttachmentCommand() {
|
|
|
7204
7609
|
context = resolveContext(options);
|
|
7205
7610
|
const credential = await requireAuthCredential(context.org);
|
|
7206
7611
|
const outputDir = options.output ?? ".";
|
|
7207
|
-
if (!
|
|
7612
|
+
if (!existsSync7(outputDir)) {
|
|
7208
7613
|
process.stderr.write(`Error: Output directory "${outputDir}" does not exist.
|
|
7209
7614
|
`);
|
|
7210
7615
|
process.exit(1);
|
|
@@ -7236,17 +7641,17 @@ function createDownloadAttachmentCommand() {
|
|
|
7236
7641
|
}
|
|
7237
7642
|
|
|
7238
7643
|
// src/commands/add-attachment.ts
|
|
7239
|
-
import { Command as
|
|
7240
|
-
import { existsSync as
|
|
7644
|
+
import { Command as Command17 } from "commander";
|
|
7645
|
+
import { existsSync as existsSync8, statSync } from "fs";
|
|
7241
7646
|
import { readFile } from "fs/promises";
|
|
7242
7647
|
import { basename } from "path";
|
|
7243
7648
|
function createAddAttachmentCommand() {
|
|
7244
|
-
const command = new
|
|
7649
|
+
const command = new Command17("add-attachment");
|
|
7245
7650
|
command.description("Attach a local file to an Azure DevOps work item").argument("<id>", "work item ID").argument("<file>", "path to the local file to upload").option("--comment <text>", "optional comment to store with the attachment").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").action(
|
|
7246
7651
|
async (idStr, file, options) => {
|
|
7247
7652
|
const id = parseWorkItemId(idStr);
|
|
7248
7653
|
validateOrgProjectPair(options);
|
|
7249
|
-
if (!
|
|
7654
|
+
if (!existsSync8(file)) {
|
|
7250
7655
|
process.stderr.write(`Error: File not found: ${file}
|
|
7251
7656
|
`);
|
|
7252
7657
|
process.exit(1);
|
|
@@ -7288,12 +7693,12 @@ function createAddAttachmentCommand() {
|
|
|
7288
7693
|
}
|
|
7289
7694
|
|
|
7290
7695
|
// src/commands/delete-attachment.ts
|
|
7291
|
-
import { Command as
|
|
7696
|
+
import { Command as Command18 } from "commander";
|
|
7292
7697
|
function formatUploadDate(iso) {
|
|
7293
7698
|
return iso ? iso.slice(0, 10) : "unknown date";
|
|
7294
7699
|
}
|
|
7295
7700
|
function createDeleteAttachmentCommand() {
|
|
7296
|
-
const command = new
|
|
7701
|
+
const command = new Command18("delete-attachment");
|
|
7297
7702
|
command.description("Remove an attachment from an Azure DevOps work item").argument("<id>", "work item ID").argument("<filename>", "name of the attachment to remove").option("--id <attachmentId>", "attachment GUID, to disambiguate when the filename is shared by more than one attachment").option("-y, --yes", "skip the interactive confirmation prompt").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").action(
|
|
7298
7703
|
async (idStr, filename, options) => {
|
|
7299
7704
|
const id = parseWorkItemId(idStr);
|
|
@@ -7370,7 +7775,7 @@ function createDeleteAttachmentCommand() {
|
|
|
7370
7775
|
}
|
|
7371
7776
|
|
|
7372
7777
|
// src/commands/relations.ts
|
|
7373
|
-
import { Command as
|
|
7778
|
+
import { Command as Command19 } from "commander";
|
|
7374
7779
|
|
|
7375
7780
|
// src/services/relations-client.ts
|
|
7376
7781
|
var API_VERSION2 = "7.1";
|
|
@@ -7587,7 +7992,7 @@ function parsePositiveInt(value, label) {
|
|
|
7587
7992
|
return n;
|
|
7588
7993
|
}
|
|
7589
7994
|
function createRelationsCommand() {
|
|
7590
|
-
const relations = new
|
|
7995
|
+
const relations = new Command19("relations").description("Manage work item relations");
|
|
7591
7996
|
addCommonOptions(
|
|
7592
7997
|
relations.command("types").description("List all available work item relation types")
|
|
7593
7998
|
).action(async (opts) => {
|
|
@@ -7666,7 +8071,7 @@ function createRelationsCommand() {
|
|
|
7666
8071
|
|
|
7667
8072
|
// src/services/update-check.ts
|
|
7668
8073
|
import fs2 from "fs";
|
|
7669
|
-
import
|
|
8074
|
+
import path4 from "path";
|
|
7670
8075
|
import os from "os";
|
|
7671
8076
|
var THROTTLE_MS = 10 * 60 * 1e3;
|
|
7672
8077
|
var FETCH_TIMEOUT_MS = 1500;
|
|
@@ -7684,7 +8089,7 @@ function skipsUpdateCheck(commandPath) {
|
|
|
7684
8089
|
return UPDATE_CHECK_EXEMPT_COMMANDS.has(withoutRoot.join(" "));
|
|
7685
8090
|
}
|
|
7686
8091
|
function getCachePath() {
|
|
7687
|
-
return
|
|
8092
|
+
return path4.join(os.homedir(), ".azdo", "update-check.json");
|
|
7688
8093
|
}
|
|
7689
8094
|
function defaultReadCache() {
|
|
7690
8095
|
try {
|
|
@@ -7696,7 +8101,7 @@ function defaultReadCache() {
|
|
|
7696
8101
|
function defaultWriteCache(data) {
|
|
7697
8102
|
try {
|
|
7698
8103
|
const cachePath = getCachePath();
|
|
7699
|
-
fs2.mkdirSync(
|
|
8104
|
+
fs2.mkdirSync(path4.dirname(cachePath), { recursive: true });
|
|
7700
8105
|
fs2.writeFileSync(cachePath, data);
|
|
7701
8106
|
} catch {
|
|
7702
8107
|
}
|
|
@@ -7782,11 +8187,12 @@ async function getUpdateNotice(opts) {
|
|
|
7782
8187
|
|
|
7783
8188
|
// src/program.ts
|
|
7784
8189
|
function createProgram() {
|
|
7785
|
-
const program2 = new
|
|
8190
|
+
const program2 = new Command20();
|
|
7786
8191
|
program2.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
7787
8192
|
program2.option("--no-update-check", "Skip the check for a newer published version");
|
|
7788
8193
|
program2.option("--trace <filepath>", "Append redacted HTTP request/response trace to a file (owner-read-only permissions)");
|
|
7789
8194
|
program2.addCommand(createGetItemCommand());
|
|
8195
|
+
program2.addCommand(createListItemsCommand());
|
|
7790
8196
|
program2.addCommand(createAuthCommand());
|
|
7791
8197
|
program2.addCommand(createClearPatCommand());
|
|
7792
8198
|
program2.addCommand(createConfigCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azdo-cli",
|
|
3
|
-
"version": "0.22.0-develop.
|
|
3
|
+
"version": "0.22.0-develop.807",
|
|
4
4
|
"description": "Azure DevOps CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@napi-rs/keyring": "^1.3.0",
|
|
29
29
|
"@primno/dpapi": "^2.0.1",
|
|
30
30
|
"commander": "^14.0.3",
|
|
31
|
+
"fflate": "^0.8.3",
|
|
31
32
|
"jimp": "^1.6.1",
|
|
32
33
|
"node-html-markdown": "^2.0.0"
|
|
33
34
|
},
|