azdo-cli 0.22.0-develop.823 → 0.22.0-develop.853
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 +169 -56
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143,6 +143,7 @@ function getActiveTraceWriter() {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/services/command-helpers.ts
|
|
146
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
146
147
|
var EXIT_CREDENTIAL_STORE_UNAVAILABLE = 4;
|
|
147
148
|
function reportCredentialStoreUnavailable(err) {
|
|
148
149
|
if (!(err instanceof CredentialStoreUnavailableError)) return false;
|
|
@@ -279,11 +280,29 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
|
|
|
279
280
|
process.exitCode = 1;
|
|
280
281
|
}
|
|
281
282
|
}
|
|
283
|
+
var STDIN_PATH = "-";
|
|
284
|
+
function readTextSource(file) {
|
|
285
|
+
if (file === STDIN_PATH) {
|
|
286
|
+
try {
|
|
287
|
+
return readFileSync2(0, "utf-8");
|
|
288
|
+
} catch {
|
|
289
|
+
throw new Error("Cannot read standard input.");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!existsSync(file)) {
|
|
293
|
+
throw new Error(`File not found: ${file}`);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
return readFileSync2(file, "utf-8");
|
|
297
|
+
} catch {
|
|
298
|
+
throw new Error(`Cannot read file: ${file}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
282
301
|
|
|
283
302
|
// src/services/image-download.ts
|
|
284
303
|
import { Jimp } from "jimp";
|
|
285
304
|
import { writeFile } from "fs/promises";
|
|
286
|
-
import { existsSync } from "fs";
|
|
305
|
+
import { existsSync as existsSync2 } from "fs";
|
|
287
306
|
import { tmpdir } from "os";
|
|
288
307
|
import { join } from "path";
|
|
289
308
|
var ATTACHMENT_GUID_RE = /_apis\/wit\/attachments\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/;
|
|
@@ -366,7 +385,7 @@ function resolveImageDownloadOptions(flags) {
|
|
|
366
385
|
maxWidth = parsed;
|
|
367
386
|
}
|
|
368
387
|
const outputDir = flags.imagesPath ?? tmpdir();
|
|
369
|
-
if (flags.imagesPath !== void 0 && !
|
|
388
|
+
if (flags.imagesPath !== void 0 && !existsSync2(outputDir)) {
|
|
370
389
|
throw new Error(`Images path "${outputDir}" does not exist.`);
|
|
371
390
|
}
|
|
372
391
|
return { enabled, maxWidth, outputDir };
|
|
@@ -668,6 +687,11 @@ function buildWorkItemCommentsUrl(context, id) {
|
|
|
668
687
|
url.searchParams.set("api-version", "7.1-preview.4");
|
|
669
688
|
return url;
|
|
670
689
|
}
|
|
690
|
+
function buildWorkItemCommentUrl(context, id, commentId) {
|
|
691
|
+
const url = buildWorkItemCommentsUrl(context, id);
|
|
692
|
+
url.pathname = `${url.pathname}/${commentId}`;
|
|
693
|
+
return url;
|
|
694
|
+
}
|
|
671
695
|
function mapWorkItemComment(comment, fallbackWorkItemId) {
|
|
672
696
|
return {
|
|
673
697
|
id: comment.id ?? comment.commentId ?? 0,
|
|
@@ -892,16 +916,11 @@ async function listWorkItemComments(context, id, cred) {
|
|
|
892
916
|
comments
|
|
893
917
|
};
|
|
894
918
|
}
|
|
895
|
-
async function
|
|
896
|
-
const url = buildWorkItemCommentsUrl(context, id);
|
|
897
|
-
url.searchParams.set("format", format);
|
|
919
|
+
async function sendCommentRequest(url, cred, method, body) {
|
|
898
920
|
const response = await fetchWithErrors(url.toString(), {
|
|
899
|
-
method
|
|
900
|
-
headers: {
|
|
901
|
-
|
|
902
|
-
"Content-Type": "application/json"
|
|
903
|
-
},
|
|
904
|
-
body: JSON.stringify({ text })
|
|
921
|
+
method,
|
|
922
|
+
headers: body === void 0 ? authHeaders(cred) : { ...authHeaders(cred), "Content-Type": "application/json" },
|
|
923
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
905
924
|
});
|
|
906
925
|
if (response.status === 400) {
|
|
907
926
|
const serverMessage = await readResponseMessage(response) ?? "Unknown error";
|
|
@@ -910,16 +929,50 @@ async function addWorkItemComment(context, id, cred, text, format = "html") {
|
|
|
910
929
|
if (!response.ok) {
|
|
911
930
|
throw httpError(response);
|
|
912
931
|
}
|
|
932
|
+
return response;
|
|
933
|
+
}
|
|
934
|
+
async function writeCommentText(url, cred, method, text, format, fallback) {
|
|
935
|
+
url.searchParams.set("format", format);
|
|
936
|
+
const response = await sendCommentRequest(url, cred, method, { text });
|
|
913
937
|
const data = await response.json();
|
|
914
938
|
return {
|
|
915
|
-
workItemId: data.workItemId ??
|
|
916
|
-
commentId: data.commentId ?? data.id ??
|
|
939
|
+
workItemId: data.workItemId ?? fallback.workItemId,
|
|
940
|
+
commentId: data.commentId ?? data.id ?? fallback.commentId,
|
|
917
941
|
text: typeof data.text === "string" ? data.text : text,
|
|
918
942
|
author: data.createdBy?.displayName ?? null,
|
|
919
943
|
createdAt: data.createdDate ?? null,
|
|
944
|
+
modifiedAt: data.modifiedDate ?? null,
|
|
920
945
|
url: data.url ?? null
|
|
921
946
|
};
|
|
922
947
|
}
|
|
948
|
+
async function addWorkItemComment(context, id, cred, text, format = "html") {
|
|
949
|
+
const written = await writeCommentText(
|
|
950
|
+
buildWorkItemCommentsUrl(context, id),
|
|
951
|
+
cred,
|
|
952
|
+
"POST",
|
|
953
|
+
text,
|
|
954
|
+
format,
|
|
955
|
+
{ workItemId: id, commentId: 0 }
|
|
956
|
+
);
|
|
957
|
+
return {
|
|
958
|
+
workItemId: written.workItemId,
|
|
959
|
+
commentId: written.commentId,
|
|
960
|
+
text: written.text,
|
|
961
|
+
author: written.author,
|
|
962
|
+
createdAt: written.createdAt,
|
|
963
|
+
url: written.url
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
async function updateWorkItemComment(context, id, commentId, cred, text, format = "html") {
|
|
967
|
+
return writeCommentText(buildWorkItemCommentUrl(context, id, commentId), cred, "PATCH", text, format, {
|
|
968
|
+
workItemId: id,
|
|
969
|
+
commentId
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
async function deleteWorkItemComment(context, id, commentId, cred) {
|
|
973
|
+
await sendCommentRequest(buildWorkItemCommentUrl(context, id, commentId), cred, "DELETE");
|
|
974
|
+
return { workItemId: id, commentId, deleted: true };
|
|
975
|
+
}
|
|
923
976
|
async function updateWorkItem(context, id, cred, fieldName, operations) {
|
|
924
977
|
const result = await applyWorkItemPatch(context, id, cred, operations);
|
|
925
978
|
const title = result.fields["System.Title"];
|
|
@@ -1093,7 +1146,7 @@ async function queryWorkItems(context, cred, filter) {
|
|
|
1093
1146
|
|
|
1094
1147
|
// src/services/auth.ts
|
|
1095
1148
|
import { createInterface } from "readline";
|
|
1096
|
-
import { existsSync as
|
|
1149
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1097
1150
|
import { dirname as dirname2, join as join2 } from "path";
|
|
1098
1151
|
|
|
1099
1152
|
// src/services/oauth-device-code.ts
|
|
@@ -1263,8 +1316,8 @@ async function promptForPat() {
|
|
|
1263
1316
|
});
|
|
1264
1317
|
}
|
|
1265
1318
|
function patFromEnvFile(envFile) {
|
|
1266
|
-
if (!
|
|
1267
|
-
const contents =
|
|
1319
|
+
if (!existsSync3(envFile)) return null;
|
|
1320
|
+
const contents = readFileSync3(envFile, "utf8");
|
|
1268
1321
|
for (const line of contents.split("\n")) {
|
|
1269
1322
|
const match = line.match(/^AZDO_PAT\s*=([^\n\r]+)$/);
|
|
1270
1323
|
if (match === null) continue;
|
|
@@ -3318,7 +3371,7 @@ function createGetMdFieldCommand() {
|
|
|
3318
3371
|
}
|
|
3319
3372
|
|
|
3320
3373
|
// src/commands/set-md-field.ts
|
|
3321
|
-
import { existsSync as
|
|
3374
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
3322
3375
|
import { Command as Command10 } from "commander";
|
|
3323
3376
|
function fail(message) {
|
|
3324
3377
|
process.stderr.write(`Error: ${message}
|
|
@@ -3338,11 +3391,11 @@ function resolveContent(inlineContent, options) {
|
|
|
3338
3391
|
return null;
|
|
3339
3392
|
}
|
|
3340
3393
|
function readFileContent(filePath) {
|
|
3341
|
-
if (!
|
|
3394
|
+
if (!existsSync4(filePath)) {
|
|
3342
3395
|
fail(`File not found: ${filePath}`);
|
|
3343
3396
|
}
|
|
3344
3397
|
try {
|
|
3345
|
-
return
|
|
3398
|
+
return readFileSync4(filePath, "utf-8");
|
|
3346
3399
|
} catch {
|
|
3347
3400
|
fail(`Cannot read file: ${filePath}`);
|
|
3348
3401
|
}
|
|
@@ -3407,7 +3460,7 @@ function createSetMdFieldCommand() {
|
|
|
3407
3460
|
}
|
|
3408
3461
|
|
|
3409
3462
|
// src/commands/upsert.ts
|
|
3410
|
-
import { existsSync as
|
|
3463
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, unlinkSync } from "fs";
|
|
3411
3464
|
import { Command as Command11 } from "commander";
|
|
3412
3465
|
|
|
3413
3466
|
// src/services/task-document.ts
|
|
@@ -3571,12 +3624,12 @@ function loadSourceContent(options) {
|
|
|
3571
3624
|
return { content: options.content };
|
|
3572
3625
|
}
|
|
3573
3626
|
const filePath = options.file;
|
|
3574
|
-
if (!
|
|
3627
|
+
if (!existsSync5(filePath)) {
|
|
3575
3628
|
fail2(`File not found: ${filePath}`);
|
|
3576
3629
|
}
|
|
3577
3630
|
try {
|
|
3578
3631
|
return {
|
|
3579
|
-
content:
|
|
3632
|
+
content: readFileSync5(filePath, "utf-8"),
|
|
3580
3633
|
sourceFile: filePath
|
|
3581
3634
|
};
|
|
3582
3635
|
} catch {
|
|
@@ -3795,7 +3848,6 @@ function createListFieldsCommand() {
|
|
|
3795
3848
|
}
|
|
3796
3849
|
|
|
3797
3850
|
// src/commands/pr.ts
|
|
3798
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3799
3851
|
import { Command as Command13 } from "commander";
|
|
3800
3852
|
|
|
3801
3853
|
// src/services/pr-client.ts
|
|
@@ -4648,24 +4700,11 @@ function writeContractError(line) {
|
|
|
4648
4700
|
process.exitCode = 1;
|
|
4649
4701
|
}
|
|
4650
4702
|
var EMPTY_BODY_ERROR = "Comment text must not be empty. Pass the text inline or use --file <path>.";
|
|
4651
|
-
|
|
4652
|
-
function readTextSource(file) {
|
|
4653
|
-
if (file === STDIN_PATH) {
|
|
4654
|
-
try {
|
|
4655
|
-
return readFileSync5(0, "utf-8");
|
|
4656
|
-
} catch {
|
|
4657
|
-
writeError("Cannot read standard input.");
|
|
4658
|
-
return null;
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4661
|
-
if (!existsSync5(file)) {
|
|
4662
|
-
writeError(`File not found: ${file}`);
|
|
4663
|
-
return null;
|
|
4664
|
-
}
|
|
4703
|
+
function readTextSource2(file) {
|
|
4665
4704
|
try {
|
|
4666
|
-
return
|
|
4667
|
-
} catch {
|
|
4668
|
-
writeError(
|
|
4705
|
+
return readTextSource(file);
|
|
4706
|
+
} catch (err) {
|
|
4707
|
+
writeError(err.message);
|
|
4669
4708
|
return null;
|
|
4670
4709
|
}
|
|
4671
4710
|
}
|
|
@@ -4676,7 +4715,7 @@ function resolveCommentBody(inline, file, emptyMessage = EMPTY_BODY_ERROR) {
|
|
|
4676
4715
|
}
|
|
4677
4716
|
let body;
|
|
4678
4717
|
if (file !== void 0) {
|
|
4679
|
-
const read =
|
|
4718
|
+
const read = readTextSource2(file);
|
|
4680
4719
|
if (read === null) {
|
|
4681
4720
|
return null;
|
|
4682
4721
|
}
|
|
@@ -4706,7 +4745,7 @@ function resolveOptionalTextInput(inline, file, flag) {
|
|
|
4706
4745
|
const emptyMessage = `${label} must not be empty. Pass the text inline or use --${flag}-file <path>.`;
|
|
4707
4746
|
let value;
|
|
4708
4747
|
if (file !== void 0) {
|
|
4709
|
-
const read =
|
|
4748
|
+
const read = readTextSource2(file);
|
|
4710
4749
|
if (read === null) {
|
|
4711
4750
|
return null;
|
|
4712
4751
|
}
|
|
@@ -5067,7 +5106,7 @@ function resolveOpenDescription(inline, file) {
|
|
|
5067
5106
|
return null;
|
|
5068
5107
|
}
|
|
5069
5108
|
if (file !== void 0) {
|
|
5070
|
-
const fromFile =
|
|
5109
|
+
const fromFile = readTextSource2(file);
|
|
5071
5110
|
if (fromFile === null) {
|
|
5072
5111
|
return null;
|
|
5073
5112
|
}
|
|
@@ -7639,6 +7678,26 @@ function createPipelineCommand() {
|
|
|
7639
7678
|
|
|
7640
7679
|
// src/commands/comments.ts
|
|
7641
7680
|
import { Command as Command15 } from "commander";
|
|
7681
|
+
async function runCommentWrite(options, target, request, humanLine) {
|
|
7682
|
+
let context;
|
|
7683
|
+
try {
|
|
7684
|
+
context = resolveContext(options);
|
|
7685
|
+
const credential = await requireAuthCredential(context.org);
|
|
7686
|
+
const result = await request(context, credential);
|
|
7687
|
+
process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}
|
|
7688
|
+
` : `${humanLine(result)}
|
|
7689
|
+
`);
|
|
7690
|
+
} catch (err) {
|
|
7691
|
+
if (target.commentId === void 0) {
|
|
7692
|
+
handleCommandError(err, target.id, context, "write");
|
|
7693
|
+
} else {
|
|
7694
|
+
handleCommentWriteError(err, target.id, target.commentId, context);
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
function commentFormat(options) {
|
|
7699
|
+
return options.markdown === true ? "markdown" : "html";
|
|
7700
|
+
}
|
|
7642
7701
|
function writeError3(message) {
|
|
7643
7702
|
process.stderr.write(`Error: ${message}
|
|
7644
7703
|
`);
|
|
@@ -7693,22 +7752,74 @@ function createCommentsAddCommand() {
|
|
|
7693
7752
|
if (text.trim() === "") {
|
|
7694
7753
|
writeError3("Comment text must be a non-empty string.");
|
|
7695
7754
|
}
|
|
7696
|
-
|
|
7755
|
+
await runCommentWrite(
|
|
7756
|
+
options,
|
|
7757
|
+
{ id },
|
|
7758
|
+
(context, credential) => addWorkItemComment(context, id, credential, text, commentFormat(options)),
|
|
7759
|
+
(result) => `Added comment #${result.commentId} to work item #${result.workItemId}`
|
|
7760
|
+
);
|
|
7761
|
+
});
|
|
7762
|
+
return command;
|
|
7763
|
+
}
|
|
7764
|
+
function parseCommentId(value) {
|
|
7765
|
+
if (!/^\d+$/.test(value) || Number(value) < 1) {
|
|
7766
|
+
writeError3(`Invalid comment ID "${value}". Expected a positive integer.`);
|
|
7767
|
+
}
|
|
7768
|
+
return Number(value);
|
|
7769
|
+
}
|
|
7770
|
+
function resolveEditText(inline, file) {
|
|
7771
|
+
if (inline !== void 0 && file !== void 0) {
|
|
7772
|
+
writeError3("Cannot specify both inline text and --file.");
|
|
7773
|
+
}
|
|
7774
|
+
let body = inline;
|
|
7775
|
+
if (file !== void 0) {
|
|
7697
7776
|
try {
|
|
7698
|
-
|
|
7699
|
-
const credential = await requireAuthCredential(context.org);
|
|
7700
|
-
const format = options.markdown === true ? "markdown" : "html";
|
|
7701
|
-
const result = await addWorkItemComment(context, id, credential, text, format);
|
|
7702
|
-
if (options.json) {
|
|
7703
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
7704
|
-
`);
|
|
7705
|
-
return;
|
|
7706
|
-
}
|
|
7707
|
-
process.stdout.write(`Added comment #${result.commentId} to work item #${result.workItemId}
|
|
7708
|
-
`);
|
|
7777
|
+
body = readTextSource(file);
|
|
7709
7778
|
} catch (err) {
|
|
7710
|
-
|
|
7779
|
+
writeError3(err.message);
|
|
7711
7780
|
}
|
|
7781
|
+
}
|
|
7782
|
+
if (body === void 0 || body.trim() === "") {
|
|
7783
|
+
writeError3("Comment text must be a non-empty string. Pass the text inline or use --file <path>.");
|
|
7784
|
+
}
|
|
7785
|
+
return body;
|
|
7786
|
+
}
|
|
7787
|
+
function handleCommentWriteError(err, id, commentId, context) {
|
|
7788
|
+
if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
|
|
7789
|
+
writeError3(
|
|
7790
|
+
`Comment ${commentId} not found on work item ${id} in ${context?.org}/${context?.project}.`
|
|
7791
|
+
);
|
|
7792
|
+
}
|
|
7793
|
+
handleCommandError(err, id, context, "write");
|
|
7794
|
+
}
|
|
7795
|
+
function createCommentsEditCommand() {
|
|
7796
|
+
const command = new Command15("edit");
|
|
7797
|
+
command.description("Edit an existing comment on a work item").argument("<id>", "work item ID").argument("<commentId>", "comment ID").argument("[text]", "new comment text (or use --file)").option("--file <path>", 'read the new comment text from a file ("-" for stdin)').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, commentIdStr, text, options) => {
|
|
7798
|
+
validateOrgProjectPair(options);
|
|
7799
|
+
const id = parseWorkItemId(idStr);
|
|
7800
|
+
const commentId = parseCommentId(commentIdStr);
|
|
7801
|
+
const body = resolveEditText(text, options.file);
|
|
7802
|
+
await runCommentWrite(
|
|
7803
|
+
options,
|
|
7804
|
+
{ id, commentId },
|
|
7805
|
+
(context, credential) => updateWorkItemComment(context, id, commentId, credential, body, commentFormat(options)),
|
|
7806
|
+
(result) => `Updated comment #${result.commentId} on work item #${result.workItemId}`
|
|
7807
|
+
);
|
|
7808
|
+
});
|
|
7809
|
+
return command;
|
|
7810
|
+
}
|
|
7811
|
+
function createCommentsDeleteCommand() {
|
|
7812
|
+
const command = new Command15("delete");
|
|
7813
|
+
command.description("Delete a comment from a work item (no confirmation prompt)").argument("<id>", "work item ID").argument("<commentId>", "comment ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (idStr, commentIdStr, options) => {
|
|
7814
|
+
validateOrgProjectPair(options);
|
|
7815
|
+
const id = parseWorkItemId(idStr);
|
|
7816
|
+
const commentId = parseCommentId(commentIdStr);
|
|
7817
|
+
await runCommentWrite(
|
|
7818
|
+
options,
|
|
7819
|
+
{ id, commentId },
|
|
7820
|
+
(context, credential) => deleteWorkItemComment(context, id, commentId, credential),
|
|
7821
|
+
(result) => `Deleted comment #${result.commentId} from work item #${result.workItemId}`
|
|
7822
|
+
);
|
|
7712
7823
|
});
|
|
7713
7824
|
return command;
|
|
7714
7825
|
}
|
|
@@ -7717,6 +7828,8 @@ function createCommentsCommand() {
|
|
|
7717
7828
|
command.description("Manage Azure DevOps work item comments");
|
|
7718
7829
|
command.addCommand(createCommentsListCommand());
|
|
7719
7830
|
command.addCommand(createCommentsAddCommand());
|
|
7831
|
+
command.addCommand(createCommentsEditCommand());
|
|
7832
|
+
command.addCommand(createCommentsDeleteCommand());
|
|
7720
7833
|
return command;
|
|
7721
7834
|
}
|
|
7722
7835
|
|