@kolatts/pncli 1.3.0 → 1.4.0

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
@@ -49,8 +49,7 @@ pncli uses a three-layer config system (highest priority wins):
49
49
  | `PNCLI_BITBUCKET_PAT` | Bitbucket personal access token |
50
50
  | `PNCLI_SONAR_BASE_URL` | SonarQube Server base URL |
51
51
  | `PNCLI_SONAR_TOKEN` | SonarQube personal access token |
52
- | `PNCLI_SDE_BASE_URL` | SDElements base URL |
53
- | `PNCLI_SDE_TOKEN` | SDElements API token |
52
+ | `PNCLI_SDE_CONNECTION` | SDElements connection string (`api-token@hostname`, e.g. `mytoken@myorg.sdelements.com`) |
54
53
  | `PNCLI_CONFIG_PATH` | Override global config file path |
55
54
 
56
55
  ## For AI Agents
@@ -88,6 +87,8 @@ This project uses Conventional Commits for automatic versioning:
88
87
  | SonarQube | ✅ Active | Server Web API |
89
88
  | SDElements | ✅ Active | REST API v2 (cloud + on-prem) |
90
89
  | Artifactory | 🔜 Coming | The nightmare never ends |
90
+ | Jenkins | 🔜 Coming | REST API |
91
+ | Azure DevOps Server | ✅ Active | REST API v7.1 (on-prem, Windows auth + PAT) |
91
92
 
92
93
  ## License
93
94
 
@@ -189,7 +189,10 @@ pncli bitbucket decline-pr
189
189
  --id <pr-id> Pull request ID
190
190
 
191
191
  pncli bitbucket list-comments
192
- --pr <pr-id> Pull request ID
192
+ --pr <pr-id> Pull request ID
193
+ --no-with-replies Exclude replies; return top-level comments only
194
+ --inline-only Return only inline file comments
195
+ --general-only Return only general (non-inline) PR comments
193
196
 
194
197
  pncli bitbucket add-comment
195
198
  --pr <pr-id> Pull request ID
@@ -497,6 +500,22 @@ pncli config set
497
500
  pncli config test
