@kolatts/pncli 1.2.0 → 1.3.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 +13 -12
- package/copilot-instructions.md +204 -3
- package/dist/cli.js +1226 -53
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/lib/output.ts
|
|
8
|
+
import fs from "fs";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
|
|
10
11
|
// src/lib/errors.ts
|
|
@@ -80,7 +81,12 @@ function fail(err, service, action, startTime) {
|
|
|
80
81
|
if (msg) process.stderr.write(msg + "\n");
|
|
81
82
|
const output = (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + "\n";
|
|
82
83
|
const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
|
|
83
|
-
|
|
84
|
+
try {
|
|
85
|
+
fs.writeSync(process.stdout.fd, output);
|
|
86
|
+
} catch (writeErr) {
|
|
87
|
+
if (writeErr.code !== "EPIPE") throw writeErr;
|
|
88
|
+
}
|
|
89
|
+
process.exitCode = exitCode;
|
|
84
90
|
throw new PncliError(errorDetail.message, errorDetail.status);
|
|
85
91
|
}
|
|
86
92
|
function log(message) {
|
|
@@ -97,7 +103,7 @@ function warn(message) {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
// src/lib/config.ts
|
|
100
|
-
import
|
|
106
|
+
import fs2 from "fs";
|
|
101
107
|
import os from "os";
|
|
102
108
|
import path from "path";
|
|
103
109
|
import { execSync } from "child_process";
|
|
@@ -108,11 +114,17 @@ var ENV_KEYS = {
|
|
|
108
114
|
JIRA_API_TOKEN: "PNCLI_JIRA_API_TOKEN",
|
|
109
115
|
BITBUCKET_BASE_URL: "PNCLI_BITBUCKET_BASE_URL",
|
|
110
116
|
BITBUCKET_PAT: "PNCLI_BITBUCKET_PAT",
|
|
117
|
+
CONFLUENCE_BASE_URL: "PNCLI_CONFLUENCE_BASE_URL",
|
|
118
|
+
CONFLUENCE_API_TOKEN: "PNCLI_CONFLUENCE_API_TOKEN",
|
|
111
119
|
ARTIFACTORY_BASE_URL: "PNCLI_ARTIFACTORY_BASE_URL",
|
|
112
120
|
ARTIFACTORY_TOKEN: "PNCLI_ARTIFACTORY_TOKEN",
|
|
113
121
|
ARTIFACTORY_REPO_NPM: "PNCLI_ARTIFACTORY_REPO_NPM",
|
|
114
122
|
ARTIFACTORY_REPO_NUGET: "PNCLI_ARTIFACTORY_REPO_NUGET",
|
|
115
123
|
ARTIFACTORY_REPO_MAVEN: "PNCLI_ARTIFACTORY_REPO_MAVEN",
|
|
124
|
+
SONAR_BASE_URL: "PNCLI_SONAR_BASE_URL",
|
|
125
|
+
SONAR_TOKEN: "PNCLI_SONAR_TOKEN",
|
|
126
|
+
SDE_BASE_URL: "PNCLI_SDE_BASE_URL",
|
|
127
|
+
SDE_TOKEN: "PNCLI_SDE_TOKEN",
|
|
116
128
|
CONFIG_PATH: "PNCLI_CONFIG_PATH"
|
|
117
129
|
};
|
|
118
130
|
function getGlobalConfigPath(overridePath) {
|
|
@@ -123,7 +135,7 @@ function getGlobalConfigPath(overridePath) {
|
|
|
123
135
|
}
|
|
124
136
|
function loadJsonFile(filePath) {
|
|
125
137
|
try {
|
|
126
|
-
const content =
|
|
138
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
127
139
|
return JSON.parse(content);
|
|
128
140
|
} catch {
|
|
129
141
|
return null;
|
|
@@ -145,7 +157,9 @@ function mergeCustomFields(global, repo) {
|
|
|
145
157
|
function mergeDefaults(global, repo) {
|
|
146
158
|
return {
|
|
147
159
|
jira: { ...global?.jira, ...repo?.jira },
|
|
148
|
-
bitbucket: { ...global?.bitbucket, ...repo?.bitbucket }
|
|
160
|
+
bitbucket: { ...global?.bitbucket, ...repo?.bitbucket },
|
|
161
|
+
sonar: { ...global?.sonar, ...repo?.sonar },
|
|
162
|
+
sde: { ...global?.sde, ...repo?.sde }
|
|
149
163
|
};
|
|
150
164
|
}
|
|
151
165
|
function loadConfig(opts = {}) {
|
|
@@ -171,6 +185,10 @@ function loadConfig(opts = {}) {
|
|
|
171
185
|
baseUrl: process.env[ENV_KEYS.BITBUCKET_BASE_URL] ?? globalConfig.bitbucket?.baseUrl,
|
|
172
186
|
pat: process.env[ENV_KEYS.BITBUCKET_PAT] ?? globalConfig.bitbucket?.pat
|
|
173
187
|
},
|
|
188
|
+
confluence: {
|
|
189
|
+
baseUrl: process.env[ENV_KEYS.CONFLUENCE_BASE_URL] ?? globalConfig.confluence?.baseUrl,
|
|
190
|
+
apiToken: process.env[ENV_KEYS.CONFLUENCE_API_TOKEN] ?? globalConfig.confluence?.apiToken ?? process.env[ENV_KEYS.JIRA_API_TOKEN] ?? globalConfig.jira?.apiToken
|
|
191
|
+
},
|
|
174
192
|
artifactory: {
|
|
175
193
|
baseUrl: process.env[ENV_KEYS.ARTIFACTORY_BASE_URL] ?? globalConfig.artifactory?.baseUrl,
|
|
176
194
|
token: process.env[ENV_KEYS.ARTIFACTORY_TOKEN] ?? globalConfig.artifactory?.token,
|
|
@@ -178,21 +196,29 @@ function loadConfig(opts = {}) {
|
|
|
178
196
|
nugetRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_NUGET] ?? globalConfig.artifactory?.nugetRepo,
|
|
179
197
|
mavenRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_MAVEN] ?? globalConfig.artifactory?.mavenRepo
|
|
180
198
|
},
|
|
199
|
+
sonar: {
|
|
200
|
+
baseUrl: process.env[ENV_KEYS.SONAR_BASE_URL] ?? globalConfig.sonar?.baseUrl,
|
|
201
|
+
token: process.env[ENV_KEYS.SONAR_TOKEN] ?? globalConfig.sonar?.token
|
|
202
|
+
},
|
|
203
|
+
sde: {
|
|
204
|
+
baseUrl: process.env[ENV_KEYS.SDE_BASE_URL] ?? globalConfig.sde?.baseUrl,
|
|
205
|
+
token: process.env[ENV_KEYS.SDE_TOKEN] ?? globalConfig.sde?.token
|
|
206
|
+
},
|
|
181
207
|
defaults: mergedDefaults
|
|
182
208
|
};
|
|
183
209
|
}
|
|
184
210
|
function writeGlobalConfig(config, configPath) {
|
|
185
211
|
const filePath = getGlobalConfigPath(configPath);
|
|
186
212
|
const dir = path.dirname(filePath);
|
|
187
|
-
if (!
|
|
188
|
-
|
|
213
|
+
if (!fs2.existsSync(dir)) {
|
|
214
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
189
215
|
}
|
|
190
|
-
|
|
216
|
+
fs2.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
191
217
|
}
|
|
192
218
|
function writeRepoConfig(config) {
|
|
193
219
|
const repoRoot = getRepoRoot();
|
|
194
220
|
const targetDir = repoRoot ?? process.cwd();
|
|
195
|
-
|
|
221
|
+
fs2.writeFileSync(path.join(targetDir, ".pncli.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
196
222
|
}
|
|
197
223
|
function setConfigValue(key, value, configPath) {
|
|
198
224
|
const filePath = getGlobalConfigPath(configPath);
|
|
@@ -221,9 +247,21 @@ function maskConfig(config) {
|
|
|
221
247
|
...config.bitbucket,
|
|
222
248
|
pat: config.bitbucket.pat ? "***" : void 0
|
|
223
249
|
},
|
|
250
|
+
confluence: {
|
|
251
|
+
...config.confluence,
|
|
252
|
+
apiToken: config.confluence.apiToken ? "***" : void 0
|
|
253
|
+
},
|
|
224
254
|
artifactory: {
|
|
225
255
|
...config.artifactory,
|
|
226
256
|
token: config.artifactory.token ? "***" : void 0
|
|
257
|
+
},
|
|
258
|
+
sonar: {
|
|
259
|
+
...config.sonar,
|
|
260
|
+
token: config.sonar.token ? "***" : void 0
|
|
261
|
+
},
|
|
262
|
+
sde: {
|
|
263
|
+
...config.sde,
|
|
264
|
+
token: config.sde.token ? "***" : void 0
|
|
227
265
|
}
|
|
228
266
|
};
|
|
229
267
|
}
|
|
@@ -428,6 +466,7 @@ function getGitContext(config) {
|
|
|
428
466
|
}
|
|
429
467
|
|
|
430
468
|
// src/lib/http.ts
|
|
469
|
+
import fs3 from "fs";
|
|
431
470
|
function buildUrl(base, path6, params) {
|
|
432
471
|
const url = new URL(path6, base.endsWith("/") ? base : base + "/");
|
|
433
472
|
if (params) {
|
|
@@ -486,6 +525,7 @@ async function request(url, init, timeoutMs) {
|
|
|
486
525
|
if (Array.isArray(parsed.errors)) {
|
|
487
526
|
for (const e of parsed.errors) {
|
|
488
527
|
if (e?.message) parts.push(e.message);
|
|
528
|
+
else if (e?.msg) parts.push(e.msg);
|
|
489
529
|
}
|
|
490
530
|
}
|
|
491
531
|
if (parts.length === 0 && parsed.message) {
|
|
@@ -499,7 +539,9 @@ async function request(url, init, timeoutMs) {
|
|
|
499
539
|
if (response.status === 204) {
|
|
500
540
|
return void 0;
|
|
501
541
|
}
|
|
502
|
-
|
|
542
|
+
const text = await response.text();
|
|
543
|
+
if (!text) return void 0;
|
|
544
|
+
return JSON.parse(text);
|
|
503
545
|
}
|
|
504
546
|
throw lastError ?? new PncliError("Request failed after retries", 1, url);
|
|
505
547
|
}
|
|
@@ -546,9 +588,9 @@ var HttpClient = class {
|
|
|
546
588
|
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
547
589
|
` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
|
|
548
590
|
` : "");
|
|
549
|
-
process.stderr.
|
|
550
|
-
|
|
551
|
-
|
|
591
|
+
fs3.writeSync(process.stderr.fd, msg);
|
|
592
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
593
|
+
throw new PncliError("dry-run", 0);
|
|
552
594
|
}
|
|
553
595
|
return request(url, init, opts.timeoutMs ?? 3e4);
|
|
554
596
|
}
|
|
@@ -568,12 +610,144 @@ Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
|
568
610
|
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
569
611
|
` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
|
|
570
612
|
` : "");
|
|
571
|
-
process.stderr.
|
|
572
|
-
|
|
573
|
-
|
|
613
|
+
fs3.writeSync(process.stderr.fd, msg);
|
|
614
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
615
|
+
throw new PncliError("dry-run", 0);
|
|
616
|
+
}
|
|
617
|
+
return request(url, init, opts.timeoutMs ?? 3e4);
|
|
618
|
+
}
|
|
619
|
+
confluenceHeaders() {
|
|
620
|
+
const { apiToken } = this.config.confluence;
|
|
621
|
+
if (!apiToken) throw new PncliError("Confluence credentials not configured. Run: pncli config init");
|
|
622
|
+
return {
|
|
623
|
+
"Authorization": `Bearer ${apiToken}`,
|
|
624
|
+
"Content-Type": "application/json",
|
|
625
|
+
"Accept": "application/json",
|
|
626
|
+
"Connection": "close"
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async confluence(path6, opts = {}) {
|
|
630
|
+
const baseUrl = this.config.confluence.baseUrl;
|
|
631
|
+
if (!baseUrl) throw new PncliError("Confluence baseUrl not configured. Run: pncli config init");
|
|
632
|
+
const url = buildUrl(baseUrl, path6, opts.params);
|
|
633
|
+
const headers = this.confluenceHeaders();
|
|
634
|
+
const init = {
|
|
635
|
+
method: opts.method ?? "GET",
|
|
636
|
+
headers,
|
|
637
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
638
|
+
};
|
|
639
|
+
if (this.dryRun) {
|
|
640
|
+
const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
|
|
641
|
+
const msg = `DRY RUN: ${init.method} ${url}
|
|
642
|
+
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
643
|
+
` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
|
|
644
|
+
` : "");
|
|
645
|
+
fs3.writeSync(process.stderr.fd, msg);
|
|
646
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
647
|
+
throw new PncliError("dry-run", 0);
|
|
648
|
+
}
|
|
649
|
+
return request(url, init, opts.timeoutMs ?? 3e4);
|
|
650
|
+
}
|
|
651
|
+
sdeHeaders() {
|
|
652
|
+
const { token } = this.config.sde;
|
|
653
|
+
if (!token) throw new PncliError("SDElements credentials not configured. Run: pncli config init");
|
|
654
|
+
return {
|
|
655
|
+
"Authorization": `Token ${token}`,
|
|
656
|
+
"Content-Type": "application/json",
|
|
657
|
+
"Accept": "application/json",
|
|
658
|
+
"Connection": "close"
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async sde(path6, opts = {}) {
|
|
662
|
+
const baseUrl = this.config.sde.baseUrl;
|
|
663
|
+
if (!baseUrl) throw new PncliError("SDElements baseUrl not configured. Run: pncli config init");
|
|
664
|
+
const url = buildUrl(baseUrl, path6, opts.params);
|
|
665
|
+
const headers = this.sdeHeaders();
|
|
666
|
+
const init = {
|
|
667
|
+
method: opts.method ?? "GET",
|
|
668
|
+
headers,
|
|
669
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
670
|
+
};
|
|
671
|
+
if (this.dryRun) {
|
|
672
|
+
const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
|
|
673
|
+
const msg = `DRY RUN: ${init.method} ${url}
|
|
674
|
+
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
675
|
+
` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
|
|
676
|
+
` : "");
|
|
677
|
+
fs3.writeSync(process.stderr.fd, msg);
|
|
678
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
679
|
+
throw new PncliError("dry-run", 0);
|
|
680
|
+
}
|
|
681
|
+
return request(url, init, opts.timeoutMs ?? 3e4);
|
|
682
|
+
}
|
|
683
|
+
async sdePaginate(fetchPage) {
|
|
684
|
+
const results = [];
|
|
685
|
+
let page = 1;
|
|
686
|
+
const pageSize = 100;
|
|
687
|
+
while (true) {
|
|
688
|
+
const response = await fetchPage(page, pageSize);
|
|
689
|
+
results.push(...response.results);
|
|
690
|
+
if (results.length >= response.count || response.results.length === 0) break;
|
|
691
|
+
page++;
|
|
692
|
+
}
|
|
693
|
+
return results;
|
|
694
|
+
}
|
|
695
|
+
sonarHeaders() {
|
|
696
|
+
const { token } = this.config.sonar;
|
|
697
|
+
if (!token) throw new PncliError("SonarQube credentials not configured. Run: pncli config init");
|
|
698
|
+
return {
|
|
699
|
+
"Authorization": `Bearer ${token}`,
|
|
700
|
+
"Content-Type": "application/json",
|
|
701
|
+
"Accept": "application/json",
|
|
702
|
+
"Connection": "close"
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async sonar(path6, opts = {}) {
|
|
706
|
+
const baseUrl = this.config.sonar.baseUrl;
|
|
707
|
+
if (!baseUrl) throw new PncliError("SonarQube baseUrl not configured. Run: pncli config init");
|
|
708
|
+
const url = buildUrl(baseUrl, path6, opts.params);
|
|
709
|
+
const headers = this.sonarHeaders();
|
|
710
|
+
const init = {
|
|
711
|
+
method: opts.method ?? "GET",
|
|
712
|
+
headers,
|
|
713
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
714
|
+
};
|
|
715
|
+
if (this.dryRun) {
|
|
716
|
+
const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
|
|
717
|
+
const msg = `DRY RUN: ${init.method} ${url}
|
|
718
|
+
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
719
|
+
` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
|
|
720
|
+
` : "");
|
|
721
|
+
fs3.writeSync(process.stderr.fd, msg);
|
|
722
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
723
|
+
throw new PncliError("dry-run", 0);
|
|
574
724
|
}
|
|
575
725
|
return request(url, init, opts.timeoutMs ?? 3e4);
|
|
576
726
|
}
|
|
727
|
+
async sonarPaginate(fetchPage) {
|
|
728
|
+
const results = [];
|
|
729
|
+
let page = 1;
|
|
730
|
+
const pageSize = 500;
|
|
731
|
+
while (true) {
|
|
732
|
+
const response = await fetchPage(page, pageSize);
|
|
733
|
+
results.push(...response.items);
|
|
734
|
+
if (results.length >= response.total || response.items.length === 0) break;
|
|
735
|
+
page++;
|
|
736
|
+
}
|
|
737
|
+
return results;
|
|
738
|
+
}
|
|
739
|
+
async confluencePaginate(fetchPage) {
|
|
740
|
+
const results = [];
|
|
741
|
+
let start = 0;
|
|
742
|
+
const limit = 25;
|
|
743
|
+
while (true) {
|
|
744
|
+
const page = await fetchPage(start, limit);
|
|
745
|
+
results.push(...page.results);
|
|
746
|
+
if (!page._links.next) break;
|
|
747
|
+
start += page.size;
|
|
748
|
+
}
|
|
749
|
+
return results;
|
|
750
|
+
}
|
|
577
751
|
async paginate(fetchPage) {
|
|
578
752
|
const results = [];
|
|
579
753
|
let start = 0;
|
|
@@ -1466,32 +1640,889 @@ function registerBitbucketCommands(program2) {
|
|
|
1466
1640
|
});
|
|
1467
1641
|
}
|
|
1468
1642
|
|
|
1643
|
+
// src/services/confluence/client.ts
|
|
1644
|
+
var API3 = "/rest/api";
|
|
1645
|
+
var ConfluenceClient = class {
|
|
1646
|
+
constructor(http) {
|
|
1647
|
+
this.http = http;
|
|
1648
|
+
}
|
|
1649
|
+
http;
|
|
1650
|
+
async getPage(id, expand = "body.storage,version,space,ancestors") {
|
|
1651
|
+
return this.http.confluence(`${API3}/content/${id}`, {
|
|
1652
|
+
params: { expand }
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
async getPageByTitle(spaceKey, title) {
|
|
1656
|
+
const result = await this.http.confluence(`${API3}/content`, {
|
|
1657
|
+
params: { spaceKey, title, expand: "body.storage,version,space", type: "page" }
|
|
1658
|
+
});
|
|
1659
|
+
return result.results[0] ?? null;
|
|
1660
|
+
}
|
|
1661
|
+
async listPages(spaceKey, opts = {}) {
|
|
1662
|
+
const initialStart = opts.start ?? 0;
|
|
1663
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1664
|
+
return this.http.confluence(`${API3}/content`, {
|
|
1665
|
+
params: { spaceKey, type: "page", expand: "version", start: initialStart + start, limit: opts.limit ?? limit }
|
|
1666
|
+
});
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
async getPageChildren(id) {
|
|
1670
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1671
|
+
return this.http.confluence(`${API3}/content/${id}/child/page`, {
|
|
1672
|
+
params: { expand: "version", start, limit }
|
|
1673
|
+
});
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
async getLabels(id) {
|
|
1677
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1678
|
+
return this.http.confluence(`${API3}/content/${id}/label`, {
|
|
1679
|
+
params: { start, limit }
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
async search(cql, opts = {}) {
|
|
1684
|
+
return this.http.confluence(`${API3}/content/search`, {
|
|
1685
|
+
params: {
|
|
1686
|
+
cql,
|
|
1687
|
+
start: opts.start ?? 0,
|
|
1688
|
+
limit: opts.limit ?? 25,
|
|
1689
|
+
...opts.expand ? { expand: opts.expand } : {}
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
async createPage(opts) {
|
|
1694
|
+
const body = {
|
|
1695
|
+
type: "page",
|
|
1696
|
+
title: opts.title,
|
|
1697
|
+
space: { key: opts.spaceKey },
|
|
1698
|
+
ancestors: opts.parentId ? [{ id: opts.parentId }] : [],
|
|
1699
|
+
body: {
|
|
1700
|
+
storage: {
|
|
1701
|
+
value: opts.body,
|
|
1702
|
+
representation: opts.representation ?? "storage"
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
return this.http.confluence(`${API3}/content`, {
|
|
1707
|
+
method: "POST",
|
|
1708
|
+
body
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
async updatePage(id, opts) {
|
|
1712
|
+
const body = {
|
|
1713
|
+
version: { number: opts.version },
|
|
1714
|
+
type: "page",
|
|
1715
|
+
title: opts.title,
|
|
1716
|
+
status: opts.status ?? "current"
|
|
1717
|
+
};
|
|
1718
|
+
if (opts.body !== void 0) {
|
|
1719
|
+
body.body = {
|
|
1720
|
+
storage: {
|
|
1721
|
+
value: opts.body,
|
|
1722
|
+
representation: opts.representation ?? "storage"
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
return this.http.confluence(`${API3}/content/${id}`, {
|
|
1727
|
+
method: "PUT",
|
|
1728
|
+
body
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
async deletePage(id) {
|
|
1732
|
+
return this.http.confluence(`${API3}/content/${id}`, { method: "DELETE" });
|
|
1733
|
+
}
|
|
1734
|
+
async listComments(pageId) {
|
|
1735
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1736
|
+
return this.http.confluence(`${API3}/content/${pageId}/child/comment`, {
|
|
1737
|
+
params: { expand: "body.storage,version", start, limit }
|
|
1738
|
+
});
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
async addComment(pageId, body, representation = "storage") {
|
|
1742
|
+
return this.http.confluence(`${API3}/content`, {
|
|
1743
|
+
method: "POST",
|
|
1744
|
+
body: {
|
|
1745
|
+
type: "comment",
|
|
1746
|
+
container: { id: pageId, type: "page" },
|
|
1747
|
+
body: {
|
|
1748
|
+
storage: { value: body, representation }
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
async addLabel(pageId, labels) {
|
|
1754
|
+
const body = labels.map((name) => ({ prefix: "global", name }));
|
|
1755
|
+
const result = await this.http.confluence(`${API3}/content/${pageId}/label`, {
|
|
1756
|
+
method: "POST",
|
|
1757
|
+
body
|
|
1758
|
+
});
|
|
1759
|
+
return result.results;
|
|
1760
|
+
}
|
|
1761
|
+
async removeLabel(pageId, label) {
|
|
1762
|
+
return this.http.confluence(`${API3}/content/${pageId}/label/${label}`, { method: "DELETE" });
|
|
1763
|
+
}
|
|
1764
|
+
async listSpaces(opts = {}) {
|
|
1765
|
+
const initialStart = opts.start ?? 0;
|
|
1766
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1767
|
+
return this.http.confluence(`${API3}/space`, {
|
|
1768
|
+
params: {
|
|
1769
|
+
start: initialStart + start,
|
|
1770
|
+
limit: opts.limit ?? limit,
|
|
1771
|
+
...opts.type ? { type: opts.type } : {}
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
async getSpace(key) {
|
|
1777
|
+
return this.http.confluence(`${API3}/space/${key}`, {
|
|
1778
|
+
params: { expand: "description.plain" }
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
async listAttachments(pageId) {
|
|
1782
|
+
return this.http.confluencePaginate(async (start, limit) => {
|
|
1783
|
+
return this.http.confluence(`${API3}/content/${pageId}/child/attachment`, {
|
|
1784
|
+
params: { start, limit }
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1469
1790
|
// src/services/confluence/commands.ts
|
|
1791
|
+
function getClient3(program2) {
|
|
1792
|
+
const opts = program2.optsWithGlobals();
|
|
1793
|
+
const config = loadConfig({ configPath: opts.config });
|
|
1794
|
+
if (!config.confluence.baseUrl) throw new PncliError("Confluence not configured. Run: pncli config init");
|
|
1795
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
1796
|
+
return new ConfluenceClient(http);
|
|
1797
|
+
}
|
|
1470
1798
|
function registerConfluenceCommands(program2) {
|
|
1471
|
-
program2.command("confluence").description("Confluence operations")
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1799
|
+
const confluence = program2.command("confluence").description("Confluence operations");
|
|
1800
|
+
confluence.command("get-page").description("Get a Confluence page by ID").requiredOption("--id <page-id>", "Page ID").option("--expand <fields>", "Comma-separated fields to expand", "body.storage,version,space,ancestors").action(async (opts) => {
|
|
1801
|
+
const start = Date.now();
|
|
1802
|
+
try {
|
|
1803
|
+
const client = getClient3(program2);
|
|
1804
|
+
const data = await client.getPage(opts.id, opts.expand);
|
|
1805
|
+
success(data, "confluence", "get-page", start);
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
fail(err, "confluence", "get-page", start);
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
confluence.command("get-page-by-title").description("Find a Confluence page by space key and title").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").action(async (opts) => {
|
|
1811
|
+
const start = Date.now();
|
|
1812
|
+
try {
|
|
1813
|
+
const client = getClient3(program2);
|
|
1814
|
+
const data = await client.getPageByTitle(opts.space, opts.title);
|
|
1815
|
+
if (!data) throw new PncliError(`Page not found: "${opts.title}" in space ${opts.space}`, 1);
|
|
1816
|
+
success(data, "confluence", "get-page-by-title", start);
|
|
1817
|
+
} catch (err) {
|
|
1818
|
+
fail(err, "confluence", "get-page-by-title", start);
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
confluence.command("list-pages").description("List pages in a Confluence space").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
|
|
1822
|
+
const start = Date.now();
|
|
1823
|
+
try {
|
|
1824
|
+
const client = getClient3(program2);
|
|
1825
|
+
const data = await client.listPages(opts.space, {
|
|
1826
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : void 0,
|
|
1827
|
+
start: opts.start ? parseInt(opts.start, 10) : void 0
|
|
1828
|
+
});
|
|
1829
|
+
success(data, "confluence", "list-pages", start);
|
|
1830
|
+
} catch (err) {
|
|
1831
|
+
fail(err, "confluence", "list-pages", start);
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
confluence.command("get-page-children").description("Get child pages of a Confluence page").requiredOption("--id <page-id>", "Parent page ID").action(async (opts) => {
|
|
1835
|
+
const start = Date.now();
|
|
1836
|
+
try {
|
|
1837
|
+
const client = getClient3(program2);
|
|
1838
|
+
const data = await client.getPageChildren(opts.id);
|
|
1839
|
+
success(data, "confluence", "get-page-children", start);
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
fail(err, "confluence", "get-page-children", start);
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
confluence.command("get-labels").description("Get labels on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
1845
|
+
const start = Date.now();
|
|
1846
|
+
try {
|
|
1847
|
+
const client = getClient3(program2);
|
|
1848
|
+
const data = await client.getLabels(opts.id);
|
|
1849
|
+
success(data, "confluence", "get-labels", start);
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
fail(err, "confluence", "get-labels", start);
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
confluence.command("search").description("Search Confluence with CQL").requiredOption("--cql <query>", 'CQL query string (e.g. "space=PROJ AND type=page")').option("--limit <n>", "Maximum number of results", "25").option("--start <n>", "Offset for first result", "0").option("--expand <fields>", "Comma-separated fields to expand").action(async (opts) => {
|
|
1855
|
+
const start = Date.now();
|
|
1856
|
+
try {
|
|
1857
|
+
const client = getClient3(program2);
|
|
1858
|
+
const data = await client.search(opts.cql, {
|
|
1859
|
+
limit: parseInt(opts.limit, 10),
|
|
1860
|
+
start: parseInt(opts.start, 10),
|
|
1861
|
+
expand: opts.expand
|
|
1862
|
+
});
|
|
1863
|
+
success(data, "confluence", "search", start);
|
|
1864
|
+
} catch (err) {
|
|
1865
|
+
fail(err, "confluence", "search", start);
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
confluence.command("create-page").description("Create a new Confluence page").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").requiredOption("--body <html>", "Page body (storage format HTML)").option("--parent-id <id>", "Parent page ID (to nest under a page)").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
1869
|
+
const start = Date.now();
|
|
1870
|
+
try {
|
|
1871
|
+
const client = getClient3(program2);
|
|
1872
|
+
const data = await client.createPage({
|
|
1873
|
+
spaceKey: opts.space,
|
|
1874
|
+
title: opts.title,
|
|
1875
|
+
body: opts.body,
|
|
1876
|
+
parentId: opts.parentId,
|
|
1877
|
+
representation: opts.representation
|
|
1878
|
+
});
|
|
1879
|
+
success(data, "confluence", "create-page", start);
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
fail(err, "confluence", "create-page", start);
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
confluence.command("update-page").description("Update a Confluence page (fetches current version automatically)").requiredOption("--id <page-id>", "Page ID").option("--title <title>", "New page title").option("--body <html>", "New page body (storage format HTML)").option("--status <status>", "Page status: current (default) or draft", "current").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
1885
|
+
const start = Date.now();
|
|
1886
|
+
try {
|
|
1887
|
+
const client = getClient3(program2);
|
|
1888
|
+
const current = await client.getPage(opts.id, "version");
|
|
1889
|
+
const nextVersion = current.version.number + 1;
|
|
1890
|
+
const data = await client.updatePage(opts.id, {
|
|
1891
|
+
version: nextVersion,
|
|
1892
|
+
title: opts.title ?? current.title,
|
|
1893
|
+
body: opts.body,
|
|
1894
|
+
status: opts.status,
|
|
1895
|
+
representation: opts.representation
|
|
1896
|
+
});
|
|
1897
|
+
success(data, "confluence", "update-page", start);
|
|
1898
|
+
} catch (err) {
|
|
1899
|
+
fail(err, "confluence", "update-page", start);
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
confluence.command("delete-page").description("Delete a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
1903
|
+
const start = Date.now();
|
|
1904
|
+
try {
|
|
1905
|
+
const client = getClient3(program2);
|
|
1906
|
+
await client.deletePage(opts.id);
|
|
1907
|
+
success({ deleted: opts.id }, "confluence", "delete-page", start);
|
|
1908
|
+
} catch (err) {
|
|
1909
|
+
fail(err, "confluence", "delete-page", start);
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
confluence.command("list-comments").description("List comments on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
1913
|
+
const start = Date.now();
|
|
1914
|
+
try {
|
|
1915
|
+
const client = getClient3(program2);
|
|
1916
|
+
const data = await client.listComments(opts.id);
|
|
1917
|
+
success(data, "confluence", "list-comments", start);
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
fail(err, "confluence", "list-comments", start);
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
confluence.command("add-comment").description("Add a comment to a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--body <text>", "Comment body (storage format HTML)").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
1923
|
+
const start = Date.now();
|
|
1924
|
+
try {
|
|
1925
|
+
const client = getClient3(program2);
|
|
1926
|
+
const data = await client.addComment(opts.id, opts.body, opts.representation);
|
|
1927
|
+
success(data, "confluence", "add-comment", start);
|
|
1928
|
+
} catch (err) {
|
|
1929
|
+
fail(err, "confluence", "add-comment", start);
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
confluence.command("add-label").description("Add labels to a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--labels <names>", "Comma-separated label names").action(async (opts) => {
|
|
1933
|
+
const start = Date.now();
|
|
1934
|
+
try {
|
|
1935
|
+
const client = getClient3(program2);
|
|
1936
|
+
const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1937
|
+
const data = await client.addLabel(opts.id, labels);
|
|
1938
|
+
success(data, "confluence", "add-label", start);
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
fail(err, "confluence", "add-label", start);
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
confluence.command("remove-label").description("Remove a label from a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--label <name>", "Label name to remove").action(async (opts) => {
|
|
1944
|
+
const start = Date.now();
|
|
1945
|
+
try {
|
|
1946
|
+
const client = getClient3(program2);
|
|
1947
|
+
await client.removeLabel(opts.id, opts.label);
|
|
1948
|
+
success({ removed: opts.label, from: opts.id }, "confluence", "remove-label", start);
|
|
1949
|
+
} catch (err) {
|
|
1950
|
+
fail(err, "confluence", "remove-label", start);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max results (default: all)").action(async (opts) => {
|
|
1954
|
+
const start = Date.now();
|
|
1955
|
+
try {
|
|
1956
|
+
const client = getClient3(program2);
|
|
1957
|
+
const data = await client.listSpaces({
|
|
1958
|
+
type: opts.type,
|
|
1959
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : void 0
|
|
1960
|
+
});
|
|
1961
|
+
success(data, "confluence", "list-spaces", start);
|
|
1962
|
+
} catch (err) {
|
|
1963
|
+
fail(err, "confluence", "list-spaces", start);
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
confluence.command("get-space").description("Get a Confluence space by key").requiredOption("--key <space-key>", "Space key").action(async (opts) => {
|
|
1967
|
+
const start = Date.now();
|
|
1968
|
+
try {
|
|
1969
|
+
const client = getClient3(program2);
|
|
1970
|
+
const data = await client.getSpace(opts.key);
|
|
1971
|
+
success(data, "confluence", "get-space", start);
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
fail(err, "confluence", "get-space", start);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
confluence.command("list-attachments").description("List attachments on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
1977
|
+
const start = Date.now();
|
|
1978
|
+
try {
|
|
1979
|
+
const client = getClient3(program2);
|
|
1980
|
+
const data = await client.listAttachments(opts.id);
|
|
1981
|
+
success(data, "confluence", "list-attachments", start);
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
fail(err, "confluence", "list-attachments", start);
|
|
1984
|
+
}
|
|
1478
1985
|
});
|
|
1479
1986
|
}
|
|
1480
1987
|
|
|
1988
|
+
// src/services/sonar/client.ts
|
|
1989
|
+
var API4 = "/api";
|
|
1990
|
+
var SonarClient = class {
|
|
1991
|
+
constructor(http) {
|
|
1992
|
+
this.http = http;
|
|
1993
|
+
}
|
|
1994
|
+
http;
|
|
1995
|
+
async getStatus() {
|
|
1996
|
+
return this.http.sonar(`${API4}/system/status`);
|
|
1997
|
+
}
|
|
1998
|
+
async getQualityGate(projectKey, branch) {
|
|
1999
|
+
return this.http.sonar(`${API4}/qualitygates/project_status`, {
|
|
2000
|
+
params: { projectKey, ...branch ? { branch } : {} }
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
async searchIssues(opts) {
|
|
2004
|
+
return this.http.sonar(`${API4}/issues/search`, {
|
|
2005
|
+
params: {
|
|
2006
|
+
componentKeys: opts.projectKey,
|
|
2007
|
+
severities: opts.severities,
|
|
2008
|
+
types: opts.types,
|
|
2009
|
+
statuses: opts.statuses,
|
|
2010
|
+
branch: opts.branch,
|
|
2011
|
+
resolved: opts.resolved !== void 0 ? String(opts.resolved) : void 0,
|
|
2012
|
+
p: opts.page ?? 1,
|
|
2013
|
+
ps: opts.pageSize ?? 100
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
async searchAllIssues(opts) {
|
|
2018
|
+
return this.http.sonarPaginate(async (page, pageSize) => {
|
|
2019
|
+
const resp = await this.http.sonar(`${API4}/issues/search`, {
|
|
2020
|
+
params: {
|
|
2021
|
+
componentKeys: opts.projectKey,
|
|
2022
|
+
severities: opts.severities,
|
|
2023
|
+
types: opts.types,
|
|
2024
|
+
statuses: opts.statuses,
|
|
2025
|
+
branch: opts.branch,
|
|
2026
|
+
resolved: opts.resolved !== void 0 ? String(opts.resolved) : void 0,
|
|
2027
|
+
p: page,
|
|
2028
|
+
ps: pageSize
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
return { total: resp.total, p: resp.p, ps: resp.ps, items: resp.issues };
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
async getMeasures(opts) {
|
|
2035
|
+
return this.http.sonar(`${API4}/measures/component`, {
|
|
2036
|
+
params: {
|
|
2037
|
+
component: opts.projectKey,
|
|
2038
|
+
metricKeys: opts.metricKeys,
|
|
2039
|
+
...opts.branch ? { branch: opts.branch } : {}
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
async searchProjects(opts = {}) {
|
|
2044
|
+
return this.http.sonar(`${API4}/projects/search`, {
|
|
2045
|
+
params: {
|
|
2046
|
+
q: opts.query,
|
|
2047
|
+
p: opts.page ?? 1,
|
|
2048
|
+
ps: opts.pageSize ?? 100
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
async searchAllProjects(query) {
|
|
2053
|
+
return this.http.sonarPaginate(async (page, pageSize) => {
|
|
2054
|
+
const resp = await this.http.sonar(`${API4}/projects/search`, {
|
|
2055
|
+
params: { q: query, p: page, ps: pageSize }
|
|
2056
|
+
});
|
|
2057
|
+
return { total: resp.paging.total, p: resp.paging.pageIndex, ps: resp.paging.pageSize, items: resp.components };
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
async searchHotspots(opts) {
|
|
2061
|
+
return this.http.sonar(`${API4}/hotspots/search`, {
|
|
2062
|
+
params: {
|
|
2063
|
+
projectKey: opts.projectKey,
|
|
2064
|
+
status: opts.status,
|
|
2065
|
+
resolution: opts.resolution,
|
|
2066
|
+
branch: opts.branch,
|
|
2067
|
+
p: opts.page ?? 1,
|
|
2068
|
+
ps: opts.pageSize ?? 100
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
async searchAllHotspots(opts) {
|
|
2073
|
+
return this.http.sonarPaginate(async (page, pageSize) => {
|
|
2074
|
+
const resp = await this.http.sonar(`${API4}/hotspots/search`, {
|
|
2075
|
+
params: {
|
|
2076
|
+
projectKey: opts.projectKey,
|
|
2077
|
+
status: opts.status,
|
|
2078
|
+
resolution: opts.resolution,
|
|
2079
|
+
branch: opts.branch,
|
|
2080
|
+
p: page,
|
|
2081
|
+
ps: pageSize
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
return { total: resp.paging.total, p: resp.paging.pageIndex, ps: resp.paging.pageSize, items: resp.hotspots };
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
|
|
1481
2089
|
// src/services/sonar/commands.ts
|
|
2090
|
+
var DEFAULT_METRICS = "coverage,duplicated_lines_density,bugs,vulnerabilities,code_smells,sqale_rating,reliability_rating,security_rating,ncloc";
|
|
2091
|
+
function getClient4(program2) {
|
|
2092
|
+
const opts = program2.optsWithGlobals();
|
|
2093
|
+
const config = loadConfig({ configPath: opts.config });
|
|
2094
|
+
if (!config.sonar.baseUrl) throw new PncliError("SonarQube not configured. Run: pncli config init");
|
|
2095
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
2096
|
+
return { client: new SonarClient(http), config };
|
|
2097
|
+
}
|
|
2098
|
+
function resolveProject(config, cliProject) {
|
|
2099
|
+
const project = cliProject ?? config.defaults.sonar.project;
|
|
2100
|
+
if (!project) throw new PncliError("No project specified. Use --project or set defaults.sonar.project in config.");
|
|
2101
|
+
return project;
|
|
2102
|
+
}
|
|
1482
2103
|
function registerSonarCommands(program2) {
|
|
1483
|
-
program2.command("sonar").description("SonarQube operations")
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
2104
|
+
const sonar = program2.command("sonar").description("SonarQube Server operations");
|
|
2105
|
+
sonar.command("quality-gate").description("Get quality gate status for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--branch <name>", "Branch name").action(async (opts) => {
|
|
2106
|
+
const start = Date.now();
|
|
2107
|
+
try {
|
|
2108
|
+
const { client, config } = getClient4(program2);
|
|
2109
|
+
const projectKey = resolveProject(config, opts.project);
|
|
2110
|
+
const data = await client.getQualityGate(projectKey, opts.branch);
|
|
2111
|
+
success(data, "sonar", "quality-gate", start);
|
|
2112
|
+
} catch (err) {
|
|
2113
|
+
fail(err, "sonar", "quality-gate", start);
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
sonar.command("issues").description("Search/list issues for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--severities <list>", "Filter by severity: BLOCKER,CRITICAL,MAJOR,MINOR,INFO (comma-separated)").option("--types <list>", "Filter by type: BUG,VULNERABILITY,CODE_SMELL (comma-separated)").option("--statuses <list>", "Filter by status: OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED (comma-separated)").option("--branch <name>", "Branch name").option("--resolved <bool>", "Filter resolved issues: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page (max 500)", "100").option("--all", "Fetch all pages (ignores --page/--page-size)").action(async (opts) => {
|
|
2117
|
+
const start = Date.now();
|
|
2118
|
+
try {
|
|
2119
|
+
const { client, config } = getClient4(program2);
|
|
2120
|
+
const projectKey = resolveProject(config, opts.project);
|
|
2121
|
+
const baseOpts = {
|
|
2122
|
+
projectKey,
|
|
2123
|
+
severities: opts.severities,
|
|
2124
|
+
types: opts.types,
|
|
2125
|
+
statuses: opts.statuses,
|
|
2126
|
+
branch: opts.branch,
|
|
2127
|
+
resolved: opts.resolved !== void 0 ? opts.resolved === "true" : void 0
|
|
2128
|
+
};
|
|
2129
|
+
if (opts.all) {
|
|
2130
|
+
const data = await client.searchAllIssues(baseOpts);
|
|
2131
|
+
success(data, "sonar", "issues", start);
|
|
2132
|
+
} else {
|
|
2133
|
+
const data = await client.searchIssues({
|
|
2134
|
+
...baseOpts,
|
|
2135
|
+
page: parseInt(opts.page, 10),
|
|
2136
|
+
pageSize: parseInt(opts.pageSize, 10)
|
|
2137
|
+
});
|
|
2138
|
+
success(data, "sonar", "issues", start);
|
|
2139
|
+
}
|
|
2140
|
+
} catch (err) {
|
|
2141
|
+
fail(err, "sonar", "issues", start);
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
sonar.command("measures").description("Get key metrics for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--metrics <list>", "Comma-separated metric keys", DEFAULT_METRICS).option("--branch <name>", "Branch name").action(async (opts) => {
|
|
2145
|
+
const start = Date.now();
|
|
2146
|
+
try {
|
|
2147
|
+
const { client, config } = getClient4(program2);
|
|
2148
|
+
const projectKey = resolveProject(config, opts.project);
|
|
2149
|
+
const data = await client.getMeasures({
|
|
2150
|
+
projectKey,
|
|
2151
|
+
metricKeys: opts.metrics,
|
|
2152
|
+
branch: opts.branch
|
|
2153
|
+
});
|
|
2154
|
+
success(data, "sonar", "measures", start);
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
fail(err, "sonar", "measures", start);
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
sonar.command("projects").description("Search/list projects").option("--query <text>", "Search query").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2160
|
+
const start = Date.now();
|
|
2161
|
+
try {
|
|
2162
|
+
const { client } = getClient4(program2);
|
|
2163
|
+
if (opts.all) {
|
|
2164
|
+
const data = await client.searchAllProjects(opts.query);
|
|
2165
|
+
success(data, "sonar", "projects", start);
|
|
2166
|
+
} else {
|
|
2167
|
+
const data = await client.searchProjects({
|
|
2168
|
+
query: opts.query,
|
|
2169
|
+
page: parseInt(opts.page, 10),
|
|
2170
|
+
pageSize: parseInt(opts.pageSize, 10)
|
|
2171
|
+
});
|
|
2172
|
+
success(data, "sonar", "projects", start);
|
|
2173
|
+
}
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
fail(err, "sonar", "projects", start);
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
sonar.command("hotspots").description("List security hotspots for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--status <status>", "Filter: TO_REVIEW or REVIEWED").option("--resolution <list>", "Filter: FIXED,SAFE,ACKNOWLEDGED (comma-separated)").option("--branch <name>", "Branch name").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2179
|
+
const start = Date.now();
|
|
2180
|
+
try {
|
|
2181
|
+
const { client, config } = getClient4(program2);
|
|
2182
|
+
const projectKey = resolveProject(config, opts.project);
|
|
2183
|
+
const baseOpts = {
|
|
2184
|
+
projectKey,
|
|
2185
|
+
status: opts.status,
|
|
2186
|
+
resolution: opts.resolution,
|
|
2187
|
+
branch: opts.branch
|
|
2188
|
+
};
|
|
2189
|
+
if (opts.all) {
|
|
2190
|
+
const data = await client.searchAllHotspots(baseOpts);
|
|
2191
|
+
success(data, "sonar", "hotspots", start);
|
|
2192
|
+
} else {
|
|
2193
|
+
const data = await client.searchHotspots({
|
|
2194
|
+
...baseOpts,
|
|
2195
|
+
page: parseInt(opts.page, 10),
|
|
2196
|
+
pageSize: parseInt(opts.pageSize, 10)
|
|
2197
|
+
});
|
|
2198
|
+
success(data, "sonar", "hotspots", start);
|
|
2199
|
+
}
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
fail(err, "sonar", "hotspots", start);
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/services/sde/client.ts
|
|
2207
|
+
var API5 = "/api/v2";
|
|
2208
|
+
function projectParams(opts, page, pageSize) {
|
|
2209
|
+
return {
|
|
2210
|
+
name: opts.name,
|
|
2211
|
+
slug: opts.slug,
|
|
2212
|
+
search: opts.search,
|
|
2213
|
+
active: opts.active,
|
|
2214
|
+
ordering: opts.ordering,
|
|
2215
|
+
expand: opts.expand,
|
|
2216
|
+
include: opts.include,
|
|
2217
|
+
page,
|
|
2218
|
+
page_size: pageSize
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function taskParams(opts, page, pageSize) {
|
|
2222
|
+
return {
|
|
2223
|
+
phase: opts.phase,
|
|
2224
|
+
priority: opts.priority,
|
|
2225
|
+
status: opts.status,
|
|
2226
|
+
assigned_to: opts.assignedTo,
|
|
2227
|
+
source: opts.source,
|
|
2228
|
+
verification: opts.verification,
|
|
2229
|
+
tag: opts.tag,
|
|
2230
|
+
accepted: opts.accepted,
|
|
2231
|
+
relevant: opts.relevant,
|
|
2232
|
+
expand: opts.expand,
|
|
2233
|
+
include: opts.include,
|
|
2234
|
+
page,
|
|
2235
|
+
page_size: pageSize
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function threatParams(opts, page, pageSize) {
|
|
2239
|
+
return {
|
|
2240
|
+
severity: opts.severity,
|
|
2241
|
+
title: opts.title,
|
|
2242
|
+
threat_id: opts.threatId,
|
|
2243
|
+
capec_id: opts.capecId,
|
|
2244
|
+
component_id: opts.componentId,
|
|
2245
|
+
search: opts.search,
|
|
2246
|
+
ordering: opts.ordering,
|
|
2247
|
+
page,
|
|
2248
|
+
page_size: pageSize
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function userParams(opts, page, pageSize) {
|
|
2252
|
+
return {
|
|
2253
|
+
email: opts.email,
|
|
2254
|
+
first_name: opts.firstName,
|
|
2255
|
+
last_name: opts.lastName,
|
|
2256
|
+
is_active: opts.isActive,
|
|
2257
|
+
page,
|
|
2258
|
+
page_size: pageSize
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
var SdeClient = class {
|
|
2262
|
+
constructor(http) {
|
|
2263
|
+
this.http = http;
|
|
2264
|
+
}
|
|
2265
|
+
http;
|
|
2266
|
+
async getServerInfo() {
|
|
2267
|
+
return this.http.sde(`${API5}/server-info/`);
|
|
2268
|
+
}
|
|
2269
|
+
async getMe() {
|
|
2270
|
+
return this.http.sde(`${API5}/users/me/`);
|
|
2271
|
+
}
|
|
2272
|
+
async getProject(projectId, expand, include) {
|
|
2273
|
+
return this.http.sde(`${API5}/projects/${projectId}/`, {
|
|
2274
|
+
params: { expand, include }
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
async getTask(projectId, taskId, expand, include) {
|
|
2278
|
+
return this.http.sde(`${API5}/projects/${projectId}/tasks/${taskId}/`, {
|
|
2279
|
+
params: { expand, include }
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
async listProjects(opts = {}) {
|
|
2283
|
+
return this.http.sde(`${API5}/projects/`, {
|
|
2284
|
+
params: projectParams(opts, opts.page ?? 1, opts.pageSize ?? 100)
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
async listAllProjects(opts = {}) {
|
|
2288
|
+
return this.http.sdePaginate(async (page, pageSize) => {
|
|
2289
|
+
const resp = await this.http.sde(`${API5}/projects/`, {
|
|
2290
|
+
params: projectParams(opts, page, pageSize)
|
|
2291
|
+
});
|
|
2292
|
+
return { count: resp.count, results: resp.results };
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
async listTasks(opts) {
|
|
2296
|
+
return this.http.sde(`${API5}/projects/${opts.projectId}/tasks/`, {
|
|
2297
|
+
params: taskParams(opts, opts.page ?? 1, opts.pageSize ?? 100)
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
async listAllTasks(opts) {
|
|
2301
|
+
return this.http.sdePaginate(async (page, pageSize) => {
|
|
2302
|
+
const resp = await this.http.sde(`${API5}/projects/${opts.projectId}/tasks/`, {
|
|
2303
|
+
params: taskParams(opts, page, pageSize)
|
|
2304
|
+
});
|
|
2305
|
+
return { count: resp.count, results: resp.results };
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
async listThreats(opts) {
|
|
2309
|
+
return this.http.sde(`${API5}/projects/${opts.projectId}/threats/`, {
|
|
2310
|
+
params: threatParams(opts, opts.page ?? 1, opts.pageSize ?? 100)
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
async listAllThreats(opts) {
|
|
2314
|
+
return this.http.sdePaginate(async (page, pageSize) => {
|
|
2315
|
+
const resp = await this.http.sde(`${API5}/projects/${opts.projectId}/threats/`, {
|
|
2316
|
+
params: threatParams(opts, page, pageSize)
|
|
2317
|
+
});
|
|
2318
|
+
return { count: resp.count, results: resp.results };
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
async listUsers(opts = {}) {
|
|
2322
|
+
return this.http.sde(`${API5}/users/`, {
|
|
2323
|
+
params: userParams(opts, opts.page ?? 1, opts.pageSize ?? 100)
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
async listAllUsers(opts = {}) {
|
|
2327
|
+
return this.http.sdePaginate(async (page, pageSize) => {
|
|
2328
|
+
const resp = await this.http.sde(`${API5}/users/`, {
|
|
2329
|
+
params: userParams(opts, page, pageSize)
|
|
2330
|
+
});
|
|
2331
|
+
return { count: resp.count, results: resp.results };
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
// src/services/sde/commands.ts
|
|
2337
|
+
function getClient5(program2) {
|
|
2338
|
+
const opts = program2.optsWithGlobals();
|
|
2339
|
+
const config = loadConfig({ configPath: opts.config });
|
|
2340
|
+
if (!config.sde.baseUrl) throw new PncliError("SDElements not configured. Run: pncli config init");
|
|
2341
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
2342
|
+
return { client: new SdeClient(http), config };
|
|
2343
|
+
}
|
|
2344
|
+
function resolveProject2(config, cliProject) {
|
|
2345
|
+
const project = cliProject ?? config.defaults.sde.project;
|
|
2346
|
+
if (!project) throw new PncliError("No project specified. Use --project or set defaults.sde.project in config.");
|
|
2347
|
+
if (!/^\d+$/.test(project)) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
2348
|
+
const id = parseInt(project, 10);
|
|
2349
|
+
if (id <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
2350
|
+
return id;
|
|
2351
|
+
}
|
|
2352
|
+
function parsePage(val, flag) {
|
|
2353
|
+
const n = parseInt(val, 10);
|
|
2354
|
+
if (isNaN(n) || n < 1) throw new PncliError(`Invalid ${flag}: "${val}". Must be a positive integer.`);
|
|
2355
|
+
return n;
|
|
2356
|
+
}
|
|
2357
|
+
function validateActive(val, allowed) {
|
|
2358
|
+
if (val === void 0) return void 0;
|
|
2359
|
+
if (!allowed.includes(val)) {
|
|
2360
|
+
throw new PncliError(`Invalid --active value: "${val}". Allowed: ${allowed.join(", ")}.`);
|
|
2361
|
+
}
|
|
2362
|
+
return val;
|
|
2363
|
+
}
|
|
2364
|
+
function registerSdeCommands(program2) {
|
|
2365
|
+
const sde = program2.command("sde").description("SDElements operations (threat modeling, countermeasures, compliance)");
|
|
2366
|
+
sde.command("server-info").description("Get server version and platform info (requires super-user)").action(async () => {
|
|
2367
|
+
const start = Date.now();
|
|
2368
|
+
try {
|
|
2369
|
+
const { client } = getClient5(program2);
|
|
2370
|
+
const data = await client.getServerInfo();
|
|
2371
|
+
success(data, "sde", "server-info", start);
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
fail(err, "sde", "server-info", start);
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
sde.command("whoami").description("Get current authenticated user").action(async () => {
|
|
2377
|
+
const start = Date.now();
|
|
2378
|
+
try {
|
|
2379
|
+
const { client } = getClient5(program2);
|
|
2380
|
+
const data = await client.getMe();
|
|
2381
|
+
success(data, "sde", "whoami", start);
|
|
2382
|
+
} catch (err) {
|
|
2383
|
+
fail(err, "sde", "whoami", start);
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
sde.command("users").description("List users").option("--email <email>", "Filter by email address").option("--first-name <name>", "Filter by first name").option("--last-name <name>", "Filter by last name").option("--active <bool>", "Filter by active status: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2387
|
+
const start = Date.now();
|
|
2388
|
+
try {
|
|
2389
|
+
const { client } = getClient5(program2);
|
|
2390
|
+
const baseOpts = {
|
|
2391
|
+
email: opts.email,
|
|
2392
|
+
firstName: opts.firstName,
|
|
2393
|
+
lastName: opts.lastName,
|
|
2394
|
+
isActive: validateActive(opts.active, ["true", "false"])
|
|
2395
|
+
};
|
|
2396
|
+
if (opts.all) {
|
|
2397
|
+
const data = await client.listAllUsers(baseOpts);
|
|
2398
|
+
success(data, "sde", "users", start);
|
|
2399
|
+
} else {
|
|
2400
|
+
const data = await client.listUsers({
|
|
2401
|
+
...baseOpts,
|
|
2402
|
+
page: parsePage(opts.page, "--page"),
|
|
2403
|
+
pageSize: parsePage(opts.pageSize, "--page-size")
|
|
2404
|
+
});
|
|
2405
|
+
success(data, "sde", "users", start);
|
|
2406
|
+
}
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
fail(err, "sde", "users", start);
|
|
2409
|
+
}
|
|
2410
|
+
});
|
|
2411
|
+
sde.command("projects").description("List projects").option("--name <name>", "Filter by project name").option("--search <text>", "Text search on name and profile").option("--active <val>", "Filter by active status: true, false, or all").option("--ordering <field>", "Sort by: name, created, updated (prefix with - for descending)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2412
|
+
const start = Date.now();
|
|
2413
|
+
try {
|
|
2414
|
+
const { client } = getClient5(program2);
|
|
2415
|
+
const baseOpts = {
|
|
2416
|
+
name: opts.name,
|
|
2417
|
+
search: opts.search,
|
|
2418
|
+
active: validateActive(opts.active, ["true", "false", "all"]),
|
|
2419
|
+
ordering: opts.ordering,
|
|
2420
|
+
expand: opts.expand,
|
|
2421
|
+
include: opts.include
|
|
2422
|
+
};
|
|
2423
|
+
if (opts.all) {
|
|
2424
|
+
const data = await client.listAllProjects(baseOpts);
|
|
2425
|
+
success(data, "sde", "projects", start);
|
|
2426
|
+
} else {
|
|
2427
|
+
const data = await client.listProjects({
|
|
2428
|
+
...baseOpts,
|
|
2429
|
+
page: parsePage(opts.page, "--page"),
|
|
2430
|
+
pageSize: parsePage(opts.pageSize, "--page-size")
|
|
2431
|
+
});
|
|
2432
|
+
success(data, "sde", "projects", start);
|
|
2433
|
+
}
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
fail(err, "sde", "projects", start);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
sde.command("project").description("Get a single project by ID").option("--id <id>", "Project ID (or set defaults.sde.project in config)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").action(async (opts) => {
|
|
2439
|
+
const start = Date.now();
|
|
2440
|
+
try {
|
|
2441
|
+
const { client, config } = getClient5(program2);
|
|
2442
|
+
const projectId = resolveProject2(config, opts.id);
|
|
2443
|
+
const data = await client.getProject(projectId, opts.expand, opts.include);
|
|
2444
|
+
success(data, "sde", "project", start);
|
|
2445
|
+
} catch (err) {
|
|
2446
|
+
fail(err, "sde", "project", start);
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
sde.command("tasks").description("List countermeasures for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--phase <slug>", "Filter by phase slug (e.g. development, architecture-design)").option("--priority <n>", "Filter by priority (1-10)").option("--status <id>", "Filter by status ID (e.g. TS1, TS2)").option("--assigned-to <email>", "Filter by assignee email").option("--source <val>", "Filter by source: default, custom, manual, project").option("--verification <val>", "Filter by verification: pass, fail, partial, none").option("--tag <name>", "Filter by tag name").option("--accepted <bool>", "Filter by accepted status: true or false").option("--relevant <bool>", "Filter by relevant status: true or false").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references,regulation_sections").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2450
|
+
const start = Date.now();
|
|
2451
|
+
try {
|
|
2452
|
+
const { client, config } = getClient5(program2);
|
|
2453
|
+
const projectId = resolveProject2(config, opts.project);
|
|
2454
|
+
const baseOpts = {
|
|
2455
|
+
projectId,
|
|
2456
|
+
phase: opts.phase,
|
|
2457
|
+
priority: opts.priority,
|
|
2458
|
+
status: opts.status,
|
|
2459
|
+
assignedTo: opts.assignedTo,
|
|
2460
|
+
source: opts.source,
|
|
2461
|
+
verification: opts.verification,
|
|
2462
|
+
tag: opts.tag,
|
|
2463
|
+
accepted: opts.accepted,
|
|
2464
|
+
relevant: opts.relevant,
|
|
2465
|
+
expand: opts.expand,
|
|
2466
|
+
include: opts.include
|
|
2467
|
+
};
|
|
2468
|
+
if (opts.all) {
|
|
2469
|
+
const data = await client.listAllTasks(baseOpts);
|
|
2470
|
+
success(data, "sde", "tasks", start);
|
|
2471
|
+
} else {
|
|
2472
|
+
const data = await client.listTasks({
|
|
2473
|
+
...baseOpts,
|
|
2474
|
+
page: parsePage(opts.page, "--page"),
|
|
2475
|
+
pageSize: parsePage(opts.pageSize, "--page-size")
|
|
2476
|
+
});
|
|
2477
|
+
success(data, "sde", "tasks", start);
|
|
2478
|
+
}
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
fail(err, "sde", "tasks", start);
|
|
2481
|
+
}
|
|
2482
|
+
});
|
|
2483
|
+
sde.command("task").description("Get a single countermeasure").option("--project <id>", "Project ID (or set defaults.sde.project in config)").requiredOption("--task <id>", "Task ID (e.g. T21)").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references").action(async (opts) => {
|
|
2484
|
+
const start = Date.now();
|
|
2485
|
+
try {
|
|
2486
|
+
const { client, config } = getClient5(program2);
|
|
2487
|
+
const projectId = resolveProject2(config, opts.project);
|
|
2488
|
+
const data = await client.getTask(projectId, opts.task, opts.expand, opts.include);
|
|
2489
|
+
success(data, "sde", "task", start);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
fail(err, "sde", "task", start);
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2494
|
+
sde.command("threats").description("List threats for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--severity <n>", "Filter by severity (1-10)").option("--search <text>", "Full-text search on title and threat ID").option("--ordering <field>", "Sort by: threat__severity, threat_id, status (prefix - for descending)").option("--capec-id <id>", "Filter by CAPEC attack pattern ID").option("--component-id <id>", "Filter by component ID").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2495
|
+
const start = Date.now();
|
|
2496
|
+
try {
|
|
2497
|
+
const { client, config } = getClient5(program2);
|
|
2498
|
+
const projectId = resolveProject2(config, opts.project);
|
|
2499
|
+
const baseOpts = {
|
|
2500
|
+
projectId,
|
|
2501
|
+
severity: opts.severity,
|
|
2502
|
+
search: opts.search,
|
|
2503
|
+
ordering: opts.ordering,
|
|
2504
|
+
capecId: opts.capecId,
|
|
2505
|
+
componentId: opts.componentId
|
|
2506
|
+
};
|
|
2507
|
+
if (opts.all) {
|
|
2508
|
+
const data = await client.listAllThreats(baseOpts);
|
|
2509
|
+
success(data, "sde", "threats", start);
|
|
2510
|
+
} else {
|
|
2511
|
+
const data = await client.listThreats({
|
|
2512
|
+
...baseOpts,
|
|
2513
|
+
page: parsePage(opts.page, "--page"),
|
|
2514
|
+
pageSize: parsePage(opts.pageSize, "--page-size")
|
|
2515
|
+
});
|
|
2516
|
+
success(data, "sde", "threats", start);
|
|
2517
|
+
}
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
fail(err, "sde", "threats", start);
|
|
2520
|
+
}
|
|
1490
2521
|
});
|
|
1491
2522
|
}
|
|
1492
2523
|
|
|
1493
2524
|
// src/services/deps/parsers/index.ts
|
|
1494
|
-
import
|
|
2525
|
+
import fs4 from "fs";
|
|
1495
2526
|
import path5 from "path";
|
|
1496
2527
|
import { execSync as execSync4, execFileSync as execFileSync2 } from "child_process";
|
|
1497
2528
|
|
|
@@ -1997,7 +3028,8 @@ function getRepoFiles(repoRoot) {
|
|
|
1997
3028
|
try {
|
|
1998
3029
|
const out = execSync4("git ls-files --cached --others --exclude-standard", {
|
|
1999
3030
|
encoding: "utf8",
|
|
2000
|
-
cwd: repoRoot
|
|
3031
|
+
cwd: repoRoot,
|
|
3032
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2001
3033
|
});
|
|
2002
3034
|
return out.trim().split("\n").filter(Boolean);
|
|
2003
3035
|
} catch {
|
|
@@ -2006,7 +3038,7 @@ function getRepoFiles(repoRoot) {
|
|
|
2006
3038
|
}
|
|
2007
3039
|
function readFile(repoRoot, relPath) {
|
|
2008
3040
|
try {
|
|
2009
|
-
return
|
|
3041
|
+
return fs4.readFileSync(path5.join(repoRoot, relPath), "utf8");
|
|
2010
3042
|
} catch {
|
|
2011
3043
|
return null;
|
|
2012
3044
|
}
|
|
@@ -2016,7 +3048,8 @@ function readFileAtRef(repoRoot, ref, relPath) {
|
|
|
2016
3048
|
return execFileSync2("git", ["show", `${ref}:${relPath}`], {
|
|
2017
3049
|
encoding: "utf8",
|
|
2018
3050
|
cwd: repoRoot,
|
|
2019
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3051
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3052
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2020
3053
|
});
|
|
2021
3054
|
} catch {
|
|
2022
3055
|
return null;
|
|
@@ -2076,7 +3109,8 @@ function scanRepoAtRef(repoRoot, ref, opts = {}) {
|
|
|
2076
3109
|
const out = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref], {
|
|
2077
3110
|
encoding: "utf8",
|
|
2078
3111
|
cwd: repoRoot,
|
|
2079
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3112
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3113
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2080
3114
|
});
|
|
2081
3115
|
files = out.trim().split("\n").filter(Boolean);
|
|
2082
3116
|
} catch {
|
|
@@ -2809,7 +3843,7 @@ function registerDepsCommands(program2) {
|
|
|
2809
3843
|
|
|
2810
3844
|
// src/services/config/commands.ts
|
|
2811
3845
|
import { input, password, confirm } from "@inquirer/prompts";
|
|
2812
|
-
import
|
|
3846
|
+
import fs5 from "fs";
|
|
2813
3847
|
function registerConfigCommands(program2) {
|
|
2814
3848
|
const config = program2.command("config").description("Manage pncli configuration");
|
|
2815
3849
|
config.command("init").description("Interactive setup wizard").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
|
|
@@ -2823,7 +3857,8 @@ function registerConfigCommands(program2) {
|
|
|
2823
3857
|
} catch (err) {
|
|
2824
3858
|
if (err instanceof Error && err.message.includes("User force closed")) {
|
|
2825
3859
|
process.stderr.write("\nSetup cancelled.\n");
|
|
2826
|
-
process.
|
|
3860
|
+
process.exitCode = ExitCode.GENERAL_ERROR;
|
|
3861
|
+
return;
|
|
2827
3862
|
}
|
|
2828
3863
|
fail(err, "config", "init", start);
|
|
2829
3864
|
}
|
|
@@ -2847,14 +3882,67 @@ function registerConfigCommands(program2) {
|
|
|
2847
3882
|
fail(err, "config", "set", start);
|
|
2848
3883
|
}
|
|
2849
3884
|
});
|
|
2850
|
-
config.command("test").description("Test connectivity to configured services").action(() => {
|
|
3885
|
+
config.command("test").description("Test connectivity to configured services").action(async () => {
|
|
2851
3886
|
const start = Date.now();
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
3887
|
+
try {
|
|
3888
|
+
const opts = program2.optsWithGlobals();
|
|
3889
|
+
const cfg = loadConfig({ configPath: opts.config });
|
|
3890
|
+
const http = createHttpClient(cfg);
|
|
3891
|
+
const results = {};
|
|
3892
|
+
if (cfg.jira.baseUrl) {
|
|
3893
|
+
try {
|
|
3894
|
+
await http.jira("/rest/api/2/myself");
|
|
3895
|
+
results.jira = { ok: true, message: "connected" };
|
|
3896
|
+
} catch (err) {
|
|
3897
|
+
results.jira = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3898
|
+
}
|
|
3899
|
+
} else {
|
|
3900
|
+
results.jira = { ok: null, message: "not configured" };
|
|
3901
|
+
}
|
|
3902
|
+
if (cfg.bitbucket.baseUrl) {
|
|
3903
|
+
try {
|
|
3904
|
+
await http.bitbucket("/rest/api/1.0/application-properties");
|
|
3905
|
+
results.bitbucket = { ok: true, message: "connected" };
|
|
3906
|
+
} catch (err) {
|
|
3907
|
+
results.bitbucket = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3908
|
+
}
|
|
3909
|
+
} else {
|
|
3910
|
+
results.bitbucket = { ok: null, message: "not configured" };
|
|
3911
|
+
}
|
|
3912
|
+
if (cfg.confluence.baseUrl) {
|
|
3913
|
+
try {
|
|
3914
|
+
await http.confluence("/rest/api/space", { params: { limit: 1 } });
|
|
3915
|
+
results.confluence = { ok: true, message: "connected" };
|
|
3916
|
+
} catch (err) {
|
|
3917
|
+
results.confluence = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3918
|
+
}
|
|
3919
|
+
} else {
|
|
3920
|
+
results.confluence = { ok: null, message: "not configured" };
|
|
3921
|
+
}
|
|
3922
|
+
if (cfg.sonar.baseUrl) {
|
|
3923
|
+
try {
|
|
3924
|
+
await http.sonar("/api/system/status");
|
|
3925
|
+
results.sonar = { ok: true, message: "connected" };
|
|
3926
|
+
} catch (err) {
|
|
3927
|
+
results.sonar = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3928
|
+
}
|
|
3929
|
+
} else {
|
|
3930
|
+
results.sonar = { ok: null, message: "not configured" };
|
|
3931
|
+
}
|
|
3932
|
+
if (cfg.sde.baseUrl) {
|
|
3933
|
+
try {
|
|
3934
|
+
await http.sde("/api/v2/users/me/");
|
|
3935
|
+
results.sde = { ok: true, message: "connected" };
|
|
3936
|
+
} catch (err) {
|
|
3937
|
+
results.sde = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3938
|
+
}
|
|
3939
|
+
} else {
|
|
3940
|
+
results.sde = { ok: null, message: "not configured" };
|
|
3941
|
+
}
|
|
3942
|
+
success(results, "config", "test", start);
|
|
3943
|
+
} catch (err) {
|
|
3944
|
+
fail(err, "config", "test", start);
|
|
3945
|
+
}
|
|
2858
3946
|
});
|
|
2859
3947
|
}
|
|
2860
3948
|
async function initGlobalConfig(start) {
|
|
@@ -2884,6 +3972,14 @@ async function initGlobalConfig(start) {
|
|
|
2884
3972
|
const bitbucketPat = await password({
|
|
2885
3973
|
message: "Bitbucket personal access token:"
|
|
2886
3974
|
});
|
|
3975
|
+
process.stderr.write("\n\u2500\u2500 Confluence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
3976
|
+
const confluenceBaseUrl = await input({
|
|
3977
|
+
message: "Confluence base URL (e.g. https://confluence.your-company.com):",
|
|
3978
|
+
default: ""
|
|
3979
|
+
});
|
|
3980
|
+
const confluenceApiToken = await password({
|
|
3981
|
+
message: "Confluence personal access token:"
|
|
3982
|
+
});
|
|
2887
3983
|
process.stderr.write("\n\u2500\u2500 Artifactory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
2888
3984
|
const useArtifactory = await confirm({
|
|
2889
3985
|
message: "Configure Artifactory for dependency commands (deps outdated, deps license-check)?",
|
|
@@ -2916,11 +4012,51 @@ async function initGlobalConfig(start) {
|
|
|
2916
4012
|
mavenRepo = await input({ message: " Maven repository name:", default: "libs-release" });
|
|
2917
4013
|
}
|
|
2918
4014
|
}
|
|
4015
|
+
process.stderr.write("\n\u2500\u2500 SonarQube \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
4016
|
+
const useSonar = await confirm({
|
|
4017
|
+
message: "Configure SonarQube Server for code quality checks?",
|
|
4018
|
+
default: false
|
|
4019
|
+
});
|
|
4020
|
+
let sonarBaseUrl = "";
|
|
4021
|
+
let sonarToken = "";
|
|
4022
|
+
if (useSonar) {
|
|
4023
|
+
sonarBaseUrl = await input({
|
|
4024
|
+
message: "SonarQube Server base URL (e.g. https://sonar.your-company.com):",
|
|
4025
|
+
default: ""
|
|
4026
|
+
});
|
|
4027
|
+
sonarToken = await password({
|
|
4028
|
+
message: "SonarQube personal access token:"
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
process.stderr.write("\n\u2500\u2500 SDElements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
4032
|
+
const useSde = await confirm({
|
|
4033
|
+
message: "Configure SDElements for threat modeling and countermeasure queries?",
|
|
4034
|
+
default: false
|
|
4035
|
+
});
|
|
4036
|
+
let sdeBaseUrl = "";
|
|
4037
|
+
let sdeToken = "";
|
|
4038
|
+
if (useSde) {
|
|
4039
|
+
sdeBaseUrl = await input({
|
|
4040
|
+
message: "SDElements base URL\n Cloud-hosted: https://your-org.sdelements.com\n On-premise: https://sde.your-company.com\n URL: ",
|
|
4041
|
+
default: ""
|
|
4042
|
+
});
|
|
4043
|
+
sdeToken = await password({
|
|
4044
|
+
message: "SDElements API token:"
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
2919
4047
|
process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
2920
4048
|
const jiraProject = await input({
|
|
2921
4049
|
message: "Default Jira project key (optional):",
|
|
2922
4050
|
default: ""
|
|
2923
4051
|
});
|
|
4052
|
+
const sonarProject = useSonar ? await input({
|
|
4053
|
+
message: "Default SonarQube project key (optional):",
|
|
4054
|
+
default: ""
|
|
4055
|
+
}) : "";
|
|
4056
|
+
const sdeProject = useSde ? await input({
|
|
4057
|
+
message: "Default SDElements project ID (optional, numeric):",
|
|
4058
|
+
default: ""
|
|
4059
|
+
}) : "";
|
|
2924
4060
|
process.stderr.write("\n");
|
|
2925
4061
|
const confirmed = await confirm({
|
|
2926
4062
|
message: "Write config to ~/.pncli/config.json?",
|
|
@@ -2928,7 +4064,8 @@ async function initGlobalConfig(start) {
|
|
|
2928
4064
|
});
|
|
2929
4065
|
if (!confirmed) {
|
|
2930
4066
|
process.stderr.write("Aborted.\n");
|
|
2931
|
-
process.
|
|
4067
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
4068
|
+
return;
|
|
2932
4069
|
}
|
|
2933
4070
|
writeGlobalConfig({
|
|
2934
4071
|
user: {
|
|
@@ -2943,6 +4080,12 @@ async function initGlobalConfig(start) {
|
|
|
2943
4080
|
baseUrl: bitbucketBaseUrl || void 0,
|
|
2944
4081
|
pat: bitbucketPat || void 0
|
|
2945
4082
|
},
|
|
4083
|
+
...confluenceBaseUrl || confluenceApiToken ? {
|
|
4084
|
+
confluence: {
|
|
4085
|
+
baseUrl: confluenceBaseUrl || void 0,
|
|
4086
|
+
apiToken: confluenceApiToken || void 0
|
|
4087
|
+
}
|
|
4088
|
+
} : {},
|
|
2946
4089
|
...useArtifactory ? {
|
|
2947
4090
|
artifactory: {
|
|
2948
4091
|
baseUrl: artifactoryBaseUrl || void 0,
|
|
@@ -2952,10 +4095,32 @@ async function initGlobalConfig(start) {
|
|
|
2952
4095
|
mavenRepo: mavenRepo || void 0
|
|
2953
4096
|
}
|
|
2954
4097
|
} : {},
|
|
4098
|
+
...useSonar ? {
|
|
4099
|
+
sonar: {
|
|
4100
|
+
baseUrl: sonarBaseUrl || void 0,
|
|
4101
|
+
token: sonarToken || void 0
|
|
4102
|
+
}
|
|
4103
|
+
} : {},
|
|
4104
|
+
...useSde ? {
|
|
4105
|
+
sde: {
|
|
4106
|
+
baseUrl: sdeBaseUrl || void 0,
|
|
4107
|
+
token: sdeToken || void 0
|
|
4108
|
+
}
|
|
4109
|
+
} : {},
|
|
2955
4110
|
defaults: {
|
|
2956
4111
|
jira: {
|
|
2957
4112
|
project: jiraProject || void 0
|
|
2958
|
-
}
|
|
4113
|
+
},
|
|
4114
|
+
...useSonar && sonarProject ? {
|
|
4115
|
+
sonar: {
|
|
4116
|
+
project: sonarProject || void 0
|
|
4117
|
+
}
|
|
4118
|
+
} : {},
|
|
4119
|
+
...useSde && sdeProject ? {
|
|
4120
|
+
sde: {
|
|
4121
|
+
project: sdeProject || void 0
|
|
4122
|
+
}
|
|
4123
|
+
} : {}
|
|
2959
4124
|
}
|
|
2960
4125
|
});
|
|
2961
4126
|
const configPath = getGlobalConfigPath();
|
|
@@ -2986,16 +4151,18 @@ async function initRepoConfig(start) {
|
|
|
2986
4151
|
});
|
|
2987
4152
|
if (!confirmed) {
|
|
2988
4153
|
process.stderr.write("Aborted.\n");
|
|
2989
|
-
process.
|
|
4154
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
4155
|
+
return;
|
|
2990
4156
|
}
|
|
2991
|
-
if (
|
|
4157
|
+
if (fs5.existsSync(".pncli.json")) {
|
|
2992
4158
|
const overwrite = await confirm({
|
|
2993
4159
|
message: ".pncli.json already exists. Overwrite?",
|
|
2994
4160
|
default: false
|
|
2995
4161
|
});
|
|
2996
4162
|
if (!overwrite) {
|
|
2997
4163
|
process.stderr.write("Aborted.\n");
|
|
2998
|
-
process.
|
|
4164
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
4165
|
+
return;
|
|
2999
4166
|
}
|
|
3000
4167
|
}
|
|
3001
4168
|
writeRepoConfig({
|
|
@@ -3014,6 +4181,9 @@ async function initRepoConfig(start) {
|
|
|
3014
4181
|
}
|
|
3015
4182
|
|
|
3016
4183
|
// src/cli.ts
|
|
4184
|
+
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
4185
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
4186
|
+
}
|
|
3017
4187
|
var require2 = createRequire(import.meta.url);
|
|
3018
4188
|
var pkg = require2("../package.json");
|
|
3019
4189
|
var TAGLINE = "One command does what three meetings couldn't.";
|
|
@@ -3036,21 +4206,24 @@ registerJiraCommands(program);
|
|
|
3036
4206
|
registerBitbucketCommands(program);
|
|
3037
4207
|
registerConfluenceCommands(program);
|
|
3038
4208
|
registerSonarCommands(program);
|
|
4209
|
+
registerSdeCommands(program);
|
|
3039
4210
|
registerDepsCommands(program);
|
|
3040
4211
|
registerConfigCommands(program);
|
|
3041
4212
|
program.addHelpText("after", `
|
|
3042
4213
|
Services:
|
|
3043
4214
|
git Local git operations (status, diff, log, branch)
|
|
3044
4215
|
deps Dependency scanning, CVE detection, license auditing
|
|
3045
|
-
jira Jira Data Cloud
|
|
3046
|
-
bitbucket Bitbucket Server
|
|
3047
|
-
confluence Confluence
|
|
3048
|
-
sonar SonarQube (
|
|
4216
|
+
jira Jira Data Cloud
|
|
4217
|
+
bitbucket Bitbucket Server
|
|
4218
|
+
confluence Confluence
|
|
4219
|
+
sonar SonarQube Server (quality gates, issues, metrics, hotspots)
|
|
4220
|
+
sde SDElements (threat modeling, countermeasures, compliance)
|
|
3049
4221
|
config Manage pncli configuration
|
|
3050
4222
|
`);
|
|
3051
4223
|
program.parseAsync(process.argv).catch((err) => {
|
|
4224
|
+
if (process.exitCode !== void 0) return;
|
|
3052
4225
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
3053
4226
|
`);
|
|
3054
|
-
process.
|
|
4227
|
+
process.exitCode = ExitCode.GENERAL_ERROR;
|
|
3055
4228
|
});
|
|
3056
4229
|
//# sourceMappingURL=cli.js.map
|