@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 +3 -2
- package/copilot-instructions.md +20 -1
- package/dist/chunk-HWXXNMGE.js +558 -0
- package/dist/chunk-HWXXNMGE.js.map +1 -0
- package/dist/chunk-JORIICS2.js +197 -0
- package/dist/chunk-JORIICS2.js.map +1 -0
- package/dist/chunk-KT3XINLJ.js +42 -0
- package/dist/chunk-KT3XINLJ.js.map +1 -0
- package/dist/cli.js +1358 -638
- package/dist/cli.js.map +1 -1
- package/dist/config-KECF7FJ2.js +18 -0
- package/dist/config-KECF7FJ2.js.map +1 -0
- package/dist/core-AJYPJ2Z7.js +8 -0
- package/dist/core-AJYPJ2Z7.js.map +1 -0
- package/dist/http-FC24JSK7.js +10 -0
- package/dist/http-FC24JSK7.js.map +1 -0
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
|
package/copilot-instructions.md
CHANGED
|
@@ -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>
|
|
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
|