azdo-cli 0.22.0-develop.823 → 0.22.0-develop.828

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.
Files changed (2) hide show
  1. package/dist/index.js +169 -56
  2. 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 && !existsSync(outputDir)) {
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 addWorkItemComment(context, id, cred, text, format = "html") {
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: "POST",
900
- headers: {
901
- ...authHeaders(cred),
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 ?? id,
916
- commentId: data.commentId ?? data.id ?? 0,
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 existsSync2, readFileSync as readFileSync2 } from "fs";
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 (!existsSync2(envFile)) return null;
1267
- const contents = readFileSync2(envFile, "utf8");
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 existsSync3, readFileSync as readFileSync3 } from "fs";
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 (!existsSync3(filePath)) {
3394
+ if (!existsSync4(filePath)) {
3342
3395
  fail(`File not found: ${filePath}`);
3343
3396
  }
3344
3397
  try {
3345
- return readFileSync3(filePath, "utf-8");
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 existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
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 (!existsSync4(filePath)) {
3627
+ if (!existsSync5(filePath)) {
3575
3628
  fail2(`File not found: ${filePath}`);
3576
3629
  }
3577
3630
  try {
3578
3631
  return {
3579
- content: readFileSync4(filePath, "utf-8"),
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
- var STDIN_PATH = "-";
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 readFileSync5(file, "utf-8");
4667
- } catch {
4668
- writeError(`Cannot read file: ${file}`);
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 = readTextSource(file);
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 = readTextSource(file);
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 = readTextSource(file);
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
- let context;
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
- context = resolveContext(options);
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
- handleCommandError(err, id, context, "write");
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.22.0-develop.823",
3
+ "version": "0.22.0-develop.828",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {