@kolatts/pncli 1.1.0 → 1.2.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
@@ -19,6 +19,24 @@ var PncliError = class extends Error {
19
19
  }
20
20
  };
21
21
 
22
+ // src/lib/exitCodes.ts
23
+ var ExitCode = {
24
+ SUCCESS: 0,
25
+ GENERAL_ERROR: 1,
26
+ USAGE_ERROR: 2,
27
+ NETWORK_ERROR: 69,
28
+ // EX_UNAVAILABLE from sysexits.h
29
+ AUTH_ERROR: 77,
30
+ // EX_NOPERM from sysexits.h
31
+ CONFIG_ERROR: 78
32
+ // EX_CONFIG from sysexits.h
33
+ };
34
+ function exitCodeFromStatus(httpStatus) {
35
+ if (httpStatus === 401 || httpStatus === 403) return ExitCode.AUTH_ERROR;
36
+ if (httpStatus === 0) return ExitCode.NETWORK_ERROR;
37
+ return ExitCode.GENERAL_ERROR;
38
+ }
39
+
22
40
  // src/lib/output.ts
23
41
  var globalOptions = { pretty: false, verbose: false };
24
42
  var globalUser = { email: void 0, userId: void 0 };
@@ -60,10 +78,10 @@ function fail(err, service, action, startTime) {
60
78
  };
61
79
  const msg = globalOptions.pretty ? chalk.red("\u2717 Error: ") + errorDetail.message : null;
62
80
  if (msg) process.stderr.write(msg + "\n");
63
- process.stdout.write(
64
- (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + "\n"
65
- );
66
- process.exit(1);
81
+ const output = (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + "\n";
82
+ const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
83
+ process.stdout.write(output, () => process.exit(exitCode));
84
+ throw new PncliError(errorDetail.message, errorDetail.status);
67
85
  }
68
86
  function log(message) {
69
87
  if (globalOptions.verbose) {
@@ -90,6 +108,11 @@ var ENV_KEYS = {
90
108
  JIRA_API_TOKEN: "PNCLI_JIRA_API_TOKEN",
91
109
  BITBUCKET_BASE_URL: "PNCLI_BITBUCKET_BASE_URL",
92
110
  BITBUCKET_PAT: "PNCLI_BITBUCKET_PAT",
111
+ ARTIFACTORY_BASE_URL: "PNCLI_ARTIFACTORY_BASE_URL",
112
+ ARTIFACTORY_TOKEN: "PNCLI_ARTIFACTORY_TOKEN",
113
+ ARTIFACTORY_REPO_NPM: "PNCLI_ARTIFACTORY_REPO_NPM",
114
+ ARTIFACTORY_REPO_NUGET: "PNCLI_ARTIFACTORY_REPO_NUGET",
115
+ ARTIFACTORY_REPO_MAVEN: "PNCLI_ARTIFACTORY_REPO_MAVEN",
93
116
  CONFIG_PATH: "PNCLI_CONFIG_PATH"
94
117
  };
95
118
  function getGlobalConfigPath(overridePath) {
@@ -148,6 +171,13 @@ function loadConfig(opts = {}) {
148
171
  baseUrl: process.env[ENV_KEYS.BITBUCKET_BASE_URL] ?? globalConfig.bitbucket?.baseUrl,
149
172
  pat: process.env[ENV_KEYS.BITBUCKET_PAT] ?? globalConfig.bitbucket?.pat
150
173
  },
174
+ artifactory: {
175
+ baseUrl: process.env[ENV_KEYS.ARTIFACTORY_BASE_URL] ?? globalConfig.artifactory?.baseUrl,
176
+ token: process.env[ENV_KEYS.ARTIFACTORY_TOKEN] ?? globalConfig.artifactory?.token,
177
+ npmRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_NPM] ?? globalConfig.artifactory?.npmRepo,
178
+ nugetRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_NUGET] ?? globalConfig.artifactory?.nugetRepo,
179
+ mavenRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_MAVEN] ?? globalConfig.artifactory?.mavenRepo
180
+ },
151
181
  defaults: mergedDefaults
152
182
  };
153
183
  }
@@ -190,6 +220,10 @@ function maskConfig(config) {
190
220
  bitbucket: {
191
221
  ...config.bitbucket,
192
222
  pat: config.bitbucket.pat ? "***" : void 0
223
+ },
224
+ artifactory: {
225
+ ...config.artifactory,
226
+ token: config.artifactory.token ? "***" : void 0
193
227
  }
194
228
  };
195
229
  }
@@ -394,8 +428,8 @@ function getGitContext(config) {
394
428
  }
395
429
 
396
430
  // src/lib/http.ts
