@kolatts/pncli 1.7.0 → 1.9.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/NOTICE +22 -398
- package/README.md +40 -17
- package/copilot-instructions.md +382 -20
- package/dist/{chunk-HWXXNMGE.js → chunk-QQZJQ5DP.js} +356 -9
- package/dist/chunk-QQZJQ5DP.js.map +1 -0
- package/dist/{chunk-KT3XINLJ.js → chunk-VDL32PSG.js} +3 -2
- package/dist/chunk-VDL32PSG.js.map +1 -0
- package/dist/{chunk-VEHCNGUP.js → chunk-YR6GFJMV.js} +74 -2
- package/dist/chunk-YR6GFJMV.js.map +1 -0
- package/dist/cli.js +1642 -106
- package/dist/cli.js.map +1 -1
- package/dist/{config-IJ57IIQA.js → config-3VKBM32K.js} +2 -2
- package/dist/{core-AJYPJ2Z7.js → core-SWYVXOQ4.js} +2 -2
- package/dist/{http-FC24JSK7.js → http-GVHZQCEY.js} +2 -2
- package/package.json +5 -2
- package/dist/chunk-HWXXNMGE.js.map +0 -1
- package/dist/chunk-KT3XINLJ.js.map +0 -1
- package/dist/chunk-VEHCNGUP.js.map +0 -1
- /package/dist/{config-IJ57IIQA.js.map → config-3VKBM32K.js.map} +0 -0
- /package/dist/{core-AJYPJ2Z7.js.map → core-SWYVXOQ4.js.map} +0 -0
- /package/dist/{http-FC24JSK7.js.map → http-GVHZQCEY.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
setRepoConfigValue,
|
|
8
8
|
writeGlobalConfig,
|
|
9
9
|
writeRepoConfig
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YR6GFJMV.js";
|
|
11
11
|
import {
|
|
12
12
|
ExitCode,
|
|
13
13
|
PncliError,
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
setGlobalUser,
|
|
19
19
|
success,
|
|
20
20
|
warn
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-QQZJQ5DP.js";
|
|
22
22
|
import {
|
|
23
23
|
AdoCoreClient
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-VDL32PSG.js";
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
import { Command } from "commander";
|
|
27
|
+
import { Command as Command2 } from "commander";
|
|
28
28
|
import { createRequire } from "module";
|
|
29
29
|
|
|
30
30
|
// src/services/git/client.ts
|
|
@@ -771,7 +771,7 @@ function registerJiraCommands(program2) {
|
|
|
771
771
|
fail(err, "jira", "get-issue", start);
|
|
772
772
|
}
|
|
773
773
|
});
|
|
774
|
-
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value (repeatable)", (val, acc) => [...acc, val], []).action(async (opts) => {
|
|
774
|
+
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--parent <key>", 'Parent issue key \u2014 creates an "is child of" link after creation').option("--field <Name=value>", "Custom field value (repeatable)", (val, acc) => [...acc, val], []).action(async (opts) => {
|
|
775
775
|
const start = Date.now();
|
|
776
776
|
try {
|
|
777
777
|
const { client, fieldMap } = getClientAndFields(program2);
|
|
@@ -784,6 +784,14 @@ function registerJiraCommands(program2) {
|
|
|
784
784
|
const customFieldValues = parseFieldArgs(opts.field, fieldMap);
|
|
785
785
|
const data = await client.createIssue({ project, issueType, summary: opts.summary, description: opts.description, priority, assignee: opts.assignee, labels, customFieldValues });
|
|
786
786
|
success(data, "jira", "create-issue", start);
|
|
787
|
+
if (opts.parent) {
|
|
788
|
+
try {
|
|
789
|
+
await client.linkIssue({ key: data.key, linkType: "is child of", target: opts.parent });
|
|
790
|
+
} catch (linkErr) {
|
|
791
|
+
warn(`Created ${data.key} but failed to link to parent ${opts.parent}: ${linkErr instanceof Error ? linkErr.message : linkErr}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
787
795
|
} catch (err) {
|
|
788
796
|
fail(err, "jira", "create-issue", start);
|
|
789
797
|
}
|
|
@@ -847,7 +855,7 @@ function registerJiraCommands(program2) {
|
|
|
847
855
|
fail(err, "jira", "list-comments", start);
|
|
848
856
|
}
|
|
849
857
|
});
|
|
850
|
-
jira.command("search").description("Search Jira issues with JQL").requiredOption("--jql <query>", "JQL query string").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
|
|
858
|
+
jira.command("search").description("Search Jira issues with JQL (consider --output-file for large results)").requiredOption("--jql <query>", "JQL query string").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
|
|
851
859
|
const start = Date.now();
|
|
852
860
|
try {
|
|
853
861
|
const { client, fieldMap, customFields } = getClientAndFields(program2);
|
|
@@ -883,7 +891,7 @@ function registerJiraCommands(program2) {
|
|
|
883
891
|
fail(err, "jira", "link-issue", start);
|
|
884
892
|
}
|
|
885
893
|
});
|
|
886
|
-
jira.command("fields").description("List custom fields (configured or discovered from Jira API)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").action(async (opts) => {
|
|
894
|
+
jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").action(async (opts) => {
|
|
887
895
|
const start = Date.now();
|
|
888
896
|
try {
|
|
889
897
|
if (opts.discover) {
|
|
@@ -936,7 +944,7 @@ function registerBitbucketCommands(program2) {
|
|
|
936
944
|
bb.command("list-prs").description("List pull requests").option("--state <state>", "PR state: OPEN|MERGED|DECLINED|ALL", "OPEN").option("--author <username>", "Filter by author username").option("--reviewer <username>", "Filter by reviewer username").action(async (opts) => {
|
|
937
945
|
const start = Date.now();
|
|
938
946
|
try {
|
|
939
|
-
const { client, project, repo } = getClient2(
|
|
947
|
+
const { client, project, repo } = getClient2(bb);
|
|
940
948
|
const data = await client.listPRs({ project, repo, state: opts.state, author: opts.author, reviewer: opts.reviewer });
|
|
941
949
|
success(data, "bitbucket", "list-prs", start);
|
|
942
950
|
} catch (err) {
|
|
@@ -946,7 +954,7 @@ function registerBitbucketCommands(program2) {
|
|
|
946
954
|
bb.command("get-pr").description("Get a pull request by ID").requiredOption("--id <pr-id>", "Pull request ID").action(async (opts) => {
|
|
947
955
|
const start = Date.now();
|
|
948
956
|
try {
|
|
949
|
-
const { client, project, repo } = getClient2(
|
|
957
|
+
const { client, project, repo } = getClient2(bb);
|
|
950
958
|
const data = await client.getPR(project, repo, parseInt(opts.id, 10));
|
|
951
959
|
success(data, "bitbucket", "get-pr", start);
|
|
952
960
|
} catch (err) {
|
|
@@ -956,8 +964,8 @@ function registerBitbucketCommands(program2) {
|
|
|
956
964
|
bb.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (defaults to config)").option("--description <desc>", "PR description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
|
|
957
965
|
const start = Date.now();
|
|
958
966
|
try {
|
|
959
|
-
const { client, project, repo } = getClient2(
|
|
960
|
-
const config = loadConfig({ configPath:
|
|
967
|
+
const { client, project, repo } = getClient2(bb);
|
|
968
|
+
const config = loadConfig({ configPath: bb.optsWithGlobals().config });
|
|
961
969
|
const target = opts.target ?? config.defaults.bitbucket?.targetBranch ?? "main";
|
|
962
970
|
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : [];
|
|
963
971
|
const data = await client.createPR({ project, repo, title: opts.title, source: opts.source, target, description: opts.description, reviewers });
|
|
@@ -969,7 +977,7 @@ function registerBitbucketCommands(program2) {
|
|
|
969
977
|
bb.command("update-pr").description("Update a pull request").requiredOption("--id <pr-id>", "Pull request ID").option("--title <title>", "New title").option("--description <desc>", "New description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
|
|
970
978
|
const start = Date.now();
|
|
971
979
|
try {
|
|
972
|
-
const { client, project, repo } = getClient2(
|
|
980
|
+
const { client, project, repo } = getClient2(bb);
|
|
973
981
|
const prId = parseInt(opts.id, 10);
|
|
974
982
|
const pr = await client.getPR(project, repo, prId);
|
|
975
983
|
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -982,7 +990,7 @@ function registerBitbucketCommands(program2) {
|
|
|
982
990
|
bb.command("merge-pr").description("Merge a pull request").requiredOption("--id <pr-id>", "Pull request ID").option("--strategy <strategy>", "Merge strategy: merge|squash|ff").option("--delete-branch", "Delete source branch after merge").action(async (opts) => {
|
|
983
991
|
const start = Date.now();
|
|
984
992
|
try {
|
|
985
|
-
const { client, project, repo } = getClient2(
|
|
993
|
+
const { client, project, repo } = getClient2(bb);
|
|
986
994
|
const prId = parseInt(opts.id, 10);
|
|
987
995
|
const pr = await client.getPR(project, repo, prId);
|
|
988
996
|
const data = await client.mergePR({ project, repo, id: prId, version: pr.version, strategy: opts.strategy, deleteBranch: opts.deleteBranch });
|
|
@@ -994,7 +1002,7 @@ function registerBitbucketCommands(program2) {
|
|
|
994
1002
|
bb.command("decline-pr").description("Decline a pull request").requiredOption("--id <pr-id>", "Pull request ID").action(async (opts) => {
|
|
995
1003
|
const start = Date.now();
|
|
996
1004
|
try {
|
|
997
|
-
const { client, project, repo } = getClient2(
|
|
1005
|
+
const { client, project, repo } = getClient2(bb);
|
|
998
1006
|
const prId = parseInt(opts.id, 10);
|
|
999
1007
|
const pr = await client.getPR(project, repo, prId);
|
|
1000
1008
|
const data = await client.declinePR(project, repo, prId, pr.version);
|
|
@@ -1006,7 +1014,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1006
1014
|
bb.command("list-comments").description("List comments on a pull request (includes threaded replies by default)").requiredOption("--pr <pr-id>", "Pull request ID").option("--no-with-replies", "Exclude replies; return top-level comments only").option("--inline-only", "Return only inline file comments").option("--general-only", "Return only general (non-inline) PR comments").action(async (opts) => {
|
|
1007
1015
|
const start = Date.now();
|
|
1008
1016
|
try {
|
|
1009
|
-
const { client, project, repo } = getClient2(
|
|
1017
|
+
const { client, project, repo } = getClient2(bb);
|
|
1010
1018
|
const data = await client.listComments(project, repo, parseInt(opts.pr, 10), {
|
|
1011
1019
|
withReplies: opts.withReplies,
|
|
1012
1020
|
inlineOnly: opts.inlineOnly,
|
|
@@ -1020,7 +1028,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1020
1028
|
bb.command("add-comment").description("Add a comment to a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--body <text>", "Comment text").action(async (opts) => {
|
|
1021
1029
|
const start = Date.now();
|
|
1022
1030
|
try {
|
|
1023
|
-
const { client, project, repo } = getClient2(
|
|
1031
|
+
const { client, project, repo } = getClient2(bb);
|
|
1024
1032
|
const data = await client.addComment(project, repo, parseInt(opts.pr, 10), opts.body);
|
|
1025
1033
|
success(data, "bitbucket", "add-comment", start);
|
|
1026
1034
|
} catch (err) {
|
|
@@ -1030,7 +1038,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1030
1038
|
bb.command("add-inline-comment").description("Add an inline comment to a file in a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--file <path>", "File path").requiredOption("--line <n>", "Line number").requiredOption("--body <text>", "Comment text").option("--line-type <type>", "Line type: ADDED|REMOVED|CONTEXT", "ADDED").action(async (opts) => {
|
|
1031
1039
|
const start = Date.now();
|
|
1032
1040
|
try {
|
|
1033
|
-
const { client, project, repo } = getClient2(
|
|
1041
|
+
const { client, project, repo } = getClient2(bb);
|
|
1034
1042
|
const data = await client.addInlineComment({
|
|
1035
1043
|
project,
|
|
1036
1044
|
repo,
|
|
@@ -1048,7 +1056,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1048
1056
|
bb.command("reply-comment").description("Reply to a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID to reply to").requiredOption("--body <text>", "Reply text").action(async (opts) => {
|
|
1049
1057
|
const start = Date.now();
|
|
1050
1058
|
try {
|
|
1051
|
-
const { client, project, repo } = getClient2(
|
|
1059
|
+
const { client, project, repo } = getClient2(bb);
|
|
1052
1060
|
const data = await client.replyComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), opts.body);
|
|
1053
1061
|
success(data, "bitbucket", "reply-comment", start);
|
|
1054
1062
|
} catch (err) {
|
|
@@ -1058,7 +1066,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1058
1066
|
bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
|
|
1059
1067
|
const start = Date.now();
|
|
1060
1068
|
try {
|
|
1061
|
-
const { client, project, repo } = getClient2(
|
|
1069
|
+
const { client, project, repo } = getClient2(bb);
|
|
1062
1070
|
await client.resolveComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.version ?? "0", 10));
|
|
1063
1071
|
success({ resolved: true }, "bitbucket", "resolve-comment", start);
|
|
1064
1072
|
} catch (err) {
|
|
@@ -1068,7 +1076,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1068
1076
|
bb.command("delete-comment").description("Delete a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
|
|
1069
1077
|
const start = Date.now();
|
|
1070
1078
|
try {
|
|
1071
|
-
const { client, project, repo } = getClient2(
|
|
1079
|
+
const { client, project, repo } = getClient2(bb);
|
|
1072
1080
|
await client.deleteComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.version ?? "0", 10));
|
|
1073
1081
|
success({ deleted: true }, "bitbucket", "delete-comment", start);
|
|
1074
1082
|
} catch (err) {
|
|
@@ -1078,7 +1086,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1078
1086
|
bb.command("diff").description("Get unified diff for a pull request").requiredOption("--pr <pr-id>", "Pull request ID").option("--file <path>", "Limit diff to a specific file").option("--context-lines <n>", "Lines of context around changes").action(async (opts) => {
|
|
1079
1087
|
const start = Date.now();
|
|
1080
1088
|
try {
|
|
1081
|
-
const { client, project, repo } = getClient2(
|
|
1089
|
+
const { client, project, repo } = getClient2(bb);
|
|
1082
1090
|
const contextLines = opts.contextLines ? parseInt(opts.contextLines, 10) : void 0;
|
|
1083
1091
|
const diff = await client.getDiff(project, repo, parseInt(opts.pr, 10), opts.file, contextLines);
|
|
1084
1092
|
success({ diff }, "bitbucket", "diff", start);
|
|
@@ -1089,7 +1097,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1089
1097
|
bb.command("list-files").description("List files changed in a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1090
1098
|
const start = Date.now();
|
|
1091
1099
|
try {
|
|
1092
|
-
const { client, project, repo } = getClient2(
|
|
1100
|
+
const { client, project, repo } = getClient2(bb);
|
|
1093
1101
|
const data = await client.listFiles(project, repo, parseInt(opts.pr, 10));
|
|
1094
1102
|
success(data, "bitbucket", "list-files", start);
|
|
1095
1103
|
} catch (err) {
|
|
@@ -1099,7 +1107,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1099
1107
|
bb.command("approve").description("Approve a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1100
1108
|
const start = Date.now();
|
|
1101
1109
|
try {
|
|
1102
|
-
const { client, project, repo } = getClient2(
|
|
1110
|
+
const { client, project, repo } = getClient2(bb);
|
|
1103
1111
|
const data = await client.approvePR(project, repo, parseInt(opts.pr, 10));
|
|
1104
1112
|
success(data, "bitbucket", "approve", start);
|
|
1105
1113
|
} catch (err) {
|
|
@@ -1109,7 +1117,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1109
1117
|
bb.command("unapprove").description("Remove approval from a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1110
1118
|
const start = Date.now();
|
|
1111
1119
|
try {
|
|
1112
|
-
const { client, project, repo } = getClient2(
|
|
1120
|
+
const { client, project, repo } = getClient2(bb);
|
|
1113
1121
|
const data = await client.unapprovePR(project, repo, parseInt(opts.pr, 10));
|
|
1114
1122
|
success(data, "bitbucket", "unapprove", start);
|
|
1115
1123
|
} catch (err) {
|
|
@@ -1119,7 +1127,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1119
1127
|
bb.command("needs-work").description("Mark a pull request as needs-work").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1120
1128
|
const start = Date.now();
|
|
1121
1129
|
try {
|
|
1122
|
-
const { client, project, repo } = getClient2(
|
|
1130
|
+
const { client, project, repo } = getClient2(bb);
|
|
1123
1131
|
const data = await client.needsWorkPR(project, repo, parseInt(opts.pr, 10));
|
|
1124
1132
|
success(data, "bitbucket", "needs-work", start);
|
|
1125
1133
|
} catch (err) {
|
|
@@ -1129,7 +1137,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1129
1137
|
bb.command("list-reviewers").description("List reviewers of a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1130
1138
|
const start = Date.now();
|
|
1131
1139
|
try {
|
|
1132
|
-
const { client, project, repo } = getClient2(
|
|
1140
|
+
const { client, project, repo } = getClient2(bb);
|
|
1133
1141
|
const data = await client.listReviewers(project, repo, parseInt(opts.pr, 10));
|
|
1134
1142
|
success(data, "bitbucket", "list-reviewers", start);
|
|
1135
1143
|
} catch (err) {
|
|
@@ -1139,7 +1147,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1139
1147
|
bb.command("list-builds").description("List build statuses for a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1140
1148
|
const start = Date.now();
|
|
1141
1149
|
try {
|
|
1142
|
-
const { client, project, repo } = getClient2(
|
|
1150
|
+
const { client, project, repo } = getClient2(bb);
|
|
1143
1151
|
const data = await client.listBuilds(project, repo, parseInt(opts.pr, 10));
|
|
1144
1152
|
success(data, "bitbucket", "list-builds", start);
|
|
1145
1153
|
} catch (err) {
|
|
@@ -1149,7 +1157,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1149
1157
|
bb.command("get-build-status").description("Get build status for a commit SHA").requiredOption("--commit <sha>", "Commit SHA").action(async (opts) => {
|
|
1150
1158
|
const start = Date.now();
|
|
1151
1159
|
try {
|
|
1152
|
-
const { client } = getClient2(
|
|
1160
|
+
const { client } = getClient2(bb);
|
|
1153
1161
|
const data = await client.getBuildStatus(opts.commit);
|
|
1154
1162
|
success(data, "bitbucket", "get-build-status", start);
|
|
1155
1163
|
} catch (err) {
|
|
@@ -1336,7 +1344,7 @@ function registerConfluenceCommands(program2) {
|
|
|
1336
1344
|
fail(err, "confluence", "get-page-by-title", start);
|
|
1337
1345
|
}
|
|
1338
1346
|
});
|
|
1339
|
-
confluence.command("list-pages").description("List pages in a Confluence space").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
|
|
1347
|
+
confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
|
|
1340
1348
|
const start = Date.now();
|
|
1341
1349
|
try {
|
|
1342
1350
|
const client = getClient3(program2);
|
|
@@ -1631,7 +1639,7 @@ function registerSonarCommands(program2) {
|
|
|
1631
1639
|
fail(err, "sonar", "quality-gate", start);
|
|
1632
1640
|
}
|
|
1633
1641
|
});
|
|
1634
|
-
sonar.command("issues").description("Search/list issues for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--severities <list>", "Filter by severity: BLOCKER,CRITICAL,MAJOR,MINOR,INFO (comma-separated)").option("--types <list>", "Filter by type: BUG,VULNERABILITY,CODE_SMELL (comma-separated)").option("--statuses <list>", "Filter by status: OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED (comma-separated)").option("--branch <name>", "Branch name").option("--resolved <bool>", "Filter resolved issues: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page (max 500)", "100").option("--all", "Fetch all pages (ignores --page/--page-size)").action(async (opts) => {
|
|
1642
|
+
sonar.command("issues").description("Search/list issues for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--severities <list>", "Filter by severity: BLOCKER,CRITICAL,MAJOR,MINOR,INFO (comma-separated)").option("--types <list>", "Filter by type: BUG,VULNERABILITY,CODE_SMELL (comma-separated)").option("--statuses <list>", "Filter by status: OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED (comma-separated)").option("--branch <name>", "Branch name").option("--resolved <bool>", "Filter resolved issues: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page (max 500)", "100").option("--all", "Fetch all pages (consider --output-file for large results; ignores --page/--page-size)").action(async (opts) => {
|
|
1635
1643
|
const start = Date.now();
|
|
1636
1644
|
try {
|
|
1637
1645
|
const { client, config } = getClient4(program2);
|
|
@@ -1674,7 +1682,7 @@ function registerSonarCommands(program2) {
|
|
|
1674
1682
|
fail(err, "sonar", "measures", start);
|
|
1675
1683
|
}
|
|
1676
1684
|
});
|
|
1677
|
-
sonar.command("projects").description("Search/list projects").option("--query <text>", "Search query").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
1685
|
+
sonar.command("projects").description("Search/list projects").option("--query <text>", "Search query").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
1678
1686
|
const start = Date.now();
|
|
1679
1687
|
try {
|
|
1680
1688
|
const { client } = getClient4(program2);
|
|
@@ -1693,7 +1701,7 @@ function registerSonarCommands(program2) {
|
|
|
1693
1701
|
fail(err, "sonar", "projects", start);
|
|
1694
1702
|
}
|
|
1695
1703
|
});
|
|
1696
|
-
sonar.command("hotspots").description("List security hotspots for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--status <status>", "Filter: TO_REVIEW or REVIEWED").option("--resolution <list>", "Filter: FIXED,SAFE,ACKNOWLEDGED (comma-separated)").option("--branch <name>", "Branch name").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
1704
|
+
sonar.command("hotspots").description("List security hotspots for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--status <status>", "Filter: TO_REVIEW or REVIEWED").option("--resolution <list>", "Filter: FIXED,SAFE,ACKNOWLEDGED (comma-separated)").option("--branch <name>", "Branch name").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
1697
1705
|
const start = Date.now();
|
|
1698
1706
|
try {
|
|
1699
1707
|
const { client, config } = getClient4(program2);
|
|
@@ -1901,7 +1909,7 @@ function registerSdeCommands(program2) {
|
|
|
1901
1909
|
fail(err, "sde", "whoami", start);
|
|
1902
1910
|
}
|
|
1903
1911
|
});
|
|
1904
|
-
sde.command("users").description("List users").option("--email <email>", "Filter by email address").option("--first-name <name>", "Filter by first name").option("--last-name <name>", "Filter by last name").option("--active <bool>", "Filter by active status: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
1912
|
+
sde.command("users").description("List users").option("--email <email>", "Filter by email address").option("--first-name <name>", "Filter by first name").option("--last-name <name>", "Filter by last name").option("--active <bool>", "Filter by active status: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
1905
1913
|
const start = Date.now();
|
|
1906
1914
|
try {
|
|
1907
1915
|
const { client } = getClient5(program2);
|
|
@@ -1926,7 +1934,7 @@ function registerSdeCommands(program2) {
|
|
|
1926
1934
|
fail(err, "sde", "users", start);
|
|
1927
1935
|
}
|
|
1928
1936
|
});
|
|
1929
|
-
sde.command("projects").description("List projects").option("--name <name>", "Filter by project name").option("--search <text>", "Text search on name and profile").option("--active <val>", "Filter by active status: true, false, or all").option("--ordering <field>", "Sort by: name, created, updated (prefix with - for descending)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
1937
|
+
sde.command("projects").description("List projects").option("--name <name>", "Filter by project name").option("--search <text>", "Text search on name and profile").option("--active <val>", "Filter by active status: true, false, or all").option("--ordering <field>", "Sort by: name, created, updated (prefix with - for descending)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
1930
1938
|
const start = Date.now();
|
|
1931
1939
|
try {
|
|
1932
1940
|
const { client } = getClient5(program2);
|
|
@@ -1964,7 +1972,7 @@ function registerSdeCommands(program2) {
|
|
|
1964
1972
|
fail(err, "sde", "project", start);
|
|
1965
1973
|
}
|
|
1966
1974
|
});
|
|
1967
|
-
sde.command("tasks").description("List countermeasures for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--phase <slug>", "Filter by phase slug (e.g. development, architecture-design)").option("--priority <n>", "Filter by priority (1-10)").option("--status <id>", "Filter by status ID (e.g. TS1, TS2)").option("--assigned-to <email>", "Filter by assignee email").option("--source <val>", "Filter by source: default, custom, manual, project").option("--verification <val>", "Filter by verification: pass, fail, partial, none").option("--tag <name>", "Filter by tag name").option("--accepted <bool>", "Filter by accepted status: true or false").option("--relevant <bool>", "Filter by relevant status: true or false").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references,regulation_sections").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
1975
|
+
sde.command("tasks").description("List countermeasures for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--phase <slug>", "Filter by phase slug (e.g. development, architecture-design)").option("--priority <n>", "Filter by priority (1-10)").option("--status <id>", "Filter by status ID (e.g. TS1, TS2)").option("--assigned-to <email>", "Filter by assignee email").option("--source <val>", "Filter by source: default, custom, manual, project").option("--verification <val>", "Filter by verification: pass, fail, partial, none").option("--tag <name>", "Filter by tag name").option("--accepted <bool>", "Filter by accepted status: true or false").option("--relevant <bool>", "Filter by relevant status: true or false").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references,regulation_sections").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
1968
1976
|
const start = Date.now();
|
|
1969
1977
|
try {
|
|
1970
1978
|
const { client, config } = getClient5(program2);
|
|
@@ -2009,7 +2017,7 @@ function registerSdeCommands(program2) {
|
|
|
2009
2017
|
fail(err, "sde", "task", start);
|
|
2010
2018
|
}
|
|
2011
2019
|
});
|
|
2012
|
-
sde.command("threats").description("List threats for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--severity <n>", "Filter by severity (1-10)").option("--search <text>", "Full-text search on title and threat ID").option("--ordering <field>", "Sort by: threat__severity, threat_id, status (prefix - for descending)").option("--capec-id <id>", "Filter by CAPEC attack pattern ID").option("--component-id <id>", "Filter by component ID").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
|
|
2020
|
+
sde.command("threats").description("List threats for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--severity <n>", "Filter by severity (1-10)").option("--search <text>", "Full-text search on title and threat ID").option("--ordering <field>", "Sort by: threat__severity, threat_id, status (prefix - for descending)").option("--capec-id <id>", "Filter by CAPEC attack pattern ID").option("--component-id <id>", "Filter by component ID").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
|
|
2013
2021
|
const start = Date.now();
|
|
2014
2022
|
try {
|
|
2015
2023
|
const { client, config } = getClient5(program2);
|
|
@@ -2039,6 +2047,9 @@ function registerSdeCommands(program2) {
|
|
|
2039
2047
|
});
|
|
2040
2048
|
}
|
|
2041
2049
|
|
|
2050
|
+
// src/services/deps/commands.ts
|
|
2051
|
+
import { Option } from "commander";
|
|
2052
|
+
|
|
2042
2053
|
// src/services/deps/parsers/index.ts
|
|
2043
2054
|
import fs from "fs";
|
|
2044
2055
|
import path4 from "path";
|
|
@@ -2893,11 +2904,128 @@ async function checkPackagesForVulns(packages) {
|
|
|
2893
2904
|
return allVulnerable;
|
|
2894
2905
|
}
|
|
2895
2906
|
|
|
2896
|
-
// src/services/deps/clients/
|
|
2897
|
-
var
|
|
2898
|
-
|
|
2907
|
+
// src/services/deps/clients/sonatype.ts
|
|
2908
|
+
var SONATYPE_URL = "https://ossindex.sonatype.org";
|
|
2909
|
+
var BATCH_SIZE2 = 128;
|
|
2910
|
+
var TIMEOUT_MS2 = 3e4;
|
|
2911
|
+
async function fetchWithTimeout2(url, init) {
|
|
2899
2912
|
const controller = new AbortController();
|
|
2900
2913
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS2);
|
|
2914
|
+
try {
|
|
2915
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2916
|
+
} finally {
|
|
2917
|
+
clearTimeout(timer);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
function toPurl(pkg2) {
|
|
2921
|
+
switch (pkg2.ecosystem) {
|
|
2922
|
+
case "npm": {
|
|
2923
|
+
const encodedName = pkg2.name.startsWith("@") ? pkg2.name.replace("@", "%40") : pkg2.name;
|
|
2924
|
+
return `pkg:npm/${encodedName}@${pkg2.version}`;
|
|
2925
|
+
}
|
|
2926
|
+
case "nuget":
|
|
2927
|
+
return `pkg:nuget/${pkg2.name}@${pkg2.version}`;
|
|
2928
|
+
case "maven": {
|
|
2929
|
+
const colonIdx = pkg2.name.indexOf(":");
|
|
2930
|
+
if (colonIdx !== -1) {
|
|
2931
|
+
const groupId = pkg2.name.slice(0, colonIdx);
|
|
2932
|
+
const artifactId = pkg2.name.slice(colonIdx + 1);
|
|
2933
|
+
return `pkg:maven/${encodeURIComponent(groupId)}/${encodeURIComponent(artifactId)}@${pkg2.version}`;
|
|
2934
|
+
}
|
|
2935
|
+
return `pkg:maven/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
|
|
2936
|
+
}
|
|
2937
|
+
default:
|
|
2938
|
+
return `pkg:generic/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
function inferSeverityFromCvss2(score) {
|
|
2942
|
+
if (score >= 9) return "CRITICAL";
|
|
2943
|
+
if (score >= 7) return "HIGH";
|
|
2944
|
+
if (score >= 4) return "MEDIUM";
|
|
2945
|
+
if (score > 0) return "LOW";
|
|
2946
|
+
return "UNKNOWN";
|
|
2947
|
+
}
|
|
2948
|
+
function mapSonatypeVuln(v, pkg2) {
|
|
2949
|
+
const refs = [v.reference, ...v.externalReferences ?? []].filter(Boolean);
|
|
2950
|
+
const advice = `See ${v.reference} for remediation details for ${pkg2.name}@${pkg2.version}.`;
|
|
2951
|
+
const cvePattern = /CVE-\d{4}-\d+/gi;
|
|
2952
|
+
const searchText = [v.displayName, v.title, v.description].filter(Boolean).join(" ");
|
|
2953
|
+
const aliases = [...new Set([...searchText.matchAll(cvePattern)].map((m) => m[0].toUpperCase()))];
|
|
2954
|
+
return {
|
|
2955
|
+
id: v.displayName || v.id,
|
|
2956
|
+
summary: v.title || v.description,
|
|
2957
|
+
severity: inferSeverityFromCvss2(v.cvssScore),
|
|
2958
|
+
cvss: v.cvssScore > 0 ? v.cvssScore : null,
|
|
2959
|
+
aliases,
|
|
2960
|
+
remediation: {
|
|
2961
|
+
fixAvailable: false,
|
|
2962
|
+
fixedVersions: [],
|
|
2963
|
+
advice
|
|
2964
|
+
},
|
|
2965
|
+
references: refs
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
async function queryBatch2(purls, packages) {
|
|
2969
|
+
const res = await fetchWithTimeout2(`${SONATYPE_URL}/api/v3/component-report`, {
|
|
2970
|
+
method: "POST",
|
|
2971
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2972
|
+
body: JSON.stringify({ coordinates: purls })
|
|
2973
|
+
});
|
|
2974
|
+
if (res.status === 429) {
|
|
2975
|
+
throw new Error(
|
|
2976
|
+
"Sonatype OSS Index is rate-limiting requests from this IP. Create a free account at ossindex.sonatype.org and add your credentials to your pncli config to raise the limit."
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
if (!res.ok) {
|
|
2980
|
+
throw new Error(`Sonatype OSS Index returned HTTP ${res.status}`);
|
|
2981
|
+
}
|
|
2982
|
+
const reports = await res.json();
|
|
2983
|
+
const vulnerable = [];
|
|
2984
|
+
const purlToPackage = /* @__PURE__ */ new Map();
|
|
2985
|
+
for (let i = 0; i < purls.length; i++) {
|
|
2986
|
+
purlToPackage.set(purls[i], packages[i]);
|
|
2987
|
+
}
|
|
2988
|
+
for (const report of reports) {
|
|
2989
|
+
if (!report?.coordinates) continue;
|
|
2990
|
+
const pkg2 = purlToPackage.get(report.coordinates);
|
|
2991
|
+
if (!pkg2) continue;
|
|
2992
|
+
const vulns = report.vulnerabilities ?? [];
|
|
2993
|
+
if (vulns.length === 0) continue;
|
|
2994
|
+
vulnerable.push({ ...pkg2, vulnerabilities: vulns.map((v) => mapSonatypeVuln(v, pkg2)) });
|
|
2995
|
+
}
|
|
2996
|
+
return vulnerable;
|
|
2997
|
+
}
|
|
2998
|
+
async function checkSonatypeConnectivity() {
|
|
2999
|
+
try {
|
|
3000
|
+
const res = await fetchWithTimeout2(`${SONATYPE_URL}/api/v3/component-report`, {
|
|
3001
|
+
method: "POST",
|
|
3002
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
3003
|
+
body: JSON.stringify({ coordinates: [] })
|
|
3004
|
+
});
|
|
3005
|
+
return { reachable: res.ok || res.status === 400 };
|
|
3006
|
+
} catch (err) {
|
|
3007
|
+
return {
|
|
3008
|
+
reachable: false,
|
|
3009
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
async function checkPackagesForVulnsViaSonatype(packages) {
|
|
3014
|
+
const allVulnerable = [];
|
|
3015
|
+
for (let i = 0; i < packages.length; i += BATCH_SIZE2) {
|
|
3016
|
+
const chunk = packages.slice(i, i + BATCH_SIZE2);
|
|
3017
|
+
const purls = chunk.map(toPurl);
|
|
3018
|
+
const results = await queryBatch2(purls, chunk);
|
|
3019
|
+
allVulnerable.push(...results);
|
|
3020
|
+
}
|
|
3021
|
+
return allVulnerable;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
// src/services/deps/clients/artifactory.ts
|
|
3025
|
+
var TIMEOUT_MS3 = 15e3;
|
|
3026
|
+
async function fetchWithTimeout3(url, token) {
|
|
3027
|
+
const controller = new AbortController();
|
|
3028
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS3);
|
|
2901
3029
|
try {
|
|
2902
3030
|
return await fetch(url, {
|
|
2903
3031
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -2933,7 +3061,7 @@ async function checkArtifactoryConnectivity(config) {
|
|
|
2933
3061
|
};
|
|
2934
3062
|
}
|
|
2935
3063
|
try {
|
|
2936
|
-
const res = await
|
|
3064
|
+
const res = await fetchWithTimeout3(`${config.baseUrl}/api/system/ping`, config.token);
|
|
2937
3065
|
if (res.status === 401 || res.status === 403) {
|
|
2938
3066
|
return {
|
|
2939
3067
|
reachable: true,
|
|
@@ -2983,7 +3111,7 @@ function repoForEcosystem(config, eco) {
|
|
|
2983
3111
|
async function getLatestNpm(baseUrl, token, repoName, packageName) {
|
|
2984
3112
|
try {
|
|
2985
3113
|
const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
|
|
2986
|
-
const res = await
|
|
3114
|
+
const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
|
|
2987
3115
|
if (!res.ok) return null;
|
|
2988
3116
|
const data = await res.json();
|
|
2989
3117
|
return data["dist-tags"]?.latest ?? null;
|
|
@@ -2994,7 +3122,7 @@ async function getLatestNpm(baseUrl, token, repoName, packageName) {
|
|
|
2994
3122
|
async function getLatestNuget(baseUrl, token, repoName, packageName) {
|
|
2995
3123
|
try {
|
|
2996
3124
|
const lower = packageName.toLowerCase();
|
|
2997
|
-
const res = await
|
|
3125
|
+
const res = await fetchWithTimeout3(
|
|
2998
3126
|
`${baseUrl}/api/nuget/v3/${repoName}/flatcontainer/${lower}/index.json`,
|
|
2999
3127
|
token
|
|
3000
3128
|
);
|
|
@@ -3010,7 +3138,7 @@ async function getLatestMaven(baseUrl, token, repoName, packageName) {
|
|
|
3010
3138
|
const [groupId, artifactId] = packageName.split(":");
|
|
3011
3139
|
if (!groupId || !artifactId) return null;
|
|
3012
3140
|
try {
|
|
3013
|
-
const res = await
|
|
3141
|
+
const res = await fetchWithTimeout3(
|
|
3014
3142
|
`${baseUrl}/api/search/latestVersion?g=${encodeURIComponent(groupId)}&a=${encodeURIComponent(artifactId)}&repos=${repoName}`,
|
|
3015
3143
|
token
|
|
3016
3144
|
);
|
|
@@ -3060,7 +3188,7 @@ async function getOutdatedPackages(packages, config, filterType) {
|
|
|
3060
3188
|
async function getLicenseNpm(baseUrl, token, repoName, packageName) {
|
|
3061
3189
|
try {
|
|
3062
3190
|
const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
|
|
3063
|
-
const res = await
|
|
3191
|
+
const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
|
|
3064
3192
|
if (!res.ok) return null;
|
|
3065
3193
|
const data = await res.json();
|
|
3066
3194
|
if (typeof data.license === "string") return data.license;
|
|
@@ -3073,7 +3201,7 @@ async function getLicenseNpm(baseUrl, token, repoName, packageName) {
|
|
|
3073
3201
|
async function getLicenseNuget(baseUrl, token, repoName, packageName, version) {
|
|
3074
3202
|
try {
|
|
3075
3203
|
const lower = packageName.toLowerCase();
|
|
3076
|
-
const res = await
|
|
3204
|
+
const res = await fetchWithTimeout3(
|
|
3077
3205
|
`${baseUrl}/api/nuget/v3/${repoName}/registration/${lower}/${version}.json`,
|
|
3078
3206
|
token
|
|
3079
3207
|
);
|
|
@@ -3125,15 +3253,20 @@ async function detectTier(config) {
|
|
|
3125
3253
|
cachedTier = { tier, osvReachable, artifactoryReachable };
|
|
3126
3254
|
return cachedTier;
|
|
3127
3255
|
}
|
|
3256
|
+
async function checkSonatypeReachable() {
|
|
3257
|
+
const result = await checkSonatypeConnectivity();
|
|
3258
|
+
return result.reachable;
|
|
3259
|
+
}
|
|
3128
3260
|
async function buildConnectivityData(config) {
|
|
3129
|
-
const [osvResult, artResult] = await Promise.all([
|
|
3261
|
+
const [osvResult, artResult, sonatypeResult] = await Promise.all([
|
|
3130
3262
|
checkOsvConnectivity(),
|
|
3131
|
-
checkArtifactoryConnectivity(config.artifactory)
|
|
3263
|
+
checkArtifactoryConnectivity(config.artifactory),
|
|
3264
|
+
checkSonatypeConnectivity()
|
|
3132
3265
|
]);
|
|
3133
3266
|
const artCfg = config.artifactory;
|
|
3134
3267
|
let tier = "local";
|
|
3135
3268
|
if (artResult.reachable && artResult.authenticated) tier = "artifactory";
|
|
3136
|
-
if (osvResult.reachable) tier = "full";
|
|
3269
|
+
if (osvResult.reachable || sonatypeResult.reachable) tier = "full";
|
|
3137
3270
|
cachedTier = {
|
|
3138
3271
|
tier,
|
|
3139
3272
|
osvReachable: osvResult.reachable,
|
|
@@ -3156,46 +3289,134 @@ async function buildConnectivityData(config) {
|
|
|
3156
3289
|
url: "https://api.osv.dev",
|
|
3157
3290
|
...osvResult.error ? { error: osvResult.error } : {}
|
|
3158
3291
|
},
|
|
3292
|
+
sonatype: {
|
|
3293
|
+
reachable: sonatypeResult.reachable,
|
|
3294
|
+
url: "https://ossindex.sonatype.org",
|
|
3295
|
+
...sonatypeResult.error ? { error: sonatypeResult.error } : {}
|
|
3296
|
+
},
|
|
3159
3297
|
tier,
|
|
3160
3298
|
capabilities: {
|
|
3161
3299
|
scan: true,
|
|
3162
3300
|
diff: true,
|
|
3163
3301
|
outdated: artResult.reachable && artResult.authenticated,
|
|
3164
3302
|
licenseCheck: artResult.reachable && artResult.authenticated,
|
|
3165
|
-
cveCheck: osvResult.reachable
|
|
3303
|
+
cveCheck: osvResult.reachable || sonatypeResult.reachable
|
|
3166
3304
|
}
|
|
3167
3305
|
};
|
|
3168
3306
|
}
|
|
3169
3307
|
|
|
3170
3308
|
// src/services/deps/frisk.ts
|
|
3171
|
-
|
|
3309
|
+
function toErrorMessage(err) {
|
|
3310
|
+
return err instanceof Error ? err.message : String(err);
|
|
3311
|
+
}
|
|
3312
|
+
async function runFrisk(config, opts, source = "osv") {
|
|
3172
3313
|
const repoRoot = getRepoRoot();
|
|
3173
3314
|
if (!repoRoot) {
|
|
3174
3315
|
throw new PncliError("Not inside a git repository.", 1);
|
|
3175
3316
|
}
|
|
3176
|
-
const { tier, osvReachable } = await detectTier(config);
|
|
3177
|
-
|
|
3317
|
+
const { tier: baseTier, osvReachable } = await detectTier(config);
|
|
3318
|
+
const sonatypeReachable = source !== "osv" ? await checkSonatypeReachable() : false;
|
|
3319
|
+
const tier = sonatypeReachable ? "full" : baseTier;
|
|
3320
|
+
if (source === "osv" && !osvReachable) {
|
|
3178
3321
|
throw new PncliError(
|
|
3179
3322
|
"deps frisk requires OSV.dev access but api.osv.dev is not reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
|
|
3180
3323
|
503
|
|
3181
3324
|
);
|
|
3182
3325
|
}
|
|
3326
|
+
if (source === "sonatype" && !sonatypeReachable) {
|
|
3327
|
+
throw new PncliError(
|
|
3328
|
+
"deps frisk --source sonatype requires Sonatype OSS Index access but ossindex.sonatype.org is not reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
|
|
3329
|
+
503
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
if (source === "all" && !osvReachable && !sonatypeReachable) {
|
|
3333
|
+
throw new PncliError(
|
|
3334
|
+
"deps frisk requires either OSV.dev or Sonatype OSS Index access, but neither is reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
|
|
3335
|
+
503
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
const sourceErrors = [];
|
|
3339
|
+
let sourcesQueried;
|
|
3340
|
+
if (source === "all") {
|
|
3341
|
+
if (!osvReachable) sourceErrors.push({ source: "osv", error: "api.osv.dev is not reachable \u2014 run pncli deps connectivity to diagnose." });
|
|
3342
|
+
if (!sonatypeReachable) sourceErrors.push({ source: "sonatype", error: "ossindex.sonatype.org is not reachable \u2014 run pncli deps connectivity to diagnose." });
|
|
3343
|
+
sourcesQueried = [
|
|
3344
|
+
...osvReachable ? ["osv"] : [],
|
|
3345
|
+
...sonatypeReachable ? ["sonatype"] : []
|
|
3346
|
+
];
|
|
3347
|
+
} else {
|
|
3348
|
+
sourcesQueried = [source];
|
|
3349
|
+
}
|
|
3183
3350
|
const scanOpts = {
|
|
3184
3351
|
...opts,
|
|
3185
3352
|
includeTransitive: opts.includeTransitive ?? true
|
|
3186
3353
|
};
|
|
3187
3354
|
const scan = scanRepo(repoRoot, scanOpts);
|
|
3188
3355
|
if (scan.packages.length === 0) {
|
|
3189
|
-
return { tier, scanned: 0, vulnerable: 0, packages: [] };
|
|
3356
|
+
return { tier, source, sourcesQueried, sourceErrors, scanned: 0, vulnerable: 0, packages: [] };
|
|
3357
|
+
}
|
|
3358
|
+
let vulnerable;
|
|
3359
|
+
if (source === "sonatype") {
|
|
3360
|
+
vulnerable = await checkPackagesForVulnsViaSonatype(scan.packages);
|
|
3361
|
+
} else if (source === "all") {
|
|
3362
|
+
const [osvSettled, sonatypeSettled] = await Promise.allSettled([
|
|
3363
|
+
osvReachable ? checkPackagesForVulns(scan.packages) : Promise.resolve([]),
|
|
3364
|
+
sonatypeReachable ? checkPackagesForVulnsViaSonatype(scan.packages) : Promise.resolve([])
|
|
3365
|
+
]);
|
|
3366
|
+
if (osvReachable) {
|
|
3367
|
+
if (osvSettled.status === "rejected") {
|
|
3368
|
+
sourceErrors.push({ source: "osv", error: toErrorMessage(osvSettled.reason) });
|
|
3369
|
+
sourcesQueried = sourcesQueried.filter((s) => s !== "osv");
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (sonatypeReachable) {
|
|
3373
|
+
if (sonatypeSettled.status === "rejected") {
|
|
3374
|
+
sourceErrors.push({ source: "sonatype", error: toErrorMessage(sonatypeSettled.reason) });
|
|
3375
|
+
sourcesQueried = sourcesQueried.filter((s) => s !== "sonatype");
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
if (sourcesQueried.length === 0) {
|
|
3379
|
+
throw new PncliError(
|
|
3380
|
+
"All vulnerability sources failed during scanning. Run 'pncli deps connectivity' to diagnose.",
|
|
3381
|
+
503
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
vulnerable = mergeVulnerablePackages(
|
|
3385
|
+
osvSettled.status === "fulfilled" ? osvSettled.value : [],
|
|
3386
|
+
sonatypeSettled.status === "fulfilled" ? sonatypeSettled.value : []
|
|
3387
|
+
);
|
|
3388
|
+
} else {
|
|
3389
|
+
vulnerable = await checkPackagesForVulns(scan.packages);
|
|
3190
3390
|
}
|
|
3191
|
-
const vulnerable = await checkPackagesForVulns(scan.packages);
|
|
3192
3391
|
return {
|
|
3193
3392
|
tier,
|
|
3393
|
+
source,
|
|
3394
|
+
sourcesQueried,
|
|
3395
|
+
sourceErrors,
|
|
3194
3396
|
scanned: scan.packages.length,
|
|
3195
3397
|
vulnerable: vulnerable.length,
|
|
3196
3398
|
packages: vulnerable
|
|
3197
3399
|
};
|
|
3198
3400
|
}
|
|
3401
|
+
function mergeVulnerablePackages(osvResults, sonatypeResults) {
|
|
3402
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3403
|
+
for (const pkg2 of osvResults) {
|
|
3404
|
+
const key = `${pkg2.ecosystem}:${pkg2.name}@${pkg2.version}`;
|
|
3405
|
+
byKey.set(key, { ...pkg2 });
|
|
3406
|
+
}
|
|
3407
|
+
for (const pkg2 of sonatypeResults) {
|
|
3408
|
+
const key = `${pkg2.ecosystem}:${pkg2.name}@${pkg2.version}`;
|
|
3409
|
+
const existing = byKey.get(key);
|
|
3410
|
+
if (existing) {
|
|
3411
|
+
const existingIds = new Set(existing.vulnerabilities.map((v) => v.id));
|
|
3412
|
+
const newVulns = pkg2.vulnerabilities.filter((v) => !existingIds.has(v.id));
|
|
3413
|
+
existing.vulnerabilities = [...existing.vulnerabilities, ...newVulns];
|
|
3414
|
+
} else {
|
|
3415
|
+
byKey.set(key, { ...pkg2 });
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return [...byKey.values()];
|
|
3419
|
+
}
|
|
3199
3420
|
|
|
3200
3421
|
// src/services/deps/outdated.ts
|
|
3201
3422
|
async function runOutdated(config, opts, filterType) {
|
|
@@ -3270,7 +3491,7 @@ async function runLicenseCheck(config, opts) {
|
|
|
3270
3491
|
// src/services/deps/commands.ts
|
|
3271
3492
|
function registerDepsCommands(program2) {
|
|
3272
3493
|
const deps = program2.command("deps").description("Dependency scanning, CVE detection, and license auditing");
|
|
3273
|
-
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) => {
|
|
3494
|
+
deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev or Sonatype OSS Index)").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).addOption(new Option("--source <source>", "Vulnerability source").choices(["osv", "sonatype", "all"]).default("osv")).action(async (opts, cmd) => {
|
|
3274
3495
|
const startTime = Date.now();
|
|
3275
3496
|
try {
|
|
3276
3497
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -3280,7 +3501,7 @@ function registerDepsCommands(program2) {
|
|
|
3280
3501
|
includeTransitive: !opts.directOnly,
|
|
3281
3502
|
includeDev: opts.includeDev
|
|
3282
3503
|
};
|
|
3283
|
-
const data = await runFrisk(config, scanOpts);
|
|
3504
|
+
const data = await runFrisk(config, scanOpts, opts.source);
|
|
3284
3505
|
success(data, "deps", "frisk", startTime);
|
|
3285
3506
|
} catch (err) {
|
|
3286
3507
|
fail(err, "deps", "frisk", startTime);
|
|
@@ -3346,7 +3567,7 @@ function registerDepsCommands(program2) {
|
|
|
3346
3567
|
fail(err, "deps", "license-check", startTime);
|
|
3347
3568
|
}
|
|
3348
3569
|
});
|
|
3349
|
-
deps.command("connectivity").description("Test network access to Artifactory
|
|
3570
|
+
deps.command("connectivity").description("Test network access to Artifactory, OSV.dev, and Sonatype OSS Index, report available tier").action(async (_opts, cmd) => {
|
|
3350
3571
|
const startTime = Date.now();
|
|
3351
3572
|
try {
|
|
3352
3573
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -3603,7 +3824,7 @@ function registerConfigCommands(program2) {
|
|
|
3603
3824
|
if (cfg.ado.baseUrl) {
|
|
3604
3825
|
try {
|
|
3605
3826
|
const collection = cfg.defaults.ado?.collection;
|
|
3606
|
-
const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1` : "/_apis/projectCollections?api-version=7.1";
|
|
3827
|
+
const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
3607
3828
|
await http.ado(path6);
|
|
3608
3829
|
results.ado = { ok: true, message: "connected" };
|
|
3609
3830
|
} catch (err) {
|
|
@@ -3612,6 +3833,67 @@ function registerConfigCommands(program2) {
|
|
|
3612
3833
|
} else {
|
|
3613
3834
|
results.ado = { ok: null, message: "not configured" };
|
|
3614
3835
|
}
|
|
3836
|
+
if (cfg.jenkins.baseUrl && cfg.jenkins.username && cfg.jenkins.apiToken) {
|
|
3837
|
+
try {
|
|
3838
|
+
await http.jenkins("/api/json", { params: { tree: "nodeName" } });
|
|
3839
|
+
results.jenkins = { ok: true, message: "connected" };
|
|
3840
|
+
} catch (err) {
|
|
3841
|
+
results.jenkins = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3842
|
+
}
|
|
3843
|
+
} else {
|
|
3844
|
+
results.jenkins = { ok: null, message: "not configured" };
|
|
3845
|
+
}
|
|
3846
|
+
if (cfg.artifactory.baseUrl) {
|
|
3847
|
+
try {
|
|
3848
|
+
await http.artifactoryText("/api/system/ping");
|
|
3849
|
+
results.artifactory = { ok: true, message: "connected" };
|
|
3850
|
+
} catch (err) {
|
|
3851
|
+
results.artifactory = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3852
|
+
}
|
|
3853
|
+
} else {
|
|
3854
|
+
results.artifactory = { ok: null, message: "not configured" };
|
|
3855
|
+
}
|
|
3856
|
+
if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
|
|
3857
|
+
try {
|
|
3858
|
+
await http.udeploy("/cli/application");
|
|
3859
|
+
results.udeploy = { ok: true, message: "connected" };
|
|
3860
|
+
} catch (err) {
|
|
3861
|
+
results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3862
|
+
}
|
|
3863
|
+
} else {
|
|
3864
|
+
results.udeploy = { ok: null, message: "not configured" };
|
|
3865
|
+
}
|
|
3866
|
+
if (cfg.checkmarx.baseUrl && cfg.checkmarx.username && cfg.checkmarx.password) {
|
|
3867
|
+
try {
|
|
3868
|
+
await http.checkmarx("/cxrestapi/projects");
|
|
3869
|
+
results.checkmarx = { ok: true, message: "connected" };
|
|
3870
|
+
} catch (err) {
|
|
3871
|
+
results.checkmarx = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3872
|
+
}
|
|
3873
|
+
} else {
|
|
3874
|
+
results.checkmarx = { ok: null, message: "not configured" };
|
|
3875
|
+
}
|
|
3876
|
+
const snConfigured = cfg.servicenow.baseUrl && cfg.servicenow.username && (cfg.servicenow.password || cfg.servicenow.apiToken);
|
|
3877
|
+
if (snConfigured) {
|
|
3878
|
+
try {
|
|
3879
|
+
await http.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 } });
|
|
3880
|
+
results.servicenow = { ok: true, message: "connected" };
|
|
3881
|
+
} catch (err) {
|
|
3882
|
+
results.servicenow = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3883
|
+
}
|
|
3884
|
+
} else {
|
|
3885
|
+
results.servicenow = { ok: null, message: "not configured" };
|
|
3886
|
+
}
|
|
3887
|
+
if (cfg.contrast.apiKey && cfg.contrast.serviceKey && cfg.contrast.username && cfg.contrast.orgUuid) {
|
|
3888
|
+
try {
|
|
3889
|
+
await http.contrast(`/Contrast/api/ng/${cfg.contrast.orgUuid}/applications`, { params: { limit: 1 } });
|
|
3890
|
+
results.contrast = { ok: true, message: "connected" };
|
|
3891
|
+
} catch (err) {
|
|
3892
|
+
results.contrast = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
3893
|
+
}
|
|
3894
|
+
} else {
|
|
3895
|
+
results.contrast = { ok: null, message: "not configured" };
|
|
3896
|
+
}
|
|
3615
3897
|
success(results, "config", "test", start);
|
|
3616
3898
|
} catch (err) {
|
|
3617
3899
|
fail(err, "config", "test", start);
|
|
@@ -3705,13 +3987,37 @@ function registerConfigCommands(program2) {
|
|
|
3705
3987
|
} else {
|
|
3706
3988
|
try {
|
|
3707
3989
|
const collection = cfg.defaults.ado?.collection;
|
|
3708
|
-
const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1` : "/_apis/projectCollections?api-version=7.1";
|
|
3990
|
+
const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
3709
3991
|
await http.ado(path6, { timeoutMs: 1e4 });
|
|
3710
3992
|
results.ado = { status: "valid", message: "ok" };
|
|
3711
3993
|
} catch (err) {
|
|
3712
3994
|
results.ado = categorize2(err);
|
|
3713
3995
|
}
|
|
3714
3996
|
}
|
|
3997
|
+
if (!cfg.jenkins.apiToken || !cfg.jenkins.username) {
|
|
3998
|
+
results.jenkins = { status: "blank", message: "not configured" };
|
|
3999
|
+
} else if (!cfg.jenkins.baseUrl) {
|
|
4000
|
+
results.jenkins = { status: "error", message: "baseUrl not configured" };
|
|
4001
|
+
} else {
|
|
4002
|
+
try {
|
|
4003
|
+
await http.jenkins("/api/json", { params: { tree: "nodeName" }, timeoutMs: 1e4 });
|
|
4004
|
+
results.jenkins = { status: "valid", message: "ok" };
|
|
4005
|
+
} catch (err) {
|
|
4006
|
+
results.jenkins = categorize2(err);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
|
|
4010
|
+
results.udeploy = { status: "blank", message: "not configured" };
|
|
4011
|
+
} else if (!cfg.udeploy.baseUrl) {
|
|
4012
|
+
results.udeploy = { status: "error", message: "baseUrl not configured" };
|
|
4013
|
+
} else {
|
|
4014
|
+
try {
|
|
4015
|
+
await http.udeploy("/cli/application", { timeoutMs: 1e4 });
|
|
4016
|
+
results.udeploy = { status: "valid", message: "ok" };
|
|
4017
|
+
} catch (err) {
|
|
4018
|
+
results.udeploy = categorize2(err);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
3715
4021
|
const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
|
|
3716
4022
|
if (!artResult.configured) {
|
|
3717
4023
|
results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
|
|
@@ -3722,11 +4028,48 @@ function registerConfigCommands(program2) {
|
|
|
3722
4028
|
} else {
|
|
3723
4029
|
results.artifactory = { status: "valid", message: "ok" };
|
|
3724
4030
|
}
|
|
4031
|
+
if (!cfg.checkmarx.username || !cfg.checkmarx.password) {
|
|
4032
|
+
results.checkmarx = { status: "blank", message: "not configured" };
|
|
4033
|
+
} else if (!cfg.checkmarx.baseUrl) {
|
|
4034
|
+
results.checkmarx = { status: "error", message: "baseUrl not configured" };
|
|
4035
|
+
} else {
|
|
4036
|
+
try {
|
|
4037
|
+
await http.checkmarx("/cxrestapi/projects", { timeoutMs: 1e4 });
|
|
4038
|
+
results.checkmarx = { status: "valid", message: "ok" };
|
|
4039
|
+
} catch (err) {
|
|
4040
|
+
results.checkmarx = categorize2(err);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
const snCreds = cfg.servicenow.username && (cfg.servicenow.password || cfg.servicenow.apiToken);
|
|
4044
|
+
if (!snCreds) {
|
|
4045
|
+
results.servicenow = { status: "blank", message: "not configured" };
|
|
4046
|
+
} else if (!cfg.servicenow.baseUrl) {
|
|
4047
|
+
results.servicenow = { status: "error", message: "baseUrl not configured" };
|
|
4048
|
+
} else {
|
|
4049
|
+
try {
|
|
4050
|
+
await http.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 }, timeoutMs: 1e4 });
|
|
4051
|
+
results.servicenow = { status: "valid", message: "ok" };
|
|
4052
|
+
} catch (err) {
|
|
4053
|
+
results.servicenow = categorize2(err);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
if (!cfg.contrast.apiKey || !cfg.contrast.serviceKey || !cfg.contrast.username) {
|
|
4057
|
+
results.contrast = { status: "blank", message: "not configured" };
|
|
4058
|
+
} else if (!cfg.contrast.orgUuid) {
|
|
4059
|
+
results.contrast = { status: "error", message: "orgUuid not configured" };
|
|
4060
|
+
} else {
|
|
4061
|
+
try {
|
|
4062
|
+
await http.contrast(`/Contrast/api/ng/${cfg.contrast.orgUuid}/applications`, { params: { limit: 1 }, timeoutMs: 1e4 });
|
|
4063
|
+
results.contrast = { status: "valid", message: "ok" };
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
results.contrast = categorize2(err);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
3725
4068
|
const statuses = Object.values(results).map((r) => r.status);
|
|
3726
4069
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
3727
4070
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
3728
4071
|
if (cmdOpts.output === "table") {
|
|
3729
|
-
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "artifactory"];
|
|
4072
|
+
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
|
|
3730
4073
|
const labelWidth = 14;
|
|
3731
4074
|
const statusWidth = 9;
|
|
3732
4075
|
for (const svc of services) {
|
|
@@ -3740,7 +4083,7 @@ function registerConfigCommands(program2) {
|
|
|
3740
4083
|
process.stdout.write("\n");
|
|
3741
4084
|
} else {
|
|
3742
4085
|
if (opts.pretty) {
|
|
3743
|
-
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "artifactory"];
|
|
4086
|
+
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
|
|
3744
4087
|
const labelWidth = 14;
|
|
3745
4088
|
const statusWidth = 9;
|
|
3746
4089
|
for (const svc of services) {
|
|
@@ -3951,6 +4294,236 @@ async function initGlobalConfig(start) {
|
|
|
3951
4294
|
}
|
|
3952
4295
|
}
|
|
3953
4296
|
}
|
|
4297
|
+
process.stderr.write("\n\u2500\u2500 Jenkins \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\u2500\n");
|
|
4298
|
+
const useJenkins = await confirm({
|
|
4299
|
+
message: "Configure Jenkins Data Center for pipeline operations?",
|
|
4300
|
+
default: false
|
|
4301
|
+
});
|
|
4302
|
+
let jenkinsBaseUrl = "";
|
|
4303
|
+
let jenkinsUsername = "";
|
|
4304
|
+
let jenkinsApiToken = "";
|
|
4305
|
+
if (useJenkins) {
|
|
4306
|
+
jenkinsBaseUrl = await input({
|
|
4307
|
+
message: "Jenkins base URL (e.g. https://jenkins.your-company.com):",
|
|
4308
|
+
default: ""
|
|
4309
|
+
});
|
|
4310
|
+
jenkinsUsername = await input({
|
|
4311
|
+
message: "Jenkins username:",
|
|
4312
|
+
default: ""
|
|
4313
|
+
});
|
|
4314
|
+
jenkinsApiToken = await password({
|
|
4315
|
+
message: "Jenkins API token:"
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
process.stderr.write("\n\u2500\u2500 IBM UrbanCode Deploy \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");
|
|
4319
|
+
const useUdeploy = await confirm({
|
|
4320
|
+
message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
|
|
4321
|
+
default: false
|
|
4322
|
+
});
|
|
4323
|
+
let udeployBaseUrl = "";
|
|
4324
|
+
let udeployPat = "";
|
|
4325
|
+
let udeployUsername = "";
|
|
4326
|
+
let udeployPassword = "";
|
|
4327
|
+
let udeployDefaultApp = "";
|
|
4328
|
+
let udeployDefaultEnv = "";
|
|
4329
|
+
if (useUdeploy) {
|
|
4330
|
+
udeployBaseUrl = await input({
|
|
4331
|
+
message: "UDeploy base URL (e.g. https://ucd.company.com:8443):",
|
|
4332
|
+
default: ""
|
|
4333
|
+
});
|
|
4334
|
+
udeployUsername = await input({
|
|
4335
|
+
message: "UDeploy username (leave blank to use a PAT instead):",
|
|
4336
|
+
default: ""
|
|
4337
|
+
});
|
|
4338
|
+
if (udeployUsername) {
|
|
4339
|
+
udeployPassword = await password({
|
|
4340
|
+
message: "UDeploy password:",
|
|
4341
|
+
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
4342
|
+
});
|
|
4343
|
+
} else {
|
|
4344
|
+
udeployPat = await password({
|
|
4345
|
+
message: "UDeploy personal access token:"
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
udeployDefaultApp = await input({
|
|
4349
|
+
message: "Default application name (optional):",
|
|
4350
|
+
default: ""
|
|
4351
|
+
});
|
|
4352
|
+
udeployDefaultEnv = await input({
|
|
4353
|
+
message: "Default environment name (optional):",
|
|
4354
|
+
default: ""
|
|
4355
|
+
});
|
|
4356
|
+
const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
|
|
4357
|
+
if (hasCredentials) {
|
|
4358
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
4359
|
+
try {
|
|
4360
|
+
const tempConfig = {
|
|
4361
|
+
...loadConfig(),
|
|
4362
|
+
udeploy: {
|
|
4363
|
+
baseUrl: udeployBaseUrl,
|
|
4364
|
+
pat: udeployPat || void 0,
|
|
4365
|
+
username: udeployUsername || void 0,
|
|
4366
|
+
password: udeployPassword || void 0
|
|
4367
|
+
}
|
|
4368
|
+
};
|
|
4369
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
4370
|
+
await tempHttp.udeploy("/cli/application");
|
|
4371
|
+
process.stderr.write(" Connected.\n");
|
|
4372
|
+
} catch (err) {
|
|
4373
|
+
warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
4374
|
+
warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
process.stderr.write("\n\u2500\u2500 Checkmarx \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");
|
|
4379
|
+
const useCheckmarx = await confirm({
|
|
4380
|
+
message: "Configure Checkmarx CxSAST for vulnerability scanning?",
|
|
4381
|
+
default: false
|
|
4382
|
+
});
|
|
4383
|
+
let checkmarxBaseUrl = "";
|
|
4384
|
+
let checkmarxUsername = "";
|
|
4385
|
+
let checkmarxPassword = "";
|
|
4386
|
+
if (useCheckmarx) {
|
|
4387
|
+
checkmarxBaseUrl = await input({
|
|
4388
|
+
message: "Checkmarx base URL (e.g. https://cx.company.com):",
|
|
4389
|
+
default: ""
|
|
4390
|
+
});
|
|
4391
|
+
checkmarxUsername = await input({
|
|
4392
|
+
message: "Checkmarx username:",
|
|
4393
|
+
default: ""
|
|
4394
|
+
});
|
|
4395
|
+
checkmarxPassword = await password({
|
|
4396
|
+
message: "Checkmarx password:",
|
|
4397
|
+
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
4398
|
+
});
|
|
4399
|
+
if (checkmarxBaseUrl && checkmarxUsername && checkmarxPassword) {
|
|
4400
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
4401
|
+
try {
|
|
4402
|
+
const tempConfig = {
|
|
4403
|
+
...loadConfig(),
|
|
4404
|
+
checkmarx: {
|
|
4405
|
+
baseUrl: checkmarxBaseUrl,
|
|
4406
|
+
username: checkmarxUsername,
|
|
4407
|
+
password: checkmarxPassword
|
|
4408
|
+
}
|
|
4409
|
+
};
|
|
4410
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
4411
|
+
await tempHttp.checkmarx("/cxrestapi/projects");
|
|
4412
|
+
process.stderr.write(" Connected.\n");
|
|
4413
|
+
} catch (err) {
|
|
4414
|
+
warn(`Could not connect to Checkmarx: ${err instanceof Error ? err.message : String(err)}`);
|
|
4415
|
+
warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
process.stderr.write("\n\u2500\u2500 ServiceNow \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");
|
|
4420
|
+
const useServiceNow = await confirm({
|
|
4421
|
+
message: "Configure ServiceNow for change management?",
|
|
4422
|
+
default: false
|
|
4423
|
+
});
|
|
4424
|
+
let servicenowBaseUrl = "";
|
|
4425
|
+
let servicenowUsername = "";
|
|
4426
|
+
let servicenowPassword = "";
|
|
4427
|
+
let servicenowApiToken = "";
|
|
4428
|
+
if (useServiceNow) {
|
|
4429
|
+
servicenowBaseUrl = await input({
|
|
4430
|
+
message: "ServiceNow instance URL (e.g. https://mycompany.service-now.com):",
|
|
4431
|
+
default: ""
|
|
4432
|
+
});
|
|
4433
|
+
servicenowUsername = await input({
|
|
4434
|
+
message: "ServiceNow username:",
|
|
4435
|
+
default: ""
|
|
4436
|
+
});
|
|
4437
|
+
const snUseToken = await confirm({
|
|
4438
|
+
message: "Authenticate with an API token instead of a password?",
|
|
4439
|
+
default: false
|
|
4440
|
+
});
|
|
4441
|
+
if (snUseToken) {
|
|
4442
|
+
servicenowApiToken = await password({
|
|
4443
|
+
message: "ServiceNow API token:",
|
|
4444
|
+
validate: (v) => v.length > 0 || "Token cannot be blank"
|
|
4445
|
+
});
|
|
4446
|
+
} else {
|
|
4447
|
+
servicenowPassword = await password({
|
|
4448
|
+
message: "ServiceNow password:",
|
|
4449
|
+
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
const snHasCreds = servicenowBaseUrl && servicenowUsername && (servicenowPassword || servicenowApiToken);
|
|
4453
|
+
if (snHasCreds) {
|
|
4454
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
4455
|
+
try {
|
|
4456
|
+
const tempConfig = {
|
|
4457
|
+
...loadConfig(),
|
|
4458
|
+
servicenow: {
|
|
4459
|
+
baseUrl: servicenowBaseUrl,
|
|
4460
|
+
username: servicenowUsername,
|
|
4461
|
+
password: servicenowPassword || void 0,
|
|
4462
|
+
apiToken: servicenowApiToken || void 0
|
|
4463
|
+
}
|
|
4464
|
+
};
|
|
4465
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
4466
|
+
await tempHttp.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 } });
|
|
4467
|
+
process.stderr.write(" Connected.\n");
|
|
4468
|
+
} catch (err) {
|
|
4469
|
+
warn(`Could not connect to ServiceNow: ${err instanceof Error ? err.message : String(err)}`);
|
|
4470
|
+
warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
process.stderr.write("\n\u2500\u2500 Contrast IAST \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");
|
|
4475
|
+
const useContrast = await confirm({
|
|
4476
|
+
message: "Configure Contrast Security IAST for vulnerability findings?",
|
|
4477
|
+
default: false
|
|
4478
|
+
});
|
|
4479
|
+
let contrastBaseUrl = "";
|
|
4480
|
+
let contrastOrgUuid = "";
|
|
4481
|
+
let contrastUsername = "";
|
|
4482
|
+
let contrastApiKey = "";
|
|
4483
|
+
let contrastServiceKey = "";
|
|
4484
|
+
if (useContrast) {
|
|
4485
|
+
process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
|
|
4486
|
+
contrastBaseUrl = await input({
|
|
4487
|
+
message: "Contrast base URL (leave blank for cloud: https://app.contrastsecurity.com):",
|
|
4488
|
+
default: ""
|
|
4489
|
+
});
|
|
4490
|
+
contrastOrgUuid = await input({
|
|
4491
|
+
message: "Organization UUID:",
|
|
4492
|
+
default: ""
|
|
4493
|
+
});
|
|
4494
|
+
contrastUsername = await input({
|
|
4495
|
+
message: "Username (email):",
|
|
4496
|
+
default: ""
|
|
4497
|
+
});
|
|
4498
|
+
contrastApiKey = await password({
|
|
4499
|
+
message: "API key:"
|
|
4500
|
+
});
|
|
4501
|
+
contrastServiceKey = await password({
|
|
4502
|
+
message: "Service key:"
|
|
4503
|
+
});
|
|
4504
|
+
const contrastHasCreds = contrastOrgUuid && contrastUsername && contrastApiKey && contrastServiceKey;
|
|
4505
|
+
if (contrastHasCreds) {
|
|
4506
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
4507
|
+
try {
|
|
4508
|
+
const tempConfig = {
|
|
4509
|
+
...loadConfig(),
|
|
4510
|
+
contrast: {
|
|
4511
|
+
baseUrl: contrastBaseUrl || void 0,
|
|
4512
|
+
orgUuid: contrastOrgUuid,
|
|
4513
|
+
username: contrastUsername,
|
|
4514
|
+
apiKey: contrastApiKey,
|
|
4515
|
+
serviceKey: contrastServiceKey
|
|
4516
|
+
}
|
|
4517
|
+
};
|
|
4518
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
4519
|
+
await tempHttp.contrast(`/Contrast/api/ng/${contrastOrgUuid}/applications`, { params: { limit: 1 } });
|
|
4520
|
+
process.stderr.write(" Connected.\n");
|
|
4521
|
+
} catch (err) {
|
|
4522
|
+
warn(`Could not connect to Contrast: ${err instanceof Error ? err.message : String(err)}`);
|
|
4523
|
+
warn("Config will be saved anyway. Check your credentials and re-run pncli config init or pncli config test.");
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
3954
4527
|
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");
|
|
3955
4528
|
const jiraProject = await input({
|
|
3956
4529
|
message: "Default Jira project key (optional):",
|
|
@@ -4022,6 +4595,45 @@ async function initGlobalConfig(start) {
|
|
|
4022
4595
|
...adoDiscoveredTypes.length ? { discoveredTypes: adoDiscoveredTypes } : {}
|
|
4023
4596
|
}
|
|
4024
4597
|
} : {},
|
|
4598
|
+
...useJenkins && jenkinsBaseUrl ? {
|
|
4599
|
+
jenkins: {
|
|
4600
|
+
baseUrl: jenkinsBaseUrl,
|
|
4601
|
+
...jenkinsUsername ? { username: jenkinsUsername } : {},
|
|
4602
|
+
...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
|
|
4603
|
+
}
|
|
4604
|
+
} : {},
|
|
4605
|
+
...useUdeploy && udeployBaseUrl ? {
|
|
4606
|
+
udeploy: {
|
|
4607
|
+
baseUrl: udeployBaseUrl,
|
|
4608
|
+
...udeployPat ? { pat: udeployPat } : {},
|
|
4609
|
+
...udeployUsername ? { username: udeployUsername } : {},
|
|
4610
|
+
...udeployPassword ? { password: udeployPassword } : {}
|
|
4611
|
+
}
|
|
4612
|
+
} : {},
|
|
4613
|
+
...useCheckmarx && checkmarxBaseUrl ? {
|
|
4614
|
+
checkmarx: {
|
|
4615
|
+
baseUrl: checkmarxBaseUrl,
|
|
4616
|
+
username: checkmarxUsername || void 0,
|
|
4617
|
+
password: checkmarxPassword || void 0
|
|
4618
|
+
}
|
|
4619
|
+
} : {},
|
|
4620
|
+
...useServiceNow && servicenowBaseUrl ? {
|
|
4621
|
+
servicenow: {
|
|
4622
|
+
baseUrl: servicenowBaseUrl,
|
|
4623
|
+
username: servicenowUsername || void 0,
|
|
4624
|
+
...servicenowApiToken ? { apiToken: servicenowApiToken } : {},
|
|
4625
|
+
...servicenowPassword ? { password: servicenowPassword } : {}
|
|
4626
|
+
}
|
|
4627
|
+
} : {},
|
|
4628
|
+
...useContrast && contrastOrgUuid ? {
|
|
4629
|
+
contrast: {
|
|
4630
|
+
...contrastBaseUrl ? { baseUrl: contrastBaseUrl } : {},
|
|
4631
|
+
orgUuid: contrastOrgUuid,
|
|
4632
|
+
username: contrastUsername || void 0,
|
|
4633
|
+
apiKey: contrastApiKey || void 0,
|
|
4634
|
+
serviceKey: contrastServiceKey || void 0
|
|
4635
|
+
}
|
|
4636
|
+
} : {},
|
|
4025
4637
|
defaults: {
|
|
4026
4638
|
jira: {
|
|
4027
4639
|
project: jiraProject || void 0
|
|
@@ -4041,6 +4653,12 @@ async function initGlobalConfig(start) {
|
|
|
4041
4653
|
collection: adoCollection || void 0,
|
|
4042
4654
|
project: adoProject || void 0
|
|
4043
4655
|
}
|
|
4656
|
+
} : {},
|
|
4657
|
+
...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
|
|
4658
|
+
udeploy: {
|
|
4659
|
+
application: udeployDefaultApp || void 0,
|
|
4660
|
+
environment: udeployDefaultEnv || void 0
|
|
4661
|
+
}
|
|
4044
4662
|
} : {}
|
|
4045
4663
|
}
|
|
4046
4664
|
});
|
|
@@ -4471,7 +5089,7 @@ function registerAdoWorkCommands(ado) {
|
|
|
4471
5089
|
fail(err, "ado", "work-get", start);
|
|
4472
5090
|
}
|
|
4473
5091
|
});
|
|
4474
|
-
work.command("create").description("Create a work item").requiredOption("--type <type>", "Work item type (e.g. Bug, Task, User Story)").requiredOption("--title <title>", "Work item title").option("--description <text>", "Description").option("--assignee <user>", "Assigned to (display name or email)").option("--priority <n>", "Priority (1-4)").option("--field <name=value>", "Additional field (repeatable)", (v, acc) => {
|
|
5092
|
+
work.command("create").description("Create a work item").requiredOption("--type <type>", "Work item type (e.g. Bug, Task, User Story)").requiredOption("--title <title>", "Work item title").option("--description <text>", "Description").option("--assignee <user>", "Assigned to (display name or email)").option("--priority <n>", "Priority (1-4)").option("--parent <id>", "Parent work item ID \u2014 creates a parent link after creation").option("--field <name=value>", "Additional field (repeatable)", (v, acc) => {
|
|
4475
5093
|
acc.push(v);
|
|
4476
5094
|
return acc;
|
|
4477
5095
|
}, []).action(async (opts) => {
|
|
@@ -4493,6 +5111,17 @@ function registerAdoWorkCommands(ado) {
|
|
|
4493
5111
|
];
|
|
4494
5112
|
const data = await workClient.createWorkItem(collection, project, opts.type, patch);
|
|
4495
5113
|
success(data, "ado", "work-create", start);
|
|
5114
|
+
if (opts.parent) {
|
|
5115
|
+
try {
|
|
5116
|
+
const baseUrl = config.ado.baseUrl?.replace(/\/$/, "");
|
|
5117
|
+
const targetUrl = `${baseUrl}/${encodeURIComponent(collection)}/_apis/wit/workitems/${opts.parent}`;
|
|
5118
|
+
const linkPatch = [{ op: "add", path: "/relations/-", value: { rel: "System.LinkTypes.Hierarchy-Reverse", url: targetUrl } }];
|
|
5119
|
+
await workClient.updateWorkItem(collection, data.id, linkPatch);
|
|
5120
|
+
} catch (linkErr) {
|
|
5121
|
+
warn(`Created #${data.id} but failed to link to parent ${opts.parent}: ${linkErr instanceof Error ? linkErr.message : linkErr}`);
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
return;
|
|
4496
5125
|
} catch (err) {
|
|
4497
5126
|
fail(err, "ado", "work-create", start);
|
|
4498
5127
|
}
|
|
@@ -4558,7 +5187,7 @@ function registerAdoWorkCommands(ado) {
|
|
|
4558
5187
|
fail(err, "ado", "work-link", start);
|
|
4559
5188
|
}
|
|
4560
5189
|
});
|
|
4561
|
-
work.command("search").description("Run a WIQL query").requiredOption("--wiql <query>", "WIQL query string").action(async (opts) => {
|
|
5190
|
+
work.command("search").description("Run a WIQL query (consider --output-file for large results)").requiredOption("--wiql <query>", "WIQL query string").action(async (opts) => {
|
|
4562
5191
|
const start = Date.now();
|
|
4563
5192
|
try {
|
|
4564
5193
|
const { collection, project, workClient } = getAdoContext(ado);
|
|
@@ -4617,7 +5246,7 @@ function registerAdoWorkCommands(ado) {
|
|
|
4617
5246
|
fail(err, "ado", "work-list-states", start);
|
|
4618
5247
|
}
|
|
4619
5248
|
});
|
|
4620
|
-
work.command("fields").description("List work item fields available in the collection").option("--type <type>", "Scope to fields for a specific work item type (e.g. Bug)").option("--custom-only", "Exclude System.* and Microsoft.VSTS.* fields").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered fields and aliases to ~/.pncli/config.json").action(async (opts) => {
|
|
5249
|
+
work.command("fields").description("List work item fields available in the collection (consider --output-file for large results)").option("--type <type>", "Scope to fields for a specific work item type (e.g. Bug)").option("--custom-only", "Exclude System.* and Microsoft.VSTS.* fields").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered fields and aliases to ~/.pncli/config.json").action(async (opts) => {
|
|
4621
5250
|
const start = Date.now();
|
|
4622
5251
|
try {
|
|
4623
5252
|
const { collection, project, workClient } = getAdoContext(ado);
|
|
@@ -4868,11 +5497,17 @@ function registerAdoRepoCommands(ado) {
|
|
|
4868
5497
|
fail(err, "ado", "repo-list-files", start);
|
|
4869
5498
|
}
|
|
4870
5499
|
});
|
|
4871
|
-
repo.command("diff").description("Show files changed in a pull request with change types and commit metadata").requiredOption("--pr <n>", "Pull request ID").action(async (opts) => {
|
|
5500
|
+
repo.command("diff").description("Show files changed in a pull request with change types and commit metadata").requiredOption("--pr <n>", "Pull request ID").option("--path <p>", "Filter diff to a specific file path").action(async (opts) => {
|
|
4872
5501
|
const start = Date.now();
|
|
4873
5502
|
try {
|
|
4874
5503
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
4875
5504
|
const data = await gitClient.getPRDiff(collection, project, repo2, parseInt(opts.pr, 10));
|
|
5505
|
+
if (opts.path) {
|
|
5506
|
+
if (opts.path) {
|
|
5507
|
+
const needle = opts.path.startsWith("/") ? opts.path : `/${opts.path}`;
|
|
5508
|
+
data.changes = (data.changes ?? []).filter((c) => c.item.path === needle);
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
4876
5511
|
success(data, "ado", "repo-diff", start);
|
|
4877
5512
|
} catch (err) {
|
|
4878
5513
|
fail(err, "ado", "repo-diff", start);
|
|
@@ -4903,11 +5538,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
4903
5538
|
const start = Date.now();
|
|
4904
5539
|
try {
|
|
4905
5540
|
const globalOpts = ado.optsWithGlobals();
|
|
4906
|
-
const { loadConfig: lc } = await import("./config-
|
|
5541
|
+
const { loadConfig: lc } = await import("./config-3VKBM32K.js");
|
|
4907
5542
|
const config = lc({ configPath: globalOpts.config });
|
|
4908
5543
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
4909
|
-
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-
|
|
4910
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
5544
|
+
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
5545
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-GVHZQCEY.js");
|
|
4911
5546
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
4912
5547
|
const coreClient = new AdoCoreClient2(http);
|
|
4913
5548
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -5035,7 +5670,7 @@ function registerAdoPipelineCommands(ado) {
|
|
|
5035
5670
|
fail(err, "ado", "pipeline-cancel-run", start);
|
|
5036
5671
|
}
|
|
5037
5672
|
});
|
|
5038
|
-
pipeline.command("logs").description("List or retrieve build logs").requiredOption("--id <n>", "Build ID").option("--log-id <n>", "Specific log ID (omit to list all logs)").action(async (opts) => {
|
|
5673
|
+
pipeline.command("logs").description("List or retrieve build logs (consider --output-file for large logs)").requiredOption("--id <n>", "Build ID").option("--log-id <n>", "Specific log ID (omit to list all logs)").action(async (opts) => {
|
|
5039
5674
|
const start = Date.now();
|
|
5040
5675
|
try {
|
|
5041
5676
|
const { collection, project, buildClient } = getAdoContext(ado);
|
|
@@ -5082,18 +5717,899 @@ function registerAdoCommands(program2) {
|
|
|
5082
5717
|
registerAdoPipelineCommands(ado);
|
|
5083
5718
|
}
|
|
5084
5719
|
|
|
5720
|
+
// src/services/jenkins/client.ts
|
|
5721
|
+
function jobPath(name) {
|
|
5722
|
+
return name.split("/").map(encodeURIComponent).join("/job/");
|
|
5723
|
+
}
|
|
5724
|
+
var JenkinsClient = class {
|
|
5725
|
+
constructor(http) {
|
|
5726
|
+
this.http = http;
|
|
5727
|
+
}
|
|
5728
|
+
http;
|
|
5729
|
+
async listJobs() {
|
|
5730
|
+
const data = await this.http.jenkins(
|
|
5731
|
+
"/api/json",
|
|
5732
|
+
{ params: { tree: "jobs[name,url,color,description,buildable,lastBuild[number,url,result]]" } }
|
|
5733
|
+
);
|
|
5734
|
+
return data.jobs;
|
|
5735
|
+
}
|
|
5736
|
+
async getJob(name) {
|
|
5737
|
+
return this.http.jenkins(
|
|
5738
|
+
`/job/${jobPath(name)}/api/json`,
|
|
5739
|
+
{ params: { tree: "name,url,color,description,buildable,nextBuildNumber,inQueue,lastBuild[number,url,result],builds[number,url,result,duration,timestamp,building,displayName]{0,10}" } }
|
|
5740
|
+
);
|
|
5741
|
+
}
|
|
5742
|
+
async triggerBuild(name, params) {
|
|
5743
|
+
const hasParams = params && Object.keys(params).length > 0;
|
|
5744
|
+
const endpoint = hasParams ? `/job/${jobPath(name)}/buildWithParameters` : `/job/${jobPath(name)}/build`;
|
|
5745
|
+
const res = await this.http.jenkinsRaw(endpoint, {
|
|
5746
|
+
method: "POST",
|
|
5747
|
+
params: hasParams ? params : void 0
|
|
5748
|
+
});
|
|
5749
|
+
const location = res.headers.get("Location") ?? "";
|
|
5750
|
+
const match = location.match(/\/queue\/item\/(\d+)\/?$/);
|
|
5751
|
+
if (!match) throw new PncliError("Build queued but Jenkins did not return a Location header \u2014 cannot track queue item");
|
|
5752
|
+
const queueItemId = parseInt(match[1], 10);
|
|
5753
|
+
return { queueItemId };
|
|
5754
|
+
}
|
|
5755
|
+
async getQueueItem(queueItemId) {
|
|
5756
|
+
return this.http.jenkins(`/queue/item/${queueItemId}/api/json`);
|
|
5757
|
+
}
|
|
5758
|
+
async listBuilds(name, top = 25) {
|
|
5759
|
+
const data = await this.http.jenkins(
|
|
5760
|
+
`/job/${jobPath(name)}/api/json`,
|
|
5761
|
+
{ params: { tree: `builds[number,url,result,duration,timestamp,building,displayName,fullDisplayName,description,id,queueId]{0,${top}}` } }
|
|
5762
|
+
);
|
|
5763
|
+
return data.builds ?? [];
|
|
5764
|
+
}
|
|
5765
|
+
async getBuild(name, buildNumber) {
|
|
5766
|
+
return this.http.jenkins(
|
|
5767
|
+
`/job/${jobPath(name)}/${buildNumber}/api/json`
|
|
5768
|
+
);
|
|
5769
|
+
}
|
|
5770
|
+
async getConsoleLog(name, buildNumber) {
|
|
5771
|
+
const res = await this.http.jenkinsRaw(
|
|
5772
|
+
`/job/${jobPath(name)}/${buildNumber}/consoleText`,
|
|
5773
|
+
{ method: "GET" }
|
|
5774
|
+
);
|
|
5775
|
+
return res.text;
|
|
5776
|
+
}
|
|
5777
|
+
};
|
|
5778
|
+
|
|
5779
|
+
// src/services/jenkins/commands.ts
|
|
5780
|
+
function getClient6(program2) {
|
|
5781
|
+
const opts = program2.optsWithGlobals();
|
|
5782
|
+
const config = loadConfig({ configPath: opts.config });
|
|
5783
|
+
if (!config.jenkins.baseUrl) throw new PncliError("Jenkins not configured. Run: pncli config init");
|
|
5784
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
5785
|
+
return new JenkinsClient(http);
|
|
5786
|
+
}
|
|
5787
|
+
async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
|
|
5788
|
+
const deadline = Date.now() + timeoutMs;
|
|
5789
|
+
while (Date.now() < deadline) {
|
|
5790
|
+
const item = await client.getQueueItem(queueItemId);
|
|
5791
|
+
if (item.executable?.number !== void 0) return item.executable.number;
|
|
5792
|
+
if (item.why === null && !item.blocked && !item.buildable) {
|
|
5793
|
+
throw new PncliError("Build was cancelled while in queue", 1);
|
|
5794
|
+
}
|
|
5795
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
5796
|
+
}
|
|
5797
|
+
throw new PncliError(`Timed out waiting for build to leave queue after ${timeoutMs / 1e3}s`, 1);
|
|
5798
|
+
}
|
|
5799
|
+
async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs) {
|
|
5800
|
+
const deadline = Date.now() + timeoutMs;
|
|
5801
|
+
while (Date.now() < deadline) {
|
|
5802
|
+
const build = await client.getBuild(jobName, buildNumber);
|
|
5803
|
+
if (!build.building) return build;
|
|
5804
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
5805
|
+
}
|
|
5806
|
+
throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
|
|
5807
|
+
}
|
|
5808
|
+
function registerJenkinsCommands(program2) {
|
|
5809
|
+
const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
|
|
5810
|
+
const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
|
|
5811
|
+
pipeline.command("list").description("List all jobs on the Jenkins instance").action(async () => {
|
|
5812
|
+
const start = Date.now();
|
|
5813
|
+
try {
|
|
5814
|
+
const client = getClient6(program2);
|
|
5815
|
+
const data = await client.listJobs();
|
|
5816
|
+
success(data, "jenkins", "pipeline-list", start);
|
|
5817
|
+
} catch (err) {
|
|
5818
|
+
fail(err, "jenkins", "pipeline-list", start);
|
|
5819
|
+
}
|
|
5820
|
+
});
|
|
5821
|
+
pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name").action(async (opts) => {
|
|
5822
|
+
const start = Date.now();
|
|
5823
|
+
try {
|
|
5824
|
+
const client = getClient6(program2);
|
|
5825
|
+
const data = await client.getJob(opts.name);
|
|
5826
|
+
success(data, "jenkins", "pipeline-get", start);
|
|
5827
|
+
} catch (err) {
|
|
5828
|
+
fail(err, "jenkins", "pipeline-get", start);
|
|
5829
|
+
}
|
|
5830
|
+
});
|
|
5831
|
+
pipeline.command("run").description("Trigger a build").requiredOption("--name <job>", "Job name").option("--parameter <k=v>", "Build parameter (repeatable)", (v, acc) => {
|
|
5832
|
+
acc.push(v);
|
|
5833
|
+
return acc;
|
|
5834
|
+
}, []).option("--wait", "Wait for the build to complete before returning").option("--timeout <s>", "Max wait time in seconds per phase (queue-wait and build-wait each get this budget; default 600)", "600").option("--poll <s>", "Poll interval in seconds (default 10)", "10").action(async (opts) => {
|
|
5835
|
+
const start = Date.now();
|
|
5836
|
+
try {
|
|
5837
|
+
const client = getClient6(program2);
|
|
5838
|
+
for (const p of opts.parameter) {
|
|
5839
|
+
if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
|
|
5840
|
+
}
|
|
5841
|
+
const params = opts.parameter.length > 0 ? Object.fromEntries(opts.parameter.map((p) => {
|
|
5842
|
+
const eq = p.indexOf("=");
|
|
5843
|
+
return [p.slice(0, eq), p.slice(eq + 1)];
|
|
5844
|
+
})) : void 0;
|
|
5845
|
+
const { queueItemId } = await client.triggerBuild(opts.name, params);
|
|
5846
|
+
log(`Queued build (queue item #${queueItemId})`);
|
|
5847
|
+
if (!opts.wait) {
|
|
5848
|
+
success({ queueItemId }, "jenkins", "pipeline-run", start);
|
|
5849
|
+
return;
|
|
5850
|
+
}
|
|
5851
|
+
const timeoutSec = parseInt(opts.timeout, 10);
|
|
5852
|
+
const pollSec = parseInt(opts.poll, 10);
|
|
5853
|
+
if (isNaN(timeoutSec) || timeoutSec <= 0) throw new PncliError(`Invalid --timeout "${opts.timeout}". Expected a positive number of seconds.`, 1);
|
|
5854
|
+
if (isNaN(pollSec) || pollSec <= 0) throw new PncliError(`Invalid --poll "${opts.poll}". Expected a positive number of seconds.`, 1);
|
|
5855
|
+
log(`Waiting for build to start (timeout ${timeoutSec}s)...`);
|
|
5856
|
+
const buildNumber = await pollQueueItem(client, queueItemId, pollSec * 1e3, timeoutSec * 1e3);
|
|
5857
|
+
log(`Build #${buildNumber} started \u2014 waiting for completion...`);
|
|
5858
|
+
const build = await pollBuildComplete(client, opts.name, buildNumber, pollSec * 1e3, timeoutSec * 1e3);
|
|
5859
|
+
if (build.result !== null && build.result !== "SUCCESS") {
|
|
5860
|
+
process.exitCode = ExitCode.GENERAL_ERROR;
|
|
5861
|
+
}
|
|
5862
|
+
success(build, "jenkins", "pipeline-run", start);
|
|
5863
|
+
} catch (err) {
|
|
5864
|
+
fail(err, "jenkins", "pipeline-run", start);
|
|
5865
|
+
}
|
|
5866
|
+
});
|
|
5867
|
+
pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
|
|
5868
|
+
const start = Date.now();
|
|
5869
|
+
try {
|
|
5870
|
+
const client = getClient6(program2);
|
|
5871
|
+
const top = parseInt(opts.top, 10);
|
|
5872
|
+
if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
|
|
5873
|
+
const data = await client.listBuilds(opts.name, top);
|
|
5874
|
+
success(data, "jenkins", "pipeline-list-runs", start);
|
|
5875
|
+
} catch (err) {
|
|
5876
|
+
fail(err, "jenkins", "pipeline-list-runs", start);
|
|
5877
|
+
}
|
|
5878
|
+
});
|
|
5879
|
+
pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
5880
|
+
const start = Date.now();
|
|
5881
|
+
try {
|
|
5882
|
+
const client = getClient6(program2);
|
|
5883
|
+
const buildNumber = parseInt(opts.number, 10);
|
|
5884
|
+
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
5885
|
+
const data = await client.getBuild(opts.name, buildNumber);
|
|
5886
|
+
success(data, "jenkins", "pipeline-get-run", start);
|
|
5887
|
+
} catch (err) {
|
|
5888
|
+
fail(err, "jenkins", "pipeline-get-run", start);
|
|
5889
|
+
}
|
|
5890
|
+
});
|
|
5891
|
+
pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
5892
|
+
const start = Date.now();
|
|
5893
|
+
try {
|
|
5894
|
+
const client = getClient6(program2);
|
|
5895
|
+
const buildNumber = parseInt(opts.number, 10);
|
|
5896
|
+
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
5897
|
+
const text = await client.getConsoleLog(opts.name, buildNumber);
|
|
5898
|
+
success({ log: text }, "jenkins", "pipeline-logs", start);
|
|
5899
|
+
} catch (err) {
|
|
5900
|
+
fail(err, "jenkins", "pipeline-logs", start);
|
|
5901
|
+
}
|
|
5902
|
+
});
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5905
|
+
// src/services/artifactory/client.ts
|
|
5906
|
+
var API9 = "api";
|
|
5907
|
+
var ArtifactoryClient = class {
|
|
5908
|
+
constructor(http) {
|
|
5909
|
+
this.http = http;
|
|
5910
|
+
}
|
|
5911
|
+
http;
|
|
5912
|
+
async ping() {
|
|
5913
|
+
return this.http.artifactoryText(`${API9}/system/ping`);
|
|
5914
|
+
}
|
|
5915
|
+
async listRepos(opts = {}) {
|
|
5916
|
+
return this.http.artifactory(`${API9}/repositories`, {
|
|
5917
|
+
params: {
|
|
5918
|
+
type: opts.type,
|
|
5919
|
+
packageType: opts.packageType
|
|
5920
|
+
}
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5923
|
+
async getStorageInfo(repo, path6) {
|
|
5924
|
+
const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
|
|
5925
|
+
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
5926
|
+
}
|
|
5927
|
+
async getProperties(repo, path6) {
|
|
5928
|
+
const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
|
|
5929
|
+
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
5930
|
+
params: { properties: "" }
|
|
5931
|
+
});
|
|
5932
|
+
}
|
|
5933
|
+
async setProperties(repo, path6, properties, opts = {}) {
|
|
5934
|
+
const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
|
|
5935
|
+
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
5936
|
+
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
5937
|
+
method: "PUT",
|
|
5938
|
+
params: {
|
|
5939
|
+
properties: propString,
|
|
5940
|
+
...opts.recursive ? { recursive: 1 } : {}
|
|
5941
|
+
}
|
|
5942
|
+
});
|
|
5943
|
+
}
|
|
5944
|
+
async searchAql(opts = {}) {
|
|
5945
|
+
const criteria = [];
|
|
5946
|
+
if (opts.repo) criteria.push(`"repo": {"$eq": "${opts.repo}"}`);
|
|
5947
|
+
if (opts.name) criteria.push(`"name": {"$match": "${opts.name}"}`);
|
|
5948
|
+
if (opts.path) criteria.push(`"path": {"$match": "${opts.path}"}`);
|
|
5949
|
+
if (opts.after) criteria.push(`"created": {"$gt": "${opts.after}"}`);
|
|
5950
|
+
if (opts.before) criteria.push(`"created": {"$lt": "${opts.before}"}`);
|
|
5951
|
+
const findClause = criteria.length > 0 ? `{${criteria.join(",")}}` : "{}";
|
|
5952
|
+
const includeFields = opts.includeProperties ? '.include("repo","path","name","type","size","created","modified","sha256","properties")' : '.include("repo","path","name","type","size","created","modified","sha256")';
|
|
5953
|
+
const sortClause = '.sort({"$desc": ["created"]})';
|
|
5954
|
+
const limitClause = opts.limit ? `.limit(${opts.limit})` : ".limit(100)";
|
|
5955
|
+
const aql = `items.find(${findClause})${includeFields}${sortClause}${limitClause}`;
|
|
5956
|
+
return this.http.artifactory(`${API9}/search/aql`, {
|
|
5957
|
+
method: "POST",
|
|
5958
|
+
body: aql,
|
|
5959
|
+
headers: { "Content-Type": "text/plain" }
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
async listBuilds() {
|
|
5963
|
+
return this.http.artifactory(`${API9}/build`);
|
|
5964
|
+
}
|
|
5965
|
+
async listBuildRuns(buildName) {
|
|
5966
|
+
return this.http.artifactory(`${API9}/build/${encodeURIComponent(buildName)}`);
|
|
5967
|
+
}
|
|
5968
|
+
async getBuildInfo(buildName, buildNumber) {
|
|
5969
|
+
return this.http.artifactory(
|
|
5970
|
+
`${API9}/build/${encodeURIComponent(buildName)}/${encodeURIComponent(buildNumber)}`
|
|
5971
|
+
);
|
|
5972
|
+
}
|
|
5973
|
+
};
|
|
5974
|
+
|
|
5975
|
+
// src/services/artifactory/commands.ts
|
|
5976
|
+
function getClient7(program2) {
|
|
5977
|
+
const opts = program2.optsWithGlobals();
|
|
5978
|
+
const config = loadConfig({ configPath: opts.config });
|
|
5979
|
+
if (!config.artifactory.baseUrl) throw new PncliError("Artifactory not configured. Run: pncli config init");
|
|
5980
|
+
if (!config.artifactory.token) throw new PncliError("Artifactory token not configured. Run: pncli config init");
|
|
5981
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
5982
|
+
return new ArtifactoryClient(http);
|
|
5983
|
+
}
|
|
5984
|
+
function registerArtifactoryCommands(program2) {
|
|
5985
|
+
const art = program2.command("artifactory").description("Artifactory operations");
|
|
5986
|
+
art.command("ping").description("Check Artifactory connectivity and authentication").action(async () => {
|
|
5987
|
+
const start = Date.now();
|
|
5988
|
+
try {
|
|
5989
|
+
const client = getClient7(program2);
|
|
5990
|
+
const data = await client.ping();
|
|
5991
|
+
success(data, "artifactory", "ping", start);
|
|
5992
|
+
} catch (err) {
|
|
5993
|
+
fail(err, "artifactory", "ping", start);
|
|
5994
|
+
}
|
|
5995
|
+
});
|
|
5996
|
+
art.command("repos").description("List repositories").option("--type <type>", "Filter by type: local, virtual, remote, federated").option("--package-type <type>", "Filter by package type: npm, maven, nuget, docker, pypi, etc.").action(async (opts) => {
|
|
5997
|
+
const start = Date.now();
|
|
5998
|
+
try {
|
|
5999
|
+
const client = getClient7(program2);
|
|
6000
|
+
const data = await client.listRepos({ type: opts.type, packageType: opts.packageType });
|
|
6001
|
+
success(data, "artifactory", "repos", start);
|
|
6002
|
+
} catch (err) {
|
|
6003
|
+
fail(err, "artifactory", "repos", start);
|
|
6004
|
+
}
|
|
6005
|
+
});
|
|
6006
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path6) => {
|
|
6007
|
+
const start = Date.now();
|
|
6008
|
+
try {
|
|
6009
|
+
const client = getClient7(program2);
|
|
6010
|
+
const data = await client.getStorageInfo(repo, path6);
|
|
6011
|
+
success(data, "artifactory", "artifact-info", start);
|
|
6012
|
+
} catch (err) {
|
|
6013
|
+
fail(err, "artifactory", "artifact-info", start);
|
|
6014
|
+
}
|
|
6015
|
+
});
|
|
6016
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path6) => {
|
|
6017
|
+
const start = Date.now();
|
|
6018
|
+
try {
|
|
6019
|
+
const client = getClient7(program2);
|
|
6020
|
+
const data = await client.getProperties(repo, path6);
|
|
6021
|
+
success(data, "artifactory", "properties-get", start);
|
|
6022
|
+
} catch (err) {
|
|
6023
|
+
fail(err, "artifactory", "properties-get", start);
|
|
6024
|
+
}
|
|
6025
|
+
});
|
|
6026
|
+
art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path6, keyvals, opts) => {
|
|
6027
|
+
const start = Date.now();
|
|
6028
|
+
try {
|
|
6029
|
+
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
6030
|
+
const properties = {};
|
|
6031
|
+
for (const kv of keyvals) {
|
|
6032
|
+
const eq = kv.indexOf("=");
|
|
6033
|
+
if (eq < 1) throw new PncliError(`Invalid property format: "${kv}" \u2014 expected key=value`);
|
|
6034
|
+
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
6035
|
+
}
|
|
6036
|
+
const client = getClient7(program2);
|
|
6037
|
+
await client.setProperties(repo, path6, properties, { recursive: opts.recursive });
|
|
6038
|
+
success({ repo, path: path6, properties }, "artifactory", "properties-set", start);
|
|
6039
|
+
} catch (err) {
|
|
6040
|
+
fail(err, "artifactory", "properties-set", start);
|
|
6041
|
+
}
|
|
6042
|
+
});
|
|
6043
|
+
art.command("search").description("Search artifacts using AQL (Artifactory Query Language) (consider --output-file for large results)").option("--repo <name>", "Filter by repository key").option("--name <pattern>", "Filter by artifact name (supports * and ? wildcards)").option("--path <pattern>", "Filter by artifact path (supports * and ? wildcards)").option("--after <date>", "Created after this date (ISO 8601, e.g. 2024-01-01)").option("--before <date>", "Created before this date (ISO 8601)").option("--limit <n>", "Maximum results to return (default: 100)", "100").option("--properties", "Include artifact properties in results", false).action(async (opts) => {
|
|
6044
|
+
const start = Date.now();
|
|
6045
|
+
try {
|
|
6046
|
+
if (!opts.repo && !opts.name && !opts.path) {
|
|
6047
|
+
throw new PncliError("Provide at least one filter: --repo, --name, or --path");
|
|
6048
|
+
}
|
|
6049
|
+
const client = getClient7(program2);
|
|
6050
|
+
const data = await client.searchAql({
|
|
6051
|
+
repo: opts.repo,
|
|
6052
|
+
name: opts.name,
|
|
6053
|
+
path: opts.path,
|
|
6054
|
+
after: opts.after,
|
|
6055
|
+
before: opts.before,
|
|
6056
|
+
limit: Number.isNaN(parseInt(opts.limit, 10)) ? 100 : parseInt(opts.limit, 10),
|
|
6057
|
+
includeProperties: opts.properties
|
|
6058
|
+
});
|
|
6059
|
+
success(data, "artifactory", "search", start);
|
|
6060
|
+
} catch (err) {
|
|
6061
|
+
fail(err, "artifactory", "search", start);
|
|
6062
|
+
}
|
|
6063
|
+
});
|
|
6064
|
+
art.command("builds").description("List all build names in Artifactory").action(async () => {
|
|
6065
|
+
const start = Date.now();
|
|
6066
|
+
try {
|
|
6067
|
+
const client = getClient7(program2);
|
|
6068
|
+
const data = await client.listBuilds();
|
|
6069
|
+
success(data, "artifactory", "builds", start);
|
|
6070
|
+
} catch (err) {
|
|
6071
|
+
fail(err, "artifactory", "builds", start);
|
|
6072
|
+
}
|
|
6073
|
+
});
|
|
6074
|
+
art.command("build-runs <name>").description("List all run numbers for a build").action(async (name) => {
|
|
6075
|
+
const start = Date.now();
|
|
6076
|
+
try {
|
|
6077
|
+
const client = getClient7(program2);
|
|
6078
|
+
const data = await client.listBuildRuns(name);
|
|
6079
|
+
success(data, "artifactory", "build-runs", start);
|
|
6080
|
+
} catch (err) {
|
|
6081
|
+
fail(err, "artifactory", "build-runs", start);
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
art.command("build-info <name> <number>").description("Get build info (artifacts, dependencies, VCS revision) for a specific run").action(async (name, number) => {
|
|
6085
|
+
const start = Date.now();
|
|
6086
|
+
try {
|
|
6087
|
+
const client = getClient7(program2);
|
|
6088
|
+
const data = await client.getBuildInfo(name, number);
|
|
6089
|
+
success(data, "artifactory", "build-info", start);
|
|
6090
|
+
} catch (err) {
|
|
6091
|
+
fail(err, "artifactory", "build-info", start);
|
|
6092
|
+
}
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
|
|
6096
|
+
// src/services/udeploy/client.ts
|
|
6097
|
+
var UdeployClient = class {
|
|
6098
|
+
constructor(http) {
|
|
6099
|
+
this.http = http;
|
|
6100
|
+
}
|
|
6101
|
+
http;
|
|
6102
|
+
listApplications() {
|
|
6103
|
+
return this.http.udeploy("/cli/application");
|
|
6104
|
+
}
|
|
6105
|
+
listEnvironments(application) {
|
|
6106
|
+
return this.http.udeploy("/cli/application/environmentsInApplication", {
|
|
6107
|
+
params: { application }
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
listComponents() {
|
|
6111
|
+
return this.http.udeploy("/cli/component");
|
|
6112
|
+
}
|
|
6113
|
+
listVersions(component) {
|
|
6114
|
+
return this.http.udeploy("/cli/component/versions", {
|
|
6115
|
+
params: { component }
|
|
6116
|
+
});
|
|
6117
|
+
}
|
|
6118
|
+
createVersion(component, name) {
|
|
6119
|
+
return this.http.udeploy("/cli/version/createVersion", {
|
|
6120
|
+
method: "POST",
|
|
6121
|
+
params: { component, name }
|
|
6122
|
+
});
|
|
6123
|
+
}
|
|
6124
|
+
finishImporting(component, version) {
|
|
6125
|
+
return this.http.udeploy("/cli/version/finishedImporting", {
|
|
6126
|
+
method: "POST",
|
|
6127
|
+
params: { component, version }
|
|
6128
|
+
});
|
|
6129
|
+
}
|
|
6130
|
+
runProcess(opts) {
|
|
6131
|
+
return this.http.udeploy("/cli/applicationProcessRequest/request", {
|
|
6132
|
+
method: "PUT",
|
|
6133
|
+
body: {
|
|
6134
|
+
application: opts.application,
|
|
6135
|
+
applicationProcess: opts.applicationProcess,
|
|
6136
|
+
environment: opts.environment,
|
|
6137
|
+
onlyChanged: opts.onlyChanged ?? false,
|
|
6138
|
+
...opts.versions ? { versions: opts.versions } : {},
|
|
6139
|
+
...opts.snapshot ? { snapshot: opts.snapshot } : {},
|
|
6140
|
+
...opts.properties ? { properties: opts.properties } : {}
|
|
6141
|
+
}
|
|
6142
|
+
});
|
|
6143
|
+
}
|
|
6144
|
+
getRequestStatus(requestId) {
|
|
6145
|
+
return this.http.udeploy("/cli/applicationProcessRequest/requestStatus", {
|
|
6146
|
+
params: { requestId }
|
|
6147
|
+
});
|
|
6148
|
+
}
|
|
6149
|
+
getRequestInfo(requestId) {
|
|
6150
|
+
return this.http.udeploy("/cli/applicationProcessRequest/requestInfo", {
|
|
6151
|
+
params: { requestId }
|
|
6152
|
+
});
|
|
6153
|
+
}
|
|
6154
|
+
};
|
|
6155
|
+
|
|
6156
|
+
// src/services/udeploy/commands.ts
|
|
6157
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
6158
|
+
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
6159
|
+
function getClient8(program2) {
|
|
6160
|
+
const opts = program2.optsWithGlobals();
|
|
6161
|
+
const config = loadConfig({ configPath: opts.config });
|
|
6162
|
+
if (!config.udeploy.baseUrl) {
|
|
6163
|
+
throw new PncliError("UDeploy baseUrl not configured. Run: pncli config init", 78);
|
|
6164
|
+
}
|
|
6165
|
+
const http = createHttpClient(config, opts.dryRun);
|
|
6166
|
+
return { client: new UdeployClient(http), config };
|
|
6167
|
+
}
|
|
6168
|
+
function resolveApplication(config, cliValue) {
|
|
6169
|
+
const app = cliValue ?? config.defaults.udeploy.application;
|
|
6170
|
+
if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
|
|
6171
|
+
return app;
|
|
6172
|
+
}
|
|
6173
|
+
function resolveEnvironment(config, cliValue) {
|
|
6174
|
+
const env = cliValue ?? config.defaults.udeploy.environment;
|
|
6175
|
+
if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
|
|
6176
|
+
return env;
|
|
6177
|
+
}
|
|
6178
|
+
function registerUdeployCommands(program2) {
|
|
6179
|
+
const udeploy = program2.command("udeploy").description("IBM UrbanCode Deploy \u2014 component versions and deployment processes").option("--application <name>", "Application name or ID").option("--environment <name>", "Environment name or ID");
|
|
6180
|
+
udeploy.command("apps").description("List all applications").action(async () => {
|
|
6181
|
+
const start = Date.now();
|
|
6182
|
+
try {
|
|
6183
|
+
const { client } = getClient8(program2);
|
|
6184
|
+
const apps = await client.listApplications();
|
|
6185
|
+
success(apps, "udeploy", "apps", start);
|
|
6186
|
+
} catch (err) {
|
|
6187
|
+
fail(err, "udeploy", "apps", start);
|
|
6188
|
+
}
|
|
6189
|
+
});
|
|
6190
|
+
udeploy.command("environments").description("List environments for an application").action(async () => {
|
|
6191
|
+
const start = Date.now();
|
|
6192
|
+
try {
|
|
6193
|
+
const { client, config } = getClient8(program2);
|
|
6194
|
+
const parentOpts = udeploy.opts();
|
|
6195
|
+
const application = resolveApplication(config, parentOpts.application);
|
|
6196
|
+
const envs = await client.listEnvironments(application);
|
|
6197
|
+
success(envs, "udeploy", "environments", start);
|
|
6198
|
+
} catch (err) {
|
|
6199
|
+
fail(err, "udeploy", "environments", start);
|
|
6200
|
+
}
|
|
6201
|
+
});
|
|
6202
|
+
udeploy.command("components").description("List all components").action(async () => {
|
|
6203
|
+
const start = Date.now();
|
|
6204
|
+
try {
|
|
6205
|
+
const { client } = getClient8(program2);
|
|
6206
|
+
const components = await client.listComponents();
|
|
6207
|
+
success(components, "udeploy", "components", start);
|
|
6208
|
+
} catch (err) {
|
|
6209
|
+
fail(err, "udeploy", "components", start);
|
|
6210
|
+
}
|
|
6211
|
+
});
|
|
6212
|
+
udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
|
|
6213
|
+
const start = Date.now();
|
|
6214
|
+
try {
|
|
6215
|
+
const { client } = getClient8(program2);
|
|
6216
|
+
const versions = await client.listVersions(cmdOpts.component);
|
|
6217
|
+
success(versions, "udeploy", "versions", start);
|
|
6218
|
+
} catch (err) {
|
|
6219
|
+
fail(err, "udeploy", "versions", start);
|
|
6220
|
+
}
|
|
6221
|
+
});
|
|
6222
|
+
udeploy.command("import-version").description("Create a component version and mark it ready for deployment").requiredOption("--component <name>", "Component name or ID").requiredOption("--name <name>", "Version name").option("--no-finish", "Skip marking the version as finished importing").action(async (cmdOpts) => {
|
|
6223
|
+
const start = Date.now();
|
|
6224
|
+
try {
|
|
6225
|
+
const { client } = getClient8(program2);
|
|
6226
|
+
const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
|
|
6227
|
+
if (cmdOpts.finish) {
|
|
6228
|
+
await client.finishImporting(cmdOpts.component, cmdOpts.name);
|
|
6229
|
+
}
|
|
6230
|
+
success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
|
|
6231
|
+
} catch (err) {
|
|
6232
|
+
fail(err, "udeploy", "import-version", start);
|
|
6233
|
+
}
|
|
6234
|
+
});
|
|
6235
|
+
udeploy.command("run").description("Run an application deployment process").requiredOption("--process <name>", "Application process name or ID").option("--component <name>", "Component name (repeatable)", collect, []).option("--component-version <name>", "Component version (repeatable; positionally paired with --component)", collect, []).option("--snapshot <name>", "Snapshot name or ID (alternative to specifying versions)").option("--only-changed", "Deploy only changed components", false).option("--wait", "Poll until the process completes", false).option("--timeout <ms>", "Max wait time in milliseconds", String(DEFAULT_TIMEOUT_MS)).action(async (cmdOpts) => {
|
|
6236
|
+
const start = Date.now();
|
|
6237
|
+
try {
|
|
6238
|
+
const { client, config } = getClient8(program2);
|
|
6239
|
+
const parentOpts = udeploy.opts();
|
|
6240
|
+
const application = resolveApplication(config, parentOpts.application);
|
|
6241
|
+
const environment = resolveEnvironment(config, parentOpts.environment);
|
|
6242
|
+
const components = cmdOpts.component;
|
|
6243
|
+
const versions = cmdOpts.componentVersion;
|
|
6244
|
+
const versionEntries = components.map((c, i) => ({
|
|
6245
|
+
component: c,
|
|
6246
|
+
version: versions[i] ?? "latest"
|
|
6247
|
+
}));
|
|
6248
|
+
const result = await client.runProcess({
|
|
6249
|
+
application,
|
|
6250
|
+
applicationProcess: cmdOpts.process,
|
|
6251
|
+
environment,
|
|
6252
|
+
onlyChanged: cmdOpts.onlyChanged,
|
|
6253
|
+
...versionEntries.length > 0 ? { versions: versionEntries } : {},
|
|
6254
|
+
...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
|
|
6255
|
+
});
|
|
6256
|
+
if (!cmdOpts.wait) {
|
|
6257
|
+
success(result, "udeploy", "run", start);
|
|
6258
|
+
return;
|
|
6259
|
+
}
|
|
6260
|
+
const timeoutMs = parseInt(cmdOpts.timeout, 10);
|
|
6261
|
+
const requestId = result.requestId;
|
|
6262
|
+
log(`Process started. Request ID: ${requestId}. Polling for completion...`);
|
|
6263
|
+
while (true) {
|
|
6264
|
+
const elapsed = Date.now() - start;
|
|
6265
|
+
if (elapsed >= timeoutMs) {
|
|
6266
|
+
throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
|
|
6267
|
+
}
|
|
6268
|
+
await sleep(POLL_INTERVAL_MS);
|
|
6269
|
+
const status = await client.getRequestStatus(requestId);
|
|
6270
|
+
if (status.status === "CLOSED") {
|
|
6271
|
+
const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
|
|
6272
|
+
if (status.result === "SUCCEEDED") {
|
|
6273
|
+
success(outcome, "udeploy", "run", start);
|
|
6274
|
+
} else {
|
|
6275
|
+
throw new PncliError(
|
|
6276
|
+
`Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
|
|
6277
|
+
1
|
|
6278
|
+
);
|
|
6279
|
+
}
|
|
6280
|
+
return;
|
|
6281
|
+
}
|
|
6282
|
+
log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
|
|
6283
|
+
}
|
|
6284
|
+
} catch (err) {
|
|
6285
|
+
fail(err, "udeploy", "run", start);
|
|
6286
|
+
}
|
|
6287
|
+
});
|
|
6288
|
+
udeploy.command("request-status").description("Get the current status of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
|
|
6289
|
+
const start = Date.now();
|
|
6290
|
+
try {
|
|
6291
|
+
const { client } = getClient8(program2);
|
|
6292
|
+
const status = await client.getRequestStatus(cmdOpts.requestId);
|
|
6293
|
+
success(status, "udeploy", "request-status", start);
|
|
6294
|
+
} catch (err) {
|
|
6295
|
+
fail(err, "udeploy", "request-status", start);
|
|
6296
|
+
}
|
|
6297
|
+
});
|
|
6298
|
+
udeploy.command("request-info").description("Get full details of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
|
|
6299
|
+
const start = Date.now();
|
|
6300
|
+
try {
|
|
6301
|
+
const { client } = getClient8(program2);
|
|
6302
|
+
const info = await client.getRequestInfo(cmdOpts.requestId);
|
|
6303
|
+
success(info, "udeploy", "request-info", start);
|
|
6304
|
+
} catch (err) {
|
|
6305
|
+
fail(err, "udeploy", "request-info", start);
|
|
6306
|
+
}
|
|
6307
|
+
});
|
|
6308
|
+
}
|
|
6309
|
+
function collect(val, prev) {
|
|
6310
|
+
return [...prev, val];
|
|
6311
|
+
}
|
|
6312
|
+
function sleep(ms) {
|
|
6313
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
// src/services/checkmarx/client.ts
|
|
6317
|
+
var CheckmarxClient = class {
|
|
6318
|
+
constructor(http) {
|
|
6319
|
+
this.http = http;
|
|
6320
|
+
}
|
|
6321
|
+
http;
|
|
6322
|
+
async listProjects() {
|
|
6323
|
+
return this.http.checkmarx("/cxrestapi/projects");
|
|
6324
|
+
}
|
|
6325
|
+
async getProject(id) {
|
|
6326
|
+
return this.http.checkmarx(`/cxrestapi/projects/${id}`);
|
|
6327
|
+
}
|
|
6328
|
+
async listScans(opts = {}) {
|
|
6329
|
+
return this.http.checkmarx("/cxrestapi/sast/scans", {
|
|
6330
|
+
params: {
|
|
6331
|
+
projectId: opts.projectId,
|
|
6332
|
+
last: opts.last
|
|
6333
|
+
}
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
async getScan(id) {
|
|
6337
|
+
return this.http.checkmarx(`/cxrestapi/sast/scans/${id}`);
|
|
6338
|
+
}
|
|
6339
|
+
async getScanResultsStatistics(scanId) {
|
|
6340
|
+
return this.http.checkmarx(`/cxrestapi/sast/scans/${scanId}/resultsStatistics`);
|
|
6341
|
+
}
|
|
6342
|
+
};
|
|
6343
|
+
|
|
6344
|
+
// src/services/checkmarx/commands.ts
|
|
6345
|
+
function getClient9(program2) {
|
|
6346
|
+
const opts = program2.optsWithGlobals();
|
|
6347
|
+
const config = loadConfig({ configPath: opts.config });
|
|
6348
|
+
if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
|
|
6349
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
6350
|
+
return new CheckmarxClient(http);
|
|
6351
|
+
}
|
|
6352
|
+
function registerCheckmarxCommands(program2) {
|
|
6353
|
+
const cx = program2.command("checkmarx").description("Checkmarx CxSAST operations");
|
|
6354
|
+
const project = cx.command("project").description("Project operations");
|
|
6355
|
+
const scan = cx.command("scan").description("Scan operations");
|
|
6356
|
+
project.command("list").description("List all projects").action(async () => {
|
|
6357
|
+
const start = Date.now();
|
|
6358
|
+
try {
|
|
6359
|
+
const data = await getClient9(program2).listProjects();
|
|
6360
|
+
success(data, "checkmarx", "project-list", start);
|
|
6361
|
+
} catch (err) {
|
|
6362
|
+
fail(err, "checkmarx", "project-list", start);
|
|
6363
|
+
}
|
|
6364
|
+
});
|
|
6365
|
+
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project ID").action(async (opts) => {
|
|
6366
|
+
const start = Date.now();
|
|
6367
|
+
try {
|
|
6368
|
+
const id = parseInt(opts.id, 10);
|
|
6369
|
+
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
6370
|
+
const data = await getClient9(program2).getProject(id);
|
|
6371
|
+
success(data, "checkmarx", "project-get", start);
|
|
6372
|
+
} catch (err) {
|
|
6373
|
+
fail(err, "checkmarx", "project-get", start);
|
|
6374
|
+
}
|
|
6375
|
+
});
|
|
6376
|
+
scan.command("list").description("List scans").option("--project <id>", "Filter by project ID").option("--last <n>", "Return only the last N scans per project").action(async (opts) => {
|
|
6377
|
+
const start = Date.now();
|
|
6378
|
+
try {
|
|
6379
|
+
const projectId = opts.project ? parseInt(opts.project, 10) : void 0;
|
|
6380
|
+
const last = opts.last ? parseInt(opts.last, 10) : void 0;
|
|
6381
|
+
if (opts.project && isNaN(projectId)) throw new PncliError(`Invalid --project "${opts.project}". Expected an integer.`, 1);
|
|
6382
|
+
if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
|
|
6383
|
+
const data = await getClient9(program2).listScans({ projectId, last });
|
|
6384
|
+
success(data, "checkmarx", "scan-list", start);
|
|
6385
|
+
} catch (err) {
|
|
6386
|
+
fail(err, "checkmarx", "scan-list", start);
|
|
6387
|
+
}
|
|
6388
|
+
});
|
|
6389
|
+
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan ID").action(async (opts) => {
|
|
6390
|
+
const start = Date.now();
|
|
6391
|
+
try {
|
|
6392
|
+
const id = parseInt(opts.id, 10);
|
|
6393
|
+
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
6394
|
+
const data = await getClient9(program2).getScan(id);
|
|
6395
|
+
success(data, "checkmarx", "scan-get", start);
|
|
6396
|
+
} catch (err) {
|
|
6397
|
+
fail(err, "checkmarx", "scan-get", start);
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
6400
|
+
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan ID").action(async (opts) => {
|
|
6401
|
+
const start = Date.now();
|
|
6402
|
+
try {
|
|
6403
|
+
const id = parseInt(opts.id, 10);
|
|
6404
|
+
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
6405
|
+
const data = await getClient9(program2).getScanResultsStatistics(id);
|
|
6406
|
+
success(data, "checkmarx", "scan-stats", start);
|
|
6407
|
+
} catch (err) {
|
|
6408
|
+
fail(err, "checkmarx", "scan-stats", start);
|
|
6409
|
+
}
|
|
6410
|
+
});
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
// src/services/servicenow/commands.ts
|
|
6414
|
+
function getHttp(program2) {
|
|
6415
|
+
const opts = program2.optsWithGlobals();
|
|
6416
|
+
const config = loadConfig({ configPath: opts.config });
|
|
6417
|
+
if (!config.servicenow.baseUrl) throw new PncliError("ServiceNow not configured. Run: pncli config init");
|
|
6418
|
+
return createHttpClient(config, Boolean(opts.dryRun));
|
|
6419
|
+
}
|
|
6420
|
+
function registerServiceNowCommands(program2) {
|
|
6421
|
+
const sn = program2.command("servicenow").description("ServiceNow operations");
|
|
6422
|
+
const change = sn.command("change").description("Change request operations");
|
|
6423
|
+
change.command("list").description("List change requests").option("--state <state>", "Filter by state (e.g. -1=Pending, 1=Open, 2=Work in Progress, 3=Closed Complete)").option("--assigned-to <user>", "Filter by assigned user sys_id or display name").option("--limit <n>", "Maximum number of results", "25").option("--fields <fields>", "Comma-separated field names to return").action(async (opts) => {
|
|
6424
|
+
const start = Date.now();
|
|
6425
|
+
try {
|
|
6426
|
+
const http = getHttp(program2);
|
|
6427
|
+
const params = {
|
|
6428
|
+
sysparm_limit: opts.limit ? parseInt(opts.limit, 10) : 25,
|
|
6429
|
+
sysparm_display_value: "true"
|
|
6430
|
+
};
|
|
6431
|
+
const sanitize = (v) => v.replace(/\^/g, "");
|
|
6432
|
+
const queries = [];
|
|
6433
|
+
if (opts.state !== void 0) queries.push(`state=${sanitize(opts.state)}`);
|
|
6434
|
+
if (opts.assignedTo) queries.push(`assigned_to=${sanitize(opts.assignedTo)}`);
|
|
6435
|
+
if (queries.length) params["sysparm_query"] = queries.join("^");
|
|
6436
|
+
if (opts.fields) params["sysparm_fields"] = opts.fields;
|
|
6437
|
+
const data = await http.servicenow("/api/now/table/change_request", { params });
|
|
6438
|
+
success(data.result, "servicenow", "change list", start);
|
|
6439
|
+
} catch (err) {
|
|
6440
|
+
fail(err, "servicenow", "change list", start);
|
|
6441
|
+
}
|
|
6442
|
+
});
|
|
6443
|
+
change.command("get").description("Get a change request by sys_id or number").requiredOption("--id <id>", "Change request sys_id or number (e.g. CHG0001234)").action(async (opts) => {
|
|
6444
|
+
const start = Date.now();
|
|
6445
|
+
try {
|
|
6446
|
+
const http = getHttp(program2);
|
|
6447
|
+
if (opts.id.toUpperCase().startsWith("CHG")) {
|
|
6448
|
+
const list = await http.servicenow("/api/now/table/change_request", {
|
|
6449
|
+
params: { sysparm_query: `number=${opts.id}`, sysparm_display_value: "true", sysparm_limit: 1 }
|
|
6450
|
+
});
|
|
6451
|
+
if (!list.result.length) throw new PncliError(`Change request not found: ${opts.id}`, 1);
|
|
6452
|
+
success(list.result[0], "servicenow", "change get", start);
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
|
|
6456
|
+
params: { sysparm_display_value: "true" }
|
|
6457
|
+
});
|
|
6458
|
+
success(data.result, "servicenow", "change get", start);
|
|
6459
|
+
} catch (err) {
|
|
6460
|
+
fail(err, "servicenow", "change get", start);
|
|
6461
|
+
}
|
|
6462
|
+
});
|
|
6463
|
+
change.command("create").description("Create a change request").requiredOption("--short-description <text>", "Short description (title)").option("--description <text>", "Full description").option("--type <type>", "Change type: normal, standard, or emergency", "normal").option("--priority <n>", "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low").option("--assigned-to <user>", "Assigned user sys_id").option("--assignment-group <group>", "Assignment group sys_id").option("--start-date <datetime>", "Planned start date (yyyy-MM-dd HH:mm:ss)").option("--end-date <datetime>", "Planned end date (yyyy-MM-dd HH:mm:ss)").action(async (opts) => {
|
|
6464
|
+
const start = Date.now();
|
|
6465
|
+
try {
|
|
6466
|
+
const http = getHttp(program2);
|
|
6467
|
+
const body = {
|
|
6468
|
+
short_description: opts.shortDescription,
|
|
6469
|
+
type: opts.type ?? "normal"
|
|
6470
|
+
};
|
|
6471
|
+
if (opts.description) body["description"] = opts.description;
|
|
6472
|
+
if (opts.priority) body["priority"] = opts.priority;
|
|
6473
|
+
if (opts.assignedTo) body["assigned_to"] = opts.assignedTo;
|
|
6474
|
+
if (opts.assignmentGroup) body["assignment_group"] = opts.assignmentGroup;
|
|
6475
|
+
if (opts.startDate) body["start_date"] = opts.startDate;
|
|
6476
|
+
if (opts.endDate) body["end_date"] = opts.endDate;
|
|
6477
|
+
const data = await http.servicenow("/api/now/table/change_request", {
|
|
6478
|
+
method: "POST",
|
|
6479
|
+
body
|
|
6480
|
+
});
|
|
6481
|
+
success(data.result, "servicenow", "change create", start);
|
|
6482
|
+
} catch (err) {
|
|
6483
|
+
fail(err, "servicenow", "change create", start);
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
change.command("update").description("Update a change request").requiredOption("--id <sys_id>", "Change request sys_id").option("--short-description <text>", "New short description").option("--description <text>", "New description").option("--state <state>", "New state value").option("--priority <n>", "New priority").option("--assigned-to <user>", "New assigned user sys_id").option("--start-date <datetime>", "New planned start date").option("--end-date <datetime>", "New planned end date").action(async (opts) => {
|
|
6487
|
+
const start = Date.now();
|
|
6488
|
+
try {
|
|
6489
|
+
const http = getHttp(program2);
|
|
6490
|
+
const body = {};
|
|
6491
|
+
if (opts.shortDescription) body["short_description"] = opts.shortDescription;
|
|
6492
|
+
if (opts.description) body["description"] = opts.description;
|
|
6493
|
+
if (opts.state !== void 0) body["state"] = opts.state;
|
|
6494
|
+
if (opts.priority) body["priority"] = opts.priority;
|
|
6495
|
+
if (opts.assignedTo) body["assigned_to"] = opts.assignedTo;
|
|
6496
|
+
if (opts.startDate) body["start_date"] = opts.startDate;
|
|
6497
|
+
if (opts.endDate) body["end_date"] = opts.endDate;
|
|
6498
|
+
if (Object.keys(body).length === 0) throw new PncliError("No fields to update. Provide at least one option besides --id.", 1);
|
|
6499
|
+
const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
|
|
6500
|
+
method: "PATCH",
|
|
6501
|
+
body
|
|
6502
|
+
});
|
|
6503
|
+
success(data.result, "servicenow", "change update", start);
|
|
6504
|
+
} catch (err) {
|
|
6505
|
+
fail(err, "servicenow", "change update", start);
|
|
6506
|
+
}
|
|
6507
|
+
});
|
|
6508
|
+
change.command("close").description("Close a change request (state=3)").requiredOption("--id <sys_id>", "Change request sys_id").option("--close-notes <text>", "Closure notes").option("--close-code <code>", "Close code (e.g. successful, unsuccessful)").action(async (opts) => {
|
|
6509
|
+
const start = Date.now();
|
|
6510
|
+
try {
|
|
6511
|
+
const http = getHttp(program2);
|
|
6512
|
+
const body = { state: 3 };
|
|
6513
|
+
if (opts.closeNotes) body["close_notes"] = opts.closeNotes;
|
|
6514
|
+
if (opts.closeCode) body["close_code"] = opts.closeCode;
|
|
6515
|
+
const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
|
|
6516
|
+
method: "PATCH",
|
|
6517
|
+
body
|
|
6518
|
+
});
|
|
6519
|
+
success(data.result, "servicenow", "change close", start);
|
|
6520
|
+
} catch (err) {
|
|
6521
|
+
fail(err, "servicenow", "change close", start);
|
|
6522
|
+
}
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
// src/services/contrast/commands.ts
|
|
6527
|
+
function getHttpAndOrg(program2) {
|
|
6528
|
+
const opts = program2.optsWithGlobals();
|
|
6529
|
+
const config = loadConfig({ configPath: opts.config });
|
|
6530
|
+
if (!config.contrast.apiKey || !config.contrast.serviceKey || !config.contrast.username) throw new PncliError("Contrast not configured. Run: pncli config init");
|
|
6531
|
+
if (!config.contrast.orgUuid) throw new PncliError("Contrast org UUID not configured. Run: pncli config set contrast.orgUuid <uuid>");
|
|
6532
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
6533
|
+
return { http, orgUuid: config.contrast.orgUuid };
|
|
6534
|
+
}
|
|
6535
|
+
function registerContrastCommands(program2) {
|
|
6536
|
+
const contrast = program2.command("contrast").description("Contrast IAST operations");
|
|
6537
|
+
const apps = contrast.command("apps").description("Application operations");
|
|
6538
|
+
apps.command("list").description("List applications in the organization").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
|
|
6539
|
+
const start = Date.now();
|
|
6540
|
+
try {
|
|
6541
|
+
const { http, orgUuid } = getHttpAndOrg(program2);
|
|
6542
|
+
const data = await http.contrast(`/Contrast/api/ng/${orgUuid}/applications`, {
|
|
6543
|
+
params: {
|
|
6544
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : 25,
|
|
6545
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : 0
|
|
6546
|
+
}
|
|
6547
|
+
});
|
|
6548
|
+
success(data, "contrast", "apps list", start);
|
|
6549
|
+
} catch (err) {
|
|
6550
|
+
fail(err, "contrast", "apps list", start);
|
|
6551
|
+
}
|
|
6552
|
+
});
|
|
6553
|
+
const findings = contrast.command("findings").description("Vulnerability findings operations");
|
|
6554
|
+
findings.command("list").description("List vulnerability findings for an application").requiredOption("--app <app-id>", "Application ID (UUID)").option("--severity <level>", "Filter by severity: CRITICAL, HIGH, MEDIUM, LOW, NOTE").option("--status <status>", "Filter by status: REPORTED, CONFIRMED, SUSPICIOUS, NOT_A_PROBLEM, REMEDIATED, FIXED").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
|
|
6555
|
+
const start = Date.now();
|
|
6556
|
+
try {
|
|
6557
|
+
const { http, orgUuid } = getHttpAndOrg(program2);
|
|
6558
|
+
const params = {
|
|
6559
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : 25,
|
|
6560
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : 0
|
|
6561
|
+
};
|
|
6562
|
+
if (opts.severity) params["severities"] = opts.severity;
|
|
6563
|
+
if (opts.status) params["statuses"] = opts.status;
|
|
6564
|
+
const data = await http.contrast(
|
|
6565
|
+
`/Contrast/api/ng/${orgUuid}/traces/${opts.app}/filter`,
|
|
6566
|
+
{ params }
|
|
6567
|
+
);
|
|
6568
|
+
success(data, "contrast", "findings list", start);
|
|
6569
|
+
} catch (err) {
|
|
6570
|
+
fail(err, "contrast", "findings list", start);
|
|
6571
|
+
}
|
|
6572
|
+
});
|
|
6573
|
+
findings.command("get").description("Get details for a specific vulnerability finding").requiredOption("--app <app-id>", "Application ID (UUID)").requiredOption("--trace <trace-id>", "Trace/finding UUID").action(async (opts) => {
|
|
6574
|
+
const start = Date.now();
|
|
6575
|
+
try {
|
|
6576
|
+
const { http, orgUuid } = getHttpAndOrg(program2);
|
|
6577
|
+
const data = await http.contrast(
|
|
6578
|
+
`/Contrast/api/ng/${orgUuid}/traces/${opts.app}/trace/${opts.trace}`
|
|
6579
|
+
);
|
|
6580
|
+
success(data, "contrast", "findings get", start);
|
|
6581
|
+
} catch (err) {
|
|
6582
|
+
fail(err, "contrast", "findings get", start);
|
|
6583
|
+
}
|
|
6584
|
+
});
|
|
6585
|
+
}
|
|
6586
|
+
|
|
5085
6587
|
// src/services/skills/commands.ts
|
|
5086
6588
|
import fs3 from "fs";
|
|
5087
6589
|
import path5 from "path";
|
|
5088
|
-
|
|
5089
|
-
var
|
|
5090
|
-
var
|
|
6590
|
+
import os from "os";
|
|
6591
|
+
var GITHUB_API = "https://api.github.com/repos/kolatts/pncli/contents/skills";
|
|
6592
|
+
var RAW_BASE = "https://raw.githubusercontent.com/kolatts/pncli/main/skills";
|
|
6593
|
+
var AGENT_PATHS = {
|
|
6594
|
+
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
6595
|
+
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
6596
|
+
};
|
|
5091
6597
|
function registerSkillsCommands(program2) {
|
|
5092
6598
|
const skills = program2.command("skills").description("Manage pncli Claude Code skills");
|
|
5093
|
-
skills.command("install").description("Download latest pncli skills
|
|
6599
|
+
skills.command("install").description("Download latest pncli skills into the current repo").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action(async (opts) => {
|
|
5094
6600
|
const start = Date.now();
|
|
5095
6601
|
try {
|
|
5096
|
-
|
|
6602
|
+
let targetDir;
|
|
6603
|
+
if (opts.target) {
|
|
6604
|
+
targetDir = path5.resolve(opts.target);
|
|
6605
|
+
} else {
|
|
6606
|
+
const agentConfig = AGENT_PATHS[opts.agent];
|
|
6607
|
+
if (!agentConfig) {
|
|
6608
|
+
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
6609
|
+
}
|
|
6610
|
+
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
6611
|
+
targetDir = path5.resolve(scopePath);
|
|
6612
|
+
}
|
|
5097
6613
|
warn("Fetching skill list from GitHub...");
|
|
5098
6614
|
const listRes = await fetch(GITHUB_API, {
|
|
5099
6615
|
headers: { "Accept": "application/vnd.github.v3+json", "User-Agent": "pncli" }
|
|
@@ -5114,7 +6630,7 @@ function registerSkillsCommands(program2) {
|
|
|
5114
6630
|
fs3.rmSync(existingDir, { recursive: true, force: true });
|
|
5115
6631
|
}
|
|
5116
6632
|
}
|
|
5117
|
-
warn(`Found ${skillDirs.length} skills. Downloading...`);
|
|
6633
|
+
warn(`Found ${skillDirs.length} skills. Downloading to ${targetDir}...`);
|
|
5118
6634
|
const installed = [];
|
|
5119
6635
|
const failed = [];
|
|
5120
6636
|
for (const skillName of skillDirs) {
|
|
@@ -5144,54 +6660,60 @@ function registerSkillsCommands(program2) {
|
|
|
5144
6660
|
if (failed.length > 0) {
|
|
5145
6661
|
warn(`Failed to download: ${failed.join(", ")}`);
|
|
5146
6662
|
}
|
|
5147
|
-
let instructionsWritten = false;
|
|
5148
|
-
if (!opts.skipInstructions) {
|
|
5149
|
-
warn("Downloading copilot-instructions.md...");
|
|
5150
|
-
try {
|
|
5151
|
-
const res = await fetch(RAW_COPILOT_INSTRUCTIONS, {
|
|
5152
|
-
headers: { "User-Agent": "pncli" }
|
|
5153
|
-
});
|
|
5154
|
-
if (res.ok) {
|
|
5155
|
-
const content = await res.text();
|
|
5156
|
-
fs3.writeFileSync("copilot-instructions.md", content, "utf8");
|
|
5157
|
-
instructionsWritten = true;
|
|
5158
|
-
warn("Wrote copilot-instructions.md");
|
|
5159
|
-
} else {
|
|
5160
|
-
warn(`Failed to download copilot-instructions.md (${res.status})`);
|
|
5161
|
-
}
|
|
5162
|
-
} catch {
|
|
5163
|
-
warn("Failed to download copilot-instructions.md");
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
6663
|
success({
|
|
5167
6664
|
installed,
|
|
5168
6665
|
failed,
|
|
5169
6666
|
target: targetDir,
|
|
5170
6667
|
total: installed.length,
|
|
5171
|
-
|
|
6668
|
+
agent: opts.target ? "custom" : opts.agent,
|
|
6669
|
+
scope: opts.target ? "custom" : opts.scope
|
|
5172
6670
|
}, "skills", "install", start);
|
|
5173
6671
|
} catch (err) {
|
|
5174
6672
|
fail(err, "skills", "install", start);
|
|
5175
6673
|
}
|
|
5176
6674
|
});
|
|
5177
|
-
skills.command("list").description("List locally installed skills").option("--
|
|
6675
|
+
skills.command("list").description("List locally installed skills").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override skills directory to scan").action((opts) => {
|
|
5178
6676
|
const start = Date.now();
|
|
5179
6677
|
try {
|
|
5180
|
-
|
|
6678
|
+
let targetDir;
|
|
6679
|
+
if (opts.target) {
|
|
6680
|
+
targetDir = path5.resolve(opts.target);
|
|
6681
|
+
} else {
|
|
6682
|
+
const agentConfig = AGENT_PATHS[opts.agent];
|
|
6683
|
+
if (!agentConfig) {
|
|
6684
|
+
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
6685
|
+
}
|
|
6686
|
+
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
6687
|
+
targetDir = path5.resolve(scopePath);
|
|
6688
|
+
}
|
|
5181
6689
|
if (!fs3.existsSync(targetDir)) {
|
|
5182
|
-
success({ skills: [], message:
|
|
6690
|
+
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
5183
6691
|
return;
|
|
5184
6692
|
}
|
|
5185
6693
|
const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
|
|
5186
6694
|
const skillPath = path5.join(targetDir, name, "SKILL.md");
|
|
5187
6695
|
return fs3.existsSync(skillPath);
|
|
5188
6696
|
});
|
|
5189
|
-
const
|
|
6697
|
+
const skillsList = skillDirs.map((name) => {
|
|
5190
6698
|
const content = fs3.readFileSync(path5.join(targetDir, name, "SKILL.md"), "utf8");
|
|
5191
6699
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
5192
6700
|
const data = {};
|
|
6701
|
+
const metadata = {};
|
|
5193
6702
|
if (frontmatter) {
|
|
6703
|
+
let inMetadata = false;
|
|
5194
6704
|
for (const line of frontmatter[1].split("\n")) {
|
|
6705
|
+
if (line.trimEnd() === "metadata:") {
|
|
6706
|
+
inMetadata = true;
|
|
6707
|
+
continue;
|
|
6708
|
+
}
|
|
6709
|
+
if (inMetadata && line.startsWith(" ")) {
|
|
6710
|
+
const colonIdx2 = line.indexOf(":");
|
|
6711
|
+
if (colonIdx2 !== -1) {
|
|
6712
|
+
metadata[line.slice(0, colonIdx2).trim()] = line.slice(colonIdx2 + 1).trim();
|
|
6713
|
+
}
|
|
6714
|
+
continue;
|
|
6715
|
+
}
|
|
6716
|
+
inMetadata = false;
|
|
5195
6717
|
const colonIdx = line.indexOf(":");
|
|
5196
6718
|
if (colonIdx === -1) continue;
|
|
5197
6719
|
data[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 1).trim();
|
|
@@ -5200,12 +6722,13 @@ function registerSkillsCommands(program2) {
|
|
|
5200
6722
|
return {
|
|
5201
6723
|
name: data.name || name,
|
|
5202
6724
|
slug: name,
|
|
5203
|
-
category: data.category || "other",
|
|
5204
|
-
services: data.services || "",
|
|
5205
|
-
providers: data.providers || "none"
|
|
6725
|
+
category: metadata.category || data.category || "other",
|
|
6726
|
+
services: metadata.services || data.services || "",
|
|
6727
|
+
providers: metadata.providers || data.providers || "none",
|
|
6728
|
+
userInvocable: data["user-invocable"] === "true"
|
|
5206
6729
|
};
|
|
5207
6730
|
});
|
|
5208
|
-
success({ skills:
|
|
6731
|
+
success({ skills: skillsList, total: skillsList.length }, "skills", "list", start);
|
|
5209
6732
|
} catch (err) {
|
|
5210
6733
|
fail(err, "skills", "list", start);
|
|
5211
6734
|
}
|
|
@@ -5219,13 +6742,14 @@ if (!process.env.PNCLI_VERIFY_TLS) {
|
|
|
5219
6742
|
var require2 = createRequire(import.meta.url);
|
|
5220
6743
|
var pkg = require2("../package.json");
|
|
5221
6744
|
var TAGLINE = "One command does what three meetings couldn't.";
|
|
5222
|
-
var program = new
|
|
5223
|
-
program.name("pncli").description(`The Paperwork Nightmare CLI \u2014 ${TAGLINE}`).version(`${pkg.version} \u2014 ${TAGLINE}`, "-v, --version").option("--pretty", "Human-readable formatted output", false).option("--verbose", "Include full response metadata", false).option("--dry-run", "Print API requests without executing", false).option("--config <path>", "Override global config file location");
|
|
6745
|
+
var program = new Command2();
|
|
6746
|
+
program.name("pncli").description(`The Paperwork Nightmare CLI \u2014 ${TAGLINE}`).version(`${pkg.version} \u2014 ${TAGLINE}`, "-v, --version").option("--pretty", "Human-readable formatted output", false).option("--verbose", "Include full response metadata", false).option("--dry-run", "Print API requests without executing", false).option("--config <path>", "Override global config file location").option("--output-file <path>", "Write JSON output to file instead of stdout");
|
|
5224
6747
|
program.hook("preAction", (thisCommand) => {
|
|
5225
6748
|
const opts = thisCommand.optsWithGlobals();
|
|
5226
6749
|
setGlobalOptions({
|
|
5227
6750
|
pretty: Boolean(opts.pretty),
|
|
5228
|
-
verbose: Boolean(opts.verbose)
|
|
6751
|
+
verbose: Boolean(opts.verbose),
|
|
6752
|
+
outputFile: opts.outputFile
|
|
5229
6753
|
});
|
|
5230
6754
|
try {
|
|
5231
6755
|
const config = loadConfig({ configPath: opts.config });
|
|
@@ -5242,6 +6766,12 @@ registerSdeCommands(program);
|
|
|
5242
6766
|
registerDepsCommands(program);
|
|
5243
6767
|
registerConfigCommands(program);
|
|
5244
6768
|
registerAdoCommands(program);
|
|
6769
|
+
registerJenkinsCommands(program);
|
|
6770
|
+
registerArtifactoryCommands(program);
|
|
6771
|
+
registerUdeployCommands(program);
|
|
6772
|
+
registerCheckmarxCommands(program);
|
|
6773
|
+
registerServiceNowCommands(program);
|
|
6774
|
+
registerContrastCommands(program);
|
|
5245
6775
|
registerSkillsCommands(program);
|
|
5246
6776
|
program.addHelpText("after", `
|
|
5247
6777
|
Services:
|
|
@@ -5253,6 +6783,12 @@ Services:
|
|
|
5253
6783
|
sonar SonarQube Server (quality gates, issues, metrics, hotspots)
|
|
5254
6784
|
sde SDElements (threat modeling, countermeasures, compliance)
|
|
5255
6785
|
ado Azure DevOps Server (work items, repos, PRs, pipelines)
|
|
6786
|
+
jenkins Jenkins Data Center (jobs, builds, logs)
|
|
6787
|
+
artifactory Artifactory (repos, artifact search, build info, properties)
|
|
6788
|
+
udeploy IBM UrbanCode Deploy (component versions, deployment processes)
|
|
6789
|
+
checkmarx Checkmarx CxSAST (projects, scans, scan statistics)
|
|
6790
|
+
servicenow ServiceNow (change requests)
|
|
6791
|
+
contrast Contrast IAST (applications, vulnerability findings)
|
|
5256
6792
|
config Manage pncli configuration
|
|
5257
6793
|
skills Download and manage Claude Code skills
|
|
5258
6794
|
`);
|