@kolatts/pncli 5.4.0 → 6.1.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 +2 -0
- package/dist/{chunk-L6OY2546.js → chunk-GHZMFNSX.js} +31 -3
- package/dist/chunk-GHZMFNSX.js.map +1 -0
- package/dist/{chunk-2J2HAFKO.js → chunk-Y3PO2RTO.js} +134 -13
- package/dist/chunk-Y3PO2RTO.js.map +1 -0
- package/dist/cli.js +913 -146
- package/dist/cli.js.map +1 -1
- package/dist/{config-OH2O5E3P.js → config-DYIXSPLA.js} +2 -2
- package/dist/{http-V7PZD7LE.js → http-3FAKF3X4.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +19 -4
- package/skills/pncli/elasticsearch.md +53 -0
- package/skills/pncli/marketplace.md +1 -1
- package/skills/pncli/openshift.md +17 -0
- package/skills/pncli/saucelabs.md +175 -0
- package/dist/chunk-2J2HAFKO.js.map +0 -1
- package/dist/chunk-L6OY2546.js.map +0 -1
- /package/dist/{config-OH2O5E3P.js.map → config-DYIXSPLA.js.map} +0 -0
- /package/dist/{http-V7PZD7LE.js.map → http-3FAKF3X4.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -28,11 +28,13 @@ import {
|
|
|
28
28
|
windowsListCredentials,
|
|
29
29
|
writeGlobalConfig,
|
|
30
30
|
writeRepoConfig
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GHZMFNSX.js";
|
|
32
32
|
import {
|
|
33
|
+
configureTls,
|
|
33
34
|
createHttpClient,
|
|
35
|
+
describeFetchError,
|
|
34
36
|
validateAlationAccessToken
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-Y3PO2RTO.js";
|
|
36
38
|
import {
|
|
37
39
|
ExitCode,
|
|
38
40
|
fail,
|
|
@@ -599,9 +601,9 @@ var BitbucketClient = class {
|
|
|
599
601
|
)
|
|
600
602
|
);
|
|
601
603
|
}
|
|
602
|
-
async getPR(project, repo,
|
|
604
|
+
async getPR(project, repo, id2) {
|
|
603
605
|
return this.http.bitbucket(
|
|
604
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${
|
|
606
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${id2}`
|
|
605
607
|
);
|
|
606
608
|
}
|
|
607
609
|
async createPR(opts) {
|
|
@@ -653,9 +655,9 @@ var BitbucketClient = class {
|
|
|
653
655
|
{ method: "POST", params, body }
|
|
654
656
|
);
|
|
655
657
|
}
|
|
656
|
-
async declinePR(project, repo,
|
|
658
|
+
async declinePR(project, repo, id2, version) {
|
|
657
659
|
return this.http.bitbucket(
|
|
658
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${
|
|
660
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${id2}/decline`,
|
|
659
661
|
{ method: "POST", params: { version } }
|
|
660
662
|
);
|
|
661
663
|
}
|
|
@@ -714,8 +716,8 @@ var BitbucketClient = class {
|
|
|
714
716
|
}
|
|
715
717
|
async resolveComment(project, repo, prId, commentId, version) {
|
|
716
718
|
await this.http.bitbucket(
|
|
717
|
-
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/comments/${commentId}
|
|
718
|
-
{ method: "PUT",
|
|
719
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/comments/${commentId}`,
|
|
720
|
+
{ method: "PUT", body: { version, threadResolved: true } }
|
|
719
721
|
);
|
|
720
722
|
}
|
|
721
723
|
async deleteComment(project, repo, prId, commentId, version) {
|
|
@@ -788,6 +790,12 @@ var BitbucketClient = class {
|
|
|
788
790
|
const pr = await this.getPR(project, repo, prId);
|
|
789
791
|
return pr.reviewers;
|
|
790
792
|
}
|
|
793
|
+
async listDefaultReviewers(project, repo) {
|
|
794
|
+
const result = await this.http.bitbucket(
|
|
795
|
+
`/rest/default-reviewers/1.0/projects/${encodeURIComponent(project)}/repos/${encodeURIComponent(repo)}/conditions`
|
|
796
|
+
);
|
|
797
|
+
return Array.isArray(result) ? result : [];
|
|
798
|
+
}
|
|
791
799
|
async addReviewer(project, repo, prId, username) {
|
|
792
800
|
return this.http.bitbucket(
|
|
793
801
|
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants`,
|
|
@@ -2885,9 +2893,9 @@ function parseFieldsFile(filePath, fieldMap) {
|
|
|
2885
2893
|
}
|
|
2886
2894
|
function translateFieldErrors(err, fieldMap) {
|
|
2887
2895
|
if (!(err instanceof PncliError) || fieldMap.byId.size === 0) return err;
|
|
2888
|
-
const translated = err.message.replace(/\bcustomfield_\d+\b/g, (
|
|
2889
|
-
const def = fieldMap.byId.get(
|
|
2890
|
-
return def ? `${def.name} (${
|
|
2896
|
+
const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id2) => {
|
|
2897
|
+
const def = fieldMap.byId.get(id2);
|
|
2898
|
+
return def ? `${def.name} (${id2})` : id2;
|
|
2891
2899
|
});
|
|
2892
2900
|
if (translated === err.message) return err;
|
|
2893
2901
|
return new PncliError(translated, err.status, err.url);
|
|
@@ -3096,7 +3104,7 @@ function registerBitbucketCommands(program2) {
|
|
|
3096
3104
|
fail(err, "bitbucket", "reply-comment", start);
|
|
3097
3105
|
}
|
|
3098
3106
|
});
|
|
3099
|
-
bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
|
|
3107
|
+
bb.command("resolve-comment").description("Resolve a comment thread on a pull request (use the root comment ID, not a reply)").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
|
|
3100
3108
|
const start = Date.now();
|
|
3101
3109
|
try {
|
|
3102
3110
|
const { client, project, repo } = getClient2(bb);
|
|
@@ -3177,6 +3185,16 @@ function registerBitbucketCommands(program2) {
|
|
|
3177
3185
|
fail(err, "bitbucket", "list-reviewers", start);
|
|
3178
3186
|
}
|
|
3179
3187
|
});
|
|
3188
|
+
bb.command("list-default-reviewers").description("List the default-reviewer conditions configured for a repository").action(async () => {
|
|
3189
|
+
const start = Date.now();
|
|
3190
|
+
try {
|
|
3191
|
+
const { client, project, repo } = getClient2(bb);
|
|
3192
|
+
const data = await client.listDefaultReviewers(project, repo);
|
|
3193
|
+
success(data, "bitbucket", "list-default-reviewers", start);
|
|
3194
|
+
} catch (err) {
|
|
3195
|
+
fail(err, "bitbucket", "list-default-reviewers", start);
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3180
3198
|
bb.command("add-reviewer").description("Add a reviewer to a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--user <username>", "Username to add as a reviewer").action(async (opts) => {
|
|
3181
3199
|
const start = Date.now();
|
|
3182
3200
|
try {
|
|
@@ -4255,8 +4273,8 @@ var ConfluenceClient = class {
|
|
|
4255
4273
|
this.http = http;
|
|
4256
4274
|
}
|
|
4257
4275
|
http;
|
|
4258
|
-
async getPage(
|
|
4259
|
-
return this.http.confluence(`${API3}/content/${
|
|
4276
|
+
async getPage(id2, expand = "body.storage,version,space,ancestors") {
|
|
4277
|
+
return this.http.confluence(`${API3}/content/${id2}`, {
|
|
4260
4278
|
params: { expand }
|
|
4261
4279
|
});
|
|
4262
4280
|
}
|
|
@@ -4274,16 +4292,16 @@ var ConfluenceClient = class {
|
|
|
4274
4292
|
});
|
|
4275
4293
|
}, opts.limit);
|
|
4276
4294
|
}
|
|
4277
|
-
async getPageChildren(
|
|
4295
|
+
async getPageChildren(id2) {
|
|
4278
4296
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
4279
|
-
return this.http.confluence(`${API3}/content/${
|
|
4297
|
+
return this.http.confluence(`${API3}/content/${id2}/child/page`, {
|
|
4280
4298
|
params: { expand: "version", start, limit }
|
|
4281
4299
|
});
|
|
4282
4300
|
});
|
|
4283
4301
|
}
|
|
4284
|
-
async getLabels(
|
|
4302
|
+
async getLabels(id2) {
|
|
4285
4303
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
4286
|
-
return this.http.confluence(`${API3}/content/${
|
|
4304
|
+
return this.http.confluence(`${API3}/content/${id2}/label`, {
|
|
4287
4305
|
params: { start, limit }
|
|
4288
4306
|
});
|
|
4289
4307
|
});
|
|
@@ -4316,7 +4334,7 @@ var ConfluenceClient = class {
|
|
|
4316
4334
|
body
|
|
4317
4335
|
});
|
|
4318
4336
|
}
|
|
4319
|
-
async updatePage(
|
|
4337
|
+
async updatePage(id2, opts) {
|
|
4320
4338
|
const body = {
|
|
4321
4339
|
version: { number: opts.version },
|
|
4322
4340
|
type: "page",
|
|
@@ -4331,13 +4349,13 @@ var ConfluenceClient = class {
|
|
|
4331
4349
|
}
|
|
4332
4350
|
};
|
|
4333
4351
|
}
|
|
4334
|
-
return this.http.confluence(`${API3}/content/${
|
|
4352
|
+
return this.http.confluence(`${API3}/content/${id2}`, {
|
|
4335
4353
|
method: "PUT",
|
|
4336
4354
|
body
|
|
4337
4355
|
});
|
|
4338
4356
|
}
|
|
4339
|
-
async deletePage(
|
|
4340
|
-
return this.http.confluence(`${API3}/content/${
|
|
4357
|
+
async deletePage(id2) {
|
|
4358
|
+
return this.http.confluence(`${API3}/content/${id2}`, { method: "DELETE" });
|
|
4341
4359
|
}
|
|
4342
4360
|
async listComments(pageId) {
|
|
4343
4361
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
@@ -4422,8 +4440,8 @@ var ConfluenceClient = class {
|
|
|
4422
4440
|
async deleteAttachment(attachmentId) {
|
|
4423
4441
|
return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
|
|
4424
4442
|
}
|
|
4425
|
-
async getPageHistory(
|
|
4426
|
-
return this.http.confluence(`${API3}/content/${
|
|
4443
|
+
async getPageHistory(id2) {
|
|
4444
|
+
return this.http.confluence(`${API3}/content/${id2}/history`);
|
|
4427
4445
|
}
|
|
4428
4446
|
async convertToStorage(value, fromRepresentation = "markdown") {
|
|
4429
4447
|
const result = await this.http.confluence(
|
|
@@ -5115,9 +5133,9 @@ function resolveProject2(config, cliProject) {
|
|
|
5115
5133
|
const project = cliProject ?? config.defaults.sde.project;
|
|
5116
5134
|
if (!project) throw new PncliError("No project specified. Use --project or set defaults.sde.project in config.");
|
|
5117
5135
|
if (!/^\d+$/.test(project)) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
5118
|
-
const
|
|
5119
|
-
if (
|
|
5120
|
-
return
|
|
5136
|
+
const id2 = parseInt(project, 10);
|
|
5137
|
+
if (id2 <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
5138
|
+
return id2;
|
|
5121
5139
|
}
|
|
5122
5140
|
function parsePage(val, flag) {
|
|
5123
5141
|
const n = parseInt(val, 10);
|
|
@@ -6045,7 +6063,7 @@ async function checkOsvConnectivity() {
|
|
|
6045
6063
|
} catch (err) {
|
|
6046
6064
|
return {
|
|
6047
6065
|
reachable: false,
|
|
6048
|
-
error:
|
|
6066
|
+
error: describeFetchError(err)
|
|
6049
6067
|
};
|
|
6050
6068
|
}
|
|
6051
6069
|
}
|
|
@@ -6250,7 +6268,7 @@ async function checkSonatypeConnectivity() {
|
|
|
6250
6268
|
} catch (err) {
|
|
6251
6269
|
return {
|
|
6252
6270
|
reachable: false,
|
|
6253
|
-
error:
|
|
6271
|
+
error: describeFetchError(err)
|
|
6254
6272
|
};
|
|
6255
6273
|
}
|
|
6256
6274
|
}
|
|
@@ -6314,10 +6332,10 @@ function mapViolationToVuln(violation) {
|
|
|
6314
6332
|
const cveRefs = violation.constraintViolations.flatMap(
|
|
6315
6333
|
(cv) => cv.reasons.filter((r) => r.reference?.type === "VULNERABILITY").map((r) => r.reference.value)
|
|
6316
6334
|
);
|
|
6317
|
-
const
|
|
6335
|
+
const id2 = cveRefs[0] ?? violation.policyId;
|
|
6318
6336
|
const severity = threatLevelToSeverity(violation.policyThreatLevel);
|
|
6319
6337
|
return {
|
|
6320
|
-
id,
|
|
6338
|
+
id: id2,
|
|
6321
6339
|
summary: violation.policyName,
|
|
6322
6340
|
severity,
|
|
6323
6341
|
cvss: null,
|
|
@@ -6473,7 +6491,7 @@ async function checkSonatypeIqConnectivity(baseUrl, userCode, passcode) {
|
|
|
6473
6491
|
return {
|
|
6474
6492
|
reachable: false,
|
|
6475
6493
|
authenticated: false,
|
|
6476
|
-
error:
|
|
6494
|
+
error: describeFetchError(err)
|
|
6477
6495
|
};
|
|
6478
6496
|
}
|
|
6479
6497
|
}
|
|
@@ -6533,7 +6551,7 @@ async function checkArtifactoryConnectivity(config) {
|
|
|
6533
6551
|
reachable: false,
|
|
6534
6552
|
authenticated: false,
|
|
6535
6553
|
configured: true,
|
|
6536
|
-
error:
|
|
6554
|
+
error: describeFetchError(err)
|
|
6537
6555
|
};
|
|
6538
6556
|
}
|
|
6539
6557
|
}
|
|
@@ -7080,9 +7098,9 @@ var AdoWorkClient = class {
|
|
|
7080
7098
|
this.http = http;
|
|
7081
7099
|
}
|
|
7082
7100
|
http;
|
|
7083
|
-
async getWorkItem(collection,
|
|
7101
|
+
async getWorkItem(collection, id2) {
|
|
7084
7102
|
return this.http.ado(
|
|
7085
|
-
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${
|
|
7103
|
+
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}&$expand=all`
|
|
7086
7104
|
);
|
|
7087
7105
|
}
|
|
7088
7106
|
async createWorkItem(collection, project, type, patch) {
|
|
@@ -7091,9 +7109,9 @@ var AdoWorkClient = class {
|
|
|
7091
7109
|
{ method: "POST", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
|
|
7092
7110
|
);
|
|
7093
7111
|
}
|
|
7094
|
-
async updateWorkItem(collection,
|
|
7112
|
+
async updateWorkItem(collection, id2, patch) {
|
|
7095
7113
|
return this.http.ado(
|
|
7096
|
-
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${
|
|
7114
|
+
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}`,
|
|
7097
7115
|
{ method: "PATCH", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
|
|
7098
7116
|
);
|
|
7099
7117
|
}
|
|
@@ -7115,23 +7133,23 @@ var AdoWorkClient = class {
|
|
|
7115
7133
|
{ method: "POST", body: { text } }
|
|
7116
7134
|
);
|
|
7117
7135
|
}
|
|
7118
|
-
async addTags(collection,
|
|
7119
|
-
const item = await this.getWorkItem(collection,
|
|
7136
|
+
async addTags(collection, id2, tags) {
|
|
7137
|
+
const item = await this.getWorkItem(collection, id2);
|
|
7120
7138
|
const existing = item.fields["System.Tags"] ?? "";
|
|
7121
7139
|
const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
|
|
7122
7140
|
const lowerSet = new Set(current.map((t) => t.toLowerCase()));
|
|
7123
7141
|
const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
|
|
7124
7142
|
const merged = [...current, ...toAdd];
|
|
7125
7143
|
const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
|
|
7126
|
-
return this.updateWorkItem(collection,
|
|
7144
|
+
return this.updateWorkItem(collection, id2, patch);
|
|
7127
7145
|
}
|
|
7128
|
-
async removeTags(collection,
|
|
7129
|
-
const item = await this.getWorkItem(collection,
|
|
7146
|
+
async removeTags(collection, id2, tags) {
|
|
7147
|
+
const item = await this.getWorkItem(collection, id2);
|
|
7130
7148
|
const existing = item.fields["System.Tags"] ?? "";
|
|
7131
7149
|
const lower = new Set(tags.map((t) => t.toLowerCase()));
|
|
7132
7150
|
const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
|
|
7133
7151
|
const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
|
|
7134
|
-
return this.updateWorkItem(collection,
|
|
7152
|
+
return this.updateWorkItem(collection, id2, patch);
|
|
7135
7153
|
}
|
|
7136
7154
|
async listWorkItemTypes(collection, project) {
|
|
7137
7155
|
const result = await this.http.ado(
|
|
@@ -7272,6 +7290,422 @@ function buildDefaultAliases(fields) {
|
|
|
7272
7290
|
return aliases;
|
|
7273
7291
|
}
|
|
7274
7292
|
|
|
7293
|
+
// src/services/saucelabs/commands.ts
|
|
7294
|
+
var SERVICE = "saucelabs";
|
|
7295
|
+
var RDA = "/rdc/v2";
|
|
7296
|
+
var TEAM_MGMT = "/team-management/v1";
|
|
7297
|
+
var BUILD_SOURCES = ["vdc", "rdc"];
|
|
7298
|
+
var BUILD_STATUSES = ["running", "error", "failed", "complete", "success"];
|
|
7299
|
+
var JOB_VISIBILITY = ["public", "public restricted", "share", "team", "private"];
|
|
7300
|
+
var PLATFORM_APIS = ["all", "appium", "webdriver"];
|
|
7301
|
+
var DEVICE_OS = ["ANDROID", "IOS"];
|
|
7302
|
+
var DEVICE_TYPES = ["PHONE", "TABLET"];
|
|
7303
|
+
var DEVICE_STATES = ["AVAILABLE", "IN_USE", "CLEANING", "MAINTENANCE", "REBOOTING", "OFFLINE"];
|
|
7304
|
+
var SESSION_STATES = ["PENDING", "CREATING", "ACTIVE", "CLOSING", "CLOSED", "ERRORED"];
|
|
7305
|
+
var ORIENTATIONS = ["PORTRAIT", "LANDSCAPE"];
|
|
7306
|
+
var STORAGE_KINDS = ["android", "ios"];
|
|
7307
|
+
function getHttp(program2) {
|
|
7308
|
+
const opts = program2.optsWithGlobals();
|
|
7309
|
+
return createHttpClient(
|
|
7310
|
+
loadConfig({ configPath: opts.config }),
|
|
7311
|
+
Boolean(opts.dryRun)
|
|
7312
|
+
);
|
|
7313
|
+
}
|
|
7314
|
+
async function verifySauceLabsCredentials(http, timeoutMs) {
|
|
7315
|
+
return http.saucelabs(
|
|
7316
|
+
`/rest/v1.2/users/${encodeURIComponent(http.saucelabsUsername())}/concurrency`,
|
|
7317
|
+
timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
7318
|
+
);
|
|
7319
|
+
}
|
|
7320
|
+
function parseIntOption(value, flag) {
|
|
7321
|
+
if (value === void 0) return void 0;
|
|
7322
|
+
const trimmed = value.trim();
|
|
7323
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
7324
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
|
|
7325
|
+
}
|
|
7326
|
+
return Number(trimmed);
|
|
7327
|
+
}
|
|
7328
|
+
function parseUnixTime(value, flag) {
|
|
7329
|
+
if (value === void 0) return void 0;
|
|
7330
|
+
const trimmed = value.trim();
|
|
7331
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
7332
|
+
const ms = Date.parse(trimmed);
|
|
7333
|
+
if (!Number.isFinite(ms)) {
|
|
7334
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected Unix seconds or an ISO 8601 date such as 2026-09-01T00:00:00Z.`, 1);
|
|
7335
|
+
}
|
|
7336
|
+
return Math.floor(ms / 1e3);
|
|
7337
|
+
}
|
|
7338
|
+
function parseChoice(value, choices, flag) {
|
|
7339
|
+
if (value === void 0) return void 0;
|
|
7340
|
+
const match = choices.find((c) => c.toLowerCase() === value.trim().toLowerCase());
|
|
7341
|
+
if (!match) {
|
|
7342
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected one of: ${choices.join(", ")}.`, 1);
|
|
7343
|
+
}
|
|
7344
|
+
return match;
|
|
7345
|
+
}
|
|
7346
|
+
function collectList(value, previous = []) {
|
|
7347
|
+
return [...previous, ...value.split(",").map((v) => v.trim()).filter(Boolean)];
|
|
7348
|
+
}
|
|
7349
|
+
function withRepeatedParam(path14, key, values) {
|
|
7350
|
+
if (!values || values.length === 0) return path14;
|
|
7351
|
+
const qs = values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join("&");
|
|
7352
|
+
return `${path14}${path14.includes("?") ? "&" : "?"}${qs}`;
|
|
7353
|
+
}
|
|
7354
|
+
function passedFlag(opts) {
|
|
7355
|
+
if (opts.passed && opts.failed) throw new PncliError("Pass only one of --passed or --failed.", 1);
|
|
7356
|
+
if (opts.passed) return true;
|
|
7357
|
+
if (opts.failed) return false;
|
|
7358
|
+
return void 0;
|
|
7359
|
+
}
|
|
7360
|
+
function requireBody(body, hint) {
|
|
7361
|
+
if (Object.keys(body).length === 0) throw new PncliError(`Nothing to update. ${hint}`, 1);
|
|
7362
|
+
return body;
|
|
7363
|
+
}
|
|
7364
|
+
function id(value) {
|
|
7365
|
+
return encodeURIComponent(value.trim());
|
|
7366
|
+
}
|
|
7367
|
+
function countOf(data, key) {
|
|
7368
|
+
const list = key ? data?.[key] : data;
|
|
7369
|
+
return Array.isArray(list) ? list.length : void 0;
|
|
7370
|
+
}
|
|
7371
|
+
function registerSauceLabsCommands(program2) {
|
|
7372
|
+
function run(action, fn) {
|
|
7373
|
+
return async (...args) => {
|
|
7374
|
+
const start = Date.now();
|
|
7375
|
+
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
7376
|
+
try {
|
|
7377
|
+
success2(await fn(getHttp(program2), ...args), SERVICE, action, start);
|
|
7378
|
+
} catch (err) {
|
|
7379
|
+
fail2(err, SERVICE, action, start);
|
|
7380
|
+
}
|
|
7381
|
+
};
|
|
7382
|
+
}
|
|
7383
|
+
const sauce = program2.command("saucelabs").description("Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms, app storage, teams)");
|
|
7384
|
+
sauce.command("status").description("Sauce Labs service status (operational flag, wait time)").action(run("status", (http) => http.saucelabs("/rest/v1/info/status")));
|
|
7385
|
+
sauce.command("concurrency").description("Concurrency allowed and in use for your organization and team (also verifies credentials)").action(run("concurrency", (http) => verifySauceLabsCredentials(http)));
|
|
7386
|
+
sauce.command("platforms").description("Supported OS/browser/device platforms").option("--api <api>", "Automation API: all, appium, webdriver", "all").action(run("platforms", async (http, opts) => {
|
|
7387
|
+
const api = parseChoice(opts.api, PLATFORM_APIS, "--api");
|
|
7388
|
+
const data = await http.saucelabs(`/rest/v1/info/platforms/${api}`);
|
|
7389
|
+
return { api, count: countOf(data), platforms: data };
|
|
7390
|
+
}));
|
|
7391
|
+
const job = sauce.command("job").description("Test jobs on Sauce virtual devices and desktop browsers (VDC)");
|
|
7392
|
+
job.command("list").description("List your recent jobs").option("--limit <n>", "Maximum number of jobs to return").option("--skip <n>", "Number of jobs to skip").option("--from <time>", "Only jobs created after this time (Unix seconds or ISO 8601)").option("--to <time>", "Only jobs created before this time (Unix seconds or ISO 8601)").action(run("job-list", async (http, opts) => {
|
|
7393
|
+
const params = {
|
|
7394
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7395
|
+
skip: parseIntOption(opts.skip, "--skip"),
|
|
7396
|
+
from: parseUnixTime(opts.from, "--from"),
|
|
7397
|
+
to: parseUnixTime(opts.to, "--to")
|
|
7398
|
+
};
|
|
7399
|
+
const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs`, { params });
|
|
7400
|
+
return { count: countOf(data), jobs: data };
|
|
7401
|
+
}));
|
|
7402
|
+
job.command("get").description("Get one job: status, platform, timings, and log/video URLs").argument("<job-id>", "Job ID").action(run("job-get", (http, jobId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`)));
|
|
7403
|
+
job.command("update").description("Update a job's name, build, tags, result, or visibility").argument("<job-id>", "Job ID").option("--name <name>", "Job name").option("--build <build>", "Build name to group the job under").option("--tags <tags>", "Comma-separated tags; replaces the existing tags", collectList).option("--passed", "Mark the job as passed").option("--failed", "Mark the job as failed").option("--public <visibility>", "Visibility: public, public restricted, share, team, private").action(run("job-update", (http, jobId, opts) => {
|
|
7404
|
+
const body = {};
|
|
7405
|
+
if (opts.name !== void 0) body["name"] = opts.name;
|
|
7406
|
+
if (opts.build !== void 0) body["build"] = opts.build;
|
|
7407
|
+
if (opts.tags !== void 0) body["tags"] = opts.tags;
|
|
7408
|
+
const passed = passedFlag(opts);
|
|
7409
|
+
if (passed !== void 0) body["passed"] = passed;
|
|
7410
|
+
const visibility = parseChoice(opts.public, JOB_VISIBILITY, "--public");
|
|
7411
|
+
if (visibility !== void 0) body["public"] = visibility;
|
|
7412
|
+
return http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, {
|
|
7413
|
+
method: "PUT",
|
|
7414
|
+
body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed, --public.")
|
|
7415
|
+
});
|
|
7416
|
+
}));
|
|
7417
|
+
job.command("stop").description("Stop a running job").argument("<job-id>", "Job ID").action(run("job-stop", (http, jobId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/stop`, { method: "PUT" })));
|
|
7418
|
+
job.command("delete").description("Delete a job and all of its assets").argument("<job-id>", "Job ID").action(run("job-delete", async (http, jobId) => {
|
|
7419
|
+
await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, { method: "DELETE" });
|
|
7420
|
+
return { jobId, deleted: true };
|
|
7421
|
+
}));
|
|
7422
|
+
job.command("assets").description("List the asset files a job produced (logs, video, screenshots) \u2014 names only, no download").argument("<job-id>", "Job ID").action(run("job-assets", async (http, jobId) => {
|
|
7423
|
+
const data = await http.saucelabs(
|
|
7424
|
+
`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/assets`
|
|
7425
|
+
);
|
|
7426
|
+
return { jobId, assets: data };
|
|
7427
|
+
}));
|
|
7428
|
+
const rdcJob = sauce.command("rdc-job").description("Test jobs that ran on real devices");
|
|
7429
|
+
rdcJob.command("list").description("List real-device jobs").option("--limit <n>", "Maximum number of jobs to return").option("--offset <n>", "Number of jobs to skip").option("--live", "Only manual (live) tests").action(run("rdc-job-list", (http, opts) => http.saucelabs("/v1/rdc/jobs", {
|
|
7430
|
+
params: {
|
|
7431
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7432
|
+
offset: parseIntOption(opts.offset, "--offset"),
|
|
7433
|
+
type: opts.live ? "LIVE" : void 0
|
|
7434
|
+
}
|
|
7435
|
+
})));
|
|
7436
|
+
rdcJob.command("get").description("Get one real-device job: device, result, timings, and log URLs").argument("<job-id>", "Job ID").action(run("rdc-job-get", (http, jobId) => http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`)));
|
|
7437
|
+
rdcJob.command("update").description("Update a real-device job's name, build, tags, or result").argument("<job-id>", "Job ID").option("--name <name>", "Job name").option("--build <build>", "Build name").option("--tags <tags>", "Comma-separated tags; replaces the existing tags", collectList).option("--passed", "Mark the job as passed").option("--failed", "Mark the job as failed").action(run("rdc-job-update", (http, jobId, opts) => {
|
|
7438
|
+
const body = {};
|
|
7439
|
+
if (opts.name !== void 0) body["name"] = opts.name;
|
|
7440
|
+
if (opts.build !== void 0) body["build"] = opts.build;
|
|
7441
|
+
if (opts.tags !== void 0) body["tags"] = opts.tags;
|
|
7442
|
+
const passed = passedFlag(opts);
|
|
7443
|
+
if (passed !== void 0) body["passed"] = passed;
|
|
7444
|
+
return http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, {
|
|
7445
|
+
method: "PUT",
|
|
7446
|
+
body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed.")
|
|
7447
|
+
});
|
|
7448
|
+
}));
|
|
7449
|
+
rdcJob.command("stop").description("Stop a running real-device job").argument("<job-id>", "Job ID").action(run("rdc-job-stop", async (http, jobId) => {
|
|
7450
|
+
await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}/stop`, { method: "PUT" });
|
|
7451
|
+
return { jobId, stopped: true };
|
|
7452
|
+
}));
|
|
7453
|
+
rdcJob.command("delete").description("Delete a real-device job (owner, team admin, or org admin only)").argument("<job-id>", "Job ID").action(run("rdc-job-delete", async (http, jobId) => {
|
|
7454
|
+
await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, { method: "DELETE" });
|
|
7455
|
+
return { jobId, deleted: true };
|
|
7456
|
+
}));
|
|
7457
|
+
const build = sauce.command("build").description("Builds (groups of jobs) on virtual (vdc) or real (rdc) devices");
|
|
7458
|
+
build.command("list").description("List builds").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").option("--status <status>", "Filter by status (repeatable or comma-separated): running, error, failed, complete, success", collectList).option("--name <name>", "Filter by build name").option("--start <time>", "Only builds started after this time (Unix seconds or ISO 8601)").option("--end <time>", "Only builds ended before this time (Unix seconds or ISO 8601)").option("--limit <n>", "Maximum number of builds to return").option("--offset <n>", "Number of builds to skip").option("--sort <order>", "Sort by creation time: asc or desc").action(run("build-list", async (http, opts) => {
|
|
7459
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7460
|
+
const statuses = opts.status?.map((s) => parseChoice(s, BUILD_STATUSES, "--status"));
|
|
7461
|
+
const data = await http.saucelabs(withRepeatedParam(`/v2/builds/${source}/`, "status", statuses), {
|
|
7462
|
+
params: {
|
|
7463
|
+
name: opts.name,
|
|
7464
|
+
start: parseUnixTime(opts.start, "--start"),
|
|
7465
|
+
end: parseUnixTime(opts.end, "--end"),
|
|
7466
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7467
|
+
offset: parseIntOption(opts.offset, "--offset"),
|
|
7468
|
+
sort: parseChoice(opts.sort, ["asc", "desc"], "--sort")
|
|
7469
|
+
}
|
|
7470
|
+
});
|
|
7471
|
+
return { source, count: countOf(data, "builds"), builds: data?.builds ?? [] };
|
|
7472
|
+
}));
|
|
7473
|
+
build.command("get").description("Get one build with its job counts").argument("<build-id>", "Build ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").action(run("build-get", (http, buildId, opts) => {
|
|
7474
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7475
|
+
return http.saucelabs(`/v2/builds/${source}/${id(buildId)}/`);
|
|
7476
|
+
}));
|
|
7477
|
+
build.command("jobs").description("List the jobs in a build").argument("<build-id>", "Build ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").option("--failed", "Only failed jobs").option("--errored", "Only errored jobs").option("--passed", "Only passed jobs").option("--running", "Only running jobs").option("--queued", "Only queued jobs").option("--completed", "Only completed jobs").option("--finished", "Only finished jobs").option("--faulty", "Only faulty (failed or errored) jobs").option("--modified-since <time>", "Only jobs modified since this ISO 8601 time").action(run("build-jobs", async (http, buildId, opts) => {
|
|
7478
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7479
|
+
const params = { modified_since: opts["modifiedSince"] };
|
|
7480
|
+
for (const flag of ["failed", "errored", "passed", "running", "queued", "completed", "finished", "faulty"]) {
|
|
7481
|
+
if (opts[flag]) params[flag] = true;
|
|
7482
|
+
}
|
|
7483
|
+
const data = await http.saucelabs(`/v2/builds/${source}/${id(buildId)}/jobs/`, { params });
|
|
7484
|
+
return { buildId, source, count: countOf(data, "jobs"), jobs: data?.jobs ?? [] };
|
|
7485
|
+
}));
|
|
7486
|
+
build.command("for-job").description("Get the build a job belongs to").argument("<job-id>", "Job ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").action(run("build-for-job", (http, jobId, opts) => {
|
|
7487
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7488
|
+
return http.saucelabs(`/v2/builds/${source}/jobs/${id(jobId)}/build/`);
|
|
7489
|
+
}));
|
|
7490
|
+
const device = sauce.command("device").description("Real-device catalog and live availability");
|
|
7491
|
+
device.command("list").description("List real devices your account can use").option("--name <regex>", 'Device name (regex), for example "iPhone.*" or "Samsung Galaxy S2[34].*"').option("--os <os>", "Operating system: android or ios").option("--os-version <version>", 'OS major version or regex, for example 17 or "1[78]"').option("--type <type>", "Device type: phone or tablet").action(run("device-list", async (http, opts) => {
|
|
7492
|
+
const data = await http.saucelabs(`${RDA}/devices`, {
|
|
7493
|
+
params: {
|
|
7494
|
+
deviceName: opts.name,
|
|
7495
|
+
os: parseChoice(opts.os, DEVICE_OS, "--os"),
|
|
7496
|
+
osVersion: opts.osVersion,
|
|
7497
|
+
deviceType: parseChoice(opts.type, DEVICE_TYPES, "--type")
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
return { count: countOf(data), devices: data };
|
|
7501
|
+
}));
|
|
7502
|
+
device.command("get").description("Get one device's full descriptor (hardware, OS, screen)").argument("<device-id>", "Device ID, for example iPhone_15_real").action(run("device-get", (http, deviceId) => http.saucelabs(`/v1/rdc/devices/${id(deviceId)}`)));
|
|
7503
|
+
device.command("status").description("Live availability of real devices (AVAILABLE, IN_USE, CLEANING, ...)").option("--state <state>", "Only devices in this state: available, in_use, cleaning, maintenance, rebooting, offline").option("--name <regex>", "Device name (regex)").option("--private-only", "Only your organization's private devices").action(run("device-status", async (http, opts) => {
|
|
7504
|
+
const data = await http.saucelabs(`${RDA}/devices/status`, {
|
|
7505
|
+
params: {
|
|
7506
|
+
state: parseChoice(opts.state, DEVICE_STATES, "--state"),
|
|
7507
|
+
deviceName: opts.name,
|
|
7508
|
+
privateOnly: opts.privateOnly ? true : void 0
|
|
7509
|
+
}
|
|
7510
|
+
});
|
|
7511
|
+
return { count: countOf(data, "devices"), devices: data?.devices ?? [] };
|
|
7512
|
+
}));
|
|
7513
|
+
const session = sauce.command("session").description("Reserve a real device and drive it over the API (open URLs, install apps, ADB shell, tests)");
|
|
7514
|
+
const sessionPath = (sessionId) => `${RDA}/sessions/${id(sessionId)}`;
|
|
7515
|
+
const deviceAction = (sessionId, action) => `${sessionPath(sessionId)}/device/${action}`;
|
|
7516
|
+
session.command("list").description("List your device sessions").option("--state <state>", "Only sessions in this state: pending, creating, active, closing, closed, errored").option("--device-name <regex>", "Device name (regex)").action(run("session-list", async (http, opts) => {
|
|
7517
|
+
const data = await http.saucelabs(`${RDA}/sessions`, {
|
|
7518
|
+
params: { state: parseChoice(opts.state, SESSION_STATES, "--state"), deviceName: opts.deviceName }
|
|
7519
|
+
});
|
|
7520
|
+
return { count: countOf(data, "sessions"), sessions: data?.sessions ?? [] };
|
|
7521
|
+
}));
|
|
7522
|
+
session.command("create").description("Reserve a real device. Returns immediately in PENDING state unless --wait is passed").option("--device-name <regex>", 'Device name or regex, for example "iPhone 15.*" (any device when omitted)').option("--os <os>", "Operating system: android or ios").option("--tunnel-name <name>", "Route device traffic through this Sauce Connect tunnel").option("--tunnel-owner <username>", "Owner of a shared tunnel").option("--duration <iso8601>", "Session length as an ISO 8601 duration, for example PT30M (default 6h; public devices cap at 1h)").option("--wait", "Poll until the session is ACTIVE (or fails) before returning").option("--timeout <seconds>", "Maximum seconds to wait with --wait", "300").action(run("session-create", async (http, opts) => {
|
|
7523
|
+
if (opts.tunnelOwner && !opts.tunnelName) throw new PncliError("--tunnel-owner requires --tunnel-name.", 1);
|
|
7524
|
+
const timeoutSeconds = parseIntOption(opts.timeout, "--timeout") ?? 300;
|
|
7525
|
+
const deviceQuery = {};
|
|
7526
|
+
if (opts.deviceName) deviceQuery["deviceName"] = opts.deviceName;
|
|
7527
|
+
const os4 = parseChoice(opts.os, ["android", "ios"], "--os");
|
|
7528
|
+
if (os4) deviceQuery["os"] = os4;
|
|
7529
|
+
const configuration = {};
|
|
7530
|
+
if (opts.tunnelName) {
|
|
7531
|
+
configuration["tunnel"] = { name: opts.tunnelName, ...opts.tunnelOwner ? { owner: opts.tunnelOwner } : {} };
|
|
7532
|
+
}
|
|
7533
|
+
if (opts.duration) configuration["sessionDuration"] = opts.duration;
|
|
7534
|
+
const body = { device: deviceQuery };
|
|
7535
|
+
if (Object.keys(configuration).length > 0) body["configuration"] = configuration;
|
|
7536
|
+
const created = await http.saucelabs(`${RDA}/sessions`, { method: "POST", body });
|
|
7537
|
+
if (!opts.wait) return created;
|
|
7538
|
+
return waitForSession(http, created.id, timeoutSeconds);
|
|
7539
|
+
}));
|
|
7540
|
+
session.command("get").description("Get a session: state, device, expiry, Appium URL, and live-view link").argument("<session-id>", "Session ID").action(run("session-get", (http, sessionId) => http.saucelabs(sessionPath(sessionId))));
|
|
7541
|
+
session.command("delete").description("Close a session and release the device").argument("<session-id>", "Session ID").option("--reboot", "Reboot the device after closing (private devices only)").action(run("session-delete", (http, sessionId, opts) => http.saucelabs(sessionPath(sessionId), {
|
|
7542
|
+
method: "DELETE",
|
|
7543
|
+
params: { rebootDevice: opts.reboot ? true : void 0 }
|
|
7544
|
+
})));
|
|
7545
|
+
session.command("open-url").description("Open a URL on the device (Chrome on Android; Safari or a deep link on iOS)").argument("<session-id>", "Session ID").argument("<url>", "URL to open").action(run("session-open-url", async (http, sessionId, url) => {
|
|
7546
|
+
await http.saucelabs(deviceAction(sessionId, "openUrl"), { method: "POST", body: { url } });
|
|
7547
|
+
return { sessionId, url, opened: true };
|
|
7548
|
+
}));
|
|
7549
|
+
session.command("shell").description("Run an ADB shell command on an Android device (public devices allow only a safe subset)").argument("<session-id>", "Session ID").argument("<command>", 'Shell command without the "adb shell" prefix, for example "getprop ro.build.version.release"').action(run("session-shell", async (http, sessionId, command) => {
|
|
7550
|
+
const data = await http.saucelabs(deviceAction(sessionId, "executeShellCommand"), {
|
|
7551
|
+
method: "POST",
|
|
7552
|
+
body: { adbShellCommand: command },
|
|
7553
|
+
timeoutMs: 12e4
|
|
7554
|
+
});
|
|
7555
|
+
return { sessionId, command, stdout: data?.stdout ?? "" };
|
|
7556
|
+
}));
|
|
7557
|
+
session.command("install-app").description("Install an app from Sauce app storage onto the device (runs in the background)").argument("<session-id>", "Session ID").argument("<app>", "App reference: storage:<file-id> or storage:filename=<name>").option("--launch", "Launch the app once installed").option("--no-instrumentation", "Install without Sauce instrumentation").action(run("session-install-app", (http, sessionId, app, opts) => http.saucelabs(deviceAction(sessionId, "installApp"), {
|
|
7558
|
+
method: "POST",
|
|
7559
|
+
body: { app, enableInstrumentation: opts.instrumentation, launchAfterInstall: Boolean(opts.launch) }
|
|
7560
|
+
})));
|
|
7561
|
+
session.command("installations").description("List app installations on the device and their status").argument("<session-id>", "Session ID").action(run("session-installations", (http, sessionId) => http.saucelabs(deviceAction(sessionId, "listAppInstallations"), { method: "POST" })));
|
|
7562
|
+
const appIdentity = (opts) => {
|
|
7563
|
+
if (Boolean(opts.bundleId) === Boolean(opts.packageName)) {
|
|
7564
|
+
throw new PncliError("Pass exactly one of --bundle-id (iOS) or --package-name (Android).", 1);
|
|
7565
|
+
}
|
|
7566
|
+
return opts.bundleId ? { bundleId: opts.bundleId } : { packageName: opts.packageName };
|
|
7567
|
+
};
|
|
7568
|
+
session.command("launch-app").description("Launch an installed app").argument("<session-id>", "Session ID").option("--bundle-id <id>", "iOS bundle ID").option("--package-name <name>", "Android package name").option("--activity <name>", "Android activity to launch").action(run("session-launch-app", async (http, sessionId, opts) => {
|
|
7569
|
+
if (opts.activity && !opts.packageName) throw new PncliError("--activity requires --package-name.", 1);
|
|
7570
|
+
const body = { ...appIdentity(opts), ...opts.activity ? { activityName: opts.activity } : {} };
|
|
7571
|
+
await http.saucelabs(deviceAction(sessionId, "launchApp"), { method: "POST", body });
|
|
7572
|
+
return { sessionId, ...body, launched: true };
|
|
7573
|
+
}));
|
|
7574
|
+
session.command("uninstall-app").description("Uninstall an app").argument("<session-id>", "Session ID").option("--bundle-id <id>", "iOS bundle ID").option("--package-name <name>", "Android package name").action(run("session-uninstall-app", async (http, sessionId, opts) => {
|
|
7575
|
+
const body = appIdentity(opts);
|
|
7576
|
+
await http.saucelabs(deviceAction(sessionId, "uninstallApp"), { method: "POST", body });
|
|
7577
|
+
return { sessionId, ...body, uninstalled: true };
|
|
7578
|
+
}));
|
|
7579
|
+
session.command("settings").description("Change device settings: orientation (both), locale and animations (Android only)").argument("<session-id>", "Session ID").option("--orientation <orientation>", "Screen orientation: portrait or landscape").option("--locale <locale>", "Locale, for example en_US (Android only)").option("--animations <on|off>", "Enable or disable system animations (Android only)").action(run("session-settings", async (http, sessionId, opts) => {
|
|
7580
|
+
const body = {};
|
|
7581
|
+
const orientation = parseChoice(opts.orientation, ORIENTATIONS, "--orientation");
|
|
7582
|
+
if (orientation) body["orientation"] = orientation;
|
|
7583
|
+
if (opts.locale) body["locale"] = opts.locale;
|
|
7584
|
+
const animations = parseChoice(opts.animations, ["on", "off"], "--animations");
|
|
7585
|
+
if (animations) body["animations"] = animations === "on";
|
|
7586
|
+
await http.saucelabs(deviceAction(sessionId, "applySettings"), {
|
|
7587
|
+
method: "POST",
|
|
7588
|
+
body: requireBody(body, "Pass at least one of --orientation, --locale, --animations.")
|
|
7589
|
+
});
|
|
7590
|
+
return { sessionId, applied: body };
|
|
7591
|
+
}));
|
|
7592
|
+
session.command("start-test").description("Start recording a test (a job) inside the session").argument("<session-id>", "Session ID").option("--name <name>", "Test name").option("--build <build>", "Build name").option("--tags <tags>", "Comma-separated tags", collectList).option("--video", "Record video").option("--device-logs", "Capture device logs").option("--network-capture", "Capture network traffic").option("--screenshots", "Capture screenshots").option("--appium-logs", "Capture Appium logs").action(run("session-start-test", (http, sessionId, opts) => {
|
|
7593
|
+
const body = {};
|
|
7594
|
+
if (opts.name) body["testName"] = opts.name;
|
|
7595
|
+
if (opts.build) body["build"] = opts.build;
|
|
7596
|
+
if (opts.tags) body["tags"] = opts.tags;
|
|
7597
|
+
const artifacts = {
|
|
7598
|
+
video: Boolean(opts.video),
|
|
7599
|
+
deviceLogs: Boolean(opts.deviceLogs),
|
|
7600
|
+
networkCapture: Boolean(opts.networkCapture),
|
|
7601
|
+
screenshots: Boolean(opts.screenshots),
|
|
7602
|
+
appiumLogs: Boolean(opts.appiumLogs)
|
|
7603
|
+
};
|
|
7604
|
+
if (Object.values(artifacts).some(Boolean)) body["artifacts"] = artifacts;
|
|
7605
|
+
return http.saucelabs(`${sessionPath(sessionId)}/startTest`, { method: "POST", body });
|
|
7606
|
+
}));
|
|
7607
|
+
session.command("end-test").description("End the active test in the session, optionally recording its result").argument("<session-id>", "Session ID").option("--passed", "Mark the test as passed").option("--failed", "Mark the test as failed").action(run("session-end-test", (http, sessionId, opts) => {
|
|
7608
|
+
const passed = passedFlag(opts);
|
|
7609
|
+
return http.saucelabs(`${sessionPath(sessionId)}/endTest`, {
|
|
7610
|
+
method: "POST",
|
|
7611
|
+
body: passed === void 0 ? {} : { status: passed ? "passed" : "failed" }
|
|
7612
|
+
});
|
|
7613
|
+
}));
|
|
7614
|
+
session.command("tests").description("List the tests recorded in a session, with links to each job").argument("<session-id>", "Session ID").action(run("session-tests", (http, sessionId) => http.saucelabs(`${sessionPath(sessionId)}/tests`)));
|
|
7615
|
+
session.command("network-profiles").description("List the network throttling profiles available to a session").argument("<session-id>", "Session ID").action(run("session-network-profiles", (http, sessionId) => http.saucelabs(`${sessionPath(sessionId)}/network/profiles`)));
|
|
7616
|
+
session.command("network").description("Throttle the device network with a named profile or explicit conditions, or --reset to clear").argument("<session-id>", "Session ID").option("--profile <name>", "Named profile from network-profiles, for example 4G-fast").option("--download <kbps>", "Download speed in kbps (0-50000)").option("--upload <kbps>", "Upload speed in kbps (0-50000)").option("--latency <ms>", "Added latency in ms (0-3000)").option("--loss <percent>", "Packet loss percent (0-100)").option("--reset", "Remove all throttling").action(run("session-network", async (http, sessionId, opts) => {
|
|
7617
|
+
const conditions = {};
|
|
7618
|
+
const set = (key, value, flag, max) => {
|
|
7619
|
+
const n = parseIntOption(value, flag);
|
|
7620
|
+
if (n === void 0) return;
|
|
7621
|
+
if (n > max) throw new PncliError(`${flag} must be between 0 and ${max}.`, 1);
|
|
7622
|
+
conditions[key] = n;
|
|
7623
|
+
};
|
|
7624
|
+
set("downloadSpeed", opts.download, "--download", 5e4);
|
|
7625
|
+
set("uploadSpeed", opts.upload, "--upload", 5e4);
|
|
7626
|
+
set("latency", opts.latency, "--latency", 3e3);
|
|
7627
|
+
set("loss", opts.loss, "--loss", 100);
|
|
7628
|
+
const modes = [Boolean(opts.reset), Boolean(opts.profile), Object.keys(conditions).length > 0].filter(Boolean).length;
|
|
7629
|
+
if (modes !== 1) {
|
|
7630
|
+
throw new PncliError("Pass exactly one of --profile, --reset, or explicit conditions (--download/--upload/--latency/--loss).", 1);
|
|
7631
|
+
}
|
|
7632
|
+
if (opts.reset) {
|
|
7633
|
+
await http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "DELETE" });
|
|
7634
|
+
return { sessionId, reset: true };
|
|
7635
|
+
}
|
|
7636
|
+
if (opts.profile) {
|
|
7637
|
+
return http.saucelabs(`${sessionPath(sessionId)}/network/profile`, { method: "POST", body: { networkProfile: opts.profile } });
|
|
7638
|
+
}
|
|
7639
|
+
return http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "POST", body: { networkConditions: conditions } });
|
|
7640
|
+
}));
|
|
7641
|
+
session.command("appium").description("Get the session's Appium server URL, or --start one").argument("<session-id>", "Session ID").option("--start", "Start an Appium server for the session").option("--appium-version <version>", "Appium version to start (see appium-versions)").action(run("session-appium", (http, sessionId, opts) => {
|
|
7642
|
+
if (opts.appiumVersion && !opts.start) throw new PncliError("--appium-version requires --start.", 1);
|
|
7643
|
+
if (!opts.start) return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`);
|
|
7644
|
+
return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`, {
|
|
7645
|
+
method: "POST",
|
|
7646
|
+
body: opts.appiumVersion ? { appiumVersion: opts.appiumVersion } : {}
|
|
7647
|
+
});
|
|
7648
|
+
}));
|
|
7649
|
+
sauce.command("appium-versions").description("Appium versions available for real-device sessions, with end-of-life dates").action(run("appium-versions", (http) => http.saucelabs(`${RDA}/appium/versions`)));
|
|
7650
|
+
const tunnel = sauce.command("tunnel").description("Sauce Connect tunnels");
|
|
7651
|
+
tunnel.command("list").description("List your tunnels with full details").option("--all", "Include tunnels shared with you").action(run("tunnel-list", async (http, opts) => {
|
|
7652
|
+
const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels`, {
|
|
7653
|
+
params: { full: true, all: opts.all ? true : void 0 }
|
|
7654
|
+
});
|
|
7655
|
+
return { count: countOf(data), tunnels: data };
|
|
7656
|
+
}));
|
|
7657
|
+
tunnel.command("get").description("Get one tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-get", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}`)));
|
|
7658
|
+
tunnel.command("jobs").description("Number of jobs currently running through a tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-jobs", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}/num_jobs`)));
|
|
7659
|
+
tunnel.command("stop").description("Shut down a tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-stop", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}`, { method: "DELETE" })));
|
|
7660
|
+
const storage = sauce.command("storage").description("App storage metadata (no upload or download)");
|
|
7661
|
+
storage.command("files").description("List uploaded app files").option("--query <text>", "Search file names and app metadata").option("--kind <kind>", "App kind: android or ios").option("--page <n>", "Page number (starts at 1)").option("--per-page <n>", "Results per page (max 100)").action(run("storage-files", (http, opts) => http.saucelabs("/v1/storage/files", {
|
|
7662
|
+
params: {
|
|
7663
|
+
q: opts.query,
|
|
7664
|
+
kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
|
|
7665
|
+
page: parseIntOption(opts.page, "--page"),
|
|
7666
|
+
per_page: parseIntOption(opts.perPage, "--per-page")
|
|
7667
|
+
}
|
|
7668
|
+
})));
|
|
7669
|
+
storage.command("groups").description("List app groups (all versions of one app)").option("--query <text>", "Search group names").option("--kind <kind>", "App kind: android or ios").option("--page <n>", "Page number (starts at 1)").option("--per-page <n>", "Results per page (max 100)").action(run("storage-groups", (http, opts) => http.saucelabs("/v1/storage/groups", {
|
|
7670
|
+
params: {
|
|
7671
|
+
q: opts.query,
|
|
7672
|
+
kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
|
|
7673
|
+
page: parseIntOption(opts.page, "--page"),
|
|
7674
|
+
per_page: parseIntOption(opts.perPage, "--per-page")
|
|
7675
|
+
}
|
|
7676
|
+
})));
|
|
7677
|
+
const team = sauce.command("team").description("Teams in your organization");
|
|
7678
|
+
team.command("list").description("List teams").option("--name <prefix>", "Team name prefix").action(run("team-list", (http, opts) => http.saucelabs(`${TEAM_MGMT}/teams/`, { params: { name: opts.name } })));
|
|
7679
|
+
team.command("get").description("Get one team and its settings").argument("<team-id>", "Team ID").action(run("team-get", (http, teamId) => http.saucelabs(`${TEAM_MGMT}/teams/${id(teamId)}/`)));
|
|
7680
|
+
team.command("members").description("List a team's members").argument("<team-id>", "Team ID").action(run("team-members", (http, teamId) => http.saucelabs(`${TEAM_MGMT}/teams/${id(teamId)}/members/`)));
|
|
7681
|
+
sauce.command("users").description("Look up users in your organization").option("--username <username>", "Exact username").option("--phrase <text>", "Search username, name, and email").option("--limit <n>", "Maximum results (default 20, max 100)").option("--offset <n>", "Number of results to skip").action(run("users", (http, opts) => http.saucelabs(`${TEAM_MGMT}/users/`, {
|
|
7682
|
+
params: {
|
|
7683
|
+
username: opts.username,
|
|
7684
|
+
phrase: opts.phrase,
|
|
7685
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7686
|
+
offset: parseIntOption(opts.offset, "--offset")
|
|
7687
|
+
}
|
|
7688
|
+
})));
|
|
7689
|
+
}
|
|
7690
|
+
var TERMINAL_SESSION_STATES = /* @__PURE__ */ new Set(["CLOSING", "CLOSED", "ERRORED"]);
|
|
7691
|
+
async function waitForSession(http, sessionId, timeoutSeconds, pollMs = 5e3) {
|
|
7692
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
7693
|
+
for (; ; ) {
|
|
7694
|
+
const current = await http.saucelabs(`${RDA}/sessions/${id(sessionId)}`);
|
|
7695
|
+
if (current.state === "ACTIVE") return current;
|
|
7696
|
+
if (TERMINAL_SESSION_STATES.has(current.state)) {
|
|
7697
|
+
throw new PncliError(`Session ${sessionId} ended in state ${current.state} before becoming ACTIVE.`, 1);
|
|
7698
|
+
}
|
|
7699
|
+
if (Date.now() + pollMs > deadline) {
|
|
7700
|
+
throw new PncliError(
|
|
7701
|
+
`Session ${sessionId} still ${current.state} after ${timeoutSeconds}s. It keeps starting in the background \u2014 check it with: pncli saucelabs session get ${sessionId}`,
|
|
7702
|
+
1
|
|
7703
|
+
);
|
|
7704
|
+
}
|
|
7705
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
|
|
7275
7709
|
// src/services/config/check.ts
|
|
7276
7710
|
async function runCredentialChecks(cfg, http) {
|
|
7277
7711
|
const results = {};
|
|
@@ -7564,6 +7998,18 @@ async function runCredentialChecks(cfg, http) {
|
|
|
7564
7998
|
results.logscale = categorize(err);
|
|
7565
7999
|
}
|
|
7566
8000
|
}
|
|
8001
|
+
if (!cfg.elasticsearch.apiKey) {
|
|
8002
|
+
results.elasticsearch = { status: "blank", message: "not configured" };
|
|
8003
|
+
} else if (!cfg.elasticsearch.baseUrl) {
|
|
8004
|
+
results.elasticsearch = { status: "error", message: "baseUrl not configured" };
|
|
8005
|
+
} else {
|
|
8006
|
+
try {
|
|
8007
|
+
await http.elasticsearch("/_cluster/health", { timeoutMs: 1e4 });
|
|
8008
|
+
results.elasticsearch = { status: "valid", message: "ok" };
|
|
8009
|
+
} catch (err) {
|
|
8010
|
+
results.elasticsearch = categorize(err);
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
7567
8013
|
if (!cfg.splitio.adminApiKey) {
|
|
7568
8014
|
results.splitio = { status: "blank", message: "not configured" };
|
|
7569
8015
|
} else if (!cfg.splitio.baseUrl) {
|
|
@@ -7602,6 +8048,20 @@ async function runCredentialChecks(cfg, http) {
|
|
|
7602
8048
|
results.alation = categorize(err);
|
|
7603
8049
|
}
|
|
7604
8050
|
}
|
|
8051
|
+
if (!cfg.saucelabs.accessKey) {
|
|
8052
|
+
results.saucelabs = { status: "blank", message: "not configured" };
|
|
8053
|
+
} else if (!cfg.saucelabs.baseUrl) {
|
|
8054
|
+
results.saucelabs = { status: "error", message: "baseUrl not configured" };
|
|
8055
|
+
} else if (!cfg.saucelabs.username) {
|
|
8056
|
+
results.saucelabs = { status: "error", message: "username not configured" };
|
|
8057
|
+
} else {
|
|
8058
|
+
try {
|
|
8059
|
+
await verifySauceLabsCredentials(http, 1e4);
|
|
8060
|
+
results.saucelabs = { status: "valid", message: "ok" };
|
|
8061
|
+
} catch (err) {
|
|
8062
|
+
results.saucelabs = categorize(err);
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
7605
8065
|
return results;
|
|
7606
8066
|
}
|
|
7607
8067
|
|
|
@@ -8985,7 +9445,7 @@ function pullMarketplace(marketplacePath, m, marketplaceName) {
|
|
|
8985
9445
|
}
|
|
8986
9446
|
const updated = !pullOutput.includes("Already up to date");
|
|
8987
9447
|
if (pullOutput.trim() && updated) warn(pullOutput.trim());
|
|
8988
|
-
warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014
|
|
9448
|
+
warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014 refreshing installed plugins from the local clone.`);
|
|
8989
9449
|
return { updated };
|
|
8990
9450
|
}
|
|
8991
9451
|
function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
|
|
@@ -9035,13 +9495,6 @@ function installMarketplaceToTarget(m, marketplacePath, pluginChoices, pluginFil
|
|
|
9035
9495
|
} else {
|
|
9036
9496
|
requested = [pluginFilter];
|
|
9037
9497
|
}
|
|
9038
|
-
const missing = requested.filter((name) => !installedHere.includes(name));
|
|
9039
|
-
if (!opts.updated && !opts.force) {
|
|
9040
|
-
if (missing.length === 0) {
|
|
9041
|
-
return { ...base, plugins: {}, total: 0, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
|
|
9042
|
-
}
|
|
9043
|
-
requested = missing;
|
|
9044
|
-
}
|
|
9045
9498
|
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, requested, target.target);
|
|
9046
9499
|
return { ...base, plugins: results, total: totalInstalled };
|
|
9047
9500
|
}
|
|
@@ -9675,7 +10128,7 @@ Plugin skills always install at user scope. --agent picks the host (default: ${D
|
|
|
9675
10128
|
fail(err, "skills", "marketplace-remove", start);
|
|
9676
10129
|
}
|
|
9677
10130
|
});
|
|
9678
|
-
marketplace.command("sync").description("With no arguments, refreshes every already-installed plugin from every marketplace (same as --marketplace all --installed-only). Pass --force, a plugin, or --marketplace to get the interactive picker instead.").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--force",
|
|
10131
|
+
marketplace.command("sync").description("With no arguments, refreshes every already-installed plugin from every marketplace (same as --marketplace all --installed-only). Pass --force, a plugin, or --marketplace to get the interactive picker instead.").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--force", "Opt into the interactive picker instead of the installed-only shorthand (plugins are always refreshed from the local clone, with or without new upstream changes)").option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").option("--no-instructions", "Do not apply the shipped AGENTS.md / CLAUDE.md to user-level instructions files").action(async (plugin, opts) => {
|
|
9679
10132
|
const start = Date.now();
|
|
9680
10133
|
try {
|
|
9681
10134
|
const configPath = getGlobalConfigPath();
|
|
@@ -10168,19 +10621,31 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
10168
10621
|
(name) => fs6.statSync(path11.join(skillsSrc, name)).isDirectory()
|
|
10169
10622
|
);
|
|
10170
10623
|
const installed = [];
|
|
10624
|
+
const active = [];
|
|
10171
10625
|
const failed = [];
|
|
10626
|
+
const disabledHere = meta ? readInstalledMeta(targetDir).disabled ?? {} : {};
|
|
10172
10627
|
for (const skillName of skillNames) {
|
|
10173
10628
|
const dest = path11.resolve(targetDir, skillName);
|
|
10174
10629
|
if (!dest.startsWith(resolvedTarget + path11.sep)) {
|
|
10175
10630
|
failed.push(skillName);
|
|
10176
10631
|
continue;
|
|
10177
10632
|
}
|
|
10633
|
+
const stashed = disabledHere[skillName];
|
|
10634
|
+
if (meta && stashed && stashed.plugin === meta.plugin) {
|
|
10635
|
+
const stashDest = path11.join(targetDir, DISABLED_SUBDIR, skillName);
|
|
10636
|
+
fs6.mkdirSync(path11.dirname(stashDest), { recursive: true });
|
|
10637
|
+
fs6.rmSync(stashDest, { recursive: true, force: true });
|
|
10638
|
+
fs6.cpSync(path11.join(skillsSrc, skillName), stashDest, { recursive: true });
|
|
10639
|
+
installed.push(skillName);
|
|
10640
|
+
continue;
|
|
10641
|
+
}
|
|
10178
10642
|
fs6.rmSync(dest, { recursive: true, force: true });
|
|
10179
10643
|
fs6.cpSync(path11.join(skillsSrc, skillName), dest, { recursive: true });
|
|
10180
10644
|
installed.push(skillName);
|
|
10645
|
+
active.push(skillName);
|
|
10181
10646
|
}
|
|
10182
10647
|
if (meta) {
|
|
10183
|
-
recordInstalledSkills(targetDir,
|
|
10648
|
+
recordInstalledSkills(targetDir, active, {
|
|
10184
10649
|
source: "marketplace",
|
|
10185
10650
|
marketplace: meta.marketplace,
|
|
10186
10651
|
plugin: meta.plugin,
|
|
@@ -10479,6 +10944,16 @@ function registerConfigCommands(program2) {
|
|
|
10479
10944
|
} else {
|
|
10480
10945
|
results.logscale = { ok: null, message: "not configured" };
|
|
10481
10946
|
}
|
|
10947
|
+
if (cfg.elasticsearch.baseUrl && cfg.elasticsearch.apiKey) {
|
|
10948
|
+
try {
|
|
10949
|
+
await http.elasticsearch("/_cluster/health");
|
|
10950
|
+
results.elasticsearch = { ok: true, message: "connected" };
|
|
10951
|
+
} catch (err) {
|
|
10952
|
+
results.elasticsearch = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
10953
|
+
}
|
|
10954
|
+
} else {
|
|
10955
|
+
results.elasticsearch = { ok: null, message: "not configured" };
|
|
10956
|
+
}
|
|
10482
10957
|
if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
|
|
10483
10958
|
try {
|
|
10484
10959
|
await http.splitio("/internal/api/v2/workspaces");
|
|
@@ -10509,6 +10984,20 @@ function registerConfigCommands(program2) {
|
|
|
10509
10984
|
} else {
|
|
10510
10985
|
results.alation = { ok: null, message: "not configured" };
|
|
10511
10986
|
}
|
|
10987
|
+
if (cfg.saucelabs.accessKey && !cfg.saucelabs.baseUrl) {
|
|
10988
|
+
results.saucelabs = { ok: false, message: "baseUrl not configured" };
|
|
10989
|
+
} else if (cfg.saucelabs.accessKey && !cfg.saucelabs.username) {
|
|
10990
|
+
results.saucelabs = { ok: false, message: "username not configured" };
|
|
10991
|
+
} else if (cfg.saucelabs.accessKey) {
|
|
10992
|
+
try {
|
|
10993
|
+
await verifySauceLabsCredentials(http);
|
|
10994
|
+
results.saucelabs = { ok: true, message: "connected" };
|
|
10995
|
+
} catch (err) {
|
|
10996
|
+
results.saucelabs = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
10997
|
+
}
|
|
10998
|
+
} else {
|
|
10999
|
+
results.saucelabs = { ok: null, message: "not configured" };
|
|
11000
|
+
}
|
|
10512
11001
|
success(results, "config", "test", start);
|
|
10513
11002
|
} catch (err) {
|
|
10514
11003
|
fail(err, "config", "test", start);
|
|
@@ -10549,9 +11038,11 @@ function registerConfigCommands(program2) {
|
|
|
10549
11038
|
"dynatrace_platform",
|
|
10550
11039
|
...dynamicEnvKeys,
|
|
10551
11040
|
"logscale",
|
|
11041
|
+
"elasticsearch",
|
|
10552
11042
|
"splitio",
|
|
10553
11043
|
"figma",
|
|
10554
|
-
"alation"
|
|
11044
|
+
"alation",
|
|
11045
|
+
"saucelabs"
|
|
10555
11046
|
];
|
|
10556
11047
|
if (cmdOpts.output === "table") {
|
|
10557
11048
|
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
@@ -11103,6 +11594,40 @@ async function initGlobalConfig(start) {
|
|
|
11103
11594
|
}
|
|
11104
11595
|
}
|
|
11105
11596
|
}
|
|
11597
|
+
process.stderr.write("\n\u2500\u2500 Elasticsearch \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");
|
|
11598
|
+
const useElasticsearch = await confirm({
|
|
11599
|
+
message: "Configure Elasticsearch for log and document searches?",
|
|
11600
|
+
default: false
|
|
11601
|
+
});
|
|
11602
|
+
let elasticsearchBaseUrl = "";
|
|
11603
|
+
let elasticsearchApiKey = "";
|
|
11604
|
+
if (useElasticsearch) {
|
|
11605
|
+
elasticsearchBaseUrl = await input2({
|
|
11606
|
+
message: "Elasticsearch base URL (e.g. https://elasticsearch.imagile.dev:9200):",
|
|
11607
|
+
default: ""
|
|
11608
|
+
});
|
|
11609
|
+
elasticsearchApiKey = await password2({
|
|
11610
|
+
message: 'Elasticsearch API key (the base64 "encoded" value from Kibana \u2192 Stack Management \u2192 API keys):'
|
|
11611
|
+
});
|
|
11612
|
+
if (elasticsearchBaseUrl && elasticsearchApiKey) {
|
|
11613
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
11614
|
+
try {
|
|
11615
|
+
const tempConfig = {
|
|
11616
|
+
...loadConfig(),
|
|
11617
|
+
elasticsearch: {
|
|
11618
|
+
baseUrl: normalizeBaseUrl(elasticsearchBaseUrl),
|
|
11619
|
+
apiKey: elasticsearchApiKey
|
|
11620
|
+
}
|
|
11621
|
+
};
|
|
11622
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
11623
|
+
await tempHttp.elasticsearch("/_cluster/health");
|
|
11624
|
+
process.stderr.write(" Connected.\n");
|
|
11625
|
+
} catch (err) {
|
|
11626
|
+
warn(`Could not connect to Elasticsearch: ${err instanceof Error ? err.message : String(err)}`);
|
|
11627
|
+
warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
}
|
|
11106
11631
|
process.stderr.write("\n\u2500\u2500 Split.IO \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");
|
|
11107
11632
|
const useSplitio = await confirm({
|
|
11108
11633
|
message: "Configure Split.IO for feature flag administration?",
|
|
@@ -11208,6 +11733,48 @@ async function initGlobalConfig(start) {
|
|
|
11208
11733
|
}
|
|
11209
11734
|
}
|
|
11210
11735
|
}
|
|
11736
|
+
process.stderr.write("\n\u2500\u2500 Sauce Labs \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");
|
|
11737
|
+
const useSaucelabs = await confirm({
|
|
11738
|
+
message: "Configure Sauce Labs for jobs, builds, and real-device access?",
|
|
11739
|
+
default: false
|
|
11740
|
+
});
|
|
11741
|
+
let saucelabsBaseUrl = "";
|
|
11742
|
+
let saucelabsUsername = "";
|
|
11743
|
+
let saucelabsAccessKey = "";
|
|
11744
|
+
if (useSaucelabs) {
|
|
11745
|
+
process.stderr.write(" Find your username and access key in Sauce Labs under Account \u2192 User Settings.\n");
|
|
11746
|
+
process.stderr.write(" The API host depends on your data center: https://api.us-west-1.saucelabs.com,\n");
|
|
11747
|
+
process.stderr.write(" https://api.eu-central-1.saucelabs.com, or https://api.us-east-4.saucelabs.com.\n");
|
|
11748
|
+
saucelabsBaseUrl = await input2({
|
|
11749
|
+
message: "Sauce Labs API base URL:",
|
|
11750
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
11751
|
+
});
|
|
11752
|
+
saucelabsUsername = await input2({
|
|
11753
|
+
message: "Sauce Labs username:",
|
|
11754
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
11755
|
+
});
|
|
11756
|
+
saucelabsAccessKey = await password2({
|
|
11757
|
+
message: "Sauce Labs access key:"
|
|
11758
|
+
});
|
|
11759
|
+
if (saucelabsBaseUrl && saucelabsUsername && saucelabsAccessKey) {
|
|
11760
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
11761
|
+
try {
|
|
11762
|
+
const tempConfig = {
|
|
11763
|
+
...loadConfig(),
|
|
11764
|
+
saucelabs: {
|
|
11765
|
+
baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
|
|
11766
|
+
username: saucelabsUsername.trim(),
|
|
11767
|
+
accessKey: saucelabsAccessKey
|
|
11768
|
+
}
|
|
11769
|
+
};
|
|
11770
|
+
await verifySauceLabsCredentials(createHttpClient(tempConfig));
|
|
11771
|
+
process.stderr.write(" Connected.\n");
|
|
11772
|
+
} catch (err) {
|
|
11773
|
+
warn(`Could not connect to Sauce Labs: ${err instanceof Error ? err.message : String(err)}`);
|
|
11774
|
+
warn("Config will be saved anyway. Check your data center URL, username, and access key and re-run pncli config init or pncli config test.");
|
|
11775
|
+
}
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11211
11778
|
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");
|
|
11212
11779
|
const jiraProject = await input2({
|
|
11213
11780
|
message: "Default Jira project key (optional):",
|
|
@@ -11358,6 +11925,12 @@ async function initGlobalConfig(start) {
|
|
|
11358
11925
|
token: logscaleToken || void 0
|
|
11359
11926
|
}
|
|
11360
11927
|
} : {},
|
|
11928
|
+
...useElasticsearch && elasticsearchBaseUrl ? {
|
|
11929
|
+
elasticsearch: {
|
|
11930
|
+
baseUrl: normalizeBaseUrl(elasticsearchBaseUrl),
|
|
11931
|
+
apiKey: elasticsearchApiKey || void 0
|
|
11932
|
+
}
|
|
11933
|
+
} : {},
|
|
11361
11934
|
...useSplitio && splitioBaseUrl ? {
|
|
11362
11935
|
splitio: {
|
|
11363
11936
|
baseUrl: normalizeBaseUrl(splitioBaseUrl),
|
|
@@ -11377,6 +11950,13 @@ async function initGlobalConfig(start) {
|
|
|
11377
11950
|
userId: alationUserId.trim() || void 0
|
|
11378
11951
|
}
|
|
11379
11952
|
} : {},
|
|
11953
|
+
...useSaucelabs && saucelabsBaseUrl ? {
|
|
11954
|
+
saucelabs: {
|
|
11955
|
+
baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
|
|
11956
|
+
username: saucelabsUsername.trim() || void 0,
|
|
11957
|
+
accessKey: saucelabsAccessKey || void 0
|
|
11958
|
+
}
|
|
11959
|
+
} : {},
|
|
11380
11960
|
defaults: {
|
|
11381
11961
|
jira: {
|
|
11382
11962
|
project: jiraProject || void 0
|
|
@@ -11818,6 +12398,7 @@ function registerDoctorCommands(program2) {
|
|
|
11818
12398
|
|
|
11819
12399
|
// src/services/ado/client/git.ts
|
|
11820
12400
|
var API7 = "7.1";
|
|
12401
|
+
var REQUIRED_REVIEWERS_POLICY_TYPE = "fd2167ab-b0be-447a-8ec8-39368250530e";
|
|
11821
12402
|
var AdoGitClient = class {
|
|
11822
12403
|
constructor(http) {
|
|
11823
12404
|
this.http = http;
|
|
@@ -11913,6 +12494,20 @@ var AdoGitClient = class {
|
|
|
11913
12494
|
const pr = await this.getPR(collection, project, repo, prId);
|
|
11914
12495
|
return pr.reviewers;
|
|
11915
12496
|
}
|
|
12497
|
+
/**
|
|
12498
|
+
* Default reviewers in Azure DevOps are "Required reviewers" branch policies.
|
|
12499
|
+
* Returns the policy configurations of that type whose scope covers the repo
|
|
12500
|
+
* (a scope entry with a null repositoryId applies project-wide).
|
|
12501
|
+
*/
|
|
12502
|
+
async listDefaultReviewers(collection, project, repo) {
|
|
12503
|
+
const repoInfo = await this.getRepo(collection, project, repo);
|
|
12504
|
+
const result = await this.http.ado(
|
|
12505
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/policy/configurations?api-version=${API7}`
|
|
12506
|
+
);
|
|
12507
|
+
return (result.value ?? []).filter(
|
|
12508
|
+
(c) => c.type?.id === REQUIRED_REVIEWERS_POLICY_TYPE && (c.settings?.scope ?? []).some((s) => !s.repositoryId || s.repositoryId === repoInfo.id)
|
|
12509
|
+
);
|
|
12510
|
+
}
|
|
11916
12511
|
// ── Threads / Comments ────────────────────────────────────────────
|
|
11917
12512
|
async listThreads(collection, project, repo, prId) {
|
|
11918
12513
|
const result = await this.http.ado(
|
|
@@ -11993,9 +12588,9 @@ var AdoBuildClient = class {
|
|
|
11993
12588
|
);
|
|
11994
12589
|
});
|
|
11995
12590
|
}
|
|
11996
|
-
async getDefinition(collection, project,
|
|
12591
|
+
async getDefinition(collection, project, id2) {
|
|
11997
12592
|
return this.http.ado(
|
|
11998
|
-
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${
|
|
12593
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id2}?api-version=${API8}`
|
|
11999
12594
|
);
|
|
12000
12595
|
}
|
|
12001
12596
|
// ── Builds (runs) ─────────────────────────────────────────────────
|
|
@@ -12605,7 +13200,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
12605
13200
|
const start = Date.now();
|
|
12606
13201
|
try {
|
|
12607
13202
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12608
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((
|
|
13203
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : [];
|
|
12609
13204
|
const data = await gitClient.createPR(collection, project, repo2, {
|
|
12610
13205
|
title: opts.title,
|
|
12611
13206
|
description: opts.description,
|
|
@@ -12623,7 +13218,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
12623
13218
|
const start = Date.now();
|
|
12624
13219
|
try {
|
|
12625
13220
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12626
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((
|
|
13221
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : void 0;
|
|
12627
13222
|
const body = {};
|
|
12628
13223
|
if (opts.title) body.title = opts.title;
|
|
12629
13224
|
if (opts.description !== void 0) body.description = opts.description;
|
|
@@ -12805,16 +13400,26 @@ function registerAdoRepoCommands(ado) {
|
|
|
12805
13400
|
fail(err, "ado", "repo-list-reviewers", start);
|
|
12806
13401
|
}
|
|
12807
13402
|
});
|
|
13403
|
+
repo.command("list-default-reviewers").description("List required-reviewer branch policies (default reviewers) that apply to the repository").action(async () => {
|
|
13404
|
+
const start = Date.now();
|
|
13405
|
+
try {
|
|
13406
|
+
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
13407
|
+
const data = await gitClient.listDefaultReviewers(collection, project, repo2);
|
|
13408
|
+
success(data, "ado", "repo-list-default-reviewers", start);
|
|
13409
|
+
} catch (err) {
|
|
13410
|
+
fail(err, "ado", "repo-list-default-reviewers", start);
|
|
13411
|
+
}
|
|
13412
|
+
});
|
|
12808
13413
|
const voteCmd = (name, description, vote, action) => {
|
|
12809
13414
|
repo.command(name).description(description).requiredOption("--pr <n>", "Pull request ID").action(async (opts) => {
|
|
12810
13415
|
const start = Date.now();
|
|
12811
13416
|
try {
|
|
12812
13417
|
const globalOpts = ado.optsWithGlobals();
|
|
12813
|
-
const { loadConfig: lc } = await import("./config-
|
|
13418
|
+
const { loadConfig: lc } = await import("./config-DYIXSPLA.js");
|
|
12814
13419
|
const config = lc({ configPath: globalOpts.config });
|
|
12815
13420
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12816
13421
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
12817
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
13422
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-3FAKF3X4.js");
|
|
12818
13423
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
12819
13424
|
const coreClient = new AdoCoreClient2(http);
|
|
12820
13425
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -13489,8 +14094,8 @@ var CheckmarxClient = class {
|
|
|
13489
14094
|
}
|
|
13490
14095
|
return projects;
|
|
13491
14096
|
}
|
|
13492
|
-
async getProject(
|
|
13493
|
-
return this.http.checkmarx(`projects/${
|
|
14097
|
+
async getProject(id2) {
|
|
14098
|
+
return this.http.checkmarx(`projects/${id2}`);
|
|
13494
14099
|
}
|
|
13495
14100
|
async listScans(opts = {}) {
|
|
13496
14101
|
const params = { limit: opts.last ?? 100 };
|
|
@@ -13503,8 +14108,8 @@ var CheckmarxClient = class {
|
|
|
13503
14108
|
}
|
|
13504
14109
|
return scans;
|
|
13505
14110
|
}
|
|
13506
|
-
async getScan(
|
|
13507
|
-
return this.http.checkmarx(`scans/${
|
|
14111
|
+
async getScan(id2) {
|
|
14112
|
+
return this.http.checkmarx(`scans/${id2}`);
|
|
13508
14113
|
}
|
|
13509
14114
|
async getScanResultsStatistics(scanId) {
|
|
13510
14115
|
return this.http.checkmarx("results/summary", {
|
|
@@ -13820,7 +14425,7 @@ function resolveCluster(config, env, instance) {
|
|
|
13820
14425
|
}
|
|
13821
14426
|
return { baseUrl: config.openshift.baseUrl, token: config.openshift.token };
|
|
13822
14427
|
}
|
|
13823
|
-
function
|
|
14428
|
+
function getHttp2(oc) {
|
|
13824
14429
|
const opts = oc.optsWithGlobals();
|
|
13825
14430
|
const config = loadConfig({ configPath: opts.config });
|
|
13826
14431
|
const cluster = resolveCluster(config, opts.env, opts.instance);
|
|
@@ -13896,7 +14501,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
13896
14501
|
oc.command("pods").description("List pod health summary for a namespace \u2014 pre-processed for LLM analysis. Use --raw to return the full unmodified Kubernetes pod objects.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--raw", "Return full Kubernetes pod objects without summarization (includes env vars, images, node name, annotations, resources, etc.)", false).action(async (opts) => {
|
|
13897
14502
|
const start = Date.now();
|
|
13898
14503
|
try {
|
|
13899
|
-
const http =
|
|
14504
|
+
const http = getHttp2(oc);
|
|
13900
14505
|
const params = {};
|
|
13901
14506
|
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
13902
14507
|
const podList = await http.openshift(
|
|
@@ -14002,10 +14607,59 @@ function registerOpenShiftCommands(program2) {
|
|
|
14002
14607
|
fail(err, "openshift", "pods", start);
|
|
14003
14608
|
}
|
|
14004
14609
|
});
|
|
14610
|
+
oc.command("deployments").description("List Deployments (or OpenShift DeploymentConfigs with --deployment-configs) for a namespace, read-only. Use --raw to return the full unmodified objects.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--deployment-configs", "Read OpenShift DeploymentConfigs (apps.openshift.io/v1) instead of Deployments (apps/v1)", false).option("--raw", "Return full objects without summarization (includes pod template, env vars, strategy, etc.)", false).action(async (opts) => {
|
|
14611
|
+
const start = Date.now();
|
|
14612
|
+
try {
|
|
14613
|
+
const http = getHttp2(oc);
|
|
14614
|
+
const params = {};
|
|
14615
|
+
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
14616
|
+
const kind = opts.deploymentConfigs ? "DeploymentConfig" : "Deployment";
|
|
14617
|
+
const path14 = opts.deploymentConfigs ? `/apis/apps.openshift.io/v1/namespaces/${encodeURIComponent(opts.namespace)}/deploymentconfigs` : `/apis/apps/v1/namespaces/${encodeURIComponent(opts.namespace)}/deployments`;
|
|
14618
|
+
const list = await http.openshift(path14, { params });
|
|
14619
|
+
if (opts.raw) {
|
|
14620
|
+
success(
|
|
14621
|
+
{ namespace: opts.namespace, kind, count: list.items.length, deployments: list.items },
|
|
14622
|
+
"openshift",
|
|
14623
|
+
"deployments",
|
|
14624
|
+
start
|
|
14625
|
+
);
|
|
14626
|
+
return;
|
|
14627
|
+
}
|
|
14628
|
+
const deployments = list.items.map((d) => {
|
|
14629
|
+
const desired = d.spec.replicas ?? 0;
|
|
14630
|
+
const status = d.status ?? {};
|
|
14631
|
+
const entry = {
|
|
14632
|
+
name: d.metadata.name,
|
|
14633
|
+
ready: `${status.readyReplicas ?? 0}/${desired}`,
|
|
14634
|
+
desiredReplicas: desired,
|
|
14635
|
+
readyReplicas: status.readyReplicas ?? 0,
|
|
14636
|
+
updatedReplicas: status.updatedReplicas ?? 0,
|
|
14637
|
+
availableReplicas: status.availableReplicas ?? 0,
|
|
14638
|
+
unavailableReplicas: status.unavailableReplicas ?? 0,
|
|
14639
|
+
age: podAge(d.metadata.creationTimestamp),
|
|
14640
|
+
images: (d.spec.template?.spec?.containers ?? []).map((c) => ({ name: c.name, image: c.image }))
|
|
14641
|
+
};
|
|
14642
|
+
if (d.spec.strategy?.type) entry["strategy"] = d.spec.strategy.type;
|
|
14643
|
+
if (status.latestVersion !== void 0) entry["latestVersion"] = status.latestVersion;
|
|
14644
|
+
const problems = (status.conditions ?? []).filter(
|
|
14645
|
+
(c) => c.type === "Available" && c.status === "False" || c.type === "Progressing" && c.status === "False" || c.type === "ReplicaFailure"
|
|
14646
|
+
);
|
|
14647
|
+
if (problems.length > 0) {
|
|
14648
|
+
entry["conditions"] = problems.map((c) => ({ type: c.type, status: c.status, reason: c.reason, message: c.message }));
|
|
14649
|
+
}
|
|
14650
|
+
if (d.metadata.labels && Object.keys(d.metadata.labels).length > 0) entry["labels"] = d.metadata.labels;
|
|
14651
|
+
return entry;
|
|
14652
|
+
});
|
|
14653
|
+
deployments.sort((a, b) => (b["unavailableReplicas"] ?? 0) - (a["unavailableReplicas"] ?? 0));
|
|
14654
|
+
success({ namespace: opts.namespace, kind, count: deployments.length, deployments }, "openshift", "deployments", start);
|
|
14655
|
+
} catch (err) {
|
|
14656
|
+
fail(err, "openshift", "deployments", start);
|
|
14657
|
+
}
|
|
14658
|
+
});
|
|
14005
14659
|
oc.command("events").description("List Warning events for a namespace \u2014 pre-filtered for LLM analysis").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--field-selector <selector>", "Additional field selector (e.g. involvedObject.name=my-pod)").option("--all", "Include Normal events in addition to Warning events", false).action(async (opts) => {
|
|
14006
14660
|
const start = Date.now();
|
|
14007
14661
|
try {
|
|
14008
|
-
const http =
|
|
14662
|
+
const http = getHttp2(oc);
|
|
14009
14663
|
const params = {};
|
|
14010
14664
|
const selectors = [];
|
|
14011
14665
|
if (!opts.all) selectors.push("type=Warning");
|
|
@@ -14039,7 +14693,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14039
14693
|
oc.command("logs").description("Get recent log lines from a pod container").requiredOption("--namespace <ns>", "Kubernetes namespace").requiredOption("--pod <name>", "Pod name").option("--container <name>", "Container name (defaults to first container)").option("--lines <n>", "Number of most-recent lines to return", "100").option("--previous", "Return logs from the previous container instance", false).action(async (opts) => {
|
|
14040
14694
|
const start = Date.now();
|
|
14041
14695
|
try {
|
|
14042
|
-
const http =
|
|
14696
|
+
const http = getHttp2(oc);
|
|
14043
14697
|
const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
|
|
14044
14698
|
const logText = await http.openshiftText(
|
|
14045
14699
|
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
|
|
@@ -14071,7 +14725,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14071
14725
|
oc.command("pod-metrics").description("Get CPU and memory usage for pods in a namespace (requires metrics-server)").requiredOption("--namespace <ns>", "Kubernetes namespace").action(async (opts) => {
|
|
14072
14726
|
const start = Date.now();
|
|
14073
14727
|
try {
|
|
14074
|
-
const http =
|
|
14728
|
+
const http = getHttp2(oc);
|
|
14075
14729
|
const metricsList = await http.openshift(
|
|
14076
14730
|
`/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
|
|
14077
14731
|
);
|
|
@@ -14091,7 +14745,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14091
14745
|
oc.command("resource-usage").description("Combined CPU/memory usage, limits, and requests for all pods in a namespace \u2014 all values normalized to millicores (m) and mebibytes (Mi). Requires metrics-server. Use --csv to emit a spreadsheet-friendly CSV.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--csv", "Output as CSV instead of JSON (suitable for Excel)", false).action(async (opts) => {
|
|
14092
14746
|
const start = Date.now();
|
|
14093
14747
|
try {
|
|
14094
|
-
const http =
|
|
14748
|
+
const http = getHttp2(oc);
|
|
14095
14749
|
const nsEncoded = encodeURIComponent(opts.namespace);
|
|
14096
14750
|
const podParams = {};
|
|
14097
14751
|
if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
|
|
@@ -14156,7 +14810,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14156
14810
|
}
|
|
14157
14811
|
|
|
14158
14812
|
// src/services/dynatrace/commands.ts
|
|
14159
|
-
function
|
|
14813
|
+
function getHttp3(program2, envName) {
|
|
14160
14814
|
const opts = program2.optsWithGlobals();
|
|
14161
14815
|
const cfg = loadConfig({ configPath: opts.config });
|
|
14162
14816
|
if (envName) {
|
|
@@ -14280,7 +14934,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14280
14934
|
const start = Date.now();
|
|
14281
14935
|
try {
|
|
14282
14936
|
const envName = dynatrace.opts().env;
|
|
14283
|
-
const data = await listEntities(
|
|
14937
|
+
const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
|
|
14284
14938
|
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
14285
14939
|
} catch (err) {
|
|
14286
14940
|
fail(err, "dynatrace", "entities list", start);
|
|
@@ -14290,7 +14944,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14290
14944
|
const start = Date.now();
|
|
14291
14945
|
try {
|
|
14292
14946
|
const envName = dynatrace.opts().env;
|
|
14293
|
-
const data = await
|
|
14947
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
14294
14948
|
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
14295
14949
|
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
14296
14950
|
);
|
|
@@ -14303,7 +14957,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14303
14957
|
const start = Date.now();
|
|
14304
14958
|
try {
|
|
14305
14959
|
const envName = dynatrace.opts().env;
|
|
14306
|
-
const data = await listEntities(
|
|
14960
|
+
const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
|
|
14307
14961
|
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
14308
14962
|
} catch (err) {
|
|
14309
14963
|
fail(err, "dynatrace", "services", start);
|
|
@@ -14314,7 +14968,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14314
14968
|
try {
|
|
14315
14969
|
const envName = dynatrace.opts().env;
|
|
14316
14970
|
const selector = 'type("CLOUD_APPLICATION")';
|
|
14317
|
-
const data = await listEntities(
|
|
14971
|
+
const data = await listEntities(getHttp3(program2, envName), selector, opts);
|
|
14318
14972
|
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
14319
14973
|
} catch (err) {
|
|
14320
14974
|
fail(err, "dynatrace", "workloads", start);
|
|
@@ -14326,7 +14980,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14326
14980
|
try {
|
|
14327
14981
|
const envName = dynatrace.opts().env;
|
|
14328
14982
|
const data = await allPages(async (nextPageKey) => {
|
|
14329
|
-
const page = await
|
|
14983
|
+
const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
|
|
14330
14984
|
params: nextPageKey ? { nextPageKey } : {
|
|
14331
14985
|
from: opts.from ?? "now-2h",
|
|
14332
14986
|
to: opts.to ?? "now",
|
|
@@ -14346,7 +15000,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14346
15000
|
const start = Date.now();
|
|
14347
15001
|
try {
|
|
14348
15002
|
const envName = dynatrace.opts().env;
|
|
14349
|
-
const data = await
|
|
15003
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
14350
15004
|
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
14351
15005
|
{ params: { fields: opts.fields } }
|
|
14352
15006
|
);
|
|
@@ -14359,7 +15013,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14359
15013
|
const start = Date.now();
|
|
14360
15014
|
try {
|
|
14361
15015
|
const envName = dynatrace.opts().env;
|
|
14362
|
-
const result = await runTraceQuery(
|
|
15016
|
+
const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
|
|
14363
15017
|
success(result, "dynatrace", "trace", start);
|
|
14364
15018
|
} catch (err) {
|
|
14365
15019
|
fail(err, "dynatrace", "trace", start);
|
|
@@ -14368,7 +15022,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14368
15022
|
}
|
|
14369
15023
|
|
|
14370
15024
|
// src/services/logscale/commands.ts
|
|
14371
|
-
function
|
|
15025
|
+
function getHttp4(program2) {
|
|
14372
15026
|
const opts = program2.optsWithGlobals();
|
|
14373
15027
|
return createHttpClient(
|
|
14374
15028
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14381,7 +15035,7 @@ function registerLogscaleCommands(program2) {
|
|
|
14381
15035
|
repositories.command("list").description("List available repositories").action(async () => {
|
|
14382
15036
|
const start = Date.now();
|
|
14383
15037
|
try {
|
|
14384
|
-
const data = await
|
|
15038
|
+
const data = await getHttp4(program2).logscale("/api/v1/repositories");
|
|
14385
15039
|
success({ count: data.length, repositories: data }, "logscale", "repositories list", start);
|
|
14386
15040
|
} catch (err) {
|
|
14387
15041
|
fail(err, "logscale", "repositories list", start);
|
|
@@ -14398,7 +15052,7 @@ function registerLogscaleCommands(program2) {
|
|
|
14398
15052
|
showQueryEventDistribution: false,
|
|
14399
15053
|
isLive: false
|
|
14400
15054
|
};
|
|
14401
|
-
const data = await
|
|
15055
|
+
const data = await getHttp4(program2).logscale(
|
|
14402
15056
|
`/api/v1/repositories/${encodeURIComponent(opts.repository)}/query`,
|
|
14403
15057
|
{
|
|
14404
15058
|
method: "POST",
|
|
@@ -14426,10 +15080,121 @@ function registerLogscaleCommands(program2) {
|
|
|
14426
15080
|
});
|
|
14427
15081
|
}
|
|
14428
15082
|
|
|
15083
|
+
// src/services/elasticsearch/commands.ts
|
|
15084
|
+
function getHttp5(program2) {
|
|
15085
|
+
const opts = program2.optsWithGlobals();
|
|
15086
|
+
return createHttpClient(
|
|
15087
|
+
loadConfig({ configPath: opts.config }),
|
|
15088
|
+
Boolean(opts.dryRun)
|
|
15089
|
+
);
|
|
15090
|
+
}
|
|
15091
|
+
function parseJsonOption(raw, flag) {
|
|
15092
|
+
let parsed;
|
|
15093
|
+
try {
|
|
15094
|
+
parsed = JSON.parse(raw);
|
|
15095
|
+
} catch {
|
|
15096
|
+
throw new PncliError(`${flag} must be valid JSON. Example: ${flag} '{"match_all":{}}'`);
|
|
15097
|
+
}
|
|
15098
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
15099
|
+
throw new PncliError(`${flag} must be a JSON object.`);
|
|
15100
|
+
}
|
|
15101
|
+
return parsed;
|
|
15102
|
+
}
|
|
15103
|
+
function parsePositiveInt(raw, flag) {
|
|
15104
|
+
const n = Number(raw);
|
|
15105
|
+
if (!Number.isInteger(n) || n < 0) throw new PncliError(`${flag} must be a non-negative integer.`);
|
|
15106
|
+
return n;
|
|
15107
|
+
}
|
|
15108
|
+
function indexPath(index) {
|
|
15109
|
+
return index.split(",").map((s) => encodeURIComponent(s.trim())).join(",");
|
|
15110
|
+
}
|
|
15111
|
+
function buildQuery(opts) {
|
|
15112
|
+
if (opts.query && opts.q) throw new PncliError("Use either --query or --q, not both.");
|
|
15113
|
+
if (opts.query) return parseJsonOption(opts.query, "--query");
|
|
15114
|
+
if (opts.q) return { query_string: { query: opts.q } };
|
|
15115
|
+
return { match_all: {} };
|
|
15116
|
+
}
|
|
15117
|
+
function registerElasticsearchCommands(program2) {
|
|
15118
|
+
const es = program2.command("elasticsearch").description("Elasticsearch search operations");
|
|
15119
|
+
const cluster = es.command("cluster").description("Cluster operations");
|
|
15120
|
+
cluster.command("health").description("Show cluster health (status, node and shard counts)").action(async () => {
|
|
15121
|
+
const start = Date.now();
|
|
15122
|
+
try {
|
|
15123
|
+
const data = await getHttp5(program2).elasticsearch("/_cluster/health");
|
|
15124
|
+
success(data, "elasticsearch", "cluster health", start);
|
|
15125
|
+
} catch (err) {
|
|
15126
|
+
fail(err, "elasticsearch", "cluster health", start);
|
|
15127
|
+
}
|
|
15128
|
+
});
|
|
15129
|
+
const indices = es.command("indices").description("Index operations");
|
|
15130
|
+
indices.command("list").description("List indices with health, document count and size").option("--pattern <pattern>", 'Index name or wildcard pattern (e.g. "logs-*")').action(async (opts) => {
|
|
15131
|
+
const start = Date.now();
|
|
15132
|
+
try {
|
|
15133
|
+
const path14 = opts.pattern ? `/_cat/indices/${indexPath(opts.pattern)}` : "/_cat/indices";
|
|
15134
|
+
const data = await getHttp5(program2).elasticsearch(path14, {
|
|
15135
|
+
params: { format: "json", h: "index,health,status,docs.count,store.size,pri,rep" }
|
|
15136
|
+
});
|
|
15137
|
+
success({ count: data.length, indices: data }, "elasticsearch", "indices list", start);
|
|
15138
|
+
} catch (err) {
|
|
15139
|
+
fail(err, "elasticsearch", "indices list", start);
|
|
15140
|
+
}
|
|
15141
|
+
});
|
|
15142
|
+
es.command("search").description("Search one or more indices with Query DSL JSON or a query string").requiredOption("--index <name>", "Index, data stream, alias or wildcard pattern; comma-separate several").option("--query <json>", `Query DSL clause as JSON (e.g. '{"match":{"message":"error"}}')`).option("--q <query-string>", 'Lucene query string (e.g. "level:error AND service:api")').option("--sort <field:order>", 'Sort, e.g. "@timestamp:desc"').option("--source <fields>", "Comma-separated fields to return (_source filtering)").option("--size <n>", "Maximum number of hits to return", "100").option("--from <n>", "Offset of the first hit", "0").option("--timeout-ms <ms>", "Client-side HTTP timeout in milliseconds", "30000").action(async (opts) => {
|
|
15143
|
+
const ts = Date.now();
|
|
15144
|
+
try {
|
|
15145
|
+
const body = {
|
|
15146
|
+
query: buildQuery(opts),
|
|
15147
|
+
size: parsePositiveInt(opts.size, "--size"),
|
|
15148
|
+
from: parsePositiveInt(opts.from, "--from")
|
|
15149
|
+
};
|
|
15150
|
+
if (opts.sort) {
|
|
15151
|
+
const [field, order] = opts.sort.split(":");
|
|
15152
|
+
body.sort = [{ [field]: { order: order ?? "asc" } }];
|
|
15153
|
+
}
|
|
15154
|
+
if (opts.source) body._source = opts.source.split(",").map((s) => s.trim()).filter(Boolean);
|
|
15155
|
+
const data = await getHttp5(program2).elasticsearch(
|
|
15156
|
+
`/${indexPath(opts.index)}/_search`,
|
|
15157
|
+
{ method: "POST", body, timeoutMs: parsePositiveInt(opts.timeoutMs, "--timeout-ms") }
|
|
15158
|
+
);
|
|
15159
|
+
const total = data.hits?.total;
|
|
15160
|
+
const hits = data.hits?.hits ?? [];
|
|
15161
|
+
success(
|
|
15162
|
+
{
|
|
15163
|
+
index: opts.index,
|
|
15164
|
+
took: data.took,
|
|
15165
|
+
timedOut: data.timed_out ?? false,
|
|
15166
|
+
total: typeof total === "object" ? total?.value : total,
|
|
15167
|
+
totalRelation: typeof total === "object" ? total?.relation : void 0,
|
|
15168
|
+
hitCount: hits.length,
|
|
15169
|
+
hits,
|
|
15170
|
+
aggregations: data.aggregations
|
|
15171
|
+
},
|
|
15172
|
+
"elasticsearch",
|
|
15173
|
+
"search",
|
|
15174
|
+
ts
|
|
15175
|
+
);
|
|
15176
|
+
} catch (err) {
|
|
15177
|
+
fail(err, "elasticsearch", "search", ts);
|
|
15178
|
+
}
|
|
15179
|
+
});
|
|
15180
|
+
es.command("count").description("Count documents matching a query").requiredOption("--index <name>", "Index, data stream, alias or wildcard pattern; comma-separate several").option("--query <json>", "Query DSL clause as JSON").option("--q <query-string>", "Lucene query string").action(async (opts) => {
|
|
15181
|
+
const start = Date.now();
|
|
15182
|
+
try {
|
|
15183
|
+
const data = await getHttp5(program2).elasticsearch(
|
|
15184
|
+
`/${indexPath(opts.index)}/_count`,
|
|
15185
|
+
{ method: "POST", body: { query: buildQuery(opts) } }
|
|
15186
|
+
);
|
|
15187
|
+
success({ index: opts.index, count: data.count }, "elasticsearch", "count", start);
|
|
15188
|
+
} catch (err) {
|
|
15189
|
+
fail(err, "elasticsearch", "count", start);
|
|
15190
|
+
}
|
|
15191
|
+
});
|
|
15192
|
+
}
|
|
15193
|
+
|
|
14429
15194
|
// src/services/splitio/commands.ts
|
|
14430
15195
|
import fs11 from "fs";
|
|
14431
15196
|
import confirm2 from "@inquirer/confirm";
|
|
14432
|
-
function
|
|
15197
|
+
function getHttp6(program2) {
|
|
14433
15198
|
const opts = program2.optsWithGlobals();
|
|
14434
15199
|
return createHttpClient(
|
|
14435
15200
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14464,7 +15229,7 @@ function buildCrBase(opts) {
|
|
|
14464
15229
|
crNumber: opts.changeNumber
|
|
14465
15230
|
};
|
|
14466
15231
|
if (opts.approvers) {
|
|
14467
|
-
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((
|
|
15232
|
+
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id2) => ({ id: id2 }));
|
|
14468
15233
|
}
|
|
14469
15234
|
return base;
|
|
14470
15235
|
}
|
|
@@ -14474,7 +15239,7 @@ function registerSplitioCommands(program2) {
|
|
|
14474
15239
|
workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
|
|
14475
15240
|
const start = Date.now();
|
|
14476
15241
|
try {
|
|
14477
|
-
const http =
|
|
15242
|
+
const http = getHttp6(program2);
|
|
14478
15243
|
const data = await http.splitio("/internal/api/v2/workspaces");
|
|
14479
15244
|
success(
|
|
14480
15245
|
{ count: data.objects.length, workspaces: data.objects },
|
|
@@ -14490,7 +15255,7 @@ function registerSplitioCommands(program2) {
|
|
|
14490
15255
|
environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
|
|
14491
15256
|
const start = Date.now();
|
|
14492
15257
|
try {
|
|
14493
|
-
const http =
|
|
15258
|
+
const http = getHttp6(program2);
|
|
14494
15259
|
const data = await http.splitio(
|
|
14495
15260
|
`/internal/api/v2/environments/ws/${encodeURIComponent(opts.workspace)}`
|
|
14496
15261
|
);
|
|
@@ -14508,7 +15273,7 @@ function registerSplitioCommands(program2) {
|
|
|
14508
15273
|
flags.command("list").description("List feature flags in a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--limit <n>", "Maximum number of flags to return", "50").option("--offset <n>", "Pagination offset", "0").option("--flag-set <name>", "Filter by flag set name").action(async (opts) => {
|
|
14509
15274
|
const start = Date.now();
|
|
14510
15275
|
try {
|
|
14511
|
-
const http =
|
|
15276
|
+
const http = getHttp6(program2);
|
|
14512
15277
|
const params = {
|
|
14513
15278
|
limit: parseInt(opts.limit, 10),
|
|
14514
15279
|
offset: parseInt(opts.offset, 10)
|
|
@@ -14531,7 +15296,7 @@ function registerSplitioCommands(program2) {
|
|
|
14531
15296
|
flags.command("get").description("Get a flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").option("--environment <id>", "Environment ID \u2014 include targeting rules for this environment").action(async (opts) => {
|
|
14532
15297
|
const start = Date.now();
|
|
14533
15298
|
try {
|
|
14534
|
-
const http =
|
|
15299
|
+
const http = getHttp6(program2);
|
|
14535
15300
|
if (opts.environment) {
|
|
14536
15301
|
const data = await http.splitio(
|
|
14537
15302
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
@@ -14553,7 +15318,7 @@ function registerSplitioCommands(program2) {
|
|
|
14553
15318
|
if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
|
|
14554
15319
|
const definition = loadInputFile(opts.inputFile);
|
|
14555
15320
|
await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
|
|
14556
|
-
const http =
|
|
15321
|
+
const http = getHttp6(program2);
|
|
14557
15322
|
const body = {
|
|
14558
15323
|
...buildCrBase(opts),
|
|
14559
15324
|
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
@@ -14579,7 +15344,7 @@ function registerSplitioCommands(program2) {
|
|
|
14579
15344
|
if (!opts.treatment && !opts.weights) {
|
|
14580
15345
|
throw new PncliError("Either --treatment or --weights must be specified");
|
|
14581
15346
|
}
|
|
14582
|
-
const http =
|
|
15347
|
+
const http = getHttp6(program2);
|
|
14583
15348
|
const current = await http.splitio(
|
|
14584
15349
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
14585
15350
|
);
|
|
@@ -14617,7 +15382,7 @@ function registerSplitioCommands(program2) {
|
|
|
14617
15382
|
flags.command("set-default").description("Update the fallback default rule for traffic matching no targeting rule").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--treatment <name>", "Treatment to assign as the default").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "Change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
|
|
14618
15383
|
const start = Date.now();
|
|
14619
15384
|
try {
|
|
14620
|
-
const http =
|
|
15385
|
+
const http = getHttp6(program2);
|
|
14621
15386
|
const current = await http.splitio(
|
|
14622
15387
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
14623
15388
|
);
|
|
@@ -14650,7 +15415,7 @@ function registerSplitioCommands(program2) {
|
|
|
14650
15415
|
const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
|
|
14651
15416
|
const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
14652
15417
|
await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
|
|
14653
|
-
const http =
|
|
15418
|
+
const http = getHttp6(program2);
|
|
14654
15419
|
const body = {
|
|
14655
15420
|
...buildCrBase(opts),
|
|
14656
15421
|
operationType: opType,
|
|
@@ -14673,7 +15438,7 @@ function registerSplitioCommands(program2) {
|
|
|
14673
15438
|
const entries = loadInputFile(opts.inputFile);
|
|
14674
15439
|
if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
|
|
14675
15440
|
await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
|
|
14676
|
-
const http =
|
|
15441
|
+
const http = getHttp6(program2);
|
|
14677
15442
|
const results = [];
|
|
14678
15443
|
for (const entry of entries) {
|
|
14679
15444
|
try {
|
|
@@ -14707,7 +15472,7 @@ function registerSplitioCommands(program2) {
|
|
|
14707
15472
|
const start = Date.now();
|
|
14708
15473
|
try {
|
|
14709
15474
|
await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
|
|
14710
|
-
const http =
|
|
15475
|
+
const http = getHttp6(program2);
|
|
14711
15476
|
const body = {
|
|
14712
15477
|
...buildCrBase(opts),
|
|
14713
15478
|
operationType: "KILL_SPLIT",
|
|
@@ -14727,7 +15492,7 @@ function registerSplitioCommands(program2) {
|
|
|
14727
15492
|
const start = Date.now();
|
|
14728
15493
|
try {
|
|
14729
15494
|
await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
|
|
14730
|
-
const http =
|
|
15495
|
+
const http = getHttp6(program2);
|
|
14731
15496
|
const body = {
|
|
14732
15497
|
...buildCrBase(opts),
|
|
14733
15498
|
operationType: "RESTORE_SPLIT",
|
|
@@ -14747,7 +15512,7 @@ function registerSplitioCommands(program2) {
|
|
|
14747
15512
|
const start = Date.now();
|
|
14748
15513
|
try {
|
|
14749
15514
|
await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
|
|
14750
|
-
const http =
|
|
15515
|
+
const http = getHttp6(program2);
|
|
14751
15516
|
const body = {
|
|
14752
15517
|
...buildCrBase(opts),
|
|
14753
15518
|
operationType: "ARCHIVE_SPLIT",
|
|
@@ -14767,7 +15532,7 @@ function registerSplitioCommands(program2) {
|
|
|
14767
15532
|
changeRequests.command("list").description("List Change Requests for a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--environment <id>", "Filter by environment ID").option("--status <status>", "Filter by status (REQUESTED, APPROVED, REJECTED, PUBLISHED)").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
|
|
14768
15533
|
const start = Date.now();
|
|
14769
15534
|
try {
|
|
14770
|
-
const http =
|
|
15535
|
+
const http = getHttp6(program2);
|
|
14771
15536
|
const params = {
|
|
14772
15537
|
limit: parseInt(opts.limit, 10),
|
|
14773
15538
|
offset: parseInt(opts.offset, 10)
|
|
@@ -14791,7 +15556,7 @@ function registerSplitioCommands(program2) {
|
|
|
14791
15556
|
changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
|
|
14792
15557
|
const start = Date.now();
|
|
14793
15558
|
try {
|
|
14794
|
-
const http =
|
|
15559
|
+
const http = getHttp6(program2);
|
|
14795
15560
|
const data = await http.splitio(
|
|
14796
15561
|
`/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
|
|
14797
15562
|
);
|
|
@@ -14843,7 +15608,7 @@ function findNodeIdInUrl(url) {
|
|
|
14843
15608
|
if (!new URL(url).searchParams.has("node-id")) return void 0;
|
|
14844
15609
|
return parseNodeId(url);
|
|
14845
15610
|
}
|
|
14846
|
-
function
|
|
15611
|
+
function getHttp7(program2) {
|
|
14847
15612
|
const opts = program2.optsWithGlobals();
|
|
14848
15613
|
return createHttpClient(
|
|
14849
15614
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14859,7 +15624,7 @@ function registerFigmaCommands(program2) {
|
|
|
14859
15624
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14860
15625
|
const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
|
|
14861
15626
|
if (nodeId) {
|
|
14862
|
-
const data2 = await
|
|
15627
|
+
const data2 = await getHttp7(program2).figma(
|
|
14863
15628
|
`/v1/files/${encodeURIComponent(fileKey)}/nodes`,
|
|
14864
15629
|
{ params: { ids: nodeId } }
|
|
14865
15630
|
);
|
|
@@ -14888,7 +15653,7 @@ function registerFigmaCommands(program2) {
|
|
|
14888
15653
|
if (!opts.document) {
|
|
14889
15654
|
params["depth"] = 1;
|
|
14890
15655
|
}
|
|
14891
|
-
const data = await
|
|
15656
|
+
const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
|
|
14892
15657
|
success2(
|
|
14893
15658
|
{
|
|
14894
15659
|
fileKey,
|
|
@@ -14918,7 +15683,7 @@ function registerFigmaCommands(program2) {
|
|
|
14918
15683
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14919
15684
|
const params = {};
|
|
14920
15685
|
if (opts.asOf) params["as_of"] = opts.asOf;
|
|
14921
|
-
const data = await
|
|
15686
|
+
const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
|
|
14922
15687
|
success2(
|
|
14923
15688
|
{ fileKey, count: data.comments.length, comments: data.comments },
|
|
14924
15689
|
"figma",
|
|
@@ -14934,7 +15699,7 @@ function registerFigmaCommands(program2) {
|
|
|
14934
15699
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14935
15700
|
try {
|
|
14936
15701
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14937
|
-
const data = await
|
|
15702
|
+
const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
|
|
14938
15703
|
success2(
|
|
14939
15704
|
{ fileKey, count: data.versions.length, versions: data.versions },
|
|
14940
15705
|
"figma",
|
|
@@ -14949,7 +15714,7 @@ function registerFigmaCommands(program2) {
|
|
|
14949
15714
|
const start = Date.now();
|
|
14950
15715
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14951
15716
|
try {
|
|
14952
|
-
const data = await
|
|
15717
|
+
const data = await getHttp7(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
|
|
14953
15718
|
success2(
|
|
14954
15719
|
{ projectId, count: data.files.length, files: data.files },
|
|
14955
15720
|
"figma",
|
|
@@ -14964,7 +15729,7 @@ function registerFigmaCommands(program2) {
|
|
|
14964
15729
|
const start = Date.now();
|
|
14965
15730
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14966
15731
|
try {
|
|
14967
|
-
const data = await
|
|
15732
|
+
const data = await getHttp7(program2).figma("/v1/me");
|
|
14968
15733
|
success2(data, "figma", "me", start);
|
|
14969
15734
|
} catch (err) {
|
|
14970
15735
|
fail2(err, "figma", "me", start);
|
|
@@ -14975,7 +15740,7 @@ function registerFigmaCommands(program2) {
|
|
|
14975
15740
|
// src/services/alation/commands.ts
|
|
14976
15741
|
import fs12 from "fs";
|
|
14977
15742
|
var MAX_PAGE = 1e3;
|
|
14978
|
-
function
|
|
15743
|
+
function parseIntOption2(value, flag) {
|
|
14979
15744
|
if (value === void 0) return void 0;
|
|
14980
15745
|
const trimmed = value.trim();
|
|
14981
15746
|
if (!/^\d+$/.test(trimmed)) {
|
|
@@ -14991,8 +15756,8 @@ function parsePositiveId(value, what) {
|
|
|
14991
15756
|
return Number(trimmed);
|
|
14992
15757
|
}
|
|
14993
15758
|
function pageParams(opts) {
|
|
14994
|
-
const limit =
|
|
14995
|
-
const skip =
|
|
15759
|
+
const limit = parseIntOption2(opts.limit, "--limit") ?? 100;
|
|
15760
|
+
const skip = parseIntOption2(opts.skip, "--skip") ?? 0;
|
|
14996
15761
|
if (limit < 1 || limit > MAX_PAGE) {
|
|
14997
15762
|
throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
|
|
14998
15763
|
}
|
|
@@ -15079,17 +15844,17 @@ var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
|
|
|
15079
15844
|
template: 7,
|
|
15080
15845
|
fields: { "10001": "Team Data Platform" }
|
|
15081
15846
|
};
|
|
15082
|
-
function
|
|
15847
|
+
function getHttp8(program2) {
|
|
15083
15848
|
const opts = program2.optsWithGlobals();
|
|
15084
15849
|
return createHttpClient(
|
|
15085
15850
|
loadConfig({ configPath: opts.config }),
|
|
15086
15851
|
Boolean(opts.dryRun)
|
|
15087
15852
|
);
|
|
15088
15853
|
}
|
|
15089
|
-
async function getOne(http, path14,
|
|
15090
|
-
const rows = await http.alation(path14, { params: { id, limit: 1 } });
|
|
15091
|
-
const row = Array.isArray(rows) ? rows.find((r) => r.id ===
|
|
15092
|
-
if (!row) throw new PncliError(`${what} ${
|
|
15854
|
+
async function getOne(http, path14, id2, what) {
|
|
15855
|
+
const rows = await http.alation(path14, { params: { id: id2, limit: 1 } });
|
|
15856
|
+
const row = Array.isArray(rows) ? rows.find((r) => r.id === id2) ?? rows[0] : void 0;
|
|
15857
|
+
if (!row) throw new PncliError(`${what} ${id2} not found`, 404);
|
|
15093
15858
|
return row;
|
|
15094
15859
|
}
|
|
15095
15860
|
function buildDocumentPayload(opts) {
|
|
@@ -15155,17 +15920,17 @@ function registerAlationCommands(program2) {
|
|
|
15155
15920
|
const params = {};
|
|
15156
15921
|
if (opts.includeUndeployed) params.include_undeployed = true;
|
|
15157
15922
|
if (opts.includeHidden) params.include_hidden = true;
|
|
15158
|
-
const data = await
|
|
15923
|
+
const data = await getHttp8(program2).alation("integration/v1/datasource/", { params });
|
|
15159
15924
|
success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
|
|
15160
15925
|
} catch (err) {
|
|
15161
15926
|
fail(err, "alation", "datasource-list", start);
|
|
15162
15927
|
}
|
|
15163
15928
|
});
|
|
15164
|
-
datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (
|
|
15929
|
+
datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id2) => {
|
|
15165
15930
|
const start = Date.now();
|
|
15166
15931
|
try {
|
|
15167
|
-
const dsId = parsePositiveId(
|
|
15168
|
-
const data = await
|
|
15932
|
+
const dsId = parsePositiveId(id2, "data source ID");
|
|
15933
|
+
const data = await getHttp8(program2).alation(`integration/v1/datasource/${dsId}/`);
|
|
15169
15934
|
success(data, "alation", "datasource-get", start);
|
|
15170
15935
|
} catch (err) {
|
|
15171
15936
|
fail(err, "alation", "datasource-get", start);
|
|
@@ -15178,16 +15943,16 @@ function registerAlationCommands(program2) {
|
|
|
15178
15943
|
const params = { ...pageParams(opts) };
|
|
15179
15944
|
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
15180
15945
|
if (opts.name) params.name = opts.name;
|
|
15181
|
-
const data = await
|
|
15946
|
+
const data = await getHttp8(program2).alation("integration/v2/schema/", { params });
|
|
15182
15947
|
success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
|
|
15183
15948
|
} catch (err) {
|
|
15184
15949
|
fail(err, "alation", "schema-list", start);
|
|
15185
15950
|
}
|
|
15186
15951
|
});
|
|
15187
|
-
schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (
|
|
15952
|
+
schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id2) => {
|
|
15188
15953
|
const start = Date.now();
|
|
15189
15954
|
try {
|
|
15190
|
-
const data = await getOne(
|
|
15955
|
+
const data = await getOne(getHttp8(program2), "integration/v2/schema/", parsePositiveId(id2, "schema ID"), "Schema");
|
|
15191
15956
|
success(data, "alation", "schema-get", start);
|
|
15192
15957
|
} catch (err) {
|
|
15193
15958
|
fail(err, "alation", "schema-get", start);
|
|
@@ -15203,17 +15968,17 @@ function registerAlationCommands(program2) {
|
|
|
15203
15968
|
if (opts.schemaName) params.schema_name = opts.schemaName;
|
|
15204
15969
|
if (opts.name) params.name = opts.name;
|
|
15205
15970
|
if (opts.search) params.name__icontains = opts.search;
|
|
15206
|
-
const data = await
|
|
15971
|
+
const data = await getHttp8(program2).alation("integration/v2/table/", { params });
|
|
15207
15972
|
success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
|
|
15208
15973
|
} catch (err) {
|
|
15209
15974
|
fail(err, "alation", "table-list", start);
|
|
15210
15975
|
}
|
|
15211
15976
|
});
|
|
15212
|
-
table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (
|
|
15977
|
+
table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (id2, opts) => {
|
|
15213
15978
|
const start = Date.now();
|
|
15214
15979
|
try {
|
|
15215
|
-
const http =
|
|
15216
|
-
const tableId = parsePositiveId(
|
|
15980
|
+
const http = getHttp8(program2);
|
|
15981
|
+
const tableId = parsePositiveId(id2, "table ID");
|
|
15217
15982
|
const data = await getOne(http, "integration/v2/table/", tableId, "Table");
|
|
15218
15983
|
if (opts.columns) {
|
|
15219
15984
|
const columns = await http.alation("integration/v2/column/", {
|
|
@@ -15241,16 +16006,16 @@ function registerAlationCommands(program2) {
|
|
|
15241
16006
|
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
15242
16007
|
if (opts.name) params.name = opts.name;
|
|
15243
16008
|
if (opts.search) params.name__icontains = opts.search;
|
|
15244
|
-
const data = await
|
|
16009
|
+
const data = await getHttp8(program2).alation("integration/v2/column/", { params });
|
|
15245
16010
|
success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
|
|
15246
16011
|
} catch (err) {
|
|
15247
16012
|
fail(err, "alation", "column-list", start);
|
|
15248
16013
|
}
|
|
15249
16014
|
});
|
|
15250
|
-
column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (
|
|
16015
|
+
column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id2) => {
|
|
15251
16016
|
const start = Date.now();
|
|
15252
16017
|
try {
|
|
15253
|
-
const data = await getOne(
|
|
16018
|
+
const data = await getOne(getHttp8(program2), "integration/v2/column/", parsePositiveId(id2, "column ID"), "Column");
|
|
15254
16019
|
success(data, "alation", "column-get", start);
|
|
15255
16020
|
} catch (err) {
|
|
15256
16021
|
fail(err, "alation", "column-get", start);
|
|
@@ -15259,13 +16024,13 @@ function registerAlationCommands(program2) {
|
|
|
15259
16024
|
alation.command("search").description("Full-text search across the catalog").argument("<query>", "Search text").option("--otype <types>", `Comma-separated object types to restrict to (e.g. table,column). Valid: ${SEARCH_OTYPES.join(", ")}`).option("--limit <n>", "Maximum results (default 20, max 10000)", "20").option("--offset <n>", "Index of the first result", "0").action(async (query, opts) => {
|
|
15260
16025
|
const start = Date.now();
|
|
15261
16026
|
try {
|
|
15262
|
-
const limit =
|
|
15263
|
-
const offset =
|
|
16027
|
+
const limit = parseIntOption2(opts.limit, "--limit") ?? 20;
|
|
16028
|
+
const offset = parseIntOption2(opts.offset, "--offset") ?? 0;
|
|
15264
16029
|
if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
|
|
15265
16030
|
const params = { q: query, limit, offset };
|
|
15266
16031
|
const otypes = parseOtypes(opts.otype);
|
|
15267
16032
|
if (otypes) params.filters = JSON.stringify({ otypes });
|
|
15268
|
-
const data = await
|
|
16033
|
+
const data = await getHttp8(program2).alation("integration/v1/search/", { params });
|
|
15269
16034
|
success(
|
|
15270
16035
|
{ query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
|
|
15271
16036
|
"alation",
|
|
@@ -15284,16 +16049,16 @@ function registerAlationCommands(program2) {
|
|
|
15284
16049
|
if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
|
|
15285
16050
|
if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
|
|
15286
16051
|
if (opts.search) params.search = opts.search;
|
|
15287
|
-
const data = await
|
|
16052
|
+
const data = await getHttp8(program2).alation("integration/v2/folder/", { params });
|
|
15288
16053
|
success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
|
|
15289
16054
|
} catch (err) {
|
|
15290
16055
|
fail(err, "alation", "folder-list", start);
|
|
15291
16056
|
}
|
|
15292
16057
|
});
|
|
15293
|
-
folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (
|
|
16058
|
+
folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id2) => {
|
|
15294
16059
|
const start = Date.now();
|
|
15295
16060
|
try {
|
|
15296
|
-
const data = await getOne(
|
|
16061
|
+
const data = await getOne(getHttp8(program2), "integration/v2/folder/", parsePositiveId(id2, "folder ID"), "Folder");
|
|
15297
16062
|
success(data, "alation", "folder-get", start);
|
|
15298
16063
|
} catch (err) {
|
|
15299
16064
|
fail(err, "alation", "folder-get", start);
|
|
@@ -15309,16 +16074,16 @@ function registerAlationCommands(program2) {
|
|
|
15309
16074
|
if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
|
|
15310
16075
|
if (opts.search) params.search = opts.search;
|
|
15311
16076
|
if (opts.deleted) params.deleted = true;
|
|
15312
|
-
const data = await
|
|
16077
|
+
const data = await getHttp8(program2).alation("integration/v2/document/", { params });
|
|
15313
16078
|
success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
|
|
15314
16079
|
} catch (err) {
|
|
15315
16080
|
fail(err, "alation", "document-list", start);
|
|
15316
16081
|
}
|
|
15317
16082
|
});
|
|
15318
|
-
document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (
|
|
16083
|
+
document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id2) => {
|
|
15319
16084
|
const start = Date.now();
|
|
15320
16085
|
try {
|
|
15321
|
-
const data = await getOne(
|
|
16086
|
+
const data = await getOne(getHttp8(program2), "integration/v2/document/", parsePositiveId(id2, "document ID"), "Document");
|
|
15322
16087
|
success(data, "alation", "document-get", start);
|
|
15323
16088
|
} catch (err) {
|
|
15324
16089
|
fail(err, "alation", "document-get", start);
|
|
@@ -15339,16 +16104,16 @@ function registerAlationCommands(program2) {
|
|
|
15339
16104
|
for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
|
|
15340
16105
|
if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
|
|
15341
16106
|
}
|
|
15342
|
-
const data = await
|
|
16107
|
+
const data = await getHttp8(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
|
|
15343
16108
|
success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
|
|
15344
16109
|
} catch (err) {
|
|
15345
16110
|
fail(err, "alation", "document-create", start);
|
|
15346
16111
|
}
|
|
15347
16112
|
});
|
|
15348
|
-
document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (
|
|
16113
|
+
document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (id2, opts) => {
|
|
15349
16114
|
const start = Date.now();
|
|
15350
16115
|
try {
|
|
15351
|
-
const documentId = parsePositiveId(
|
|
16116
|
+
const documentId = parsePositiveId(id2, "document ID");
|
|
15352
16117
|
const { payload, overrides } = buildDocumentPayload(opts);
|
|
15353
16118
|
if (payload.document_hub_id !== void 0) {
|
|
15354
16119
|
throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
|
|
@@ -15357,18 +16122,18 @@ function registerAlationCommands(program2) {
|
|
|
15357
16122
|
throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
|
|
15358
16123
|
}
|
|
15359
16124
|
const body = [{ id: documentId, ...payload }];
|
|
15360
|
-
const data = await
|
|
16125
|
+
const data = await getHttp8(program2).alation("integration/v2/document/", { method: "PUT", body });
|
|
15361
16126
|
success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
|
|
15362
16127
|
} catch (err) {
|
|
15363
16128
|
fail(err, "alation", "document-update", start);
|
|
15364
16129
|
}
|
|
15365
16130
|
});
|
|
15366
16131
|
const job = alation.command("job").description("Background job operations");
|
|
15367
|
-
job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (
|
|
16132
|
+
job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id2) => {
|
|
15368
16133
|
const start = Date.now();
|
|
15369
16134
|
try {
|
|
15370
|
-
const jobId = parsePositiveId(
|
|
15371
|
-
const data = await
|
|
16135
|
+
const jobId = parsePositiveId(id2, "job ID");
|
|
16136
|
+
const data = await getHttp8(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
|
|
15372
16137
|
success({ jobId, ...data }, "alation", "job-get", start);
|
|
15373
16138
|
} catch (err) {
|
|
15374
16139
|
fail(err, "alation", "job-get", start);
|
|
@@ -15377,9 +16142,7 @@ function registerAlationCommands(program2) {
|
|
|
15377
16142
|
}
|
|
15378
16143
|
|
|
15379
16144
|
// src/cli.ts
|
|
15380
|
-
|
|
15381
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
15382
|
-
}
|
|
16145
|
+
configureTls();
|
|
15383
16146
|
await configureProxy();
|
|
15384
16147
|
var TAGLINE = "Connectivity without MCP.";
|
|
15385
16148
|
var program = new Command3();
|
|
@@ -15419,9 +16182,11 @@ registerJwtCommands(program);
|
|
|
15419
16182
|
registerOpenShiftCommands(program);
|
|
15420
16183
|
registerDynatraceCommands(program);
|
|
15421
16184
|
registerLogscaleCommands(program);
|
|
16185
|
+
registerElasticsearchCommands(program);
|
|
15422
16186
|
registerSplitioCommands(program);
|
|
15423
16187
|
registerFigmaCommands(program);
|
|
15424
16188
|
registerAlationCommands(program);
|
|
16189
|
+
registerSauceLabsCommands(program);
|
|
15425
16190
|
program.addHelpText("after", `
|
|
15426
16191
|
Services:
|
|
15427
16192
|
git Local git operations (status, diff, log, branch)
|
|
@@ -15438,12 +16203,14 @@ Services:
|
|
|
15438
16203
|
checkmarx Checkmarx One (projects, scans, scan statistics)
|
|
15439
16204
|
contrast Contrast IAST (applications, vulnerability findings, libraries)
|
|
15440
16205
|
sonatypeiq Sonatype IQ Server (applications, organizations, policies)
|
|
15441
|
-
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
16206
|
+
openshift OpenShift / Kubernetes (pods, deployments, events, logs, metrics)
|
|
15442
16207
|
dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
|
|
15443
16208
|
logscale LogScale (log query, repositories)
|
|
16209
|
+
elasticsearch Elasticsearch (search, count, indices, cluster health)
|
|
15444
16210
|
splitio Split.IO (feature flags, Change Requests)
|
|
15445
16211
|
figma Figma (files, comments, version history)
|
|
15446
16212
|
alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
|
|
16213
|
+
saucelabs Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms)
|
|
15447
16214
|
config Manage pncli configuration (and OS keychain storage for credentials)
|
|
15448
16215
|
doctor Diagnose pncli setup (config, credentials, keychain, git auth, agent skills, marketplaces)
|
|
15449
16216
|
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|