azdo-cli 0.10.0-develop.394 → 0.10.0-develop.423

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.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +932 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -61,6 +61,13 @@ azdo pr comments --code-related-only # only file/line-anchored threads
61
61
  azdo pr status # PR checks (status + branch policies) + code-comment counts
62
62
  azdo pr comment-resolve 17 --pr-number 64 # idempotent: exit 0 even when already resolved
63
63
  azdo pr comment-reopen 17 --pr-number 64
64
+
65
+ # Pipelines — list, inspect runs, wait (exit code = result), start
66
+ azdo pipeline list --filter ci
67
+ azdo pipeline get-runs 12 --branch develop --limit 1
68
+ azdo pipeline wait 3456 # blocks; exit 0 success / non-zero failure / 124 timeout
69
+ azdo pipeline get-run-detail 3456 # errors, failing tests, per-stage status
70
+ azdo pipeline start 12 --branch develop --parameter env=staging
64
71
  ```
65
72
 
66
73
  ## Documentation
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-C7RAZJHV.js";
32
32
 
33
33
  // src/index.ts
34
- import { Command as Command15 } from "commander";
34
+ import { Command as Command16 } from "commander";
35
35
 
36
36
  // src/version.ts
37
37
  import { readFileSync } from "fs";
@@ -479,13 +479,13 @@ async function classifyDeviceTokenResponse(response) {
479
479
  async function pollForDeviceToken(deviceCode, oauthConfig, initialIntervalSec, expiresAtMs, deps) {
480
480
  const fetchFn = deps.fetch ?? fetch;
481
481
  const now = deps.now ?? (() => Date.now());
482
- const sleep = deps.sleep ?? defaultSleep;
482
+ const sleep2 = deps.sleep ?? defaultSleep;
483
483
  let intervalSec = Math.max(MIN_INTERVAL_SEC, initialIntervalSec);
484
484
  for (; ; ) {
485
485
  if (now() >= expiresAtMs) {
486
486
  throw new DeviceCodeFlowError("expired_token", "device-code flow expired before authorisation completed");
487
487
  }
488
- await sleep(intervalSec * 1e3);
488
+ await sleep2(intervalSec * 1e3);
489
489
  const body = new URLSearchParams({
490
490
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
491
491
  client_id: oauthConfig.clientId,
@@ -3368,10 +3368,922 @@ function createPrCommand() {
3368
3368
  return command;
3369
3369
  }
3370
3370
 
3371
- // src/commands/comments.ts
3371
+ // src/commands/pipeline.ts
3372
3372
  import { Command as Command13 } from "commander";
3373
+
3374
+ // src/services/pipeline-client.ts
3375
+ var API_VERSION = "7.1";
3376
+ function orgProjectBase(context) {
3377
+ return `https://dev.azure.com/${encodeURIComponent(context.org)}/${encodeURIComponent(context.project)}`;
3378
+ }
3379
+ function withApiVersion(url) {
3380
+ url.searchParams.set("api-version", API_VERSION);
3381
+ return url;
3382
+ }
3383
+ async function readJsonResponse2(response) {
3384
+ if (!response.ok) {
3385
+ throw new Error(`HTTP_${response.status}`);
3386
+ }
3387
+ return await response.json();
3388
+ }
3389
+ function mapPipeline(pipeline) {
3390
+ return {
3391
+ id: pipeline.id,
3392
+ name: pipeline.name,
3393
+ folder: pipeline.folder?.trim() ? pipeline.folder : null
3394
+ };
3395
+ }
3396
+ function mapRunState(state) {
3397
+ if (state === "inProgress" || state === "completed") {
3398
+ return state;
3399
+ }
3400
+ return "unknown";
3401
+ }
3402
+ function mapRunResult(result) {
3403
+ if (result === "succeeded" || result === "failed" || result === "canceled") {
3404
+ return result;
3405
+ }
3406
+ if (result === "partiallySucceeded") {
3407
+ return "failed";
3408
+ }
3409
+ return null;
3410
+ }
3411
+ function mapBuildState(status2) {
3412
+ if (status2 === "completed") return "completed";
3413
+ if (status2 === void 0 || status2 === "none") return "unknown";
3414
+ return "inProgress";
3415
+ }
3416
+ function mapBuildSummary(build) {
3417
+ return {
3418
+ id: build.id,
3419
+ name: build.buildNumber ?? null,
3420
+ state: mapBuildState(build.status),
3421
+ result: mapRunResult(build.result),
3422
+ createdDate: build.queueTime ?? build.startTime ?? null,
3423
+ finishedDate: build.finishTime ?? null,
3424
+ sourceBranch: build.sourceBranch ?? null,
3425
+ sourceCommit: build.sourceVersion ?? null
3426
+ };
3427
+ }
3428
+ function normalizeRef(branch) {
3429
+ return branch.startsWith("refs/") ? branch : `refs/heads/${branch}`;
3430
+ }
3431
+ async function getPipelineDefinitions(context, cred) {
3432
+ const url = withApiVersion(new URL(`${orgProjectBase(context)}/_apis/pipelines`));
3433
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3434
+ const data = await readJsonResponse2(response);
3435
+ return data.value.map(mapPipeline);
3436
+ }
3437
+ var COMMIT_LOOKBACK = 200;
3438
+ async function getPipelineRuns(context, cred, query) {
3439
+ const url = withApiVersion(new URL(`${orgProjectBase(context)}/_apis/build/builds`));
3440
+ if (query.definitionId !== void 0) {
3441
+ url.searchParams.set("definitions", String(query.definitionId));
3442
+ }
3443
+ if (query.prNumber !== void 0) {
3444
+ url.searchParams.set("branchName", `refs/pull/${query.prNumber}/merge`);
3445
+ } else if (query.branch) {
3446
+ url.searchParams.set("branchName", normalizeRef(query.branch));
3447
+ }
3448
+ url.searchParams.set("queryOrder", "queueTimeDescending");
3449
+ url.searchParams.set("$top", String(query.commit ? COMMIT_LOOKBACK : query.top));
3450
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3451
+ const data = await readJsonResponse2(response);
3452
+ let runs = data.value.map(mapBuildSummary);
3453
+ if (query.commit) {
3454
+ const needle = query.commit.toLowerCase();
3455
+ runs = runs.filter((run) => run.sourceCommit?.toLowerCase().startsWith(needle));
3456
+ }
3457
+ return runs.slice(0, query.top);
3458
+ }
3459
+ async function runPipeline(context, cred, pipelineId, opts) {
3460
+ const url = withApiVersion(
3461
+ new URL(`${orgProjectBase(context)}/_apis/pipelines/${pipelineId}/runs`)
3462
+ );
3463
+ const body = {};
3464
+ if (opts.branch) {
3465
+ const refName = opts.branch.startsWith("refs/") ? opts.branch : `refs/heads/${opts.branch}`;
3466
+ body.resources = { repositories: { self: { refName } } };
3467
+ }
3468
+ if (opts.parameters && Object.keys(opts.parameters).length > 0) {
3469
+ body.templateParameters = opts.parameters;
3470
+ }
3471
+ const response = await fetchWithErrors(url.toString(), {
3472
+ method: "POST",
3473
+ headers: { ...authHeaders(cred), "Content-Type": "application/json" },
3474
+ body: JSON.stringify(body)
3475
+ });
3476
+ const data = await readJsonResponse2(response);
3477
+ return {
3478
+ id: data.id,
3479
+ state: mapRunState(data.state),
3480
+ webUrl: data._links?.web?.href ?? null
3481
+ };
3482
+ }
3483
+ function buildUrl(context, buildId) {
3484
+ return withApiVersion(new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}`));
3485
+ }
3486
+ async function getBuild(context, cred, buildId) {
3487
+ const response = await fetchWithErrors(buildUrl(context, buildId).toString(), {
3488
+ headers: authHeaders(cred)
3489
+ });
3490
+ return readJsonResponse2(response);
3491
+ }
3492
+ async function getBuildStatus(context, cred, buildId) {
3493
+ const build = await getBuild(context, cred, buildId);
3494
+ return { state: mapBuildState(build.status), result: mapRunResult(build.result) };
3495
+ }
3496
+ async function getBuildTimeline(context, cred, buildId) {
3497
+ const url = withApiVersion(
3498
+ new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}/timeline`)
3499
+ );
3500
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3501
+ const data = await readJsonResponse2(response);
3502
+ const records = data.records ?? [];
3503
+ const errors = [];
3504
+ const stages = [];
3505
+ const jobs = [];
3506
+ const logSteps = /* @__PURE__ */ new Map();
3507
+ for (const record of records) {
3508
+ for (const issue of record.issues ?? []) {
3509
+ if (issue.type === "error" && issue.message) {
3510
+ errors.push({ message: issue.message, source: record.name ?? null });
3511
+ }
3512
+ }
3513
+ if (record.name && record.log?.id !== void 0) {
3514
+ logSteps.set(record.log.id, record.name);
3515
+ }
3516
+ if (!record.name) continue;
3517
+ const status2 = {
3518
+ name: record.name,
3519
+ state: record.state ?? "unknown",
3520
+ result: record.result ?? null
3521
+ };
3522
+ if (record.type === "Stage") {
3523
+ stages.push(status2);
3524
+ } else if (record.type === "Job") {
3525
+ jobs.push({ startTime: record.startTime, status: status2 });
3526
+ }
3527
+ }
3528
+ jobs.sort((a, b) => {
3529
+ if (a.startTime === b.startTime) return 0;
3530
+ if (a.startTime === void 0) return 1;
3531
+ if (b.startTime === void 0) return -1;
3532
+ return a.startTime < b.startTime ? -1 : 1;
3533
+ });
3534
+ return { errors, stages, jobs: jobs.map((j) => j.status), logSteps };
3535
+ }
3536
+ async function listTestRuns(context, cred, buildId) {
3537
+ const url = withApiVersion(new URL(`${orgProjectBase(context)}/_apis/test/runs`));
3538
+ url.searchParams.set("buildUri", `vstfs:///Build/Build/${buildId}`);
3539
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3540
+ const data = await readJsonResponse2(response);
3541
+ return data.value;
3542
+ }
3543
+ async function getTestSummary(context, cred, buildId) {
3544
+ const testRuns = await listTestRuns(context, cred, buildId);
3545
+ let total = 0;
3546
+ let failed = 0;
3547
+ for (const run of testRuns) {
3548
+ const runTotal = run.totalTests ?? 0;
3549
+ total += runTotal;
3550
+ const passedOrSkipped = (run.passedTests ?? 0) + (run.notApplicableTests ?? 0) + (run.incompleteTests ?? 0);
3551
+ failed += Math.max(0, runTotal - passedOrSkipped);
3552
+ }
3553
+ if (total === 0) {
3554
+ return { present: false, total: 0, failed: 0, failedTests: [] };
3555
+ }
3556
+ return { present: true, total, failed, failedTests: [] };
3557
+ }
3558
+ var MAX_FAILED_TESTS = 50;
3559
+ async function getFailedTests(context, cred, buildId) {
3560
+ const testRuns = await listTestRuns(context, cred, buildId);
3561
+ const failed = [];
3562
+ for (const testRun of testRuns) {
3563
+ if (failed.length >= MAX_FAILED_TESTS) break;
3564
+ const resultsUrl = withApiVersion(
3565
+ new URL(`${orgProjectBase(context)}/_apis/test/runs/${testRun.id}/results`)
3566
+ );
3567
+ resultsUrl.searchParams.set("outcomes", "Failed");
3568
+ resultsUrl.searchParams.set("$top", String(MAX_FAILED_TESTS - failed.length));
3569
+ const resultsResponse = await fetchWithErrors(resultsUrl.toString(), {
3570
+ headers: authHeaders(cred)
3571
+ });
3572
+ const resultsData = await readJsonResponse2(resultsResponse);
3573
+ for (const result of resultsData.value) {
3574
+ failed.push({
3575
+ name: result.testCaseTitle ?? result.automatedTestName ?? "(unnamed test)",
3576
+ errorMessage: result.errorMessage ?? null
3577
+ });
3578
+ }
3579
+ }
3580
+ return failed;
3581
+ }
3582
+ function secondsBetween(start, finish) {
3583
+ if (!start || !finish) return null;
3584
+ const ms = Date.parse(finish) - Date.parse(start);
3585
+ return Number.isFinite(ms) ? Math.round(ms / 1e3) : null;
3586
+ }
3587
+ async function getRunDetail(context, cred, buildId) {
3588
+ const build = await getBuild(context, cred, buildId);
3589
+ let errors = [];
3590
+ let stages = [];
3591
+ let jobs = [];
3592
+ let errorsAvailable = true;
3593
+ try {
3594
+ const timeline = await getBuildTimeline(context, cred, buildId);
3595
+ errors = timeline.errors;
3596
+ stages = timeline.stages;
3597
+ jobs = timeline.jobs;
3598
+ } catch {
3599
+ errorsAvailable = false;
3600
+ }
3601
+ let tests = { present: false, total: 0, failed: 0, failedTests: [] };
3602
+ let testsAvailable = true;
3603
+ try {
3604
+ tests = await getTestSummary(context, cred, buildId);
3605
+ if (tests.failed > 0) {
3606
+ try {
3607
+ tests = { ...tests, failedTests: await getFailedTests(context, cred, buildId) };
3608
+ } catch {
3609
+ }
3610
+ }
3611
+ } catch {
3612
+ testsAvailable = false;
3613
+ }
3614
+ return {
3615
+ id: build.id,
3616
+ name: build.buildNumber ?? null,
3617
+ state: mapBuildState(build.status),
3618
+ result: mapRunResult(build.result),
3619
+ // createdDate is the queue time, matching the run-list mapping.
3620
+ createdDate: build.queueTime ?? build.startTime ?? null,
3621
+ startedDate: build.startTime ?? null,
3622
+ finishedDate: build.finishTime ?? null,
3623
+ durationSeconds: secondsBetween(build.startTime, build.finishTime),
3624
+ reason: build.reason ?? null,
3625
+ requestedFor: build.requestedFor?.displayName ?? null,
3626
+ sourceBranch: build.sourceBranch ?? null,
3627
+ sourceCommit: build.sourceVersion ?? null,
3628
+ webUrl: build._links?.web?.href ?? null,
3629
+ errors,
3630
+ errorsAvailable,
3631
+ stages,
3632
+ jobs,
3633
+ tests,
3634
+ testsAvailable
3635
+ };
3636
+ }
3637
+ async function getRunLogs(context, cred, buildId) {
3638
+ const url = withApiVersion(
3639
+ new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}/logs`)
3640
+ );
3641
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3642
+ const data = await readJsonResponse2(response);
3643
+ let logSteps = /* @__PURE__ */ new Map();
3644
+ try {
3645
+ logSteps = (await getBuildTimeline(context, cred, buildId)).logSteps;
3646
+ } catch {
3647
+ }
3648
+ return data.value.map((log) => ({
3649
+ id: log.id,
3650
+ createdOn: log.createdOn ?? null,
3651
+ lineCount: log.lineCount ?? null,
3652
+ step: logSteps.get(log.id) ?? null
3653
+ }));
3654
+ }
3655
+ async function getRunLog(context, cred, buildId, logId) {
3656
+ const url = withApiVersion(
3657
+ new URL(`${orgProjectBase(context)}/_apis/build/builds/${buildId}/logs/${logId}`)
3658
+ );
3659
+ const response = await fetchWithErrors(url.toString(), { headers: authHeaders(cred) });
3660
+ if (!response.ok) {
3661
+ throw new Error(`HTTP_${response.status}`);
3662
+ }
3663
+ return response.text();
3664
+ }
3665
+
3666
+ // src/commands/pipeline.ts
3667
+ var EXIT_FAILED = 1;
3668
+ var EXIT_CANCELED = 2;
3669
+ var EXIT_TIMEOUT = 124;
3373
3670
  function writeError2(message) {
3374
3671
  process.stderr.write(`Error: ${message}
3672
+ `);
3673
+ process.exitCode = 1;
3674
+ }
3675
+ function handlePipelineError(err, context) {
3676
+ const error = err instanceof Error ? err : new Error(String(err));
3677
+ if (error.message === "AUTH_FAILED") {
3678
+ writeError2('Authentication failed. Check that your credential is valid and has the "Build (Read)" scope.');
3679
+ return;
3680
+ }
3681
+ if (error.message === "PERMISSION_DENIED") {
3682
+ writeError2(`Access denied. Your credential may lack pipeline permissions for project "${context?.project}".`);
3683
+ return;
3684
+ }
3685
+ if (error.message === "NETWORK_ERROR") {
3686
+ writeError2("Could not connect to Azure DevOps. Check your network connection.");
3687
+ return;
3688
+ }
3689
+ if (error.message.startsWith("NOT_FOUND")) {
3690
+ writeError2(`Resource not found in ${context?.org}/${context?.project}.`);
3691
+ return;
3692
+ }
3693
+ if (error.message.startsWith("HTTP_")) {
3694
+ writeError2(`Azure DevOps request failed with ${error.message}.`);
3695
+ return;
3696
+ }
3697
+ writeError2(error.message);
3698
+ }
3699
+ function parsePositiveId(raw) {
3700
+ if (!/^\d+$/.test(raw)) return null;
3701
+ const n = Number.parseInt(raw, 10);
3702
+ return Number.isFinite(n) && n > 0 ? n : null;
3703
+ }
3704
+ function parseOptionalCount(value, flag) {
3705
+ if (value === void 0) return void 0;
3706
+ const parsed = parsePositiveId(value);
3707
+ if (parsed === null) {
3708
+ writeError2(`Invalid ${flag} "${value}"; expected a positive integer.`);
3709
+ return null;
3710
+ }
3711
+ return parsed;
3712
+ }
3713
+ function formatBranchName2(refName) {
3714
+ if (!refName) return "\u2014";
3715
+ return refName.startsWith("refs/heads/") ? refName.slice("refs/heads/".length) : refName;
3716
+ }
3717
+ async function resolvePipelineContext(options) {
3718
+ const context = resolveContext(options);
3719
+ const cred = await requireAuthCredential(context.org);
3720
+ return { context, cred };
3721
+ }
3722
+ function sleep(ms) {
3723
+ return new Promise((resolve2) => {
3724
+ setTimeout(resolve2, ms);
3725
+ });
3726
+ }
3727
+ function formatTable(rows, rightAlign = /* @__PURE__ */ new Set()) {
3728
+ const widths = [];
3729
+ for (const row of rows) {
3730
+ row.forEach((cell, i) => {
3731
+ widths[i] = Math.max(widths[i] ?? 0, cell.length);
3732
+ });
3733
+ }
3734
+ return rows.map(
3735
+ (row) => row.map((cell, i) => rightAlign.has(i) ? cell.padStart(widths[i]) : cell.padEnd(widths[i])).join(" ").trimEnd()
3736
+ ).join("\n");
3737
+ }
3738
+ function createPipelineListCommand() {
3739
+ const command = new Command13("list");
3740
+ command.description("List Azure DevOps pipeline definitions").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--filter <name>", "filter definitions by name (case-insensitive substring)").option("--json", "output JSON").action(async (options) => {
3741
+ validateOrgProjectPair(options);
3742
+ let context;
3743
+ try {
3744
+ const resolved = await resolvePipelineContext(options);
3745
+ context = resolved.context;
3746
+ let definitions = await getPipelineDefinitions(resolved.context, resolved.cred);
3747
+ if (options.filter) {
3748
+ const needle = options.filter.toLowerCase();
3749
+ definitions = definitions.filter((d) => d.name.toLowerCase().includes(needle));
3750
+ }
3751
+ if (options.json) {
3752
+ process.stdout.write(`${JSON.stringify(definitions, null, 2)}
3753
+ `);
3754
+ return;
3755
+ }
3756
+ if (definitions.length === 0) {
3757
+ process.stdout.write("No pipelines found.\n");
3758
+ return;
3759
+ }
3760
+ const hasFolder = definitions.some((d) => d.folder);
3761
+ const rows = definitions.map(
3762
+ (d) => hasFolder ? [String(d.id), d.name, d.folder ?? ""] : [String(d.id), d.name]
3763
+ );
3764
+ process.stdout.write(`${formatTable(rows, /* @__PURE__ */ new Set([0]))}
3765
+ `);
3766
+ } catch (err) {
3767
+ handlePipelineError(err, context);
3768
+ }
3769
+ });
3770
+ return command;
3771
+ }
3772
+ function runRow(run) {
3773
+ const status2 = run.result ? `${run.state}/${run.result}` : run.state;
3774
+ return [
3775
+ String(run.id),
3776
+ `[${status2}]`,
3777
+ run.createdDate ?? "\u2014",
3778
+ formatBranchName2(run.sourceBranch),
3779
+ run.sourceCommit ? run.sourceCommit.slice(0, 8) : "\u2014"
3780
+ ];
3781
+ }
3782
+ var COMMIT_SHA_PATTERN = /^[0-9a-f]{6,40}$/i;
3783
+ function parseGetRunsInputs(defIdRaw, options) {
3784
+ let defId;
3785
+ if (defIdRaw !== void 0) {
3786
+ const parsed = parsePositiveId(defIdRaw);
3787
+ if (parsed === null) {
3788
+ writeError2(`Invalid definition id "${defIdRaw}"; expected a positive integer.`);
3789
+ return null;
3790
+ }
3791
+ defId = parsed;
3792
+ } else if (options.commit === void 0 && options.pr === void 0) {
3793
+ writeError2("Definition id is required unless --commit or --pr is given.");
3794
+ return null;
3795
+ }
3796
+ const limit = parseOptionalCount(options.limit, "--limit");
3797
+ if (limit === null) return null;
3798
+ const prNumber = parseOptionalCount(options.pr, "--pr");
3799
+ if (prNumber === null) return null;
3800
+ if (options.commit !== void 0 && !COMMIT_SHA_PATTERN.test(options.commit)) {
3801
+ writeError2(`Invalid --commit "${options.commit}"; expected 6-40 hex characters.`);
3802
+ return null;
3803
+ }
3804
+ if (options.branch !== void 0 && prNumber !== void 0) {
3805
+ writeError2("Use either --branch or --pr, not both.");
3806
+ return null;
3807
+ }
3808
+ return { defId, limit: limit ?? 10, prNumber };
3809
+ }
3810
+ function createPipelineGetRunsCommand() {
3811
+ const command = new Command13("get-runs");
3812
+ command.description("List recent runs for a pipeline definition (newest first)").argument("[def_id]", "pipeline definition id (optional with --commit or --pr)").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--limit <n>", "maximum number of runs to show (default 10)").option("--branch <branch>", "only show runs for this source branch").option("--commit <sha>", "only show runs that built this commit (full or abbreviated SHA)").option("--pr <number>", "only show runs for this pull request").option("--json", "output JSON").action(async (defIdRaw, options) => {
3813
+ validateOrgProjectPair(options);
3814
+ const inputs = parseGetRunsInputs(defIdRaw, options);
3815
+ if (inputs === null) {
3816
+ return;
3817
+ }
3818
+ let context;
3819
+ try {
3820
+ const resolved = await resolvePipelineContext(options);
3821
+ context = resolved.context;
3822
+ const runs = await getPipelineRuns(resolved.context, resolved.cred, {
3823
+ definitionId: inputs.defId,
3824
+ branch: options.branch,
3825
+ prNumber: inputs.prNumber,
3826
+ commit: options.commit,
3827
+ top: inputs.limit
3828
+ });
3829
+ if (options.json) {
3830
+ process.stdout.write(`${JSON.stringify(runs, null, 2)}
3831
+ `);
3832
+ return;
3833
+ }
3834
+ if (runs.length === 0) {
3835
+ process.stdout.write(
3836
+ inputs.defId === void 0 ? "No runs found matching the filters.\n" : `No runs found for pipeline ${inputs.defId}.
3837
+ `
3838
+ );
3839
+ return;
3840
+ }
3841
+ process.stdout.write(`${formatTable(runs.map(runRow), /* @__PURE__ */ new Set([0]))}
3842
+ `);
3843
+ } catch (err) {
3844
+ handlePipelineError(err, context);
3845
+ }
3846
+ });
3847
+ return command;
3848
+ }
3849
+ function applyWaitExitCode(result) {
3850
+ if (result.timedOut) {
3851
+ process.exitCode = EXIT_TIMEOUT;
3852
+ return;
3853
+ }
3854
+ switch (result.result) {
3855
+ case "succeeded":
3856
+ return;
3857
+ case "canceled":
3858
+ process.exitCode = EXIT_CANCELED;
3859
+ return;
3860
+ case "failed":
3861
+ default:
3862
+ process.exitCode = EXIT_FAILED;
3863
+ }
3864
+ }
3865
+ function createPipelineWaitCommand() {
3866
+ const command = new Command13("wait");
3867
+ command.description("Wait for a pipeline run to finish; exit code reflects the result (0 success, non-zero otherwise)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--timeout <seconds>", "maximum seconds to wait (default 1800)").option("--poll-interval <seconds>", "seconds between status checks (default 5)").option("--json", "output JSON").action(async (runIdRaw, options) => {
3868
+ validateOrgProjectPair(options);
3869
+ const runId = parsePositiveId(runIdRaw);
3870
+ if (runId === null) {
3871
+ writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
3872
+ return;
3873
+ }
3874
+ const timeoutSec = options.timeout === void 0 ? 1800 : Number(options.timeout);
3875
+ const pollSec = options.pollInterval === void 0 ? 5 : Number(options.pollInterval);
3876
+ if (!Number.isFinite(timeoutSec) || timeoutSec < 0) {
3877
+ writeError2(`Invalid --timeout "${options.timeout}"; expected a non-negative number.`);
3878
+ return;
3879
+ }
3880
+ if (!Number.isFinite(pollSec) || pollSec <= 0) {
3881
+ writeError2(`Invalid --poll-interval "${options.pollInterval}"; expected a positive number.`);
3882
+ return;
3883
+ }
3884
+ let context;
3885
+ try {
3886
+ const resolved = await resolvePipelineContext(options);
3887
+ context = resolved.context;
3888
+ const deadline = Date.now() + timeoutSec * 1e3;
3889
+ let waitResult = null;
3890
+ for (; ; ) {
3891
+ const status2 = await getBuildStatus(resolved.context, resolved.cred, runId);
3892
+ if (status2.state === "completed") {
3893
+ waitResult = { id: runId, state: status2.state, result: status2.result, timedOut: false };
3894
+ break;
3895
+ }
3896
+ if (Date.now() >= deadline) {
3897
+ waitResult = { id: runId, state: status2.state, result: status2.result, timedOut: true };
3898
+ break;
3899
+ }
3900
+ await sleep(pollSec * 1e3);
3901
+ }
3902
+ applyWaitExitCode(waitResult);
3903
+ if (options.json) {
3904
+ process.stdout.write(`${JSON.stringify(waitResult, null, 2)}
3905
+ `);
3906
+ return;
3907
+ }
3908
+ if (waitResult.timedOut) {
3909
+ process.stdout.write(`Run ${runId} did not finish within ${timeoutSec}s (still ${waitResult.state}).
3910
+ `);
3911
+ } else {
3912
+ process.stdout.write(`Run ${runId} finished: ${waitResult.result ?? waitResult.state}.
3913
+ `);
3914
+ }
3915
+ } catch (err) {
3916
+ handlePipelineError(err, context);
3917
+ }
3918
+ });
3919
+ return command;
3920
+ }
3921
+ function timelineRows(items, available) {
3922
+ if (!available) {
3923
+ return [" unavailable"];
3924
+ }
3925
+ if (items.length === 0) {
3926
+ return [" (none)"];
3927
+ }
3928
+ return items.map((item) => ` - ${item.name} [${item.result ?? item.state}]`);
3929
+ }
3930
+ function formatDuration(totalSeconds) {
3931
+ const h = Math.floor(totalSeconds / 3600);
3932
+ const m = Math.floor(totalSeconds % 3600 / 60);
3933
+ const s = totalSeconds % 60;
3934
+ if (h > 0) return `${h}h${m}m${s}s`;
3935
+ if (m > 0) return `${m}m${s}s`;
3936
+ return `${s}s`;
3937
+ }
3938
+ function errorRows(detail) {
3939
+ if (!detail.errorsAvailable) {
3940
+ return [" unavailable"];
3941
+ }
3942
+ if (detail.errors.length === 0) {
3943
+ return [" (none)"];
3944
+ }
3945
+ return detail.errors.map((error) => {
3946
+ const source = error.source ? `[${error.source}] ` : "";
3947
+ return ` - ${source}${error.message}`;
3948
+ });
3949
+ }
3950
+ function failedTestRow(test) {
3951
+ if (!test.errorMessage) {
3952
+ return ` - ${test.name}`;
3953
+ }
3954
+ const firstLine = test.errorMessage.split("\n", 1)[0].trim();
3955
+ return ` - ${test.name}: ${firstLine}`;
3956
+ }
3957
+ function testRows(detail) {
3958
+ if (!detail.testsAvailable) {
3959
+ return [" unavailable"];
3960
+ }
3961
+ if (detail.tests.present) {
3962
+ return [
3963
+ ` ${detail.tests.failed} failing of ${detail.tests.total}`,
3964
+ ...detail.tests.failedTests.map(failedTestRow)
3965
+ ];
3966
+ }
3967
+ return [" no tests present"];
3968
+ }
3969
+ function formatRunDetail(detail) {
3970
+ const status2 = detail.result ? `${detail.state}/${detail.result}` : detail.state;
3971
+ const name = detail.name ? ` ${detail.name}` : "";
3972
+ const duration = detail.durationSeconds == null ? "\u2014" : formatDuration(detail.durationSeconds);
3973
+ return [
3974
+ `Run #${detail.id} [${status2}]${name}`,
3975
+ `Queued: ${detail.createdDate ?? "\u2014"} Started: ${detail.startedDate ?? "\u2014"} Finished: ${detail.finishedDate ?? "\u2014"}`,
3976
+ `Duration: ${duration} Reason: ${detail.reason ?? "\u2014"} Requested for: ${detail.requestedFor ?? "\u2014"}`,
3977
+ `Branch: ${formatBranchName2(detail.sourceBranch)} Commit: ${detail.sourceCommit ?? "unavailable"}`,
3978
+ ...detail.webUrl ? [`Link: ${detail.webUrl}`] : [],
3979
+ "",
3980
+ "Stages:",
3981
+ ...timelineRows(detail.stages, detail.errorsAvailable),
3982
+ "",
3983
+ "Jobs:",
3984
+ ...timelineRows(detail.jobs, detail.errorsAvailable),
3985
+ "",
3986
+ "Errors:",
3987
+ ...errorRows(detail),
3988
+ "",
3989
+ "Tests:",
3990
+ ...testRows(detail)
3991
+ ].join("\n");
3992
+ }
3993
+ function createPipelineGetRunDetailCommand() {
3994
+ const command = new Command13("get-run-detail");
3995
+ command.description("Show a detailed summary of a single pipeline run (errors, failing tests, stages)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").action(async (runIdRaw, options) => {
3996
+ validateOrgProjectPair(options);
3997
+ const runId = parsePositiveId(runIdRaw);
3998
+ if (runId === null) {
3999
+ writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
4000
+ return;
4001
+ }
4002
+ let context;
4003
+ try {
4004
+ const resolved = await resolvePipelineContext(options);
4005
+ context = resolved.context;
4006
+ const detail = await getRunDetail(resolved.context, resolved.cred, runId);
4007
+ if (options.json) {
4008
+ process.stdout.write(`${JSON.stringify(detail, null, 2)}
4009
+ `);
4010
+ return;
4011
+ }
4012
+ process.stdout.write(`${formatRunDetail(detail)}
4013
+ `);
4014
+ } catch (err) {
4015
+ handlePipelineError(err, context);
4016
+ }
4017
+ });
4018
+ return command;
4019
+ }
4020
+ function grepWithContext(lines, grep, context) {
4021
+ const include = /* @__PURE__ */ new Set();
4022
+ lines.forEach((line, i) => {
4023
+ if (grep.test(line)) {
4024
+ for (let j = Math.max(0, i - context); j <= Math.min(lines.length - 1, i + context); j++) {
4025
+ include.add(j);
4026
+ }
4027
+ }
4028
+ });
4029
+ const selected = [];
4030
+ let prev = -1;
4031
+ for (const i of [...include].sort((a, b) => a - b)) {
4032
+ if (selected.length > 0 && i > prev + 1) {
4033
+ selected.push("--");
4034
+ }
4035
+ selected.push(lines[i]);
4036
+ prev = i;
4037
+ }
4038
+ return selected;
4039
+ }
4040
+ function filterLogLines(content, grep, tail, context) {
4041
+ let lines = content.split("\n");
4042
+ if (lines.at(-1) === "") {
4043
+ lines.pop();
4044
+ }
4045
+ if (grep) {
4046
+ lines = context > 0 ? grepWithContext(lines, grep, context) : lines.filter((line) => grep.test(line));
4047
+ }
4048
+ if (tail !== void 0 && lines.length > tail) {
4049
+ lines = lines.slice(-tail);
4050
+ }
4051
+ return lines;
4052
+ }
4053
+ function parseLogFilters(options) {
4054
+ if (options.logId !== void 0 && options.step !== void 0) {
4055
+ writeError2("Use either --log-id or --step, not both.");
4056
+ return null;
4057
+ }
4058
+ const selectsSingleLog = options.logId !== void 0 || options.step !== void 0;
4059
+ const slices = options.tail !== void 0 || options.grep !== void 0 || options.context !== void 0;
4060
+ if (slices && !selectsSingleLog) {
4061
+ writeError2("--tail, --grep, and --context require --log-id or --step.");
4062
+ return null;
4063
+ }
4064
+ if (options.context !== void 0 && options.grep === void 0) {
4065
+ writeError2("--context requires --grep.");
4066
+ return null;
4067
+ }
4068
+ const tail = parseOptionalCount(options.tail, "--tail");
4069
+ if (tail === null) return null;
4070
+ const contextLines = parseOptionalCount(options.context, "--context");
4071
+ if (contextLines === null) return null;
4072
+ let grep;
4073
+ if (options.grep !== void 0) {
4074
+ try {
4075
+ grep = new RegExp(options.grep);
4076
+ } catch {
4077
+ writeError2(`Invalid --grep "${options.grep}"; expected a valid regular expression.`);
4078
+ return null;
4079
+ }
4080
+ }
4081
+ return { tail, contextLines: contextLines ?? 0, grep };
4082
+ }
4083
+ function chooseStepLog(logs, step, runId) {
4084
+ const needle = step.toLowerCase();
4085
+ const matches = logs.filter((l) => l.step?.toLowerCase().includes(needle));
4086
+ const exact = matches.filter((l) => l.step?.toLowerCase() === needle);
4087
+ const chosen = exact.length === 1 ? exact : matches;
4088
+ if (chosen.length === 0) {
4089
+ writeError2(`No log matches step "${step}" in run ${runId}.`);
4090
+ return null;
4091
+ }
4092
+ if (chosen.length > 1) {
4093
+ const candidates = chosen.map((l) => `${l.id} (${l.step})`).join(", ");
4094
+ writeError2(`Step "${step}" matches multiple logs: ${candidates}. Be more specific or use --log-id.`);
4095
+ return null;
4096
+ }
4097
+ return chosen[0].id;
4098
+ }
4099
+ async function resolveRequestedLogId(resolved, runId, options) {
4100
+ if (options.logId !== void 0) {
4101
+ const parsed = parsePositiveId(options.logId);
4102
+ if (parsed === null) {
4103
+ writeError2(`Invalid --log-id "${options.logId}"; expected a positive integer.`);
4104
+ return null;
4105
+ }
4106
+ return parsed;
4107
+ }
4108
+ if (options.step === void 0) {
4109
+ return void 0;
4110
+ }
4111
+ const allLogs = await getRunLogs(resolved.context, resolved.cred, runId);
4112
+ return chooseStepLog(allLogs, options.step, runId);
4113
+ }
4114
+ function printSingleLog(content, filters) {
4115
+ if (filters.grep !== void 0 || filters.tail !== void 0) {
4116
+ const lines = filterLogLines(content, filters.grep, filters.tail, filters.contextLines);
4117
+ if (lines.length > 0) {
4118
+ process.stdout.write(`${lines.join("\n")}
4119
+ `);
4120
+ }
4121
+ return;
4122
+ }
4123
+ process.stdout.write(content.endsWith("\n") ? content : `${content}
4124
+ `);
4125
+ }
4126
+ function createPipelineLogsCommand() {
4127
+ const command = new Command13("logs");
4128
+ command.description("List a pipeline run's logs, or print a specific log with --log-id").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--log-id <id>", "print the content of this log id").option("--step <name>", "print the log of the step/job matching this name (case-insensitive substring)").option("--tail <n>", "with --log-id/--step, print only the last N lines").option("--grep <pattern>", "with --log-id/--step, print only lines matching this regular expression").option("--context <n>", "with --grep, also print N lines around each match (grep -C)").option("--json", "output JSON").action(async (runIdRaw, options) => {
4129
+ validateOrgProjectPair(options);
4130
+ const runId = parsePositiveId(runIdRaw);
4131
+ if (runId === null) {
4132
+ writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
4133
+ return;
4134
+ }
4135
+ const filters = parseLogFilters(options);
4136
+ if (filters === null) {
4137
+ return;
4138
+ }
4139
+ let context;
4140
+ try {
4141
+ const resolved = await resolvePipelineContext(options);
4142
+ context = resolved.context;
4143
+ const logId = await resolveRequestedLogId(resolved, runId, options);
4144
+ if (logId === null) {
4145
+ return;
4146
+ }
4147
+ if (logId !== void 0) {
4148
+ const content = await getRunLog(resolved.context, resolved.cred, runId, logId);
4149
+ printSingleLog(content, filters);
4150
+ return;
4151
+ }
4152
+ const logs = await getRunLogs(resolved.context, resolved.cred, runId);
4153
+ if (options.json) {
4154
+ process.stdout.write(`${JSON.stringify(logs, null, 2)}
4155
+ `);
4156
+ return;
4157
+ }
4158
+ if (logs.length === 0) {
4159
+ process.stdout.write(`No logs found for run ${runId}.
4160
+ `);
4161
+ return;
4162
+ }
4163
+ const rows = logs.map((l) => [
4164
+ String(l.id),
4165
+ l.createdOn ?? "\u2014",
4166
+ l.lineCount == null ? "" : `${l.lineCount} lines`,
4167
+ l.step ?? ""
4168
+ ]);
4169
+ process.stdout.write(`${formatTable(rows, /* @__PURE__ */ new Set([0]))}
4170
+ `);
4171
+ } catch (err) {
4172
+ handlePipelineError(err, context);
4173
+ }
4174
+ });
4175
+ return command;
4176
+ }
4177
+ function createPipelineTestsCommand() {
4178
+ const command = new Command13("tests");
4179
+ command.description("Show a run's test results: summary plus failing tests by name (no log grepping needed)").argument("<run_id>", "pipeline run id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--failed", "list only the failing tests").option("--json", "output JSON").action(async (runIdRaw, options) => {
4180
+ validateOrgProjectPair(options);
4181
+ const runId = parsePositiveId(runIdRaw);
4182
+ if (runId === null) {
4183
+ writeError2(`Invalid run id "${runIdRaw}"; expected a positive integer.`);
4184
+ return;
4185
+ }
4186
+ let context;
4187
+ try {
4188
+ const resolved = await resolvePipelineContext(options);
4189
+ context = resolved.context;
4190
+ const summary = await getTestSummary(resolved.context, resolved.cred, runId);
4191
+ const failedTests = summary.failed > 0 ? await getFailedTests(resolved.context, resolved.cred, runId) : [];
4192
+ if (options.json) {
4193
+ process.stdout.write(`${JSON.stringify({ ...summary, failedTests }, null, 2)}
4194
+ `);
4195
+ return;
4196
+ }
4197
+ if (!summary.present) {
4198
+ process.stdout.write(`No test results published for run ${runId}.
4199
+ `);
4200
+ return;
4201
+ }
4202
+ if (!options.failed) {
4203
+ process.stdout.write(`Run #${runId}: ${summary.failed} failing of ${summary.total} tests
4204
+ `);
4205
+ }
4206
+ if (failedTests.length > 0) {
4207
+ process.stdout.write(`${failedTests.map(failedTestRow).join("\n")}
4208
+ `);
4209
+ } else if (options.failed) {
4210
+ process.stdout.write("No failing tests.\n");
4211
+ }
4212
+ } catch (err) {
4213
+ handlePipelineError(err, context);
4214
+ }
4215
+ });
4216
+ return command;
4217
+ }
4218
+ function parseParameters(values) {
4219
+ const result = {};
4220
+ for (const entry of values ?? []) {
4221
+ const eq = entry.indexOf("=");
4222
+ if (eq <= 0) {
4223
+ return null;
4224
+ }
4225
+ const key = entry.slice(0, eq);
4226
+ const value = entry.slice(eq + 1);
4227
+ result[key] = value;
4228
+ }
4229
+ return result;
4230
+ }
4231
+ function collectParameter(value, previous) {
4232
+ return previous.concat([value]);
4233
+ }
4234
+ function createPipelineStartCommand() {
4235
+ const command = new Command13("start");
4236
+ command.description("Queue a new run of a pipeline definition").argument("<def_id>", "pipeline definition id").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--branch <branch>", "branch to run against (default: pipeline default branch)").option("--parameter <key=value>", "template parameter (repeatable)", collectParameter, []).option("--json", "output JSON").action(async (defIdRaw, options) => {
4237
+ validateOrgProjectPair(options);
4238
+ const defId = parsePositiveId(defIdRaw);
4239
+ if (defId === null) {
4240
+ writeError2(`Invalid definition id "${defIdRaw}"; expected a positive integer.`);
4241
+ return;
4242
+ }
4243
+ const parameters = parseParameters(options.parameter);
4244
+ if (parameters === null) {
4245
+ writeError2("Invalid --parameter; expected key=value.");
4246
+ return;
4247
+ }
4248
+ let context;
4249
+ try {
4250
+ const resolved = await resolvePipelineContext(options);
4251
+ context = resolved.context;
4252
+ const result = await runPipeline(resolved.context, resolved.cred, defId, {
4253
+ branch: options.branch,
4254
+ parameters
4255
+ });
4256
+ if (options.json) {
4257
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
4258
+ `);
4259
+ return;
4260
+ }
4261
+ process.stdout.write(`Queued run #${result.id} [${result.state}]
4262
+ ${result.webUrl ?? "\u2014"}
4263
+ `);
4264
+ } catch (err) {
4265
+ handlePipelineError(err, context);
4266
+ }
4267
+ });
4268
+ return command;
4269
+ }
4270
+ function createPipelineCommand() {
4271
+ const command = new Command13("pipeline");
4272
+ command.description("Manage Azure DevOps pipelines");
4273
+ command.addCommand(createPipelineListCommand());
4274
+ command.addCommand(createPipelineGetRunsCommand());
4275
+ command.addCommand(createPipelineWaitCommand());
4276
+ command.addCommand(createPipelineGetRunDetailCommand());
4277
+ command.addCommand(createPipelineLogsCommand());
4278
+ command.addCommand(createPipelineTestsCommand());
4279
+ command.addCommand(createPipelineStartCommand());
4280
+ return command;
4281
+ }
4282
+
4283
+ // src/commands/comments.ts
4284
+ import { Command as Command14 } from "commander";
4285
+ function writeError3(message) {
4286
+ process.stderr.write(`Error: ${message}
3375
4287
  `);
3376
4288
  process.exit(1);
3377
4289
  }
@@ -3389,7 +4301,7 @@ function formatComments(result, convertMarkdown) {
3389
4301
  return lines.join("\n");
3390
4302
  }
3391
4303
  function createCommentsListCommand() {
3392
- const command = new Command13("list");
4304
+ const command = new Command14("list");
3393
4305
  command.description("List visible comments for a work item").argument("<id>", "work item ID").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "convert HTML comment bodies to markdown").action(async (idStr, options) => {
3394
4306
  validateOrgProjectPair(options);
3395
4307
  const id = parseWorkItemId(idStr);
@@ -3417,12 +4329,12 @@ function createCommentsListCommand() {
3417
4329
  return command;
3418
4330
  }
3419
4331
  function createCommentsAddCommand() {
3420
- const command = new Command13("add");
4332
+ const command = new Command14("add");
3421
4333
  command.description("Add a comment to a work item").argument("<id>", "work item ID").argument("<text>", "comment text").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--json", "output JSON").option("--markdown", "post comment as markdown").action(async (idStr, text, options) => {
3422
4334
  validateOrgProjectPair(options);
3423
4335
  const id = parseWorkItemId(idStr);
3424
4336
  if (text.trim() === "") {
3425
- writeError2("Comment text must be a non-empty string.");
4337
+ writeError3("Comment text must be a non-empty string.");
3426
4338
  }
3427
4339
  let context;
3428
4340
  try {
@@ -3444,7 +4356,7 @@ function createCommentsAddCommand() {
3444
4356
  return command;
3445
4357
  }
3446
4358
  function createCommentsCommand() {
3447
- const command = new Command13("comments");
4359
+ const command = new Command14("comments");
3448
4360
  command.description("Manage Azure DevOps work item comments");
3449
4361
  command.addCommand(createCommentsListCommand());
3450
4362
  command.addCommand(createCommentsAddCommand());
@@ -3452,12 +4364,12 @@ function createCommentsCommand() {
3452
4364
  }
3453
4365
 
3454
4366
  // src/commands/download-attachment.ts
3455
- import { Command as Command14 } from "commander";
4367
+ import { Command as Command15 } from "commander";
3456
4368
  import { writeFile as writeFile2 } from "fs/promises";
3457
4369
  import { existsSync as existsSync5 } from "fs";
3458
4370
  import { join as join3 } from "path";
3459
4371
  function createDownloadAttachmentCommand() {
3460
- const command = new Command14("download-attachment");
4372
+ const command = new Command15("download-attachment");
3461
4373
  command.description("Download an attachment from an Azure DevOps work item").argument("<id>", "work item ID").argument("<filename>", "name of the attachment to download").option("--org <org>", "Azure DevOps organization").option("--project <project>", "Azure DevOps project").option("--output <dir>", "target directory for the downloaded file").action(
3462
4374
  async (idStr, filename, options) => {
3463
4375
  const id = parseWorkItemId(idStr);
@@ -3603,7 +4515,15 @@ async function getUpdateNotice(opts) {
3603
4515
  }
3604
4516
 
3605
4517
  // src/index.ts
3606
- var program = new Command15();
4518
+ function exitOnEpipe(err) {
4519
+ if (err.code === "EPIPE") {
4520
+ process.exit(0);
4521
+ }
4522
+ throw err;
4523
+ }
4524
+ process.stdout.on("error", exitOnEpipe);
4525
+ process.stderr.on("error", exitOnEpipe);
4526
+ var program = new Command16();
3607
4527
  program.name("azdo").description("Azure DevOps CLI tool").version(version, "-v, --version");
3608
4528
  program.option("--no-update-check", "Skip the check for a newer published version");
3609
4529
  program.addCommand(createGetItemCommand());
@@ -3618,6 +4538,7 @@ program.addCommand(createSetMdFieldCommand());
3618
4538
  program.addCommand(createUpsertCommand());
3619
4539
  program.addCommand(createListFieldsCommand());
3620
4540
  program.addCommand(createPrCommand());
4541
+ program.addCommand(createPipelineCommand());
3621
4542
  program.addCommand(createCommentsCommand());
3622
4543
  program.addCommand(createDownloadAttachmentCommand());
3623
4544
  program.showHelpAfterError();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.10.0-develop.394",
3
+ "version": "0.10.0-develop.423",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {