@thanaen/ado-cli 0.1.0 → 0.3.0
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 +6 -2
- package/dist/cli.js +242 -7
- package/package.json +1 -1
- package/skills/ado-workflows/SKILL.md +4 -0
package/README.md
CHANGED
|
@@ -37,8 +37,8 @@ On-prem / localserver example:
|
|
|
37
37
|
```bash
|
|
38
38
|
export DEVOPS_PAT="***"
|
|
39
39
|
export ADO_COLLECTION_URL="https://localserver/DefaultCollection"
|
|
40
|
-
export ADO_PROJECT="
|
|
41
|
-
export ADO_REPO="
|
|
40
|
+
export ADO_PROJECT="ExampleProject"
|
|
41
|
+
export ADO_REPO="Example Repository"
|
|
42
42
|
export ADO_INSECURE=1
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -59,6 +59,9 @@ ado smoke
|
|
|
59
59
|
|
|
60
60
|
## Commands
|
|
61
61
|
|
|
62
|
+
- `-v`, `--version`
|
|
63
|
+
- `init [--local]`
|
|
64
|
+
- `config`
|
|
62
65
|
- `smoke`
|
|
63
66
|
- `repos`
|
|
64
67
|
- `branches [repo]`
|
|
@@ -71,6 +74,7 @@ ado smoke
|
|
|
71
74
|
- `pr-get <id> [repo]`
|
|
72
75
|
- `pr-create --title=... --source=... --target=... [--description=...] [--repo=...] [--work-items=123,456]`
|
|
73
76
|
- `pr-update <id> [--title=...] [--description=...] [--repo=...] [--work-items=123,456]`
|
|
77
|
+
- `pr-cherry-pick <id> --target=... [--topic=branch-name] [--repo=...]`
|
|
74
78
|
- `pr-approve <id> [repo]`
|
|
75
79
|
- `pr-autocomplete <id> [repo]`
|
|
76
80
|
- `builds [top]`
|
package/dist/cli.js
CHANGED
|
@@ -57181,27 +57181,78 @@ import { readFile } from "node:fs/promises";
|
|
|
57181
57181
|
|
|
57182
57182
|
// src/config.ts
|
|
57183
57183
|
var import_azure_devops_node_api = __toESM(require_WebApi(), 1);
|
|
57184
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
57185
|
+
import { join } from "node:path";
|
|
57186
|
+
import { homedir } from "node:os";
|
|
57184
57187
|
var DEFAULT_COLLECTION_URL = "https://dev.azure.com/<your-org>";
|
|
57185
57188
|
var DEFAULT_PROJECT = "<your-project>";
|
|
57186
57189
|
var DEFAULT_REPO = "<your-repository>";
|
|
57190
|
+
var LOCAL_CONFIG_FILENAME = "ado.json";
|
|
57187
57191
|
function isDefaultPlaceholder(value) {
|
|
57188
57192
|
return value.includes("<your-");
|
|
57189
57193
|
}
|
|
57194
|
+
function getConfigDir() {
|
|
57195
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
57196
|
+
const base = xdgConfig && xdgConfig.length > 0 ? xdgConfig : join(homedir(), ".config");
|
|
57197
|
+
return join(base, "ado");
|
|
57198
|
+
}
|
|
57199
|
+
function getConfigFilePath() {
|
|
57200
|
+
return join(getConfigDir(), "config.json");
|
|
57201
|
+
}
|
|
57202
|
+
function loadFileConfig() {
|
|
57203
|
+
const configPath = getConfigFilePath();
|
|
57204
|
+
if (!existsSync(configPath)) {
|
|
57205
|
+
return {};
|
|
57206
|
+
}
|
|
57207
|
+
const content = readFileSync(configPath, "utf8");
|
|
57208
|
+
try {
|
|
57209
|
+
return JSON.parse(content);
|
|
57210
|
+
} catch {
|
|
57211
|
+
console.error(`Warning: could not parse config file at ${configPath}. Using defaults.`);
|
|
57212
|
+
return {};
|
|
57213
|
+
}
|
|
57214
|
+
}
|
|
57215
|
+
function getLocalConfigFilePath() {
|
|
57216
|
+
return join(process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
57217
|
+
}
|
|
57218
|
+
function loadLocalConfig() {
|
|
57219
|
+
const localPath = getLocalConfigFilePath();
|
|
57220
|
+
if (!existsSync(localPath)) {
|
|
57221
|
+
return {};
|
|
57222
|
+
}
|
|
57223
|
+
const content = readFileSync(localPath, "utf8");
|
|
57224
|
+
try {
|
|
57225
|
+
return JSON.parse(content);
|
|
57226
|
+
} catch {
|
|
57227
|
+
console.error(`Warning: could not parse local config file at ${localPath}. Please check the JSON syntax. Ignoring.`);
|
|
57228
|
+
return {};
|
|
57229
|
+
}
|
|
57230
|
+
}
|
|
57231
|
+
function censorPat(pat) {
|
|
57232
|
+
if (pat.length <= 8) {
|
|
57233
|
+
return "****";
|
|
57234
|
+
}
|
|
57235
|
+
return `${pat.slice(0, 4)}${"*".repeat(pat.length - 8)}${pat.slice(-4)}`;
|
|
57236
|
+
}
|
|
57190
57237
|
function getConfig() {
|
|
57191
|
-
const
|
|
57238
|
+
const fileConfig = loadFileConfig();
|
|
57239
|
+
const localConfig = loadLocalConfig();
|
|
57240
|
+
const pat = process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
|
|
57192
57241
|
if (!pat) {
|
|
57193
|
-
console.error("Missing DEVOPS_PAT environment variable.");
|
|
57242
|
+
console.error("Missing DEVOPS_PAT environment variable or pat in config file.");
|
|
57243
|
+
console.error(`Run "ado init" to create a config file at ${getConfigFilePath()}`);
|
|
57194
57244
|
process.exit(1);
|
|
57195
57245
|
}
|
|
57196
|
-
const collectionUrl = process.env.ADO_COLLECTION_URL ?? DEFAULT_COLLECTION_URL;
|
|
57197
|
-
const project = process.env.ADO_PROJECT ?? DEFAULT_PROJECT;
|
|
57198
|
-
const repo = process.env.ADO_REPO ?? DEFAULT_REPO;
|
|
57246
|
+
const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl ?? DEFAULT_COLLECTION_URL;
|
|
57247
|
+
const project = process.env.ADO_PROJECT ?? localConfig.project ?? fileConfig.project ?? DEFAULT_PROJECT;
|
|
57248
|
+
const repo = process.env.ADO_REPO ?? localConfig.repo ?? fileConfig.repo ?? DEFAULT_REPO;
|
|
57199
57249
|
if (isDefaultPlaceholder(collectionUrl) || isDefaultPlaceholder(project) || isDefaultPlaceholder(repo)) {
|
|
57200
57250
|
console.error("ADO configuration is incomplete. Set ADO_COLLECTION_URL, ADO_PROJECT, and ADO_REPO.");
|
|
57201
|
-
console.error(
|
|
57251
|
+
console.error(`You can also run "ado init" to create a config file at ${getConfigFilePath()}`);
|
|
57202
57252
|
process.exit(1);
|
|
57203
57253
|
}
|
|
57204
|
-
|
|
57254
|
+
const insecure = process.env.ADO_INSECURE === "1" || localConfig.insecure === true || fileConfig.insecure === true;
|
|
57255
|
+
if (insecure) {
|
|
57205
57256
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
57206
57257
|
}
|
|
57207
57258
|
const authHandler = import_azure_devops_node_api.getPersonalAccessTokenHandler(pat);
|
|
@@ -57314,6 +57365,42 @@ function buildRecentWorkItemsWiql(filters = {}) {
|
|
|
57314
57365
|
return `SELECT [System.Id] FROM WorkItems${whereClause} ORDER BY [System.ChangedDate] DESC`;
|
|
57315
57366
|
}
|
|
57316
57367
|
|
|
57368
|
+
// src/cherry-pick.ts
|
|
57369
|
+
function parseCherryPickArgs(args) {
|
|
57370
|
+
const kv = Object.fromEntries(args.filter((a) => a.startsWith("--")).map((arg) => {
|
|
57371
|
+
const [k, ...rest] = arg.split("=");
|
|
57372
|
+
return [k.replace(/^--/, ""), rest.join("=")];
|
|
57373
|
+
}));
|
|
57374
|
+
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
57375
|
+
const prId = Number(positionals[0]);
|
|
57376
|
+
if (!Number.isFinite(prId) || prId <= 0) {
|
|
57377
|
+
throw new Error("A valid pull request ID is required as the first argument.");
|
|
57378
|
+
}
|
|
57379
|
+
const target = kv.target;
|
|
57380
|
+
if (!target || target.trim().length === 0) {
|
|
57381
|
+
throw new Error("--target is required.");
|
|
57382
|
+
}
|
|
57383
|
+
const allowedOptions = new Set(["target", "topic", "repo"]);
|
|
57384
|
+
for (const key of Object.keys(kv)) {
|
|
57385
|
+
if (!allowedOptions.has(key)) {
|
|
57386
|
+
throw new Error(`Unknown option: --${key}`);
|
|
57387
|
+
}
|
|
57388
|
+
}
|
|
57389
|
+
return {
|
|
57390
|
+
prId,
|
|
57391
|
+
target: target.trim(),
|
|
57392
|
+
topic: kv.topic?.trim() || undefined,
|
|
57393
|
+
repo: kv.repo?.trim() || undefined
|
|
57394
|
+
};
|
|
57395
|
+
}
|
|
57396
|
+
function buildGeneratedRefName(prId, target, topic) {
|
|
57397
|
+
if (topic) {
|
|
57398
|
+
return topic.startsWith("refs/heads/") ? topic : `refs/heads/${topic}`;
|
|
57399
|
+
}
|
|
57400
|
+
const safeBranch = target.replace(/^refs\/heads\//, "");
|
|
57401
|
+
return `refs/heads/cherry-pick-pr-${prId}-onto-${safeBranch}`;
|
|
57402
|
+
}
|
|
57403
|
+
|
|
57317
57404
|
// src/cli.ts
|
|
57318
57405
|
var import_GitInterfaces = __toESM(require_GitInterfaces(), 1);
|
|
57319
57406
|
var import_WorkItemTrackingInterfaces = __toESM(require_WorkItemTrackingInterfaces(), 1);
|
|
@@ -57739,10 +57826,137 @@ async function cmdPrUpdate(config, idRaw, args) {
|
|
|
57739
57826
|
await linkWorkItemsToPr(config, repo, updated, workItemIds);
|
|
57740
57827
|
}
|
|
57741
57828
|
}
|
|
57829
|
+
async function cmdPrCherryPick(config, args) {
|
|
57830
|
+
const usage = "Usage: pr-cherry-pick <id> --target=main [--topic=branch-name] [--repo=...]";
|
|
57831
|
+
let parsed;
|
|
57832
|
+
try {
|
|
57833
|
+
parsed = parseCherryPickArgs(args);
|
|
57834
|
+
} catch (error) {
|
|
57835
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
57836
|
+
console.error(usage);
|
|
57837
|
+
process.exit(1);
|
|
57838
|
+
}
|
|
57839
|
+
const repo = pickRepo(config, parsed.repo);
|
|
57840
|
+
const gitApi = await config.connection.getGitApi();
|
|
57841
|
+
const repository = await gitApi.getRepository(repo, config.project);
|
|
57842
|
+
if (!repository?.id) {
|
|
57843
|
+
console.error(`Repository "${repo}" not found.`);
|
|
57844
|
+
process.exit(1);
|
|
57845
|
+
}
|
|
57846
|
+
const targetRef = parsed.target.startsWith("refs/") ? parsed.target : `refs/heads/${parsed.target}`;
|
|
57847
|
+
const generatedRefName = buildGeneratedRefName(parsed.prId, parsed.target, parsed.topic);
|
|
57848
|
+
const cherryPick = await gitApi.createCherryPick({
|
|
57849
|
+
source: { pullRequestId: parsed.prId },
|
|
57850
|
+
ontoRefName: targetRef,
|
|
57851
|
+
generatedRefName,
|
|
57852
|
+
repository: { id: repository.id }
|
|
57853
|
+
}, config.project, repository.id);
|
|
57854
|
+
let status = cherryPick.status;
|
|
57855
|
+
const cherryPickId = cherryPick.cherryPickId;
|
|
57856
|
+
if (cherryPickId == null) {
|
|
57857
|
+
console.error("Cherry-pick operation could not be started.");
|
|
57858
|
+
process.exit(1);
|
|
57859
|
+
}
|
|
57860
|
+
const MAX_POLL_ATTEMPTS = 30;
|
|
57861
|
+
const POLL_INTERVAL_MS = 1000;
|
|
57862
|
+
for (let attempt = 0;attempt < MAX_POLL_ATTEMPTS && (status === import_GitInterfaces.GitAsyncOperationStatus.Queued || status === import_GitInterfaces.GitAsyncOperationStatus.InProgress); attempt++) {
|
|
57863
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
57864
|
+
const result = await gitApi.getCherryPick(config.project, cherryPickId, repository.id);
|
|
57865
|
+
status = result.status;
|
|
57866
|
+
}
|
|
57867
|
+
if (status === import_GitInterfaces.GitAsyncOperationStatus.Completed) {
|
|
57868
|
+
console.log(`Cherry-pick of PR #${parsed.prId} completed. Branch created: ${generatedRefName.replace("refs/heads/", "")}`);
|
|
57869
|
+
} else if (status === import_GitInterfaces.GitAsyncOperationStatus.Failed) {
|
|
57870
|
+
console.error(`Cherry-pick of PR #${parsed.prId} failed.`);
|
|
57871
|
+
process.exit(1);
|
|
57872
|
+
} else {
|
|
57873
|
+
console.error(`Cherry-pick of PR #${parsed.prId} did not complete in time (status: ${import_GitInterfaces.GitAsyncOperationStatus[status ?? 0] ?? "unknown"}).`);
|
|
57874
|
+
process.exit(1);
|
|
57875
|
+
}
|
|
57876
|
+
}
|
|
57877
|
+
async function cmdInit(args) {
|
|
57878
|
+
const isLocal = args.includes("--local");
|
|
57879
|
+
const { createInterface } = await import("node:readline/promises");
|
|
57880
|
+
const { mkdirSync, writeFileSync, existsSync: existsSync2 } = await import("node:fs");
|
|
57881
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
57882
|
+
const existing = isLocal ? loadLocalConfig() : loadFileConfig();
|
|
57883
|
+
const configPath = isLocal ? getLocalConfigFilePath() : getConfigFilePath();
|
|
57884
|
+
console.log(`Azure DevOps CLI — ${isLocal ? "Local " : ""}Configuration`);
|
|
57885
|
+
console.log(`Config file: ${configPath}`);
|
|
57886
|
+
console.log(`Press Enter to keep existing values shown in brackets.
|
|
57887
|
+
`);
|
|
57888
|
+
if (isLocal) {
|
|
57889
|
+
const collectionUrl = await rl.question(`Collection URL${existing.collectionUrl ? ` [${existing.collectionUrl}]` : ""}: `) || existing.collectionUrl || "";
|
|
57890
|
+
const project = await rl.question(`Project${existing.project ? ` [${existing.project}]` : ""}: `) || existing.project || "";
|
|
57891
|
+
const repo = await rl.question(`Repository${existing.repo ? ` [${existing.repo}]` : ""}: `) || existing.repo || "";
|
|
57892
|
+
rl.close();
|
|
57893
|
+
const config = {};
|
|
57894
|
+
if (collectionUrl)
|
|
57895
|
+
config.collectionUrl = collectionUrl;
|
|
57896
|
+
if (project)
|
|
57897
|
+
config.project = project;
|
|
57898
|
+
if (repo)
|
|
57899
|
+
config.repo = repo;
|
|
57900
|
+
if (Object.keys(config).length === 0) {
|
|
57901
|
+
console.error(`
|
|
57902
|
+
At least one field must be provided for local config.`);
|
|
57903
|
+
process.exit(1);
|
|
57904
|
+
}
|
|
57905
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + `
|
|
57906
|
+
`, "utf8");
|
|
57907
|
+
console.log(`
|
|
57908
|
+
Local configuration saved to ${configPath}`);
|
|
57909
|
+
} else {
|
|
57910
|
+
const pat = await rl.question(`Personal Access Token (PAT)${existing.pat ? " [****]" : ""}: `) || existing.pat || "";
|
|
57911
|
+
const collectionUrl = await rl.question(`Collection URL${existing.collectionUrl ? ` [${existing.collectionUrl}]` : ""}: `) || existing.collectionUrl || "";
|
|
57912
|
+
const project = await rl.question(`Project${existing.project ? ` [${existing.project}]` : ""}: `) || existing.project || "";
|
|
57913
|
+
const repo = await rl.question(`Repository${existing.repo ? ` [${existing.repo}]` : ""}: `) || existing.repo || "";
|
|
57914
|
+
const insecureInput = await rl.question(`Disable TLS verification (insecure)? (y/N)${existing.insecure ? " [y]" : ""}: `) || (existing.insecure ? "y" : "n");
|
|
57915
|
+
const insecure = insecureInput.toLowerCase() === "y" || insecureInput === "1";
|
|
57916
|
+
rl.close();
|
|
57917
|
+
if (!pat || !collectionUrl || !project || !repo) {
|
|
57918
|
+
console.error(`
|
|
57919
|
+
All fields (PAT, Collection URL, Project, Repository) are required.`);
|
|
57920
|
+
process.exit(1);
|
|
57921
|
+
}
|
|
57922
|
+
const config = { pat, collectionUrl, project, repo, insecure };
|
|
57923
|
+
const configDir = getConfigDir();
|
|
57924
|
+
if (!existsSync2(configDir)) {
|
|
57925
|
+
mkdirSync(configDir, { recursive: true });
|
|
57926
|
+
}
|
|
57927
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + `
|
|
57928
|
+
`, "utf8");
|
|
57929
|
+
console.log(`
|
|
57930
|
+
Configuration saved to ${configPath}`);
|
|
57931
|
+
}
|
|
57932
|
+
}
|
|
57933
|
+
function cmdConfig() {
|
|
57934
|
+
const fileConfig = loadFileConfig();
|
|
57935
|
+
const localConfig = loadLocalConfig();
|
|
57936
|
+
const pat = process.env.DEVOPS_PAT ?? localConfig.pat ?? fileConfig.pat;
|
|
57937
|
+
const collectionUrl = process.env.ADO_COLLECTION_URL ?? localConfig.collectionUrl ?? fileConfig.collectionUrl;
|
|
57938
|
+
const project = process.env.ADO_PROJECT ?? localConfig.project ?? fileConfig.project;
|
|
57939
|
+
const repo = process.env.ADO_REPO ?? localConfig.repo ?? fileConfig.repo;
|
|
57940
|
+
const insecure = process.env.ADO_INSECURE === "1" || localConfig.insecure === true || fileConfig.insecure === true;
|
|
57941
|
+
console.log(JSON.stringify({
|
|
57942
|
+
pat: pat ? censorPat(pat) : undefined,
|
|
57943
|
+
collectionUrl: collectionUrl ?? undefined,
|
|
57944
|
+
project: project ?? undefined,
|
|
57945
|
+
repo: repo ?? undefined,
|
|
57946
|
+
insecure,
|
|
57947
|
+
sources: {
|
|
57948
|
+
globalConfig: getConfigFilePath(),
|
|
57949
|
+
localConfig: getLocalConfigFilePath()
|
|
57950
|
+
}
|
|
57951
|
+
}, null, 2));
|
|
57952
|
+
}
|
|
57742
57953
|
function printHelp() {
|
|
57743
57954
|
console.log(`Azure DevOps CLI
|
|
57744
57955
|
|
|
57745
57956
|
Commands:
|
|
57957
|
+
-v, --version
|
|
57958
|
+
init [--local]
|
|
57959
|
+
config
|
|
57746
57960
|
smoke
|
|
57747
57961
|
repos
|
|
57748
57962
|
branches [repo]
|
|
@@ -57755,17 +57969,35 @@ Commands:
|
|
|
57755
57969
|
pr-get <id> [repo]
|
|
57756
57970
|
pr-create --title=... --source=... --target=... [--description=...] [--repo=...] [--work-items=123,456]
|
|
57757
57971
|
pr-update <id> [--title=...] [--description=...] [--repo=...] [--work-items=123,456]
|
|
57972
|
+
pr-cherry-pick <id> --target=... [--topic=branch-name] [--repo=...]
|
|
57758
57973
|
pr-approve <id> [repo]
|
|
57759
57974
|
pr-autocomplete <id> [repo]
|
|
57760
57975
|
builds [top]
|
|
57761
57976
|
`);
|
|
57762
57977
|
}
|
|
57978
|
+
async function printVersion() {
|
|
57979
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
57980
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
57981
|
+
console.log(pkg.version);
|
|
57982
|
+
}
|
|
57763
57983
|
async function main() {
|
|
57764
57984
|
const [command = "smoke", ...args] = process.argv.slice(2);
|
|
57765
57985
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
57766
57986
|
printHelp();
|
|
57767
57987
|
return;
|
|
57768
57988
|
}
|
|
57989
|
+
if (command === "-v" || command === "--version") {
|
|
57990
|
+
await printVersion();
|
|
57991
|
+
return;
|
|
57992
|
+
}
|
|
57993
|
+
if (command === "init") {
|
|
57994
|
+
await cmdInit(args);
|
|
57995
|
+
return;
|
|
57996
|
+
}
|
|
57997
|
+
if (command === "config") {
|
|
57998
|
+
cmdConfig();
|
|
57999
|
+
return;
|
|
58000
|
+
}
|
|
57769
58001
|
const config = getConfig();
|
|
57770
58002
|
switch (command) {
|
|
57771
58003
|
case "smoke":
|
|
@@ -57804,6 +58036,9 @@ async function main() {
|
|
|
57804
58036
|
case "pr-update":
|
|
57805
58037
|
await cmdPrUpdate(config, args[0], args.slice(1));
|
|
57806
58038
|
break;
|
|
58039
|
+
case "pr-cherry-pick":
|
|
58040
|
+
await cmdPrCherryPick(config, args);
|
|
58041
|
+
break;
|
|
57807
58042
|
case "pr-approve":
|
|
57808
58043
|
await cmdPrApprove(config, args[0], args[1]);
|
|
57809
58044
|
break;
|
package/package.json
CHANGED
|
@@ -19,6 +19,9 @@ If auth is missing, stop and ask for `DEVOPS_PAT`.
|
|
|
19
19
|
|
|
20
20
|
## Core commands
|
|
21
21
|
|
|
22
|
+
- Initialize global config: `ado init`
|
|
23
|
+
- Initialize local (per-project) config: `ado init --local`
|
|
24
|
+
- Show resolved config: `ado config`
|
|
22
25
|
- List repos: `ado repos`
|
|
23
26
|
- List branches: `ado branches "MyRepo"`
|
|
24
27
|
- Get work item: `ado workitem-get <id>`
|
|
@@ -32,6 +35,7 @@ If auth is missing, stop and ask for `DEVOPS_PAT`.
|
|
|
32
35
|
- Get PR: `ado pr-get <id> "MyRepo"`
|
|
33
36
|
- Create PR: `ado pr-create --title="..." --source="feature/x" --target="develop" --description="..." --repo="MyRepo" --work-items=123,456`
|
|
34
37
|
- Update PR: `ado pr-update <id> --title="..." --description="..." --repo="MyRepo" --work-items=123,456`
|
|
38
|
+
- Cherry-pick PR onto another branch: `ado pr-cherry-pick <id> --target="main" --topic="cherry-pick-branch" --repo="MyRepo"`
|
|
35
39
|
- Approve PR: `ado pr-approve <id> "MyRepo"`
|
|
36
40
|
- Enable auto-complete: `ado pr-autocomplete <id> "MyRepo"`
|
|
37
41
|
- List builds: `ado builds 10`
|