azdo-cli 0.10.0-develop.479 → 0.10.0-develop.501
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 +9 -1
- package/dist/index.js +303 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ azdo comments list 12345
|
|
|
54
54
|
azdo comments add 12345 "Investigating the root cause now."
|
|
55
55
|
|
|
56
56
|
# PR comment threads — list, filter, target by number, resolve or reopen
|
|
57
|
-
azdo pr comments # active-branch PR
|
|
57
|
+
azdo pr comments # active-branch PR; code-anchored threads show file:line
|
|
58
58
|
azdo pr comments --pr-number 64 # any PR by number (skips branch lookup)
|
|
59
59
|
azdo pr comments --pr-number 64 --hide-resolved # or --exclude-resolved (alias)
|
|
60
60
|
azdo pr comments --code-related-only # only file/line-anchored threads
|
|
@@ -68,6 +68,14 @@ azdo pipeline get-runs 12 --branch develop --limit 1
|
|
|
68
68
|
azdo pipeline wait 3456 # blocks; exit 0 success / non-zero failure / 124 timeout
|
|
69
69
|
azdo pipeline get-run-detail 3456 # errors, failing tests, per-stage status
|
|
70
70
|
azdo pipeline start 12 --branch develop --parameter env=staging
|
|
71
|
+
|
|
72
|
+
# Work item relations — types, add, remove, list
|
|
73
|
+
azdo relations types # list all relation types (Child, Parent, Related, ...)
|
|
74
|
+
azdo relations types --json # machine-readable JSON array
|
|
75
|
+
azdo relations add child 1000 2000 # make #2000 a child of #1000 (idempotent)
|
|
76
|
+
azdo relations remove child 1000 2000 # remove the child relation
|
|
77
|
+
azdo relations list 1000 # show all relations on work item #1000
|
|
78
|
+
azdo relations list 1000 --json # JSON: { workItemId, relations: [...] }
|
|
71
79
|
```
|
|
72
80
|
|
|
73
81
|
## Documentation
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
} from "./chunk-XVXMDWQE.js";
|
|
39
39
|
|
|
40
40
|
// src/index.ts
|
|
41
|
-
import { Command as
|
|
41
|
+
import { Command as Command17 } from "commander";
|
|
42
42
|
|
|
43
43
|
// src/version.ts
|
|
44
44
|
import { readFileSync } from "fs";
|
|
@@ -3034,18 +3034,22 @@ function mapThread(thread) {
|
|
|
3034
3034
|
if (comments.length === 0) {
|
|
3035
3035
|
return null;
|
|
3036
3036
|
}
|
|
3037
|
+
const line = thread.threadContext?.rightFileStart?.line ?? thread.threadContext?.leftFileStart?.line ?? null;
|
|
3037
3038
|
return {
|
|
3038
3039
|
id: thread.id,
|
|
3039
3040
|
status: thread.status ?? "unknown",
|
|
3040
3041
|
threadContext: thread.threadContext?.filePath ?? null,
|
|
3042
|
+
line,
|
|
3041
3043
|
comments
|
|
3042
3044
|
};
|
|
3043
3045
|
}
|
|
3044
3046
|
function toActiveCommentThread(thread) {
|
|
3047
|
+
const line = thread.threadContext?.rightFileStart?.line ?? thread.threadContext?.leftFileStart?.line ?? null;
|
|
3045
3048
|
return {
|
|
3046
3049
|
id: thread.id,
|
|
3047
3050
|
status: thread.status ?? "unknown",
|
|
3048
3051
|
threadContext: thread.threadContext?.filePath ?? null,
|
|
3052
|
+
line,
|
|
3049
3053
|
comments: thread.comments.map(mapComment).filter((comment) => comment !== null)
|
|
3050
3054
|
};
|
|
3051
3055
|
}
|
|
@@ -3333,7 +3337,9 @@ function threadStatusLabel(status2) {
|
|
|
3333
3337
|
function formatThreads(prId, title, threads) {
|
|
3334
3338
|
const lines = [`Comment threads for pull request #${prId}: ${title}`];
|
|
3335
3339
|
for (const thread of threads) {
|
|
3336
|
-
|
|
3340
|
+
const lineSuffix = thread.line === null ? "" : `:${thread.line}`;
|
|
3341
|
+
const location = thread.threadContext ? `${thread.threadContext}${lineSuffix}` : "(general)";
|
|
3342
|
+
lines.push("", `Thread #${thread.id} [${threadStatusLabel(thread.status)}] ${location}`);
|
|
3337
3343
|
for (const comment of thread.comments) {
|
|
3338
3344
|
lines.push(` ${comment.author ?? "Unknown"}: ${comment.content}`);
|
|
3339
3345
|
}
|
|
@@ -4703,6 +4709,299 @@ function createDownloadAttachmentCommand() {
|
|
|
4703
4709
|
return command;
|
|
4704
4710
|
}
|
|
4705
4711
|
|
|
4712
|
+
// src/commands/relations.ts
|
|
4713
|
+
import { Command as Command16 } from "commander";
|
|
4714
|
+
|
|
4715
|
+
// src/services/relations-client.ts
|
|
4716
|
+
var API_VERSION2 = "7.1";
|
|
4717
|
+
function buildRelationTypesUrl(context) {
|
|
4718
|
+
const url = new URL(
|
|
4719
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/_apis/wit/workitemrelationtypes`
|
|
4720
|
+
);
|
|
4721
|
+
url.searchParams.set("api-version", API_VERSION2);
|
|
4722
|
+
return url;
|
|
4723
|
+
}
|
|
4724
|
+
function buildWorkItemUrl2(context, id, expand) {
|
|
4725
|
+
const url = new URL(
|
|
4726
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitems/${id}`
|
|
4727
|
+
);
|
|
4728
|
+
url.searchParams.set("api-version", API_VERSION2);
|
|
4729
|
+
if (expand) url.searchParams.set("$expand", expand);
|
|
4730
|
+
return url;
|
|
4731
|
+
}
|
|
4732
|
+
function buildBatchWorkItemsUrl(context, ids) {
|
|
4733
|
+
const url = new URL(
|
|
4734
|
+
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/wit/workitems`
|
|
4735
|
+
);
|
|
4736
|
+
url.searchParams.set("ids", ids.join(","));
|
|
4737
|
+
url.searchParams.set("fields", "System.Id,System.Title");
|
|
4738
|
+
url.searchParams.set("api-version", API_VERSION2);
|
|
4739
|
+
return url;
|
|
4740
|
+
}
|
|
4741
|
+
function mapRelationType(raw) {
|
|
4742
|
+
return {
|
|
4743
|
+
referenceName: raw.referenceName,
|
|
4744
|
+
name: raw.name,
|
|
4745
|
+
usage: raw.attributes?.usage ?? "unknown",
|
|
4746
|
+
enabled: raw.attributes?.enabled !== false,
|
|
4747
|
+
directional: raw.attributes?.directional ?? null
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
4750
|
+
async function readJsonResponse3(response) {
|
|
4751
|
+
if (!response.ok) throw new Error(`HTTP_${response.status}`);
|
|
4752
|
+
return await response.json();
|
|
4753
|
+
}
|
|
4754
|
+
function parseTargetId(url) {
|
|
4755
|
+
const match = /\/workItems\/(\d+)$/i.exec(url);
|
|
4756
|
+
return match ? Number(match[1]) : null;
|
|
4757
|
+
}
|
|
4758
|
+
async function getWorkItemRelationTypes(context, cred) {
|
|
4759
|
+
const url = buildRelationTypesUrl(context);
|
|
4760
|
+
const response = await fetchWithErrors(url.toString(), {
|
|
4761
|
+
headers: authHeaders(cred)
|
|
4762
|
+
});
|
|
4763
|
+
const body = await readJsonResponse3(response);
|
|
4764
|
+
return (body.value ?? []).map(mapRelationType).filter((t) => t.usage === "workItemLink" && t.enabled);
|
|
4765
|
+
}
|
|
4766
|
+
async function resolveRelationType(context, cred, alias) {
|
|
4767
|
+
const types = await getWorkItemRelationTypes(context, cred);
|
|
4768
|
+
const lower = alias.toLowerCase();
|
|
4769
|
+
const match = types.find((t) => t.name.toLowerCase() === lower);
|
|
4770
|
+
if (!match) throw new Error(`UNKNOWN_RELATION_TYPE:${alias}`);
|
|
4771
|
+
return match;
|
|
4772
|
+
}
|
|
4773
|
+
async function getWorkItemWithRelations(context, cred, id) {
|
|
4774
|
+
const url = buildWorkItemUrl2(context, id, "relations");
|
|
4775
|
+
const response = await fetchWithErrors(url.toString(), {
|
|
4776
|
+
headers: authHeaders(cred)
|
|
4777
|
+
});
|
|
4778
|
+
return readJsonResponse3(response);
|
|
4779
|
+
}
|
|
4780
|
+
async function addWorkItemRelation(context, cred, type, id1, id2) {
|
|
4781
|
+
if (id1 === id2) throw new Error("SELF_RELATION");
|
|
4782
|
+
const relType = await resolveRelationType(context, cred, type);
|
|
4783
|
+
const workItem = await getWorkItemWithRelations(context, cred, id1);
|
|
4784
|
+
const targetUrl = `https://dev.azure.com/${encodeURIComponent(context.org)}/_apis/wit/workItems/${id2}`;
|
|
4785
|
+
const existing = (workItem.relations ?? []).find(
|
|
4786
|
+
(r) => r.rel === relType.referenceName && r.url.toLowerCase() === targetUrl.toLowerCase()
|
|
4787
|
+
);
|
|
4788
|
+
if (existing) {
|
|
4789
|
+
return { status: "already_exists", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
4790
|
+
}
|
|
4791
|
+
const patchUrl = buildWorkItemUrl2(context, id1);
|
|
4792
|
+
const response = await fetchWithErrors(patchUrl.toString(), {
|
|
4793
|
+
method: "PATCH",
|
|
4794
|
+
headers: { ...authHeaders(cred), "Content-Type": "application/json-patch+json" },
|
|
4795
|
+
body: JSON.stringify([
|
|
4796
|
+
{ op: "add", path: "/relations/-", value: { rel: relType.referenceName, url: targetUrl } }
|
|
4797
|
+
])
|
|
4798
|
+
});
|
|
4799
|
+
if (!response.ok) throw new Error(`HTTP_${response.status}`);
|
|
4800
|
+
return { status: "added", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
4801
|
+
}
|
|
4802
|
+
async function removeWorkItemRelation(context, cred, type, id1, id2) {
|
|
4803
|
+
if (id1 === id2) throw new Error("SELF_RELATION");
|
|
4804
|
+
const relType = await resolveRelationType(context, cred, type);
|
|
4805
|
+
const workItem = await getWorkItemWithRelations(context, cred, id1);
|
|
4806
|
+
const relations = workItem.relations ?? [];
|
|
4807
|
+
const index = relations.findIndex(
|
|
4808
|
+
(r) => r.rel === relType.referenceName && parseTargetId(r.url) === id2
|
|
4809
|
+
);
|
|
4810
|
+
if (index === -1) {
|
|
4811
|
+
return { status: "not_found", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
4812
|
+
}
|
|
4813
|
+
const patchUrl = buildWorkItemUrl2(context, id1);
|
|
4814
|
+
const response = await fetchWithErrors(patchUrl.toString(), {
|
|
4815
|
+
method: "PATCH",
|
|
4816
|
+
headers: { ...authHeaders(cred), "Content-Type": "application/json-patch+json" },
|
|
4817
|
+
body: JSON.stringify([{ op: "remove", path: `/relations/${index}` }])
|
|
4818
|
+
});
|
|
4819
|
+
if (!response.ok) throw new Error(`HTTP_${response.status}`);
|
|
4820
|
+
return { status: "removed", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
4821
|
+
}
|
|
4822
|
+
async function listWorkItemRelations(context, cred, id) {
|
|
4823
|
+
const workItem = await getWorkItemWithRelations(context, cred, id);
|
|
4824
|
+
const allRelations = workItem.relations ?? [];
|
|
4825
|
+
const wiRelations = allRelations.filter(
|
|
4826
|
+
(r) => !r.rel.startsWith("AttachedFile") && !r.rel.startsWith("Hyperlink") && !r.rel.startsWith("ArtifactLink")
|
|
4827
|
+
);
|
|
4828
|
+
if (wiRelations.length === 0) {
|
|
4829
|
+
return { workItemId: id, relations: [] };
|
|
4830
|
+
}
|
|
4831
|
+
const types = await getWorkItemRelationTypes(context, cred);
|
|
4832
|
+
const typeNameMap = new Map(types.map((t) => [t.referenceName, t.name]));
|
|
4833
|
+
const targetIds = wiRelations.map((r) => parseTargetId(r.url)).filter((n) => n !== null);
|
|
4834
|
+
const titleMap = /* @__PURE__ */ new Map();
|
|
4835
|
+
if (targetIds.length > 0) {
|
|
4836
|
+
try {
|
|
4837
|
+
const batchUrl = buildBatchWorkItemsUrl(context, targetIds);
|
|
4838
|
+
const batchResponse = await fetchWithErrors(batchUrl.toString(), {
|
|
4839
|
+
headers: authHeaders(cred)
|
|
4840
|
+
});
|
|
4841
|
+
const batchBody = await readJsonResponse3(batchResponse);
|
|
4842
|
+
for (const item of batchBody.value ?? []) {
|
|
4843
|
+
titleMap.set(item.id, item.fields["System.Title"] ?? "");
|
|
4844
|
+
}
|
|
4845
|
+
} catch {
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
const relations = wiRelations.map((r) => {
|
|
4849
|
+
const targetId = parseTargetId(r.url);
|
|
4850
|
+
return {
|
|
4851
|
+
rel: r.rel,
|
|
4852
|
+
relName: typeNameMap.get(r.rel) ?? r.rel,
|
|
4853
|
+
targetId: targetId ?? 0,
|
|
4854
|
+
targetTitle: targetId !== null ? titleMap.get(targetId) ?? null : null,
|
|
4855
|
+
targetUrl: r.url,
|
|
4856
|
+
comment: r.attributes?.comment ?? null
|
|
4857
|
+
};
|
|
4858
|
+
});
|
|
4859
|
+
return { workItemId: id, relations };
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
// src/commands/relations.ts
|
|
4863
|
+
function addCommonOptions(cmd) {
|
|
4864
|
+
return cmd.option("--json", "Output as JSON").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project");
|
|
4865
|
+
}
|
|
4866
|
+
async function resolveCredentials(opts) {
|
|
4867
|
+
const context = resolveContext(opts);
|
|
4868
|
+
const cred = await requireAuthCredential(context.org);
|
|
4869
|
+
return { context, cred };
|
|
4870
|
+
}
|
|
4871
|
+
function formatRelationTypes(types) {
|
|
4872
|
+
if (types.length === 0) return "No work item relation types found.";
|
|
4873
|
+
const nameWidth = Math.max(...types.map((t) => t.name.length), 4);
|
|
4874
|
+
const lines = ["Available work item relation types:", ""];
|
|
4875
|
+
for (const t of types) {
|
|
4876
|
+
lines.push(`${t.name.padEnd(nameWidth + 2)}${t.referenceName}`);
|
|
4877
|
+
}
|
|
4878
|
+
return lines.join("\n");
|
|
4879
|
+
}
|
|
4880
|
+
function formatRelationsList(workItemId, relations) {
|
|
4881
|
+
if (relations.length === 0) return `Work item #${workItemId} has no relations.`;
|
|
4882
|
+
const typeWidth = Math.max(...relations.map((r) => r.relName.length), 4) + 2;
|
|
4883
|
+
const idWidth = Math.max(...relations.map((r) => String(r.targetId).length), 4) + 1;
|
|
4884
|
+
const lines = [`Relations for work item #${workItemId}:`, ""];
|
|
4885
|
+
for (const r of relations) {
|
|
4886
|
+
const typeLabel = `[${r.relName}]`.padEnd(typeWidth);
|
|
4887
|
+
const idLabel = `#${r.targetId}`.padEnd(idWidth);
|
|
4888
|
+
lines.push(`${typeLabel} ${idLabel} ${r.targetTitle ?? "(unknown)"}`);
|
|
4889
|
+
}
|
|
4890
|
+
return lines.join("\n");
|
|
4891
|
+
}
|
|
4892
|
+
function handleRelationError(err, id1) {
|
|
4893
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4894
|
+
if (msg === "SELF_RELATION") {
|
|
4895
|
+
process.stderr.write(`Error: cannot relate a work item to itself (#${id1}).
|
|
4896
|
+
`);
|
|
4897
|
+
} else if (msg.startsWith("UNKNOWN_RELATION_TYPE:")) {
|
|
4898
|
+
const name = msg.replace("UNKNOWN_RELATION_TYPE:", "");
|
|
4899
|
+
process.stderr.write(
|
|
4900
|
+
`Error: unknown relation type "${name}". Run 'azdo relations types' to see valid names.
|
|
4901
|
+
`
|
|
4902
|
+
);
|
|
4903
|
+
} else if (msg.startsWith("NOT_FOUND")) {
|
|
4904
|
+
const target = id1 !== void 0 ? id1 : "unknown";
|
|
4905
|
+
process.stderr.write(`Error: work item #${target} not found.
|
|
4906
|
+
`);
|
|
4907
|
+
} else if (msg === "AUTH_FAILED") {
|
|
4908
|
+
process.stderr.write(
|
|
4909
|
+
`Error: authentication failed. Check your PAT has Work Items \u2192 Read & Write scope.
|
|
4910
|
+
`
|
|
4911
|
+
);
|
|
4912
|
+
} else {
|
|
4913
|
+
process.stderr.write(`Error: ${msg}
|
|
4914
|
+
`);
|
|
4915
|
+
}
|
|
4916
|
+
process.exit(1);
|
|
4917
|
+
}
|
|
4918
|
+
function parsePositiveInt(value, label) {
|
|
4919
|
+
const n = Number(value);
|
|
4920
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
4921
|
+
process.stderr.write(`Error: ${label} must be a positive integer.
|
|
4922
|
+
`);
|
|
4923
|
+
process.exit(1);
|
|
4924
|
+
}
|
|
4925
|
+
return n;
|
|
4926
|
+
}
|
|
4927
|
+
function createRelationsCommand() {
|
|
4928
|
+
const relations = new Command16("relations").description("Manage work item relations");
|
|
4929
|
+
addCommonOptions(
|
|
4930
|
+
relations.command("types").description("List all available work item relation types")
|
|
4931
|
+
).action(async (opts) => {
|
|
4932
|
+
try {
|
|
4933
|
+
const { context, cred } = await resolveCredentials(opts);
|
|
4934
|
+
const types = await getWorkItemRelationTypes(context, cred);
|
|
4935
|
+
if (opts.json) {
|
|
4936
|
+
process.stdout.write(JSON.stringify(types, null, 2) + "\n");
|
|
4937
|
+
} else {
|
|
4938
|
+
process.stdout.write(formatRelationTypes(types) + "\n");
|
|
4939
|
+
}
|
|
4940
|
+
} catch (err) {
|
|
4941
|
+
handleRelationError(err);
|
|
4942
|
+
}
|
|
4943
|
+
});
|
|
4944
|
+
addCommonOptions(
|
|
4945
|
+
relations.command("add <type> <id1> <id2>").description("Add a directed relation from work item <id1> to <id2>")
|
|
4946
|
+
).action(async (type, id1Str, id2Str, opts) => {
|
|
4947
|
+
const id1 = parsePositiveInt(id1Str, "id1");
|
|
4948
|
+
const id2 = parsePositiveInt(id2Str, "id2");
|
|
4949
|
+
try {
|
|
4950
|
+
const { context, cred } = await resolveCredentials(opts);
|
|
4951
|
+
const result = await addWorkItemRelation(context, cred, type, id1, id2);
|
|
4952
|
+
if (opts.json) {
|
|
4953
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
4954
|
+
} else if (result.status === "already_exists") {
|
|
4955
|
+
process.stdout.write(`Relation already exists: #${id1} --[${result.type}]--> #${id2}
|
|
4956
|
+
`);
|
|
4957
|
+
} else {
|
|
4958
|
+
process.stdout.write(`Added relation: #${id1} --[${result.type}]--> #${id2}
|
|
4959
|
+
`);
|
|
4960
|
+
}
|
|
4961
|
+
} catch (err) {
|
|
4962
|
+
handleRelationError(err, id1);
|
|
4963
|
+
}
|
|
4964
|
+
});
|
|
4965
|
+
addCommonOptions(
|
|
4966
|
+
relations.command("remove <type> <id1> <id2>").description("Remove a directed relation of <type> from work item <id1> to <id2>")
|
|
4967
|
+
).action(async (type, id1Str, id2Str, opts) => {
|
|
4968
|
+
const id1 = parsePositiveInt(id1Str, "id1");
|
|
4969
|
+
const id2 = parsePositiveInt(id2Str, "id2");
|
|
4970
|
+
try {
|
|
4971
|
+
const { context, cred } = await resolveCredentials(opts);
|
|
4972
|
+
const result = await removeWorkItemRelation(context, cred, type, id1, id2);
|
|
4973
|
+
if (opts.json) {
|
|
4974
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
4975
|
+
} else if (result.status === "not_found") {
|
|
4976
|
+
process.stdout.write(`No relation of type '${result.type}' found between #${id1} and #${id2}
|
|
4977
|
+
`);
|
|
4978
|
+
} else {
|
|
4979
|
+
process.stdout.write(`Removed relation: #${id1} --[${result.type}]--> #${id2}
|
|
4980
|
+
`);
|
|
4981
|
+
}
|
|
4982
|
+
} catch (err) {
|
|
4983
|
+
handleRelationError(err, id1);
|
|
4984
|
+
}
|
|
4985
|
+
});
|
|
4986
|
+
addCommonOptions(
|
|
4987
|
+
relations.command("list <id>").description("List all work item link relations on a work item")
|
|
4988
|
+
).action(async (idStr, opts) => {
|
|
4989
|
+
const id = parsePositiveInt(idStr, "id");
|
|
4990
|
+
try {
|
|
4991
|
+
const { context, cred } = await resolveCredentials(opts);
|
|
4992
|
+
const result = await listWorkItemRelations(context, cred, id);
|
|
4993
|
+
if (opts.json) {
|
|
4994
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
4995
|
+
} else {
|
|
4996
|
+
process.stdout.write(formatRelationsList(result.workItemId, result.relations) + "\n");
|
|
4997
|
+
}
|
|
4998
|
+
} catch (err) {
|
|
4999
|
+
handleRelationError(err, id);
|
|
5000
|
+
}
|
|
5001
|
+
});
|
|
5002
|
+
return relations;
|
|
5003
|
+
}
|
|
5004
|
+
|
|
4706
5005
|
// src/services/update-check.ts
|
|
4707
5006
|
import fs2 from "fs";
|
|
4708
5007
|
import path2 from "path";
|
|
@@ -4816,7 +5115,7 @@ function exitOnEpipe(err) {
|
|
|
4816
5115
|
}
|
|
4817
5116
|
process.stdout.on("error", exitOnEpipe);
|
|
4818
5117
|
process.stderr.on("error", exitOnEpipe);
|
|
4819
|
-
var program = new
|
|
5118
|
+
var program = new Command17();
|
|
4820
5119
|
program.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
4821
5120
|
program.option("--no-update-check", "Skip the check for a newer published version");
|
|
4822
5121
|
program.addCommand(createGetItemCommand());
|
|
@@ -4834,6 +5133,7 @@ program.addCommand(createPrCommand());
|
|
|
4834
5133
|
program.addCommand(createPipelineCommand());
|
|
4835
5134
|
program.addCommand(createCommentsCommand());
|
|
4836
5135
|
program.addCommand(createDownloadAttachmentCommand());
|
|
5136
|
+
program.addCommand(createRelationsCommand());
|
|
4837
5137
|
program.showHelpAfterError();
|
|
4838
5138
|
program.hook("postAction", async () => {
|
|
4839
5139
|
const notice = await getUpdateNotice({ enabled: program.opts().updateCheck });
|