@kolatts/pncli 1.18.0 → 1.20.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 +404 -0
- package/README.md +5 -3
- package/copilot-instructions.md +22 -5
- package/dist/{chunk-IVUDXPDP.js → chunk-FXY262BR.js} +26 -6
- package/dist/chunk-FXY262BR.js.map +1 -0
- package/dist/{chunk-WX4ZRHIC.js → chunk-PC4AHQCN.js} +115 -22
- package/dist/chunk-PC4AHQCN.js.map +1 -0
- package/dist/cli.js +565 -75
- package/dist/cli.js.map +1 -1
- package/dist/{config-QN7KDHE4.js → config-CPRXY5SN.js} +2 -2
- package/dist/{http-DDY5DLF6.js → http-IXJA4TMS.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +2 -1
- package/skills/pncli/ado.md +3 -3
- package/skills/pncli/artifactory.md +3 -3
- package/skills/pncli/bitbucket.md +3 -3
- package/skills/pncli/checkmarx.md +20 -13
- package/skills/pncli/confluence.md +3 -3
- package/skills/pncli/contrast.md +1 -1
- package/skills/pncli/dynatrace.md +46 -0
- package/skills/pncli/jenkins.md +5 -5
- package/skills/pncli/jira.md +3 -3
- package/skills/pncli/marketplace.md +1 -1
- package/skills/pncli/openshift.md +4 -4
- package/skills/pncli/sde.md +3 -3
- package/skills/pncli/servicenow.md +3 -3
- package/skills/pncli/sonarqube.md +3 -3
- package/skills/pncli/sonatypeiq.md +3 -3
- package/skills/pncli/udeploy.md +3 -3
- package/dist/chunk-IVUDXPDP.js.map +0 -1
- package/dist/chunk-WX4ZRHIC.js.map +0 -1
- /package/dist/{config-QN7KDHE4.js.map → config-CPRXY5SN.js.map} +0 -0
- /package/dist/{http-DDY5DLF6.js.map → http-IXJA4TMS.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
setRepoConfigValue,
|
|
9
9
|
writeGlobalConfig,
|
|
10
10
|
writeRepoConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FXY262BR.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCode,
|
|
14
14
|
PncliError,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
success,
|
|
21
21
|
warn,
|
|
22
22
|
writeRawOutput
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PC4AHQCN.js";
|
|
24
24
|
import {
|
|
25
25
|
AdoCoreClient
|
|
26
26
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -683,9 +683,9 @@ var BitbucketClient = class {
|
|
|
683
683
|
async getDiff(project, repo, prId, file, contextLines) {
|
|
684
684
|
const params = {};
|
|
685
685
|
if (contextLines !== void 0) params.contextLines = contextLines;
|
|
686
|
-
|
|
686
|
+
const diffPath = file ? `${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/diff/${file}` : `${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/diff`;
|
|
687
687
|
const result = await this.http.bitbucket(
|
|
688
|
-
|
|
688
|
+
diffPath,
|
|
689
689
|
{ params }
|
|
690
690
|
);
|
|
691
691
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
@@ -697,21 +697,46 @@ var BitbucketClient = class {
|
|
|
697
697
|
);
|
|
698
698
|
return (result.values ?? []).map((v) => typeof v.path === "string" ? v.path : JSON.stringify(v.path));
|
|
699
699
|
}
|
|
700
|
+
async getCurrentUser() {
|
|
701
|
+
const { headers } = await this.http.bitbucketRaw(
|
|
702
|
+
`${API}/application-properties`
|
|
703
|
+
);
|
|
704
|
+
const username = headers.get("x-ausername");
|
|
705
|
+
if (!username) {
|
|
706
|
+
throw new PncliError(
|
|
707
|
+
"Could not determine current Bitbucket user: X-AUSERNAME header was absent from the server response",
|
|
708
|
+
1,
|
|
709
|
+
`${API}/application-properties`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
return this.http.bitbucket(`${API}/users/${encodeURIComponent(username)}`);
|
|
713
|
+
}
|
|
700
714
|
async approvePR(project, repo, prId) {
|
|
701
715
|
return this.http.bitbucket(
|
|
702
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/
|
|
703
|
-
{ method: "
|
|
716
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/approve`,
|
|
717
|
+
{ method: "POST" }
|
|
704
718
|
);
|
|
705
719
|
}
|
|
706
720
|
async unapprovePR(project, repo, prId) {
|
|
707
721
|
return this.http.bitbucket(
|
|
708
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/
|
|
722
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/approve`,
|
|
709
723
|
{ method: "DELETE" }
|
|
710
724
|
);
|
|
711
725
|
}
|
|
712
726
|
async needsWorkPR(project, repo, prId) {
|
|
727
|
+
const [currentUser, pr] = await Promise.all([
|
|
728
|
+
this.getCurrentUser(),
|
|
729
|
+
this.getPR(project, repo, prId)
|
|
730
|
+
]);
|
|
731
|
+
const isReviewer = pr.reviewers.some((r) => r.user.slug === currentUser.slug);
|
|
732
|
+
if (!isReviewer) {
|
|
733
|
+
await this.http.bitbucket(
|
|
734
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants`,
|
|
735
|
+
{ method: "POST", body: { user: { name: currentUser.name }, role: "REVIEWER" } }
|
|
736
|
+
);
|
|
737
|
+
}
|
|
713
738
|
return this.http.bitbucket(
|
|
714
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants
|
|
739
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants/${currentUser.slug}`,
|
|
715
740
|
{ method: "PUT", body: { status: "NEEDS_WORK" } }
|
|
716
741
|
);
|
|
717
742
|
}
|
|
@@ -4753,7 +4778,7 @@ function requireArtifactory(config, command) {
|
|
|
4753
4778
|
if (!config.baseUrl && !config.token) {
|
|
4754
4779
|
throw new PncliError(
|
|
4755
4780
|
`${command} requires Artifactory. Add the following to ~/.pncli/config.json:
|
|
4756
|
-
"artifactory": { "baseUrl": "https://artifactory.
|
|
4781
|
+
"artifactory": { "baseUrl": "https://artifactory.imagile.dev", "token": "your-token" }
|
|
4757
4782
|
Or set PNCLI_ARTIFACTORY_BASE_URL and PNCLI_ARTIFACTORY_TOKEN environment variables.`,
|
|
4758
4783
|
503
|
|
4759
4784
|
);
|
|
@@ -5639,9 +5664,9 @@ function registerConfigCommands(program2) {
|
|
|
5639
5664
|
} else {
|
|
5640
5665
|
results.udeploy = { ok: null, message: "not configured" };
|
|
5641
5666
|
}
|
|
5642
|
-
if (cfg.checkmarx.baseUrl && cfg.checkmarx.
|
|
5667
|
+
if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
|
|
5643
5668
|
try {
|
|
5644
|
-
await http.checkmarx("/
|
|
5669
|
+
await http.checkmarx("/api/projects", { params: { limit: 1 } });
|
|
5645
5670
|
results.checkmarx = { ok: true, message: "connected" };
|
|
5646
5671
|
} catch (err) {
|
|
5647
5672
|
results.checkmarx = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -5690,6 +5715,37 @@ function registerConfigCommands(program2) {
|
|
|
5690
5715
|
} else {
|
|
5691
5716
|
results.openshift = { ok: null, message: "not configured" };
|
|
5692
5717
|
}
|
|
5718
|
+
if (cfg.dynatrace.baseUrl && cfg.dynatrace.apiToken) {
|
|
5719
|
+
try {
|
|
5720
|
+
await http.dynatrace("/api/v2/entities", {
|
|
5721
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 }
|
|
5722
|
+
});
|
|
5723
|
+
results.dynatrace = { ok: true, message: "connected" };
|
|
5724
|
+
} catch (err) {
|
|
5725
|
+
results.dynatrace = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5726
|
+
}
|
|
5727
|
+
} else {
|
|
5728
|
+
results.dynatrace = { ok: null, message: "not configured" };
|
|
5729
|
+
}
|
|
5730
|
+
if (cfg.dynatrace.platformUrl && cfg.dynatrace.platformToken) {
|
|
5731
|
+
try {
|
|
5732
|
+
await http.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
5733
|
+
method: "POST",
|
|
5734
|
+
body: {
|
|
5735
|
+
query: "fetch spans | limit 1",
|
|
5736
|
+
requestTimeoutMilliseconds: 5e3
|
|
5737
|
+
},
|
|
5738
|
+
timeoutMs: 1e4
|
|
5739
|
+
});
|
|
5740
|
+
results.dynatrace_platform = { ok: true, message: "connected" };
|
|
5741
|
+
} catch (err) {
|
|
5742
|
+
results.dynatrace_platform = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5743
|
+
}
|
|
5744
|
+
} else if (cfg.dynatrace.platformUrl || cfg.dynatrace.platformToken) {
|
|
5745
|
+
results.dynatrace_platform = { ok: false, message: "platformUrl and platformToken must both be configured" };
|
|
5746
|
+
} else {
|
|
5747
|
+
results.dynatrace_platform = { ok: null, message: "not configured" };
|
|
5748
|
+
}
|
|
5693
5749
|
success(results, "config", "test", start);
|
|
5694
5750
|
} catch (err) {
|
|
5695
5751
|
fail(err, "config", "test", start);
|
|
@@ -5836,13 +5892,13 @@ function registerConfigCommands(program2) {
|
|
|
5836
5892
|
} else {
|
|
5837
5893
|
results.artifactory = { status: "valid", message: "ok" };
|
|
5838
5894
|
}
|
|
5839
|
-
if (!cfg.checkmarx.
|
|
5895
|
+
if (!cfg.checkmarx.tenantName || !cfg.checkmarx.apiKey && (!cfg.checkmarx.clientId || !cfg.checkmarx.clientSecret)) {
|
|
5840
5896
|
results.checkmarx = { status: "blank", message: "not configured" };
|
|
5841
5897
|
} else if (!cfg.checkmarx.baseUrl) {
|
|
5842
5898
|
results.checkmarx = { status: "error", message: "baseUrl not configured" };
|
|
5843
5899
|
} else {
|
|
5844
5900
|
try {
|
|
5845
|
-
await http.checkmarx("/
|
|
5901
|
+
await http.checkmarx("/api/projects", { params: { limit: 1 }, timeoutMs: 1e4 });
|
|
5846
5902
|
results.checkmarx = { status: "valid", message: "ok" };
|
|
5847
5903
|
} catch (err) {
|
|
5848
5904
|
results.checkmarx = categorize2(err);
|
|
@@ -5897,11 +5953,48 @@ function registerConfigCommands(program2) {
|
|
|
5897
5953
|
results.openshift = categorize2(err);
|
|
5898
5954
|
}
|
|
5899
5955
|
}
|
|
5956
|
+
if (!cfg.dynatrace.apiToken) {
|
|
5957
|
+
results.dynatrace = { status: "blank", message: "not configured" };
|
|
5958
|
+
} else if (!cfg.dynatrace.baseUrl) {
|
|
5959
|
+
results.dynatrace = { status: "error", message: "baseUrl not configured" };
|
|
5960
|
+
} else {
|
|
5961
|
+
try {
|
|
5962
|
+
await http.dynatrace("/api/v2/entities", {
|
|
5963
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 },
|
|
5964
|
+
timeoutMs: 1e4
|
|
5965
|
+
});
|
|
5966
|
+
results.dynatrace = { status: "valid", message: "ok" };
|
|
5967
|
+
} catch (err) {
|
|
5968
|
+
results.dynatrace = categorize2(err);
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
if (!cfg.dynatrace.platformUrl && !cfg.dynatrace.platformToken) {
|
|
5972
|
+
results.dynatrace_platform = { status: "blank", message: "not configured" };
|
|
5973
|
+
} else if (!cfg.dynatrace.platformUrl || !cfg.dynatrace.platformToken) {
|
|
5974
|
+
results.dynatrace_platform = {
|
|
5975
|
+
status: "error",
|
|
5976
|
+
message: "platformUrl and platformToken must both be configured"
|
|
5977
|
+
};
|
|
5978
|
+
} else {
|
|
5979
|
+
try {
|
|
5980
|
+
await http.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
5981
|
+
method: "POST",
|
|
5982
|
+
body: {
|
|
5983
|
+
query: "fetch spans | limit 1",
|
|
5984
|
+
requestTimeoutMilliseconds: 5e3
|
|
5985
|
+
},
|
|
5986
|
+
timeoutMs: 1e4
|
|
5987
|
+
});
|
|
5988
|
+
results.dynatrace_platform = { status: "valid", message: "ok" };
|
|
5989
|
+
} catch (err) {
|
|
5990
|
+
results.dynatrace_platform = categorize2(err);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5900
5993
|
const statuses = Object.values(results).map((r) => r.status);
|
|
5901
5994
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
5902
5995
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
5903
5996
|
if (cmdOpts.output === "table") {
|
|
5904
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
5997
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
|
|
5905
5998
|
const labelWidth = 14;
|
|
5906
5999
|
const statusWidth = 9;
|
|
5907
6000
|
for (const svc of services) {
|
|
@@ -5915,7 +6008,7 @@ function registerConfigCommands(program2) {
|
|
|
5915
6008
|
process.stdout.write("\n");
|
|
5916
6009
|
} else {
|
|
5917
6010
|
if (opts.pretty) {
|
|
5918
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
6011
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
|
|
5919
6012
|
const labelWidth = 14;
|
|
5920
6013
|
const statusWidth = 9;
|
|
5921
6014
|
for (const svc of services) {
|
|
@@ -5953,7 +6046,7 @@ async function initGlobalConfig(start) {
|
|
|
5953
6046
|
});
|
|
5954
6047
|
process.stderr.write("\n\u2500\u2500 Jira \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\u2500\u2500\u2500\n");
|
|
5955
6048
|
const jiraBaseUrl = await input({
|
|
5956
|
-
message: "Jira base URL (e.g. jira.
|
|
6049
|
+
message: "Jira base URL (e.g. jira.imagile.dev):",
|
|
5957
6050
|
default: ""
|
|
5958
6051
|
});
|
|
5959
6052
|
const jiraApiToken = await password({
|
|
@@ -5961,7 +6054,7 @@ async function initGlobalConfig(start) {
|
|
|
5961
6054
|
});
|
|
5962
6055
|
process.stderr.write("\n\u2500\u2500 Bitbucket \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");
|
|
5963
6056
|
const bitbucketBaseUrl = await input({
|
|
5964
|
-
message: "Bitbucket Server base URL (e.g. bitbucket.
|
|
6057
|
+
message: "Bitbucket Server base URL (e.g. bitbucket.imagile.dev):",
|
|
5965
6058
|
default: ""
|
|
5966
6059
|
});
|
|
5967
6060
|
const bitbucketPat = await password({
|
|
@@ -6003,7 +6096,7 @@ async function initGlobalConfig(start) {
|
|
|
6003
6096
|
}
|
|
6004
6097
|
process.stderr.write("\n\u2500\u2500 Confluence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
6005
6098
|
const confluenceBaseUrl = await input({
|
|
6006
|
-
message: "Confluence base URL (e.g. confluence.
|
|
6099
|
+
message: "Confluence base URL (e.g. confluence.imagile.dev):",
|
|
6007
6100
|
default: ""
|
|
6008
6101
|
});
|
|
6009
6102
|
const confluenceApiToken = await password({
|
|
@@ -6021,7 +6114,7 @@ async function initGlobalConfig(start) {
|
|
|
6021
6114
|
let mavenRepo = "";
|
|
6022
6115
|
if (useArtifactory) {
|
|
6023
6116
|
artifactoryBaseUrl = await input({
|
|
6024
|
-
message: "Artifactory base URL (e.g. artifactory.
|
|
6117
|
+
message: "Artifactory base URL (e.g. artifactory.imagile.dev):",
|
|
6025
6118
|
default: ""
|
|
6026
6119
|
});
|
|
6027
6120
|
artifactoryToken = await password({
|
|
@@ -6050,7 +6143,7 @@ async function initGlobalConfig(start) {
|
|
|
6050
6143
|
let sonarToken = "";
|
|
6051
6144
|
if (useSonar) {
|
|
6052
6145
|
sonarBaseUrl = await input({
|
|
6053
|
-
message: "SonarQube Server base URL (e.g. sonar.
|
|
6146
|
+
message: "SonarQube Server base URL (e.g. sonar.imagile.dev):",
|
|
6054
6147
|
default: ""
|
|
6055
6148
|
});
|
|
6056
6149
|
sonarToken = await password({
|
|
@@ -6081,7 +6174,7 @@ async function initGlobalConfig(start) {
|
|
|
6081
6174
|
}
|
|
6082
6175
|
} else {
|
|
6083
6176
|
sdeBaseUrl = await input({
|
|
6084
|
-
message: "SDElements hostname\n Cloud-hosted:
|
|
6177
|
+
message: "SDElements hostname\n Cloud-hosted: imagile.sdelements.com\n On-premise: sde.imagile.dev\n Host: ",
|
|
6085
6178
|
default: ""
|
|
6086
6179
|
});
|
|
6087
6180
|
sdeToken = await password({
|
|
@@ -6103,7 +6196,7 @@ async function initGlobalConfig(start) {
|
|
|
6103
6196
|
let adoDiscoveredTypes = [];
|
|
6104
6197
|
if (useAdo) {
|
|
6105
6198
|
adoBaseUrl = await input({
|
|
6106
|
-
message: "Azure DevOps Server base URL (e.g. tfs.
|
|
6199
|
+
message: "Azure DevOps Server base URL (e.g. tfs.imagile.dev or devops.imagile.dev/tfs):",
|
|
6107
6200
|
default: ""
|
|
6108
6201
|
});
|
|
6109
6202
|
adoPat = await password({
|
|
@@ -6175,7 +6268,7 @@ async function initGlobalConfig(start) {
|
|
|
6175
6268
|
let jenkinsApiToken = "";
|
|
6176
6269
|
if (useJenkins) {
|
|
6177
6270
|
jenkinsBaseUrl = await input({
|
|
6178
|
-
message: "Jenkins base URL (e.g. jenkins.
|
|
6271
|
+
message: "Jenkins base URL (e.g. jenkins.imagile.dev):",
|
|
6179
6272
|
default: ""
|
|
6180
6273
|
});
|
|
6181
6274
|
jenkinsUsername = await input({
|
|
@@ -6199,7 +6292,7 @@ async function initGlobalConfig(start) {
|
|
|
6199
6292
|
let udeployDefaultEnv = "";
|
|
6200
6293
|
if (useUdeploy) {
|
|
6201
6294
|
udeployBaseUrl = await input({
|
|
6202
|
-
message: "UDeploy base URL (e.g. ucd.
|
|
6295
|
+
message: "UDeploy base URL (e.g. ucd.imagile.dev:8443):",
|
|
6203
6296
|
default: ""
|
|
6204
6297
|
});
|
|
6205
6298
|
udeployUsername = await input({
|
|
@@ -6248,38 +6341,51 @@ async function initGlobalConfig(start) {
|
|
|
6248
6341
|
}
|
|
6249
6342
|
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");
|
|
6250
6343
|
const useCheckmarx = await confirm({
|
|
6251
|
-
message: "Configure Checkmarx
|
|
6344
|
+
message: "Configure Checkmarx One for vulnerability scanning?",
|
|
6252
6345
|
default: false
|
|
6253
6346
|
});
|
|
6254
6347
|
let checkmarxBaseUrl = "";
|
|
6255
|
-
let
|
|
6256
|
-
let
|
|
6348
|
+
let checkmarxTenantName = "";
|
|
6349
|
+
let checkmarxApiKey = "";
|
|
6350
|
+
let checkmarxClientId = "";
|
|
6351
|
+
let checkmarxClientSecret = "";
|
|
6257
6352
|
if (useCheckmarx) {
|
|
6258
6353
|
checkmarxBaseUrl = await input({
|
|
6259
|
-
message: "Checkmarx base URL (e.g.
|
|
6354
|
+
message: "Checkmarx One API base URL (e.g. https://ast.checkmarx.net):",
|
|
6260
6355
|
default: ""
|
|
6261
6356
|
});
|
|
6262
|
-
|
|
6263
|
-
message: "Checkmarx
|
|
6357
|
+
checkmarxTenantName = await input({
|
|
6358
|
+
message: "Checkmarx One tenant name (IAM realm, e.g. imagile):",
|
|
6264
6359
|
default: ""
|
|
6265
6360
|
});
|
|
6266
|
-
|
|
6267
|
-
message: "Checkmarx
|
|
6268
|
-
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
6361
|
+
checkmarxApiKey = await password({
|
|
6362
|
+
message: "Checkmarx One API key (leave blank to use an OAuth client):"
|
|
6269
6363
|
});
|
|
6270
|
-
if (
|
|
6364
|
+
if (!checkmarxApiKey) {
|
|
6365
|
+
checkmarxClientId = await input({
|
|
6366
|
+
message: "Checkmarx One OAuth client ID:",
|
|
6367
|
+
default: ""
|
|
6368
|
+
});
|
|
6369
|
+
checkmarxClientSecret = await password({
|
|
6370
|
+
message: "Checkmarx One OAuth client secret:",
|
|
6371
|
+
validate: (v) => v.length > 0 || "Client secret cannot be blank"
|
|
6372
|
+
});
|
|
6373
|
+
}
|
|
6374
|
+
if (checkmarxBaseUrl && checkmarxTenantName && (checkmarxApiKey || checkmarxClientId && checkmarxClientSecret)) {
|
|
6271
6375
|
process.stderr.write("\n Verifying connection...\n");
|
|
6272
6376
|
try {
|
|
6273
6377
|
const tempConfig = {
|
|
6274
6378
|
...loadConfig(),
|
|
6275
6379
|
checkmarx: {
|
|
6276
6380
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
6277
|
-
|
|
6278
|
-
|
|
6381
|
+
tenantName: checkmarxTenantName,
|
|
6382
|
+
apiKey: checkmarxApiKey || void 0,
|
|
6383
|
+
clientId: checkmarxClientId,
|
|
6384
|
+
clientSecret: checkmarxClientSecret
|
|
6279
6385
|
}
|
|
6280
6386
|
};
|
|
6281
6387
|
const tempHttp = createHttpClient(tempConfig);
|
|
6282
|
-
await tempHttp.checkmarx("/
|
|
6388
|
+
await tempHttp.checkmarx("/api/projects", { params: { limit: 1 } });
|
|
6283
6389
|
process.stderr.write(" Connected.\n");
|
|
6284
6390
|
} catch (err) {
|
|
6285
6391
|
warn(`Could not connect to Checkmarx: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6298,7 +6404,7 @@ async function initGlobalConfig(start) {
|
|
|
6298
6404
|
let servicenowApiToken = "";
|
|
6299
6405
|
if (useServiceNow) {
|
|
6300
6406
|
servicenowBaseUrl = await input({
|
|
6301
|
-
message: "ServiceNow instance URL (e.g.
|
|
6407
|
+
message: "ServiceNow instance URL (e.g. imagile.service-now.com):",
|
|
6302
6408
|
default: ""
|
|
6303
6409
|
});
|
|
6304
6410
|
servicenowUsername = await input({
|
|
@@ -6353,7 +6459,7 @@ async function initGlobalConfig(start) {
|
|
|
6353
6459
|
if (useSonatypeIq) {
|
|
6354
6460
|
process.stderr.write(" User Token credentials are found in your IQ Server profile under User Token.\n");
|
|
6355
6461
|
sonatypeIqBaseUrl = await input({
|
|
6356
|
-
message: "Sonatype IQ Server base URL (e.g. iq.
|
|
6462
|
+
message: "Sonatype IQ Server base URL (e.g. iq.imagile.dev):",
|
|
6357
6463
|
default: ""
|
|
6358
6464
|
});
|
|
6359
6465
|
sonatypeIqUserCode = await input({
|
|
@@ -6447,7 +6553,7 @@ async function initGlobalConfig(start) {
|
|
|
6447
6553
|
if (useOpenShift) {
|
|
6448
6554
|
process.stderr.write(" Service account token: cat /var/run/secrets/kubernetes.io/serviceaccount/token\n");
|
|
6449
6555
|
openShiftBaseUrl = await input({
|
|
6450
|
-
message: "OpenShift API server URL (e.g. https://api.cluster.
|
|
6556
|
+
message: "OpenShift API server URL (e.g. https://api.cluster.imagile.dev:6443):",
|
|
6451
6557
|
default: ""
|
|
6452
6558
|
});
|
|
6453
6559
|
openShiftToken = await password({
|
|
@@ -6472,6 +6578,59 @@ async function initGlobalConfig(start) {
|
|
|
6472
6578
|
}
|
|
6473
6579
|
}
|
|
6474
6580
|
}
|
|
6581
|
+
process.stderr.write("\n\u2500\u2500 Dynatrace \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");
|
|
6582
|
+
const useDynatrace = await confirm({
|
|
6583
|
+
message: "Configure Dynatrace for services, problems, workloads, and traces?",
|
|
6584
|
+
default: false
|
|
6585
|
+
});
|
|
6586
|
+
let dynatraceBaseUrl = "";
|
|
6587
|
+
let dynatraceApiToken = "";
|
|
6588
|
+
let dynatracePlatformUrl = "";
|
|
6589
|
+
let dynatracePlatformToken = "";
|
|
6590
|
+
if (useDynatrace) {
|
|
6591
|
+
dynatraceBaseUrl = await input({
|
|
6592
|
+
message: "Dynatrace environment URL (e.g. https://abc12345.live.dynatrace.com):",
|
|
6593
|
+
default: ""
|
|
6594
|
+
});
|
|
6595
|
+
dynatraceApiToken = await password({
|
|
6596
|
+
message: "Environment API token (entities.read and problems.read):"
|
|
6597
|
+
});
|
|
6598
|
+
const configureTraces = await confirm({
|
|
6599
|
+
message: "Configure the latest Dynatrace platform for Grail trace queries?",
|
|
6600
|
+
default: false
|
|
6601
|
+
});
|
|
6602
|
+
if (configureTraces) {
|
|
6603
|
+
dynatracePlatformUrl = await input({
|
|
6604
|
+
message: "Dynatrace platform URL (e.g. https://abc12345.apps.dynatrace.com):",
|
|
6605
|
+
default: ""
|
|
6606
|
+
});
|
|
6607
|
+
dynatracePlatformToken = await password({
|
|
6608
|
+
message: "Platform token with Grail span read permissions:"
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
if (dynatraceBaseUrl && dynatraceApiToken) {
|
|
6612
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
6613
|
+
try {
|
|
6614
|
+
const tempConfig = {
|
|
6615
|
+
...loadConfig(),
|
|
6616
|
+
dynatrace: {
|
|
6617
|
+
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
6618
|
+
apiToken: dynatraceApiToken,
|
|
6619
|
+
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
6620
|
+
platformToken: dynatracePlatformToken || void 0
|
|
6621
|
+
}
|
|
6622
|
+
};
|
|
6623
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
6624
|
+
await tempHttp.dynatrace("/api/v2/entities", {
|
|
6625
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 }
|
|
6626
|
+
});
|
|
6627
|
+
process.stderr.write(" Connected.\n");
|
|
6628
|
+
} catch (err) {
|
|
6629
|
+
warn(`Could not connect to Dynatrace: ${err instanceof Error ? err.message : String(err)}`);
|
|
6630
|
+
warn("Config will be saved anyway. Check your URLs and tokens and re-run pncli config test.");
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6475
6634
|
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");
|
|
6476
6635
|
const jiraProject = await input({
|
|
6477
6636
|
message: "Default Jira project key (optional):",
|
|
@@ -6567,8 +6726,10 @@ async function initGlobalConfig(start) {
|
|
|
6567
6726
|
...useCheckmarx && checkmarxBaseUrl ? {
|
|
6568
6727
|
checkmarx: {
|
|
6569
6728
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
6570
|
-
|
|
6571
|
-
|
|
6729
|
+
tenantName: checkmarxTenantName || void 0,
|
|
6730
|
+
apiKey: checkmarxApiKey || void 0,
|
|
6731
|
+
clientId: checkmarxClientId || void 0,
|
|
6732
|
+
clientSecret: checkmarxClientSecret || void 0
|
|
6572
6733
|
}
|
|
6573
6734
|
} : {},
|
|
6574
6735
|
...useServiceNow && servicenowBaseUrl ? {
|
|
@@ -6601,6 +6762,14 @@ async function initGlobalConfig(start) {
|
|
|
6601
6762
|
token: openShiftToken || void 0
|
|
6602
6763
|
}
|
|
6603
6764
|
} : {},
|
|
6765
|
+
...useDynatrace && dynatraceBaseUrl ? {
|
|
6766
|
+
dynatrace: {
|
|
6767
|
+
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
6768
|
+
apiToken: dynatraceApiToken || void 0,
|
|
6769
|
+
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
6770
|
+
platformToken: dynatracePlatformToken || void 0
|
|
6771
|
+
}
|
|
6772
|
+
} : {},
|
|
6604
6773
|
defaults: {
|
|
6605
6774
|
jira: {
|
|
6606
6775
|
project: jiraProject || void 0
|
|
@@ -7688,11 +7857,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
7688
7857
|
const start = Date.now();
|
|
7689
7858
|
try {
|
|
7690
7859
|
const globalOpts = ado.optsWithGlobals();
|
|
7691
|
-
const { loadConfig: lc } = await import("./config-
|
|
7860
|
+
const { loadConfig: lc } = await import("./config-CPRXY5SN.js");
|
|
7692
7861
|
const config = lc({ configPath: globalOpts.config });
|
|
7693
7862
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7694
7863
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
7695
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
7864
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-IXJA4TMS.js");
|
|
7696
7865
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
7697
7866
|
const coreClient = new AdoCoreClient2(http);
|
|
7698
7867
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8506,24 +8675,35 @@ var CheckmarxClient = class {
|
|
|
8506
8675
|
}
|
|
8507
8676
|
http;
|
|
8508
8677
|
async listProjects() {
|
|
8509
|
-
|
|
8678
|
+
const res = await this.http.checkmarx("projects", { params: { limit: 100 } });
|
|
8679
|
+
const projects = res.projects ?? [];
|
|
8680
|
+
if (res.filteredTotalCount > projects.length) {
|
|
8681
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} projects found; only showing first ${projects.length}
|
|
8682
|
+
`);
|
|
8683
|
+
}
|
|
8684
|
+
return projects;
|
|
8510
8685
|
}
|
|
8511
8686
|
async getProject(id) {
|
|
8512
|
-
return this.http.checkmarx(
|
|
8687
|
+
return this.http.checkmarx(`projects/${id}`);
|
|
8513
8688
|
}
|
|
8514
8689
|
async listScans(opts = {}) {
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8690
|
+
const params = { limit: opts.last ?? 100 };
|
|
8691
|
+
if (opts.projectId) params["project-id"] = opts.projectId;
|
|
8692
|
+
const res = await this.http.checkmarx("scans", { params });
|
|
8693
|
+
const scans = res.scans ?? [];
|
|
8694
|
+
if (res.filteredTotalCount > scans.length) {
|
|
8695
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} scans found; only showing first ${scans.length}
|
|
8696
|
+
`);
|
|
8697
|
+
}
|
|
8698
|
+
return scans;
|
|
8521
8699
|
}
|
|
8522
8700
|
async getScan(id) {
|
|
8523
|
-
return this.http.checkmarx(
|
|
8701
|
+
return this.http.checkmarx(`scans/${id}`);
|
|
8524
8702
|
}
|
|
8525
8703
|
async getScanResultsStatistics(scanId) {
|
|
8526
|
-
return this.http.checkmarx(
|
|
8704
|
+
return this.http.checkmarx("results/summary", {
|
|
8705
|
+
params: { "scan-id": scanId }
|
|
8706
|
+
});
|
|
8527
8707
|
}
|
|
8528
8708
|
};
|
|
8529
8709
|
|
|
@@ -8536,7 +8716,7 @@ function getClient10(program2) {
|
|
|
8536
8716
|
return new CheckmarxClient(http);
|
|
8537
8717
|
}
|
|
8538
8718
|
function registerCheckmarxCommands(program2) {
|
|
8539
|
-
const cx = program2.command("checkmarx").description("Checkmarx
|
|
8719
|
+
const cx = program2.command("checkmarx").description("Checkmarx One operations");
|
|
8540
8720
|
const project = cx.command("project").description("Project operations");
|
|
8541
8721
|
const scan = cx.command("scan").description("Scan operations");
|
|
8542
8722
|
project.command("list").description("List all projects").action(async () => {
|
|
@@ -8548,47 +8728,39 @@ function registerCheckmarxCommands(program2) {
|
|
|
8548
8728
|
fail(err, "checkmarx", "project-list", start);
|
|
8549
8729
|
}
|
|
8550
8730
|
});
|
|
8551
|
-
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project
|
|
8731
|
+
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
|
|
8552
8732
|
const start = Date.now();
|
|
8553
8733
|
try {
|
|
8554
|
-
const
|
|
8555
|
-
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
8556
|
-
const data = await getClient10(program2).getProject(id);
|
|
8734
|
+
const data = await getClient10(program2).getProject(opts.id);
|
|
8557
8735
|
success(data, "checkmarx", "project-get", start);
|
|
8558
8736
|
} catch (err) {
|
|
8559
8737
|
fail(err, "checkmarx", "project-get", start);
|
|
8560
8738
|
}
|
|
8561
8739
|
});
|
|
8562
|
-
scan.command("list").description("List scans").option("--project <id>", "Filter by project
|
|
8740
|
+
scan.command("list").description("List scans").option("--project <id>", "Filter by project UUID").option("--last <n>", "Return only the last N scans").action(async (opts) => {
|
|
8563
8741
|
const start = Date.now();
|
|
8564
8742
|
try {
|
|
8565
|
-
const projectId = opts.project ? parseInt(opts.project, 10) : void 0;
|
|
8566
8743
|
const last = opts.last ? parseInt(opts.last, 10) : void 0;
|
|
8567
|
-
if (opts.project && isNaN(projectId)) throw new PncliError(`Invalid --project "${opts.project}". Expected an integer.`, 1);
|
|
8568
8744
|
if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
|
|
8569
|
-
const data = await getClient10(program2).listScans({ projectId, last });
|
|
8745
|
+
const data = await getClient10(program2).listScans({ projectId: opts.project, last });
|
|
8570
8746
|
success(data, "checkmarx", "scan-list", start);
|
|
8571
8747
|
} catch (err) {
|
|
8572
8748
|
fail(err, "checkmarx", "scan-list", start);
|
|
8573
8749
|
}
|
|
8574
8750
|
});
|
|
8575
|
-
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan
|
|
8751
|
+
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
8576
8752
|
const start = Date.now();
|
|
8577
8753
|
try {
|
|
8578
|
-
const
|
|
8579
|
-
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
8580
|
-
const data = await getClient10(program2).getScan(id);
|
|
8754
|
+
const data = await getClient10(program2).getScan(opts.id);
|
|
8581
8755
|
success(data, "checkmarx", "scan-get", start);
|
|
8582
8756
|
} catch (err) {
|
|
8583
8757
|
fail(err, "checkmarx", "scan-get", start);
|
|
8584
8758
|
}
|
|
8585
8759
|
});
|
|
8586
|
-
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan
|
|
8760
|
+
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
8587
8761
|
const start = Date.now();
|
|
8588
8762
|
try {
|
|
8589
|
-
const
|
|
8590
|
-
if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
|
|
8591
|
-
const data = await getClient10(program2).getScanResultsStatistics(id);
|
|
8763
|
+
const data = await getClient10(program2).getScanResultsStatistics(opts.id);
|
|
8592
8764
|
success(data, "checkmarx", "scan-stats", start);
|
|
8593
8765
|
} catch (err) {
|
|
8594
8766
|
fail(err, "checkmarx", "scan-stats", start);
|
|
@@ -8899,18 +9071,52 @@ function getBundledSkillsDir() {
|
|
|
8899
9071
|
function getInstalledMetaPath(targetDir) {
|
|
8900
9072
|
return path7.join(targetDir, ".pncli-installed.json");
|
|
8901
9073
|
}
|
|
9074
|
+
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
8902
9075
|
function readInstalledMeta(targetDir) {
|
|
8903
9076
|
const metaPath = getInstalledMetaPath(targetDir);
|
|
8904
9077
|
const raw = loadJsonFile(metaPath);
|
|
8905
9078
|
if (raw && raw.version === 1 && raw.skills) return raw;
|
|
8906
9079
|
return { version: 1, skills: {} };
|
|
8907
9080
|
}
|
|
9081
|
+
function getSkillOriginPath(skillDir) {
|
|
9082
|
+
return path7.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
9083
|
+
}
|
|
9084
|
+
function readSkillOrigin(skillDir) {
|
|
9085
|
+
const originPath = getSkillOriginPath(skillDir);
|
|
9086
|
+
if (!fs4.existsSync(originPath)) return null;
|
|
9087
|
+
try {
|
|
9088
|
+
const raw = JSON.parse(fs4.readFileSync(originPath, "utf8"));
|
|
9089
|
+
if (raw && raw.version === 1 && raw.source) return raw;
|
|
9090
|
+
} catch {
|
|
9091
|
+
}
|
|
9092
|
+
return null;
|
|
9093
|
+
}
|
|
9094
|
+
function detectRepoBranch(repoPath) {
|
|
9095
|
+
try {
|
|
9096
|
+
const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
9097
|
+
encoding: "utf8",
|
|
9098
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9099
|
+
}).trim();
|
|
9100
|
+
return result && result !== "HEAD" ? result : void 0;
|
|
9101
|
+
} catch {
|
|
9102
|
+
return void 0;
|
|
9103
|
+
}
|
|
9104
|
+
}
|
|
8908
9105
|
function recordInstalledSkills(targetDir, skillNames, record) {
|
|
8909
9106
|
if (skillNames.length === 0) return;
|
|
8910
9107
|
const meta = readInstalledMeta(targetDir);
|
|
8911
9108
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8912
9109
|
for (const skillName of skillNames) {
|
|
8913
|
-
|
|
9110
|
+
const fullRecord = { ...record, installedAt: now };
|
|
9111
|
+
meta.skills[skillName] = fullRecord;
|
|
9112
|
+
const skillDir = path7.join(targetDir, skillName);
|
|
9113
|
+
if (fs4.existsSync(skillDir)) {
|
|
9114
|
+
const origin = { version: 1, ...fullRecord };
|
|
9115
|
+
try {
|
|
9116
|
+
fs4.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
|
|
9117
|
+
} catch {
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
8914
9120
|
}
|
|
8915
9121
|
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
8916
9122
|
}
|
|
@@ -9024,6 +9230,7 @@ function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
|
|
|
9024
9230
|
function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
|
|
9025
9231
|
const results = {};
|
|
9026
9232
|
let totalInstalled = 0;
|
|
9233
|
+
const branch = detectRepoBranch(marketplacePath);
|
|
9027
9234
|
for (const pluginName of pluginNames) {
|
|
9028
9235
|
const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
|
|
9029
9236
|
if (!fs4.existsSync(skillsSrc)) {
|
|
@@ -9034,7 +9241,8 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
9034
9241
|
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir, {
|
|
9035
9242
|
marketplace: marketplaceName,
|
|
9036
9243
|
plugin: pluginName,
|
|
9037
|
-
installedFrom: repoUrl ?? ""
|
|
9244
|
+
installedFrom: repoUrl ?? "",
|
|
9245
|
+
branch
|
|
9038
9246
|
});
|
|
9039
9247
|
results[pluginName] = { installed, failed };
|
|
9040
9248
|
totalInstalled += installed.length;
|
|
@@ -9472,6 +9680,121 @@ function registerSkillsCommands(program2) {
|
|
|
9472
9680
|
fail(err, "skills", "marketplace-sync", start);
|
|
9473
9681
|
}
|
|
9474
9682
|
});
|
|
9683
|
+
marketplace.command("purge-plugin").description("Remove all skills installed from a specific plugin (or all plugins in a marketplace)").argument("<plugin>", 'Plugin name to purge, or "all" to remove every skill from the marketplace').option("--marketplace <name>", "Restrict purge to skills from this marketplace (by name or repo URL)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
9684
|
+
const start = Date.now();
|
|
9685
|
+
try {
|
|
9686
|
+
let targetDir;
|
|
9687
|
+
if (opts.target) {
|
|
9688
|
+
targetDir = path7.resolve(opts.target);
|
|
9689
|
+
} else {
|
|
9690
|
+
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9691
|
+
const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
|
|
9692
|
+
targetDir = path7.resolve(scopePath);
|
|
9693
|
+
}
|
|
9694
|
+
const resolvedTarget = path7.resolve(targetDir);
|
|
9695
|
+
if (!fs4.existsSync(targetDir)) {
|
|
9696
|
+
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
9697
|
+
return;
|
|
9698
|
+
}
|
|
9699
|
+
const meta = readInstalledMeta(targetDir);
|
|
9700
|
+
const removed = [];
|
|
9701
|
+
const skipped = [];
|
|
9702
|
+
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9703
|
+
if (name.startsWith(".")) return false;
|
|
9704
|
+
const p = path7.join(targetDir, name);
|
|
9705
|
+
try {
|
|
9706
|
+
return fs4.statSync(p).isDirectory();
|
|
9707
|
+
} catch {
|
|
9708
|
+
return false;
|
|
9709
|
+
}
|
|
9710
|
+
});
|
|
9711
|
+
for (const skillName of skillDirs) {
|
|
9712
|
+
const skillDir = path7.resolve(targetDir, skillName);
|
|
9713
|
+
if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
|
|
9714
|
+
let record = meta.skills[skillName] ?? null;
|
|
9715
|
+
if (!record) {
|
|
9716
|
+
const perSkill = readSkillOrigin(skillDir);
|
|
9717
|
+
if (perSkill) {
|
|
9718
|
+
record = {
|
|
9719
|
+
source: perSkill.source,
|
|
9720
|
+
marketplace: perSkill.marketplace,
|
|
9721
|
+
plugin: perSkill.plugin,
|
|
9722
|
+
installedFrom: perSkill.installedFrom,
|
|
9723
|
+
branch: perSkill.branch,
|
|
9724
|
+
installedAt: perSkill.installedAt
|
|
9725
|
+
};
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
9728
|
+
if (!record || record.source !== "marketplace") {
|
|
9729
|
+
skipped.push(skillName);
|
|
9730
|
+
continue;
|
|
9731
|
+
}
|
|
9732
|
+
const matchesMarketplace = !opts.marketplace || record.marketplace === opts.marketplace || record.installedFrom === opts.marketplace;
|
|
9733
|
+
const matchesPlugin = plugin === "all" || record.plugin === plugin;
|
|
9734
|
+
if (!matchesMarketplace || !matchesPlugin) {
|
|
9735
|
+
skipped.push(skillName);
|
|
9736
|
+
continue;
|
|
9737
|
+
}
|
|
9738
|
+
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9739
|
+
delete meta.skills[skillName];
|
|
9740
|
+
removed.push(skillName);
|
|
9741
|
+
}
|
|
9742
|
+
if (removed.length > 0) {
|
|
9743
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
9744
|
+
}
|
|
9745
|
+
success({
|
|
9746
|
+
removed,
|
|
9747
|
+
skipped: skipped.length,
|
|
9748
|
+
total: removed.length,
|
|
9749
|
+
target: targetDir,
|
|
9750
|
+
plugin,
|
|
9751
|
+
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
9752
|
+
}, "skills", "marketplace-purge-plugin", start);
|
|
9753
|
+
} catch (err) {
|
|
9754
|
+
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
9755
|
+
}
|
|
9756
|
+
});
|
|
9757
|
+
skills.command("purge-user").description("Remove all skills from the user-level skills folder for the target agent").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", "Skip confirmation \u2014 remove all skills without prompting").action((opts) => {
|
|
9758
|
+
const start = Date.now();
|
|
9759
|
+
try {
|
|
9760
|
+
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
9761
|
+
if (!fs4.existsSync(userDir)) {
|
|
9762
|
+
success({ removed: 0, target: userDir, message: "No user-level skills directory found \u2014 nothing to purge." }, "skills", "purge-user", start);
|
|
9763
|
+
return;
|
|
9764
|
+
}
|
|
9765
|
+
const skillDirs = fs4.readdirSync(userDir).filter((name) => {
|
|
9766
|
+
if (name.startsWith(".")) return false;
|
|
9767
|
+
const p = path7.join(userDir, name);
|
|
9768
|
+
try {
|
|
9769
|
+
return fs4.statSync(p).isDirectory();
|
|
9770
|
+
} catch {
|
|
9771
|
+
return false;
|
|
9772
|
+
}
|
|
9773
|
+
});
|
|
9774
|
+
if (skillDirs.length === 0) {
|
|
9775
|
+
success({ removed: 0, target: userDir, message: "No skills found in user-level skills directory." }, "skills", "purge-user", start);
|
|
9776
|
+
return;
|
|
9777
|
+
}
|
|
9778
|
+
if (!opts.force) {
|
|
9779
|
+
throw new Error(
|
|
9780
|
+
`This will remove ${skillDirs.length} skill(s) from ${userDir}: ${skillDirs.join(", ")}.
|
|
9781
|
+
Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\` to target a specific plugin.`
|
|
9782
|
+
);
|
|
9783
|
+
}
|
|
9784
|
+
for (const name of skillDirs) {
|
|
9785
|
+
const skillDir = path7.resolve(userDir, name);
|
|
9786
|
+
if (!skillDir.startsWith(path7.resolve(userDir) + path7.sep)) continue;
|
|
9787
|
+
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9788
|
+
}
|
|
9789
|
+
const metaPath = getInstalledMetaPath(userDir);
|
|
9790
|
+
if (fs4.existsSync(metaPath)) {
|
|
9791
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ version: 1, skills: {} }, null, 2), "utf8");
|
|
9792
|
+
}
|
|
9793
|
+
success({ removed: skillDirs.length, skills: skillDirs, target: userDir }, "skills", "purge-user", start);
|
|
9794
|
+
} catch (err) {
|
|
9795
|
+
fail(err, "skills", "purge-user", start);
|
|
9796
|
+
}
|
|
9797
|
+
});
|
|
9475
9798
|
}
|
|
9476
9799
|
function resolvePluginChoices(marketplacePath) {
|
|
9477
9800
|
const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
@@ -9521,7 +9844,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
9521
9844
|
source: "marketplace",
|
|
9522
9845
|
marketplace: meta.marketplace,
|
|
9523
9846
|
plugin: meta.plugin,
|
|
9524
|
-
installedFrom: meta.installedFrom
|
|
9847
|
+
installedFrom: meta.installedFrom,
|
|
9848
|
+
...meta.branch ? { branch: meta.branch } : {}
|
|
9525
9849
|
});
|
|
9526
9850
|
}
|
|
9527
9851
|
return { installed, failed };
|
|
@@ -9783,6 +10107,170 @@ function registerOpenShiftCommands(program2) {
|
|
|
9783
10107
|
});
|
|
9784
10108
|
}
|
|
9785
10109
|
|
|
10110
|
+
// src/services/dynatrace/commands.ts
|
|
10111
|
+
function getHttp3(program2) {
|
|
10112
|
+
const opts = program2.optsWithGlobals();
|
|
10113
|
+
return createHttpClient(
|
|
10114
|
+
loadConfig({ configPath: opts.config }),
|
|
10115
|
+
Boolean(opts.dryRun)
|
|
10116
|
+
);
|
|
10117
|
+
}
|
|
10118
|
+
async function allPages(fetchPage) {
|
|
10119
|
+
const items = [];
|
|
10120
|
+
let nextPageKey;
|
|
10121
|
+
do {
|
|
10122
|
+
const page = await fetchPage(nextPageKey);
|
|
10123
|
+
items.push(...page.items);
|
|
10124
|
+
nextPageKey = page.nextPageKey;
|
|
10125
|
+
} while (nextPageKey);
|
|
10126
|
+
return { items, totalCount: items.length };
|
|
10127
|
+
}
|
|
10128
|
+
async function listEntities(http, entitySelector, opts) {
|
|
10129
|
+
return allPages(async (nextPageKey) => {
|
|
10130
|
+
const page = await http.dynatrace("/api/v2/entities", {
|
|
10131
|
+
params: nextPageKey ? { nextPageKey } : {
|
|
10132
|
+
entitySelector,
|
|
10133
|
+
from: opts.from,
|
|
10134
|
+
to: opts.to,
|
|
10135
|
+
fields: opts.fields,
|
|
10136
|
+
pageSize: 500
|
|
10137
|
+
}
|
|
10138
|
+
});
|
|
10139
|
+
return {
|
|
10140
|
+
items: page.entities,
|
|
10141
|
+
nextPageKey: page.nextPageKey,
|
|
10142
|
+
totalCount: page.totalCount
|
|
10143
|
+
};
|
|
10144
|
+
});
|
|
10145
|
+
}
|
|
10146
|
+
async function runTraceQuery(http, traceId) {
|
|
10147
|
+
if (!/^[0-9a-f]{16,32}$/i.test(traceId)) {
|
|
10148
|
+
throw new PncliError("Trace ID must be a 16- or 32-character hexadecimal value");
|
|
10149
|
+
}
|
|
10150
|
+
const deadline = Date.now() + 6e4;
|
|
10151
|
+
let response = await http.dynatracePlatform(
|
|
10152
|
+
"/platform/storage/query/v1/query:execute",
|
|
10153
|
+
{
|
|
10154
|
+
method: "POST",
|
|
10155
|
+
body: {
|
|
10156
|
+
query: `fetch spans | filter trace.id == "${traceId.toLowerCase()}" | sort start_time asc`,
|
|
10157
|
+
requestTimeoutMilliseconds: 5e3
|
|
10158
|
+
},
|
|
10159
|
+
timeoutMs: 1e4
|
|
10160
|
+
}
|
|
10161
|
+
);
|
|
10162
|
+
while (response.state === "RUNNING" || response.state === "NOT_STARTED") {
|
|
10163
|
+
if (!response.requestToken) throw new PncliError("Dynatrace did not return a query request token");
|
|
10164
|
+
const remainingMs = deadline - Date.now();
|
|
10165
|
+
if (remainingMs <= 0) throw new PncliError("Dynatrace trace query did not finish within 60 seconds");
|
|
10166
|
+
const pollStarted = Date.now();
|
|
10167
|
+
response = await http.dynatracePlatform(
|
|
10168
|
+
"/platform/storage/query/v1/query:poll",
|
|
10169
|
+
{
|
|
10170
|
+
params: {
|
|
10171
|
+
"request-token": response.requestToken,
|
|
10172
|
+
"request-timeout": Math.min(5e3, remainingMs)
|
|
10173
|
+
},
|
|
10174
|
+
timeoutMs: 1e4
|
|
10175
|
+
}
|
|
10176
|
+
);
|
|
10177
|
+
if (response.state === "RUNNING" || response.state === "NOT_STARTED") {
|
|
10178
|
+
const delayMs = Math.min(1e3 - (Date.now() - pollStarted), deadline - Date.now());
|
|
10179
|
+
if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
10180
|
+
}
|
|
10181
|
+
}
|
|
10182
|
+
if (response.state !== "SUCCEEDED") {
|
|
10183
|
+
throw new PncliError(response.error?.message ?? `Dynatrace trace query ${response.state.toLowerCase()}`);
|
|
10184
|
+
}
|
|
10185
|
+
return response.result;
|
|
10186
|
+
}
|
|
10187
|
+
function registerDynatraceCommands(program2) {
|
|
10188
|
+
const dynatrace = program2.command("dynatrace").description("Dynatrace observability operations");
|
|
10189
|
+
const entities = dynatrace.command("entities").description("Monitored entity operations");
|
|
10190
|
+
entities.command("list").description("List monitored entities using an entity selector").requiredOption("--selector <selector>", 'Dynatrace entity selector, e.g. type("SERVICE")').option("--from <time>", "Start time (ISO 8601 or relative, e.g. now-2h)").option("--to <time>", "End time (ISO 8601 or relative)").option("--fields <fields>", "Additional entity fields").action(async (opts) => {
|
|
10191
|
+
const start = Date.now();
|
|
10192
|
+
try {
|
|
10193
|
+
const data = await listEntities(getHttp3(program2), opts.selector, opts);
|
|
10194
|
+
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
10195
|
+
} catch (err) {
|
|
10196
|
+
fail(err, "dynatrace", "entities list", start);
|
|
10197
|
+
}
|
|
10198
|
+
});
|
|
10199
|
+
entities.command("get").description("Get a monitored entity by ID").requiredOption("--id <id>", "Entity ID, e.g. SERVICE-1234567890ABCDEF").option("--from <time>", "Start time").option("--to <time>", "End time").option("--fields <fields>", "Additional entity fields").action(async (opts) => {
|
|
10200
|
+
const start = Date.now();
|
|
10201
|
+
try {
|
|
10202
|
+
const data = await getHttp3(program2).dynatrace(
|
|
10203
|
+
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
10204
|
+
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
10205
|
+
);
|
|
10206
|
+
success(data, "dynatrace", "entities get", start);
|
|
10207
|
+
} catch (err) {
|
|
10208
|
+
fail(err, "dynatrace", "entities get", start);
|
|
10209
|
+
}
|
|
10210
|
+
});
|
|
10211
|
+
dynatrace.command("services").description("List monitored services").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
10212
|
+
const start = Date.now();
|
|
10213
|
+
try {
|
|
10214
|
+
const data = await listEntities(getHttp3(program2), 'type("SERVICE")', opts);
|
|
10215
|
+
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
10216
|
+
} catch (err) {
|
|
10217
|
+
fail(err, "dynatrace", "services", start);
|
|
10218
|
+
}
|
|
10219
|
+
});
|
|
10220
|
+
dynatrace.command("workloads").description("List monitored Kubernetes workloads").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
10221
|
+
const start = Date.now();
|
|
10222
|
+
try {
|
|
10223
|
+
const selector = 'type("CLOUD_APPLICATION")';
|
|
10224
|
+
const data = await listEntities(getHttp3(program2), selector, opts);
|
|
10225
|
+
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
10226
|
+
} catch (err) {
|
|
10227
|
+
fail(err, "dynatrace", "workloads", start);
|
|
10228
|
+
}
|
|
10229
|
+
});
|
|
10230
|
+
const problems = dynatrace.command("problems").description("Problem operations");
|
|
10231
|
+
problems.command("list").description("List problems").option("--from <time>", "Start time (default: now-2h)").option("--to <time>", "End time (default: now)").option("--problem-selector <selector>", "Dynatrace problem selector").option("--entity-selector <selector>", "Affected entity selector").action(async (opts) => {
|
|
10232
|
+
const start = Date.now();
|
|
10233
|
+
try {
|
|
10234
|
+
const data = await allPages(async (nextPageKey) => {
|
|
10235
|
+
const page = await getHttp3(program2).dynatrace("/api/v2/problems", {
|
|
10236
|
+
params: nextPageKey ? { nextPageKey } : {
|
|
10237
|
+
from: opts.from ?? "now-2h",
|
|
10238
|
+
to: opts.to ?? "now",
|
|
10239
|
+
problemSelector: opts.problemSelector,
|
|
10240
|
+
entitySelector: opts.entitySelector,
|
|
10241
|
+
pageSize: 500
|
|
10242
|
+
}
|
|
10243
|
+
});
|
|
10244
|
+
return { items: page.problems, nextPageKey: page.nextPageKey, totalCount: page.totalCount };
|
|
10245
|
+
});
|
|
10246
|
+
success({ totalCount: data.totalCount, problems: data.items }, "dynatrace", "problems list", start);
|
|
10247
|
+
} catch (err) {
|
|
10248
|
+
fail(err, "dynatrace", "problems list", start);
|
|
10249
|
+
}
|
|
10250
|
+
});
|
|
10251
|
+
problems.command("get").description("Get a problem by ID").requiredOption("--id <id>", "Problem ID").option("--fields <fields>", "Additional fields").action(async (opts) => {
|
|
10252
|
+
const start = Date.now();
|
|
10253
|
+
try {
|
|
10254
|
+
const data = await getHttp3(program2).dynatrace(
|
|
10255
|
+
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
10256
|
+
{ params: { fields: opts.fields } }
|
|
10257
|
+
);
|
|
10258
|
+
success(data, "dynatrace", "problems get", start);
|
|
10259
|
+
} catch (err) {
|
|
10260
|
+
fail(err, "dynatrace", "problems get", start);
|
|
10261
|
+
}
|
|
10262
|
+
});
|
|
10263
|
+
dynatrace.command("trace").description("Get distributed-trace spans from Grail").requiredOption("--id <trace-id>", "16- or 32-character hexadecimal trace ID").action(async (opts) => {
|
|
10264
|
+
const start = Date.now();
|
|
10265
|
+
try {
|
|
10266
|
+
const result = await runTraceQuery(getHttp3(program2), opts.id);
|
|
10267
|
+
success(result, "dynatrace", "trace", start);
|
|
10268
|
+
} catch (err) {
|
|
10269
|
+
fail(err, "dynatrace", "trace", start);
|
|
10270
|
+
}
|
|
10271
|
+
});
|
|
10272
|
+
}
|
|
10273
|
+
|
|
9786
10274
|
// src/cli.ts
|
|
9787
10275
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
9788
10276
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -9826,6 +10314,7 @@ registerSonatypeIqCommands(program);
|
|
|
9826
10314
|
registerSkillsCommands(program);
|
|
9827
10315
|
registerJwtCommands(program);
|
|
9828
10316
|
registerOpenShiftCommands(program);
|
|
10317
|
+
registerDynatraceCommands(program);
|
|
9829
10318
|
program.addHelpText("after", `
|
|
9830
10319
|
Services:
|
|
9831
10320
|
git Local git operations (status, diff, log, branch)
|
|
@@ -9840,11 +10329,12 @@ Services:
|
|
|
9840
10329
|
jenkins Jenkins Data Center (jobs, builds, logs)
|
|
9841
10330
|
artifactory Artifactory (repos, artifact search, build info, properties)
|
|
9842
10331
|
udeploy IBM UrbanCode Deploy (component versions, deployment processes)
|
|
9843
|
-
checkmarx Checkmarx
|
|
10332
|
+
checkmarx Checkmarx One (projects, scans, scan statistics)
|
|
9844
10333
|
servicenow ServiceNow (change requests)
|
|
9845
10334
|
contrast Contrast IAST (applications, vulnerability findings)
|
|
9846
10335
|
sonatypeiq Sonatype IQ Server (applications, organizations, policies)
|
|
9847
10336
|
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
10337
|
+
dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
|
|
9848
10338
|
config Manage pncli configuration
|
|
9849
10339
|
skills Download and manage Claude Code skills
|
|
9850
10340
|
jwt JWT token utilities (decode header and payload)
|