azdo-cli 0.24.0-feature-059-delete-item.906 → 0.24.0-feature-060-pr-comment-anchor.910
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/dist/index.js +240 -121
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
} from "./chunk-DNOOZIXA.js";
|
|
49
49
|
|
|
50
50
|
// src/program.ts
|
|
51
|
-
import { Command as
|
|
51
|
+
import { Command as Command20 } from "commander";
|
|
52
52
|
|
|
53
53
|
// src/version.ts
|
|
54
54
|
import { readFileSync } from "fs";
|
|
@@ -967,25 +967,6 @@ async function applyWorkItemPatch(context, id, cred, operations) {
|
|
|
967
967
|
});
|
|
968
968
|
return readWriteResponse(response, "UPDATE_REJECTED");
|
|
969
969
|
}
|
|
970
|
-
async function deleteWorkItem(context, id, cred, permanent = false) {
|
|
971
|
-
const url = new URL(
|
|
972
|
-
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitems/${id}`
|
|
973
|
-
);
|
|
974
|
-
if (permanent) url.searchParams.set("destroy", "true");
|
|
975
|
-
url.searchParams.set("api-version", "7.1");
|
|
976
|
-
const response = await fetchWithErrors(url.toString(), {
|
|
977
|
-
method: "DELETE",
|
|
978
|
-
headers: authHeaders(cred)
|
|
979
|
-
});
|
|
980
|
-
if (response.status === 400) {
|
|
981
|
-
const serverMessage = await readResponseMessage(response) ?? "Unknown error";
|
|
982
|
-
throw new Error(`BAD_REQUEST: ${serverMessage}`);
|
|
983
|
-
}
|
|
984
|
-
if (!response.ok) {
|
|
985
|
-
throw httpError(response);
|
|
986
|
-
}
|
|
987
|
-
return { id, permanent };
|
|
988
|
-
}
|
|
989
970
|
async function downloadAttachment(url, cred) {
|
|
990
971
|
const response = await fetchWithErrors(url, { headers: authHeaders(cred) });
|
|
991
972
|
if (!response.ok) {
|
|
@@ -4315,7 +4296,7 @@ async function getPullRequestThread(context, repo, cred, prId, threadId) {
|
|
|
4315
4296
|
const data = await readJsonResponse(response);
|
|
4316
4297
|
return toActiveCommentThread(data);
|
|
4317
4298
|
}
|
|
4318
|
-
async function createPullRequestThread(context, repo, cred, prId, content, status2) {
|
|
4299
|
+
async function createPullRequestThread(context, repo, cred, prId, content, status2, anchor) {
|
|
4319
4300
|
const url = new URL(
|
|
4320
4301
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/threads`
|
|
4321
4302
|
);
|
|
@@ -4326,6 +4307,10 @@ async function createPullRequestThread(context, repo, cred, prId, content, statu
|
|
|
4326
4307
|
if (status2 !== void 0) {
|
|
4327
4308
|
payload.status = status2;
|
|
4328
4309
|
}
|
|
4310
|
+
if (anchor !== void 0) {
|
|
4311
|
+
payload.threadContext = anchor.threadContext;
|
|
4312
|
+
payload.pullRequestThreadContext = anchor.pullRequestThreadContext;
|
|
4313
|
+
}
|
|
4329
4314
|
const response = await fetchWithErrors(url.toString(), {
|
|
4330
4315
|
method: "POST",
|
|
4331
4316
|
headers: {
|
|
@@ -4337,6 +4322,42 @@ async function createPullRequestThread(context, repo, cred, prId, content, statu
|
|
|
4337
4322
|
const data = await readJsonResponse(response);
|
|
4338
4323
|
return toActiveCommentThread(data);
|
|
4339
4324
|
}
|
|
4325
|
+
function buildIterationsUrl(context, repo, prId, suffix = "") {
|
|
4326
|
+
const url = new URL(
|
|
4327
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/iterations${suffix}`
|
|
4328
|
+
);
|
|
4329
|
+
url.searchParams.set("api-version", "7.1");
|
|
4330
|
+
return url;
|
|
4331
|
+
}
|
|
4332
|
+
async function getLatestPullRequestIterationId(context, repo, cred, prId) {
|
|
4333
|
+
const url = buildIterationsUrl(context, repo, prId);
|
|
4334
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
4335
|
+
const data = await readJsonResponse(response);
|
|
4336
|
+
const ids = (data.value ?? []).map((iteration) => iteration.id);
|
|
4337
|
+
if (ids.length === 0) {
|
|
4338
|
+
throw new Error(`Pull request #${prId} has no iterations.`);
|
|
4339
|
+
}
|
|
4340
|
+
return Math.max(...ids);
|
|
4341
|
+
}
|
|
4342
|
+
async function getPullRequestIterationChanges(context, repo, cred, prId, iterationId) {
|
|
4343
|
+
const changes = [];
|
|
4344
|
+
let skip = 0;
|
|
4345
|
+
for (; ; ) {
|
|
4346
|
+
const url = buildIterationsUrl(context, repo, prId, `/${iterationId}/changes`);
|
|
4347
|
+
url.searchParams.set("$top", "2000");
|
|
4348
|
+
if (skip > 0) {
|
|
4349
|
+
url.searchParams.set("$skip", String(skip));
|
|
4350
|
+
}
|
|
4351
|
+
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
4352
|
+
const page = await readJsonResponse(response);
|
|
4353
|
+
changes.push(...page.changeEntries ?? []);
|
|
4354
|
+
const next = page.nextSkip ?? 0;
|
|
4355
|
+
if (next <= skip) {
|
|
4356
|
+
return changes;
|
|
4357
|
+
}
|
|
4358
|
+
skip = next;
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4340
4361
|
function buildThreadCommentUrl(context, repo, prId, threadId) {
|
|
4341
4362
|
const url = new URL(
|
|
4342
4363
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/threads/${threadId}/comments`
|
|
@@ -4660,6 +4681,36 @@ async function resolvePullRequestTemplate(context, repo, cred, defaultBranch, ta
|
|
|
4660
4681
|
return firstMatchingTemplate(context, repo, cred, defaultBranch, defaultTemplateCandidates(), "default");
|
|
4661
4682
|
}
|
|
4662
4683
|
|
|
4684
|
+
// src/services/thread-anchor.ts
|
|
4685
|
+
var LINE_END_OFFSET = 1e3;
|
|
4686
|
+
function normalizeThreadFilePath(raw) {
|
|
4687
|
+
let path5 = raw.trim().replaceAll("\\", "/");
|
|
4688
|
+
while (path5.startsWith("./")) {
|
|
4689
|
+
path5 = path5.slice(2);
|
|
4690
|
+
}
|
|
4691
|
+
path5 = path5.replace(/^\/+/, "");
|
|
4692
|
+
return path5 === "" ? null : `/${path5}`;
|
|
4693
|
+
}
|
|
4694
|
+
function buildThreadAnchorContext(filePath, line, endLine, side) {
|
|
4695
|
+
const start = { line, offset: 1 };
|
|
4696
|
+
const end = { line: endLine, offset: LINE_END_OFFSET };
|
|
4697
|
+
return side === "right" ? { filePath, rightFileStart: start, rightFileEnd: end } : { filePath, leftFileStart: start, leftFileEnd: end };
|
|
4698
|
+
}
|
|
4699
|
+
function buildPullRequestThreadContext(iterationId, changeTrackingId) {
|
|
4700
|
+
const context = {
|
|
4701
|
+
iterationContext: { firstComparingIteration: iterationId, secondComparingIteration: iterationId }
|
|
4702
|
+
};
|
|
4703
|
+
if (changeTrackingId !== void 0) {
|
|
4704
|
+
context.changeTrackingId = changeTrackingId;
|
|
4705
|
+
}
|
|
4706
|
+
return context;
|
|
4707
|
+
}
|
|
4708
|
+
function findChangeTrackingId(changes, filePath, side) {
|
|
4709
|
+
const byPath = (change) => change.item?.path === filePath;
|
|
4710
|
+
const match = side === "left" ? changes.find((change) => change.originalPath === filePath) ?? changes.find(byPath) : changes.find(byPath);
|
|
4711
|
+
return match?.changeTrackingId;
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4663
4714
|
// src/commands/pr.ts
|
|
4664
4715
|
function parsePositivePrNumber(raw) {
|
|
4665
4716
|
if (!/^\d+$/.test(raw)) {
|
|
@@ -5813,6 +5864,130 @@ var CREATABLE_THREAD_STATUSES = [
|
|
|
5813
5864
|
"byDesign",
|
|
5814
5865
|
"pending"
|
|
5815
5866
|
];
|
|
5867
|
+
var THREAD_ANCHOR_SIDES = ["right", "left"];
|
|
5868
|
+
function parseCommentAddStatus(raw) {
|
|
5869
|
+
if (raw === void 0) {
|
|
5870
|
+
return void 0;
|
|
5871
|
+
}
|
|
5872
|
+
const match = CREATABLE_THREAD_STATUSES.find((candidate) => candidate === raw);
|
|
5873
|
+
if (match === void 0) {
|
|
5874
|
+
writeError(`Invalid --status "${raw}"; expected one of ${CREATABLE_THREAD_STATUSES.join(", ")}.`);
|
|
5875
|
+
return null;
|
|
5876
|
+
}
|
|
5877
|
+
return match;
|
|
5878
|
+
}
|
|
5879
|
+
function parseLineOption(flag, raw) {
|
|
5880
|
+
const value = parsePositivePrNumber(raw);
|
|
5881
|
+
if (value === null) {
|
|
5882
|
+
writeError(`Invalid ${flag} "${raw}"; expected a positive integer (1-based line number).`);
|
|
5883
|
+
}
|
|
5884
|
+
return value;
|
|
5885
|
+
}
|
|
5886
|
+
function parseCommentAnchorOptions(options) {
|
|
5887
|
+
if (options.filePath === void 0) {
|
|
5888
|
+
const stray = [
|
|
5889
|
+
["--line", options.line],
|
|
5890
|
+
["--end-line", options.endLine],
|
|
5891
|
+
["--side", options.side],
|
|
5892
|
+
["--allow-unchanged-file", options.allowUnchangedFile]
|
|
5893
|
+
].find(([, value]) => value !== void 0);
|
|
5894
|
+
if (stray !== void 0) {
|
|
5895
|
+
writeError(`${stray[0]} requires --file-path.`);
|
|
5896
|
+
return null;
|
|
5897
|
+
}
|
|
5898
|
+
return void 0;
|
|
5899
|
+
}
|
|
5900
|
+
if (options.line === void 0) {
|
|
5901
|
+
writeError("--file-path requires --line.");
|
|
5902
|
+
return null;
|
|
5903
|
+
}
|
|
5904
|
+
const filePath = normalizeThreadFilePath(options.filePath);
|
|
5905
|
+
if (filePath === null) {
|
|
5906
|
+
writeError(`Invalid --file-path "${options.filePath}"; expected a repository file path.`);
|
|
5907
|
+
return null;
|
|
5908
|
+
}
|
|
5909
|
+
const line = parseLineOption("--line", options.line);
|
|
5910
|
+
if (line === null) {
|
|
5911
|
+
return null;
|
|
5912
|
+
}
|
|
5913
|
+
const endLine = options.endLine === void 0 ? line : parseLineOption("--end-line", options.endLine);
|
|
5914
|
+
if (endLine === null) {
|
|
5915
|
+
return null;
|
|
5916
|
+
}
|
|
5917
|
+
if (endLine < line) {
|
|
5918
|
+
writeError(`--end-line (${endLine}) must not be less than --line (${line}).`);
|
|
5919
|
+
return null;
|
|
5920
|
+
}
|
|
5921
|
+
const side = THREAD_ANCHOR_SIDES.find((candidate) => candidate === (options.side ?? "right"));
|
|
5922
|
+
if (side === void 0) {
|
|
5923
|
+
writeError(`Invalid --side "${options.side}"; expected ${THREAD_ANCHOR_SIDES.join(" or ")}.`);
|
|
5924
|
+
return null;
|
|
5925
|
+
}
|
|
5926
|
+
return { filePath, line, endLine, side, allowUnchangedFile: options.allowUnchangedFile === true };
|
|
5927
|
+
}
|
|
5928
|
+
async function resolveCommentAnchor(target, request) {
|
|
5929
|
+
const prId = target.pullRequest.id;
|
|
5930
|
+
const iterationId = await getLatestPullRequestIterationId(target.context, target.repo, target.pat, prId);
|
|
5931
|
+
const changes = await getPullRequestIterationChanges(target.context, target.repo, target.pat, prId, iterationId);
|
|
5932
|
+
const changeTrackingId = findChangeTrackingId(changes, request.filePath, request.side);
|
|
5933
|
+
if (changeTrackingId === void 0 && !request.allowUnchangedFile) {
|
|
5934
|
+
writeError(
|
|
5935
|
+
`${request.filePath} is not among the files changed by pull request #${prId} (iteration ${iterationId}). Check the path, or pass --allow-unchanged-file to post the comment anyway.`,
|
|
5936
|
+
3
|
|
5937
|
+
);
|
|
5938
|
+
return null;
|
|
5939
|
+
}
|
|
5940
|
+
return {
|
|
5941
|
+
request,
|
|
5942
|
+
iterationId,
|
|
5943
|
+
changeTrackingId,
|
|
5944
|
+
threadContext: buildThreadAnchorContext(request.filePath, request.line, request.endLine, request.side),
|
|
5945
|
+
pullRequestThreadContext: buildPullRequestThreadContext(iterationId, changeTrackingId)
|
|
5946
|
+
};
|
|
5947
|
+
}
|
|
5948
|
+
function describeCommentAnchor(anchor) {
|
|
5949
|
+
const { filePath, line, endLine, side } = anchor.request;
|
|
5950
|
+
const lines = line === endLine ? `line ${line}` : `lines ${line}-${endLine}`;
|
|
5951
|
+
const tracking = anchor.changeTrackingId === void 0 ? "no changeTrackingId" : `changeTrackingId ${anchor.changeTrackingId}`;
|
|
5952
|
+
return ` at ${filePath} ${lines} (${side} side, iteration ${anchor.iterationId}, ${tracking})`;
|
|
5953
|
+
}
|
|
5954
|
+
function warnIfUntracked(anchor) {
|
|
5955
|
+
if (anchor !== void 0 && anchor.changeTrackingId === void 0) {
|
|
5956
|
+
process.stderr.write(
|
|
5957
|
+
`Note: ${anchor.request.filePath} is not among the pull request's changed files, so the thread is posted without a changeTrackingId and will not follow the line across later pushes.
|
|
5958
|
+
`
|
|
5959
|
+
);
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
function withAnchorFields(result, anchor) {
|
|
5963
|
+
if (anchor === void 0) {
|
|
5964
|
+
return result;
|
|
5965
|
+
}
|
|
5966
|
+
return {
|
|
5967
|
+
...result,
|
|
5968
|
+
threadContext: anchor.threadContext,
|
|
5969
|
+
pullRequestThreadContext: anchor.pullRequestThreadContext,
|
|
5970
|
+
changeTracked: anchor.changeTrackingId !== void 0
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
function writeCommentAddDryRun(prId, body, status2, anchor, json) {
|
|
5974
|
+
if (json) {
|
|
5975
|
+
const dryResult = withAnchorFields(
|
|
5976
|
+
{ pullRequestId: prId, threadId: null, commentId: null, status: status2 ?? null, content: body, dryRun: true },
|
|
5977
|
+
anchor
|
|
5978
|
+
);
|
|
5979
|
+
process.stdout.write(`${JSON.stringify(dryResult, null, 2)}
|
|
5980
|
+
`);
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
5983
|
+
const statusSuffix = status2 === void 0 ? "" : ` with status ${status2}`;
|
|
5984
|
+
const anchorSuffix = anchor === void 0 ? "" : describeCommentAnchor(anchor);
|
|
5985
|
+
process.stdout.write(
|
|
5986
|
+
`Dry run: would post a new comment thread${statusSuffix} on pull request #${prId}${anchorSuffix} (${body.length} chars).
|
|
5987
|
+
${body}
|
|
5988
|
+
`
|
|
5989
|
+
);
|
|
5990
|
+
}
|
|
5816
5991
|
async function runCommentAdd(text, options) {
|
|
5817
5992
|
let context;
|
|
5818
5993
|
try {
|
|
@@ -5820,16 +5995,13 @@ async function runCommentAdd(text, options) {
|
|
|
5820
5995
|
if (body === null) {
|
|
5821
5996
|
return;
|
|
5822
5997
|
}
|
|
5823
|
-
|
|
5824
|
-
if (
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
return;
|
|
5831
|
-
}
|
|
5832
|
-
status2 = match;
|
|
5998
|
+
const status2 = parseCommentAddStatus(options.status);
|
|
5999
|
+
if (status2 === null) {
|
|
6000
|
+
return;
|
|
6001
|
+
}
|
|
6002
|
+
const anchorRequest = parseCommentAnchorOptions(options);
|
|
6003
|
+
if (anchorRequest === null) {
|
|
6004
|
+
return;
|
|
5833
6005
|
}
|
|
5834
6006
|
const target = await resolvePullRequestTarget(options, {
|
|
5835
6007
|
onContextResolved: (resolved) => {
|
|
@@ -5839,26 +6011,13 @@ async function runCommentAdd(text, options) {
|
|
|
5839
6011
|
if (target === null) {
|
|
5840
6012
|
return;
|
|
5841
6013
|
}
|
|
6014
|
+
const anchor = anchorRequest === void 0 ? void 0 : await resolveCommentAnchor(target, anchorRequest);
|
|
6015
|
+
if (anchor === null) {
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
warnIfUntracked(anchor);
|
|
5842
6019
|
if (options.dryRun === true) {
|
|
5843
|
-
|
|
5844
|
-
pullRequestId: target.pullRequest.id,
|
|
5845
|
-
threadId: null,
|
|
5846
|
-
commentId: null,
|
|
5847
|
-
status: status2 ?? null,
|
|
5848
|
-
content: body,
|
|
5849
|
-
dryRun: true
|
|
5850
|
-
};
|
|
5851
|
-
if (options.json) {
|
|
5852
|
-
process.stdout.write(`${JSON.stringify(dryResult, null, 2)}
|
|
5853
|
-
`);
|
|
5854
|
-
return;
|
|
5855
|
-
}
|
|
5856
|
-
const statusSuffix = status2 === void 0 ? "" : ` with status ${status2}`;
|
|
5857
|
-
process.stdout.write(
|
|
5858
|
-
`Dry run: would post a new comment thread${statusSuffix} on pull request #${target.pullRequest.id} (${body.length} chars).
|
|
5859
|
-
${body}
|
|
5860
|
-
`
|
|
5861
|
-
);
|
|
6020
|
+
writeCommentAddDryRun(target.pullRequest.id, body, status2, anchor, options.json);
|
|
5862
6021
|
return;
|
|
5863
6022
|
}
|
|
5864
6023
|
const thread = await createPullRequestThread(
|
|
@@ -5867,24 +6026,29 @@ ${body}
|
|
|
5867
6026
|
target.pat,
|
|
5868
6027
|
target.pullRequest.id,
|
|
5869
6028
|
body,
|
|
5870
|
-
status2
|
|
6029
|
+
status2,
|
|
6030
|
+
...anchor === void 0 ? [] : [{ threadContext: anchor.threadContext, pullRequestThreadContext: anchor.pullRequestThreadContext }]
|
|
5871
6031
|
);
|
|
5872
6032
|
const created = thread.comments[0];
|
|
5873
|
-
const result =
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6033
|
+
const result = withAnchorFields(
|
|
6034
|
+
{
|
|
6035
|
+
pullRequestId: target.pullRequest.id,
|
|
6036
|
+
threadId: thread.id,
|
|
6037
|
+
commentId: created?.id ?? null,
|
|
6038
|
+
status: thread.status,
|
|
6039
|
+
content: created?.content ?? body,
|
|
6040
|
+
dryRun: false
|
|
6041
|
+
},
|
|
6042
|
+
anchor
|
|
6043
|
+
);
|
|
5881
6044
|
if (options.json) {
|
|
5882
6045
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
5883
6046
|
`);
|
|
5884
6047
|
return;
|
|
5885
6048
|
}
|
|
6049
|
+
const anchorSuffix = anchor === void 0 ? "" : describeCommentAnchor(anchor);
|
|
5886
6050
|
process.stdout.write(
|
|
5887
|
-
`Comment posted to pull request #${target.pullRequest.id} (thread #${thread.id}).
|
|
6051
|
+
`Comment posted to pull request #${target.pullRequest.id} (thread #${thread.id})${anchorSuffix}.
|
|
5888
6052
|
`
|
|
5889
6053
|
);
|
|
5890
6054
|
} catch (err) {
|
|
@@ -5896,18 +6060,21 @@ function buildCommentAddCommand(name, description) {
|
|
|
5896
6060
|
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(
|
|
5897
6061
|
"--status <status>",
|
|
5898
6062
|
`thread status (${CREATABLE_THREAD_STATUSES.join(" | ")}); omit for a plain, non-resolvable comment`
|
|
6063
|
+
).option("--file-path <path>", "anchor the thread to this repository file (Files tab); requires --line").option("--line <n>", "first line of the anchored range (1-based)").option("--end-line <n>", "last line of the anchored range (default: --line)").option("--side <side>", "right (new version, default) or left (old version, for deleted lines)").option(
|
|
6064
|
+
"--allow-unchanged-file",
|
|
6065
|
+
"post even when --file-path is not among the pull request's changed files (the thread will not follow the line across pushes)"
|
|
5899
6066
|
).option("--dry-run", "resolve the target pull request and print what would be posted, without writing anything").option("--pr-number <N>", PR_NUMBER_HELP).option("--json", "output JSON").action(async (text, _options, command2) => {
|
|
5900
6067
|
await runCommentAdd(text, mergedPrOptions(command2));
|
|
5901
6068
|
});
|
|
5902
6069
|
return command;
|
|
5903
6070
|
}
|
|
5904
6071
|
function createPrCommentsAddCommand() {
|
|
5905
|
-
return buildCommentAddCommand("add", "Post a new comment thread on the pull request overview");
|
|
6072
|
+
return buildCommentAddCommand("add", "Post a new comment thread on the pull request overview, or on a file and line");
|
|
5906
6073
|
}
|
|
5907
6074
|
function createPrCommentAddCommand() {
|
|
5908
6075
|
return buildCommentAddCommand(
|
|
5909
6076
|
"comment-add",
|
|
5910
|
-
'Post a new comment thread on the pull request overview (alias of "azdo pr comments add")'
|
|
6077
|
+
'Post a new comment thread on the pull request overview, or on a file and line (alias of "azdo pr comments add")'
|
|
5911
6078
|
);
|
|
5912
6079
|
}
|
|
5913
6080
|
function parseExplicitCommentId(options) {
|
|
@@ -6197,13 +6364,15 @@ async function attachWorkItemIds(context, repo, cred, pullRequests) {
|
|
|
6197
6364
|
const result = new Array(pullRequests.length);
|
|
6198
6365
|
let next = 0;
|
|
6199
6366
|
const worker = async () => {
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
next += 1;
|
|
6203
|
-
const pullRequest = pullRequests[index];
|
|
6204
|
-
const workItemIds = await getPullRequestWorkItemIds(context, repo, cred, pullRequest.id);
|
|
6205
|
-
result[index] = { ...pullRequest, workItemIds };
|
|
6367
|
+
if (next >= pullRequests.length) {
|
|
6368
|
+
return;
|
|
6206
6369
|
}
|
|
6370
|
+
const index = next;
|
|
6371
|
+
next += 1;
|
|
6372
|
+
const pullRequest = pullRequests[index];
|
|
6373
|
+
const workItemIds = await getPullRequestWorkItemIds(context, repo, cred, pullRequest.id);
|
|
6374
|
+
result[index] = { ...pullRequest, workItemIds };
|
|
6375
|
+
await worker();
|
|
6207
6376
|
};
|
|
6208
6377
|
await Promise.all(Array.from({ length: Math.min(WORK_ITEM_LOOKUP_CONCURRENCY, pullRequests.length) }, worker));
|
|
6209
6378
|
return result;
|
|
@@ -8023,57 +8192,8 @@ function createDeleteAttachmentCommand() {
|
|
|
8023
8192
|
return command;
|
|
8024
8193
|
}
|
|
8025
8194
|
|
|
8026
|
-
// src/commands/delete-item.ts
|
|
8027
|
-
import { Command as Command19 } from "commander";
|
|
8028
|
-
function createDeleteItemCommand() {
|
|
8029
|
-
const command = new Command19("delete-item");
|
|
8030
|
-
command.description("Delete an Azure DevOps work item (moves it to the Recycle Bin unless --permanent)").argument("<id>", "work item ID").option("--permanent", "destroy the work item permanently instead of moving it to the Recycle Bin").option("-y, --yes", "skip the interactive confirmation prompt").option("--json", "output { id, permanent, deleted }").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").action(
|
|
8031
|
-
async (idStr, options) => {
|
|
8032
|
-
const id = parseWorkItemId(idStr);
|
|
8033
|
-
validateOrgProjectPair(options);
|
|
8034
|
-
const permanent = options.permanent === true;
|
|
8035
|
-
let context;
|
|
8036
|
-
try {
|
|
8037
|
-
context = resolveContext(options);
|
|
8038
|
-
const credential = await requireAuthCredential(context.org);
|
|
8039
|
-
let confirmed = options.yes === true;
|
|
8040
|
-
if (!confirmed) {
|
|
8041
|
-
if (!process.stdin.isTTY) {
|
|
8042
|
-
process.stderr.write(
|
|
8043
|
-
"Error: confirmation required. Re-run with --yes to skip the prompt in a non-interactive shell.\n"
|
|
8044
|
-
);
|
|
8045
|
-
process.exitCode = 1;
|
|
8046
|
-
return;
|
|
8047
|
-
}
|
|
8048
|
-
const question = permanent ? `Permanently delete work item ${id}? This cannot be undone. [y/N] ` : `Delete work item ${id} (moves it to the Recycle Bin)? [y/N] `;
|
|
8049
|
-
confirmed = await promptYesNo(question);
|
|
8050
|
-
}
|
|
8051
|
-
if (!confirmed) {
|
|
8052
|
-
process.stderr.write("Aborted: work item not deleted.\n");
|
|
8053
|
-
process.exitCode = 1;
|
|
8054
|
-
return;
|
|
8055
|
-
}
|
|
8056
|
-
await deleteWorkItem(context, id, credential, permanent);
|
|
8057
|
-
if (options.json) {
|
|
8058
|
-
process.stdout.write(`${JSON.stringify({ id, permanent, deleted: true }, null, 2)}
|
|
8059
|
-
`);
|
|
8060
|
-
} else {
|
|
8061
|
-
process.stdout.write(
|
|
8062
|
-
permanent ? `Permanently deleted work item ${id}
|
|
8063
|
-
` : `Deleted work item ${id}
|
|
8064
|
-
`
|
|
8065
|
-
);
|
|
8066
|
-
}
|
|
8067
|
-
} catch (err) {
|
|
8068
|
-
handleCommandError(err, id, context, "write");
|
|
8069
|
-
}
|
|
8070
|
-
}
|
|
8071
|
-
);
|
|
8072
|
-
return command;
|
|
8073
|
-
}
|
|
8074
|
-
|
|
8075
8195
|
// src/commands/relations.ts
|
|
8076
|
-
import { Command as
|
|
8196
|
+
import { Command as Command19 } from "commander";
|
|
8077
8197
|
|
|
8078
8198
|
// src/services/relations-client.ts
|
|
8079
8199
|
var API_VERSION2 = "7.1";
|
|
@@ -8290,7 +8410,7 @@ function parsePositiveInt(value, label) {
|
|
|
8290
8410
|
return n;
|
|
8291
8411
|
}
|
|
8292
8412
|
function createRelationsCommand() {
|
|
8293
|
-
const relations = new
|
|
8413
|
+
const relations = new Command19("relations").description("Manage work item relations");
|
|
8294
8414
|
addCommonOptions(
|
|
8295
8415
|
relations.command("types").description("List all available work item relation types")
|
|
8296
8416
|
).action(async (opts) => {
|
|
@@ -8485,7 +8605,7 @@ async function getUpdateNotice(opts) {
|
|
|
8485
8605
|
|
|
8486
8606
|
// src/program.ts
|
|
8487
8607
|
function createProgram() {
|
|
8488
|
-
const program2 = new
|
|
8608
|
+
const program2 = new Command20();
|
|
8489
8609
|
program2.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
8490
8610
|
program2.option("--no-update-check", "Skip the check for a newer published version");
|
|
8491
8611
|
program2.option("--trace <filepath>", "Append redacted HTTP request/response trace to a file (owner-read-only permissions)");
|
|
@@ -8507,7 +8627,6 @@ function createProgram() {
|
|
|
8507
8627
|
program2.addCommand(createDownloadAttachmentCommand());
|
|
8508
8628
|
program2.addCommand(createAddAttachmentCommand());
|
|
8509
8629
|
program2.addCommand(createDeleteAttachmentCommand());
|
|
8510
|
-
program2.addCommand(createDeleteItemCommand());
|
|
8511
8630
|
program2.addCommand(createRelationsCommand());
|
|
8512
8631
|
program2.showHelpAfterError();
|
|
8513
8632
|
program2.hook("preAction", () => {
|