azdo-cli 0.19.0-develop.717 → 0.19.0-develop.724
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
CHANGED
|
@@ -19,7 +19,7 @@ Azure DevOps CLI focused on work item read/write workflows.
|
|
|
19
19
|
- Feed long PR titles, descriptions and comment bodies from a file or a pipe instead of the shell (`--title-file`, `--description-file`, `--file`; `-` reads standard input)
|
|
20
20
|
- Persist org/project/default fields in local config (`config`)
|
|
21
21
|
- List all fields of a work item (`list-fields`)
|
|
22
|
-
- Authenticate per Azure DevOps organization with `azdo auth login` — OAuth (Microsoft Entra) by default, or a Personal Access Token via `--use-pat` (or the `AZDO_PAT` env var). Credentials are stored in the OS credential store. Inspect with `azdo auth status`, remove with `azdo auth logout`. Diagnose auth problems with `azdo auth diagnose`. See [docs/authentication.md](docs/authentication.md).
|
|
22
|
+
- Authenticate per Azure DevOps organization with `azdo auth login` — OAuth (Microsoft Entra) by default, or a Personal Access Token via `--use-pat` (or the `AZDO_PAT` env var). Credentials are stored in the OS credential store. Inspect with `azdo auth status`, remove with `azdo auth logout`. Diagnose auth problems with `azdo auth diagnose`. Print the credential the CLI itself would use, for API calls the CLI does not yet wrap, with `azdo auth token`. See [docs/authentication.md](docs/authentication.md).
|
|
23
23
|
- Trace all HTTP requests to a local file with `--trace <filepath>` (sensitive headers and tokens are automatically redacted).
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
@@ -825,10 +825,18 @@ function entryFor(account) {
|
|
|
825
825
|
return wrapUnavailable(() => new Entry(SERVICE, account));
|
|
826
826
|
}
|
|
827
827
|
var legacyUnsetNoticeEmitted = false;
|
|
828
|
+
var noticesSuppressed = false;
|
|
829
|
+
function suppressCredentialStoreNotices(suppressed) {
|
|
830
|
+
noticesSuppressed = suppressed;
|
|
831
|
+
}
|
|
832
|
+
function writeNotice(message) {
|
|
833
|
+
if (noticesSuppressed) return;
|
|
834
|
+
process.stderr.write(message);
|
|
835
|
+
}
|
|
828
836
|
function emitLegacyUnsetNoticeOnce() {
|
|
829
837
|
if (legacyUnsetNoticeEmitted) return;
|
|
830
838
|
legacyUnsetNoticeEmitted = true;
|
|
831
|
-
|
|
839
|
+
writeNotice(
|
|
832
840
|
'A legacy PAT exists in the OS vault from a previous azdo-cli version, but no "org" is set in config. Run `azdo auth --org <name>` to re-store it under the per-org key, then `azdo clear-pat` to remove the legacy slot.\n'
|
|
833
841
|
);
|
|
834
842
|
}
|
|
@@ -925,7 +933,7 @@ async function maybeMigrateLegacy(targetOrg) {
|
|
|
925
933
|
backend: probeBackend(),
|
|
926
934
|
masked_pat: maskedDisplay(legacy)
|
|
927
935
|
});
|
|
928
|
-
|
|
936
|
+
writeNotice(`Migrated legacy PAT to org ${targetOrg}.
|
|
929
937
|
`);
|
|
930
938
|
return legacy;
|
|
931
939
|
}
|
|
@@ -990,7 +998,7 @@ async function deletePat(org) {
|
|
|
990
998
|
}
|
|
991
999
|
try {
|
|
992
1000
|
const { unlinkSync: unlinkSync2 } = await import("fs");
|
|
993
|
-
const { lockPath: lockPath2 } = await import("./oauth-token-refresh-
|
|
1001
|
+
const { lockPath: lockPath2 } = await import("./oauth-token-refresh-PXUQE5JE.js");
|
|
994
1002
|
unlinkSync2(lockPath2(org));
|
|
995
1003
|
} catch {
|
|
996
1004
|
}
|
|
@@ -1180,6 +1188,7 @@ function _resetInFlight() {
|
|
|
1180
1188
|
export {
|
|
1181
1189
|
CredentialStoreUnavailableError,
|
|
1182
1190
|
CredentialMissingError,
|
|
1191
|
+
CredentialRefreshError,
|
|
1183
1192
|
appendAuthAuditEvent,
|
|
1184
1193
|
readAuditEvents,
|
|
1185
1194
|
SETTINGS,
|
|
@@ -1211,6 +1220,7 @@ export {
|
|
|
1211
1220
|
refreshIfNeeded,
|
|
1212
1221
|
_resetInFlight,
|
|
1213
1222
|
probeBackend,
|
|
1223
|
+
suppressCredentialStoreNotices,
|
|
1214
1224
|
getPat,
|
|
1215
1225
|
getStoredCredential,
|
|
1216
1226
|
storePat,
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
AZDO_RESOURCE_ID,
|
|
4
4
|
CredentialMissingError,
|
|
5
|
+
CredentialRefreshError,
|
|
5
6
|
CredentialStoreUnavailableError,
|
|
6
7
|
SETTINGS,
|
|
7
8
|
appendAuthAuditEvent,
|
|
@@ -32,12 +33,13 @@ import {
|
|
|
32
33
|
setOrgScopedValue,
|
|
33
34
|
storeOAuthCredential,
|
|
34
35
|
storePat,
|
|
36
|
+
suppressCredentialStoreNotices,
|
|
35
37
|
tokenResponseToCredential,
|
|
36
38
|
unsetConfigValue,
|
|
37
39
|
unsetOrgScopedValue
|
|
38
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-K2CKMY72.js";
|
|
39
41
|
|
|
40
|
-
// src/
|
|
42
|
+
// src/program.ts
|
|
41
43
|
import { Command as Command19 } from "commander";
|
|
42
44
|
|
|
43
45
|
// src/version.ts
|
|
@@ -1165,28 +1167,28 @@ function findDotEnvPat(startDir = process.cwd()) {
|
|
|
1165
1167
|
return null;
|
|
1166
1168
|
}
|
|
1167
1169
|
async function resolveAuthCredential(org) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
if (stored.kind === "pat") {
|
|
1175
|
-
return { pat: stored.token, source: "credential-store", kind: "pat" };
|
|
1170
|
+
let cred;
|
|
1171
|
+
try {
|
|
1172
|
+
cred = await exportCredential(org);
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
if (err instanceof CredentialMissingError) {
|
|
1175
|
+
return null;
|
|
1176
1176
|
}
|
|
1177
|
-
|
|
1177
|
+
throw err;
|
|
1178
|
+
}
|
|
1179
|
+
if (cred.kind === "oauth") {
|
|
1178
1180
|
return {
|
|
1179
|
-
pat:
|
|
1181
|
+
pat: cred.token,
|
|
1180
1182
|
source: "credential-store",
|
|
1181
1183
|
kind: "oauth",
|
|
1182
|
-
accountId:
|
|
1184
|
+
accountId: cred.accountId
|
|
1183
1185
|
};
|
|
1184
1186
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1187
|
+
return {
|
|
1188
|
+
pat: cred.token,
|
|
1189
|
+
source: cred.source === "credential-store" ? "credential-store" : "env",
|
|
1190
|
+
kind: "pat"
|
|
1191
|
+
};
|
|
1190
1192
|
}
|
|
1191
1193
|
var lastResolvedCredential = null;
|
|
1192
1194
|
async function requireAuthCredential(org) {
|
|
@@ -1351,6 +1353,32 @@ async function status() {
|
|
|
1351
1353
|
}
|
|
1352
1354
|
return { orgs: out };
|
|
1353
1355
|
}
|
|
1356
|
+
async function exportCredential(org) {
|
|
1357
|
+
const envPat = process.env.AZDO_PAT;
|
|
1358
|
+
if (envPat && envPat.length > 0) {
|
|
1359
|
+
return { kind: "pat", token: envPat, source: "env" };
|
|
1360
|
+
}
|
|
1361
|
+
const stored = await getStoredCredential(org);
|
|
1362
|
+
if (stored === null) {
|
|
1363
|
+
const dotEnvPat = findDotEnvPat();
|
|
1364
|
+
if (dotEnvPat !== null) {
|
|
1365
|
+
return { kind: "pat", token: dotEnvPat, source: "dotenv" };
|
|
1366
|
+
}
|
|
1367
|
+
throw new CredentialMissingError(org);
|
|
1368
|
+
}
|
|
1369
|
+
if (stored.kind === "pat") {
|
|
1370
|
+
return { kind: "pat", token: stored.token, source: "credential-store" };
|
|
1371
|
+
}
|
|
1372
|
+
const fresh = await refreshIfNeeded(org, stored);
|
|
1373
|
+
return {
|
|
1374
|
+
kind: "oauth",
|
|
1375
|
+
token: fresh.accessToken,
|
|
1376
|
+
source: "credential-store",
|
|
1377
|
+
accountId: fresh.accountId,
|
|
1378
|
+
expiresAt: fresh.expiresAt,
|
|
1379
|
+
scope: fresh.scope
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1354
1382
|
|
|
1355
1383
|
// src/services/git-remote.ts
|
|
1356
1384
|
import { execFileSync } from "child_process";
|
|
@@ -2364,6 +2392,73 @@ async function handleLogout(options, orgFromGlobal) {
|
|
|
2364
2392
|
throw err;
|
|
2365
2393
|
}
|
|
2366
2394
|
}
|
|
2395
|
+
function describeCredentialSource(cred) {
|
|
2396
|
+
switch (cred.source) {
|
|
2397
|
+
case "env":
|
|
2398
|
+
return "the AZDO_PAT environment variable";
|
|
2399
|
+
case "dotenv":
|
|
2400
|
+
return "the AZDO_PAT entry in a .env file";
|
|
2401
|
+
default:
|
|
2402
|
+
return `the OS credential store (${probeBackend()})`;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function describeExportedCredential(cred, org) {
|
|
2406
|
+
const source = describeCredentialSource(cred);
|
|
2407
|
+
if (cred.kind === "oauth") {
|
|
2408
|
+
const expires = new Date(cred.expiresAt * 1e3).toISOString();
|
|
2409
|
+
return `OAuth access token for org ${org} from ${source}; account ${cred.accountId}, expires ${expires}.
|
|
2410
|
+
Send it as: Authorization: Bearer <token>
|
|
2411
|
+
`;
|
|
2412
|
+
}
|
|
2413
|
+
return `PAT for org ${org} from ${source}.
|
|
2414
|
+
Send it as: Authorization: Basic base64(":<token>") (curl: -u :<token>)
|
|
2415
|
+
`;
|
|
2416
|
+
}
|
|
2417
|
+
function reportTokenFailure(err) {
|
|
2418
|
+
if (err instanceof CredentialStoreUnavailableError) {
|
|
2419
|
+
process.stderr.write(`${err.message}
|
|
2420
|
+
`);
|
|
2421
|
+
process.exitCode = 4;
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
if (err instanceof CredentialMissingError || err instanceof CredentialRefreshError) {
|
|
2425
|
+
process.stderr.write(`${err.message}
|
|
2426
|
+
`);
|
|
2427
|
+
process.exitCode = 1;
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
throw err;
|
|
2431
|
+
}
|
|
2432
|
+
async function handleToken(orgFromGlobal) {
|
|
2433
|
+
const resolved = resolveOrg({ org: orgFromGlobal });
|
|
2434
|
+
if (!resolved) {
|
|
2435
|
+
process.stderr.write(`${formatResolutionError()}
|
|
2436
|
+
`);
|
|
2437
|
+
process.exitCode = 3;
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const org = resolved.org;
|
|
2441
|
+
const stderrIsTty = Boolean(process.stderr.isTTY);
|
|
2442
|
+
suppressCredentialStoreNotices(!stderrIsTty);
|
|
2443
|
+
let cred;
|
|
2444
|
+
try {
|
|
2445
|
+
cred = await exportCredential(org);
|
|
2446
|
+
} catch (err) {
|
|
2447
|
+
reportTokenFailure(err);
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
appendAuthAuditEvent({
|
|
2451
|
+
event: "auth.token",
|
|
2452
|
+
org,
|
|
2453
|
+
backend: probeBackend(),
|
|
2454
|
+
...cred.kind === "oauth" ? { accountId: cred.accountId } : {}
|
|
2455
|
+
});
|
|
2456
|
+
if (stderrIsTty) {
|
|
2457
|
+
process.stderr.write(describeExportedCredential(cred, org));
|
|
2458
|
+
}
|
|
2459
|
+
process.stdout.write(`${cred.token}
|
|
2460
|
+
`);
|
|
2461
|
+
}
|
|
2367
2462
|
function createAuthCommand() {
|
|
2368
2463
|
const command = new Command3("auth");
|
|
2369
2464
|
command.description(
|
|
@@ -2448,6 +2543,38 @@ Note: \`azdo auth\` (no subcommand) preserves the legacy PAT-prompt entry point;
|
|
|
2448
2543
|
const globals = logoutCmd.optsWithGlobals();
|
|
2449
2544
|
await handleLogout(options, globals.org);
|
|
2450
2545
|
});
|
|
2546
|
+
const tokenCmd = command.command("token").description(
|
|
2547
|
+
"Print the credential the CLI uses for an org \u2014 a PAT or an OAuth access token \u2014 to stdout (for scripted API calls)"
|
|
2548
|
+
).option("--org <name>", "Azure DevOps organization (defaults: git remote -> config)");
|
|
2549
|
+
tokenCmd.action(async () => {
|
|
2550
|
+
const globals = tokenCmd.optsWithGlobals();
|
|
2551
|
+
await handleToken(globals.org);
|
|
2552
|
+
});
|
|
2553
|
+
tokenCmd.addHelpText(
|
|
2554
|
+
"after",
|
|
2555
|
+
`
|
|
2556
|
+
stdout carries the token and nothing else, so it is safe to capture:
|
|
2557
|
+
|
|
2558
|
+
TOKEN=$(azdo auth token --org myorg)
|
|
2559
|
+
|
|
2560
|
+
The token is whichever credential the CLI would use itself (AZDO_PAT, then the
|
|
2561
|
+
stored credential for the org, then a .env AZDO_PAT); an expired OAuth access
|
|
2562
|
+
token is refreshed first. Treat it as a password \u2014 it carries your full access.
|
|
2563
|
+
|
|
2564
|
+
Azure DevOps needs a different header for each kind:
|
|
2565
|
+
|
|
2566
|
+
PAT curl -u :"$TOKEN" https://dev.azure.com/myorg/_apis/projects
|
|
2567
|
+
OAuth access tok curl -H "Authorization: Bearer $TOKEN" https://dev.azure.com/myorg/_apis/projects
|
|
2568
|
+
|
|
2569
|
+
When stderr is a terminal the command names the kind, source and expiry there;
|
|
2570
|
+
redirected or piped, stderr stays silent. There is deliberately no --json: use
|
|
2571
|
+
\`azdo auth status --json\` for machine-readable credential metadata, which never
|
|
2572
|
+
includes token material.
|
|
2573
|
+
|
|
2574
|
+
Exit codes: 1 no stored credential (or a rejected OAuth refresh), 3 org could
|
|
2575
|
+
not be resolved, 4 OS credential store unavailable. stdout stays empty on all.
|
|
2576
|
+
`
|
|
2577
|
+
);
|
|
2451
2578
|
const diagnoseCmd = command.command("diagnose").description("Show auth type, credential source, org, and live connectivity test result").option("--org <name>", "Azure DevOps organization (overrides context resolution)").option("--project <name>", "Azure DevOps project (optional context)").option("--json", "emit JSON instead of human-readable text", false);
|
|
2452
2579
|
diagnoseCmd.action(async (options) => {
|
|
2453
2580
|
const globals = diagnoseCmd.optsWithGlobals();
|
|
@@ -7087,6 +7214,18 @@ import os from "os";
|
|
|
7087
7214
|
var THROTTLE_MS = 10 * 60 * 1e3;
|
|
7088
7215
|
var FETCH_TIMEOUT_MS = 1500;
|
|
7089
7216
|
var REGISTRY_URL = "https://registry.npmjs.org/azdo-cli/latest";
|
|
7217
|
+
var UPDATE_CHECK_EXEMPT_COMMANDS = /* @__PURE__ */ new Set(["auth token"]);
|
|
7218
|
+
function commandPathOf(command) {
|
|
7219
|
+
const names = [];
|
|
7220
|
+
for (let node = command; node !== null; node = node.parent) {
|
|
7221
|
+
names.unshift(node.name());
|
|
7222
|
+
}
|
|
7223
|
+
return names;
|
|
7224
|
+
}
|
|
7225
|
+
function skipsUpdateCheck(commandPath) {
|
|
7226
|
+
const withoutRoot = commandPath.slice(1);
|
|
7227
|
+
return UPDATE_CHECK_EXEMPT_COMMANDS.has(withoutRoot.join(" "));
|
|
7228
|
+
}
|
|
7090
7229
|
function getCachePath() {
|
|
7091
7230
|
return path2.join(os.homedir(), ".azdo", "update-check.json");
|
|
7092
7231
|
}
|
|
@@ -7184,6 +7323,49 @@ async function getUpdateNotice(opts) {
|
|
|
7184
7323
|
}
|
|
7185
7324
|
}
|
|
7186
7325
|
|
|
7326
|
+
// src/program.ts
|
|
7327
|
+
function createProgram() {
|
|
7328
|
+
const program2 = new Command19();
|
|
7329
|
+
program2.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
7330
|
+
program2.option("--no-update-check", "Skip the check for a newer published version");
|
|
7331
|
+
program2.option("--trace <filepath>", "Append redacted HTTP request/response trace to a file (owner-read-only permissions)");
|
|
7332
|
+
program2.addCommand(createGetItemCommand());
|
|
7333
|
+
program2.addCommand(createAuthCommand());
|
|
7334
|
+
program2.addCommand(createClearPatCommand());
|
|
7335
|
+
program2.addCommand(createConfigCommand());
|
|
7336
|
+
program2.addCommand(createSetStateCommand());
|
|
7337
|
+
program2.addCommand(createAssignCommand());
|
|
7338
|
+
program2.addCommand(createSetFieldCommand());
|
|
7339
|
+
program2.addCommand(createGetMdFieldCommand());
|
|
7340
|
+
program2.addCommand(createSetMdFieldCommand());
|
|
7341
|
+
program2.addCommand(createUpsertCommand());
|
|
7342
|
+
program2.addCommand(createListFieldsCommand());
|
|
7343
|
+
program2.addCommand(createPrCommand());
|
|
7344
|
+
program2.addCommand(createPipelineCommand());
|
|
7345
|
+
program2.addCommand(createCommentsCommand());
|
|
7346
|
+
program2.addCommand(createDownloadAttachmentCommand());
|
|
7347
|
+
program2.addCommand(createAddAttachmentCommand());
|
|
7348
|
+
program2.addCommand(createDeleteAttachmentCommand());
|
|
7349
|
+
program2.addCommand(createRelationsCommand());
|
|
7350
|
+
program2.showHelpAfterError();
|
|
7351
|
+
program2.hook("preAction", () => {
|
|
7352
|
+
const { trace } = program2.opts();
|
|
7353
|
+
if (trace) {
|
|
7354
|
+
initTraceWriter(trace);
|
|
7355
|
+
}
|
|
7356
|
+
});
|
|
7357
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
7358
|
+
if (skipsUpdateCheck(commandPathOf(actionCommand))) {
|
|
7359
|
+
return;
|
|
7360
|
+
}
|
|
7361
|
+
const notice = await getUpdateNotice({ enabled: program2.opts().updateCheck });
|
|
7362
|
+
if (notice) {
|
|
7363
|
+
process.stderr.write(notice + "\n");
|
|
7364
|
+
}
|
|
7365
|
+
});
|
|
7366
|
+
return program2;
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7187
7369
|
// src/index.ts
|
|
7188
7370
|
function exitOnEpipe(err) {
|
|
7189
7371
|
if (err.code === "EPIPE") {
|
|
@@ -7193,41 +7375,7 @@ function exitOnEpipe(err) {
|
|
|
7193
7375
|
}
|
|
7194
7376
|
process.stdout.on("error", exitOnEpipe);
|
|
7195
7377
|
process.stderr.on("error", exitOnEpipe);
|
|
7196
|
-
var program =
|
|
7197
|
-
program.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
|
|
7198
|
-
program.option("--no-update-check", "Skip the check for a newer published version");
|
|
7199
|
-
program.option("--trace <filepath>", "Append redacted HTTP request/response trace to a file (owner-read-only permissions)");
|
|
7200
|
-
program.addCommand(createGetItemCommand());
|
|
7201
|
-
program.addCommand(createAuthCommand());
|
|
7202
|
-
program.addCommand(createClearPatCommand());
|
|
7203
|
-
program.addCommand(createConfigCommand());
|
|
7204
|
-
program.addCommand(createSetStateCommand());
|
|
7205
|
-
program.addCommand(createAssignCommand());
|
|
7206
|
-
program.addCommand(createSetFieldCommand());
|
|
7207
|
-
program.addCommand(createGetMdFieldCommand());
|
|
7208
|
-
program.addCommand(createSetMdFieldCommand());
|
|
7209
|
-
program.addCommand(createUpsertCommand());
|
|
7210
|
-
program.addCommand(createListFieldsCommand());
|
|
7211
|
-
program.addCommand(createPrCommand());
|
|
7212
|
-
program.addCommand(createPipelineCommand());
|
|
7213
|
-
program.addCommand(createCommentsCommand());
|
|
7214
|
-
program.addCommand(createDownloadAttachmentCommand());
|
|
7215
|
-
program.addCommand(createAddAttachmentCommand());
|
|
7216
|
-
program.addCommand(createDeleteAttachmentCommand());
|
|
7217
|
-
program.addCommand(createRelationsCommand());
|
|
7218
|
-
program.showHelpAfterError();
|
|
7219
|
-
program.hook("preAction", () => {
|
|
7220
|
-
const { trace } = program.opts();
|
|
7221
|
-
if (trace) {
|
|
7222
|
-
initTraceWriter(trace);
|
|
7223
|
-
}
|
|
7224
|
-
});
|
|
7225
|
-
program.hook("postAction", async () => {
|
|
7226
|
-
const notice = await getUpdateNotice({ enabled: program.opts().updateCheck });
|
|
7227
|
-
if (notice) {
|
|
7228
|
-
process.stderr.write(notice + "\n");
|
|
7229
|
-
}
|
|
7230
|
-
});
|
|
7378
|
+
var program = createProgram();
|
|
7231
7379
|
await program.parseAsync();
|
|
7232
7380
|
if (process.argv.length <= 2) {
|
|
7233
7381
|
program.help();
|