@kolatts/pncli 1.18.0 → 1.19.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/README.md +5 -3
- package/dist/{chunk-IVUDXPDP.js → chunk-FXY262BR.js} +26 -6
- package/dist/chunk-FXY262BR.js.map +1 -0
- package/dist/{chunk-WX4ZRHIC.js → chunk-WM5NYVEO.js} +80 -18
- package/dist/chunk-WM5NYVEO.js.map +1 -0
- package/dist/cli.js +523 -58
- package/dist/cli.js.map +1 -1
- package/dist/{config-QN7KDHE4.js → config-CPRXY5SN.js} +2 -2
- package/dist/{http-DDY5DLF6.js → http-FNRE7NQU.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +2 -1
- package/skills/pncli/checkmarx.md +20 -13
- package/skills/pncli/dynatrace.md +46 -0
- 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-FNRE7NQU.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-WM5NYVEO.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);
|
|
@@ -5639,9 +5639,9 @@ function registerConfigCommands(program2) {
|
|
|
5639
5639
|
} else {
|
|
5640
5640
|
results.udeploy = { ok: null, message: "not configured" };
|
|
5641
5641
|
}
|
|
5642
|
-
if (cfg.checkmarx.baseUrl && cfg.checkmarx.
|
|
5642
|
+
if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
|
|
5643
5643
|
try {
|
|
5644
|
-
await http.checkmarx("/
|
|
5644
|
+
await http.checkmarx("/api/projects", { params: { limit: 1 } });
|
|
5645
5645
|
results.checkmarx = { ok: true, message: "connected" };
|
|
5646
5646
|
} catch (err) {
|
|
5647
5647
|
results.checkmarx = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -5690,6 +5690,37 @@ function registerConfigCommands(program2) {
|
|
|
5690
5690
|
} else {
|
|
5691
5691
|
results.openshift = { ok: null, message: "not configured" };
|
|
5692
5692
|
}
|
|
5693
|
+
if (cfg.dynatrace.baseUrl && cfg.dynatrace.apiToken) {
|
|
5694
|
+
try {
|
|
5695
|
+
await http.dynatrace("/api/v2/entities", {
|
|
5696
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 }
|
|
5697
|
+
});
|
|
5698
|
+
results.dynatrace = { ok: true, message: "connected" };
|
|
5699
|
+
} catch (err) {
|
|
5700
|
+
results.dynatrace = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5701
|
+
}
|
|
5702
|
+
} else {
|
|
5703
|
+
results.dynatrace = { ok: null, message: "not configured" };
|
|
5704
|
+
}
|
|
5705
|
+
if (cfg.dynatrace.platformUrl && cfg.dynatrace.platformToken) {
|
|
5706
|
+
try {
|
|
5707
|
+
await http.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
5708
|
+
method: "POST",
|
|
5709
|
+
body: {
|
|
5710
|
+
query: "fetch spans | limit 1",
|
|
5711
|
+
requestTimeoutMilliseconds: 5e3
|
|
5712
|
+
},
|
|
5713
|
+
timeoutMs: 1e4
|
|
5714
|
+
});
|
|
5715
|
+
results.dynatrace_platform = { ok: true, message: "connected" };
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
results.dynatrace_platform = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5718
|
+
}
|
|
5719
|
+
} else if (cfg.dynatrace.platformUrl || cfg.dynatrace.platformToken) {
|
|
5720
|
+
results.dynatrace_platform = { ok: false, message: "platformUrl and platformToken must both be configured" };
|
|
5721
|
+
} else {
|
|
5722
|
+
results.dynatrace_platform = { ok: null, message: "not configured" };
|
|
5723
|
+
}
|
|
5693
5724
|
success(results, "config", "test", start);
|
|
5694
5725
|
} catch (err) {
|
|
5695
5726
|
fail(err, "config", "test", start);
|
|
@@ -5836,13 +5867,13 @@ function registerConfigCommands(program2) {
|
|
|
5836
5867
|
} else {
|
|
5837
5868
|
results.artifactory = { status: "valid", message: "ok" };
|
|
5838
5869
|
}
|
|
5839
|
-
if (!cfg.checkmarx.
|
|
5870
|
+
if (!cfg.checkmarx.tenantName || !cfg.checkmarx.apiKey && (!cfg.checkmarx.clientId || !cfg.checkmarx.clientSecret)) {
|
|
5840
5871
|
results.checkmarx = { status: "blank", message: "not configured" };
|
|
5841
5872
|
} else if (!cfg.checkmarx.baseUrl) {
|
|
5842
5873
|
results.checkmarx = { status: "error", message: "baseUrl not configured" };
|
|
5843
5874
|
} else {
|
|
5844
5875
|
try {
|
|
5845
|
-
await http.checkmarx("/
|
|
5876
|
+
await http.checkmarx("/api/projects", { params: { limit: 1 }, timeoutMs: 1e4 });
|
|
5846
5877
|
results.checkmarx = { status: "valid", message: "ok" };
|
|
5847
5878
|
} catch (err) {
|
|
5848
5879
|
results.checkmarx = categorize2(err);
|
|
@@ -5897,11 +5928,48 @@ function registerConfigCommands(program2) {
|
|
|
5897
5928
|
results.openshift = categorize2(err);
|
|
5898
5929
|
}
|
|
5899
5930
|
}
|
|
5931
|
+
if (!cfg.dynatrace.apiToken) {
|
|
5932
|
+
results.dynatrace = { status: "blank", message: "not configured" };
|
|
5933
|
+
} else if (!cfg.dynatrace.baseUrl) {
|
|
5934
|
+
results.dynatrace = { status: "error", message: "baseUrl not configured" };
|
|
5935
|
+
} else {
|
|
5936
|
+
try {
|
|
5937
|
+
await http.dynatrace("/api/v2/entities", {
|
|
5938
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 },
|
|
5939
|
+
timeoutMs: 1e4
|
|
5940
|
+
});
|
|
5941
|
+
results.dynatrace = { status: "valid", message: "ok" };
|
|
5942
|
+
} catch (err) {
|
|
5943
|
+
results.dynatrace = categorize2(err);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
if (!cfg.dynatrace.platformUrl && !cfg.dynatrace.platformToken) {
|
|
5947
|
+
results.dynatrace_platform = { status: "blank", message: "not configured" };
|
|
5948
|
+
} else if (!cfg.dynatrace.platformUrl || !cfg.dynatrace.platformToken) {
|
|
5949
|
+
results.dynatrace_platform = {
|
|
5950
|
+
status: "error",
|
|
5951
|
+
message: "platformUrl and platformToken must both be configured"
|
|
5952
|
+
};
|
|
5953
|
+
} else {
|
|
5954
|
+
try {
|
|
5955
|
+
await http.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
5956
|
+
method: "POST",
|
|
5957
|
+
body: {
|
|
5958
|
+
query: "fetch spans | limit 1",
|
|
5959
|
+
requestTimeoutMilliseconds: 5e3
|
|
5960
|
+
},
|
|
5961
|
+
timeoutMs: 1e4
|
|
5962
|
+
});
|
|
5963
|
+
results.dynatrace_platform = { status: "valid", message: "ok" };
|
|
5964
|
+
} catch (err) {
|
|
5965
|
+
results.dynatrace_platform = categorize2(err);
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5900
5968
|
const statuses = Object.values(results).map((r) => r.status);
|
|
5901
5969
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
5902
5970
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
5903
5971
|
if (cmdOpts.output === "table") {
|
|
5904
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
5972
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
|
|
5905
5973
|
const labelWidth = 14;
|
|
5906
5974
|
const statusWidth = 9;
|
|
5907
5975
|
for (const svc of services) {
|
|
@@ -5915,7 +5983,7 @@ function registerConfigCommands(program2) {
|
|
|
5915
5983
|
process.stdout.write("\n");
|
|
5916
5984
|
} else {
|
|
5917
5985
|
if (opts.pretty) {
|
|
5918
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
5986
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
|
|
5919
5987
|
const labelWidth = 14;
|
|
5920
5988
|
const statusWidth = 9;
|
|
5921
5989
|
for (const svc of services) {
|
|
@@ -6248,38 +6316,51 @@ async function initGlobalConfig(start) {
|
|
|
6248
6316
|
}
|
|
6249
6317
|
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
6318
|
const useCheckmarx = await confirm({
|
|
6251
|
-
message: "Configure Checkmarx
|
|
6319
|
+
message: "Configure Checkmarx One for vulnerability scanning?",
|
|
6252
6320
|
default: false
|
|
6253
6321
|
});
|
|
6254
6322
|
let checkmarxBaseUrl = "";
|
|
6255
|
-
let
|
|
6256
|
-
let
|
|
6323
|
+
let checkmarxTenantName = "";
|
|
6324
|
+
let checkmarxApiKey = "";
|
|
6325
|
+
let checkmarxClientId = "";
|
|
6326
|
+
let checkmarxClientSecret = "";
|
|
6257
6327
|
if (useCheckmarx) {
|
|
6258
6328
|
checkmarxBaseUrl = await input({
|
|
6259
|
-
message: "Checkmarx base URL (e.g.
|
|
6329
|
+
message: "Checkmarx One API base URL (e.g. https://ast.checkmarx.net):",
|
|
6260
6330
|
default: ""
|
|
6261
6331
|
});
|
|
6262
|
-
|
|
6263
|
-
message: "Checkmarx
|
|
6332
|
+
checkmarxTenantName = await input({
|
|
6333
|
+
message: "Checkmarx One tenant name (IAM realm, e.g. mycompany):",
|
|
6264
6334
|
default: ""
|
|
6265
6335
|
});
|
|
6266
|
-
|
|
6267
|
-
message: "Checkmarx
|
|
6268
|
-
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
6336
|
+
checkmarxApiKey = await password({
|
|
6337
|
+
message: "Checkmarx One API key (leave blank to use an OAuth client):"
|
|
6269
6338
|
});
|
|
6270
|
-
if (
|
|
6339
|
+
if (!checkmarxApiKey) {
|
|
6340
|
+
checkmarxClientId = await input({
|
|
6341
|
+
message: "Checkmarx One OAuth client ID:",
|
|
6342
|
+
default: ""
|
|
6343
|
+
});
|
|
6344
|
+
checkmarxClientSecret = await password({
|
|
6345
|
+
message: "Checkmarx One OAuth client secret:",
|
|
6346
|
+
validate: (v) => v.length > 0 || "Client secret cannot be blank"
|
|
6347
|
+
});
|
|
6348
|
+
}
|
|
6349
|
+
if (checkmarxBaseUrl && checkmarxTenantName && (checkmarxApiKey || checkmarxClientId && checkmarxClientSecret)) {
|
|
6271
6350
|
process.stderr.write("\n Verifying connection...\n");
|
|
6272
6351
|
try {
|
|
6273
6352
|
const tempConfig = {
|
|
6274
6353
|
...loadConfig(),
|
|
6275
6354
|
checkmarx: {
|
|
6276
6355
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
6277
|
-
|
|
6278
|
-
|
|
6356
|
+
tenantName: checkmarxTenantName,
|
|
6357
|
+
apiKey: checkmarxApiKey || void 0,
|
|
6358
|
+
clientId: checkmarxClientId,
|
|
6359
|
+
clientSecret: checkmarxClientSecret
|
|
6279
6360
|
}
|
|
6280
6361
|
};
|
|
6281
6362
|
const tempHttp = createHttpClient(tempConfig);
|
|
6282
|
-
await tempHttp.checkmarx("/
|
|
6363
|
+
await tempHttp.checkmarx("/api/projects", { params: { limit: 1 } });
|
|
6283
6364
|
process.stderr.write(" Connected.\n");
|
|
6284
6365
|
} catch (err) {
|
|
6285
6366
|
warn(`Could not connect to Checkmarx: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6472,6 +6553,59 @@ async function initGlobalConfig(start) {
|
|
|
6472
6553
|
}
|
|
6473
6554
|
}
|
|
6474
6555
|
}
|
|
6556
|
+
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");
|
|
6557
|
+
const useDynatrace = await confirm({
|
|
6558
|
+
message: "Configure Dynatrace for services, problems, workloads, and traces?",
|
|
6559
|
+
default: false
|
|
6560
|
+
});
|
|
6561
|
+
let dynatraceBaseUrl = "";
|
|
6562
|
+
let dynatraceApiToken = "";
|
|
6563
|
+
let dynatracePlatformUrl = "";
|
|
6564
|
+
let dynatracePlatformToken = "";
|
|
6565
|
+
if (useDynatrace) {
|
|
6566
|
+
dynatraceBaseUrl = await input({
|
|
6567
|
+
message: "Dynatrace environment URL (e.g. https://abc12345.live.dynatrace.com):",
|
|
6568
|
+
default: ""
|
|
6569
|
+
});
|
|
6570
|
+
dynatraceApiToken = await password({
|
|
6571
|
+
message: "Environment API token (entities.read and problems.read):"
|
|
6572
|
+
});
|
|
6573
|
+
const configureTraces = await confirm({
|
|
6574
|
+
message: "Configure the latest Dynatrace platform for Grail trace queries?",
|
|
6575
|
+
default: false
|
|
6576
|
+
});
|
|
6577
|
+
if (configureTraces) {
|
|
6578
|
+
dynatracePlatformUrl = await input({
|
|
6579
|
+
message: "Dynatrace platform URL (e.g. https://abc12345.apps.dynatrace.com):",
|
|
6580
|
+
default: ""
|
|
6581
|
+
});
|
|
6582
|
+
dynatracePlatformToken = await password({
|
|
6583
|
+
message: "Platform token with Grail span read permissions:"
|
|
6584
|
+
});
|
|
6585
|
+
}
|
|
6586
|
+
if (dynatraceBaseUrl && dynatraceApiToken) {
|
|
6587
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
6588
|
+
try {
|
|
6589
|
+
const tempConfig = {
|
|
6590
|
+
...loadConfig(),
|
|
6591
|
+
dynatrace: {
|
|
6592
|
+
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
6593
|
+
apiToken: dynatraceApiToken,
|
|
6594
|
+
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
6595
|
+
platformToken: dynatracePlatformToken || void 0
|
|
6596
|
+
}
|
|
6597
|
+
};
|
|
6598
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
6599
|
+
await tempHttp.dynatrace("/api/v2/entities", {
|
|
6600
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 }
|
|
6601
|
+
});
|
|
6602
|
+
process.stderr.write(" Connected.\n");
|
|
6603
|
+
} catch (err) {
|
|
6604
|
+
warn(`Could not connect to Dynatrace: ${err instanceof Error ? err.message : String(err)}`);
|
|
6605
|
+
warn("Config will be saved anyway. Check your URLs and tokens and re-run pncli config test.");
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6475
6609
|
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
6610
|
const jiraProject = await input({
|
|
6477
6611
|
message: "Default Jira project key (optional):",
|
|
@@ -6567,8 +6701,10 @@ async function initGlobalConfig(start) {
|
|
|
6567
6701
|
...useCheckmarx && checkmarxBaseUrl ? {
|
|
6568
6702
|
checkmarx: {
|
|
6569
6703
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
6570
|
-
|
|
6571
|
-
|
|
6704
|
+
tenantName: checkmarxTenantName || void 0,
|
|
6705
|
+
apiKey: checkmarxApiKey || void 0,
|
|
6706
|
+
clientId: checkmarxClientId || void 0,
|
|
6707
|
+
clientSecret: checkmarxClientSecret || void 0
|
|
6572
6708
|
}
|
|
6573
6709
|
} : {},
|
|
6574
6710
|
...useServiceNow && servicenowBaseUrl ? {
|
|
@@ -6601,6 +6737,14 @@ async function initGlobalConfig(start) {
|
|
|
6601
6737
|
token: openShiftToken || void 0
|
|
6602
6738
|
}
|
|
6603
6739
|
} : {},
|
|
6740
|
+
...useDynatrace && dynatraceBaseUrl ? {
|
|
6741
|
+
dynatrace: {
|
|
6742
|
+
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
6743
|
+
apiToken: dynatraceApiToken || void 0,
|
|
6744
|
+
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
6745
|
+
platformToken: dynatracePlatformToken || void 0
|
|
6746
|
+
}
|
|
6747
|
+
} : {},
|
|
6604
6748
|
defaults: {
|
|
6605
6749
|
jira: {
|
|
6606
6750
|
project: jiraProject || void 0
|
|
@@ -7688,11 +7832,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
7688
7832
|
const start = Date.now();
|
|
7689
7833
|
try {
|
|
7690
7834
|
const globalOpts = ado.optsWithGlobals();
|
|
7691
|
-
const { loadConfig: lc } = await import("./config-
|
|
7835
|
+
const { loadConfig: lc } = await import("./config-CPRXY5SN.js");
|
|
7692
7836
|
const config = lc({ configPath: globalOpts.config });
|
|
7693
7837
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7694
7838
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
7695
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
7839
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-FNRE7NQU.js");
|
|
7696
7840
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
7697
7841
|
const coreClient = new AdoCoreClient2(http);
|
|
7698
7842
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8506,24 +8650,35 @@ var CheckmarxClient = class {
|
|
|
8506
8650
|
}
|
|
8507
8651
|
http;
|
|
8508
8652
|
async listProjects() {
|
|
8509
|
-
|
|
8653
|
+
const res = await this.http.checkmarx("projects", { params: { limit: 100 } });
|
|
8654
|
+
const projects = res.projects ?? [];
|
|
8655
|
+
if (res.filteredTotalCount > projects.length) {
|
|
8656
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} projects found; only showing first ${projects.length}
|
|
8657
|
+
`);
|
|
8658
|
+
}
|
|
8659
|
+
return projects;
|
|
8510
8660
|
}
|
|
8511
8661
|
async getProject(id) {
|
|
8512
|
-
return this.http.checkmarx(
|
|
8662
|
+
return this.http.checkmarx(`projects/${id}`);
|
|
8513
8663
|
}
|
|
8514
8664
|
async listScans(opts = {}) {
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8665
|
+
const params = { limit: opts.last ?? 100 };
|
|
8666
|
+
if (opts.projectId) params["project-id"] = opts.projectId;
|
|
8667
|
+
const res = await this.http.checkmarx("scans", { params });
|
|
8668
|
+
const scans = res.scans ?? [];
|
|
8669
|
+
if (res.filteredTotalCount > scans.length) {
|
|
8670
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} scans found; only showing first ${scans.length}
|
|
8671
|
+
`);
|
|
8672
|
+
}
|
|
8673
|
+
return scans;
|
|
8521
8674
|
}
|
|
8522
8675
|
async getScan(id) {
|
|
8523
|
-
return this.http.checkmarx(
|
|
8676
|
+
return this.http.checkmarx(`scans/${id}`);
|
|
8524
8677
|
}
|
|
8525
8678
|
async getScanResultsStatistics(scanId) {
|
|
8526
|
-
return this.http.checkmarx(
|
|
8679
|
+
return this.http.checkmarx("results/summary", {
|
|
8680
|
+
params: { "scan-id": scanId }
|
|
8681
|
+
});
|
|
8527
8682
|
}
|
|
8528
8683
|
};
|
|
8529
8684
|
|
|
@@ -8536,7 +8691,7 @@ function getClient10(program2) {
|
|
|
8536
8691
|
return new CheckmarxClient(http);
|
|
8537
8692
|
}
|
|
8538
8693
|
function registerCheckmarxCommands(program2) {
|
|
8539
|
-
const cx = program2.command("checkmarx").description("Checkmarx
|
|
8694
|
+
const cx = program2.command("checkmarx").description("Checkmarx One operations");
|
|
8540
8695
|
const project = cx.command("project").description("Project operations");
|
|
8541
8696
|
const scan = cx.command("scan").description("Scan operations");
|
|
8542
8697
|
project.command("list").description("List all projects").action(async () => {
|
|
@@ -8548,47 +8703,39 @@ function registerCheckmarxCommands(program2) {
|
|
|
8548
8703
|
fail(err, "checkmarx", "project-list", start);
|
|
8549
8704
|
}
|
|
8550
8705
|
});
|
|
8551
|
-
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project
|
|
8706
|
+
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
|
|
8552
8707
|
const start = Date.now();
|
|
8553
8708
|
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);
|
|
8709
|
+
const data = await getClient10(program2).getProject(opts.id);
|
|
8557
8710
|
success(data, "checkmarx", "project-get", start);
|
|
8558
8711
|
} catch (err) {
|
|
8559
8712
|
fail(err, "checkmarx", "project-get", start);
|
|
8560
8713
|
}
|
|
8561
8714
|
});
|
|
8562
|
-
scan.command("list").description("List scans").option("--project <id>", "Filter by project
|
|
8715
|
+
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
8716
|
const start = Date.now();
|
|
8564
8717
|
try {
|
|
8565
|
-
const projectId = opts.project ? parseInt(opts.project, 10) : void 0;
|
|
8566
8718
|
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
8719
|
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 });
|
|
8720
|
+
const data = await getClient10(program2).listScans({ projectId: opts.project, last });
|
|
8570
8721
|
success(data, "checkmarx", "scan-list", start);
|
|
8571
8722
|
} catch (err) {
|
|
8572
8723
|
fail(err, "checkmarx", "scan-list", start);
|
|
8573
8724
|
}
|
|
8574
8725
|
});
|
|
8575
|
-
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan
|
|
8726
|
+
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
8576
8727
|
const start = Date.now();
|
|
8577
8728
|
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);
|
|
8729
|
+
const data = await getClient10(program2).getScan(opts.id);
|
|
8581
8730
|
success(data, "checkmarx", "scan-get", start);
|
|
8582
8731
|
} catch (err) {
|
|
8583
8732
|
fail(err, "checkmarx", "scan-get", start);
|
|
8584
8733
|
}
|
|
8585
8734
|
});
|
|
8586
|
-
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan
|
|
8735
|
+
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
8587
8736
|
const start = Date.now();
|
|
8588
8737
|
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);
|
|
8738
|
+
const data = await getClient10(program2).getScanResultsStatistics(opts.id);
|
|
8592
8739
|
success(data, "checkmarx", "scan-stats", start);
|
|
8593
8740
|
} catch (err) {
|
|
8594
8741
|
fail(err, "checkmarx", "scan-stats", start);
|
|
@@ -8899,18 +9046,52 @@ function getBundledSkillsDir() {
|
|
|
8899
9046
|
function getInstalledMetaPath(targetDir) {
|
|
8900
9047
|
return path7.join(targetDir, ".pncli-installed.json");
|
|
8901
9048
|
}
|
|
9049
|
+
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
8902
9050
|
function readInstalledMeta(targetDir) {
|
|
8903
9051
|
const metaPath = getInstalledMetaPath(targetDir);
|
|
8904
9052
|
const raw = loadJsonFile(metaPath);
|
|
8905
9053
|
if (raw && raw.version === 1 && raw.skills) return raw;
|
|
8906
9054
|
return { version: 1, skills: {} };
|
|
8907
9055
|
}
|
|
9056
|
+
function getSkillOriginPath(skillDir) {
|
|
9057
|
+
return path7.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
9058
|
+
}
|
|
9059
|
+
function readSkillOrigin(skillDir) {
|
|
9060
|
+
const originPath = getSkillOriginPath(skillDir);
|
|
9061
|
+
if (!fs4.existsSync(originPath)) return null;
|
|
9062
|
+
try {
|
|
9063
|
+
const raw = JSON.parse(fs4.readFileSync(originPath, "utf8"));
|
|
9064
|
+
if (raw && raw.version === 1 && raw.source) return raw;
|
|
9065
|
+
} catch {
|
|
9066
|
+
}
|
|
9067
|
+
return null;
|
|
9068
|
+
}
|
|
9069
|
+
function detectRepoBranch(repoPath) {
|
|
9070
|
+
try {
|
|
9071
|
+
const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
9072
|
+
encoding: "utf8",
|
|
9073
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9074
|
+
}).trim();
|
|
9075
|
+
return result && result !== "HEAD" ? result : void 0;
|
|
9076
|
+
} catch {
|
|
9077
|
+
return void 0;
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
8908
9080
|
function recordInstalledSkills(targetDir, skillNames, record) {
|
|
8909
9081
|
if (skillNames.length === 0) return;
|
|
8910
9082
|
const meta = readInstalledMeta(targetDir);
|
|
8911
9083
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8912
9084
|
for (const skillName of skillNames) {
|
|
8913
|
-
|
|
9085
|
+
const fullRecord = { ...record, installedAt: now };
|
|
9086
|
+
meta.skills[skillName] = fullRecord;
|
|
9087
|
+
const skillDir = path7.join(targetDir, skillName);
|
|
9088
|
+
if (fs4.existsSync(skillDir)) {
|
|
9089
|
+
const origin = { version: 1, ...fullRecord };
|
|
9090
|
+
try {
|
|
9091
|
+
fs4.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
|
|
9092
|
+
} catch {
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
8914
9095
|
}
|
|
8915
9096
|
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
8916
9097
|
}
|
|
@@ -9024,6 +9205,7 @@ function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
|
|
|
9024
9205
|
function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
|
|
9025
9206
|
const results = {};
|
|
9026
9207
|
let totalInstalled = 0;
|
|
9208
|
+
const branch = detectRepoBranch(marketplacePath);
|
|
9027
9209
|
for (const pluginName of pluginNames) {
|
|
9028
9210
|
const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
|
|
9029
9211
|
if (!fs4.existsSync(skillsSrc)) {
|
|
@@ -9034,7 +9216,8 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
9034
9216
|
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir, {
|
|
9035
9217
|
marketplace: marketplaceName,
|
|
9036
9218
|
plugin: pluginName,
|
|
9037
|
-
installedFrom: repoUrl ?? ""
|
|
9219
|
+
installedFrom: repoUrl ?? "",
|
|
9220
|
+
branch
|
|
9038
9221
|
});
|
|
9039
9222
|
results[pluginName] = { installed, failed };
|
|
9040
9223
|
totalInstalled += installed.length;
|
|
@@ -9472,6 +9655,121 @@ function registerSkillsCommands(program2) {
|
|
|
9472
9655
|
fail(err, "skills", "marketplace-sync", start);
|
|
9473
9656
|
}
|
|
9474
9657
|
});
|
|
9658
|
+
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) => {
|
|
9659
|
+
const start = Date.now();
|
|
9660
|
+
try {
|
|
9661
|
+
let targetDir;
|
|
9662
|
+
if (opts.target) {
|
|
9663
|
+
targetDir = path7.resolve(opts.target);
|
|
9664
|
+
} else {
|
|
9665
|
+
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9666
|
+
const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
|
|
9667
|
+
targetDir = path7.resolve(scopePath);
|
|
9668
|
+
}
|
|
9669
|
+
const resolvedTarget = path7.resolve(targetDir);
|
|
9670
|
+
if (!fs4.existsSync(targetDir)) {
|
|
9671
|
+
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
9672
|
+
return;
|
|
9673
|
+
}
|
|
9674
|
+
const meta = readInstalledMeta(targetDir);
|
|
9675
|
+
const removed = [];
|
|
9676
|
+
const skipped = [];
|
|
9677
|
+
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9678
|
+
if (name.startsWith(".")) return false;
|
|
9679
|
+
const p = path7.join(targetDir, name);
|
|
9680
|
+
try {
|
|
9681
|
+
return fs4.statSync(p).isDirectory();
|
|
9682
|
+
} catch {
|
|
9683
|
+
return false;
|
|
9684
|
+
}
|
|
9685
|
+
});
|
|
9686
|
+
for (const skillName of skillDirs) {
|
|
9687
|
+
const skillDir = path7.resolve(targetDir, skillName);
|
|
9688
|
+
if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
|
|
9689
|
+
let record = meta.skills[skillName] ?? null;
|
|
9690
|
+
if (!record) {
|
|
9691
|
+
const perSkill = readSkillOrigin(skillDir);
|
|
9692
|
+
if (perSkill) {
|
|
9693
|
+
record = {
|
|
9694
|
+
source: perSkill.source,
|
|
9695
|
+
marketplace: perSkill.marketplace,
|
|
9696
|
+
plugin: perSkill.plugin,
|
|
9697
|
+
installedFrom: perSkill.installedFrom,
|
|
9698
|
+
branch: perSkill.branch,
|
|
9699
|
+
installedAt: perSkill.installedAt
|
|
9700
|
+
};
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9703
|
+
if (!record || record.source !== "marketplace") {
|
|
9704
|
+
skipped.push(skillName);
|
|
9705
|
+
continue;
|
|
9706
|
+
}
|
|
9707
|
+
const matchesMarketplace = !opts.marketplace || record.marketplace === opts.marketplace || record.installedFrom === opts.marketplace;
|
|
9708
|
+
const matchesPlugin = plugin === "all" || record.plugin === plugin;
|
|
9709
|
+
if (!matchesMarketplace || !matchesPlugin) {
|
|
9710
|
+
skipped.push(skillName);
|
|
9711
|
+
continue;
|
|
9712
|
+
}
|
|
9713
|
+
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9714
|
+
delete meta.skills[skillName];
|
|
9715
|
+
removed.push(skillName);
|
|
9716
|
+
}
|
|
9717
|
+
if (removed.length > 0) {
|
|
9718
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
9719
|
+
}
|
|
9720
|
+
success({
|
|
9721
|
+
removed,
|
|
9722
|
+
skipped: skipped.length,
|
|
9723
|
+
total: removed.length,
|
|
9724
|
+
target: targetDir,
|
|
9725
|
+
plugin,
|
|
9726
|
+
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
9727
|
+
}, "skills", "marketplace-purge-plugin", start);
|
|
9728
|
+
} catch (err) {
|
|
9729
|
+
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
9730
|
+
}
|
|
9731
|
+
});
|
|
9732
|
+
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) => {
|
|
9733
|
+
const start = Date.now();
|
|
9734
|
+
try {
|
|
9735
|
+
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
9736
|
+
if (!fs4.existsSync(userDir)) {
|
|
9737
|
+
success({ removed: 0, target: userDir, message: "No user-level skills directory found \u2014 nothing to purge." }, "skills", "purge-user", start);
|
|
9738
|
+
return;
|
|
9739
|
+
}
|
|
9740
|
+
const skillDirs = fs4.readdirSync(userDir).filter((name) => {
|
|
9741
|
+
if (name.startsWith(".")) return false;
|
|
9742
|
+
const p = path7.join(userDir, name);
|
|
9743
|
+
try {
|
|
9744
|
+
return fs4.statSync(p).isDirectory();
|
|
9745
|
+
} catch {
|
|
9746
|
+
return false;
|
|
9747
|
+
}
|
|
9748
|
+
});
|
|
9749
|
+
if (skillDirs.length === 0) {
|
|
9750
|
+
success({ removed: 0, target: userDir, message: "No skills found in user-level skills directory." }, "skills", "purge-user", start);
|
|
9751
|
+
return;
|
|
9752
|
+
}
|
|
9753
|
+
if (!opts.force) {
|
|
9754
|
+
throw new Error(
|
|
9755
|
+
`This will remove ${skillDirs.length} skill(s) from ${userDir}: ${skillDirs.join(", ")}.
|
|
9756
|
+
Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\` to target a specific plugin.`
|
|
9757
|
+
);
|
|
9758
|
+
}
|
|
9759
|
+
for (const name of skillDirs) {
|
|
9760
|
+
const skillDir = path7.resolve(userDir, name);
|
|
9761
|
+
if (!skillDir.startsWith(path7.resolve(userDir) + path7.sep)) continue;
|
|
9762
|
+
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9763
|
+
}
|
|
9764
|
+
const metaPath = getInstalledMetaPath(userDir);
|
|
9765
|
+
if (fs4.existsSync(metaPath)) {
|
|
9766
|
+
fs4.writeFileSync(metaPath, JSON.stringify({ version: 1, skills: {} }, null, 2), "utf8");
|
|
9767
|
+
}
|
|
9768
|
+
success({ removed: skillDirs.length, skills: skillDirs, target: userDir }, "skills", "purge-user", start);
|
|
9769
|
+
} catch (err) {
|
|
9770
|
+
fail(err, "skills", "purge-user", start);
|
|
9771
|
+
}
|
|
9772
|
+
});
|
|
9475
9773
|
}
|
|
9476
9774
|
function resolvePluginChoices(marketplacePath) {
|
|
9477
9775
|
const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
@@ -9521,7 +9819,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
9521
9819
|
source: "marketplace",
|
|
9522
9820
|
marketplace: meta.marketplace,
|
|
9523
9821
|
plugin: meta.plugin,
|
|
9524
|
-
installedFrom: meta.installedFrom
|
|
9822
|
+
installedFrom: meta.installedFrom,
|
|
9823
|
+
...meta.branch ? { branch: meta.branch } : {}
|
|
9525
9824
|
});
|
|
9526
9825
|
}
|
|
9527
9826
|
return { installed, failed };
|
|
@@ -9783,6 +10082,170 @@ function registerOpenShiftCommands(program2) {
|
|
|
9783
10082
|
});
|
|
9784
10083
|
}
|
|
9785
10084
|
|
|
10085
|
+
// src/services/dynatrace/commands.ts
|
|
10086
|
+
function getHttp3(program2) {
|
|
10087
|
+
const opts = program2.optsWithGlobals();
|
|
10088
|
+
return createHttpClient(
|
|
10089
|
+
loadConfig({ configPath: opts.config }),
|
|
10090
|
+
Boolean(opts.dryRun)
|
|
10091
|
+
);
|
|
10092
|
+
}
|
|
10093
|
+
async function allPages(fetchPage) {
|
|
10094
|
+
const items = [];
|
|
10095
|
+
let nextPageKey;
|
|
10096
|
+
do {
|
|
10097
|
+
const page = await fetchPage(nextPageKey);
|
|
10098
|
+
items.push(...page.items);
|
|
10099
|
+
nextPageKey = page.nextPageKey;
|
|
10100
|
+
} while (nextPageKey);
|
|
10101
|
+
return { items, totalCount: items.length };
|
|
10102
|
+
}
|
|
10103
|
+
async function listEntities(http, entitySelector, opts) {
|
|
10104
|
+
return allPages(async (nextPageKey) => {
|
|
10105
|
+
const page = await http.dynatrace("/api/v2/entities", {
|
|
10106
|
+
params: nextPageKey ? { nextPageKey } : {
|
|
10107
|
+
entitySelector,
|
|
10108
|
+
from: opts.from,
|
|
10109
|
+
to: opts.to,
|
|
10110
|
+
fields: opts.fields,
|
|
10111
|
+
pageSize: 500
|
|
10112
|
+
}
|
|
10113
|
+
});
|
|
10114
|
+
return {
|
|
10115
|
+
items: page.entities,
|
|
10116
|
+
nextPageKey: page.nextPageKey,
|
|
10117
|
+
totalCount: page.totalCount
|
|
10118
|
+
};
|
|
10119
|
+
});
|
|
10120
|
+
}
|
|
10121
|
+
async function runTraceQuery(http, traceId) {
|
|
10122
|
+
if (!/^[0-9a-f]{16,32}$/i.test(traceId)) {
|
|
10123
|
+
throw new PncliError("Trace ID must be a 16- or 32-character hexadecimal value");
|
|
10124
|
+
}
|
|
10125
|
+
const deadline = Date.now() + 6e4;
|
|
10126
|
+
let response = await http.dynatracePlatform(
|
|
10127
|
+
"/platform/storage/query/v1/query:execute",
|
|
10128
|
+
{
|
|
10129
|
+
method: "POST",
|
|
10130
|
+
body: {
|
|
10131
|
+
query: `fetch spans | filter trace.id == "${traceId.toLowerCase()}" | sort start_time asc`,
|
|
10132
|
+
requestTimeoutMilliseconds: 5e3
|
|
10133
|
+
},
|
|
10134
|
+
timeoutMs: 1e4
|
|
10135
|
+
}
|
|
10136
|
+
);
|
|
10137
|
+
while (response.state === "RUNNING" || response.state === "NOT_STARTED") {
|
|
10138
|
+
if (!response.requestToken) throw new PncliError("Dynatrace did not return a query request token");
|
|
10139
|
+
const remainingMs = deadline - Date.now();
|
|
10140
|
+
if (remainingMs <= 0) throw new PncliError("Dynatrace trace query did not finish within 60 seconds");
|
|
10141
|
+
const pollStarted = Date.now();
|
|
10142
|
+
response = await http.dynatracePlatform(
|
|
10143
|
+
"/platform/storage/query/v1/query:poll",
|
|
10144
|
+
{
|
|
10145
|
+
params: {
|
|
10146
|
+
"request-token": response.requestToken,
|
|
10147
|
+
"request-timeout": Math.min(5e3, remainingMs)
|
|
10148
|
+
},
|
|
10149
|
+
timeoutMs: 1e4
|
|
10150
|
+
}
|
|
10151
|
+
);
|
|
10152
|
+
if (response.state === "RUNNING" || response.state === "NOT_STARTED") {
|
|
10153
|
+
const delayMs = Math.min(1e3 - (Date.now() - pollStarted), deadline - Date.now());
|
|
10154
|
+
if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
10155
|
+
}
|
|
10156
|
+
}
|
|
10157
|
+
if (response.state !== "SUCCEEDED") {
|
|
10158
|
+
throw new PncliError(response.error?.message ?? `Dynatrace trace query ${response.state.toLowerCase()}`);
|
|
10159
|
+
}
|
|
10160
|
+
return response.result;
|
|
10161
|
+
}
|
|
10162
|
+
function registerDynatraceCommands(program2) {
|
|
10163
|
+
const dynatrace = program2.command("dynatrace").description("Dynatrace observability operations");
|
|
10164
|
+
const entities = dynatrace.command("entities").description("Monitored entity operations");
|
|
10165
|
+
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) => {
|
|
10166
|
+
const start = Date.now();
|
|
10167
|
+
try {
|
|
10168
|
+
const data = await listEntities(getHttp3(program2), opts.selector, opts);
|
|
10169
|
+
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
10170
|
+
} catch (err) {
|
|
10171
|
+
fail(err, "dynatrace", "entities list", start);
|
|
10172
|
+
}
|
|
10173
|
+
});
|
|
10174
|
+
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) => {
|
|
10175
|
+
const start = Date.now();
|
|
10176
|
+
try {
|
|
10177
|
+
const data = await getHttp3(program2).dynatrace(
|
|
10178
|
+
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
10179
|
+
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
10180
|
+
);
|
|
10181
|
+
success(data, "dynatrace", "entities get", start);
|
|
10182
|
+
} catch (err) {
|
|
10183
|
+
fail(err, "dynatrace", "entities get", start);
|
|
10184
|
+
}
|
|
10185
|
+
});
|
|
10186
|
+
dynatrace.command("services").description("List monitored services").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
10187
|
+
const start = Date.now();
|
|
10188
|
+
try {
|
|
10189
|
+
const data = await listEntities(getHttp3(program2), 'type("SERVICE")', opts);
|
|
10190
|
+
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
10191
|
+
} catch (err) {
|
|
10192
|
+
fail(err, "dynatrace", "services", start);
|
|
10193
|
+
}
|
|
10194
|
+
});
|
|
10195
|
+
dynatrace.command("workloads").description("List monitored Kubernetes workloads").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
10196
|
+
const start = Date.now();
|
|
10197
|
+
try {
|
|
10198
|
+
const selector = 'type("CLOUD_APPLICATION")';
|
|
10199
|
+
const data = await listEntities(getHttp3(program2), selector, opts);
|
|
10200
|
+
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
10201
|
+
} catch (err) {
|
|
10202
|
+
fail(err, "dynatrace", "workloads", start);
|
|
10203
|
+
}
|
|
10204
|
+
});
|
|
10205
|
+
const problems = dynatrace.command("problems").description("Problem operations");
|
|
10206
|
+
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) => {
|
|
10207
|
+
const start = Date.now();
|
|
10208
|
+
try {
|
|
10209
|
+
const data = await allPages(async (nextPageKey) => {
|
|
10210
|
+
const page = await getHttp3(program2).dynatrace("/api/v2/problems", {
|
|
10211
|
+
params: nextPageKey ? { nextPageKey } : {
|
|
10212
|
+
from: opts.from ?? "now-2h",
|
|
10213
|
+
to: opts.to ?? "now",
|
|
10214
|
+
problemSelector: opts.problemSelector,
|
|
10215
|
+
entitySelector: opts.entitySelector,
|
|
10216
|
+
pageSize: 500
|
|
10217
|
+
}
|
|
10218
|
+
});
|
|
10219
|
+
return { items: page.problems, nextPageKey: page.nextPageKey, totalCount: page.totalCount };
|
|
10220
|
+
});
|
|
10221
|
+
success({ totalCount: data.totalCount, problems: data.items }, "dynatrace", "problems list", start);
|
|
10222
|
+
} catch (err) {
|
|
10223
|
+
fail(err, "dynatrace", "problems list", start);
|
|
10224
|
+
}
|
|
10225
|
+
});
|
|
10226
|
+
problems.command("get").description("Get a problem by ID").requiredOption("--id <id>", "Problem ID").option("--fields <fields>", "Additional fields").action(async (opts) => {
|
|
10227
|
+
const start = Date.now();
|
|
10228
|
+
try {
|
|
10229
|
+
const data = await getHttp3(program2).dynatrace(
|
|
10230
|
+
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
10231
|
+
{ params: { fields: opts.fields } }
|
|
10232
|
+
);
|
|
10233
|
+
success(data, "dynatrace", "problems get", start);
|
|
10234
|
+
} catch (err) {
|
|
10235
|
+
fail(err, "dynatrace", "problems get", start);
|
|
10236
|
+
}
|
|
10237
|
+
});
|
|
10238
|
+
dynatrace.command("trace").description("Get distributed-trace spans from Grail").requiredOption("--id <trace-id>", "16- or 32-character hexadecimal trace ID").action(async (opts) => {
|
|
10239
|
+
const start = Date.now();
|
|
10240
|
+
try {
|
|
10241
|
+
const result = await runTraceQuery(getHttp3(program2), opts.id);
|
|
10242
|
+
success(result, "dynatrace", "trace", start);
|
|
10243
|
+
} catch (err) {
|
|
10244
|
+
fail(err, "dynatrace", "trace", start);
|
|
10245
|
+
}
|
|
10246
|
+
});
|
|
10247
|
+
}
|
|
10248
|
+
|
|
9786
10249
|
// src/cli.ts
|
|
9787
10250
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
9788
10251
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -9826,6 +10289,7 @@ registerSonatypeIqCommands(program);
|
|
|
9826
10289
|
registerSkillsCommands(program);
|
|
9827
10290
|
registerJwtCommands(program);
|
|
9828
10291
|
registerOpenShiftCommands(program);
|
|
10292
|
+
registerDynatraceCommands(program);
|
|
9829
10293
|
program.addHelpText("after", `
|
|
9830
10294
|
Services:
|
|
9831
10295
|
git Local git operations (status, diff, log, branch)
|
|
@@ -9840,11 +10304,12 @@ Services:
|
|
|
9840
10304
|
jenkins Jenkins Data Center (jobs, builds, logs)
|
|
9841
10305
|
artifactory Artifactory (repos, artifact search, build info, properties)
|
|
9842
10306
|
udeploy IBM UrbanCode Deploy (component versions, deployment processes)
|
|
9843
|
-
checkmarx Checkmarx
|
|
10307
|
+
checkmarx Checkmarx One (projects, scans, scan statistics)
|
|
9844
10308
|
servicenow ServiceNow (change requests)
|
|
9845
10309
|
contrast Contrast IAST (applications, vulnerability findings)
|
|
9846
10310
|
sonatypeiq Sonatype IQ Server (applications, organizations, policies)
|
|
9847
10311
|
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
10312
|
+
dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
|
|
9848
10313
|
config Manage pncli configuration
|
|
9849
10314
|
skills Download and manage Claude Code skills
|
|
9850
10315
|
jwt JWT token utilities (decode header and payload)
|