azdo-cli 0.23.0-develop.895 → 0.23.0-feature-058-credential-error-cause.897

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/services/oauth-token-refresh.ts
4
- import { closeSync, mkdirSync, openSync, unlinkSync } from "fs";
4
+ import { closeSync as closeSync2, mkdirSync, openSync as openSync2, unlinkSync } from "fs";
5
5
  import { homedir } from "os";
6
6
  import { join } from "path";
7
7
 
@@ -37,12 +37,12 @@ import os from "os";
37
37
 
38
38
  // src/services/credential-store-kind.ts
39
39
  var CREDENTIAL_STORES = ["keyring", "dpapi"];
40
- function parseCredentialStore(value, platform = process.platform) {
40
+ function parseCredentialStore(value, platform2 = process.platform) {
41
41
  const normalised = value.trim().toLowerCase();
42
42
  if (!CREDENTIAL_STORES.includes(normalised)) {
43
43
  throw new Error(`Invalid value "${value}" for credentialStore. Must be one of: ${CREDENTIAL_STORES.join(", ")}.`);
44
44
  }
45
- if (normalised === "dpapi" && platform !== "win32") {
45
+ if (normalised === "dpapi" && platform2 !== "win32") {
46
46
  throw new Error('credentialStore "dpapi" is only available on Windows.');
47
47
  }
48
48
  return normalised;
@@ -349,14 +349,14 @@ function buildScopeString(scopes) {
349
349
 
350
350
  // src/services/browser-open.ts
351
351
  import { execFile } from "child_process";
352
- function isHeadless(platform, hasDisplay) {
353
- if (platform === "linux") {
352
+ function isHeadless(platform2, hasDisplay) {
353
+ if (platform2 === "linux") {
354
354
  return !hasDisplay;
355
355
  }
356
356
  return false;
357
357
  }
358
- function commandForPlatform(platform) {
359
- switch (platform) {
358
+ function commandForPlatform(platform2) {
359
+ switch (platform2) {
360
360
  case "darwin":
361
361
  return { cmd: "open", args: (url) => [url] };
362
362
  case "win32":
@@ -368,15 +368,15 @@ function commandForPlatform(platform) {
368
368
  }
369
369
  }
370
370
  async function openUrl(url, opts = {}) {
371
- const platform = opts.platform ?? process.platform;
371
+ const platform2 = opts.platform ?? process.platform;
372
372
  const hasDisplay = opts.hasDisplay ?? (process.env.DISPLAY !== void 0 && process.env.DISPLAY !== "");
373
373
  const forcePrint = opts.forcePrint ?? false;
374
- if (forcePrint || isHeadless(platform, hasDisplay)) {
374
+ if (forcePrint || isHeadless(platform2, hasDisplay)) {
375
375
  process.stderr.write(`Open this URL in your browser: ${url}
376
376
  `);
377
377
  return "printed";
378
378
  }
379
- const spec = commandForPlatform(platform);
379
+ const spec = commandForPlatform(platform2);
380
380
  if (!spec) {
381
381
  process.stderr.write(`Open this URL in your browser: ${url}
382
382
  `);
@@ -780,9 +780,110 @@ function readAuditEvents() {
780
780
  // src/services/credential-store.ts
781
781
  import { Entry } from "@napi-rs/keyring";
782
782
 
783
+ // src/services/trace-writer.ts
784
+ import { openSync, writeSync, closeSync } from "fs";
785
+ import { platform } from "os";
786
+ var REDACTED = "[REDACTED]";
787
+ var SENSITIVE_HEADER = /^(authorization|x-.*token)$/i;
788
+ var SENSITIVE_QUERY_PARAM = /^(token|pat)$/i;
789
+ var SENSITIVE_BODY_FIELD = /^(token|accessToken|pat)$/;
790
+ function redactHeaders(headers) {
791
+ const out = {};
792
+ for (const [key, value] of Object.entries(headers)) {
793
+ out[key] = SENSITIVE_HEADER.test(key) ? REDACTED : value;
794
+ }
795
+ return out;
796
+ }
797
+ function redactUrl(url) {
798
+ try {
799
+ const u = new URL(url);
800
+ for (const [key] of u.searchParams.entries()) {
801
+ if (SENSITIVE_QUERY_PARAM.test(key)) {
802
+ u.searchParams.set(key, REDACTED);
803
+ }
804
+ }
805
+ return u.toString();
806
+ } catch {
807
+ return url;
808
+ }
809
+ }
810
+ var SECRET_ASSIGNMENT = String.raw`["']?\s*[:=]\s*["']?)[^"'\s,;&}]+`;
811
+ var SECRET_IN_TEXT = [
812
+ [/\b(Bearer|Basic)\s+[\w.~+/=-]{8,}/gi, `$1 ${REDACTED}`],
813
+ [new RegExp(String.raw`(\b(?:access|refresh|api)[_-]?(?:token|key)\b${SECRET_ASSIGNMENT}`, "gi"), `$1${REDACTED}`],
814
+ [new RegExp(String.raw`(\b(?:token|pat|password|secret)\b${SECRET_ASSIGNMENT}`, "gi"), `$1${REDACTED}`],
815
+ [/\beyJ[A-Za-z0-9._~+/=-]{20,}/g, REDACTED],
816
+ [/\b(?=[A-Za-z0-9]*\d)(?=[A-Za-z0-9]*[A-Za-z])[A-Za-z0-9]{40,}\b/g, REDACTED]
817
+ ];
818
+ function redactText(text) {
819
+ let out = text;
820
+ for (const [pattern, replacement] of SECRET_IN_TEXT) {
821
+ out = out.replace(pattern, replacement);
822
+ }
823
+ return out;
824
+ }
825
+ function redactBody(body) {
826
+ if (body === null) return null;
827
+ try {
828
+ const parsed = JSON.parse(body);
829
+ let changed = false;
830
+ const redacted = { ...parsed };
831
+ for (const key of Object.keys(parsed)) {
832
+ if (SENSITIVE_BODY_FIELD.test(key)) {
833
+ redacted[key] = REDACTED;
834
+ changed = true;
835
+ }
836
+ }
837
+ return changed ? JSON.stringify(redacted) : body;
838
+ } catch {
839
+ return body;
840
+ }
841
+ }
842
+ var TraceWriter = class {
843
+ fd;
844
+ constructor(filepath) {
845
+ const mode = platform() === "win32" ? void 0 : 384;
846
+ this.fd = openSync(filepath, "a", mode);
847
+ }
848
+ append(entry) {
849
+ const line = JSON.stringify(entry) + "\n\n";
850
+ writeSync(this.fd, line);
851
+ }
852
+ close() {
853
+ closeSync(this.fd);
854
+ }
855
+ };
856
+ var activeWriter = null;
857
+ function initTraceWriter(filepath) {
858
+ try {
859
+ activeWriter = new TraceWriter(filepath);
860
+ } catch (err) {
861
+ const msg = err instanceof Error ? err.message : String(err);
862
+ process.stderr.write(`Warning: could not open trace file "${filepath}": ${msg}
863
+ `);
864
+ }
865
+ }
866
+ function getActiveTraceWriter() {
867
+ return activeWriter;
868
+ }
869
+
783
870
  // src/types/credential.ts
784
- function defaultUnavailableMessage(backend) {
785
- const base = `OS secret backend unavailable (${backend}). Install the platform's credential service and try again.`;
871
+ function underlyingDetail(cause) {
872
+ if (!(cause instanceof Error)) return null;
873
+ const text = redactText(cause.message).replace(/\s+/g, " ").trim();
874
+ if (!text) return null;
875
+ const name = cause.name && cause.name !== "Error" ? `${cause.name}: ` : "";
876
+ return `${name}${text}`;
877
+ }
878
+ function defaultUnavailableMessage(backend, cause) {
879
+ const detail = underlyingDetail(cause);
880
+ const suffix = detail ? `
881
+ Underlying error: ${detail}` : "";
882
+ if (backend === "linux-libsecret") {
883
+ return `OS secret backend unavailable (linux-libsecret). The Secret Service may be missing or unreachable (for example a sandbox or container blocking the user D-Bus socket, or no session bus).${suffix}
884
+ Install/start a Secret Service (see docs/linux-credential-store.md), run azdo outside the sandbox, or set AZDO_PAT to bypass the keyring.`;
885
+ }
886
+ const base = `OS secret backend unavailable (${backend}). Install the platform's credential service and try again.${suffix}`;
786
887
  if (backend !== "windows-credential-manager") return base;
787
888
  return `${base}
788
889
  Over SSH, Windows Credential Manager is not reachable. Store credentials with DPAPI in ~/.azdo instead: \`azdo config set credentialStore dpapi\` (or AZDO_CREDENTIAL_STORE=dpapi), then \`azdo auth login\` again.`;
@@ -790,7 +891,7 @@ Over SSH, Windows Credential Manager is not reachable. Store credentials with DP
790
891
  var CredentialStoreUnavailableError = class extends Error {
791
892
  backend;
792
893
  constructor(backend, cause, message) {
793
- super(message ?? defaultUnavailableMessage(backend));
894
+ super(message ?? defaultUnavailableMessage(backend, cause));
794
895
  this.name = "CredentialStoreUnavailableError";
795
896
  this.backend = backend;
796
897
  if (cause instanceof Error) {
@@ -1189,7 +1290,7 @@ async function deletePat(org) {
1189
1290
  }
1190
1291
  try {
1191
1292
  const { unlinkSync: unlinkSync2 } = await import("fs");
1192
- const { lockPath: lockPath2 } = await import("./oauth-token-refresh-QPPOTES2.js");
1293
+ const { lockPath: lockPath2 } = await import("./oauth-token-refresh-RLDAAR5D.js");
1193
1294
  unlinkSync2(lockPath2(org));
1194
1295
  } catch {
1195
1296
  }
@@ -1289,8 +1390,8 @@ async function defaultAcquireLock(org, waitMs = DEFAULT_LOCK_WAIT_MS) {
1289
1390
  const deadline = Date.now() + waitMs;
1290
1391
  for (; ; ) {
1291
1392
  try {
1292
- const fd = openSync(path4, "wx");
1293
- closeSync(fd);
1393
+ const fd = openSync2(path4, "wx");
1394
+ closeSync2(fd);
1294
1395
  return {
1295
1396
  release: () => {
1296
1397
  try {
@@ -1433,6 +1534,12 @@ function _resetInFlight() {
1433
1534
  }
1434
1535
 
1435
1536
  export {
1537
+ redactHeaders,
1538
+ redactUrl,
1539
+ redactText,
1540
+ redactBody,
1541
+ initTraceWriter,
1542
+ getActiveTraceWriter,
1436
1543
  CredentialStoreUnavailableError,
1437
1544
  CredentialMissingError,
1438
1545
  CredentialRefreshError,
package/dist/index.js CHANGED
@@ -13,10 +13,12 @@ import {
13
13
  deleteOrgScope,
14
14
  deletePat,
15
15
  firstPartyShippedScopes,
16
+ getActiveTraceWriter,
16
17
  getConfigValue,
17
18
  getOrgScopedValue,
18
19
  getPat,
19
20
  getStoredCredential,
21
+ initTraceWriter,
20
22
  listKeyringCredentials,
21
23
  listOrgsWithStoredPat,
22
24
  loadConfig,
@@ -27,6 +29,10 @@ import {
27
29
  probeBackend,
28
30
  readAuditEvents,
29
31
  readTokenResponse,
32
+ redactBody,
33
+ redactHeaders,
34
+ redactText,
35
+ redactUrl,
30
36
  refreshIfNeeded,
31
37
  resolveOAuthConfig,
32
38
  resolveScopedConfig,
@@ -39,7 +45,7 @@ import {
39
45
  tokenResponseToCredential,
40
46
  unsetConfigValue,
41
47
  unsetOrgScopedValue
42
- } from "./chunk-MHJ6OBX3.js";
48
+ } from "./chunk-DNOOZIXA.js";
43
49
 
44
50
  // src/program.ts
45
51
  import { Command as Command20 } from "commander";
@@ -55,93 +61,6 @@ var version = pkg.version;
55
61
  // src/commands/get-item.ts
56
62
  import { Command } from "commander";
57
63
 
58
- // src/services/trace-writer.ts
59
- import { openSync, writeSync, closeSync } from "fs";
60
- import { platform } from "os";
61
- var REDACTED = "[REDACTED]";
62
- var SENSITIVE_HEADER = /^(authorization|x-.*token)$/i;
63
- var SENSITIVE_QUERY_PARAM = /^(token|pat)$/i;
64
- var SENSITIVE_BODY_FIELD = /^(token|accessToken|pat)$/;
65
- function redactHeaders(headers) {
66
- const out = {};
67
- for (const [key, value] of Object.entries(headers)) {
68
- out[key] = SENSITIVE_HEADER.test(key) ? REDACTED : value;
69
- }
70
- return out;
71
- }
72
- function redactUrl(url) {
73
- try {
74
- const u = new URL(url);
75
- for (const [key] of u.searchParams.entries()) {
76
- if (SENSITIVE_QUERY_PARAM.test(key)) {
77
- u.searchParams.set(key, REDACTED);
78
- }
79
- }
80
- return u.toString();
81
- } catch {
82
- return url;
83
- }
84
- }
85
- var SECRET_ASSIGNMENT = String.raw`["']?\s*[:=]\s*["']?)[^"'\s,;&}]+`;
86
- var SECRET_IN_TEXT = [
87
- [/\b(Bearer|Basic)\s+[\w.~+/=-]{8,}/gi, `$1 ${REDACTED}`],
88
- [new RegExp(String.raw`(\b(?:access|refresh|api)[_-]?(?:token|key)\b${SECRET_ASSIGNMENT}`, "gi"), `$1${REDACTED}`],
89
- [new RegExp(String.raw`(\b(?:token|pat|password|secret)\b${SECRET_ASSIGNMENT}`, "gi"), `$1${REDACTED}`],
90
- [/\beyJ[A-Za-z0-9._~+/=-]{20,}/g, REDACTED],
91
- [/\b(?=[A-Za-z0-9]*\d)(?=[A-Za-z0-9]*[A-Za-z])[A-Za-z0-9]{40,}\b/g, REDACTED]
92
- ];
93
- function redactText(text) {
94
- let out = text;
95
- for (const [pattern, replacement] of SECRET_IN_TEXT) {
96
- out = out.replace(pattern, replacement);
97
- }
98
- return out;
99
- }
100
- function redactBody(body) {
101
- if (body === null) return null;
102
- try {
103
- const parsed = JSON.parse(body);
104
- let changed = false;
105
- const redacted = { ...parsed };
106
- for (const key of Object.keys(parsed)) {
107
- if (SENSITIVE_BODY_FIELD.test(key)) {
108
- redacted[key] = REDACTED;
109
- changed = true;
110
- }
111
- }
112
- return changed ? JSON.stringify(redacted) : body;
113
- } catch {
114
- return body;
115
- }
116
- }
117
- var TraceWriter = class {
118
- fd;
119
- constructor(filepath) {
120
- const mode = platform() === "win32" ? void 0 : 384;
121
- this.fd = openSync(filepath, "a", mode);
122
- }
123
- append(entry) {
124
- const line = JSON.stringify(entry) + "\n\n";
125
- writeSync(this.fd, line);
126
- }
127
- close() {
128
- closeSync(this.fd);
129
- }
130
- };
131
- var activeWriter = null;
132
- function initTraceWriter(filepath) {
133
- try {
134
- activeWriter = new TraceWriter(filepath);
135
- } catch (err) {
136
- const msg = err instanceof Error ? err.message : String(err);
137
- process.stderr.write(`Warning: could not open trace file "${filepath}": ${msg}
138
- `);
139
- }
140
- }
141
- function getActiveTraceWriter() {
142
- return activeWriter;
143
- }
144
-
145
64
  // src/services/command-helpers.ts
146
65
  import { existsSync, readFileSync as readFileSync2 } from "fs";
147
66
  var EXIT_CREDENTIAL_STORE_UNAVAILABLE = 4;
@@ -5,7 +5,7 @@ import {
5
5
  lockPath,
6
6
  locksDir,
7
7
  refreshIfNeeded
8
- } from "./chunk-MHJ6OBX3.js";
8
+ } from "./chunk-DNOOZIXA.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.23.0-develop.895",
3
+ "version": "0.23.0-feature-058-credential-error-cause.897",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {