azdo-cli 0.24.0-develop.918 → 0.24.0-develop.932

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
@@ -162,6 +162,7 @@ azdo relations list 1000 --json # JSON: { workItemId, relations: [..
162
162
  | Linux credential store setup | [docs/linux-credential-store.md](docs/linux-credential-store.md) |
163
163
  | Full command reference — every command, alias and option, credential resolution order, and the `--json` output contracts | [docs/commands.md](docs/commands.md) |
164
164
  | Development setup | [docs/development.md](docs/development.md) |
165
+ | Project operations and automation (Dev Container, VM, Hermes) | [instructions/README.md](instructions/README.md) |
165
166
 
166
167
  ## License
167
168
 
@@ -1047,6 +1047,17 @@ var DpapiEntry = class {
1047
1047
  }
1048
1048
  };
1049
1049
 
1050
+ // src/lib/async.ts
1051
+ function runSequentially(items, task) {
1052
+ return items.reduce(
1053
+ (previous, item, index) => previous.then(() => task(item, index)),
1054
+ Promise.resolve()
1055
+ );
1056
+ }
1057
+ function asPromise(fn) {
1058
+ return Promise.resolve().then(fn);
1059
+ }
1060
+
1050
1061
  // src/services/credential-store.ts
1051
1062
  var SERVICE = "azdo-cli";
1052
1063
  var LEGACY_ACCOUNT = "pat";
@@ -1187,7 +1198,7 @@ function serializeCredential(cred) {
1187
1198
  }
1188
1199
  return JSON.stringify(cred);
1189
1200
  }
