@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/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
- process.stdout.write(output, () => process.exit(exitCode));
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 fs from "fs";
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 = fs.readFileSync(filePath, "utf8");
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 (!fs.existsSync(dir)) {
188
- fs.mkdirSync(dir, { recursive: true });
213
+ if (!fs2.existsSync(dir)) {
214
+ fs2.mkdirSync(dir, { recursive: true });
189
215
  }
190
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf8");
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
- fs.writeFileSync(path.join(targetDir, ".pncli.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
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
- return response.json();
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.write(msg, () => process.exit(ExitCode.SUCCESS));
550
- return new Promise(() => {
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.write(msg, () => process.exit(ExitCode.SUCCESS));
572
- return new Promise(() => {
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").action(() => {
1472
- success(
1473
- { message: "Coming soon \u2014 the nightmare never ends." },
1474
- "confluence",
1475
- "stub",
1476
- Date.now()
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").action(() => {
1484
- success(
1485
- { message: "Coming soon \u2014 the nightmare never ends." },
1486
- "sonar",
1487
- "stub",
1488
- Date.now()
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 fs2 from "fs";
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 fs2.readFileSync(path5.join(repoRoot, relPath), "utf8");
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 fs3 from "fs";
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.exit(ExitCode.GENERAL_ERROR);
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
- success(
2853
- { message: "Service connectivity test available after Phase 2." },
2854
- "config",
2855
- "test",
2856
- start
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.exit(ExitCode.SUCCESS);
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.exit(ExitCode.SUCCESS);
4154
+ process.exitCode = ExitCode.SUCCESS;
4155
+ return;
2990
4156
  }
2991
- if (fs3.existsSync(".pncli.json")) {
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.exit(ExitCode.SUCCESS);
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 (coming soon)
3046
- bitbucket Bitbucket Server (coming soon)
3047
- confluence Confluence (coming soon)
3048
- sonar SonarQube (coming soon)
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.exit(ExitCode.GENERAL_ERROR);
4227
+ process.exitCode = ExitCode.GENERAL_ERROR;
3055
4228
  });
3056
4229
  //# sourceMappingURL=cli.js.map