397
- function buildUrl(base, path2, params) {
398
- const url = new URL(path2, base.endsWith("/") ? base : base + "/");
431
+ function buildUrl(base, path6, params) {
432
+ const url = new URL(path6, base.endsWith("/") ? base : base + "/");
399
433
  if (params) {
400
434
  for (const [key, val] of Object.entries(params)) {
401
435
  if (val !== void 0) {
@@ -440,8 +474,24 @@ async function request(url, init, timeoutMs) {
440
474
  try {
441
475
  const body = await response.text();
442
476
  const parsed = JSON.parse(body);
443
- if (parsed.message) message = parsed.message;
444
- else if (parsed.errors?.[0]?.message) message = parsed.errors[0].message;
477
+ const parts = [];
478
+ if (Array.isArray(parsed.errorMessages)) {
479
+ parts.push(...parsed.errorMessages.filter(Boolean));
480
+ }
481
+ if (parsed.errors && typeof parsed.errors === "object" && !Array.isArray(parsed.errors)) {
482
+ for (const [field, msg] of Object.entries(parsed.errors)) {
483
+ parts.push(`${field}: ${msg}`);
484
+ }
485
+ }
486
+ if (Array.isArray(parsed.errors)) {
487
+ for (const e of parsed.errors) {
488
+ if (e?.message) parts.push(e.message);
489
+ }
490
+ }
491
+ if (parts.length === 0 && parsed.message) {
492
+ parts.push(String(parsed.message));
493
+ }
494
+ if (parts.length > 0) message = parts.join("; ");
445
495
  } catch {
446
496
  }
447
497
  throw new PncliError(message, response.status, url);
@@ -466,7 +516,8 @@ var HttpClient = class {
466
516
  return {
467
517
  "Authorization": `Bearer ${apiToken}`,
468
518
  "Content-Type": "application/json",
469
- "Accept": "application/json"
519
+ "Accept": "application/json",
520
+ "Connection": "close"
470
521
  };
471
522
  }
472
523
  bitbucketHeaders() {
@@ -475,13 +526,14 @@ var HttpClient = class {
475
526
  return {
476
527
  "Authorization": `Bearer ${pat}`,
477
528
  "Content-Type": "application/json",
478
- "Accept": "application/json"
529
+ "Accept": "application/json",
530
+ "Connection": "close"
479
531
  };
480
532
  }
481
- async jira(path2, opts = {}) {
533
+ async jira(path6, opts = {}) {
482
534
  const baseUrl = this.config.jira.baseUrl;
483
535
  if (!baseUrl) throw new PncliError("Jira baseUrl not configured. Run: pncli config init");
484
- const url = buildUrl(baseUrl, path2, opts.params);
536
+ const url = buildUrl(baseUrl, path6, opts.params);
485
537
  const headers = this.jiraHeaders();
486
538
  const init = {
487
539
  method: opts.method ?? "GET",
@@ -490,19 +542,20 @@ var HttpClient = class {
490
542
  };
491
543
  if (this.dryRun) {
492
544
  const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
493
- process.stderr.write(`DRY RUN: ${init.method} ${url}
545
+ const msg = `DRY RUN: ${init.method} ${url}
494
546
  Headers: ${JSON.stringify(safeHeaders, null, 2)}
495
- `);
496
- if (opts.body) process.stderr.write(`Body: ${JSON.stringify(opts.body, null, 2)}
497
- `);
498
- process.exit(0);
547
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
548
+ ` : "");
549
+ process.stderr.write(msg, () => process.exit(ExitCode.SUCCESS));
550
+ return new Promise(() => {
551
+ });
499
552
  }
500
553
  return request(url, init, opts.timeoutMs ?? 3e4);
501
554
  }
502
- async bitbucket(path2, opts = {}) {
555
+ async bitbucket(path6, opts = {}) {
503
556
  const baseUrl = this.config.bitbucket.baseUrl;
504
557
  if (!baseUrl) throw new PncliError("Bitbucket baseUrl not configured. Run: pncli config init");
505
- const url = buildUrl(baseUrl, path2, opts.params);
558
+ const url = buildUrl(baseUrl, path6, opts.params);
506
559
  const headers = this.bitbucketHeaders();
507
560
  const init = {
508
561
  method: opts.method ?? "GET",
@@ -511,12 +564,13 @@ Headers: ${JSON.stringify(safeHeaders, null, 2)}
511
564
  };
512
565
  if (this.dryRun) {
513
566
  const safeHeaders = { ...headers, Authorization: "[REDACTED]" };
514
- process.stderr.write(`DRY RUN: ${init.method} ${url}
567
+ const msg = `DRY RUN: ${init.method} ${url}
515
568
  Headers: ${JSON.stringify(safeHeaders, null, 2)}
516
- `);
517
- if (opts.body) process.stderr.write(`Body: ${JSON.stringify(opts.body, null, 2)}
518
- `);
519
- process.exit(0);
569
+ ` + (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}
570
+ ` : "");
571
+ process.stderr.write(msg, () => process.exit(ExitCode.SUCCESS));
572
+ return new Promise(() => {
573
+ });
520
574
  }
521
575
  return request(url, init, opts.timeoutMs ?? 3e4);
522
576
  }
@@ -1436,21 +1490,1326 @@ function registerSonarCommands(program2) {
1436
1490
  });
1437
1491
  }
1438
1492
 
1439
- // src/services/artifactory/commands.ts
1440
- function registerArtifactoryCommands(program2) {
1441
- program2.command("artifactory").description("Artifactory operations").action(() => {
1442
- success(
1443
- { message: "Coming soon \u2014 the nightmare never ends." },
1444
- "artifactory",
1445
- "stub",
1446
- Date.now()
1493
+ // src/services/deps/parsers/index.ts
1494
+ import fs2 from "fs";
1495
+ import path5 from "path";
1496
+ import { execSync as execSync4, execFileSync as execFileSync2 } from "child_process";
1497
+
1498
+ // src/services/deps/parsers/npm.ts
1499
+ import path2 from "path";
1500
+ function findNpmManifests(files) {
1501
+ const lockFiles = new Set(
1502
+ files.filter((f) => {
1503
+ const base = path2.basename(f);
1504
+ return base === "package-lock.json" || base === "yarn.lock" || base === "pnpm-lock.yaml";
1505
+ })
1506
+ );
1507
+ const manifests = [];
1508
+ for (const file of files) {
1509
+ if (path2.basename(file) !== "package.json") continue;
1510
+ if (file.split("/").includes("node_modules")) continue;
1511
+ const dir = path2.dirname(file);
1512
+ let lockFile;
1513
+ for (const lf of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
1514
+ const candidate = dir === "." ? lf : `${dir}/${lf}`;
1515
+ if (lockFiles.has(candidate)) {
1516
+ lockFile = candidate;
1517
+ break;
1518
+ }
1519
+ }
1520
+ manifests.push({ file, ecosystem: "npm", lockFile });
1521
+ }
1522
+ return manifests;
1523
+ }
1524
+ function parseNpmPackages(content, manifest, opts, lockContent) {
1525
+ if (lockContent && manifest.lockFile) {
1526
+ const base = path2.basename(manifest.lockFile);
1527
+ if (base === "package-lock.json") {
1528
+ const pkgs = parsePackageLock(lockContent, manifest.lockFile, opts);
1529
+ if (pkgs.length > 0) return pkgs;
1530
+ } else if (base === "yarn.lock") {
1531
+ return parseYarnLock(lockContent, manifest.lockFile, opts, content);
1532
+ } else if (base === "pnpm-lock.yaml") {
1533
+ return parsePnpmLock(lockContent, manifest.lockFile, opts, content);
1534
+ }
1535
+ }
1536
+ return parsePackageJson(content, manifest.file, opts);
1537
+ }
1538
+ function stripVersionPrefix(v) {
1539
+ return v.replace(/^[\^~>=<* ]+/, "").split(/\s/)[0] ?? v;
1540
+ }
1541
+ function parsePackageJson(content, filePath, opts) {
1542
+ let pkg2;
1543
+ try {
1544
+ pkg2 = JSON.parse(content);
1545
+ } catch {
1546
+ return [];
1547
+ }
1548
+ const packages = [];
1549
+ for (const [name, version] of Object.entries(pkg2.dependencies ?? {})) {
1550
+ packages.push({
1551
+ name,
1552
+ version: stripVersionPrefix(version),
1553
+ ecosystem: "npm",
1554
+ source: filePath,
1555
+ type: "direct",
1556
+ scope: "production"
1557
+ });
1558
+ }
1559
+ if (opts.includeDev) {
1560
+ for (const [name, version] of Object.entries(pkg2.devDependencies ?? {})) {
1561
+ packages.push({
1562
+ name,
1563
+ version: stripVersionPrefix(version),
1564
+ ecosystem: "npm",
1565
+ source: filePath,
1566
+ type: "direct",
1567
+ scope: "dev"
1568
+ });
1569
+ }
1570
+ }
1571
+ return packages;
1572
+ }
1573
+ function parsePackageLock(content, lockFilePath, opts) {
1574
+ let lock;
1575
+ try {
1576
+ lock = JSON.parse(content);
1577
+ } catch {
1578
+ return [];
1579
+ }
1580
+ if ((lock.lockfileVersion ?? 1) < 2 || !lock.packages) return [];
1581
+ const packages = [];
1582
+ for (const [pkgPath, meta] of Object.entries(lock.packages)) {
1583
+ if (pkgPath === "") continue;
1584
+ if (!meta.version) continue;
1585
+ const isDev = meta.dev === true || meta.devOptional === true;
1586
+ if (isDev && !opts.includeDev) continue;
1587
+ const withoutRoot = pkgPath.startsWith("node_modules/") ? pkgPath.slice("node_modules/".length) : pkgPath;
1588
+ const segments = withoutRoot.split("/node_modules/");
1589
+ const isTransitive = segments.length > 1;
1590
+ if (isTransitive && !opts.includeTransitive) continue;
1591
+ const name = segments[segments.length - 1] ?? pkgPath;
1592
+ packages.push({
1593
+ name,
1594
+ version: meta.version,
1595
+ ecosystem: "npm",
1596
+ source: lockFilePath,
1597
+ type: isTransitive ? "transitive" : "direct",
1598
+ scope: isDev ? "dev" : "production"
1599
+ });
1600
+ }
1601
+ return packages;
1602
+ }
1603
+ function parseYarnLock(content, lockFilePath, opts, packageJsonContent) {
1604
+ let pkgJson;
1605
+ try {
1606
+ pkgJson = JSON.parse(packageJsonContent);
1607
+ } catch {
1608
+ pkgJson = {};
1609
+ }
1610
+ const directDeps = new Set(Object.keys(pkgJson.dependencies ?? {}));
1611
+ const devDeps = new Set(Object.keys(pkgJson.devDependencies ?? {}));
1612
+ const allDirectDeps = /* @__PURE__ */ new Set([...directDeps, ...devDeps]);
1613
+ const packages = [];
1614
+ const seen = /* @__PURE__ */ new Set();
1615
+ const blockRegex = /^"?(@?[^@"\n][^"\n]*)@[^:]+:?\n\s+version "([^"]+)"/gm;
1616
+ for (const match of content.matchAll(blockRegex)) {
1617
+ const name = match[1].trim().replace(/^"|"$/g, "");
1618
+ const version = match[2];
1619
+ const key = `${name}@${version}`;
1620
+ if (seen.has(key)) continue;
1621
+ seen.add(key);
1622
+ const isDev = devDeps.has(name) && !directDeps.has(name);
1623
+ if (isDev && !opts.includeDev) continue;
1624
+ const isTransitive = !allDirectDeps.has(name);
1625
+ if (isTransitive && !opts.includeTransitive) continue;
1626
+ packages.push({
1627
+ name,
1628
+ version,
1629
+ ecosystem: "npm",
1630
+ source: lockFilePath,
1631
+ type: isTransitive ? "transitive" : "direct",
1632
+ scope: isDev ? "dev" : "production"
1633
+ });
1634
+ }
1635
+ return packages;
1636
+ }
1637
+ function parsePnpmLock(content, lockFilePath, opts, packageJsonContent) {
1638
+ let pkgJson;
1639
+ try {
1640
+ pkgJson = JSON.parse(packageJsonContent);
1641
+ } catch {
1642
+ pkgJson = {};
1643
+ }
1644
+ const directDeps = new Set(Object.keys(pkgJson.dependencies ?? {}));
1645
+ const devDeps = new Set(Object.keys(pkgJson.devDependencies ?? {}));
1646
+ const allDirectDeps = /* @__PURE__ */ new Set([...directDeps, ...devDeps]);
1647
+ const packages = [];
1648
+ const seen = /* @__PURE__ */ new Set();
1649
+ const blockRegex = /^\s{2}(\/?@?[^@\s/][^@\s]*)@(\d[^:\s]*):\s*\n((?:\s{4}[^\n]+\n)*)/gm;
1650
+ for (const match of content.matchAll(blockRegex)) {
1651
+ const name = match[1].replace(/^\//, "");
1652
+ const version = match[2];
1653
+ const attrs = match[3] ?? "";
1654
+ const key = `${name}@${version}`;
1655
+ if (seen.has(key)) continue;
1656
+ seen.add(key);
1657
+ const isDev = devDeps.has(name) && !directDeps.has(name) || /^\s+dev:\s*true/m.test(attrs);
1658
+ if (isDev && !opts.includeDev) continue;
1659
+ const isTransitive = !allDirectDeps.has(name);
1660
+ if (isTransitive && !opts.includeTransitive) continue;
1661
+ packages.push({
1662
+ name,
1663
+ version,
1664
+ ecosystem: "npm",
1665
+ source: lockFilePath,
1666
+ type: isTransitive ? "transitive" : "direct",
1667
+ scope: isDev ? "dev" : "production"
1668
+ });
1669
+ }
1670
+ return packages;
1671
+ }
1672
+
1673
+ // src/services/deps/parsers/nuget.ts
1674
+ import path3 from "path";
1675
+ var PROJ_EXTENSIONS = /* @__PURE__ */ new Set([".csproj", ".fsproj", ".vbproj"]);
1676
+ function findNugetManifests(files) {
1677
+ const lockFiles = /* @__PURE__ */ new Map();
1678
+ for (const f of files) {
1679
+ if (path3.basename(f) === "packages.lock.json") {
1680
+ lockFiles.set(path3.dirname(f), f);
1681
+ }
1682
+ }
1683
+ const manifests = [];
1684
+ for (const file of files) {
1685
+ const base = path3.basename(file);
1686
+ const ext = path3.extname(file);
1687
+ if (PROJ_EXTENSIONS.has(ext) || base === "packages.config") {
1688
+ const dir = path3.dirname(file);
1689
+ const lockFile = lockFiles.get(dir);
1690
+ manifests.push({ file, ecosystem: "nuget", lockFile });
1691
+ }
1692
+ if (base === "Directory.Packages.props") {
1693
+ const dir = path3.dirname(file);
1694
+ manifests.push({ file, ecosystem: "nuget", lockFile: lockFiles.get(dir) });
1695
+ }
1696
+ }
1697
+ const deduped = [];
1698
+ const seenLocks = /* @__PURE__ */ new Set();
1699
+ for (const manifest of manifests) {
1700
+ if (!manifest.lockFile) {
1701
+ deduped.push(manifest);
1702
+ continue;
1703
+ }
1704
+ if (!seenLocks.has(manifest.lockFile)) {
1705
+ seenLocks.add(manifest.lockFile);
1706
+ deduped.push(manifest);
1707
+ }
1708
+ }
1709
+ return deduped;
1710
+ }
1711
+ function parseNugetPackages(content, manifest, opts, lockContent, propsContent) {
1712
+ const base = path3.basename(manifest.file);
1713
+ if (lockContent) {
1714
+ return parsePackagesLock(lockContent, manifest.lockFile ?? manifest.file, opts);
1715
+ }
1716
+ if (base === "packages.config") {
1717
+ return parsePackagesConfig(content, manifest.file);
1718
+ }
1719
+ if (base === "Directory.Packages.props") {
1720
+ return parseDirectoryPackagesProps(content, manifest.file);
1721
+ }
1722
+ return parseCsproj(content, manifest.file, propsContent);
1723
+ }
1724
+ function parseCsproj(content, filePath, propsContent) {
1725
+ const centralVersions = propsContent ? extractCentralVersions(propsContent) : /* @__PURE__ */ new Map();
1726
+ const packages = [];
1727
+ const singleLine = /<PackageReference\s+([^>]+?)\/>/gi;
1728
+ const multiLine = /<PackageReference\s+([^>]+?)>([\s\S]*?)<\/PackageReference>/gi;
1729
+ const extractFromAttrsAndInner = (attrs, inner) => {
1730
+ const nameMatch = /\bInclude="([^"]+)"/i.exec(attrs);
1731
+ if (!nameMatch) return;
1732
+ const name = nameMatch[1];
1733
+ let version;
1734
+ const attrVersion = /\bVersion="([^"]+)"/i.exec(attrs);
1735
+ if (attrVersion) {
1736
+ version = attrVersion[1];
1737
+ } else if (inner) {
1738
+ const innerVersion = /<Version>([^<]+)<\/Version>/i.exec(inner);
1739
+ if (innerVersion) version = innerVersion[1];
1740
+ }
1741
+ if (!version || version.startsWith("$(")) {
1742
+ version = centralVersions.get(name) ?? centralVersions.get(name.toLowerCase());
1743
+ }
1744
+ if (!version) return;
1745
+ packages.push({
1746
+ name,
1747
+ version,
1748
+ ecosystem: "nuget",
1749
+ source: filePath,
1750
+ type: "direct",
1751
+ scope: "production"
1752
+ });
1753
+ };
1754
+ for (const m of content.matchAll(singleLine)) {
1755
+ extractFromAttrsAndInner(m[1], "");
1756
+ }
1757
+ for (const m of content.matchAll(multiLine)) {
1758
+ extractFromAttrsAndInner(m[1], m[2] ?? "");
1759
+ }
1760
+ return packages;
1761
+ }
1762
+ function extractCentralVersions(propsContent) {
1763
+ const map = /* @__PURE__ */ new Map();
1764
+ const regex = /<PackageVersion\s+Include="([^"]+)"\s+Version="([^"]+)"/gi;
1765
+ for (const m of propsContent.matchAll(regex)) {
1766
+ map.set(m[1], m[2]);
1767
+ map.set(m[1].toLowerCase(), m[2]);
1768
+ }
1769
+ return map;
1770
+ }
1771
+ function parseDirectoryPackagesProps(content, filePath) {
1772
+ const packages = [];
1773
+ const regex = /<PackageVersion\s+Include="([^"]+)"\s+Version="([^"]+)"/gi;
1774
+ for (const m of content.matchAll(regex)) {
1775
+ packages.push({
1776
+ name: m[1],
1777
+ version: m[2],
1778
+ ecosystem: "nuget",
1779
+ source: filePath,
1780
+ type: "direct",
1781
+ scope: "production"
1782
+ });
1783
+ }
1784
+ return packages;
1785
+ }
1786
+ function parsePackagesConfig(content, filePath) {
1787
+ const packages = [];
1788
+ const regex = /<package\s+id="([^"]+)"\s+version="([^"]+)"/gi;
1789
+ for (const m of content.matchAll(regex)) {
1790
+ packages.push({
1791
+ name: m[1],
1792
+ version: m[2],
1793
+ ecosystem: "nuget",
1794
+ source: filePath,
1795
+ type: "direct",
1796
+ scope: "production"
1797
+ });
1798
+ }
1799
+ return packages;
1800
+ }
1801
+ function parsePackagesLock(content, filePath, opts) {
1802
+ let lock;
1803
+ try {
1804
+ lock = JSON.parse(content);
1805
+ } catch {
1806
+ return [];
1807
+ }
1808
+ const packages = [];
1809
+ const seen = /* @__PURE__ */ new Set();
1810
+ for (const frameworkDeps of Object.values(lock.dependencies ?? {})) {
1811
+ for (const [name, meta] of Object.entries(frameworkDeps)) {
1812
+ const version = meta.resolved ?? "";
1813
+ if (!version) continue;
1814
+ const isTransitive = meta.type === "Transitive";
1815
+ if (isTransitive && !opts.includeTransitive) continue;
1816
+ const key = `${name}@${version}`;
1817
+ if (seen.has(key)) continue;
1818
+ seen.add(key);
1819
+ packages.push({
1820
+ name,
1821
+ version,
1822
+ ecosystem: "nuget",
1823
+ source: filePath,
1824
+ type: isTransitive ? "transitive" : "direct",
1825
+ scope: "production"
1826
+ });
1827
+ }
1828
+ }
1829
+ return packages;
1830
+ }
1831
+
1832
+ // src/services/deps/parsers/maven.ts
1833
+ import path4 from "path";
1834
+ function findMavenManifests(files) {
1835
+ const lockFiles = new Set(files.filter((f) => path4.basename(f) === "gradle.lockfile"));
1836
+ const manifests = [];
1837
+ for (const file of files) {
1838
+ const base = path4.basename(file);
1839
+ if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
1840
+ const dir = path4.dirname(file);
1841
+ const lockCandidate = dir === "." ? "gradle.lockfile" : `${dir}/gradle.lockfile`;
1842
+ const lockFile = lockFiles.has(lockCandidate) ? lockCandidate : void 0;
1843
+ manifests.push({ file, ecosystem: "maven", lockFile });
1844
+ }
1845
+ }
1846
+ return manifests;
1847
+ }
1848
+ function parseMavenPackages(content, manifest, opts, lockContent) {
1849
+ const base = path4.basename(manifest.file);
1850
+ if (lockContent && manifest.lockFile) {
1851
+ return parseGradleLock(lockContent, manifest.lockFile, opts);
1852
+ }
1853
+ if (base === "pom.xml") {
1854
+ return parsePomXml(content, manifest.file, opts);
1855
+ }
1856
+ if (base === "build.gradle" || base === "build.gradle.kts") {
1857
+ return parseBuildGradle(content, manifest.file, opts);
1858
+ }
1859
+ return [];
1860
+ }
1861
+ function parsePomXml(content, filePath, opts) {
1862
+ const props = extractPomProperties(content);
1863
+ const managedVersions = extractDependencyManagement(content, props);
1864
+ const withoutMgmt = content.replace(/<dependencyManagement>[\s\S]*?<\/dependencyManagement>/gi, "");
1865
+ const packages = [];
1866
+ const depRegex = /<dependency>([\s\S]*?)<\/dependency>/gi;
1867
+ for (const match of withoutMgmt.matchAll(depRegex)) {
1868
+ const inner = match[1];
1869
+ const groupId = /<groupId>\s*([^<\s]+)\s*<\/groupId>/i.exec(inner)?.[1];
1870
+ const artifactId = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/i.exec(inner)?.[1];
1871
+ if (!groupId || !artifactId) continue;
1872
+ const rawVersion = /<version>\s*([^<\s]+)\s*<\/version>/i.exec(inner)?.[1];
1873
+ const resolvedVersion = resolveProperty(rawVersion ?? null, props) ?? managedVersions.get(`${groupId}:${artifactId}`);
1874
+ if (!resolvedVersion) continue;
1875
+ const scope = /<scope>\s*([^<\s]+)\s*<\/scope>/i.exec(inner)?.[1] ?? "compile";
1876
+ const isDev = scope === "test" || scope === "provided";
1877
+ if (isDev && !opts.includeDev) continue;
1878
+ packages.push({
1879
+ name: `${groupId}:${artifactId}`,
1880
+ version: resolvedVersion,
1881
+ ecosystem: "maven",
1882
+ source: filePath,
1883
+ type: "direct",
1884
+ scope: isDev ? "dev" : "production"
1885
+ });
1886
+ }
1887
+ return packages;
1888
+ }
1889
+ function extractPomProperties(content) {
1890
+ const props = /* @__PURE__ */ new Map();
1891
+ const propsMatch = /<properties>([\s\S]*?)<\/properties>/i.exec(content);
1892
+ if (!propsMatch) return props;
1893
+ const propRegex = /<([a-zA-Z0-9._-]+)>\s*([^<]+)\s*<\/\1>/g;
1894
+ for (const m of propsMatch[1].matchAll(propRegex)) {
1895
+ props.set(m[1], m[2].trim());
1896
+ }
1897
+ return props;
1898
+ }
1899
+ function extractDependencyManagement(content, props) {
1900
+ const versions = /* @__PURE__ */ new Map();
1901
+ const mgmtMatch = /<dependencyManagement>([\s\S]*?)<\/dependencyManagement>/i.exec(content);
1902
+ if (!mgmtMatch) return versions;
1903
+ const depRegex = /<dependency>([\s\S]*?)<\/dependency>/gi;
1904
+ for (const m of mgmtMatch[1].matchAll(depRegex)) {
1905
+ const inner = m[1];
1906
+ const groupId = /<groupId>\s*([^<\s]+)\s*<\/groupId>/i.exec(inner)?.[1];
1907
+ const artifactId = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/i.exec(inner)?.[1];
1908
+ const rawVersion = /<version>\s*([^<\s]+)\s*<\/version>/i.exec(inner)?.[1];
1909
+ if (!groupId || !artifactId || !rawVersion) continue;
1910
+ const resolved = resolveProperty(rawVersion, props);
1911
+ if (resolved) versions.set(`${groupId}:${artifactId}`, resolved);
1912
+ }
1913
+ return versions;
1914
+ }
1915
+ function resolveProperty(value, props) {
1916
+ if (!value) return null;
1917
+ const resolved = value.replace(/\$\{([^}]+)\}/g, (_, key) => props.get(key) ?? `\${${key}}`);
1918
+ return /\$\{[^}]+\}/.test(resolved) ? null : resolved;
1919
+ }
1920
+ var GRADLE_DEV_CONFIGS = /* @__PURE__ */ new Set(["testImplementation", "testCompileOnly", "testRuntimeOnly", "testApi"]);
1921
+ function parseBuildGradle(content, filePath, opts) {
1922
+ const packages = [];
1923
+ const seen = /* @__PURE__ */ new Set();
1924
+ const stringNotation = /\b(\w+)\s*\(?\s*['"]([^:'"]+):([^:'"]+):([^'"]+)['"]\s*\)?/g;
1925
+ for (const m of content.matchAll(stringNotation)) {
1926
+ const config = m[1];
1927
+ const groupId = m[2];
1928
+ const artifactId = m[3];
1929
+ const version = m[4].split("@")[0];
1930
+ const isDev = GRADLE_DEV_CONFIGS.has(config);
1931
+ if (isDev && !opts.includeDev) continue;
1932
+ const name = `${groupId}:${artifactId}`;
1933
+ const key = `${name}@${version}`;
1934
+ if (seen.has(key)) continue;
1935
+ seen.add(key);
1936
+ packages.push({
1937
+ name,
1938
+ version,
1939
+ ecosystem: "maven",
1940
+ source: filePath,
1941
+ type: "direct",
1942
+ scope: isDev ? "dev" : "production"
1943
+ });
1944
+ }
1945
+ const mapNotation = /\b(\w+)\s+group:\s*['"]([^'"]+)['"]\s*,\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]/g;
1946
+ for (const m of content.matchAll(mapNotation)) {
1947
+ const config = m[1];
1948
+ const groupId = m[2];
1949
+ const artifactId = m[3];
1950
+ const version = m[4];
1951
+ const isDev = GRADLE_DEV_CONFIGS.has(config);
1952
+ if (isDev && !opts.includeDev) continue;
1953
+ const name = `${groupId}:${artifactId}`;
1954
+ const key = `${name}@${version}`;
1955
+ if (seen.has(key)) continue;
1956
+ seen.add(key);
1957
+ packages.push({
1958
+ name,
1959
+ version,
1960
+ ecosystem: "maven",
1961
+ source: filePath,
1962
+ type: "direct",
1963
+ scope: isDev ? "dev" : "production"
1964
+ });
1965
+ }
1966
+ return packages;
1967
+ }
1968
+ function parseGradleLock(content, filePath, opts) {
1969
+ const packages = [];
1970
+ const seen = /* @__PURE__ */ new Set();
1971
+ const lineRegex = /^([^:#\s]+):([^:#\s]+):([^=\s]+)=(.+)$/gm;
1972
+ for (const m of content.matchAll(lineRegex)) {
1973
+ const groupId = m[1];
1974
+ const artifactId = m[2];
1975
+ const version = m[3];
1976
+ const configs = m[4].split(",").map((s) => s.trim());
1977
+ const isDev = configs.every((c) => GRADLE_DEV_CONFIGS.has(c));
1978
+ if (isDev && !opts.includeDev) continue;
1979
+ const name = `${groupId}:${artifactId}`;
1980
+ const key = `${name}@${version}`;
1981
+ if (seen.has(key)) continue;
1982
+ seen.add(key);
1983
+ packages.push({
1984
+ name,
1985
+ version,
1986
+ ecosystem: "maven",
1987
+ source: filePath,
1988
+ type: "direct",
1989
+ scope: isDev ? "dev" : "production"
1990
+ });
1991
+ }
1992
+ return packages;
1993
+ }
1994
+
1995
+ // src/services/deps/parsers/index.ts
1996
+ function getRepoFiles(repoRoot) {
1997
+ try {
1998
+ const out = execSync4("git ls-files --cached --others --exclude-standard", {
1999
+ encoding: "utf8",
2000
+ cwd: repoRoot
2001
+ });
2002
+ return out.trim().split("\n").filter(Boolean);
2003
+ } catch {
2004
+ return [];
2005
+ }
2006
+ }
2007
+ function readFile(repoRoot, relPath) {
2008
+ try {
2009
+ return fs2.readFileSync(path5.join(repoRoot, relPath), "utf8");
2010
+ } catch {
2011
+ return null;
2012
+ }
2013
+ }
2014
+ function readFileAtRef(repoRoot, ref, relPath) {
2015
+ try {
2016
+ return execFileSync2("git", ["show", `${ref}:${relPath}`], {
2017
+ encoding: "utf8",
2018
+ cwd: repoRoot,
2019
+ stdio: ["pipe", "pipe", "pipe"]
2020
+ });
2021
+ } catch {
2022
+ return null;
2023
+ }
2024
+ }
2025
+ function findPropsContent(manifestFile, readFn) {
2026
+ let dir = path5.dirname(manifestFile);
2027
+ for (let depth = 0; depth < 10; depth++) {
2028
+ const candidate = dir === "." ? "Directory.Packages.props" : `${dir}/Directory.Packages.props`;
2029
+ const content = readFn(candidate);
2030
+ if (content) return content;
2031
+ if (dir === ".") break;
2032
+ dir = path5.dirname(dir);
2033
+ }
2034
+ return null;
2035
+ }
2036
+ function parseManifests(manifests, opts, readFn) {
2037
+ const allPackages = [];
2038
+ const seen = /* @__PURE__ */ new Set();
2039
+ for (const manifest of manifests) {
2040
+ const content = readFn(manifest.file);
2041
+ if (!content) continue;
2042
+ const lockContent = manifest.lockFile ? readFn(manifest.lockFile) ?? void 0 : void 0;
2043
+ let pkgs = [];
2044
+ if (manifest.ecosystem === "npm") {
2045
+ pkgs = parseNpmPackages(content, manifest, opts, lockContent);
2046
+ } else if (manifest.ecosystem === "nuget") {
2047
+ const propsContent = findPropsContent(manifest.file, readFn) ?? void 0;
2048
+ pkgs = parseNugetPackages(content, manifest, opts, lockContent, propsContent);
2049
+ } else if (manifest.ecosystem === "maven") {
2050
+ pkgs = parseMavenPackages(content, manifest, opts, lockContent);
2051
+ }
2052
+ for (const pkg2 of pkgs) {
2053
+ const key = `${pkg2.ecosystem}:${pkg2.name}@${pkg2.version}:${pkg2.source}`;
2054
+ if (!seen.has(key)) {
2055
+ seen.add(key);
2056
+ allPackages.push(pkg2);
2057
+ }
2058
+ }
2059
+ }
2060
+ return allPackages;
2061
+ }
2062
+ function scanRepo(repoRoot, opts = {}) {
2063
+ const files = getRepoFiles(repoRoot);
2064
+ const eco = opts.ecosystem ?? "all";
2065
+ const npmManifests = eco === "all" || eco === "npm" ? findNpmManifests(files) : [];
2066
+ const nugetManifests = eco === "all" || eco === "nuget" ? findNugetManifests(files) : [];
2067
+ const mavenManifests = eco === "all" || eco === "maven" ? findMavenManifests(files) : [];
2068
+ const manifests = [...npmManifests, ...nugetManifests, ...mavenManifests];
2069
+ const packages = parseManifests(manifests, opts, (rel) => readFile(repoRoot, rel));
2070
+ return buildScanData(manifests, packages);
2071
+ }
2072
+ function scanRepoAtRef(repoRoot, ref, opts = {}) {
2073
+ const eco = opts.ecosystem ?? "all";
2074
+ let files = [];
2075
+ try {
2076
+ const out = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref], {
2077
+ encoding: "utf8",
2078
+ cwd: repoRoot,
2079
+ stdio: ["pipe", "pipe", "pipe"]
2080
+ });
2081
+ files = out.trim().split("\n").filter(Boolean);
2082
+ } catch {
2083
+ return buildScanData([], []);
2084
+ }
2085
+ const npmManifests = eco === "all" || eco === "npm" ? findNpmManifests(files) : [];
2086
+ const nugetManifests = eco === "all" || eco === "nuget" ? findNugetManifests(files) : [];
2087
+ const mavenManifests = eco === "all" || eco === "maven" ? findMavenManifests(files) : [];
2088
+ const manifests = [...npmManifests, ...nugetManifests, ...mavenManifests];
2089
+ const packages = parseManifests(manifests, opts, (rel) => readFileAtRef(repoRoot, ref, rel));
2090
+ return buildScanData(manifests, packages);
2091
+ }
2092
+ function buildScanData(manifests, packages) {
2093
+ const ecosystemSet = new Set(manifests.map((m) => m.ecosystem));
2094
+ const byEcosystem = {};
2095
+ const byType = { direct: 0, transitive: 0 };
2096
+ const byScope = { production: 0, dev: 0 };
2097
+ for (const pkg2 of packages) {
2098
+ byEcosystem[pkg2.ecosystem] = (byEcosystem[pkg2.ecosystem] ?? 0) + 1;
2099
+ byType[pkg2.type] = (byType[pkg2.type] ?? 0) + 1;
2100
+ byScope[pkg2.scope] = (byScope[pkg2.scope] ?? 0) + 1;
2101
+ }
2102
+ return {
2103
+ ecosystems: Array.from(ecosystemSet),
2104
+ manifests,
2105
+ packages,
2106
+ summary: {
2107
+ totalPackages: packages.length,
2108
+ byEcosystem,
2109
+ byType,
2110
+ byScope
2111
+ }
2112
+ };
2113
+ }
2114
+
2115
+ // src/services/deps/scan.ts
2116
+ function runScan(config, opts) {
2117
+ void config;
2118
+ const repoRoot = getRepoRoot2();
2119
+ if (!repoRoot) {
2120
+ throw new PncliError("Not inside a git repository. deps commands require a git repo root.", 1);
2121
+ }
2122
+ return scanRepo(repoRoot, opts);
2123
+ }
2124
+
2125
+ // src/services/deps/semver.ts
2126
+ function parse(v) {
2127
+ const clean = v.replace(/^[^0-9]*/, "");
2128
+ const dashIdx = clean.indexOf("-");
2129
+ const plusIdx = clean.indexOf("+");
2130
+ const metaStart = plusIdx > -1 ? plusIdx : Infinity;
2131
+ const preStart = dashIdx > -1 && dashIdx < metaStart ? dashIdx : Infinity;
2132
+ const numeric = clean.slice(0, Math.min(preStart, metaStart));
2133
+ const preRelease = preStart < Infinity ? clean.slice(preStart + 1, metaStart < Infinity ? metaStart : void 0) : "";
2134
+ const parts = numeric.split(".").map((n) => parseInt(n, 10) || 0);
2135
+ return { major: parts[0] ?? 0, minor: parts[1] ?? 0, patch: parts[2] ?? 0, preRelease };
2136
+ }
2137
+ function compareSemver(a, b) {
2138
+ const av = parse(a);
2139
+ const bv = parse(b);
2140
+ if (av.major !== bv.major) return av.major - bv.major;
2141
+ if (av.minor !== bv.minor) return av.minor - bv.minor;
2142
+ if (av.patch !== bv.patch) return av.patch - bv.patch;
2143
+ if (!av.preRelease && bv.preRelease) return 1;
2144
+ if (av.preRelease && !bv.preRelease) return -1;
2145
+ if (av.preRelease && bv.preRelease) return av.preRelease.localeCompare(bv.preRelease);
2146
+ return 0;
2147
+ }
2148
+ function isNewer(current, latest) {
2149
+ return compareSemver(latest, current) > 0;
2150
+ }
2151
+ function updateType(current, latest) {
2152
+ const cv = parse(current);
2153
+ const lv = parse(latest);
2154
+ if (lv.major > cv.major) return "major";
2155
+ if (lv.minor > cv.minor) return "minor";
2156
+ return "patch";
2157
+ }
2158
+ function isDowngrade(from, to) {
2159
+ return compareSemver(to, from) < 0;
2160
+ }
2161
+
2162
+ // src/services/deps/diff.ts
2163
+ function runDiff(config, from, to, opts) {
2164
+ void config;
2165
+ const repoRoot = getRepoRoot2();
2166
+ if (!repoRoot) {
2167
+ throw new PncliError("Not inside a git repository.", 1);
2168
+ }
2169
+ const fromScan = scanRepoAtRef(repoRoot, from, opts);
2170
+ const toScan = to ? scanRepoAtRef(repoRoot, to, opts) : scanRepo(repoRoot, opts);
2171
+ const fromMap = /* @__PURE__ */ new Map();
2172
+ const toMap = /* @__PURE__ */ new Map();
2173
+ for (const pkg2 of fromScan.packages) {
2174
+ const key = `${pkg2.ecosystem}:${pkg2.name}:${pkg2.source}`;
2175
+ fromMap.set(key, pkg2.version);
2176
+ }
2177
+ for (const pkg2 of toScan.packages) {
2178
+ const key = `${pkg2.ecosystem}:${pkg2.name}:${pkg2.source}`;
2179
+ toMap.set(key, pkg2.version);
2180
+ }
2181
+ const changes = [];
2182
+ const allKeys = /* @__PURE__ */ new Set([...fromMap.keys(), ...toMap.keys()]);
2183
+ for (const key of allKeys) {
2184
+ const parts = key.split(":");
2185
+ const eco = parts[0];
2186
+ const source = parts[parts.length - 1] ?? "";
2187
+ const name = parts.slice(1, -1).join(":");
2188
+ const fromVer = fromMap.get(key) ?? null;
2189
+ const toVer = toMap.get(key) ?? null;
2190
+ let change;
2191
+ if (!fromVer) {
2192
+ change = "added";
2193
+ } else if (!toVer) {
2194
+ change = "removed";
2195
+ } else if (fromVer === toVer) {
2196
+ continue;
2197
+ } else {
2198
+ change = isDowngrade(fromVer, toVer) ? "downgraded" : "upgraded";
2199
+ }
2200
+ changes.push({ name, ecosystem: eco, change, from: fromVer, to: toVer, source });
2201
+ }
2202
+ const summary = { added: 0, removed: 0, upgraded: 0, downgraded: 0, unchanged: 0 };
2203
+ for (const c of changes) summary[c.change]++;
2204
+ summary.unchanged = [...allKeys].filter((k) => {
2205
+ const fv = fromMap.get(k);
2206
+ const tv = toMap.get(k);
2207
+ return fv !== void 0 && tv !== void 0 && fv === tv;
2208
+ }).length;
2209
+ return { from, to: to ?? "working tree", changes, summary };
2210
+ }
2211
+
2212
+ // src/services/deps/clients/osv.ts
2213
+ var OSV_URL = "https://api.osv.dev";
2214
+ var BATCH_SIZE = 500;
2215
+ var TIMEOUT_MS = 3e4;
2216
+ var OSV_ECOSYSTEM = {
2217
+ npm: "npm",
2218
+ nuget: "NuGet",
2219
+ maven: "Maven"
2220
+ };
2221
+ async function fetchWithTimeout2(url, init) {
2222
+ const controller = new AbortController();
2223
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
2224
+ try {
2225
+ return await fetch(url, { ...init, signal: controller.signal });
2226
+ } finally {
2227
+ clearTimeout(timer);
2228
+ }
2229
+ }
2230
+ async function checkOsvConnectivity() {
2231
+ try {
2232
+ const res = await fetchWithTimeout2(`${OSV_URL}/v1/querybatch`, {
2233
+ method: "POST",
2234
+ headers: { "Content-Type": "application/json" },
2235
+ body: JSON.stringify({ queries: [] })
2236
+ });
2237
+ return { reachable: res.ok || res.status === 400 };
2238
+ } catch (err) {
2239
+ return {
2240
+ reachable: false,
2241
+ error: err instanceof Error ? err.message : String(err)
2242
+ };
2243
+ }
2244
+ }
2245
+ function extractRemediation(vuln, pkg2) {
2246
+ const fixedVersions = [];
2247
+ for (const affected of vuln.affected ?? []) {
2248
+ for (const range of affected.ranges ?? []) {
2249
+ if (range.type === "SEMVER" || range.type === "ECOSYSTEM") {
2250
+ for (const event of range.events ?? []) {
2251
+ if (event.fixed) fixedVersions.push(event.fixed);
2252
+ }
2253
+ }
2254
+ }
2255
+ }
2256
+ const unique = [...new Set(fixedVersions)];
2257
+ const fixAvailable = unique.length > 0;
2258
+ const advice = fixAvailable ? `Upgrade ${pkg2.name} to ${unique.join(" or ")}` : `No fix available yet for ${pkg2.name}. Monitor ${vuln.id} for updates.`;
2259
+ return { fixAvailable, fixedVersions: unique, advice };
2260
+ }
2261
+ function extractSeverity(vuln) {
2262
+ const dbSpecific = vuln.database_specific ?? {};
2263
+ const dbSeverity = typeof dbSpecific["severity"] === "string" ? dbSpecific["severity"] : void 0;
2264
+ let cvss = null;
2265
+ const rawCvss = dbSpecific["cvss"];
2266
+ if (typeof rawCvss === "number") {
2267
+ cvss = rawCvss;
2268
+ } else if (rawCvss && typeof rawCvss === "object" && "score" in rawCvss) {
2269
+ const score = rawCvss["score"];
2270
+ if (typeof score === "number") cvss = score;
2271
+ }
2272
+ for (const s of vuln.severity ?? []) {
2273
+ const score = parseFloat(s.score);
2274
+ if (!isNaN(score) && score >= 0 && score <= 10) {
2275
+ cvss = score;
2276
+ break;
2277
+ }
2278
+ }
2279
+ return {
2280
+ severity: dbSeverity ?? inferSeverityFromCvss(cvss),
2281
+ cvss
2282
+ };
2283
+ }
2284
+ function inferSeverityFromCvss(cvss) {
2285
+ if (cvss === null) return "UNKNOWN";
2286
+ if (cvss >= 9) return "CRITICAL";
2287
+ if (cvss >= 7) return "HIGH";
2288
+ if (cvss >= 4) return "MEDIUM";
2289
+ return "LOW";
2290
+ }
2291
+ function mapVuln(vuln, pkg2) {
2292
+ const { severity, cvss } = extractSeverity(vuln);
2293
+ return {
2294
+ id: vuln.id,
2295
+ summary: vuln.summary ?? "",
2296
+ severity,
2297
+ cvss,
2298
+ aliases: vuln.aliases ?? [],
2299
+ remediation: extractRemediation(vuln, pkg2),
2300
+ references: (vuln.references ?? []).map((r) => r.url ?? "").filter(Boolean)
2301
+ };
2302
+ }
2303
+ async function queryBatch(queries, packages) {
2304
+ const res = await fetchWithTimeout2(`${OSV_URL}/v1/querybatch`, {
2305
+ method: "POST",
2306
+ headers: { "Content-Type": "application/json" },
2307
+ body: JSON.stringify({ queries })
2308
+ });
2309
+ if (!res.ok) {
2310
+ throw new Error(`OSV.dev returned HTTP ${res.status}`);
2311
+ }
2312
+ const data = await res.json();
2313
+ const vulnerable = [];
2314
+ for (let i = 0; i < data.results.length; i++) {
2315
+ const result = data.results[i];
2316
+ const pkg2 = packages[i];
2317
+ if (!result || !pkg2) continue;
2318
+ const vulns = result.vulns ?? [];
2319
+ if (vulns.length === 0) continue;
2320
+ vulnerable.push({
2321
+ ...pkg2,
2322
+ vulnerabilities: vulns.map((v) => mapVuln(v, pkg2))
2323
+ });
2324
+ }
2325
+ return vulnerable;
2326
+ }
2327
+ async function checkPackagesForVulns(packages) {
2328
+ const allVulnerable = [];
2329
+ for (let i = 0; i < packages.length; i += BATCH_SIZE) {
2330
+ const chunk = packages.slice(i, i + BATCH_SIZE);
2331
+ const queries = chunk.map((pkg2) => ({
2332
+ package: {
2333
+ name: pkg2.name,
2334
+ ecosystem: OSV_ECOSYSTEM[pkg2.ecosystem] ?? pkg2.ecosystem
2335
+ },
2336
+ version: pkg2.version
2337
+ }));
2338
+ const results = await queryBatch(queries, chunk);
2339
+ allVulnerable.push(...results);
2340
+ }
2341
+ return allVulnerable;
2342
+ }
2343
+
2344
+ // src/services/deps/clients/artifactory.ts
2345
+ var TIMEOUT_MS2 = 15e3;
2346
+ async function fetchWithTimeout3(url, token) {
2347
+ const controller = new AbortController();
2348
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS2);
2349
+ try {
2350
+ return await fetch(url, {
2351
+ headers: { Authorization: `Bearer ${token}` },
2352
+ signal: controller.signal
2353
+ });
2354
+ } finally {
2355
+ clearTimeout(timer);
2356
+ }
2357
+ }
2358
+ async function checkArtifactoryConnectivity(config) {
2359
+ if (!config.baseUrl && !config.token) {
2360
+ return {
2361
+ reachable: false,
2362
+ authenticated: false,
2363
+ configured: false,
2364
+ error: "Artifactory is not configured"
2365
+ };
2366
+ }
2367
+ if (!config.baseUrl) {
2368
+ return {
2369
+ reachable: false,
2370
+ authenticated: false,
2371
+ configured: false,
2372
+ error: "Artifactory baseUrl is not set"
2373
+ };
2374
+ }
2375
+ if (!config.token) {
2376
+ return {
2377
+ reachable: false,
2378
+ authenticated: false,
2379
+ configured: false,
2380
+ error: "Artifactory token is not set"
2381
+ };
2382
+ }
2383
+ try {
2384
+ const res = await fetchWithTimeout3(`${config.baseUrl}/api/system/ping`, config.token);
2385
+ if (res.status === 401 || res.status === 403) {
2386
+ return {
2387
+ reachable: true,
2388
+ authenticated: false,
2389
+ configured: true,
2390
+ error: "Artifactory token is invalid or lacks permissions"
2391
+ };
2392
+ }
2393
+ return { reachable: res.ok, authenticated: res.ok, configured: true };
2394
+ } catch (err) {
2395
+ return {
2396
+ reachable: false,
2397
+ authenticated: false,
2398
+ configured: true,
2399
+ error: err instanceof Error ? err.message : String(err)
2400
+ };
2401
+ }
2402
+ }
2403
+ function requireArtifactory(config, command) {
2404
+ if (!config.baseUrl && !config.token) {
2405
+ throw new PncliError(
2406
+ `${command} requires Artifactory. Add the following to ~/.pncli/config.json:
2407
+ "artifactory": { "baseUrl": "https://artifactory.company.com", "token": "your-token" }
2408
+ Or set PNCLI_ARTIFACTORY_BASE_URL and PNCLI_ARTIFACTORY_TOKEN environment variables.`,
2409
+ 503
2410
+ );
2411
+ }
2412
+ if (!config.baseUrl) {
2413
+ throw new PncliError(
2414
+ `${command} requires an Artifactory URL. Set artifactory.baseUrl in ~/.pncli/config.json or PNCLI_ARTIFACTORY_BASE_URL.`,
2415
+ 503
2416
+ );
2417
+ }
2418
+ if (!config.token) {
2419
+ throw new PncliError(
2420
+ `${command} requires an Artifactory token. Set artifactory.token in ~/.pncli/config.json or PNCLI_ARTIFACTORY_TOKEN.`,
2421
+ 503
2422
+ );
2423
+ }
2424
+ }
2425
+ function repoForEcosystem(config, eco) {
2426
+ if (eco === "npm") return config.npmRepo;
2427
+ if (eco === "nuget") return config.nugetRepo;
2428
+ if (eco === "maven") return config.mavenRepo;
2429
+ return void 0;
2430
+ }
2431
+ async function getLatestNpm(baseUrl, token, repoName, packageName) {
2432
+ try {
2433
+ const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
2434
+ const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
2435
+ if (!res.ok) return null;
2436
+ const data = await res.json();
2437
+ return data["dist-tags"]?.latest ?? null;
2438
+ } catch {
2439
+ return null;
2440
+ }
2441
+ }
2442
+ async function getLatestNuget(baseUrl, token, repoName, packageName) {
2443
+ try {
2444
+ const lower = packageName.toLowerCase();
2445
+ const res = await fetchWithTimeout3(
2446
+ `${baseUrl}/api/nuget/v3/${repoName}/flatcontainer/${lower}/index.json`,
2447
+ token
1447
2448
  );
2449
+ if (!res.ok) return null;
2450
+ const data = await res.json();
2451
+ const versions = data.versions ?? [];
2452
+ return versions[versions.length - 1] ?? null;
2453
+ } catch {
2454
+ return null;
2455
+ }
2456
+ }
2457
+ async function getLatestMaven(baseUrl, token, repoName, packageName) {
2458
+ const [groupId, artifactId] = packageName.split(":");
2459
+ if (!groupId || !artifactId) return null;
2460
+ try {
2461
+ const res = await fetchWithTimeout3(
2462
+ `${baseUrl}/api/search/latestVersion?g=${encodeURIComponent(groupId)}&a=${encodeURIComponent(artifactId)}&repos=${repoName}`,
2463
+ token
2464
+ );
2465
+ if (!res.ok) return null;
2466
+ const text = await res.text();
2467
+ return text.trim() || null;
2468
+ } catch {
2469
+ return null;
2470
+ }
2471
+ }
2472
+ async function getOutdatedPackages(packages, config, filterType) {
2473
+ const { baseUrl, token } = config;
2474
+ if (!baseUrl || !token) return { outdated: [], uncheckedEcosystems: [] };
2475
+ const outdated = [];
2476
+ const unchecked = /* @__PURE__ */ new Set();
2477
+ for (const pkg2 of packages) {
2478
+ const repoName = repoForEcosystem(config, pkg2.ecosystem);
2479
+ if (!repoName) {
2480
+ unchecked.add(pkg2.ecosystem);
2481
+ continue;
2482
+ }
2483
+ let latest = null;
2484
+ if (pkg2.ecosystem === "npm") {
2485
+ latest = await getLatestNpm(baseUrl, token, repoName, pkg2.name);
2486
+ } else if (pkg2.ecosystem === "nuget") {
2487
+ latest = await getLatestNuget(baseUrl, token, repoName, pkg2.name);
2488
+ } else if (pkg2.ecosystem === "maven") {
2489
+ latest = await getLatestMaven(baseUrl, token, repoName, pkg2.name);
2490
+ }
2491
+ if (!latest || !isNewer(pkg2.version, latest)) continue;
2492
+ const type = updateType(pkg2.version, latest);
2493
+ if (filterType) {
2494
+ const order = { major: 3, minor: 2, patch: 1 };
2495
+ if (order[type] < order[filterType]) continue;
2496
+ }
2497
+ outdated.push({
2498
+ name: pkg2.name,
2499
+ ecosystem: pkg2.ecosystem,
2500
+ current: pkg2.version,
2501
+ latest,
2502
+ updateType: type,
2503
+ source: pkg2.source
2504
+ });
2505
+ }
2506
+ return { outdated, uncheckedEcosystems: [...unchecked] };
2507
+ }
2508
+ async function getLicenseNpm(baseUrl, token, repoName, packageName) {
2509
+ try {
2510
+ const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
2511
+ const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
2512
+ if (!res.ok) return null;
2513
+ const data = await res.json();
2514
+ if (typeof data.license === "string") return data.license;
2515
+ if (typeof data.license === "object" && data.license !== null) return data.license.type ?? null;
2516
+ return null;
2517
+ } catch {
2518
+ return null;
2519
+ }
2520
+ }
2521
+ async function getLicenseNuget(baseUrl, token, repoName, packageName, version) {
2522
+ try {
2523
+ const lower = packageName.toLowerCase();
2524
+ const res = await fetchWithTimeout3(
2525
+ `${baseUrl}/api/nuget/v3/${repoName}/registration/${lower}/${version}.json`,
2526
+ token
2527
+ );
2528
+ if (!res.ok) return null;
2529
+ const data = await res.json();
2530
+ return data.licenseExpression ?? (data.licenseUrl ? "See licenseUrl" : null);
2531
+ } catch {
2532
+ return null;
2533
+ }
2534
+ }
2535
+ async function getLicensedPackages(packages, config) {
2536
+ const { baseUrl, token } = config;
2537
+ if (!baseUrl || !token) return [];
2538
+ const result = [];
2539
+ for (const pkg2 of packages) {
2540
+ const repoName = repoForEcosystem(config, pkg2.ecosystem);
2541
+ let license = null;
2542
+ if (repoName) {
2543
+ if (pkg2.ecosystem === "npm") {
2544
+ license = await getLicenseNpm(baseUrl, token, repoName, pkg2.name);
2545
+ } else if (pkg2.ecosystem === "nuget") {
2546
+ license = await getLicenseNuget(baseUrl, token, repoName, pkg2.name, pkg2.version);
2547
+ }
2548
+ }
2549
+ result.push({
2550
+ name: pkg2.name,
2551
+ version: pkg2.version,
2552
+ ecosystem: pkg2.ecosystem,
2553
+ source: pkg2.source,
2554
+ license
2555
+ });
2556
+ }
2557
+ return result;
2558
+ }
2559
+
2560
+ // src/services/deps/connectivity.ts
2561
+ var cachedTier = null;
2562
+ async function detectTier(config) {
2563
+ if (cachedTier) return cachedTier;
2564
+ const [osvResult, artResult] = await Promise.all([
2565
+ checkOsvConnectivity(),
2566
+ checkArtifactoryConnectivity(config.artifactory)
2567
+ ]);
2568
+ const artifactoryReachable = artResult.reachable && artResult.authenticated;
2569
+ const osvReachable = osvResult.reachable;
2570
+ let tier = "local";
2571
+ if (artifactoryReachable) tier = "artifactory";
2572
+ if (osvReachable) tier = "full";
2573
+ cachedTier = { tier, osvReachable, artifactoryReachable };
2574
+ return cachedTier;
2575
+ }
2576
+ async function buildConnectivityData(config) {
2577
+ const [osvResult, artResult] = await Promise.all([
2578
+ checkOsvConnectivity(),
2579
+ checkArtifactoryConnectivity(config.artifactory)
2580
+ ]);
2581
+ const artCfg = config.artifactory;
2582
+ let tier = "local";
2583
+ if (artResult.reachable && artResult.authenticated) tier = "artifactory";
2584
+ if (osvResult.reachable) tier = "full";
2585
+ cachedTier = {
2586
+ tier,
2587
+ osvReachable: osvResult.reachable,
2588
+ artifactoryReachable: artResult.reachable && artResult.authenticated
2589
+ };
2590
+ return {
2591
+ artifactory: {
2592
+ reachable: artResult.reachable,
2593
+ url: artCfg.baseUrl ?? "(not configured)",
2594
+ authenticated: artResult.authenticated,
2595
+ repositories: {
2596
+ npm: artCfg.npmRepo ?? "(not configured)",
2597
+ nuget: artCfg.nugetRepo ?? "(not configured)",
2598
+ maven: artCfg.mavenRepo ?? "(not configured)"
2599
+ },
2600
+ ...artResult.error ? { error: artResult.error } : {}
2601
+ },
2602
+ osv: {
2603
+ reachable: osvResult.reachable,
2604
+ url: "https://api.osv.dev",
2605
+ ...osvResult.error ? { error: osvResult.error } : {}
2606
+ },
2607
+ tier,
2608
+ capabilities: {
2609
+ scan: true,
2610
+ diff: true,
2611
+ outdated: artResult.reachable && artResult.authenticated,
2612
+ licenseCheck: artResult.reachable && artResult.authenticated,
2613
+ cveCheck: osvResult.reachable
2614
+ }
2615
+ };
2616
+ }
2617
+
2618
+ // src/services/deps/frisk.ts
2619
+ async function runFrisk(config, opts) {
2620
+ const repoRoot = getRepoRoot2();
2621
+ if (!repoRoot) {
2622
+ throw new PncliError("Not inside a git repository.", 1);
2623
+ }
2624
+ const { tier, osvReachable } = await detectTier(config);
2625
+ if (!osvReachable) {
2626
+ throw new PncliError(
2627
+ "deps frisk requires OSV.dev access but api.osv.dev is not reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
2628
+ 503
2629
+ );
2630
+ }
2631
+ const scanOpts = {
2632
+ ...opts,
2633
+ includeTransitive: opts.includeTransitive ?? true
2634
+ };
2635
+ const scan = scanRepo(repoRoot, scanOpts);
2636
+ if (scan.packages.length === 0) {
2637
+ return { tier, scanned: 0, vulnerable: 0, packages: [] };
2638
+ }
2639
+ const vulnerable = await checkPackagesForVulns(scan.packages);
2640
+ return {
2641
+ tier,
2642
+ scanned: scan.packages.length,
2643
+ vulnerable: vulnerable.length,
2644
+ packages: vulnerable
2645
+ };
2646
+ }
2647
+
2648
+ // src/services/deps/outdated.ts
2649
+ async function runOutdated(config, opts, filterType) {
2650
+ const repoRoot = getRepoRoot2();
2651
+ if (!repoRoot) {
2652
+ throw new PncliError("Not inside a git repository.", 1);
2653
+ }
2654
+ requireArtifactory(config.artifactory, "deps outdated");
2655
+ const { artifactoryReachable } = await detectTier(config);
2656
+ if (!artifactoryReachable) {
2657
+ throw new PncliError(
2658
+ `Artifactory at ${config.artifactory.baseUrl} is not reachable or authentication failed. Run 'pncli deps connectivity' to diagnose.`,
2659
+ 503
2660
+ );
2661
+ }
2662
+ const scan = scanRepo(repoRoot, { ...opts, includeTransitive: false });
2663
+ const directPackages = scan.packages.filter((p) => p.type === "direct");
2664
+ const { outdated, uncheckedEcosystems } = await getOutdatedPackages(
2665
+ directPackages,
2666
+ config.artifactory,
2667
+ filterType
2668
+ );
2669
+ const summary = { total: outdated.length, major: 0, minor: 0, patch: 0 };
2670
+ for (const pkg2 of outdated) summary[pkg2.updateType]++;
2671
+ return {
2672
+ source: "artifactory",
2673
+ artifactoryUrl: config.artifactory.baseUrl ?? "",
2674
+ outdated,
2675
+ uncheckedEcosystems,
2676
+ summary
2677
+ };
2678
+ }
2679
+
2680
+ // src/services/deps/license-check.ts
2681
+ async function runLicenseCheck(config, opts) {
2682
+ const repoRoot = getRepoRoot2();
2683
+ if (!repoRoot) {
2684
+ throw new PncliError("Not inside a git repository.", 1);
2685
+ }
2686
+ requireArtifactory(config.artifactory, "deps license-check");
2687
+ const { artifactoryReachable } = await detectTier(config);
2688
+ if (!artifactoryReachable) {
2689
+ throw new PncliError(
2690
+ `Artifactory at ${config.artifactory.baseUrl} is not reachable or authentication failed. Run 'pncli deps connectivity' to diagnose.`,
2691
+ 503
2692
+ );
2693
+ }
2694
+ const scan = scanRepo(repoRoot, { ...opts, includeTransitive: false });
2695
+ const directPackages = scan.packages.filter((p) => p.type === "direct");
2696
+ const licensed = await getLicensedPackages(directPackages, config.artifactory);
2697
+ const byLicense = {};
2698
+ let unknown = 0;
2699
+ for (const pkg2 of licensed) {
2700
+ if (!pkg2.license) {
2701
+ unknown++;
2702
+ } else {
2703
+ byLicense[pkg2.license] = (byLicense[pkg2.license] ?? 0) + 1;
2704
+ }
2705
+ }
2706
+ return {
2707
+ source: "artifactory",
2708
+ artifactoryUrl: config.artifactory.baseUrl ?? "",
2709
+ packages: licensed,
2710
+ summary: {
2711
+ total: licensed.length,
2712
+ byLicense,
2713
+ unknown
2714
+ }
2715
+ };
2716
+ }
2717
+
2718
+ // src/services/deps/commands.ts
2719
+ function registerDepsCommands(program2) {
2720
+ const deps = program2.command("deps").description("Dependency scanning, CVE detection, and license auditing");
2721
+ deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--direct-only", "Only scan direct dependencies (default: include transitive)", false).option("--include-dev", "Include dev/test dependencies", false).action(async (opts, cmd) => {
2722
+ const startTime = Date.now();
2723
+ try {
2724
+ const globalOpts = cmd.optsWithGlobals();
2725
+ const config = loadConfig({ configPath: globalOpts.config });
2726
+ const scanOpts = {
2727
+ ecosystem: opts.ecosystem,
2728
+ includeTransitive: !opts.directOnly,
2729
+ includeDev: opts.includeDev
2730
+ };
2731
+ const data = await runFrisk(config, scanOpts);
2732
+ success(data, "deps", "frisk", startTime);
2733
+ } catch (err) {
2734
+ fail(err, "deps", "frisk", startTime);
2735
+ }
2736
+ });
2737
+ deps.command("scan").description("Inventory all dependencies from manifest files (local only, no network)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--include-transitive", "Include transitive dependencies", false).option("--include-dev", "Include dev/test dependencies", false).action((opts, cmd) => {
2738
+ const startTime = Date.now();
2739
+ try {
2740
+ const globalOpts = cmd.optsWithGlobals();
2741
+ const config = loadConfig({ configPath: globalOpts.config });
2742
+ const scanOpts = {
2743
+ ecosystem: opts.ecosystem,
2744
+ includeTransitive: opts.includeTransitive,
2745
+ includeDev: opts.includeDev
2746
+ };
2747
+ const data = runScan(config, scanOpts);
2748
+ success(data, "deps", "scan", startTime);
2749
+ } catch (err) {
2750
+ fail(err, "deps", "scan", startTime);
2751
+ }
2752
+ });
2753
+ deps.command("diff").description("Show dependency changes between two git refs (local only, no network)").requiredOption("--from <ref>", "Base git ref (commit, tag, or branch)").option("--to <ref>", "Target git ref (default: working tree)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--include-dev", "Include dev/test dependencies", false).action((opts, cmd) => {
2754
+ const startTime = Date.now();
2755
+ try {
2756
+ const globalOpts = cmd.optsWithGlobals();
2757
+ const config = loadConfig({ configPath: globalOpts.config });
2758
+ const scanOpts = {
2759
+ ecosystem: opts.ecosystem,
2760
+ includeTransitive: true,
2761
+ includeDev: opts.includeDev
2762
+ };
2763
+ const data = runDiff(config, opts.from, opts.to ?? null, scanOpts);
2764
+ success(data, "deps", "diff", startTime);
2765
+ } catch (err) {
2766
+ fail(err, "deps", "diff", startTime);
2767
+ }
2768
+ });
2769
+ deps.command("outdated").description("Check for newer versions available in Artifactory (requires Artifactory)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--major", "Only show major version bumps").option("--minor", "Only show minor version bumps or higher").option("--patch", "Only show patch version bumps or higher").action(async (opts, cmd) => {
2770
+ const startTime = Date.now();
2771
+ try {
2772
+ const globalOpts = cmd.optsWithGlobals();
2773
+ const config = loadConfig({ configPath: globalOpts.config });
2774
+ const scanOpts = { ecosystem: opts.ecosystem };
2775
+ const filterType = opts.major ? "major" : opts.minor ? "minor" : opts.patch ? "patch" : void 0;
2776
+ const data = await runOutdated(config, scanOpts, filterType);
2777
+ success(data, "deps", "outdated", startTime);
2778
+ } catch (err) {
2779
+ fail(err, "deps", "outdated", startTime);
2780
+ }
2781
+ });
2782
+ deps.command("license-check").description("Report licenses for all direct dependencies via Artifactory (requires Artifactory)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--include-dev", "Include dev/test dependencies", false).action(async (opts, cmd) => {
2783
+ const startTime = Date.now();
2784
+ try {
2785
+ const globalOpts = cmd.optsWithGlobals();
2786
+ const config = loadConfig({ configPath: globalOpts.config });
2787
+ const scanOpts = {
2788
+ ecosystem: opts.ecosystem,
2789
+ includeDev: opts.includeDev
2790
+ };
2791
+ const data = await runLicenseCheck(config, scanOpts);
2792
+ success(data, "deps", "license-check", startTime);
2793
+ } catch (err) {
2794
+ fail(err, "deps", "license-check", startTime);
2795
+ }
2796
+ });
2797
+ deps.command("connectivity").description("Test network access to Artifactory and OSV.dev, report available tier").action(async (_opts, cmd) => {
2798
+ const startTime = Date.now();
2799
+ try {
2800
+ const globalOpts = cmd.optsWithGlobals();
2801
+ const config = loadConfig({ configPath: globalOpts.config });
2802
+ const data = await buildConnectivityData(config);
2803
+ success(data, "deps", "connectivity", startTime);
2804
+ } catch (err) {
2805
+ fail(err, "deps", "connectivity", startTime);
2806
+ }
1448
2807
  });
1449
2808
  }
1450
2809
 
1451
2810
  // src/services/config/commands.ts
1452
2811
  import { input, password, confirm } from "@inquirer/prompts";
1453
- import fs2 from "fs";
2812
+ import fs3 from "fs";
1454
2813
  function registerConfigCommands(program2) {
1455
2814
  const config = program2.command("config").description("Manage pncli configuration");
1456
2815
  config.command("init").description("Interactive setup wizard").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
@@ -1464,7 +2823,7 @@ function registerConfigCommands(program2) {
1464
2823
  } catch (err) {
1465
2824
  if (err instanceof Error && err.message.includes("User force closed")) {
1466
2825
  process.stderr.write("\nSetup cancelled.\n");
1467
- process.exit(1);
2826
+ process.exit(ExitCode.GENERAL_ERROR);
1468
2827
  }
1469
2828
  fail(err, "config", "init", start);
1470
2829
  }
@@ -1525,6 +2884,38 @@ async function initGlobalConfig(start) {
1525
2884
  const bitbucketPat = await password({
1526
2885
  message: "Bitbucket personal access token:"
1527
2886
  });
2887
+ 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
+ const useArtifactory = await confirm({
2889
+ message: "Configure Artifactory for dependency commands (deps outdated, deps license-check)?",
2890
+ default: false
2891
+ });
2892
+ let artifactoryBaseUrl = "";
2893
+ let artifactoryToken = "";
2894
+ let npmRepo = "";
2895
+ let nugetRepo = "";
2896
+ let mavenRepo = "";
2897
+ if (useArtifactory) {
2898
+ artifactoryBaseUrl = await input({
2899
+ message: "Artifactory base URL (e.g. https://artifactory.company.com):",
2900
+ default: ""
2901
+ });
2902
+ artifactoryToken = await password({
2903
+ message: "Artifactory API token:"
2904
+ });
2905
+ process.stderr.write("\nConfigure which ecosystems you use (skip any that don't apply):\n");
2906
+ const useNpm = await confirm({ message: " Use npm packages from Artifactory?", default: true });
2907
+ if (useNpm) {
2908
+ npmRepo = await input({ message: " npm repository name:", default: "npm-remote" });
2909
+ }
2910
+ const useNuget = await confirm({ message: " Use NuGet packages from Artifactory?", default: false });
2911
+ if (useNuget) {
2912
+ nugetRepo = await input({ message: " NuGet repository name:", default: "nuget-remote" });
2913
+ }
2914
+ const useMaven = await confirm({ message: " Use Maven packages from Artifactory?", default: false });
2915
+ if (useMaven) {
2916
+ mavenRepo = await input({ message: " Maven repository name:", default: "libs-release" });
2917
+ }
2918
+ }
1528
2919
  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");
1529
2920
  const jiraProject = await input({
1530
2921
  message: "Default Jira project key (optional):",
@@ -1537,7 +2928,7 @@ async function initGlobalConfig(start) {
1537
2928
  });
1538
2929
  if (!confirmed) {
1539
2930
  process.stderr.write("Aborted.\n");
1540
- process.exit(0);
2931
+ process.exit(ExitCode.SUCCESS);
1541
2932
  }
1542
2933
  writeGlobalConfig({
1543
2934
  user: {
@@ -1552,6 +2943,15 @@ async function initGlobalConfig(start) {
1552
2943
  baseUrl: bitbucketBaseUrl || void 0,
1553
2944
  pat: bitbucketPat || void 0
1554
2945
  },
2946
+ ...useArtifactory ? {
2947
+ artifactory: {
2948
+ baseUrl: artifactoryBaseUrl || void 0,
2949
+ token: artifactoryToken || void 0,
2950
+ npmRepo: npmRepo || void 0,
2951
+ nugetRepo: nugetRepo || void 0,
2952
+ mavenRepo: mavenRepo || void 0
2953
+ }
2954
+ } : {},
1555
2955
  defaults: {
1556
2956
  jira: {
1557
2957
  project: jiraProject || void 0
@@ -1586,16 +2986,16 @@ async function initRepoConfig(start) {
1586
2986
  });
1587
2987
  if (!confirmed) {
1588
2988
  process.stderr.write("Aborted.\n");
1589
- process.exit(0);
2989
+ process.exit(ExitCode.SUCCESS);
1590
2990
  }
1591
- if (fs2.existsSync(".pncli.json")) {
2991
+ if (fs3.existsSync(".pncli.json")) {
1592
2992
  const overwrite = await confirm({
1593
2993
  message: ".pncli.json already exists. Overwrite?",
1594
2994
  default: false
1595
2995
  });
1596
2996
  if (!overwrite) {
1597
2997
  process.stderr.write("Aborted.\n");
1598
- process.exit(0);
2998
+ process.exit(ExitCode.SUCCESS);
1599
2999
  }
1600
3000
  }
1601
3001
  writeRepoConfig({
@@ -1636,21 +3036,21 @@ registerJiraCommands(program);
1636
3036
  registerBitbucketCommands(program);
1637
3037
  registerConfluenceCommands(program);
1638
3038
  registerSonarCommands(program);
1639
- registerArtifactoryCommands(program);
3039
+ registerDepsCommands(program);
1640
3040
  registerConfigCommands(program);
1641
3041
  program.addHelpText("after", `
1642
3042
  Services:
1643
3043
  git Local git operations (status, diff, log, branch)
3044
+ deps Dependency scanning, CVE detection, license auditing
1644
3045
  jira Jira Data Cloud (coming soon)
1645
3046
  bitbucket Bitbucket Server (coming soon)
1646
3047
  confluence Confluence (coming soon)
1647
3048
  sonar SonarQube (coming soon)
1648
- artifactory Artifactory (coming soon)
1649
3049
  config Manage pncli configuration
1650
3050
  `);
1651
3051
  program.parseAsync(process.argv).catch((err) => {
1652
3052
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
1653
3053
  `);
1654
- process.exit(1);
3054
+ process.exit(ExitCode.GENERAL_ERROR);
1655
3055
  });
1656
3056
  //# sourceMappingURL=cli.js.map