azdo-cli 0.19.0 → 0.20.0-develop.741
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 +173 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1146,25 +1146,26 @@ async function promptForPat() {
|
|
|
1146
1146
|
process.stdin.on("data", onData);
|
|
1147
1147
|
});
|
|
1148
1148
|
}
|
|
1149
|
+
function patFromEnvFile(envFile) {
|
|
1150
|
+
if (!existsSync2(envFile)) return null;
|
|
1151
|
+
const contents = readFileSync2(envFile, "utf8");
|
|
1152
|
+
for (const line of contents.split("\n")) {
|
|
1153
|
+
const match = line.match(/^AZDO_PAT\s*=([^\n\r]+)$/);
|
|
1154
|
+
if (match === null) continue;
|
|
1155
|
+
const value = match[1].trim().replace(/^["']|["']$/g, "");
|
|
1156
|
+
if (value.length > 0) return value;
|
|
1157
|
+
}
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1149
1160
|
function findDotEnvPat(startDir = process.cwd()) {
|
|
1150
1161
|
let current = startDir;
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1153
|
-
if (
|
|
1154
|
-
const contents = readFileSync2(envFile, "utf8");
|
|
1155
|
-
for (const line of contents.split("\n")) {
|
|
1156
|
-
const match = line.match(/^AZDO_PAT\s*=([^\n\r]+)$/);
|
|
1157
|
-
if (match) {
|
|
1158
|
-
const value = match[1].trim().replace(/^["']|["']$/g, "");
|
|
1159
|
-
if (value.length > 0) return value;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1162
|
+
for (; ; ) {
|
|
1163
|
+
const found = patFromEnvFile(join2(current, ".env"));
|
|
1164
|
+
if (found !== null) return found;
|
|
1163
1165
|
const parent = dirname2(current);
|
|
1164
|
-
if (parent === current)
|
|
1166
|
+
if (parent === current) return null;
|
|
1165
1167
|
current = parent;
|
|
1166
1168
|
}
|
|
1167
|
-
return null;
|
|
1168
1169
|
}
|
|
1169
1170
|
async function resolveAuthCredential(org) {
|
|
1170
1171
|
let cred;
|
|
@@ -2336,36 +2337,30 @@ Identifier: ${masked}
|
|
|
2336
2337
|
` : "")
|
|
2337
2338
|
);
|
|
2338
2339
|
}
|
|
2339
|
-
async function
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
if (result.removed.length === 0) {
|
|
2349
|
-
process.stdout.write("No stored credentials to remove.\n");
|
|
2350
|
-
return;
|
|
2351
|
-
}
|
|
2352
|
-
for (const r of result.removed) {
|
|
2353
|
-
process.stdout.write(`Removed ${r.kind} credential for org ${r.org}.
|
|
2354
|
-
`);
|
|
2355
|
-
}
|
|
2356
|
-
} catch (err) {
|
|
2357
|
-
if (err instanceof CredentialStoreUnavailableError) {
|
|
2358
|
-
process.stderr.write(`${err.message}
|
|
2340
|
+
async function logoutAllOrgs() {
|
|
2341
|
+
try {
|
|
2342
|
+
const result = await logout({ all: true });
|
|
2343
|
+
if (result.removed.length === 0) {
|
|
2344
|
+
process.stdout.write("No stored credentials to remove.\n");
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
for (const r of result.removed) {
|
|
2348
|
+
process.stdout.write(`Removed ${r.kind} credential for org ${r.org}.
|
|
2359
2349
|
`);
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
process.stderr.write(
|
|
2350
|
+
}
|
|
2351
|
+
} catch (err) {
|
|
2352
|
+
if (err instanceof CredentialStoreUnavailableError) {
|
|
2353
|
+
process.stderr.write(`${err.message}
|
|
2364
2354
|
`);
|
|
2365
|
-
process.exitCode =
|
|
2355
|
+
process.exitCode = 4;
|
|
2356
|
+
return;
|
|
2366
2357
|
}
|
|
2367
|
-
|
|
2358
|
+
process.stderr.write(`Failed to remove credentials: ${err.message}
|
|
2359
|
+
`);
|
|
2360
|
+
process.exitCode = 1;
|
|
2368
2361
|
}
|
|
2362
|
+
}
|
|
2363
|
+
async function logoutSingleOrg(orgFromGlobal) {
|
|
2369
2364
|
const resolved = resolveOrg({ org: orgFromGlobal });
|
|
2370
2365
|
if (!resolved) {
|
|
2371
2366
|
process.stderr.write(`${formatResolutionError()}
|
|
@@ -2392,6 +2387,18 @@ async function handleLogout(options, orgFromGlobal) {
|
|
|
2392
2387
|
throw err;
|
|
2393
2388
|
}
|
|
2394
2389
|
}
|
|
2390
|
+
async function handleLogout(options, orgFromGlobal) {
|
|
2391
|
+
if (options.all && orgFromGlobal) {
|
|
2392
|
+
process.stderr.write("--org and --all are mutually exclusive.\n");
|
|
2393
|
+
process.exitCode = 1;
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
if (options.all) {
|
|
2397
|
+
await logoutAllOrgs();
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
await logoutSingleOrg(orgFromGlobal);
|
|
2401
|
+
}
|
|
2395
2402
|
function describeCredentialSource(cred) {
|
|
2396
2403
|
switch (cred.source) {
|
|
2397
2404
|
case "env":
|
|
@@ -3440,7 +3447,9 @@ function createUpsertCommand() {
|
|
|
3440
3447
|
import { Command as Command11 } from "commander";
|
|
3441
3448
|
function stringifyValue(value) {
|
|
3442
3449
|
if (value === null || value === void 0) return "";
|
|
3450
|
+
if (typeof value === "string") return value;
|
|
3443
3451
|
if (typeof value === "object") return JSON.stringify(value);
|
|
3452
|
+
if (typeof value === "symbol" || typeof value === "function") return value.toString();
|
|
3444
3453
|
return String(value);
|
|
3445
3454
|
}
|
|
3446
3455
|
function formatRichValue(raw) {
|
|
@@ -4893,79 +4902,136 @@ function createPrReactivateCommand() {
|
|
|
4893
4902
|
});
|
|
4894
4903
|
return command;
|
|
4895
4904
|
}
|
|
4905
|
+
function parseCommentsOptions(options) {
|
|
4906
|
+
let explicitPrId = null;
|
|
4907
|
+
if (options.prNumber !== void 0) {
|
|
4908
|
+
explicitPrId = parsePositivePrNumber(options.prNumber);
|
|
4909
|
+
if (explicitPrId === null) {
|
|
4910
|
+
writeError(`Invalid --pr-number "${options.prNumber}"; expected a positive integer.`);
|
|
4911
|
+
return null;
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
let maxChars = 0;
|
|
4915
|
+
if (options.maxChars !== void 0) {
|
|
4916
|
+
const parsed = parseNonNegativeInt(options.maxChars);
|
|
4917
|
+
if (parsed === null) {
|
|
4918
|
+
writeError(`Invalid --max-chars "${options.maxChars}"; expected a non-negative integer.`);
|
|
4919
|
+
return null;
|
|
4920
|
+
}
|
|
4921
|
+
maxChars = parsed;
|
|
4922
|
+
}
|
|
4923
|
+
let threadFilter = null;
|
|
4924
|
+
if (options.thread !== void 0) {
|
|
4925
|
+
threadFilter = parsePositivePrNumber(options.thread);
|
|
4926
|
+
if (threadFilter === null) {
|
|
4927
|
+
writeError(`Invalid --thread "${options.thread}"; expected a positive integer.`);
|
|
4928
|
+
return null;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
return { explicitPrId, maxChars, threadFilter };
|
|
4932
|
+
}
|
|
4933
|
+
async function resolveCommentsPullRequest(resolved, explicitPrId) {
|
|
4934
|
+
if (explicitPrId !== null) {
|
|
4935
|
+
let pullRequest;
|
|
4936
|
+
try {
|
|
4937
|
+
pullRequest = await getPullRequestById(resolved.context, resolved.repo, resolved.pat, explicitPrId);
|
|
4938
|
+
} catch (err) {
|
|
4939
|
+
if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
|
|
4940
|
+
writeError(`Pull request #${explicitPrId} not found in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`, EXIT_NOT_FOUND);
|
|
4941
|
+
return null;
|
|
4942
|
+
}
|
|
4943
|
+
throw err;
|
|
4944
|
+
}
|
|
4945
|
+
return { pullRequest, branchLabel: resolved.branch ?? pullRequest.sourceRefName };
|
|
4946
|
+
}
|
|
4947
|
+
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
|
|
4948
|
+
status: "active"
|
|
4949
|
+
});
|
|
4950
|
+
if (pullRequests.length === 0) {
|
|
4951
|
+
writeContractError(autoDetectZeroMatch(resolved.branch));
|
|
4952
|
+
return null;
|
|
4953
|
+
}
|
|
4954
|
+
if (pullRequests.length > 1) {
|
|
4955
|
+
writeContractError(autoDetectMultiMatch(resolved.branch, pullRequests.map((pr) => pr.id)));
|
|
4956
|
+
return null;
|
|
4957
|
+
}
|
|
4958
|
+
return { pullRequest: pullRequests[0], branchLabel: resolved.branch };
|
|
4959
|
+
}
|
|
4960
|
+
function selectCommentThreads(fetchedThreads, threadFilter, pullRequestId) {
|
|
4961
|
+
if (threadFilter === null) {
|
|
4962
|
+
return fetchedThreads;
|
|
4963
|
+
}
|
|
4964
|
+
if (!fetchedThreads.some((thread) => thread.id === threadFilter)) {
|
|
4965
|
+
writeError(`Thread #${threadFilter} not found on pull request #${pullRequestId}.`, EXIT_NOT_FOUND);
|
|
4966
|
+
return null;
|
|
4967
|
+
}
|
|
4968
|
+
return fetchedThreads.filter((thread) => thread.id === threadFilter);
|
|
4969
|
+
}
|
|
4970
|
+
function shapeCommentThreads(allThreads, opts) {
|
|
4971
|
+
return allThreads.filter(
|
|
4972
|
+
(thread) => (!opts.hideResolved || !isThreadResolved(thread.status)) && (!opts.codeRelatedOnly || thread.threadContext !== null)
|
|
4973
|
+
).map(
|
|
4974
|
+
(thread) => shapeThreadForOutput(thread, {
|
|
4975
|
+
excludeSystem: opts.excludeSystem,
|
|
4976
|
+
maxChars: opts.maxChars,
|
|
4977
|
+
contains: opts.contains
|
|
4978
|
+
})
|
|
4979
|
+
).filter((thread) => thread !== null);
|
|
4980
|
+
}
|
|
4981
|
+
function describeEmptyThreads(pullRequestId, allThreadCount, active) {
|
|
4982
|
+
const filters = [];
|
|
4983
|
+
if (active.contains !== void 0) {
|
|
4984
|
+
filters.push("matching");
|
|
4985
|
+
}
|
|
4986
|
+
if (active.codeRelatedOnly) {
|
|
4987
|
+
filters.push("code-related");
|
|
4988
|
+
}
|
|
4989
|
+
if (active.hideResolved) {
|
|
4990
|
+
filters.push("unresolved");
|
|
4991
|
+
}
|
|
4992
|
+
if (active.excludeSystem) {
|
|
4993
|
+
filters.push("non-system");
|
|
4994
|
+
}
|
|
4995
|
+
if (allThreadCount > 0 && filters.length > 0) {
|
|
4996
|
+
return `Pull request #${pullRequestId} has no ${filters.join(" ")} comment threads (filtered from ${allThreadCount} thread${allThreadCount === 1 ? "" : "s"}).
|
|
4997
|
+
`;
|
|
4998
|
+
}
|
|
4999
|
+
return `Pull request #${pullRequestId} has no comment threads.
|
|
5000
|
+
`;
|
|
5001
|
+
}
|
|
4896
5002
|
function createPrCommentsCommand() {
|
|
4897
5003
|
const command = new Command12("comments");
|
|
4898
5004
|
withCommonPrOptions(configureUnwrappedHelp(command)).description("List pull request comment threads for the current branch").option("--pr-number <N>", PR_NUMBER_HELP).option("--hide-resolved", "hide threads whose status is resolved / won't fix / closed / by design").option("--exclude-resolved", "alias of --hide-resolved: exclude resolved / won't fix / closed / by design threads").option("--code-related-only", "show only threads anchored to a file/line; omit general discussion threads").option("--exclude-system", "omit Azure DevOps system comments (branch updates, reviewer votes, build events)").option("--max-chars <N>", "truncate each comment body to N characters (0 = no limit, the default)").option("--thread <id>", "show only the thread with this numeric id; fails when the pull request has no such thread").option("--contains <text>", "show only threads holding a comment that contains this literal, case-sensitive substring (matched before --max-chars truncates)").option("--json", "output JSON").action(async (options) => {
|
|
4899
5005
|
validateOrgProjectPair(options);
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
explicitPrId = parsePositivePrNumber(options.prNumber);
|
|
4904
|
-
if (explicitPrId === null) {
|
|
4905
|
-
writeError(`Invalid --pr-number "${options.prNumber}"; expected a positive integer.`);
|
|
4906
|
-
return;
|
|
4907
|
-
}
|
|
4908
|
-
}
|
|
4909
|
-
let maxChars = 0;
|
|
4910
|
-
if (options.maxChars !== void 0) {
|
|
4911
|
-
const parsed = parseNonNegativeInt(options.maxChars);
|
|
4912
|
-
if (parsed === null) {
|
|
4913
|
-
writeError(`Invalid --max-chars "${options.maxChars}"; expected a non-negative integer.`);
|
|
4914
|
-
return;
|
|
4915
|
-
}
|
|
4916
|
-
maxChars = parsed;
|
|
4917
|
-
}
|
|
4918
|
-
let threadFilter = null;
|
|
4919
|
-
if (options.thread !== void 0) {
|
|
4920
|
-
threadFilter = parsePositivePrNumber(options.thread);
|
|
4921
|
-
if (threadFilter === null) {
|
|
4922
|
-
writeError(`Invalid --thread "${options.thread}"; expected a positive integer.`);
|
|
4923
|
-
return;
|
|
4924
|
-
}
|
|
5006
|
+
const parsed = parseCommentsOptions(options);
|
|
5007
|
+
if (parsed === null) {
|
|
5008
|
+
return;
|
|
4925
5009
|
}
|
|
5010
|
+
const { explicitPrId, maxChars, threadFilter } = parsed;
|
|
5011
|
+
let context;
|
|
4926
5012
|
try {
|
|
4927
5013
|
const resolved = await resolvePrCommandContext(options, { requireBranch: explicitPrId === null });
|
|
4928
5014
|
context = resolved.context;
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
try {
|
|
4933
|
-
pullRequest = await getPullRequestById(resolved.context, resolved.repo, resolved.pat, explicitPrId);
|
|
4934
|
-
} catch (err) {
|
|
4935
|
-
if (err instanceof Error && err.message.startsWith("NOT_FOUND")) {
|
|
4936
|
-
writeError(`Pull request #${explicitPrId} not found in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`, EXIT_NOT_FOUND);
|
|
4937
|
-
return;
|
|
4938
|
-
}
|
|
4939
|
-
throw err;
|
|
4940
|
-
}
|
|
4941
|
-
branchLabel = resolved.branch ?? pullRequest.sourceRefName;
|
|
4942
|
-
} else {
|
|
4943
|
-
const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, resolved.branch, {
|
|
4944
|
-
status: "active"
|
|
4945
|
-
});
|
|
4946
|
-
if (pullRequests.length === 0) {
|
|
4947
|
-
writeContractError(autoDetectZeroMatch(resolved.branch));
|
|
4948
|
-
return;
|
|
4949
|
-
}
|
|
4950
|
-
if (pullRequests.length > 1) {
|
|
4951
|
-
writeContractError(autoDetectMultiMatch(resolved.branch, pullRequests.map((pr) => pr.id)));
|
|
4952
|
-
return;
|
|
4953
|
-
}
|
|
4954
|
-
pullRequest = pullRequests[0];
|
|
4955
|
-
branchLabel = resolved.branch;
|
|
5015
|
+
const target = await resolveCommentsPullRequest(resolved, explicitPrId);
|
|
5016
|
+
if (target === null) {
|
|
5017
|
+
return;
|
|
4956
5018
|
}
|
|
5019
|
+
const { pullRequest, branchLabel } = target;
|
|
4957
5020
|
const hideResolved = options.hideResolved === true || options.excludeResolved === true;
|
|
4958
5021
|
const codeRelatedOnly = options.codeRelatedOnly === true;
|
|
4959
5022
|
const excludeSystem = options.excludeSystem === true;
|
|
4960
5023
|
const fetchedThreads = await getPullRequestThreads(resolved.context, resolved.repo, resolved.pat, pullRequest.id);
|
|
4961
|
-
|
|
4962
|
-
|
|
5024
|
+
const allThreads = selectCommentThreads(fetchedThreads, threadFilter, pullRequest.id);
|
|
5025
|
+
if (allThreads === null) {
|
|
4963
5026
|
return;
|
|
4964
5027
|
}
|
|
4965
|
-
const
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5028
|
+
const threads = shapeCommentThreads(allThreads, {
|
|
5029
|
+
hideResolved,
|
|
5030
|
+
codeRelatedOnly,
|
|
5031
|
+
excludeSystem,
|
|
5032
|
+
maxChars,
|
|
5033
|
+
contains: options.contains
|
|
5034
|
+
});
|
|
4969
5035
|
const result = { branch: branchLabel, pullRequest, threads };
|
|
4970
5036
|
if (options.json) {
|
|
4971
5037
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
@@ -4973,28 +5039,12 @@ function createPrCommentsCommand() {
|
|
|
4973
5039
|
return;
|
|
4974
5040
|
}
|
|
4975
5041
|
if (threads.length === 0) {
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
filters.push("code-related");
|
|
4983
|
-
}
|
|
4984
|
-
if (hideResolved) {
|
|
4985
|
-
filters.push("unresolved");
|
|
4986
|
-
}
|
|
4987
|
-
if (excludeSystem) {
|
|
4988
|
-
filters.push("non-system");
|
|
4989
|
-
}
|
|
4990
|
-
process.stdout.write(
|
|
4991
|
-
`Pull request #${pullRequest.id} has no ${filters.join(" ")} comment threads (filtered from ${allThreads.length} thread${allThreads.length === 1 ? "" : "s"}).
|
|
4992
|
-
`
|
|
4993
|
-
);
|
|
4994
|
-
} else {
|
|
4995
|
-
process.stdout.write(`Pull request #${pullRequest.id} has no comment threads.
|
|
4996
|
-
`);
|
|
4997
|
-
}
|
|
5042
|
+
process.stdout.write(describeEmptyThreads(pullRequest.id, allThreads.length, {
|
|
5043
|
+
hideResolved,
|
|
5044
|
+
codeRelatedOnly,
|
|
5045
|
+
excludeSystem,
|
|
5046
|
+
contains: options.contains
|
|
5047
|
+
}));
|
|
4998
5048
|
return;
|
|
4999
5049
|
}
|
|
5000
5050
|
process.stdout.write(`${formatThreads(pullRequest.id, pullRequest.title, threads)}
|
|
@@ -5174,8 +5224,7 @@ async function runCommentReply(threadIdRaw, text, options) {
|
|
|
5174
5224
|
return;
|
|
5175
5225
|
}
|
|
5176
5226
|
const threads = await getPullRequestThreads(target.context, target.repo, target.pat, target.pullRequest.id);
|
|
5177
|
-
|
|
5178
|
-
if (!thread) {
|
|
5227
|
+
if (!threads.some((t) => t.id === target.threadId)) {
|
|
5179
5228
|
writeError(`Thread #${target.threadId} not found on pull request #${target.pullRequest.id}.`, EXIT_NOT_FOUND);
|
|
5180
5229
|
return;
|
|
5181
5230
|
}
|
|
@@ -7105,7 +7154,7 @@ function handleRelationError(err, id1) {
|
|
|
7105
7154
|
`
|
|
7106
7155
|
);
|
|
7107
7156
|
} else if (msg.startsWith("NOT_FOUND")) {
|
|
7108
|
-
const target = id1
|
|
7157
|
+
const target = id1 ?? "unknown";
|
|
7109
7158
|
process.stderr.write(`Error: work item #${target} not found.
|
|
7110
7159
|
`);
|
|
7111
7160
|
} else if (isSentinel(msg, "AUTH_FAILED")) {
|
|
@@ -7315,7 +7364,7 @@ async function getUpdateNotice(opts) {
|
|
|
7315
7364
|
if (!latest) return null;
|
|
7316
7365
|
writeCache(JSON.stringify({ lastCheck: now(), latestVersion: latest }));
|
|
7317
7366
|
if (isNewer(latest, currentVersion)) {
|
|
7318
|
-
return `A new version of azdo-cli is available: ${currentVersion} \u2192 ${latest}. Run \`npm
|
|
7367
|
+
return `A new version of azdo-cli is available: ${currentVersion} \u2192 ${latest}. Run \`npm install -g azdo-cli@latest\` to update.`;
|
|
7319
7368
|
}
|
|
7320
7369
|
return null;
|
|
7321
7370
|
} catch {
|