1190
- async function maybeMigrateLegacy(targetOrg) {
1201
+ function maybeMigrateLegacy(targetOrg) {
1191
1202
  if (activeCredentialStore() === "dpapi") return null;
1192
1203
  const config = loadConfig();
1193
1204
  if (!config.org || config.org !== targetOrg) {
@@ -1235,30 +1246,31 @@ async function getPat(org) {
1235
1246
  if (cred.kind === "pat") return cred.token;
1236
1247
  return null;
1237
1248
  }
1238
- async function getStoredCredential(org) {
1239
- const entry = entryFor(accountFor(org));
1240
- const value = wrapUnavailable(() => entry.getPassword());
1241
- if (value === null) {
1242
- const migrated = await maybeMigrateLegacy(org);
1243
- if (migrated === null) return null;
1244
- return parseStoredValue(migrated);
1245
- }
1246
- return parseStoredValue(value);
1249
+ function getStoredCredential(org) {
1250
+ return asPromise(() => {
1251
+ const entry = entryFor(accountFor(org));
1252
+ const value = wrapUnavailable(() => entry.getPassword()) ?? maybeMigrateLegacy(org);
1253
+ return value === null ? null : parseStoredValue(value);
1254
+ });
1247
1255
  }
1248
- async function storePat(org, pat) {
1249
- const cred = { kind: "pat", token: pat };
1250
- const entry = entryFor(accountFor(org));
1251
- wrapUnavailable(() => entry.setPassword(serializeCredential(cred)));
1252
- appendAuthAuditEvent({
1253
- event: "auth.store",
1254
- org,
1255
- backend: probeBackend(),
1256
- masked_pat: maskedDisplay(pat)
1256
+ function storePat(org, pat) {
1257
+ return asPromise(() => {
1258
+ const cred = { kind: "pat", token: pat };
1259
+ const entry = entryFor(accountFor(org));
1260
+ wrapUnavailable(() => entry.setPassword(serializeCredential(cred)));
1261
+ appendAuthAuditEvent({
1262
+ event: "auth.store",
1263
+ org,
1264
+ backend: probeBackend(),
1265
+ masked_pat: maskedDisplay(pat)
1266
+ });
1257
1267
  });
1258
1268
  }
1259
- async function storeOAuthCredential(org, cred) {
1260
- const entry = entryFor(accountFor(org));
1261
- wrapUnavailable(() => entry.setPassword(serializeCredential(cred)));
1269
+ function storeOAuthCredential(org, cred) {
1270
+ return asPromise(() => {
1271
+ const entry = entryFor(accountFor(org));
1272
+ wrapUnavailable(() => entry.setPassword(serializeCredential(cred)));
1273
+ });
1262
1274
  }
1263
1275
  async function deletePat(org) {
1264
1276
  const entry = entryFor(accountFor(org));
@@ -1290,13 +1302,16 @@ async function deletePat(org) {
1290
1302
  }
1291
1303
  try {
1292
1304
  const { unlinkSync: unlinkSync2 } = await import("fs");
1293
- const { lockPath: lockPath2 } = await import("./oauth-token-refresh-RLDAAR5D.js");
1305
+ const { lockPath: lockPath2 } = await import("./oauth-token-refresh-4WISWRPJ.js");
1294
1306
  unlinkSync2(lockPath2(org));
1295
1307
  } catch {
1296
1308
  }
1297
1309
  return true;
1298
1310
  }
1299
- async function listOrgsWithStoredPat() {
1311
+ function listOrgsWithStoredPat() {
1312
+ return asPromise(listOrgsWithStoredPatSync);
1313
+ }
1314
+ function listOrgsWithStoredPatSync() {
1300
1315
  const seen = /* @__PURE__ */ new Set();
1301
1316
  for (const ev of readAuditEvents()) {
1302
1317
  if (ev.event === "auth.store" || ev.event === "oauth-login-success") {
@@ -1387,25 +1402,26 @@ function lockPath(org) {
1387
1402
  async function defaultAcquireLock(org, waitMs = DEFAULT_LOCK_WAIT_MS) {
1388
1403
  const path4 = lockPath(org);
1389
1404
  mkdirSync(locksDir(), { recursive: true, mode: 448 });
1390
- const deadline = Date.now() + waitMs;
1391
- for (; ; ) {
1392
- try {
1393
- const fd = openSync2(path4, "wx");
1394
- closeSync2(fd);
1395
- return {
1396
- release: () => {
1397
- try {
1398
- unlinkSync(path4);
1399
- } catch {
1400
- }
1405
+ return tryAcquireLock(path4, Date.now() + waitMs);
1406
+ }
1407
+ async function tryAcquireLock(path4, deadline) {
1408
+ try {
1409
+ const fd = openSync2(path4, "wx");
1410
+ closeSync2(fd);
1411
+ return {
1412
+ release: () => {
1413
+ try {
1414
+ unlinkSync(path4);
1415
+ } catch {
1401
1416
  }
1402
- };
1403
- } catch (err) {
1404
- if (err.code !== "EEXIST") throw err;
1405
- if (Date.now() > deadline) return null;
1406
- await new Promise((r) => setTimeout(r, 100));
1407
- }
1417
+ }
1418
+ };
1419
+ } catch (err) {
1420
+ if (err.code !== "EEXIST") throw err;
1421
+ if (Date.now() > deadline) return null;
1408
1422
  }
1423
+ await new Promise((r) => setTimeout(r, 100));
1424
+ return tryAcquireLock(path4, deadline);
1409
1425
  }
1410
1426
  function classifyRefreshFailure(error) {
1411
1427
  const code = typeof error === "string" ? error : error.error ?? "";
@@ -1543,6 +1559,7 @@ export {
1543
1559
  CredentialStoreUnavailableError,
1544
1560
  CredentialMissingError,
1545
1561
  CredentialRefreshError,
1562
+ runSequentially,
1546
1563
  appendAuthAuditEvent,
1547
1564
  readAuditEvents,
1548
1565
  SETTINGS,
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  resolveOAuthConfig,
38
38
  resolveScopedConfig,
39
39
  runAuthCodeFlow,
40
+ runSequentially,
40
41
  setConfigValue,
41
42
  setOrgScopedValue,
42
43
  storeOAuthCredential,
@@ -45,7 +46,7 @@ import {
45
46
  tokenResponseToCredential,
46
47
  unsetConfigValue,
47
48
  unsetOrgScopedValue
48
- } from "./chunk-DNOOZIXA.js";
49
+ } from "./chunk-WCO6SF63.js";
49
50
 
50
51
  // src/program.ts
51
52
  import { Command as Command20 } from "commander";
@@ -154,6 +155,14 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
154
155
  return;
155
156
  }
156
157
  const error = err instanceof Error ? err : new Error(String(err));
158
+ writeCommandError(error, id, context, scope);
159
+ if (exit) {
160
+ process.exit(1);
161
+ } else {
162
+ process.exitCode = 1;
163
+ }
164
+ }
165
+ function writeCommandError(error, id, context, scope) {
157
166
  const msg = error.message;
158
167
  const scopeLabel = scope === "read" ? "Work Items (read)" : "Work Items (Read & Write)";
159
168
  if (isSentinel(msg, "AUTH_FAILED")) {
@@ -193,11 +202,6 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
193
202
  process.stderr.write(`Error: ${msg}
194
203
  `);
195
204
  }
196
- if (exit) {
197
- process.exit(1);
198
- } else {
199
- process.exitCode = 1;
200
- }
201
205
  }
202
206
  var STDIN_PATH = "-";
203
207
  function readTextSource(file) {
@@ -340,9 +344,8 @@ async function downloadImagesFromFields(fields, args, credential) {
340
344
  }
341
345
  }
342
346
  const results = [];
343
- let index = 0;
344
- for (const reference of references) {
345
- index += 1;
347
+ await runSequentially(references, async (reference, position) => {
348
+ const index = position + 1;
346
349
  try {
347
350
  const bytes = await downloadAttachment(reference.url, credential);
348
351
  const processed = await processImageBytes(bytes, options.maxWidth);
@@ -363,7 +366,7 @@ async function downloadImagesFromFields(fields, args, credential) {
363
366
  error: err instanceof Error ? err.message : String(err)
364
367
  });
365
368
  }
366
- }
369
+ });
367
370
  return results;
368
371
  }
369
372
  async function runImageDownload(fields, args, credential) {
@@ -846,23 +849,24 @@ async function getWorkItemFieldValue(context, id, cred, fieldName) {
846
849
  }
847
850
  return stringifyFieldValue(value);
848
851
  }
852
+ async function listWorkItemCommentPages(context, id, cred, continuationToken, comments) {
853
+ const response = await fetchWithErrors(
854
+ buildWorkItemCommentsListUrl(context, id, continuationToken ?? void 0).toString(),
855
+ { headers: authHeaders(cred) }
856
+ );
857
+ if (!response.ok) {
858
+ throw httpError(response);
859
+ }
860
+ const data = await response.json();
861
+ comments.push(
862
+ ...(data.comments ?? []).map((comment) => mapWorkItemComment(comment, id)).filter((comment) => !comment.isDeleted)
863
+ );
864
+ const next = readContinuationToken(response, data);
865
+ if (next === null) return comments;
866
+ return listWorkItemCommentPages(context, id, cred, next, comments);
867
+ }
849
868
  async function listWorkItemComments(context, id, cred) {
850
- const comments = [];
851
- let continuationToken = null;
852
- do {
853
- const response = await fetchWithErrors(
854
- buildWorkItemCommentsListUrl(context, id, continuationToken ?? void 0).toString(),
855
- { headers: authHeaders(cred) }
856
- );
857
- if (!response.ok) {
858
- throw httpError(response);
859
- }
860
- const data = await response.json();
861
- comments.push(
862
- ...(data.comments ?? []).map((comment) => mapWorkItemComment(comment, id)).filter((comment) => !comment.isDeleted)
863
- );
864
- continuationToken = readContinuationToken(response, data);
865
- } while (continuationToken !== null);
869
+ const comments = await listWorkItemCommentPages(context, id, cred, null, []);
866
870
  return {
867
871
  workItemId: id,
868
872
  count: comments.length,
@@ -1442,29 +1446,31 @@ async function loginWithOAuth(org, opts = {}) {
1442
1446
  async function logout(opts = {}) {
1443
1447
  if (opts.all) {
1444
1448
  const orgs = await listOrgsWithStoredPat();
1445
- const removed = [];
1446
- for (const o of orgs) {
1447
- const cred2 = await getStoredCredential(o);
1448
- const ok2 = await deletePat(o);
1449
- if (ok2 && cred2 !== null) {
1450
- removed.push({ org: o, kind: cred2.kind });
1451
- }
1452
- }
1453
- return { removed };
1449
+ return { removed: await logoutOrgsInOrder(orgs, 0, []) };
1454
1450
  }
1455
1451
  if (!opts.org) {
1456
1452
  throw new Error("logout requires an org or --all");
1457
1453
  }
1458
- const cred = await getStoredCredential(opts.org);
1459
- const ok = await deletePat(opts.org);
1460
- return { removed: ok && cred !== null ? [{ org: opts.org, kind: cred.kind }] : [] };
1454
+ return { removed: await logoutOrg(opts.org) };
1455
+ }
1456
+ async function logoutOrgsInOrder(orgs, index, removed) {
1457
+ const org = orgs[index];
1458
+ if (org === void 0) return removed;
1459
+ removed.push(...await logoutOrg(org));
1460
+ return logoutOrgsInOrder(orgs, index + 1, removed);
1461
+ }
1462
+ async function logoutOrg(org) {
1463
+ const cred = await getStoredCredential(org);
1464
+ const ok = await deletePat(org);
1465
+ return ok && cred !== null ? [{ org, kind: cred.kind }] : [];
1461
1466
  }
1462
1467
  async function status() {
1463
1468
  const orgs = await listOrgsWithStoredPat();
1469
+ const creds = await Promise.all(orgs.map((org) => getStoredCredential(org)));
1464
1470
  const out = [];
1465
- for (const org of orgs) {
1466
- const cred = await getStoredCredential(org);
1467
- if (cred === null) continue;
1471
+ orgs.forEach((org, i) => {
1472
+ const cred = creds[i];
1473
+ if (cred === null) return;
1468
1474
  if (cred.kind === "pat") {
1469
1475
  out.push({ org, kind: "pat", backend: probeBackend() });
1470
1476
  } else {
@@ -1477,7 +1483,7 @@ async function status() {
1477
1483
  backend: probeBackend()
1478
1484
  });
1479
1485
  }
1480
- }
1486
+ });
1481
1487
  return { orgs: out };
1482
1488
  }
1483
1489
  async function exportCredential(org) {
@@ -3128,10 +3134,8 @@ ${renderSettingsHelp()}`);
3128
3134
  const ask = createAsk(rl);
3129
3135
  process.stderr.write("Azure DevOps CLI - Configuration Wizard\n");
3130
3136
  process.stderr.write("=======================================\n\n");
3131
- for (const setting of SETTINGS) {
3132
- if (setting.key === "credentialStore") continue;
3133
- await promptForSetting(cfg, setting, ask);
3134
- }
3137
+ const wizardSettings = SETTINGS.filter((setting) => setting.key !== "credentialStore");
3138
+ await runSequentially(wizardSettings, (setting) => promptForSetting(cfg, setting, ask));
3135
3139
  rl.close();
3136
3140
  process.stderr.write("Configuration complete!\n");
3137
3141
  });
@@ -3771,7 +3775,8 @@ function stringifyValue(value) {
3771
3775
  if (value === null || value === void 0) return "";
3772
3776
  if (typeof value === "string") return value;
3773
3777
  if (typeof value === "object") return JSON.stringify(value);
3774
- if (typeof value === "symbol" || typeof value === "function") return value.toString();
3778
+ if (typeof value === "symbol") return value.toString();
3779
+ if (typeof value === "function") return `[function ${value.name || "anonymous"}]`;
3775
3780
  return String(value);
3776
3781
  }
3777
3782
  function formatRichValue(raw) {
@@ -6871,26 +6876,27 @@ async function getTestSummary(context, cred, buildId) {
6871
6876
  var MAX_FAILED_TESTS = 50;
6872
6877
  async function getFailedTests(context, cred, buildId) {
6873
6878
  const testRuns = await listTestRuns(context, cred, buildId);
6874
- const failed = [];
6875
- for (const testRun of testRuns) {
6876
- if (failed.length >= MAX_FAILED_TESTS) break;
6877
- const resultsUrl = withApiVersion(
6878
- new URL(`${orgProjectBase(context)}/_apis/test/runs/${testRun.id}/results`)
6879
- );
6880
- resultsUrl.searchParams.set("outcomes", "Failed");
6881
- resultsUrl.searchParams.set("$top", String(MAX_FAILED_TESTS - failed.length));
6882
- const resultsResponse = await fetchWithErrors(resultsUrl.toString(), {
6883
- headers: authHeaders(cred)
6879
+ return collectFailedTests(context, cred, testRuns, 0, []);
6880
+ }
6881
+ async function collectFailedTests(context, cred, testRuns, index, failed) {
6882
+ const testRun = testRuns[index];
6883
+ if (testRun === void 0 || failed.length >= MAX_FAILED_TESTS) return failed;
6884
+ const resultsUrl = withApiVersion(
6885
+ new URL(`${orgProjectBase(context)}/_apis/test/runs/${testRun.id}/results`)
6886
+ );
6887
+ resultsUrl.searchParams.set("outcomes", "Failed");
6888
+ resultsUrl.searchParams.set("$top", String(MAX_FAILED_TESTS - failed.length));
6889
+ const resultsResponse = await fetchWithErrors(resultsUrl.toString(), {
6890
+ headers: authHeaders(cred)
6891
+ });
6892
+ const resultsData = await readJsonResponse2(resultsResponse);
6893
+ for (const result of resultsData.value) {
6894
+ failed.push({
6895
+ name: result.testCaseTitle ?? result.automatedTestName ?? "(unnamed test)",
6896
+ errorMessage: result.errorMessage ?? null
6884
6897
  });
6885
- const resultsData = await readJsonResponse2(resultsResponse);
6886
- for (const result of resultsData.value) {
6887
- failed.push({
6888
- name: result.testCaseTitle ?? result.automatedTestName ?? "(unnamed test)",
6889
- errorMessage: result.errorMessage ?? null
6890
- });
6891
- }
6892
6898
  }
6893
- return failed;
6899
+ return collectFailedTests(context, cred, testRuns, index + 1, failed);
6894
6900
  }
6895
6901
  function secondsBetween(start, finish) {
6896
6902
  if (!start || !finish) return null;
@@ -7163,6 +7169,17 @@ function sleep(ms) {
7163
7169
  setTimeout(resolve2, ms);
7164
7170
  });
7165
7171
  }
7172
+ async function pollRunUntilDone(context, cred, runId, timing) {
7173
+ const status2 = await getBuildStatus(context, cred, runId);
7174
+ if (status2.state === "completed") {
7175
+ return { id: runId, state: status2.state, result: status2.result, timedOut: false };
7176
+ }
7177
+ if (Date.now() >= timing.deadline) {
7178
+ return { id: runId, state: status2.state, result: status2.result, timedOut: true };
7179
+ }
7180
+ await sleep(timing.pollMs);
7181
+ return pollRunUntilDone(context, cred, runId, timing);
7182
+ }
7166
7183
  function formatTable(rows, rightAlign = /* @__PURE__ */ new Set()) {
7167
7184
  const widths = [];
7168
7185
  for (const row of rows) {
@@ -7325,19 +7342,10 @@ function createPipelineWaitCommand() {
7325
7342
  const resolved = await resolvePipelineContext(options);
7326
7343
  context = resolved.context;
7327
7344
  const deadline = Date.now() + timeoutSec * 1e3;
7328
- let waitResult = null;
7329
- for (; ; ) {
7330
- const status2 = await getBuildStatus(resolved.context, resolved.cred, runId);
7331
- if (status2.state === "completed") {
7332
- waitResult = { id: runId, state: status2.state, result: status2.result, timedOut: false };
7333
- break;
7334
- }
7335
- if (Date.now() >= deadline) {
7336
- waitResult = { id: runId, state: status2.state, result: status2.result, timedOut: true };
7337
- break;
7338
- }
7339
- await sleep(pollSec * 1e3);
7340
- }
7345
+ const waitResult = await pollRunUntilDone(resolved.context, resolved.cred, runId, {
7346
+ deadline,
7347
+ pollMs: pollSec * 1e3
7348
+ });
7341
7349
  applyWaitExitCode(waitResult);
7342
7350
  if (options.json) {
7343
7351
  process.stdout.write(`${JSON.stringify(waitResult, null, 2)}
@@ -5,7 +5,7 @@ import {
5
5
  lockPath,
6
6
  locksDir,
7
7
  refreshIfNeeded
8
- } from "./chunk-DNOOZIXA.js";
8
+ } from "./chunk-WCO6SF63.js";
9
9
  export {
10
10
  _resetInFlight,
11
11
  classifyRefreshFailure,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.24.0-develop.918",
3
+ "version": "0.24.0-develop.932",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {