@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/copilot-instructions.md +34 -2
- package/dist/cli.js +1441 -41
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
66
|
-
|
|
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,
|
|
398
|
-
const url = new URL(
|
|
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
|
-
|
|
444
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
545
|
+
const msg = `DRY RUN: ${init.method} ${url}
|
|
494
546
|
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
495
|
-
`)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
567
|
+
const msg = `DRY RUN: ${init.method} ${url}
|
|
515
568
|
Headers: ${JSON.stringify(safeHeaders, null, 2)}
|
|
516
|
-
`)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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/
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
2989
|
+
process.exit(ExitCode.SUCCESS);
|
|
1590
2990
|
}
|
|
1591
|
-
if (
|
|
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(
|
|
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
|
-
|
|
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(
|
|
3054
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
1655
3055
|
});
|
|
1656
3056
|
//# sourceMappingURL=cli.js.map
|