498
501
  ```
499
502
 
503
+ ### Ado
504
+
505
+ ```
506
+ pncli ado whoami
507
+
508
+ pncli ado connection-data
509
+
510
+ pncli ado project
511
+
512
+ pncli ado work
513
+
514
+ pncli ado repo
515
+
516
+ pncli ado pipeline
517
+ ```
518
+
500
519
  <!-- COMMAND-REFERENCE:END -->
501
520
 
502
521
  ## Output Format
@@ -0,0 +1,558 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/http.ts
4
+ import fs2 from "fs";
5
+
6
+ // src/lib/errors.ts
7
+ var PncliError = class extends Error {
8
+ status;
9
+ url;
10
+ constructor(message, status = 1, url) {
11
+ super(message);
12
+ this.name = "PncliError";
13
+ this.status = status;
14
+ this.url = url;
15
+ }
16
+ };
17
+
18
+ // src/lib/exitCodes.ts
19
+ var ExitCode = {
20
+ SUCCESS: 0,
21
+ GENERAL_ERROR: 1,
22
+ USAGE_ERROR: 2,
23
+ NETWORK_ERROR: 69,
24
+ // EX_UNAVAILABLE from sysexits.h
25
+ AUTH_ERROR: 77,
26
+ // EX_NOPERM from sysexits.h
27
+ CONFIG_ERROR: 78
28
+ // EX_CONFIG from sysexits.h
29
+ };
30
+ function exitCodeFromStatus(httpStatus) {
31
+ if (httpStatus === 401 || httpStatus === 403) return ExitCode.AUTH_ERROR;
32
+ if (httpStatus === 0) return ExitCode.NETWORK_ERROR;
33
+ return ExitCode.GENERAL_ERROR;
34
+ }
35
+
36
+ // src/lib/output.ts
37
+ import fs from "fs";
38
+ import chalk from "chalk";
39
+ var globalOptions = { pretty: false, verbose: false };
40
+ var globalUser = { email: void 0, userId: void 0 };
41
+ function setGlobalOptions(opts) {
42
+ globalOptions = opts;
43
+ }
44
+ function setGlobalUser(user) {
45
+ globalUser = user;
46
+ }
47
+ function buildMeta(service, action, startTime) {
48
+ return {
49
+ service,
50
+ action,
51
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
52
+ duration_ms: Date.now() - startTime,
53
+ user: globalUser.email ?? globalUser.userId ? globalUser : void 0
54
+ };
55
+ }
56
+ function success(data, service, action, startTime) {
57
+ const envelope = {
58
+ ok: true,
59
+ data,
60
+ meta: buildMeta(service, action, startTime)
61
+ };
62
+ process.stdout.write(
63
+ (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + "\n"
64
+ );
65
+ }
66
+ function fail(err, service, action, startTime) {
67
+ const errorDetail = {
68
+ status: err instanceof PncliError ? err.status : 1,
69
+ message: err instanceof Error ? err.message : String(err),
70
+ url: err instanceof PncliError ? err.url ?? null : null
71
+ };
72
+ const envelope = {
73
+ ok: false,
74
+ error: errorDetail,
75
+ meta: buildMeta(service, action, startTime)
76
+ };
77
+ const msg = globalOptions.pretty ? chalk.red("\u2717 Error: ") + errorDetail.message : null;
78
+ if (msg) process.stderr.write(msg + "\n");
79
+ const output = (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + "\n";
80
+ const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
81
+ try {
82
+ fs.writeSync(process.stdout.fd, output);
83
+ } catch (writeErr) {
84
+ if (writeErr.code !== "EPIPE") throw writeErr;
85
+ }
86
+ process.exitCode = exitCode;
87
+ throw new PncliError(errorDetail.message, errorDetail.status);
88
+ }
89
+ function log(message) {
90
+ if (globalOptions.verbose) {
91
+ process.stderr.write(
92
+ (globalOptions.pretty ? chalk.gray("\u25B8 ") : "") + message + "\n"
93
+ );
94
+ }
95
+ }
96
+ function warn(message) {
97
+ process.stderr.write(
98
+ (globalOptions.pretty ? chalk.yellow("\u26A0 ") : "") + message + "\n"
99
+ );
100
+ }
101
+
102
+ // src/lib/adoFetch.ts
103
+ async function buildAdoFetcher(config) {
104
+ const { pat, baseUrl } = config.ado;
105
+ if (!baseUrl) {
106
+ throw new PncliError("Azure DevOps baseUrl not configured. Run: pncli config init", 1);
107
+ }
108
+ if (!pat) {
109
+ throw new PncliError(
110
+ "Azure DevOps PAT not configured. Set a PAT via pncli config init or PNCLI_ADO_PAT env var.",
111
+ 1
112
+ );
113
+ }
114
+ const encoded = Buffer.from(":" + pat).toString("base64");
115
+ const authHeader = `Basic ${encoded}`;
116
+ return (url, init) => fetch(url, {
117
+ ...init,
118
+ headers: {
119
+ ...init?.headers,
120
+ "Authorization": authHeader,
121
+ "Content-Type": "application/json",
122
+ "Accept": "application/json"
123
+ }
124
+ });
125
+ }
126
+
127
+ // src/lib/http.ts
128
+ function buildUrl(base, path, params) {
129
+ const url = new URL(path, base.endsWith("/") ? base : base + "/");
130
+ if (params) {
131
+ for (const [key, val] of Object.entries(params)) {
132
+ if (val !== void 0) {
133
+ url.searchParams.set(key, String(val));
134
+ }
135
+ }
136
+ }
137
+ return url.toString();
138
+ }
139
+ async function fetchWithTimeout(url, init, timeoutMs, fetcher = fetch) {
140
+ const controller = new AbortController();
141
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
142
+ try {
143
+ return await fetcher(url, { ...init, signal: controller.signal });
144
+ } finally {
145
+ clearTimeout(timer);
146
+ }
147
+ }
148
+ async function request(url, init, timeoutMs, fetcher = fetch) {
149
+ let lastError;
150
+ for (let attempt = 0; attempt < 3; attempt++) {
151
+ let response;
152
+ try {
153
+ response = await fetchWithTimeout(url, init, timeoutMs, fetcher);
154
+ } catch (err) {
155
+ throw new PncliError(
156
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`,
157
+ 0,
158
+ url
159
+ );
160
+ }
161
+ if (response.status === 429) {
162
+ const retryAfter = response.headers.get("Retry-After");
163
+ const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : (attempt + 1) * 1e3;
164
+ log(`Rate limited. Retrying after ${waitMs}ms...`);
165
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
166
+ lastError = new PncliError("Rate limited", 429, url);
167
+ continue;
168
+ }
169
+ if (!response.ok) {
170
+ let message = `HTTP ${response.status} ${response.statusText}`;
171
+ try {
172
+ const body = await response.text();
173
+ const parsed = JSON.parse(body);
174
+ const parts = [];
175
+ if (Array.isArray(parsed.errorMessages)) {
176
+ parts.push(...parsed.errorMessages.filter(Boolean));
177
+ }
178
+ if (parsed.errors && typeof parsed.errors === "object" && !Array.isArray(parsed.errors)) {
179
+ for (const [field, msg] of Object.entries(parsed.errors)) {
180
+ parts.push(`${field}: ${msg}`);
181
+ }
182
+ }
183
+ if (Array.isArray(parsed.errors)) {
184
+ for (const e of parsed.errors) {
185
+ if (e?.message) parts.push(e.message);
186
+ else if (e?.msg) parts.push(e.msg);
187
+ }
188
+ }
189
+ if (parts.length === 0 && parsed.message) {
190
+ parts.push(String(parsed.message));
191
+ }
192
+ if (parts.length > 0) message = parts.join("; ");
193
+ } catch {
194
+ }
195
+ throw new PncliError(message, response.status, url);
196
+ }
197
+ if (response.status === 204) {
198
+ return void 0;
199
+ }
200
+ const text = await response.text();
201
+ if (!text) return void 0;
202
+ return JSON.parse(text);
203
+ }
204
+ throw lastError ?? new PncliError("Request failed after retries", 1, url);
205
+ }
206
+ var HttpClient = class {
207
+ config;
208
+ dryRun;
209
+ adoFetcher = null;
210
+ constructor(config, dryRun = false) {
211
+ this.config = config;
212
+ this.dryRun = dryRun;
213
+ }
214
+ jiraHeaders() {
215
+ const { apiToken } = this.config.jira;
216
+ if (!apiToken) throw new PncliError("Jira credentials not configured. Run: pncli config init");
217
+ return {
218
+ "Authorization": `Bearer ${apiToken}`,
219
+ "Content-Type": "application/json",
220
+ "Accept": "application/json",
221
+ "Connection": "close"
222
+ };
223
+ }
224
+ bitbucketHeaders() {
225
+ const { pat } = this.config.bitbucket;
226
+ if (!pat) throw new PncliError("Bitbucket credentials not configured. Run: pncli config init");
227
+ return {
228
+ "Authorization": `Bearer ${pat}`,
229
+ "Content-Type": "application/json",
230
+ "Accept": "application/json",
231
+ "Connection": "close"
232
+ };
233
+ }
234
+ async jira(path, opts = {}) {
235
+ const baseUrl = this.config.jira.baseUrl;
236
+ if (!baseUrl) throw new PncliError("Jira baseUrl not configured. Run: pncli config init");
237
+ const url = buildUrl(baseUrl, path, opts.params);
238
+ const headers = this.jiraHeaders();
239
+ const init = {
240
+ method: opts.method ?? "GET",
241
+ headers,
242
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
243
+ };
244
+ if (this.dryRun) {
245
+ const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
246
+ const msg = `DRY RUN: ${init.method} ${url}
247
+ Headers: ${JSON.stringify(safeHeaders, null, 2)}
248
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
249
+ ` : "");
250
+ fs2.writeSync(process.stderr.fd, msg);
251
+ process.exitCode = ExitCode.SUCCESS;
252
+ throw new PncliError("dry-run", 0);
253
+ }
254
+ return request(url, init, opts.timeoutMs ?? 3e4);
255
+ }
256
+ async bitbucket(path, opts = {}) {
257
+ const baseUrl = this.config.bitbucket.baseUrl;
258
+ if (!baseUrl) throw new PncliError("Bitbucket baseUrl not configured. Run: pncli config init");
259
+ const url = buildUrl(baseUrl, path, opts.params);
260
+ const headers = this.bitbucketHeaders();
261
+ const init = {
262
+ method: opts.method ?? "GET",
263
+ headers,
264
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
265
+ };
266
+ if (this.dryRun) {
267
+ const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
268
+ const msg = `DRY RUN: ${init.method} ${url}
269
+ Headers: ${JSON.stringify(safeHeaders, null, 2)}
270
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
271
+ ` : "");
272
+ fs2.writeSync(process.stderr.fd, msg);
273
+ process.exitCode = ExitCode.SUCCESS;
274
+ throw new PncliError("dry-run", 0);
275
+ }
276
+ return request(url, init, opts.timeoutMs ?? 3e4);
277
+ }
278
+ confluenceHeaders() {
279
+ const { apiToken } = this.config.confluence;
280
+ if (!apiToken) throw new PncliError("Confluence credentials not configured. Run: pncli config init");
281
+ return {
282
+ "Authorization": `Bearer ${apiToken}`,
283
+ "Content-Type": "application/json",
284
+ "Accept": "application/json",
285
+ "Connection": "close"
286
+ };
287
+ }
288
+ async confluence(path, opts = {}) {
289
+ const baseUrl = this.config.confluence.baseUrl;
290
+ if (!baseUrl) throw new PncliError("Confluence baseUrl not configured. Run: pncli config init");
291
+ const url = buildUrl(baseUrl, path, opts.params);
292
+ const headers = this.confluenceHeaders();
293
+ const init = {
294
+ method: opts.method ?? "GET",
295
+ headers,
296
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
297
+ };
298
+ if (this.dryRun) {
299
+ const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
300
+ const msg = `DRY RUN: ${init.method} ${url}
301
+ Headers: ${JSON.stringify(safeHeaders, null, 2)}
302
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
303
+ ` : "");
304
+ fs2.writeSync(process.stderr.fd, msg);
305
+ process.exitCode = ExitCode.SUCCESS;
306
+ throw new PncliError("dry-run", 0);
307
+ }
308
+ return request(url, init, opts.timeoutMs ?? 3e4);
309
+ }
310
+ sdeHeaders() {
311
+ const { token } = this.config.sde;
312
+ if (!token) throw new PncliError("SDElements credentials not configured. Run: pncli config init");
313
+ return {
314
+ "Authorization": `Token ${token}`,
315
+ "Content-Type": "application/json",
316
+ "Accept": "application/json",
317
+ "Connection": "close"
318
+ };
319
+ }
320
+ async sde(path, opts = {}) {
321
+ const baseUrl = this.config.sde.baseUrl;
322
+ if (!baseUrl) throw new PncliError("SDElements baseUrl not configured. Run: pncli config init");
323
+ const url = buildUrl(baseUrl, path, opts.params);
324
+ const headers = this.sdeHeaders();
325
+ const init = {
326
+ method: opts.method ?? "GET",
327
+ headers,
328
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
329
+ };
330
+ if (this.dryRun) {
331
+ const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
332
+ const msg = `DRY RUN: ${init.method} ${url}
333
+ Headers: ${JSON.stringify(safeHeaders, null, 2)}
334
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
335
+ ` : "");
336
+ fs2.writeSync(process.stderr.fd, msg);
337
+ process.exitCode = ExitCode.SUCCESS;
338
+ throw new PncliError("dry-run", 0);
339
+ }
340
+ return request(url, init, opts.timeoutMs ?? 3e4);
341
+ }
342
+ async sdePaginate(fetchPage) {
343
+ const results = [];
344
+ let page = 1;
345
+ const pageSize = 100;
346
+ while (true) {
347
+ const response = await fetchPage(page, pageSize);
348
+ results.push(...response.results);
349
+ if (results.length >= response.count || response.results.length === 0) break;
350
+ page++;
351
+ }
352
+ return results;
353
+ }
354
+ async getAdoFetcher() {
355
+ if (!this.adoFetcher) {
356
+ this.adoFetcher = await buildAdoFetcher(this.config);
357
+ }
358
+ return this.adoFetcher;
359
+ }
360
+ async ado(path, opts = {}) {
361
+ const baseUrl = this.config.ado.baseUrl;
362
+ if (!baseUrl) throw new PncliError("Azure DevOps baseUrl not configured. Run: pncli config init");
363
+ const url = buildUrl(baseUrl, path, opts.params);
364
+ if (this.dryRun) {
365
+ const msg = `DRY RUN: ${opts.method ?? "GET"} ${url}
366
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
367
+ ` : "");
368
+ fs2.writeSync(process.stderr.fd, msg);
369
+ process.exitCode = ExitCode.SUCCESS;
370
+ throw new PncliError("dry-run", 0);
371
+ }
372
+ const fetcher = await this.getAdoFetcher();
373
+ const defaultHeaders = {
374
+ "Accept": "application/json",
375
+ "Content-Type": "application/json",
376
+ ...opts.headers
377
+ };
378
+ const init = {
379
+ method: opts.method ?? "GET",
380
+ headers: defaultHeaders,
381
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
382
+ };
383
+ return request(url, init, opts.timeoutMs ?? 3e4, fetcher);
384
+ }
385
+ async adoText(path, opts = {}) {
386
+ const baseUrl = this.config.ado.baseUrl;
387
+ if (!baseUrl) throw new PncliError("Azure DevOps baseUrl not configured. Run: pncli config init");
388
+ const url = buildUrl(baseUrl, path, opts.params);
389
+ if (this.dryRun) {
390
+ fs2.writeSync(process.stderr.fd, `DRY RUN: ${opts.method ?? "GET"} ${url}
391
+ `);
392
+ process.exitCode = ExitCode.SUCCESS;
393
+ throw new PncliError("dry-run", 0);
394
+ }
395
+ const fetcher = await this.getAdoFetcher();
396
+ const response = await fetchWithTimeout(url, {
397
+ method: opts.method ?? "GET",
398
+ headers: { "Accept": "text/plain", ...opts.headers }
399
+ }, opts.timeoutMs ?? 3e4, fetcher);
400
+ if (!response.ok) {
401
+ let message = `HTTP ${response.status} ${response.statusText}`;
402
+ try {
403
+ const parsed = JSON.parse(await response.text());
404
+ if (parsed.message) message = String(parsed.message);
405
+ } catch {
406
+ }
407
+ throw new PncliError(message, response.status, url);
408
+ }
409
+ return response.text();
410
+ }
411
+ async adoRaw(path, opts = {}) {
412
+ const baseUrl = this.config.ado.baseUrl;
413
+ if (!baseUrl) throw new PncliError("Azure DevOps baseUrl not configured. Run: pncli config init");
414
+ const url = buildUrl(baseUrl, path, opts.params);
415
+ if (this.dryRun) {
416
+ fs2.writeSync(process.stderr.fd, `DRY RUN: ${opts.method ?? "GET"} ${url}
417
+ `);
418
+ process.exitCode = ExitCode.SUCCESS;
419
+ throw new PncliError("dry-run", 0);
420
+ }
421
+ const fetcher = await this.getAdoFetcher();
422
+ const controller = new AbortController();
423
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 3e4);
424
+ let response;
425
+ try {
426
+ response = await fetcher(url, {
427
+ method: opts.method ?? "GET",
428
+ headers: { "Accept": "application/json", "Content-Type": "application/json", ...opts.headers },
429
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
430
+ signal: controller.signal
431
+ });
432
+ } finally {
433
+ clearTimeout(timer);
434
+ }
435
+ if (!response.ok) {
436
+ let message = `HTTP ${response.status} ${response.statusText}`;
437
+ try {
438
+ const parsed = JSON.parse(await response.text());
439
+ if (parsed.message) message = String(parsed.message);
440
+ } catch {
441
+ }
442
+ throw new PncliError(message, response.status, url);
443
+ }
444
+ const text = await response.text();
445
+ const data = text ? JSON.parse(text) : void 0;
446
+ return { data, headers: response.headers };
447
+ }
448
+ async adoPaginate(fetchPage) {
449
+ const results = [];
450
+ let token;
451
+ while (true) {
452
+ const { data, headers } = await fetchPage(token);
453
+ results.push(...data.value ?? []);
454
+ const next = headers.get("x-ms-continuationtoken");
455
+ if (!next) break;
456
+ token = next;
457
+ }
458
+ return results;
459
+ }
460
+ sonarHeaders() {
461
+ const { token } = this.config.sonar;
462
+ if (!token) throw new PncliError("SonarQube credentials not configured. Run: pncli config init");
463
+ return {
464
+ "Authorization": `Bearer ${token}`,
465
+ "Content-Type": "application/json",
466
+ "Accept": "application/json",
467
+ "Connection": "close"
468
+ };
469
+ }
470
+ async sonar(path, opts = {}) {
471
+ const baseUrl = this.config.sonar.baseUrl;
472
+ if (!baseUrl) throw new PncliError("SonarQube baseUrl not configured. Run: pncli config init");
473
+ const url = buildUrl(baseUrl, path, opts.params);
474
+ const headers = this.sonarHeaders();
475
+ const init = {
476
+ method: opts.method ?? "GET",
477
+ headers,
478
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
479
+ };
480
+ if (this.dryRun) {
481
+ const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
482
+ const msg = `DRY RUN: ${init.method} ${url}
483
+ Headers: ${JSON.stringify(safeHeaders, null, 2)}
484
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
485
+ ` : "");
486
+ fs2.writeSync(process.stderr.fd, msg);
487
+ process.exitCode = ExitCode.SUCCESS;
488
+ throw new PncliError("dry-run", 0);
489
+ }
490
+ return request(url, init, opts.timeoutMs ?? 3e4);
491
+ }
492
+ async sonarPaginate(fetchPage) {
493
+ const results = [];
494
+ let page = 1;
495
+ const pageSize = 500;
496
+ while (true) {
497
+ const response = await fetchPage(page, pageSize);
498
+ results.push(...response.items);
499
+ if (results.length >= response.total || response.items.length === 0) break;
500
+ page++;
501
+ }
502
+ return results;
503
+ }
504
+ async confluencePaginate(fetchPage) {
505
+ const results = [];
506
+ let start = 0;
507
+ const limit = 25;
508
+ while (true) {
509
+ const page = await fetchPage(start, limit);
510
+ results.push(...page.results);
511
+ if (!page._links.next) break;
512
+ start += page.size;
513
+ }
514
+ return results;
515
+ }
516
+ async paginate(fetchPage) {
517
+ const results = [];
518
+ let start = 0;
519
+ const limit = 100;
520
+ while (true) {
521
+ const page = await fetchPage(start, limit);
522
+ results.push(...page.values);
523
+ if (page.isLastPage) break;
524
+ start = page.nextPageStart ?? start + limit;
525
+ }
526
+ return results;
527
+ }
528
+ async jiraPaginate(fetchPage) {
529
+ const results = [];
530
+ let startAt = 0;
531
+ const maxResults = 100;
532
+ while (true) {
533
+ const page = await fetchPage(startAt, maxResults);
534
+ const items = page.issues ?? page.values ?? [];
535
+ results.push(...items);
536
+ startAt += items.length;
537
+ if (startAt >= page.total || items.length === 0) break;
538
+ }
539
+ return results;
540
+ }
541
+ };
542
+ function createHttpClient(config, dryRun = false) {
543
+ return new HttpClient(config, dryRun);
544
+ }
545
+
546
+ export {
547
+ PncliError,
548
+ ExitCode,
549
+ setGlobalOptions,
550
+ setGlobalUser,
551
+ success,
552
+ fail,
553
+ log,
554
+ warn,
555
+ HttpClient,
556
+ createHttpClient
557
+ };
558
+ //# sourceMappingURL=chunk-HWXXNMGE.js.map