azdo-cli 0.22.0-develop.807 → 0.22.0-develop.823
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 +1 -0
- package/dist/index.js +183 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,7 @@ git log -1 --format=%B | azdo pr comments add --file - --pr-number 64 # "-"
|
|
|
105
105
|
azdo pr open --title "Fix the thing" --description "Because X was broken"
|
|
106
106
|
azdo pr open --title "Fix the thing" # uses docs/pull_request_template[/branches/<branch>].md if present
|
|
107
107
|
azdo pr open --title "Fix the thing" --description-file body.md # or --description-file - to pipe it in
|
|
108
|
+
azdo pr open --title "Fix the thing" --target master --source feature/x --draft --work-item 1234 --label bug # --work-item/--label repeat; --json adds top-level id and url
|
|
108
109
|
# The description plus the template must stay within Azure DevOps' 4000-character cap; over it,
|
|
109
110
|
# the command says by how much and creates nothing. See docs/commands.md.
|
|
110
111
|
|
package/dist/index.js
CHANGED
|
@@ -4130,43 +4130,26 @@ function formatContributions(composed) {
|
|
|
4130
4130
|
function describeDescriptionBudget(composed) {
|
|
4131
4131
|
return `description: ${composed.providedChars} provided + ${composed.separatorChars} separator + ${composed.templateChars} template = ${composed.totalChars} characters (client limit ${MAX_PR_DESCRIPTION_CHARS})`;
|
|
4132
4132
|
}
|
|
4133
|
-
|
|
4134
|
-
const existing = await listPullRequests(context, repo, cred, sourceBranch, {
|
|
4135
|
-
status: "active",
|
|
4136
|
-
targetBranch: "develop"
|
|
4137
|
-
});
|
|
4138
|
-
if (existing.length === 1) {
|
|
4139
|
-
return {
|
|
4140
|
-
branch: sourceBranch,
|
|
4141
|
-
targetBranch: "develop",
|
|
4142
|
-
created: false,
|
|
4143
|
-
pullRequest: existing[0]
|
|
4144
|
-
};
|
|
4145
|
-
}
|
|
4146
|
-
if (existing.length > 1) {
|
|
4147
|
-
throw new Error(`AMBIGUOUS_PRS:${existing.map((pullRequest) => pullRequest.id).join(",")}`);
|
|
4148
|
-
}
|
|
4149
|
-
const repository = await getRepository(context, repo, cred);
|
|
4150
|
-
const defaultBranch = repository.defaultBranch ? repository.defaultBranch.replace(/^refs\/heads\//, "") : "develop";
|
|
4151
|
-
const template = await resolvePullRequestTemplate(context, repo, cred, defaultBranch, "develop");
|
|
4152
|
-
const composed = composeDescription(description, template);
|
|
4153
|
-
if (composed === null) {
|
|
4154
|
-
throw new Error("DESCRIPTION_REQUIRED");
|
|
4155
|
-
}
|
|
4156
|
-
if (composed.totalChars > MAX_PR_DESCRIPTION_CHARS) {
|
|
4157
|
-
throw new Error(`DESCRIPTION_TOO_LONG: ${formatDescriptionOverflow(composed)}`);
|
|
4158
|
-
}
|
|
4133
|
+
function buildOpenPayload(sourceBranch, targetBranch, title, description, options) {
|
|
4159
4134
|
const payload = {
|
|
4160
4135
|
sourceRefName: `refs/heads/${sourceBranch}`,
|
|
4161
|
-
targetRefName:
|
|
4136
|
+
targetRefName: `refs/heads/${targetBranch}`,
|
|
4162
4137
|
title,
|
|
4163
|
-
description
|
|
4138
|
+
description
|
|
4164
4139
|
};
|
|
4140
|
+
if (options.isDraft) {
|
|
4141
|
+
payload.isDraft = true;
|
|
4142
|
+
}
|
|
4143
|
+
if (options.labels && options.labels.length > 0) {
|
|
4144
|
+
payload.labels = options.labels.map((name) => ({ name }));
|
|
4145
|
+
}
|
|
4146
|
+
return payload;
|
|
4147
|
+
}
|
|
4148
|
+
async function createPullRequest(context, repo, cred, payload, composed) {
|
|
4165
4149
|
const url = new URL(
|
|
4166
4150
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests`
|
|
4167
4151
|
);
|
|
4168
4152
|
url.searchParams.set("api-version", "7.1");
|
|
4169
|
-
let data;
|
|
4170
4153
|
try {
|
|
4171
4154
|
const response = await fetchWithErrors(url.toString(), {
|
|
4172
4155
|
method: "POST",
|
|
@@ -4176,19 +4159,69 @@ async function openPullRequest(context, repo, cred, sourceBranch, title, descrip
|
|
|
4176
4159
|
},
|
|
4177
4160
|
body: JSON.stringify(payload)
|
|
4178
4161
|
});
|
|
4179
|
-
|
|
4162
|
+
return await readJsonResponse(response);
|
|
4180
4163
|
} catch (err) {
|
|
4181
4164
|
if (err instanceof Error && err.message.startsWith("HTTP_400")) {
|
|
4182
4165
|
throw new Error(`${err.message} | ${describeDescriptionBudget(composed)}`, { cause: err });
|
|
4183
4166
|
}
|
|
4184
4167
|
throw err;
|
|
4185
4168
|
}
|
|
4186
|
-
|
|
4169
|
+
}
|
|
4170
|
+
function linkWorkItems(context, repo, cred, prId, workItemIds) {
|
|
4171
|
+
return Promise.all(
|
|
4172
|
+
workItemIds.map(
|
|
4173
|
+
(id) => linkWorkItemToPullRequest(context, repo, cred, prId, id).then(
|
|
4174
|
+
() => ({ id, linked: true }),
|
|
4175
|
+
(err) => ({ id, linked: false, error: err instanceof Error ? err.message : String(err) })
|
|
4176
|
+
)
|
|
4177
|
+
)
|
|
4178
|
+
);
|
|
4179
|
+
}
|
|
4180
|
+
async function openPullRequest(context, repo, cred, sourceBranch, title, description, options = {}) {
|
|
4181
|
+
const targetBranch = options.targetBranch ?? "develop";
|
|
4182
|
+
const existing = await listPullRequests(context, repo, cred, sourceBranch, {
|
|
4183
|
+
status: "active",
|
|
4184
|
+
targetBranch
|
|
4185
|
+
});
|
|
4186
|
+
if (existing.length === 1) {
|
|
4187
|
+
return {
|
|
4188
|
+
branch: sourceBranch,
|
|
4189
|
+
targetBranch,
|
|
4190
|
+
created: false,
|
|
4191
|
+
pullRequest: existing[0]
|
|
4192
|
+
};
|
|
4193
|
+
}
|
|
4194
|
+
if (existing.length > 1) {
|
|
4195
|
+
throw new Error(`AMBIGUOUS_PRS:${existing.map((pullRequest2) => pullRequest2.id).join(",")}`);
|
|
4196
|
+
}
|
|
4197
|
+
const repository = await getRepository(context, repo, cred);
|
|
4198
|
+
const defaultBranch = repository.defaultBranch ? repository.defaultBranch.replace(/^refs\/heads\//, "") : "develop";
|
|
4199
|
+
const template = await resolvePullRequestTemplate(context, repo, cred, defaultBranch, targetBranch);
|
|
4200
|
+
const composed = composeDescription(description, template);
|
|
4201
|
+
if (composed === null) {
|
|
4202
|
+
throw new Error("DESCRIPTION_REQUIRED");
|
|
4203
|
+
}
|
|
4204
|
+
if (composed.totalChars > MAX_PR_DESCRIPTION_CHARS) {
|
|
4205
|
+
throw new Error(`DESCRIPTION_TOO_LONG: ${formatDescriptionOverflow(composed)}`);
|
|
4206
|
+
}
|
|
4207
|
+
const data = await createPullRequest(
|
|
4208
|
+
context,
|
|
4209
|
+
repo,
|
|
4210
|
+
cred,
|
|
4211
|
+
buildOpenPayload(sourceBranch, targetBranch, title, composed.text, options),
|
|
4212
|
+
composed
|
|
4213
|
+
);
|
|
4214
|
+
const pullRequest = mapPullRequest(context, repo, data);
|
|
4215
|
+
const result = {
|
|
4187
4216
|
branch: sourceBranch,
|
|
4188
|
-
targetBranch
|
|
4217
|
+
targetBranch,
|
|
4189
4218
|
created: true,
|
|
4190
|
-
pullRequest
|
|
4219
|
+
pullRequest
|
|
4191
4220
|
};
|
|
4221
|
+
if (options.workItemIds && options.workItemIds.length > 0) {
|
|
4222
|
+
result.workItems = await linkWorkItems(context, repo, cred, pullRequest.id, options.workItemIds);
|
|
4223
|
+
}
|
|
4224
|
+
return result;
|
|
4192
4225
|
}
|
|
4193
4226
|
async function updatePullRequest(context, repo, cred, prId, fields) {
|
|
4194
4227
|
if (fields.description !== void 0 && fields.description.length > MAX_PR_DESCRIPTION_CHARS) {
|
|
@@ -4970,11 +5003,11 @@ function createPrStatusCommand() {
|
|
|
4970
5003
|
});
|
|
4971
5004
|
return command;
|
|
4972
5005
|
}
|
|
4973
|
-
function handlePrOpenError(err, context) {
|
|
5006
|
+
function handlePrOpenError(err, context, targetBranch = "develop") {
|
|
4974
5007
|
const message = err instanceof Error ? err.message : "";
|
|
4975
5008
|
if (message.startsWith("AMBIGUOUS_PRS:")) {
|
|
4976
5009
|
const ids = message.replace("AMBIGUOUS_PRS:", "").split(",").map((id) => `#${id}`).join(", ");
|
|
4977
|
-
writeError(`Multiple active pull requests already exist for this branch targeting
|
|
5010
|
+
writeError(`Multiple active pull requests already exist for this branch targeting ${targetBranch}: ${ids}. Use pr status to review them.`);
|
|
4978
5011
|
return;
|
|
4979
5012
|
}
|
|
4980
5013
|
if (message === "DESCRIPTION_REQUIRED") {
|
|
@@ -4987,6 +5020,47 @@ function handlePrOpenError(err, context) {
|
|
|
4987
5020
|
}
|
|
4988
5021
|
handlePrCommandError(err, context, "write");
|
|
4989
5022
|
}
|
|
5023
|
+
function collectOption(value, previous) {
|
|
5024
|
+
return [...previous, value];
|
|
5025
|
+
}
|
|
5026
|
+
function stripHeadsPrefix(branch) {
|
|
5027
|
+
return branch.trim().replace(/^refs\/heads\//, "");
|
|
5028
|
+
}
|
|
5029
|
+
function resolveOpenOptions(options) {
|
|
5030
|
+
const targetBranch = options.target === void 0 ? "develop" : stripHeadsPrefix(options.target);
|
|
5031
|
+
const source = options.source === void 0 ? void 0 : stripHeadsPrefix(options.source);
|
|
5032
|
+
if (!targetBranch) {
|
|
5033
|
+
writeError("--target must not be empty.");
|
|
5034
|
+
return null;
|
|
5035
|
+
}
|
|
5036
|
+
if (source !== void 0 && !source) {
|
|
5037
|
+
writeError("--source must not be empty.");
|
|
5038
|
+
return null;
|
|
5039
|
+
}
|
|
5040
|
+
const workItemIds = [];
|
|
5041
|
+
for (const raw of options.workItem ?? []) {
|
|
5042
|
+
const id = Number(raw.trim());
|
|
5043
|
+
if (!/^\d+$/.test(raw.trim()) || !Number.isSafeInteger(id) || id <= 0) {
|
|
5044
|
+
writeError(`--work-item must be a positive integer, got "${raw}".`);
|
|
5045
|
+
return null;
|
|
5046
|
+
}
|
|
5047
|
+
if (!workItemIds.includes(id)) {
|
|
5048
|
+
workItemIds.push(id);
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
const labels = [];
|
|
5052
|
+
for (const raw of options.label ?? []) {
|
|
5053
|
+
const name = raw.trim();
|
|
5054
|
+
if (!name) {
|
|
5055
|
+
writeError("--label must not be empty.");
|
|
5056
|
+
return null;
|
|
5057
|
+
}
|
|
5058
|
+
if (!labels.includes(name)) {
|
|
5059
|
+
labels.push(name);
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
return { targetBranch, source, isDraft: options.draft === true, labels, workItemIds };
|
|
5063
|
+
}
|
|
4990
5064
|
function resolveOpenDescription(inline, file) {
|
|
4991
5065
|
if (inline !== void 0 && file !== void 0) {
|
|
4992
5066
|
writeError("Cannot specify both --description and --description-file.");
|
|
@@ -5012,13 +5086,18 @@ function resolveOpenDescription(inline, file) {
|
|
|
5012
5086
|
}
|
|
5013
5087
|
function createPrOpenCommand() {
|
|
5014
5088
|
const command = new Command13("open");
|
|
5015
|
-
withCommonPrOptions(command).description("Open a pull request from the current branch to develop").option("--title <title>", "pull request title").option(
|
|
5089
|
+
withCommonPrOptions(command).description("Open a pull request (default: from the current branch to develop)").option("--title <title>", "pull request title").option(
|
|
5016
5090
|
"--description <description>",
|
|
5017
5091
|
"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
5092
|
).option(
|
|
5019
5093
|
"--description-file <path>",
|
|
5020
5094
|
'read the description from a UTF-8 file instead of --description; "-" reads standard input'
|
|
5021
|
-
).option("--
|
|
5095
|
+
).option("--target <branch>", "target branch (default: develop); refs/heads/ prefix accepted").option("--source <branch>", "source branch (default: the current branch); no local checkout needed").option("--draft", "open the pull request as a draft").option(
|
|
5096
|
+
"--work-item <id>",
|
|
5097
|
+
"link a work item to the pull request (positive integer; repeatable)",
|
|
5098
|
+
collectOption,
|
|
5099
|
+
[]
|
|
5100
|
+
).option("--label <label>", "add a label to the pull request (repeatable)", collectOption, []).option("--json", "output JSON; adds top-level id and url next to pullRequest").action(async (options) => {
|
|
5022
5101
|
validateOrgProjectPair(options);
|
|
5023
5102
|
const title = options.title?.trim();
|
|
5024
5103
|
if (!title) {
|
|
@@ -5029,45 +5108,71 @@ function createPrOpenCommand() {
|
|
|
5029
5108
|
if (description === null) {
|
|
5030
5109
|
return;
|
|
5031
5110
|
}
|
|
5111
|
+
const openOptions = resolveOpenOptions(options);
|
|
5112
|
+
if (openOptions === null) {
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
5032
5115
|
let context;
|
|
5033
5116
|
try {
|
|
5034
|
-
const
|
|
5035
|
-
|
|
5036
|
-
if (
|
|
5037
|
-
writeError(
|
|
5117
|
+
const openBranch = openOptions.source ?? getCurrentBranch();
|
|
5118
|
+
const target = openOptions.targetBranch;
|
|
5119
|
+
if (openBranch === target) {
|
|
5120
|
+
writeError(
|
|
5121
|
+
target === "develop" ? "Pull request creation requires a source branch other than develop." : `Pull request creation requires a source branch other than the target branch (${target}).`
|
|
5122
|
+
);
|
|
5038
5123
|
return;
|
|
5039
5124
|
}
|
|
5040
|
-
const
|
|
5125
|
+
const resolved = await resolvePrCommandContext(options, { requireBranch: false });
|
|
5126
|
+
context = resolved.context;
|
|
5041
5127
|
const result = await openPullRequest(
|
|
5042
5128
|
resolved.context,
|
|
5043
5129
|
resolved.repo,
|
|
5044
5130
|
resolved.pat,
|
|
5045
5131
|
openBranch,
|
|
5046
5132
|
title,
|
|
5047
|
-
description
|
|
5133
|
+
description,
|
|
5134
|
+
openOptions
|
|
5048
5135
|
);
|
|
5049
5136
|
if (options.json) {
|
|
5050
|
-
|
|
5137
|
+
const output = { ...result, id: result.pullRequest.id, url: result.pullRequest.url };
|
|
5138
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}
|
|
5051
5139
|
`);
|
|
5140
|
+
reportWorkItemLinkFailures(result);
|
|
5052
5141
|
return;
|
|
5053
5142
|
}
|
|
5054
5143
|
if (result.created) {
|
|
5055
5144
|
process.stdout.write(`Created pull request #${result.pullRequest.id}: ${result.pullRequest.title}
|
|
5056
5145
|
${result.pullRequest.url ?? "\u2014"}
|
|
5057
5146
|
`);
|
|
5147
|
+
reportWorkItemLinkFailures(result);
|
|
5058
5148
|
return;
|
|
5059
5149
|
}
|
|
5060
5150
|
process.stdout.write(
|
|
5061
|
-
`Active pull request already exists for ${
|
|
5151
|
+
`Active pull request already exists for ${openBranch} -> ${result.targetBranch}: #${result.pullRequest.id}
|
|
5062
5152
|
${result.pullRequest.url ?? "\u2014"}
|
|
5063
5153
|
`
|
|
5064
5154
|
);
|
|
5155
|
+
if (openOptions.isDraft || openOptions.labels?.length || openOptions.workItemIds?.length) {
|
|
5156
|
+
const hint = openOptions.workItemIds?.length ? " Use pr work-items link to link a work item." : "";
|
|
5157
|
+
process.stdout.write(
|
|
5158
|
+
`--draft, --label and --work-item were not applied; the existing pull request was left unchanged.${hint}
|
|
5159
|
+
`
|
|
5160
|
+
);
|
|
5161
|
+
}
|
|
5065
5162
|
} catch (err) {
|
|
5066
|
-
handlePrOpenError(err, context);
|
|
5163
|
+
handlePrOpenError(err, context, openOptions.targetBranch);
|
|
5067
5164
|
}
|
|
5068
5165
|
});
|
|
5069
5166
|
return command;
|
|
5070
5167
|
}
|
|
5168
|
+
function reportWorkItemLinkFailures(result) {
|
|
5169
|
+
const failed = (result.workItems ?? []).filter((item) => !item.linked);
|
|
5170
|
+
if (failed.length === 0) return;
|
|
5171
|
+
const detail = failed.map((item) => `#${item.id} (${item.error ?? "unknown error"})`).join(", ");
|
|
5172
|
+
writeError(
|
|
5173
|
+
`Pull request #${result.pullRequest.id} was created but could not be linked to work item ${detail}. Retry with: azdo pr work-items link <id> --pr-number ${result.pullRequest.id}`
|
|
5174
|
+
);
|
|
5175
|
+
}
|
|
5071
5176
|
function handlePrUpdateError(err, context) {
|
|
5072
5177
|
const message = err instanceof Error ? err.message : "";
|
|
5073
5178
|
if (message.startsWith("DESCRIPTION_TOO_LONG: ")) {
|
|
@@ -6669,6 +6774,26 @@ async function listBuildArtifacts(context, cred, buildId) {
|
|
|
6669
6774
|
};
|
|
6670
6775
|
});
|
|
6671
6776
|
}
|
|
6777
|
+
var INITIAL_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
6778
|
+
async function collectBody(body, declared, onProgress, initialBytes = INITIAL_BUFFER_BYTES) {
|
|
6779
|
+
const hint = Number.isSafeInteger(declared) && declared > 0 ? declared : Infinity;
|
|
6780
|
+
let buffer = new Uint8Array(Math.min(hint, initialBytes));
|
|
6781
|
+
let received = 0;
|
|
6782
|
+
for await (const chunk of body) {
|
|
6783
|
+
const needed = received + chunk.length;
|
|
6784
|
+
if (needed > buffer.length) {
|
|
6785
|
+
let capacity = Math.max(buffer.length * 2, needed);
|
|
6786
|
+
if (hint >= needed) capacity = Math.min(capacity, hint);
|
|
6787
|
+
const grown = new Uint8Array(capacity);
|
|
6788
|
+
grown.set(buffer.subarray(0, received));
|
|
6789
|
+
buffer = grown;
|
|
6790
|
+
}
|
|
6791
|
+
buffer.set(chunk, received);
|
|
6792
|
+
received = needed;
|
|
6793
|
+
onProgress?.(received);
|
|
6794
|
+
}
|
|
6795
|
+
return buffer.subarray(0, received);
|
|
6796
|
+
}
|
|
6672
6797
|
async function downloadArtifactZip(cred, artifact, onProgress) {
|
|
6673
6798
|
if (!artifact.downloadUrl) {
|
|
6674
6799
|
throw new Error(`Artifact "${artifact.name}" (${artifact.type ?? "unknown type"}) has no download URL.`);
|
|
@@ -6682,14 +6807,8 @@ async function downloadArtifactZip(cred, artifact, onProgress) {
|
|
|
6682
6807
|
if (!response.body) {
|
|
6683
6808
|
return new Uint8Array(await response.arrayBuffer());
|
|
6684
6809
|
}
|
|
6685
|
-
const
|
|
6686
|
-
|
|
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);
|
|
6810
|
+
const declared = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
6811
|
+
return collectBody(response.body, declared, onProgress);
|
|
6693
6812
|
}
|
|
6694
6813
|
|
|
6695
6814
|
// src/services/artifact-extract.ts
|
|
@@ -7142,10 +7261,18 @@ function grepWithContext(lines, grep, context) {
|
|
|
7142
7261
|
}
|
|
7143
7262
|
return selected;
|
|
7144
7263
|
}
|
|
7264
|
+
var PROGRESS_BAR = /(?<![\d.])\d+(?:\.\d+)? [KMGT]?i?B \/ \d+(?:\.\d+)? [KMGT]?i?B \[/g;
|
|
7265
|
+
var LOG_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z /;
|
|
7266
|
+
function collapseGluedBars(line) {
|
|
7267
|
+
const starts = [...line.matchAll(PROGRESS_BAR)].map((m) => m.index ?? 0);
|
|
7268
|
+
if (starts.length < 2) return line;
|
|
7269
|
+
const prefix = LOG_TIMESTAMP.exec(line)?.[0] ?? "";
|
|
7270
|
+
return prefix + line.slice(starts.at(-1));
|
|
7271
|
+
}
|
|
7145
7272
|
function collapseProgress(line) {
|
|
7146
7273
|
let end = line.length;
|
|
7147
7274
|
while (end > 0 && line[end - 1] === "\r") end--;
|
|
7148
|
-
return line.slice(line.lastIndexOf("\r", end - 1) + 1, end);
|
|
7275
|
+
return collapseGluedBars(line.slice(line.lastIndexOf("\r", end - 1) + 1, end));
|
|
7149
7276
|
}
|
|
7150
7277
|
function filterLogLines(content, filters) {
|
|
7151
7278
|
const { grep, tail, head, contextLines: context } = filters;
|
|
@@ -7255,7 +7382,7 @@ function printSingleLog(content, filters) {
|
|
|
7255
7382
|
}
|
|
7256
7383
|
function createPipelineLogsCommand() {
|
|
7257
7384
|
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
|
|
7385
|
+
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 progress-bar redraws (carriage-return or glued into one line) to their final state").option("--json", "output JSON").action(async (runIdRaw, options) => {
|
|
7259
7386
|
validateOrgProjectPair(options);
|
|
7260
7387
|
const runId = parsePositiveId(runIdRaw);
|
|
7261
7388
|
if (runId === null) {
|