azdo-cli 0.19.0-feature-automata.685 → 0.19.0-feature-037-api-error-surfacing.693
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 +182 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -326,6 +326,52 @@ async function fetchRaw(url, init) {
|
|
|
326
326
|
const body = await response.text();
|
|
327
327
|
return { status: response.status, body };
|
|
328
328
|
}
|
|
329
|
+
var MAX_DETAIL_CHARS = 500;
|
|
330
|
+
var MAX_RAW_BODY_CHARS = 200;
|
|
331
|
+
var failureDetails = /* @__PURE__ */ new WeakMap();
|
|
332
|
+
function describeFailureBody(body, contentType) {
|
|
333
|
+
if (body === null) return null;
|
|
334
|
+
const trimmed = body.trim();
|
|
335
|
+
if (trimmed === "") return null;
|
|
336
|
+
if (contentType.toLowerCase().startsWith("text/html") || trimmed.startsWith("<")) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const redacted = redactBody(trimmed) ?? trimmed;
|
|
340
|
+
let parsed;
|
|
341
|
+
try {
|
|
342
|
+
parsed = JSON.parse(redacted);
|
|
343
|
+
} catch {
|
|
344
|
+
return truncateDetail(redacted.slice(0, MAX_RAW_BODY_CHARS));
|
|
345
|
+
}
|
|
346
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
347
|
+
return truncateDetail(redacted.slice(0, MAX_RAW_BODY_CHARS));
|
|
348
|
+
}
|
|
349
|
+
const record = parsed;
|
|
350
|
+
const parts = [];
|
|
351
|
+
if (typeof record.message === "string" && record.message.trim() !== "") {
|
|
352
|
+
parts.push(record.message.trim());
|
|
353
|
+
}
|
|
354
|
+
if (typeof record.typeKey === "string" && record.typeKey.trim() !== "") {
|
|
355
|
+
parts.push(`[${record.typeKey.trim()}]`);
|
|
356
|
+
} else if (typeof record.errorCode === "number" || typeof record.errorCode === "string") {
|
|
357
|
+
parts.push(`[errorCode ${record.errorCode}]`);
|
|
358
|
+
}
|
|
359
|
+
if (parts.length === 0) {
|
|
360
|
+
return truncateDetail(redacted.slice(0, MAX_RAW_BODY_CHARS));
|
|
361
|
+
}
|
|
362
|
+
return truncateDetail(parts.join(" "));
|
|
363
|
+
}
|
|
364
|
+
function truncateDetail(detail) {
|
|
365
|
+
const collapsed = detail.replace(/\s+/g, " ").trim();
|
|
366
|
+
if (collapsed === "") return null;
|
|
367
|
+
return collapsed.length > MAX_DETAIL_CHARS ? `${collapsed.slice(0, MAX_DETAIL_CHARS)}\u2026(truncated)` : collapsed;
|
|
368
|
+
}
|
|
369
|
+
function withDetail(sentinel, detail) {
|
|
370
|
+
return detail === null ? sentinel : `${sentinel}: ${detail}`;
|
|
371
|
+
}
|
|
372
|
+
function httpError(response) {
|
|
373
|
+
return new Error(withDetail(`HTTP_${response.status}`, failureDetails.get(response) ?? null));
|
|
374
|
+
}
|
|
329
375
|
async function fetchWithErrors(url, init) {
|
|
330
376
|
const writer = getActiveTraceWriter();
|
|
331
377
|
let response;
|
|
@@ -334,6 +380,7 @@ async function fetchWithErrors(url, init) {
|
|
|
334
380
|
} catch (err) {
|
|
335
381
|
throw new Error("NETWORK_ERROR", { cause: err });
|
|
336
382
|
}
|
|
383
|
+
let tracedBody = null;
|
|
337
384
|
if (writer) {
|
|
338
385
|
const reqHeaders = redactHeaders(init.headers ?? {});
|
|
339
386
|
const reqBody = typeof init.body === "string" ? redactBody(init.body) : null;
|
|
@@ -343,6 +390,7 @@ async function fetchWithErrors(url, init) {
|
|
|
343
390
|
responseBody = await clone.text();
|
|
344
391
|
} catch {
|
|
345
392
|
}
|
|
393
|
+
tracedBody = responseBody;
|
|
346
394
|
const respHeaders = {};
|
|
347
395
|
response.headers.forEach((v, k) => {
|
|
348
396
|
respHeaders[k] = v;
|
|
@@ -359,18 +407,33 @@ async function fetchWithErrors(url, init) {
|
|
|
359
407
|
};
|
|
360
408
|
writer.append(entry);
|
|
361
409
|
}
|
|
362
|
-
|
|
363
|
-
|
|
410
|
+
const contentType = response.headers?.get("content-type") ?? "";
|
|
411
|
+
let failureDetail = null;
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
let body = tracedBody;
|
|
414
|
+
if (body === null) {
|
|
415
|
+
try {
|
|
416
|
+
body = await response.clone().text();
|
|
417
|
+
} catch {
|
|
418
|
+
body = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
failureDetail = describeFailureBody(body, contentType);
|
|
422
|
+
if (failureDetail !== null) {
|
|
423
|
+
failureDetails.set(response, failureDetail);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (response.status === 401) throw new Error(withDetail("AUTH_FAILED", failureDetail));
|
|
427
|
+
if (response.status === 403) throw new Error(withDetail("PERMISSION_DENIED", failureDetail));
|
|
364
428
|
if (response.status === 404) {
|
|
365
429
|
let detail = "";
|
|
366
430
|
try {
|
|
367
|
-
const body = await response.text();
|
|
431
|
+
const body = tracedBody ?? await response.text();
|
|
368
432
|
detail = ` | url=${url} | body=${body}`;
|
|
369
433
|
} catch {
|
|
370
434
|
}
|
|
371
435
|
throw new Error(`NOT_FOUND${detail}`);
|
|
372
436
|
}
|
|
373
|
-
const contentType = response.headers?.get("content-type") ?? "";
|
|
374
437
|
if (contentType.toLowerCase().startsWith("text/html")) {
|
|
375
438
|
throw new Error("AUTH_FAILED");
|
|
376
439
|
}
|
|
@@ -464,7 +527,7 @@ async function readWriteResponse(response, errorCode) {
|
|
|
464
527
|
throw new Error(`${errorCode}: ${serverMessage}`);
|
|
465
528
|
}
|
|
466
529
|
if (!response.ok) {
|
|
467
|
-
throw
|
|
530
|
+
throw httpError(response);
|
|
468
531
|
}
|
|
469
532
|
const data = await response.json();
|
|
470
533
|
return {
|
|
@@ -487,7 +550,7 @@ async function getWorkItemFields(context, id, cred) {
|
|
|
487
550
|
}
|
|
488
551
|
}
|
|
489
552
|
if (!response.ok) {
|
|
490
|
-
throw
|
|
553
|
+
throw httpError(response);
|
|
491
554
|
}
|
|
492
555
|
const data = await response.json();
|
|
493
556
|
return data.fields;
|
|
@@ -539,7 +602,7 @@ async function fetchWorkItemResponse(context, id, cred, options = {}) {
|
|
|
539
602
|
}
|
|
540
603
|
}
|
|
541
604
|
if (!response.ok) {
|
|
542
|
-
throw
|
|
605
|
+
throw httpError(response);
|
|
543
606
|
}
|
|
544
607
|
return await response.json();
|
|
545
608
|
}
|
|
@@ -550,7 +613,7 @@ async function getOrgFieldNames(context, cred) {
|
|
|
550
613
|
url.searchParams.set("api-version", "7.1");
|
|
551
614
|
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
552
615
|
if (!response.ok) {
|
|
553
|
-
throw
|
|
616
|
+
throw httpError(response);
|
|
554
617
|
}
|
|
555
618
|
const data = await response.json();
|
|
556
619
|
return (data.value ?? []).map((f) => f.referenceName);
|
|
@@ -632,7 +695,7 @@ async function getWorkItemFieldValue(context, id, cred, fieldName) {
|
|
|
632
695
|
}
|
|
633
696
|
}
|
|
634
697
|
if (!response.ok) {
|
|
635
|
-
throw
|
|
698
|
+
throw httpError(response);
|
|
636
699
|
}
|
|
637
700
|
const data = await response.json();
|
|
638
701
|
const value = data.fields[fieldName];
|
|
@@ -650,7 +713,7 @@ async function listWorkItemComments(context, id, cred) {
|
|
|
650
713
|
{ headers: authHeaders(cred) }
|
|
651
714
|
);
|
|
652
715
|
if (!response.ok) {
|
|
653
|
-
throw
|
|
716
|
+
throw httpError(response);
|
|
654
717
|
}
|
|
655
718
|
const data = await response.json();
|
|
656
719
|
comments.push(
|
|
@@ -680,7 +743,7 @@ async function addWorkItemComment(context, id, cred, text, format = "html") {
|
|
|
680
743
|
throw new Error(`BAD_REQUEST: ${serverMessage}`);
|
|
681
744
|
}
|
|
682
745
|
if (!response.ok) {
|
|
683
|
-
throw
|
|
746
|
+
throw httpError(response);
|
|
684
747
|
}
|
|
685
748
|
const data = await response.json();
|
|
686
749
|
return {
|
|
@@ -732,7 +795,7 @@ async function applyWorkItemPatch(context, id, cred, operations) {
|
|
|
732
795
|
async function downloadAttachment(url, cred) {
|
|
733
796
|
const response = await fetchWithErrors(url, { headers: authHeaders(cred) });
|
|
734
797
|
if (!response.ok) {
|
|
735
|
-
throw
|
|
798
|
+
throw httpError(response);
|
|
736
799
|
}
|
|
737
800
|
return response.arrayBuffer();
|
|
738
801
|
}
|
|
@@ -757,7 +820,7 @@ async function createAttachment(context, fileName, content, cred) {
|
|
|
757
820
|
}
|
|
758
821
|
}
|
|
759
822
|
if (!response.ok) {
|
|
760
|
-
throw
|
|
823
|
+
throw httpError(response);
|
|
761
824
|
}
|
|
762
825
|
return await response.json();
|
|
763
826
|
}
|
|
@@ -1547,6 +1610,23 @@ function toMarkdown(content) {
|
|
|
1547
1610
|
}
|
|
1548
1611
|
|
|
1549
1612
|
// src/services/command-helpers.ts
|
|
1613
|
+
function isSentinel(message, sentinel) {
|
|
1614
|
+
return message === sentinel || message.startsWith(`${sentinel}: `);
|
|
1615
|
+
}
|
|
1616
|
+
function sentinelDetail(message, sentinel) {
|
|
1617
|
+
return message.startsWith(`${sentinel}: `) ? message.slice(sentinel.length + 2) : null;
|
|
1618
|
+
}
|
|
1619
|
+
function splitSentinel(message) {
|
|
1620
|
+
const separator = message.indexOf(": ");
|
|
1621
|
+
return separator === -1 ? { sentinel: message, detail: null } : { sentinel: message.slice(0, separator), detail: message.slice(separator + 2) };
|
|
1622
|
+
}
|
|
1623
|
+
function writeErrorDetail(message, sentinel) {
|
|
1624
|
+
const detail = sentinelDetail(message, sentinel);
|
|
1625
|
+
if (detail !== null) {
|
|
1626
|
+
process.stderr.write(` ${detail}
|
|
1627
|
+
`);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1550
1630
|
async function promptYesNo(prompt) {
|
|
1551
1631
|
if (!process.stdin.isTTY) return true;
|
|
1552
1632
|
process.stderr.write(prompt);
|
|
@@ -1608,16 +1688,18 @@ function handleCommandError(err, id, context, scope = "write", exit = true) {
|
|
|
1608
1688
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1609
1689
|
const msg = error.message;
|
|
1610
1690
|
const scopeLabel = scope === "read" ? "Work Items (read)" : "Work Items (Read & Write)";
|
|
1611
|
-
if (msg
|
|
1691
|
+
if (isSentinel(msg, "AUTH_FAILED")) {
|
|
1612
1692
|
process.stderr.write(
|
|
1613
1693
|
`Error: Authentication failed. Check that your PAT is valid and has the "${scopeLabel}" scope.
|
|
1614
1694
|
`
|
|
1615
1695
|
);
|
|
1616
|
-
|
|
1696
|
+
writeErrorDetail(msg, "AUTH_FAILED");
|
|
1697
|
+
} else if (isSentinel(msg, "PERMISSION_DENIED")) {
|
|
1617
1698
|
process.stderr.write(
|
|
1618
1699
|
`Error: Access denied. Your PAT may lack ${scope} permissions for project "${context?.project}".
|
|
1619
1700
|
`
|
|
1620
1701
|
);
|
|
1702
|
+
writeErrorDetail(msg, "PERMISSION_DENIED");
|
|
1621
1703
|
} else if (msg.startsWith("NOT_FOUND")) {
|
|
1622
1704
|
process.stderr.write(
|
|
1623
1705
|
`Error: Work item ${id} not found in ${context?.org}/${context?.project}.
|
|
@@ -3132,10 +3214,10 @@ function buildUpsertResult(action, writeResult, fields, fallbackWorkItemType) {
|
|
|
3132
3214
|
};
|
|
3133
3215
|
}
|
|
3134
3216
|
function isUpdateWriteError(err) {
|
|
3135
|
-
return err.message
|
|
3217
|
+
return isSentinel(err.message, "AUTH_FAILED") || isSentinel(err.message, "PERMISSION_DENIED") || err.message.startsWith("NOT_FOUND") || err.message === "NETWORK_ERROR" || err.message.startsWith("BAD_REQUEST:") || err.message.startsWith("UPDATE_REJECTED:");
|
|
3136
3218
|
}
|
|
3137
3219
|
function isCreateWriteError(err) {
|
|
3138
|
-
return err.message
|
|
3220
|
+
return isSentinel(err.message, "AUTH_FAILED") || isSentinel(err.message, "PERMISSION_DENIED") || err.message === "NETWORK_ERROR" || err.message.startsWith("BAD_REQUEST:") || err.message.startsWith("HTTP_");
|
|
3139
3221
|
}
|
|
3140
3222
|
function handleUpsertError(err, id, context) {
|
|
3141
3223
|
if (!(err instanceof Error)) {
|
|
@@ -3465,7 +3547,7 @@ function isThreadResolved(status2) {
|
|
|
3465
3547
|
}
|
|
3466
3548
|
async function readJsonResponse(response) {
|
|
3467
3549
|
if (!response.ok) {
|
|
3468
|
-
throw
|
|
3550
|
+
throw httpError(response);
|
|
3469
3551
|
}
|
|
3470
3552
|
return response.json();
|
|
3471
3553
|
}
|
|
@@ -3552,19 +3634,32 @@ async function getPullRequestBuilds(context, cred, prId) {
|
|
|
3552
3634
|
isBlocking: null
|
|
3553
3635
|
}));
|
|
3554
3636
|
}
|
|
3637
|
+
var MAX_PR_DESCRIPTION_CHARS = 4e3;
|
|
3638
|
+
var DESCRIPTION_SEPARATOR = "\n\n";
|
|
3555
3639
|
function composeDescription(description, template) {
|
|
3556
|
-
if (description
|
|
3557
|
-
return
|
|
3558
|
-
|
|
3559
|
-
${template.content}`;
|
|
3560
|
-
}
|
|
3561
|
-
if (description !== void 0) {
|
|
3562
|
-
return description;
|
|
3563
|
-
}
|
|
3564
|
-
if (template !== null) {
|
|
3565
|
-
return template.content;
|
|
3640
|
+
if (description === void 0 && template === null) {
|
|
3641
|
+
return null;
|
|
3566
3642
|
}
|
|
3567
|
-
|
|
3643
|
+
const provided = description ?? "";
|
|
3644
|
+
const templateContent = template?.content ?? "";
|
|
3645
|
+
const separator = description !== void 0 && template !== null ? DESCRIPTION_SEPARATOR : "";
|
|
3646
|
+
const text = `${provided}${separator}${templateContent}`;
|
|
3647
|
+
return {
|
|
3648
|
+
text,
|
|
3649
|
+
providedChars: provided.length,
|
|
3650
|
+
separatorChars: separator.length,
|
|
3651
|
+
templateChars: templateContent.length,
|
|
3652
|
+
templatePath: template?.path ?? null,
|
|
3653
|
+
totalChars: text.length
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
function formatDescriptionOverflow(composed) {
|
|
3657
|
+
const overflow = composed.totalChars - MAX_PR_DESCRIPTION_CHARS;
|
|
3658
|
+
const breakdown = composed.templateChars > 0 && composed.providedChars > 0 ? ` (${composed.providedChars} provided + ${composed.separatorChars} separator + ${composed.templateChars} from the repository pull request template ${composed.templatePath})` : composed.templateChars > 0 ? ` (all of it from the repository pull request template ${composed.templatePath})` : "";
|
|
3659
|
+
return `description is ${composed.totalChars} characters${breakdown}, exceeding the Azure DevOps limit of ${MAX_PR_DESCRIPTION_CHARS} characters. Shorten the description by at least ${overflow} characters.`;
|
|
3660
|
+
}
|
|
3661
|
+
function describeDescriptionBudget(composed) {
|
|
3662
|
+
return `description: ${composed.providedChars} provided + ${composed.separatorChars} separator + ${composed.templateChars} template = ${composed.totalChars} characters (client limit ${MAX_PR_DESCRIPTION_CHARS})`;
|
|
3568
3663
|
}
|
|
3569
3664
|
async function openPullRequest(context, repo, cred, sourceBranch, title, description) {
|
|
3570
3665
|
const existing = await listPullRequests(context, repo, cred, sourceBranch, {
|
|
@@ -3585,29 +3680,40 @@ async function openPullRequest(context, repo, cred, sourceBranch, title, descrip
|
|
|
3585
3680
|
const repository = await getRepository(context, repo, cred);
|
|
3586
3681
|
const defaultBranch = repository.defaultBranch ? repository.defaultBranch.replace(/^refs\/heads\//, "") : "develop";
|
|
3587
3682
|
const template = await resolvePullRequestTemplate(context, repo, cred, defaultBranch, "develop");
|
|
3588
|
-
const
|
|
3589
|
-
if (
|
|
3683
|
+
const composed = composeDescription(description, template);
|
|
3684
|
+
if (composed === null) {
|
|
3590
3685
|
throw new Error("DESCRIPTION_REQUIRED");
|
|
3591
3686
|
}
|
|
3687
|
+
if (composed.totalChars > MAX_PR_DESCRIPTION_CHARS) {
|
|
3688
|
+
throw new Error(`DESCRIPTION_TOO_LONG: ${formatDescriptionOverflow(composed)}`);
|
|
3689
|
+
}
|
|
3592
3690
|
const payload = {
|
|
3593
3691
|
sourceRefName: `refs/heads/${sourceBranch}`,
|
|
3594
3692
|
targetRefName: "refs/heads/develop",
|
|
3595
3693
|
title,
|
|
3596
|
-
description:
|
|
3694
|
+
description: composed.text
|
|
3597
3695
|
};
|
|
3598
3696
|
const url = new URL(
|
|
3599
3697
|
`https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests`
|
|
3600
3698
|
);
|
|
3601
3699
|
url.searchParams.set("api-version", "7.1");
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3700
|
+
let data;
|
|
3701
|
+
try {
|
|
3702
|
+
const response = await fetchWithErrors(url.toString(), {
|
|
3703
|
+
method: "POST",
|
|
3704
|
+
headers: {
|
|
3705
|
+
...authHeaders(cred),
|
|
3706
|
+
"Content-Type": "application/json"
|
|
3707
|
+
},
|
|
3708
|
+
body: JSON.stringify(payload)
|
|
3709
|
+
});
|
|
3710
|
+
data = await readJsonResponse(response);
|
|
3711
|
+
} catch (err) {
|
|
3712
|
+
if (err instanceof Error && err.message.startsWith("HTTP_400")) {
|
|
3713
|
+
throw new Error(`${err.message} | ${describeDescriptionBudget(composed)}`, { cause: err });
|
|
3714
|
+
}
|
|
3715
|
+
throw err;
|
|
3716
|
+
}
|
|
3611
3717
|
return {
|
|
3612
3718
|
branch: sourceBranch,
|
|
3613
3719
|
targetBranch: "develop",
|
|
@@ -3745,7 +3851,7 @@ async function patchWorkItemRelations(context, cred, workItemId, operation) {
|
|
|
3745
3851
|
body: JSON.stringify([operation])
|
|
3746
3852
|
});
|
|
3747
3853
|
if (!response.ok) {
|
|
3748
|
-
throw
|
|
3854
|
+
throw httpError(response);
|
|
3749
3855
|
}
|
|
3750
3856
|
}
|
|
3751
3857
|
async function linkWorkItemToPullRequest(context, repo, cred, prId, workItemId) {
|
|
@@ -3794,7 +3900,7 @@ async function resolveReviewerIdentity(org, cred, input) {
|
|
|
3794
3900
|
headers: authHeaders(cred)
|
|
3795
3901
|
});
|
|
3796
3902
|
} catch (err) {
|
|
3797
|
-
if (err instanceof Error && err.message
|
|
3903
|
+
if (err instanceof Error && isSentinel(err.message, "AUTH_FAILED")) {
|
|
3798
3904
|
throw new Error("IDENTITY_SCOPE_MISSING", { cause: err });
|
|
3799
3905
|
}
|
|
3800
3906
|
throw err;
|
|
@@ -3853,7 +3959,7 @@ async function removePullRequestReviewer(context, repo, cred, prId, reviewerId)
|
|
|
3853
3959
|
headers: authHeaders(cred)
|
|
3854
3960
|
});
|
|
3855
3961
|
if (!response.ok) {
|
|
3856
|
-
throw
|
|
3962
|
+
throw httpError(response);
|
|
3857
3963
|
}
|
|
3858
3964
|
return { reviewer: existing, noop: false };
|
|
3859
3965
|
}
|
|
@@ -3892,7 +3998,7 @@ async function fetchRepositoryItemContent(context, repo, cred, path3, branch) {
|
|
|
3892
3998
|
throw err;
|
|
3893
3999
|
}
|
|
3894
4000
|
if (!response.ok) {
|
|
3895
|
-
throw
|
|
4001
|
+
throw httpError(response);
|
|
3896
4002
|
}
|
|
3897
4003
|
return response.text();
|
|
3898
4004
|
}
|
|
@@ -4027,7 +4133,7 @@ function writeError(message, exitCode = 1) {
|
|
|
4027
4133
|
}
|
|
4028
4134
|
function handlePrCommandError(err, context, mode = "read") {
|
|
4029
4135
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4030
|
-
if (error.message
|
|
4136
|
+
if (isSentinel(error.message, "AUTH_FAILED")) {
|
|
4031
4137
|
const scopeLabel = mode === "write" ? "Code (Read & Write)" : "Code (Read)";
|
|
4032
4138
|
writeError(`Authentication failed. Check that your PAT is valid and has the "${scopeLabel}" scope.`, EXIT_NOT_PERMITTED);
|
|
4033
4139
|
const credentialHint = describeResolvedCredential();
|
|
@@ -4035,6 +4141,7 @@ function handlePrCommandError(err, context, mode = "read") {
|
|
|
4035
4141
|
process.stderr.write(` ${credentialHint}
|
|
4036
4142
|
`);
|
|
4037
4143
|
}
|
|
4144
|
+
writeErrorDetail(error.message, "AUTH_FAILED");
|
|
4038
4145
|
return;
|
|
4039
4146
|
}
|
|
4040
4147
|
if (error.message === "IDENTITY_SCOPE_MISSING") {
|
|
@@ -4044,8 +4151,9 @@ function handlePrCommandError(err, context, mode = "read") {
|
|
|
4044
4151
|
);
|
|
4045
4152
|
return;
|
|
4046
4153
|
}
|
|
4047
|
-
if (error.message
|
|
4154
|
+
if (isSentinel(error.message, "PERMISSION_DENIED")) {
|
|
4048
4155
|
writeError(`Access denied. Your PAT may lack ${mode} permissions for project "${context?.project}".`, EXIT_NOT_PERMITTED);
|
|
4156
|
+
writeErrorDetail(error.message, "PERMISSION_DENIED");
|
|
4049
4157
|
return;
|
|
4050
4158
|
}
|
|
4051
4159
|
if (error.message === "NETWORK_ERROR") {
|
|
@@ -4057,7 +4165,12 @@ function handlePrCommandError(err, context, mode = "read") {
|
|
|
4057
4165
|
return;
|
|
4058
4166
|
}
|
|
4059
4167
|
if (error.message.startsWith("HTTP_")) {
|
|
4060
|
-
|
|
4168
|
+
const { sentinel, detail } = splitSentinel(error.message);
|
|
4169
|
+
writeError(`Azure DevOps request failed with ${sentinel}.`);
|
|
4170
|
+
if (detail !== null) {
|
|
4171
|
+
process.stderr.write(` ${detail}
|
|
4172
|
+
`);
|
|
4173
|
+
}
|
|
4061
4174
|
return;
|
|
4062
4175
|
}
|
|
4063
4176
|
writeError(error.message);
|
|
@@ -4289,6 +4402,10 @@ ${result.pullRequest.url ?? "\u2014"}
|
|
|
4289
4402
|
writeError("--description is required for pull request creation.");
|
|
4290
4403
|
return;
|
|
4291
4404
|
}
|
|
4405
|
+
if (err instanceof Error && err.message.startsWith("DESCRIPTION_TOO_LONG: ")) {
|
|
4406
|
+
writeError(err.message.slice("DESCRIPTION_TOO_LONG: ".length));
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4292
4409
|
handlePrCommandError(err, context, "write");
|
|
4293
4410
|
}
|
|
4294
4411
|
});
|
|
@@ -5098,7 +5215,7 @@ function withApiVersion(url) {
|
|
|
5098
5215
|
}
|
|
5099
5216
|
async function readJsonResponse2(response) {
|
|
5100
5217
|
if (!response.ok) {
|
|
5101
|
-
throw
|
|
5218
|
+
throw httpError(response);
|
|
5102
5219
|
}
|
|
5103
5220
|
return await response.json();
|
|
5104
5221
|
}
|
|
@@ -5374,7 +5491,7 @@ async function getRunLog(context, cred, buildId, logId) {
|
|
|
5374
5491
|
);
|
|
5375
5492
|
const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
|
|
5376
5493
|
if (!response.ok) {
|
|
5377
|
-
throw
|
|
5494
|
+
throw httpError(response);
|
|
5378
5495
|
}
|
|
5379
5496
|
return response.text();
|
|
5380
5497
|
}
|
|
@@ -5390,12 +5507,14 @@ function writeError2(message) {
|
|
|
5390
5507
|
}
|
|
5391
5508
|
function handlePipelineError(err, context) {
|
|
5392
5509
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
5393
|
-
if (error.message
|
|
5510
|
+
if (isSentinel(error.message, "AUTH_FAILED")) {
|
|
5394
5511
|
writeError2('Authentication failed. Check that your credential is valid and has the "Build (Read)" scope.');
|
|
5512
|
+
writeErrorDetail(error.message, "AUTH_FAILED");
|
|
5395
5513
|
return;
|
|
5396
5514
|
}
|
|
5397
|
-
if (error.message
|
|
5515
|
+
if (isSentinel(error.message, "PERMISSION_DENIED")) {
|
|
5398
5516
|
writeError2(`Access denied. Your credential may lack pipeline permissions for project "${context?.project}".`);
|
|
5517
|
+
writeErrorDetail(error.message, "PERMISSION_DENIED");
|
|
5399
5518
|
return;
|
|
5400
5519
|
}
|
|
5401
5520
|
if (error.message === "NETWORK_ERROR") {
|
|
@@ -5407,7 +5526,12 @@ function handlePipelineError(err, context) {
|
|
|
5407
5526
|
return;
|
|
5408
5527
|
}
|
|
5409
5528
|
if (error.message.startsWith("HTTP_")) {
|
|
5410
|
-
|
|
5529
|
+
const { sentinel, detail } = splitSentinel(error.message);
|
|
5530
|
+
writeError2(`Azure DevOps request failed with ${sentinel}.`);
|
|
5531
|
+
if (detail !== null) {
|
|
5532
|
+
process.stderr.write(` ${detail}
|
|
5533
|
+
`);
|
|
5534
|
+
}
|
|
5411
5535
|
return;
|
|
5412
5536
|
}
|
|
5413
5537
|
writeError2(error.message);
|
|
@@ -6299,7 +6423,7 @@ function mapRelationType(raw) {
|
|
|
6299
6423
|
};
|
|
6300
6424
|
}
|
|
6301
6425
|
async function readJsonResponse3(response) {
|
|
6302
|
-
if (!response.ok) throw
|
|
6426
|
+
if (!response.ok) throw httpError(response);
|
|
6303
6427
|
return await response.json();
|
|
6304
6428
|
}
|
|
6305
6429
|
function parseTargetId(url) {
|
|
@@ -6347,7 +6471,7 @@ async function addWorkItemRelation(context, cred, type, id1, id2) {
|
|
|
6347
6471
|
{ op: "add", path: "/relations/-", value: { rel: relType.referenceName, url: targetUrl } }
|
|
6348
6472
|
])
|
|
6349
6473
|
});
|
|
6350
|
-
if (!response.ok) throw
|
|
6474
|
+
if (!response.ok) throw httpError(response);
|
|
6351
6475
|
return { status: "added", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
6352
6476
|
}
|
|
6353
6477
|
async function removeWorkItemRelation(context, cred, type, id1, id2) {
|
|
@@ -6367,7 +6491,7 @@ async function removeWorkItemRelation(context, cred, type, id1, id2) {
|
|
|
6367
6491
|
headers: { ...authHeaders(cred), "Content-Type": "application/json-patch+json" },
|
|
6368
6492
|
body: JSON.stringify([{ op: "remove", path: `/relations/${index}` }])
|
|
6369
6493
|
});
|
|
6370
|
-
if (!response.ok) throw
|
|
6494
|
+
if (!response.ok) throw httpError(response);
|
|
6371
6495
|
return { status: "removed", type: relType.name, referenceName: relType.referenceName, id1, id2 };
|
|
6372
6496
|
}
|
|
6373
6497
|
async function listWorkItemRelations(context, cred, id) {
|
|
@@ -6455,11 +6579,12 @@ function handleRelationError(err, id1) {
|
|
|
6455
6579
|
const target = id1 !== void 0 ? id1 : "unknown";
|
|
6456
6580
|
process.stderr.write(`Error: work item #${target} not found.
|
|
6457
6581
|
`);
|
|
6458
|
-
} else if (msg
|
|
6582
|
+
} else if (isSentinel(msg, "AUTH_FAILED")) {
|
|
6459
6583
|
process.stderr.write(
|
|
6460
6584
|
`Error: authentication failed. Check your PAT has Work Items \u2192 Read & Write scope.
|
|
6461
6585
|
`
|
|
6462
6586
|
);
|
|
6587
|
+
writeErrorDetail(msg, "AUTH_FAILED");
|
|
6463
6588
|
} else {
|
|
6464
6589
|
process.stderr.write(`Error: ${msg}
|
|
6465
6590
|
`);
|