@kolatts/pncli 5.4.0 → 6.0.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 +1 -0
- package/dist/{chunk-2J2HAFKO.js → chunk-4FR4WGNR.js} +100 -13
- package/dist/chunk-4FR4WGNR.js.map +1 -0
- package/dist/{chunk-L6OY2546.js → chunk-WDWAPTVC.js} +20 -3
- package/dist/chunk-WDWAPTVC.js.map +1 -0
- package/dist/cli.js +628 -132
- package/dist/cli.js.map +1 -1
- package/dist/{config-OH2O5E3P.js → config-5LC7ZTEP.js} +2 -2
- package/dist/{http-V7PZD7LE.js → http-DJSGEGD6.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +18 -4
- 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-5LC7ZTEP.js.map} +0 -0
- /package/dist/{http-V7PZD7LE.js.map → http-DJSGEGD6.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-WDWAPTVC.js";
|
|
32
32
|
import {
|
|
33
|
+
configureTls,
|
|
33
34
|
createHttpClient,
|
|
35
|
+
describeFetchError,
|
|
34
36
|
validateAlationAccessToken
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-4FR4WGNR.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
|
}
|
|
@@ -2885,9 +2887,9 @@ function parseFieldsFile(filePath, fieldMap) {
|
|
|
2885
2887
|
}
|
|
2886
2888
|
function translateFieldErrors(err, fieldMap) {
|
|
2887
2889
|
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} (${
|
|
2890
|
+
const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id2) => {
|
|
2891
|
+
const def = fieldMap.byId.get(id2);
|
|
2892
|
+
return def ? `${def.name} (${id2})` : id2;
|
|
2891
2893
|
});
|
|
2892
2894
|
if (translated === err.message) return err;
|
|
2893
2895
|
return new PncliError(translated, err.status, err.url);
|
|
@@ -4255,8 +4257,8 @@ var ConfluenceClient = class {
|
|
|
4255
4257
|
this.http = http;
|
|
4256
4258
|
}
|
|
4257
4259
|
http;
|
|
4258
|
-
async getPage(
|
|
4259
|
-
return this.http.confluence(`${API3}/content/${
|
|
4260
|
+
async getPage(id2, expand = "body.storage,version,space,ancestors") {
|
|
4261
|
+
return this.http.confluence(`${API3}/content/${id2}`, {
|
|
4260
4262
|
params: { expand }
|
|
4261
4263
|
});
|
|
4262
4264
|
}
|
|
@@ -4274,16 +4276,16 @@ var ConfluenceClient = class {
|
|
|
4274
4276
|
});
|
|
4275
4277
|
}, opts.limit);
|
|
4276
4278
|
}
|
|
4277
|
-
async getPageChildren(
|
|
4279
|
+
async getPageChildren(id2) {
|
|
4278
4280
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
4279
|
-
return this.http.confluence(`${API3}/content/${
|
|
4281
|
+
return this.http.confluence(`${API3}/content/${id2}/child/page`, {
|
|
4280
4282
|
params: { expand: "version", start, limit }
|
|
4281
4283
|
});
|
|
4282
4284
|
});
|
|
4283
4285
|
}
|
|
4284
|
-
async getLabels(
|
|
4286
|
+
async getLabels(id2) {
|
|
4285
4287
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
4286
|
-
return this.http.confluence(`${API3}/content/${
|
|
4288
|
+
return this.http.confluence(`${API3}/content/${id2}/label`, {
|
|
4287
4289
|
params: { start, limit }
|
|
4288
4290
|
});
|
|
4289
4291
|
});
|
|
@@ -4316,7 +4318,7 @@ var ConfluenceClient = class {
|
|
|
4316
4318
|
body
|
|
4317
4319
|
});
|
|
4318
4320
|
}
|
|
4319
|
-
async updatePage(
|
|
4321
|
+
async updatePage(id2, opts) {
|
|
4320
4322
|
const body = {
|
|
4321
4323
|
version: { number: opts.version },
|
|
4322
4324
|
type: "page",
|
|
@@ -4331,13 +4333,13 @@ var ConfluenceClient = class {
|
|
|
4331
4333
|
}
|
|
4332
4334
|
};
|
|
4333
4335
|
}
|
|
4334
|
-
return this.http.confluence(`${API3}/content/${
|
|
4336
|
+
return this.http.confluence(`${API3}/content/${id2}`, {
|
|
4335
4337
|
method: "PUT",
|
|
4336
4338
|
body
|
|
4337
4339
|
});
|
|
4338
4340
|
}
|
|
4339
|
-
async deletePage(
|
|
4340
|
-
return this.http.confluence(`${API3}/content/${
|
|
4341
|
+
async deletePage(id2) {
|
|
4342
|
+
return this.http.confluence(`${API3}/content/${id2}`, { method: "DELETE" });
|
|
4341
4343
|
}
|
|
4342
4344
|
async listComments(pageId) {
|
|
4343
4345
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
@@ -4422,8 +4424,8 @@ var ConfluenceClient = class {
|
|
|
4422
4424
|
async deleteAttachment(attachmentId) {
|
|
4423
4425
|
return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
|
|
4424
4426
|
}
|
|
4425
|
-
async getPageHistory(
|
|
4426
|
-
return this.http.confluence(`${API3}/content/${
|
|
4427
|
+
async getPageHistory(id2) {
|
|
4428
|
+
return this.http.confluence(`${API3}/content/${id2}/history`);
|
|
4427
4429
|
}
|
|
4428
4430
|
async convertToStorage(value, fromRepresentation = "markdown") {
|
|
4429
4431
|
const result = await this.http.confluence(
|
|
@@ -5115,9 +5117,9 @@ function resolveProject2(config, cliProject) {
|
|
|
5115
5117
|
const project = cliProject ?? config.defaults.sde.project;
|
|
5116
5118
|
if (!project) throw new PncliError("No project specified. Use --project or set defaults.sde.project in config.");
|
|
5117
5119
|
if (!/^\d+$/.test(project)) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
5118
|
-
const
|
|
5119
|
-
if (
|
|
5120
|
-
return
|
|
5120
|
+
const id2 = parseInt(project, 10);
|
|
5121
|
+
if (id2 <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
|
|
5122
|
+
return id2;
|
|
5121
5123
|
}
|
|
5122
5124
|
function parsePage(val, flag) {
|
|
5123
5125
|
const n = parseInt(val, 10);
|
|
@@ -6045,7 +6047,7 @@ async function checkOsvConnectivity() {
|
|
|
6045
6047
|
} catch (err) {
|
|
6046
6048
|
return {
|
|
6047
6049
|
reachable: false,
|
|
6048
|
-
error:
|
|
6050
|
+
error: describeFetchError(err)
|
|
6049
6051
|
};
|
|
6050
6052
|
}
|
|
6051
6053
|
}
|
|
@@ -6250,7 +6252,7 @@ async function checkSonatypeConnectivity() {
|
|
|
6250
6252
|
} catch (err) {
|
|
6251
6253
|
return {
|
|
6252
6254
|
reachable: false,
|
|
6253
|
-
error:
|
|
6255
|
+
error: describeFetchError(err)
|
|
6254
6256
|
};
|
|
6255
6257
|
}
|
|
6256
6258
|
}
|
|
@@ -6314,10 +6316,10 @@ function mapViolationToVuln(violation) {
|
|
|
6314
6316
|
const cveRefs = violation.constraintViolations.flatMap(
|
|
6315
6317
|
(cv) => cv.reasons.filter((r) => r.reference?.type === "VULNERABILITY").map((r) => r.reference.value)
|
|
6316
6318
|
);
|
|
6317
|
-
const
|
|
6319
|
+
const id2 = cveRefs[0] ?? violation.policyId;
|
|
6318
6320
|
const severity = threatLevelToSeverity(violation.policyThreatLevel);
|
|
6319
6321
|
return {
|
|
6320
|
-
id,
|
|
6322
|
+
id: id2,
|
|
6321
6323
|
summary: violation.policyName,
|
|
6322
6324
|
severity,
|
|
6323
6325
|
cvss: null,
|
|
@@ -6473,7 +6475,7 @@ async function checkSonatypeIqConnectivity(baseUrl, userCode, passcode) {
|
|
|
6473
6475
|
return {
|
|
6474
6476
|
reachable: false,
|
|
6475
6477
|
authenticated: false,
|
|
6476
|
-
error:
|
|
6478
|
+
error: describeFetchError(err)
|
|
6477
6479
|
};
|
|
6478
6480
|
}
|
|
6479
6481
|
}
|
|
@@ -6533,7 +6535,7 @@ async function checkArtifactoryConnectivity(config) {
|
|
|
6533
6535
|
reachable: false,
|
|
6534
6536
|
authenticated: false,
|
|
6535
6537
|
configured: true,
|
|
6536
|
-
error:
|
|
6538
|
+
error: describeFetchError(err)
|
|
6537
6539
|
};
|
|
6538
6540
|
}
|
|
6539
6541
|
}
|
|
@@ -7080,9 +7082,9 @@ var AdoWorkClient = class {
|
|
|
7080
7082
|
this.http = http;
|
|
7081
7083
|
}
|
|
7082
7084
|
http;
|
|
7083
|
-
async getWorkItem(collection,
|
|
7085
|
+
async getWorkItem(collection, id2) {
|
|
7084
7086
|
return this.http.ado(
|
|
7085
|
-
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${
|
|
7087
|
+
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}&$expand=all`
|
|
7086
7088
|
);
|
|
7087
7089
|
}
|
|
7088
7090
|
async createWorkItem(collection, project, type, patch) {
|
|
@@ -7091,9 +7093,9 @@ var AdoWorkClient = class {
|
|
|
7091
7093
|
{ method: "POST", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
|
|
7092
7094
|
);
|
|
7093
7095
|
}
|
|
7094
|
-
async updateWorkItem(collection,
|
|
7096
|
+
async updateWorkItem(collection, id2, patch) {
|
|
7095
7097
|
return this.http.ado(
|
|
7096
|
-
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${
|
|
7098
|
+
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}`,
|
|
7097
7099
|
{ method: "PATCH", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
|
|
7098
7100
|
);
|
|
7099
7101
|
}
|
|
@@ -7115,23 +7117,23 @@ var AdoWorkClient = class {
|
|
|
7115
7117
|
{ method: "POST", body: { text } }
|
|
7116
7118
|
);
|
|
7117
7119
|
}
|
|
7118
|
-
async addTags(collection,
|
|
7119
|
-
const item = await this.getWorkItem(collection,
|
|
7120
|
+
async addTags(collection, id2, tags) {
|
|
7121
|
+
const item = await this.getWorkItem(collection, id2);
|
|
7120
7122
|
const existing = item.fields["System.Tags"] ?? "";
|
|
7121
7123
|
const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
|
|
7122
7124
|
const lowerSet = new Set(current.map((t) => t.toLowerCase()));
|
|
7123
7125
|
const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
|
|
7124
7126
|
const merged = [...current, ...toAdd];
|
|
7125
7127
|
const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
|
|
7126
|
-
return this.updateWorkItem(collection,
|
|
7128
|
+
return this.updateWorkItem(collection, id2, patch);
|
|
7127
7129
|
}
|
|
7128
|
-
async removeTags(collection,
|
|
7129
|
-
const item = await this.getWorkItem(collection,
|
|
7130
|
+
async removeTags(collection, id2, tags) {
|
|
7131
|
+
const item = await this.getWorkItem(collection, id2);
|
|
7130
7132
|
const existing = item.fields["System.Tags"] ?? "";
|
|
7131
7133
|
const lower = new Set(tags.map((t) => t.toLowerCase()));
|
|
7132
7134
|
const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
|
|
7133
7135
|
const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
|
|
7134
|
-
return this.updateWorkItem(collection,
|
|
7136
|
+
return this.updateWorkItem(collection, id2, patch);
|
|
7135
7137
|
}
|
|
7136
7138
|
async listWorkItemTypes(collection, project) {
|
|
7137
7139
|
const result = await this.http.ado(
|
|
@@ -7272,6 +7274,422 @@ function buildDefaultAliases(fields) {
|
|
|
7272
7274
|
return aliases;
|
|
7273
7275
|
}
|
|
7274
7276
|
|
|
7277
|
+
// src/services/saucelabs/commands.ts
|
|
7278
|
+
var SERVICE = "saucelabs";
|
|
7279
|
+
var RDA = "/rdc/v2";
|
|
7280
|
+
var TEAM_MGMT = "/team-management/v1";
|
|
7281
|
+
var BUILD_SOURCES = ["vdc", "rdc"];
|
|
7282
|
+
var BUILD_STATUSES = ["running", "error", "failed", "complete", "success"];
|
|
7283
|
+
var JOB_VISIBILITY = ["public", "public restricted", "share", "team", "private"];
|
|
7284
|
+
var PLATFORM_APIS = ["all", "appium", "webdriver"];
|
|
7285
|
+
var DEVICE_OS = ["ANDROID", "IOS"];
|
|
7286
|
+
var DEVICE_TYPES = ["PHONE", "TABLET"];
|
|
7287
|
+
var DEVICE_STATES = ["AVAILABLE", "IN_USE", "CLEANING", "MAINTENANCE", "REBOOTING", "OFFLINE"];
|
|
7288
|
+
var SESSION_STATES = ["PENDING", "CREATING", "ACTIVE", "CLOSING", "CLOSED", "ERRORED"];
|
|
7289
|
+
var ORIENTATIONS = ["PORTRAIT", "LANDSCAPE"];
|
|
7290
|
+
var STORAGE_KINDS = ["android", "ios"];
|
|
7291
|
+
function getHttp(program2) {
|
|
7292
|
+
const opts = program2.optsWithGlobals();
|
|
7293
|
+
return createHttpClient(
|
|
7294
|
+
loadConfig({ configPath: opts.config }),
|
|
7295
|
+
Boolean(opts.dryRun)
|
|
7296
|
+
);
|
|
7297
|
+
}
|
|
7298
|
+
async function verifySauceLabsCredentials(http, timeoutMs) {
|
|
7299
|
+
return http.saucelabs(
|
|
7300
|
+
`/rest/v1.2/users/${encodeURIComponent(http.saucelabsUsername())}/concurrency`,
|
|
7301
|
+
timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
7302
|
+
);
|
|
7303
|
+
}
|
|
7304
|
+
function parseIntOption(value, flag) {
|
|
7305
|
+
if (value === void 0) return void 0;
|
|
7306
|
+
const trimmed = value.trim();
|
|
7307
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
7308
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
|
|
7309
|
+
}
|
|
7310
|
+
return Number(trimmed);
|
|
7311
|
+
}
|
|
7312
|
+
function parseUnixTime(value, flag) {
|
|
7313
|
+
if (value === void 0) return void 0;
|
|
7314
|
+
const trimmed = value.trim();
|
|
7315
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
7316
|
+
const ms = Date.parse(trimmed);
|
|
7317
|
+
if (!Number.isFinite(ms)) {
|
|
7318
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected Unix seconds or an ISO 8601 date such as 2026-09-01T00:00:00Z.`, 1);
|
|
7319
|
+
}
|
|
7320
|
+
return Math.floor(ms / 1e3);
|
|
7321
|
+
}
|
|
7322
|
+
function parseChoice(value, choices, flag) {
|
|
7323
|
+
if (value === void 0) return void 0;
|
|
7324
|
+
const match = choices.find((c) => c.toLowerCase() === value.trim().toLowerCase());
|
|
7325
|
+
if (!match) {
|
|
7326
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected one of: ${choices.join(", ")}.`, 1);
|
|
7327
|
+
}
|
|
7328
|
+
return match;
|
|
7329
|
+
}
|
|
7330
|
+
function collectList(value, previous = []) {
|
|
7331
|
+
return [...previous, ...value.split(",").map((v) => v.trim()).filter(Boolean)];
|
|
7332
|
+
}
|
|
7333
|
+
function withRepeatedParam(path14, key, values) {
|
|
7334
|
+
if (!values || values.length === 0) return path14;
|
|
7335
|
+
const qs = values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join("&");
|
|
7336
|
+
return `${path14}${path14.includes("?") ? "&" : "?"}${qs}`;
|
|
7337
|
+
}
|
|
7338
|
+
function passedFlag(opts) {
|
|
7339
|
+
if (opts.passed && opts.failed) throw new PncliError("Pass only one of --passed or --failed.", 1);
|
|
7340
|
+
if (opts.passed) return true;
|
|
7341
|
+
if (opts.failed) return false;
|
|
7342
|
+
return void 0;
|
|
7343
|
+
}
|
|
7344
|
+
function requireBody(body, hint) {
|
|
7345
|
+
if (Object.keys(body).length === 0) throw new PncliError(`Nothing to update. ${hint}`, 1);
|
|
7346
|
+
return body;
|
|
7347
|
+
}
|
|
7348
|
+
function id(value) {
|
|
7349
|
+
return encodeURIComponent(value.trim());
|
|
7350
|
+
}
|
|
7351
|
+
function countOf(data, key) {
|
|
7352
|
+
const list = key ? data?.[key] : data;
|
|
7353
|
+
return Array.isArray(list) ? list.length : void 0;
|
|
7354
|
+
}
|
|
7355
|
+
function registerSauceLabsCommands(program2) {
|
|
7356
|
+
function run(action, fn) {
|
|
7357
|
+
return async (...args) => {
|
|
7358
|
+
const start = Date.now();
|
|
7359
|
+
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
7360
|
+
try {
|
|
7361
|
+
success2(await fn(getHttp(program2), ...args), SERVICE, action, start);
|
|
7362
|
+
} catch (err) {
|
|
7363
|
+
fail2(err, SERVICE, action, start);
|
|
7364
|
+
}
|
|
7365
|
+
};
|
|
7366
|
+
}
|
|
7367
|
+
const sauce = program2.command("saucelabs").description("Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms, app storage, teams)");
|
|
7368
|
+
sauce.command("status").description("Sauce Labs service status (operational flag, wait time)").action(run("status", (http) => http.saucelabs("/rest/v1/info/status")));
|
|
7369
|
+
sauce.command("concurrency").description("Concurrency allowed and in use for your organization and team (also verifies credentials)").action(run("concurrency", (http) => verifySauceLabsCredentials(http)));
|
|
7370
|
+
sauce.command("platforms").description("Supported OS/browser/device platforms").option("--api <api>", "Automation API: all, appium, webdriver", "all").action(run("platforms", async (http, opts) => {
|
|
7371
|
+
const api = parseChoice(opts.api, PLATFORM_APIS, "--api");
|
|
7372
|
+
const data = await http.saucelabs(`/rest/v1/info/platforms/${api}`);
|
|
7373
|
+
return { api, count: countOf(data), platforms: data };
|
|
7374
|
+
}));
|
|
7375
|
+
const job = sauce.command("job").description("Test jobs on Sauce virtual devices and desktop browsers (VDC)");
|
|
7376
|
+
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) => {
|
|
7377
|
+
const params = {
|
|
7378
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7379
|
+
skip: parseIntOption(opts.skip, "--skip"),
|
|
7380
|
+
from: parseUnixTime(opts.from, "--from"),
|
|
7381
|
+
to: parseUnixTime(opts.to, "--to")
|
|
7382
|
+
};
|
|
7383
|
+
const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs`, { params });
|
|
7384
|
+
return { count: countOf(data), jobs: data };
|
|
7385
|
+
}));
|
|
7386
|
+
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)}`)));
|
|
7387
|
+
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) => {
|
|
7388
|
+
const body = {};
|
|
7389
|
+
if (opts.name !== void 0) body["name"] = opts.name;
|
|
7390
|
+
if (opts.build !== void 0) body["build"] = opts.build;
|
|
7391
|
+
if (opts.tags !== void 0) body["tags"] = opts.tags;
|
|
7392
|
+
const passed = passedFlag(opts);
|
|
7393
|
+
if (passed !== void 0) body["passed"] = passed;
|
|
7394
|
+
const visibility = parseChoice(opts.public, JOB_VISIBILITY, "--public");
|
|
7395
|
+
if (visibility !== void 0) body["public"] = visibility;
|
|
7396
|
+
return http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, {
|
|
7397
|
+
method: "PUT",
|
|
7398
|
+
body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed, --public.")
|
|
7399
|
+
});
|
|
7400
|
+
}));
|
|
7401
|
+
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" })));
|
|
7402
|
+
job.command("delete").description("Delete a job and all of its assets").argument("<job-id>", "Job ID").action(run("job-delete", async (http, jobId) => {
|
|
7403
|
+
await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, { method: "DELETE" });
|
|
7404
|
+
return { jobId, deleted: true };
|
|
7405
|
+
}));
|
|
7406
|
+
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) => {
|
|
7407
|
+
const data = await http.saucelabs(
|
|
7408
|
+
`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/assets`
|
|
7409
|
+
);
|
|
7410
|
+
return { jobId, assets: data };
|
|
7411
|
+
}));
|
|
7412
|
+
const rdcJob = sauce.command("rdc-job").description("Test jobs that ran on real devices");
|
|
7413
|
+
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", {
|
|
7414
|
+
params: {
|
|
7415
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7416
|
+
offset: parseIntOption(opts.offset, "--offset"),
|
|
7417
|
+
type: opts.live ? "LIVE" : void 0
|
|
7418
|
+
}
|
|
7419
|
+
})));
|
|
7420
|
+
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)}`)));
|
|
7421
|
+
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) => {
|
|
7422
|
+
const body = {};
|
|
7423
|
+
if (opts.name !== void 0) body["name"] = opts.name;
|
|
7424
|
+
if (opts.build !== void 0) body["build"] = opts.build;
|
|
7425
|
+
if (opts.tags !== void 0) body["tags"] = opts.tags;
|
|
7426
|
+
const passed = passedFlag(opts);
|
|
7427
|
+
if (passed !== void 0) body["passed"] = passed;
|
|
7428
|
+
return http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, {
|
|
7429
|
+
method: "PUT",
|
|
7430
|
+
body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed.")
|
|
7431
|
+
});
|
|
7432
|
+
}));
|
|
7433
|
+
rdcJob.command("stop").description("Stop a running real-device job").argument("<job-id>", "Job ID").action(run("rdc-job-stop", async (http, jobId) => {
|
|
7434
|
+
await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}/stop`, { method: "PUT" });
|
|
7435
|
+
return { jobId, stopped: true };
|
|
7436
|
+
}));
|
|
7437
|
+
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) => {
|
|
7438
|
+
await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, { method: "DELETE" });
|
|
7439
|
+
return { jobId, deleted: true };
|
|
7440
|
+
}));
|
|
7441
|
+
const build = sauce.command("build").description("Builds (groups of jobs) on virtual (vdc) or real (rdc) devices");
|
|
7442
|
+
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) => {
|
|
7443
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7444
|
+
const statuses = opts.status?.map((s) => parseChoice(s, BUILD_STATUSES, "--status"));
|
|
7445
|
+
const data = await http.saucelabs(withRepeatedParam(`/v2/builds/${source}/`, "status", statuses), {
|
|
7446
|
+
params: {
|
|
7447
|
+
name: opts.name,
|
|
7448
|
+
start: parseUnixTime(opts.start, "--start"),
|
|
7449
|
+
end: parseUnixTime(opts.end, "--end"),
|
|
7450
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7451
|
+
offset: parseIntOption(opts.offset, "--offset"),
|
|
7452
|
+
sort: parseChoice(opts.sort, ["asc", "desc"], "--sort")
|
|
7453
|
+
}
|
|
7454
|
+
});
|
|
7455
|
+
return { source, count: countOf(data, "builds"), builds: data?.builds ?? [] };
|
|
7456
|
+
}));
|
|
7457
|
+
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) => {
|
|
7458
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7459
|
+
return http.saucelabs(`/v2/builds/${source}/${id(buildId)}/`);
|
|
7460
|
+
}));
|
|
7461
|
+
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) => {
|
|
7462
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7463
|
+
const params = { modified_since: opts["modifiedSince"] };
|
|
7464
|
+
for (const flag of ["failed", "errored", "passed", "running", "queued", "completed", "finished", "faulty"]) {
|
|
7465
|
+
if (opts[flag]) params[flag] = true;
|
|
7466
|
+
}
|
|
7467
|
+
const data = await http.saucelabs(`/v2/builds/${source}/${id(buildId)}/jobs/`, { params });
|
|
7468
|
+
return { buildId, source, count: countOf(data, "jobs"), jobs: data?.jobs ?? [] };
|
|
7469
|
+
}));
|
|
7470
|
+
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) => {
|
|
7471
|
+
const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
|
|
7472
|
+
return http.saucelabs(`/v2/builds/${source}/jobs/${id(jobId)}/build/`);
|
|
7473
|
+
}));
|
|
7474
|
+
const device = sauce.command("device").description("Real-device catalog and live availability");
|
|
7475
|
+
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) => {
|
|
7476
|
+
const data = await http.saucelabs(`${RDA}/devices`, {
|
|
7477
|
+
params: {
|
|
7478
|
+
deviceName: opts.name,
|
|
7479
|
+
os: parseChoice(opts.os, DEVICE_OS, "--os"),
|
|
7480
|
+
osVersion: opts.osVersion,
|
|
7481
|
+
deviceType: parseChoice(opts.type, DEVICE_TYPES, "--type")
|
|
7482
|
+
}
|
|
7483
|
+
});
|
|
7484
|
+
return { count: countOf(data), devices: data };
|
|
7485
|
+
}));
|
|
7486
|
+
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)}`)));
|
|
7487
|
+
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) => {
|
|
7488
|
+
const data = await http.saucelabs(`${RDA}/devices/status`, {
|
|
7489
|
+
params: {
|
|
7490
|
+
state: parseChoice(opts.state, DEVICE_STATES, "--state"),
|
|
7491
|
+
deviceName: opts.name,
|
|
7492
|
+
privateOnly: opts.privateOnly ? true : void 0
|
|
7493
|
+
}
|
|
7494
|
+
});
|
|
7495
|
+
return { count: countOf(data, "devices"), devices: data?.devices ?? [] };
|
|
7496
|
+
}));
|
|
7497
|
+
const session = sauce.command("session").description("Reserve a real device and drive it over the API (open URLs, install apps, ADB shell, tests)");
|
|
7498
|
+
const sessionPath = (sessionId) => `${RDA}/sessions/${id(sessionId)}`;
|
|
7499
|
+
const deviceAction = (sessionId, action) => `${sessionPath(sessionId)}/device/${action}`;
|
|
7500
|
+
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) => {
|
|
7501
|
+
const data = await http.saucelabs(`${RDA}/sessions`, {
|
|
7502
|
+
params: { state: parseChoice(opts.state, SESSION_STATES, "--state"), deviceName: opts.deviceName }
|
|
7503
|
+
});
|
|
7504
|
+
return { count: countOf(data, "sessions"), sessions: data?.sessions ?? [] };
|
|
7505
|
+
}));
|
|
7506
|
+
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) => {
|
|
7507
|
+
if (opts.tunnelOwner && !opts.tunnelName) throw new PncliError("--tunnel-owner requires --tunnel-name.", 1);
|
|
7508
|
+
const timeoutSeconds = parseIntOption(opts.timeout, "--timeout") ?? 300;
|
|
7509
|
+
const deviceQuery = {};
|
|
7510
|
+
if (opts.deviceName) deviceQuery["deviceName"] = opts.deviceName;
|
|
7511
|
+
const os4 = parseChoice(opts.os, ["android", "ios"], "--os");
|
|
7512
|
+
if (os4) deviceQuery["os"] = os4;
|
|
7513
|
+
const configuration = {};
|
|
7514
|
+
if (opts.tunnelName) {
|
|
7515
|
+
configuration["tunnel"] = { name: opts.tunnelName, ...opts.tunnelOwner ? { owner: opts.tunnelOwner } : {} };
|
|
7516
|
+
}
|
|
7517
|
+
if (opts.duration) configuration["sessionDuration"] = opts.duration;
|
|
7518
|
+
const body = { device: deviceQuery };
|
|
7519
|
+
if (Object.keys(configuration).length > 0) body["configuration"] = configuration;
|
|
7520
|
+
const created = await http.saucelabs(`${RDA}/sessions`, { method: "POST", body });
|
|
7521
|
+
if (!opts.wait) return created;
|
|
7522
|
+
return waitForSession(http, created.id, timeoutSeconds);
|
|
7523
|
+
}));
|
|
7524
|
+
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))));
|
|
7525
|
+
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), {
|
|
7526
|
+
method: "DELETE",
|
|
7527
|
+
params: { rebootDevice: opts.reboot ? true : void 0 }
|
|
7528
|
+
})));
|
|
7529
|
+
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) => {
|
|
7530
|
+
await http.saucelabs(deviceAction(sessionId, "openUrl"), { method: "POST", body: { url } });
|
|
7531
|
+
return { sessionId, url, opened: true };
|
|
7532
|
+
}));
|
|
7533
|
+
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) => {
|
|
7534
|
+
const data = await http.saucelabs(deviceAction(sessionId, "executeShellCommand"), {
|
|
7535
|
+
method: "POST",
|
|
7536
|
+
body: { adbShellCommand: command },
|
|
7537
|
+
timeoutMs: 12e4
|
|
7538
|
+
});
|
|
7539
|
+
return { sessionId, command, stdout: data?.stdout ?? "" };
|
|
7540
|
+
}));
|
|
7541
|
+
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"), {
|
|
7542
|
+
method: "POST",
|
|
7543
|
+
body: { app, enableInstrumentation: opts.instrumentation, launchAfterInstall: Boolean(opts.launch) }
|
|
7544
|
+
})));
|
|
7545
|
+
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" })));
|
|
7546
|
+
const appIdentity = (opts) => {
|
|
7547
|
+
if (Boolean(opts.bundleId) === Boolean(opts.packageName)) {
|
|
7548
|
+
throw new PncliError("Pass exactly one of --bundle-id (iOS) or --package-name (Android).", 1);
|
|
7549
|
+
}
|
|
7550
|
+
return opts.bundleId ? { bundleId: opts.bundleId } : { packageName: opts.packageName };
|
|
7551
|
+
};
|
|
7552
|
+
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) => {
|
|
7553
|
+
if (opts.activity && !opts.packageName) throw new PncliError("--activity requires --package-name.", 1);
|
|
7554
|
+
const body = { ...appIdentity(opts), ...opts.activity ? { activityName: opts.activity } : {} };
|
|
7555
|
+
await http.saucelabs(deviceAction(sessionId, "launchApp"), { method: "POST", body });
|
|
7556
|
+
return { sessionId, ...body, launched: true };
|
|
7557
|
+
}));
|
|
7558
|
+
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) => {
|
|
7559
|
+
const body = appIdentity(opts);
|
|
7560
|
+
await http.saucelabs(deviceAction(sessionId, "uninstallApp"), { method: "POST", body });
|
|
7561
|
+
return { sessionId, ...body, uninstalled: true };
|
|
7562
|
+
}));
|
|
7563
|
+
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) => {
|
|
7564
|
+
const body = {};
|
|
7565
|
+
const orientation = parseChoice(opts.orientation, ORIENTATIONS, "--orientation");
|
|
7566
|
+
if (orientation) body["orientation"] = orientation;
|
|
7567
|
+
if (opts.locale) body["locale"] = opts.locale;
|
|
7568
|
+
const animations = parseChoice(opts.animations, ["on", "off"], "--animations");
|
|
7569
|
+
if (animations) body["animations"] = animations === "on";
|
|
7570
|
+
await http.saucelabs(deviceAction(sessionId, "applySettings"), {
|
|
7571
|
+
method: "POST",
|
|
7572
|
+
body: requireBody(body, "Pass at least one of --orientation, --locale, --animations.")
|
|
7573
|
+
});
|
|
7574
|
+
return { sessionId, applied: body };
|
|
7575
|
+
}));
|
|
7576
|
+
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) => {
|
|
7577
|
+
const body = {};
|
|
7578
|
+
if (opts.name) body["testName"] = opts.name;
|
|
7579
|
+
if (opts.build) body["build"] = opts.build;
|
|
7580
|
+
if (opts.tags) body["tags"] = opts.tags;
|
|
7581
|
+
const artifacts = {
|
|
7582
|
+
video: Boolean(opts.video),
|
|
7583
|
+
deviceLogs: Boolean(opts.deviceLogs),
|
|
7584
|
+
networkCapture: Boolean(opts.networkCapture),
|
|
7585
|
+
screenshots: Boolean(opts.screenshots),
|
|
7586
|
+
appiumLogs: Boolean(opts.appiumLogs)
|
|
7587
|
+
};
|
|
7588
|
+
if (Object.values(artifacts).some(Boolean)) body["artifacts"] = artifacts;
|
|
7589
|
+
return http.saucelabs(`${sessionPath(sessionId)}/startTest`, { method: "POST", body });
|
|
7590
|
+
}));
|
|
7591
|
+
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) => {
|
|
7592
|
+
const passed = passedFlag(opts);
|
|
7593
|
+
return http.saucelabs(`${sessionPath(sessionId)}/endTest`, {
|
|
7594
|
+
method: "POST",
|
|
7595
|
+
body: passed === void 0 ? {} : { status: passed ? "passed" : "failed" }
|
|
7596
|
+
});
|
|
7597
|
+
}));
|
|
7598
|
+
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`)));
|
|
7599
|
+
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`)));
|
|
7600
|
+
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) => {
|
|
7601
|
+
const conditions = {};
|
|
7602
|
+
const set = (key, value, flag, max) => {
|
|
7603
|
+
const n = parseIntOption(value, flag);
|
|
7604
|
+
if (n === void 0) return;
|
|
7605
|
+
if (n > max) throw new PncliError(`${flag} must be between 0 and ${max}.`, 1);
|
|
7606
|
+
conditions[key] = n;
|
|
7607
|
+
};
|
|
7608
|
+
set("downloadSpeed", opts.download, "--download", 5e4);
|
|
7609
|
+
set("uploadSpeed", opts.upload, "--upload", 5e4);
|
|
7610
|
+
set("latency", opts.latency, "--latency", 3e3);
|
|
7611
|
+
set("loss", opts.loss, "--loss", 100);
|
|
7612
|
+
const modes = [Boolean(opts.reset), Boolean(opts.profile), Object.keys(conditions).length > 0].filter(Boolean).length;
|
|
7613
|
+
if (modes !== 1) {
|
|
7614
|
+
throw new PncliError("Pass exactly one of --profile, --reset, or explicit conditions (--download/--upload/--latency/--loss).", 1);
|
|
7615
|
+
}
|
|
7616
|
+
if (opts.reset) {
|
|
7617
|
+
await http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "DELETE" });
|
|
7618
|
+
return { sessionId, reset: true };
|
|
7619
|
+
}
|
|
7620
|
+
if (opts.profile) {
|
|
7621
|
+
return http.saucelabs(`${sessionPath(sessionId)}/network/profile`, { method: "POST", body: { networkProfile: opts.profile } });
|
|
7622
|
+
}
|
|
7623
|
+
return http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "POST", body: { networkConditions: conditions } });
|
|
7624
|
+
}));
|
|
7625
|
+
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) => {
|
|
7626
|
+
if (opts.appiumVersion && !opts.start) throw new PncliError("--appium-version requires --start.", 1);
|
|
7627
|
+
if (!opts.start) return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`);
|
|
7628
|
+
return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`, {
|
|
7629
|
+
method: "POST",
|
|
7630
|
+
body: opts.appiumVersion ? { appiumVersion: opts.appiumVersion } : {}
|
|
7631
|
+
});
|
|
7632
|
+
}));
|
|
7633
|
+
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`)));
|
|
7634
|
+
const tunnel = sauce.command("tunnel").description("Sauce Connect tunnels");
|
|
7635
|
+
tunnel.command("list").description("List your tunnels with full details").option("--all", "Include tunnels shared with you").action(run("tunnel-list", async (http, opts) => {
|
|
7636
|
+
const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels`, {
|
|
7637
|
+
params: { full: true, all: opts.all ? true : void 0 }
|
|
7638
|
+
});
|
|
7639
|
+
return { count: countOf(data), tunnels: data };
|
|
7640
|
+
}));
|
|
7641
|
+
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)}`)));
|
|
7642
|
+
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`)));
|
|
7643
|
+
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" })));
|
|
7644
|
+
const storage = sauce.command("storage").description("App storage metadata (no upload or download)");
|
|
7645
|
+
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", {
|
|
7646
|
+
params: {
|
|
7647
|
+
q: opts.query,
|
|
7648
|
+
kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
|
|
7649
|
+
page: parseIntOption(opts.page, "--page"),
|
|
7650
|
+
per_page: parseIntOption(opts.perPage, "--per-page")
|
|
7651
|
+
}
|
|
7652
|
+
})));
|
|
7653
|
+
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", {
|
|
7654
|
+
params: {
|
|
7655
|
+
q: opts.query,
|
|
7656
|
+
kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
|
|
7657
|
+
page: parseIntOption(opts.page, "--page"),
|
|
7658
|
+
per_page: parseIntOption(opts.perPage, "--per-page")
|
|
7659
|
+
}
|
|
7660
|
+
})));
|
|
7661
|
+
const team = sauce.command("team").description("Teams in your organization");
|
|
7662
|
+
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 } })));
|
|
7663
|
+
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)}/`)));
|
|
7664
|
+
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/`)));
|
|
7665
|
+
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/`, {
|
|
7666
|
+
params: {
|
|
7667
|
+
username: opts.username,
|
|
7668
|
+
phrase: opts.phrase,
|
|
7669
|
+
limit: parseIntOption(opts.limit, "--limit"),
|
|
7670
|
+
offset: parseIntOption(opts.offset, "--offset")
|
|
7671
|
+
}
|
|
7672
|
+
})));
|
|
7673
|
+
}
|
|
7674
|
+
var TERMINAL_SESSION_STATES = /* @__PURE__ */ new Set(["CLOSING", "CLOSED", "ERRORED"]);
|
|
7675
|
+
async function waitForSession(http, sessionId, timeoutSeconds, pollMs = 5e3) {
|
|
7676
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
7677
|
+
for (; ; ) {
|
|
7678
|
+
const current = await http.saucelabs(`${RDA}/sessions/${id(sessionId)}`);
|
|
7679
|
+
if (current.state === "ACTIVE") return current;
|
|
7680
|
+
if (TERMINAL_SESSION_STATES.has(current.state)) {
|
|
7681
|
+
throw new PncliError(`Session ${sessionId} ended in state ${current.state} before becoming ACTIVE.`, 1);
|
|
7682
|
+
}
|
|
7683
|
+
if (Date.now() + pollMs > deadline) {
|
|
7684
|
+
throw new PncliError(
|
|
7685
|
+
`Session ${sessionId} still ${current.state} after ${timeoutSeconds}s. It keeps starting in the background \u2014 check it with: pncli saucelabs session get ${sessionId}`,
|
|
7686
|
+
1
|
|
7687
|
+
);
|
|
7688
|
+
}
|
|
7689
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7275
7693
|
// src/services/config/check.ts
|
|
7276
7694
|
async function runCredentialChecks(cfg, http) {
|
|
7277
7695
|
const results = {};
|
|
@@ -7602,6 +8020,20 @@ async function runCredentialChecks(cfg, http) {
|
|
|
7602
8020
|
results.alation = categorize(err);
|
|
7603
8021
|
}
|
|
7604
8022
|
}
|
|
8023
|
+
if (!cfg.saucelabs.accessKey) {
|
|
8024
|
+
results.saucelabs = { status: "blank", message: "not configured" };
|
|
8025
|
+
} else if (!cfg.saucelabs.baseUrl) {
|
|
8026
|
+
results.saucelabs = { status: "error", message: "baseUrl not configured" };
|
|
8027
|
+
} else if (!cfg.saucelabs.username) {
|
|
8028
|
+
results.saucelabs = { status: "error", message: "username not configured" };
|
|
8029
|
+
} else {
|
|
8030
|
+
try {
|
|
8031
|
+
await verifySauceLabsCredentials(http, 1e4);
|
|
8032
|
+
results.saucelabs = { status: "valid", message: "ok" };
|
|
8033
|
+
} catch (err) {
|
|
8034
|
+
results.saucelabs = categorize(err);
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
7605
8037
|
return results;
|
|
7606
8038
|
}
|
|
7607
8039
|
|
|
@@ -10509,6 +10941,20 @@ function registerConfigCommands(program2) {
|
|
|
10509
10941
|
} else {
|
|
10510
10942
|
results.alation = { ok: null, message: "not configured" };
|
|
10511
10943
|
}
|
|
10944
|
+
if (cfg.saucelabs.accessKey && !cfg.saucelabs.baseUrl) {
|
|
10945
|
+
results.saucelabs = { ok: false, message: "baseUrl not configured" };
|
|
10946
|
+
} else if (cfg.saucelabs.accessKey && !cfg.saucelabs.username) {
|
|
10947
|
+
results.saucelabs = { ok: false, message: "username not configured" };
|
|
10948
|
+
} else if (cfg.saucelabs.accessKey) {
|
|
10949
|
+
try {
|
|
10950
|
+
await verifySauceLabsCredentials(http);
|
|
10951
|
+
results.saucelabs = { ok: true, message: "connected" };
|
|
10952
|
+
} catch (err) {
|
|
10953
|
+
results.saucelabs = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
10954
|
+
}
|
|
10955
|
+
} else {
|
|
10956
|
+
results.saucelabs = { ok: null, message: "not configured" };
|
|
10957
|
+
}
|
|
10512
10958
|
success(results, "config", "test", start);
|
|
10513
10959
|
} catch (err) {
|
|
10514
10960
|
fail(err, "config", "test", start);
|
|
@@ -10551,7 +10997,8 @@ function registerConfigCommands(program2) {
|
|
|
10551
10997
|
"logscale",
|
|
10552
10998
|
"splitio",
|
|
10553
10999
|
"figma",
|
|
10554
|
-
"alation"
|
|
11000
|
+
"alation",
|
|
11001
|
+
"saucelabs"
|
|
10555
11002
|
];
|
|
10556
11003
|
if (cmdOpts.output === "table") {
|
|
10557
11004
|
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
@@ -11208,6 +11655,48 @@ async function initGlobalConfig(start) {
|
|
|
11208
11655
|
}
|
|
11209
11656
|
}
|
|
11210
11657
|
}
|
|
11658
|
+
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");
|
|
11659
|
+
const useSaucelabs = await confirm({
|
|
11660
|
+
message: "Configure Sauce Labs for jobs, builds, and real-device access?",
|
|
11661
|
+
default: false
|
|
11662
|
+
});
|
|
11663
|
+
let saucelabsBaseUrl = "";
|
|
11664
|
+
let saucelabsUsername = "";
|
|
11665
|
+
let saucelabsAccessKey = "";
|
|
11666
|
+
if (useSaucelabs) {
|
|
11667
|
+
process.stderr.write(" Find your username and access key in Sauce Labs under Account \u2192 User Settings.\n");
|
|
11668
|
+
process.stderr.write(" The API host depends on your data center: https://api.us-west-1.saucelabs.com,\n");
|
|
11669
|
+
process.stderr.write(" https://api.eu-central-1.saucelabs.com, or https://api.us-east-4.saucelabs.com.\n");
|
|
11670
|
+
saucelabsBaseUrl = await input2({
|
|
11671
|
+
message: "Sauce Labs API base URL:",
|
|
11672
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
11673
|
+
});
|
|
11674
|
+
saucelabsUsername = await input2({
|
|
11675
|
+
message: "Sauce Labs username:",
|
|
11676
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
11677
|
+
});
|
|
11678
|
+
saucelabsAccessKey = await password2({
|
|
11679
|
+
message: "Sauce Labs access key:"
|
|
11680
|
+
});
|
|
11681
|
+
if (saucelabsBaseUrl && saucelabsUsername && saucelabsAccessKey) {
|
|
11682
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
11683
|
+
try {
|
|
11684
|
+
const tempConfig = {
|
|
11685
|
+
...loadConfig(),
|
|
11686
|
+
saucelabs: {
|
|
11687
|
+
baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
|
|
11688
|
+
username: saucelabsUsername.trim(),
|
|
11689
|
+
accessKey: saucelabsAccessKey
|
|
11690
|
+
}
|
|
11691
|
+
};
|
|
11692
|
+
await verifySauceLabsCredentials(createHttpClient(tempConfig));
|
|
11693
|
+
process.stderr.write(" Connected.\n");
|
|
11694
|
+
} catch (err) {
|
|
11695
|
+
warn(`Could not connect to Sauce Labs: ${err instanceof Error ? err.message : String(err)}`);
|
|
11696
|
+
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.");
|
|
11697
|
+
}
|
|
11698
|
+
}
|
|
11699
|
+
}
|
|
11211
11700
|
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
11701
|
const jiraProject = await input2({
|
|
11213
11702
|
message: "Default Jira project key (optional):",
|
|
@@ -11377,6 +11866,13 @@ async function initGlobalConfig(start) {
|
|
|
11377
11866
|
userId: alationUserId.trim() || void 0
|
|
11378
11867
|
}
|
|
11379
11868
|
} : {},
|
|
11869
|
+
...useSaucelabs && saucelabsBaseUrl ? {
|
|
11870
|
+
saucelabs: {
|
|
11871
|
+
baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
|
|
11872
|
+
username: saucelabsUsername.trim() || void 0,
|
|
11873
|
+
accessKey: saucelabsAccessKey || void 0
|
|
11874
|
+
}
|
|
11875
|
+
} : {},
|
|
11380
11876
|
defaults: {
|
|
11381
11877
|
jira: {
|
|
11382
11878
|
project: jiraProject || void 0
|
|
@@ -11993,9 +12489,9 @@ var AdoBuildClient = class {
|
|
|
11993
12489
|
);
|
|
11994
12490
|
});
|
|
11995
12491
|
}
|
|
11996
|
-
async getDefinition(collection, project,
|
|
12492
|
+
async getDefinition(collection, project, id2) {
|
|
11997
12493
|
return this.http.ado(
|
|
11998
|
-
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${
|
|
12494
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id2}?api-version=${API8}`
|
|
11999
12495
|
);
|
|
12000
12496
|
}
|
|
12001
12497
|
// ── Builds (runs) ─────────────────────────────────────────────────
|
|
@@ -12605,7 +13101,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
12605
13101
|
const start = Date.now();
|
|
12606
13102
|
try {
|
|
12607
13103
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12608
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((
|
|
13104
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : [];
|
|
12609
13105
|
const data = await gitClient.createPR(collection, project, repo2, {
|
|
12610
13106
|
title: opts.title,
|
|
12611
13107
|
description: opts.description,
|
|
@@ -12623,7 +13119,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
12623
13119
|
const start = Date.now();
|
|
12624
13120
|
try {
|
|
12625
13121
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12626
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((
|
|
13122
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : void 0;
|
|
12627
13123
|
const body = {};
|
|
12628
13124
|
if (opts.title) body.title = opts.title;
|
|
12629
13125
|
if (opts.description !== void 0) body.description = opts.description;
|
|
@@ -12810,11 +13306,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
12810
13306
|
const start = Date.now();
|
|
12811
13307
|
try {
|
|
12812
13308
|
const globalOpts = ado.optsWithGlobals();
|
|
12813
|
-
const { loadConfig: lc } = await import("./config-
|
|
13309
|
+
const { loadConfig: lc } = await import("./config-5LC7ZTEP.js");
|
|
12814
13310
|
const config = lc({ configPath: globalOpts.config });
|
|
12815
13311
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
12816
13312
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
12817
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
13313
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-DJSGEGD6.js");
|
|
12818
13314
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
12819
13315
|
const coreClient = new AdoCoreClient2(http);
|
|
12820
13316
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -13489,8 +13985,8 @@ var CheckmarxClient = class {
|
|
|
13489
13985
|
}
|
|
13490
13986
|
return projects;
|
|
13491
13987
|
}
|
|
13492
|
-
async getProject(
|
|
13493
|
-
return this.http.checkmarx(`projects/${
|
|
13988
|
+
async getProject(id2) {
|
|
13989
|
+
return this.http.checkmarx(`projects/${id2}`);
|
|
13494
13990
|
}
|
|
13495
13991
|
async listScans(opts = {}) {
|
|
13496
13992
|
const params = { limit: opts.last ?? 100 };
|
|
@@ -13503,8 +13999,8 @@ var CheckmarxClient = class {
|
|
|
13503
13999
|
}
|
|
13504
14000
|
return scans;
|
|
13505
14001
|
}
|
|
13506
|
-
async getScan(
|
|
13507
|
-
return this.http.checkmarx(`scans/${
|
|
14002
|
+
async getScan(id2) {
|
|
14003
|
+
return this.http.checkmarx(`scans/${id2}`);
|
|
13508
14004
|
}
|
|
13509
14005
|
async getScanResultsStatistics(scanId) {
|
|
13510
14006
|
return this.http.checkmarx("results/summary", {
|
|
@@ -13820,7 +14316,7 @@ function resolveCluster(config, env, instance) {
|
|
|
13820
14316
|
}
|
|
13821
14317
|
return { baseUrl: config.openshift.baseUrl, token: config.openshift.token };
|
|
13822
14318
|
}
|
|
13823
|
-
function
|
|
14319
|
+
function getHttp2(oc) {
|
|
13824
14320
|
const opts = oc.optsWithGlobals();
|
|
13825
14321
|
const config = loadConfig({ configPath: opts.config });
|
|
13826
14322
|
const cluster = resolveCluster(config, opts.env, opts.instance);
|
|
@@ -13896,7 +14392,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
13896
14392
|
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
14393
|
const start = Date.now();
|
|
13898
14394
|
try {
|
|
13899
|
-
const http =
|
|
14395
|
+
const http = getHttp2(oc);
|
|
13900
14396
|
const params = {};
|
|
13901
14397
|
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
13902
14398
|
const podList = await http.openshift(
|
|
@@ -14005,7 +14501,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14005
14501
|
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
14502
|
const start = Date.now();
|
|
14007
14503
|
try {
|
|
14008
|
-
const http =
|
|
14504
|
+
const http = getHttp2(oc);
|
|
14009
14505
|
const params = {};
|
|
14010
14506
|
const selectors = [];
|
|
14011
14507
|
if (!opts.all) selectors.push("type=Warning");
|
|
@@ -14039,7 +14535,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14039
14535
|
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
14536
|
const start = Date.now();
|
|
14041
14537
|
try {
|
|
14042
|
-
const http =
|
|
14538
|
+
const http = getHttp2(oc);
|
|
14043
14539
|
const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
|
|
14044
14540
|
const logText = await http.openshiftText(
|
|
14045
14541
|
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
|
|
@@ -14071,7 +14567,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14071
14567
|
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
14568
|
const start = Date.now();
|
|
14073
14569
|
try {
|
|
14074
|
-
const http =
|
|
14570
|
+
const http = getHttp2(oc);
|
|
14075
14571
|
const metricsList = await http.openshift(
|
|
14076
14572
|
`/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
|
|
14077
14573
|
);
|
|
@@ -14091,7 +14587,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14091
14587
|
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
14588
|
const start = Date.now();
|
|
14093
14589
|
try {
|
|
14094
|
-
const http =
|
|
14590
|
+
const http = getHttp2(oc);
|
|
14095
14591
|
const nsEncoded = encodeURIComponent(opts.namespace);
|
|
14096
14592
|
const podParams = {};
|
|
14097
14593
|
if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
|
|
@@ -14156,7 +14652,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
14156
14652
|
}
|
|
14157
14653
|
|
|
14158
14654
|
// src/services/dynatrace/commands.ts
|
|
14159
|
-
function
|
|
14655
|
+
function getHttp3(program2, envName) {
|
|
14160
14656
|
const opts = program2.optsWithGlobals();
|
|
14161
14657
|
const cfg = loadConfig({ configPath: opts.config });
|
|
14162
14658
|
if (envName) {
|
|
@@ -14280,7 +14776,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14280
14776
|
const start = Date.now();
|
|
14281
14777
|
try {
|
|
14282
14778
|
const envName = dynatrace.opts().env;
|
|
14283
|
-
const data = await listEntities(
|
|
14779
|
+
const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
|
|
14284
14780
|
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
14285
14781
|
} catch (err) {
|
|
14286
14782
|
fail(err, "dynatrace", "entities list", start);
|
|
@@ -14290,7 +14786,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14290
14786
|
const start = Date.now();
|
|
14291
14787
|
try {
|
|
14292
14788
|
const envName = dynatrace.opts().env;
|
|
14293
|
-
const data = await
|
|
14789
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
14294
14790
|
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
14295
14791
|
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
14296
14792
|
);
|
|
@@ -14303,7 +14799,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14303
14799
|
const start = Date.now();
|
|
14304
14800
|
try {
|
|
14305
14801
|
const envName = dynatrace.opts().env;
|
|
14306
|
-
const data = await listEntities(
|
|
14802
|
+
const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
|
|
14307
14803
|
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
14308
14804
|
} catch (err) {
|
|
14309
14805
|
fail(err, "dynatrace", "services", start);
|
|
@@ -14314,7 +14810,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14314
14810
|
try {
|
|
14315
14811
|
const envName = dynatrace.opts().env;
|
|
14316
14812
|
const selector = 'type("CLOUD_APPLICATION")';
|
|
14317
|
-
const data = await listEntities(
|
|
14813
|
+
const data = await listEntities(getHttp3(program2, envName), selector, opts);
|
|
14318
14814
|
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
14319
14815
|
} catch (err) {
|
|
14320
14816
|
fail(err, "dynatrace", "workloads", start);
|
|
@@ -14326,7 +14822,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14326
14822
|
try {
|
|
14327
14823
|
const envName = dynatrace.opts().env;
|
|
14328
14824
|
const data = await allPages(async (nextPageKey) => {
|
|
14329
|
-
const page = await
|
|
14825
|
+
const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
|
|
14330
14826
|
params: nextPageKey ? { nextPageKey } : {
|
|
14331
14827
|
from: opts.from ?? "now-2h",
|
|
14332
14828
|
to: opts.to ?? "now",
|
|
@@ -14346,7 +14842,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14346
14842
|
const start = Date.now();
|
|
14347
14843
|
try {
|
|
14348
14844
|
const envName = dynatrace.opts().env;
|
|
14349
|
-
const data = await
|
|
14845
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
14350
14846
|
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
14351
14847
|
{ params: { fields: opts.fields } }
|
|
14352
14848
|
);
|
|
@@ -14359,7 +14855,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14359
14855
|
const start = Date.now();
|
|
14360
14856
|
try {
|
|
14361
14857
|
const envName = dynatrace.opts().env;
|
|
14362
|
-
const result = await runTraceQuery(
|
|
14858
|
+
const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
|
|
14363
14859
|
success(result, "dynatrace", "trace", start);
|
|
14364
14860
|
} catch (err) {
|
|
14365
14861
|
fail(err, "dynatrace", "trace", start);
|
|
@@ -14368,7 +14864,7 @@ function registerDynatraceCommands(program2) {
|
|
|
14368
14864
|
}
|
|
14369
14865
|
|
|
14370
14866
|
// src/services/logscale/commands.ts
|
|
14371
|
-
function
|
|
14867
|
+
function getHttp4(program2) {
|
|
14372
14868
|
const opts = program2.optsWithGlobals();
|
|
14373
14869
|
return createHttpClient(
|
|
14374
14870
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14381,7 +14877,7 @@ function registerLogscaleCommands(program2) {
|
|
|
14381
14877
|
repositories.command("list").description("List available repositories").action(async () => {
|
|
14382
14878
|
const start = Date.now();
|
|
14383
14879
|
try {
|
|
14384
|
-
const data = await
|
|
14880
|
+
const data = await getHttp4(program2).logscale("/api/v1/repositories");
|
|
14385
14881
|
success({ count: data.length, repositories: data }, "logscale", "repositories list", start);
|
|
14386
14882
|
} catch (err) {
|
|
14387
14883
|
fail(err, "logscale", "repositories list", start);
|
|
@@ -14398,7 +14894,7 @@ function registerLogscaleCommands(program2) {
|
|
|
14398
14894
|
showQueryEventDistribution: false,
|
|
14399
14895
|
isLive: false
|
|
14400
14896
|
};
|
|
14401
|
-
const data = await
|
|
14897
|
+
const data = await getHttp4(program2).logscale(
|
|
14402
14898
|
`/api/v1/repositories/${encodeURIComponent(opts.repository)}/query`,
|
|
14403
14899
|
{
|
|
14404
14900
|
method: "POST",
|
|
@@ -14429,7 +14925,7 @@ function registerLogscaleCommands(program2) {
|
|
|
14429
14925
|
// src/services/splitio/commands.ts
|
|
14430
14926
|
import fs11 from "fs";
|
|
14431
14927
|
import confirm2 from "@inquirer/confirm";
|
|
14432
|
-
function
|
|
14928
|
+
function getHttp5(program2) {
|
|
14433
14929
|
const opts = program2.optsWithGlobals();
|
|
14434
14930
|
return createHttpClient(
|
|
14435
14931
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14464,7 +14960,7 @@ function buildCrBase(opts) {
|
|
|
14464
14960
|
crNumber: opts.changeNumber
|
|
14465
14961
|
};
|
|
14466
14962
|
if (opts.approvers) {
|
|
14467
|
-
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((
|
|
14963
|
+
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id2) => ({ id: id2 }));
|
|
14468
14964
|
}
|
|
14469
14965
|
return base;
|
|
14470
14966
|
}
|
|
@@ -14474,7 +14970,7 @@ function registerSplitioCommands(program2) {
|
|
|
14474
14970
|
workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
|
|
14475
14971
|
const start = Date.now();
|
|
14476
14972
|
try {
|
|
14477
|
-
const http =
|
|
14973
|
+
const http = getHttp5(program2);
|
|
14478
14974
|
const data = await http.splitio("/internal/api/v2/workspaces");
|
|
14479
14975
|
success(
|
|
14480
14976
|
{ count: data.objects.length, workspaces: data.objects },
|
|
@@ -14490,7 +14986,7 @@ function registerSplitioCommands(program2) {
|
|
|
14490
14986
|
environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
|
|
14491
14987
|
const start = Date.now();
|
|
14492
14988
|
try {
|
|
14493
|
-
const http =
|
|
14989
|
+
const http = getHttp5(program2);
|
|
14494
14990
|
const data = await http.splitio(
|
|
14495
14991
|
`/internal/api/v2/environments/ws/${encodeURIComponent(opts.workspace)}`
|
|
14496
14992
|
);
|
|
@@ -14508,7 +15004,7 @@ function registerSplitioCommands(program2) {
|
|
|
14508
15004
|
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
15005
|
const start = Date.now();
|
|
14510
15006
|
try {
|
|
14511
|
-
const http =
|
|
15007
|
+
const http = getHttp5(program2);
|
|
14512
15008
|
const params = {
|
|
14513
15009
|
limit: parseInt(opts.limit, 10),
|
|
14514
15010
|
offset: parseInt(opts.offset, 10)
|
|
@@ -14531,7 +15027,7 @@ function registerSplitioCommands(program2) {
|
|
|
14531
15027
|
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
15028
|
const start = Date.now();
|
|
14533
15029
|
try {
|
|
14534
|
-
const http =
|
|
15030
|
+
const http = getHttp5(program2);
|
|
14535
15031
|
if (opts.environment) {
|
|
14536
15032
|
const data = await http.splitio(
|
|
14537
15033
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
@@ -14553,7 +15049,7 @@ function registerSplitioCommands(program2) {
|
|
|
14553
15049
|
if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
|
|
14554
15050
|
const definition = loadInputFile(opts.inputFile);
|
|
14555
15051
|
await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
|
|
14556
|
-
const http =
|
|
15052
|
+
const http = getHttp5(program2);
|
|
14557
15053
|
const body = {
|
|
14558
15054
|
...buildCrBase(opts),
|
|
14559
15055
|
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
@@ -14579,7 +15075,7 @@ function registerSplitioCommands(program2) {
|
|
|
14579
15075
|
if (!opts.treatment && !opts.weights) {
|
|
14580
15076
|
throw new PncliError("Either --treatment or --weights must be specified");
|
|
14581
15077
|
}
|
|
14582
|
-
const http =
|
|
15078
|
+
const http = getHttp5(program2);
|
|
14583
15079
|
const current = await http.splitio(
|
|
14584
15080
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
14585
15081
|
);
|
|
@@ -14617,7 +15113,7 @@ function registerSplitioCommands(program2) {
|
|
|
14617
15113
|
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
15114
|
const start = Date.now();
|
|
14619
15115
|
try {
|
|
14620
|
-
const http =
|
|
15116
|
+
const http = getHttp5(program2);
|
|
14621
15117
|
const current = await http.splitio(
|
|
14622
15118
|
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
14623
15119
|
);
|
|
@@ -14650,7 +15146,7 @@ function registerSplitioCommands(program2) {
|
|
|
14650
15146
|
const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
|
|
14651
15147
|
const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
14652
15148
|
await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
|
|
14653
|
-
const http =
|
|
15149
|
+
const http = getHttp5(program2);
|
|
14654
15150
|
const body = {
|
|
14655
15151
|
...buildCrBase(opts),
|
|
14656
15152
|
operationType: opType,
|
|
@@ -14673,7 +15169,7 @@ function registerSplitioCommands(program2) {
|
|
|
14673
15169
|
const entries = loadInputFile(opts.inputFile);
|
|
14674
15170
|
if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
|
|
14675
15171
|
await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
|
|
14676
|
-
const http =
|
|
15172
|
+
const http = getHttp5(program2);
|
|
14677
15173
|
const results = [];
|
|
14678
15174
|
for (const entry of entries) {
|
|
14679
15175
|
try {
|
|
@@ -14707,7 +15203,7 @@ function registerSplitioCommands(program2) {
|
|
|
14707
15203
|
const start = Date.now();
|
|
14708
15204
|
try {
|
|
14709
15205
|
await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
|
|
14710
|
-
const http =
|
|
15206
|
+
const http = getHttp5(program2);
|
|
14711
15207
|
const body = {
|
|
14712
15208
|
...buildCrBase(opts),
|
|
14713
15209
|
operationType: "KILL_SPLIT",
|
|
@@ -14727,7 +15223,7 @@ function registerSplitioCommands(program2) {
|
|
|
14727
15223
|
const start = Date.now();
|
|
14728
15224
|
try {
|
|
14729
15225
|
await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
|
|
14730
|
-
const http =
|
|
15226
|
+
const http = getHttp5(program2);
|
|
14731
15227
|
const body = {
|
|
14732
15228
|
...buildCrBase(opts),
|
|
14733
15229
|
operationType: "RESTORE_SPLIT",
|
|
@@ -14747,7 +15243,7 @@ function registerSplitioCommands(program2) {
|
|
|
14747
15243
|
const start = Date.now();
|
|
14748
15244
|
try {
|
|
14749
15245
|
await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
|
|
14750
|
-
const http =
|
|
15246
|
+
const http = getHttp5(program2);
|
|
14751
15247
|
const body = {
|
|
14752
15248
|
...buildCrBase(opts),
|
|
14753
15249
|
operationType: "ARCHIVE_SPLIT",
|
|
@@ -14767,7 +15263,7 @@ function registerSplitioCommands(program2) {
|
|
|
14767
15263
|
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
15264
|
const start = Date.now();
|
|
14769
15265
|
try {
|
|
14770
|
-
const http =
|
|
15266
|
+
const http = getHttp5(program2);
|
|
14771
15267
|
const params = {
|
|
14772
15268
|
limit: parseInt(opts.limit, 10),
|
|
14773
15269
|
offset: parseInt(opts.offset, 10)
|
|
@@ -14791,7 +15287,7 @@ function registerSplitioCommands(program2) {
|
|
|
14791
15287
|
changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
|
|
14792
15288
|
const start = Date.now();
|
|
14793
15289
|
try {
|
|
14794
|
-
const http =
|
|
15290
|
+
const http = getHttp5(program2);
|
|
14795
15291
|
const data = await http.splitio(
|
|
14796
15292
|
`/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
|
|
14797
15293
|
);
|
|
@@ -14843,7 +15339,7 @@ function findNodeIdInUrl(url) {
|
|
|
14843
15339
|
if (!new URL(url).searchParams.has("node-id")) return void 0;
|
|
14844
15340
|
return parseNodeId(url);
|
|
14845
15341
|
}
|
|
14846
|
-
function
|
|
15342
|
+
function getHttp6(program2) {
|
|
14847
15343
|
const opts = program2.optsWithGlobals();
|
|
14848
15344
|
return createHttpClient(
|
|
14849
15345
|
loadConfig({ configPath: opts.config }),
|
|
@@ -14859,7 +15355,7 @@ function registerFigmaCommands(program2) {
|
|
|
14859
15355
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14860
15356
|
const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
|
|
14861
15357
|
if (nodeId) {
|
|
14862
|
-
const data2 = await
|
|
15358
|
+
const data2 = await getHttp6(program2).figma(
|
|
14863
15359
|
`/v1/files/${encodeURIComponent(fileKey)}/nodes`,
|
|
14864
15360
|
{ params: { ids: nodeId } }
|
|
14865
15361
|
);
|
|
@@ -14888,7 +15384,7 @@ function registerFigmaCommands(program2) {
|
|
|
14888
15384
|
if (!opts.document) {
|
|
14889
15385
|
params["depth"] = 1;
|
|
14890
15386
|
}
|
|
14891
|
-
const data = await
|
|
15387
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
|
|
14892
15388
|
success2(
|
|
14893
15389
|
{
|
|
14894
15390
|
fileKey,
|
|
@@ -14918,7 +15414,7 @@ function registerFigmaCommands(program2) {
|
|
|
14918
15414
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14919
15415
|
const params = {};
|
|
14920
15416
|
if (opts.asOf) params["as_of"] = opts.asOf;
|
|
14921
|
-
const data = await
|
|
15417
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
|
|
14922
15418
|
success2(
|
|
14923
15419
|
{ fileKey, count: data.comments.length, comments: data.comments },
|
|
14924
15420
|
"figma",
|
|
@@ -14934,7 +15430,7 @@ function registerFigmaCommands(program2) {
|
|
|
14934
15430
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14935
15431
|
try {
|
|
14936
15432
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
14937
|
-
const data = await
|
|
15433
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
|
|
14938
15434
|
success2(
|
|
14939
15435
|
{ fileKey, count: data.versions.length, versions: data.versions },
|
|
14940
15436
|
"figma",
|
|
@@ -14949,7 +15445,7 @@ function registerFigmaCommands(program2) {
|
|
|
14949
15445
|
const start = Date.now();
|
|
14950
15446
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14951
15447
|
try {
|
|
14952
|
-
const data = await
|
|
15448
|
+
const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
|
|
14953
15449
|
success2(
|
|
14954
15450
|
{ projectId, count: data.files.length, files: data.files },
|
|
14955
15451
|
"figma",
|
|
@@ -14964,7 +15460,7 @@ function registerFigmaCommands(program2) {
|
|
|
14964
15460
|
const start = Date.now();
|
|
14965
15461
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
14966
15462
|
try {
|
|
14967
|
-
const data = await
|
|
15463
|
+
const data = await getHttp6(program2).figma("/v1/me");
|
|
14968
15464
|
success2(data, "figma", "me", start);
|
|
14969
15465
|
} catch (err) {
|
|
14970
15466
|
fail2(err, "figma", "me", start);
|
|
@@ -14975,7 +15471,7 @@ function registerFigmaCommands(program2) {
|
|
|
14975
15471
|
// src/services/alation/commands.ts
|
|
14976
15472
|
import fs12 from "fs";
|
|
14977
15473
|
var MAX_PAGE = 1e3;
|
|
14978
|
-
function
|
|
15474
|
+
function parseIntOption2(value, flag) {
|
|
14979
15475
|
if (value === void 0) return void 0;
|
|
14980
15476
|
const trimmed = value.trim();
|
|
14981
15477
|
if (!/^\d+$/.test(trimmed)) {
|
|
@@ -14991,8 +15487,8 @@ function parsePositiveId(value, what) {
|
|
|
14991
15487
|
return Number(trimmed);
|
|
14992
15488
|
}
|
|
14993
15489
|
function pageParams(opts) {
|
|
14994
|
-
const limit =
|
|
14995
|
-
const skip =
|
|
15490
|
+
const limit = parseIntOption2(opts.limit, "--limit") ?? 100;
|
|
15491
|
+
const skip = parseIntOption2(opts.skip, "--skip") ?? 0;
|
|
14996
15492
|
if (limit < 1 || limit > MAX_PAGE) {
|
|
14997
15493
|
throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
|
|
14998
15494
|
}
|
|
@@ -15079,17 +15575,17 @@ var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
|
|
|
15079
15575
|
template: 7,
|
|
15080
15576
|
fields: { "10001": "Team Data Platform" }
|
|
15081
15577
|
};
|
|
15082
|
-
function
|
|
15578
|
+
function getHttp7(program2) {
|
|
15083
15579
|
const opts = program2.optsWithGlobals();
|
|
15084
15580
|
return createHttpClient(
|
|
15085
15581
|
loadConfig({ configPath: opts.config }),
|
|
15086
15582
|
Boolean(opts.dryRun)
|
|
15087
15583
|
);
|
|
15088
15584
|
}
|
|
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} ${
|
|
15585
|
+
async function getOne(http, path14, id2, what) {
|
|
15586
|
+
const rows = await http.alation(path14, { params: { id: id2, limit: 1 } });
|
|
15587
|
+
const row = Array.isArray(rows) ? rows.find((r) => r.id === id2) ?? rows[0] : void 0;
|
|
15588
|
+
if (!row) throw new PncliError(`${what} ${id2} not found`, 404);
|
|
15093
15589
|
return row;
|
|
15094
15590
|
}
|
|
15095
15591
|
function buildDocumentPayload(opts) {
|
|
@@ -15155,17 +15651,17 @@ function registerAlationCommands(program2) {
|
|
|
15155
15651
|
const params = {};
|
|
15156
15652
|
if (opts.includeUndeployed) params.include_undeployed = true;
|
|
15157
15653
|
if (opts.includeHidden) params.include_hidden = true;
|
|
15158
|
-
const data = await
|
|
15654
|
+
const data = await getHttp7(program2).alation("integration/v1/datasource/", { params });
|
|
15159
15655
|
success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
|
|
15160
15656
|
} catch (err) {
|
|
15161
15657
|
fail(err, "alation", "datasource-list", start);
|
|
15162
15658
|
}
|
|
15163
15659
|
});
|
|
15164
|
-
datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (
|
|
15660
|
+
datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id2) => {
|
|
15165
15661
|
const start = Date.now();
|
|
15166
15662
|
try {
|
|
15167
|
-
const dsId = parsePositiveId(
|
|
15168
|
-
const data = await
|
|
15663
|
+
const dsId = parsePositiveId(id2, "data source ID");
|
|
15664
|
+
const data = await getHttp7(program2).alation(`integration/v1/datasource/${dsId}/`);
|
|
15169
15665
|
success(data, "alation", "datasource-get", start);
|
|
15170
15666
|
} catch (err) {
|
|
15171
15667
|
fail(err, "alation", "datasource-get", start);
|
|
@@ -15178,16 +15674,16 @@ function registerAlationCommands(program2) {
|
|
|
15178
15674
|
const params = { ...pageParams(opts) };
|
|
15179
15675
|
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
15180
15676
|
if (opts.name) params.name = opts.name;
|
|
15181
|
-
const data = await
|
|
15677
|
+
const data = await getHttp7(program2).alation("integration/v2/schema/", { params });
|
|
15182
15678
|
success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
|
|
15183
15679
|
} catch (err) {
|
|
15184
15680
|
fail(err, "alation", "schema-list", start);
|
|
15185
15681
|
}
|
|
15186
15682
|
});
|
|
15187
|
-
schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (
|
|
15683
|
+
schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id2) => {
|
|
15188
15684
|
const start = Date.now();
|
|
15189
15685
|
try {
|
|
15190
|
-
const data = await getOne(
|
|
15686
|
+
const data = await getOne(getHttp7(program2), "integration/v2/schema/", parsePositiveId(id2, "schema ID"), "Schema");
|
|
15191
15687
|
success(data, "alation", "schema-get", start);
|
|
15192
15688
|
} catch (err) {
|
|
15193
15689
|
fail(err, "alation", "schema-get", start);
|
|
@@ -15203,17 +15699,17 @@ function registerAlationCommands(program2) {
|
|
|
15203
15699
|
if (opts.schemaName) params.schema_name = opts.schemaName;
|
|
15204
15700
|
if (opts.name) params.name = opts.name;
|
|
15205
15701
|
if (opts.search) params.name__icontains = opts.search;
|
|
15206
|
-
const data = await
|
|
15702
|
+
const data = await getHttp7(program2).alation("integration/v2/table/", { params });
|
|
15207
15703
|
success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
|
|
15208
15704
|
} catch (err) {
|
|
15209
15705
|
fail(err, "alation", "table-list", start);
|
|
15210
15706
|
}
|
|
15211
15707
|
});
|
|
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 (
|
|
15708
|
+
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
15709
|
const start = Date.now();
|
|
15214
15710
|
try {
|
|
15215
|
-
const http =
|
|
15216
|
-
const tableId = parsePositiveId(
|
|
15711
|
+
const http = getHttp7(program2);
|
|
15712
|
+
const tableId = parsePositiveId(id2, "table ID");
|
|
15217
15713
|
const data = await getOne(http, "integration/v2/table/", tableId, "Table");
|
|
15218
15714
|
if (opts.columns) {
|
|
15219
15715
|
const columns = await http.alation("integration/v2/column/", {
|
|
@@ -15241,16 +15737,16 @@ function registerAlationCommands(program2) {
|
|
|
15241
15737
|
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
15242
15738
|
if (opts.name) params.name = opts.name;
|
|
15243
15739
|
if (opts.search) params.name__icontains = opts.search;
|
|
15244
|
-
const data = await
|
|
15740
|
+
const data = await getHttp7(program2).alation("integration/v2/column/", { params });
|
|
15245
15741
|
success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
|
|
15246
15742
|
} catch (err) {
|
|
15247
15743
|
fail(err, "alation", "column-list", start);
|
|
15248
15744
|
}
|
|
15249
15745
|
});
|
|
15250
|
-
column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (
|
|
15746
|
+
column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id2) => {
|
|
15251
15747
|
const start = Date.now();
|
|
15252
15748
|
try {
|
|
15253
|
-
const data = await getOne(
|
|
15749
|
+
const data = await getOne(getHttp7(program2), "integration/v2/column/", parsePositiveId(id2, "column ID"), "Column");
|
|
15254
15750
|
success(data, "alation", "column-get", start);
|
|
15255
15751
|
} catch (err) {
|
|
15256
15752
|
fail(err, "alation", "column-get", start);
|
|
@@ -15259,13 +15755,13 @@ function registerAlationCommands(program2) {
|
|
|
15259
15755
|
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
15756
|
const start = Date.now();
|
|
15261
15757
|
try {
|
|
15262
|
-
const limit =
|
|
15263
|
-
const offset =
|
|
15758
|
+
const limit = parseIntOption2(opts.limit, "--limit") ?? 20;
|
|
15759
|
+
const offset = parseIntOption2(opts.offset, "--offset") ?? 0;
|
|
15264
15760
|
if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
|
|
15265
15761
|
const params = { q: query, limit, offset };
|
|
15266
15762
|
const otypes = parseOtypes(opts.otype);
|
|
15267
15763
|
if (otypes) params.filters = JSON.stringify({ otypes });
|
|
15268
|
-
const data = await
|
|
15764
|
+
const data = await getHttp7(program2).alation("integration/v1/search/", { params });
|
|
15269
15765
|
success(
|
|
15270
15766
|
{ query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
|
|
15271
15767
|
"alation",
|
|
@@ -15284,16 +15780,16 @@ function registerAlationCommands(program2) {
|
|
|
15284
15780
|
if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
|
|
15285
15781
|
if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
|
|
15286
15782
|
if (opts.search) params.search = opts.search;
|
|
15287
|
-
const data = await
|
|
15783
|
+
const data = await getHttp7(program2).alation("integration/v2/folder/", { params });
|
|
15288
15784
|
success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
|
|
15289
15785
|
} catch (err) {
|
|
15290
15786
|
fail(err, "alation", "folder-list", start);
|
|
15291
15787
|
}
|
|
15292
15788
|
});
|
|
15293
|
-
folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (
|
|
15789
|
+
folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id2) => {
|
|
15294
15790
|
const start = Date.now();
|
|
15295
15791
|
try {
|
|
15296
|
-
const data = await getOne(
|
|
15792
|
+
const data = await getOne(getHttp7(program2), "integration/v2/folder/", parsePositiveId(id2, "folder ID"), "Folder");
|
|
15297
15793
|
success(data, "alation", "folder-get", start);
|
|
15298
15794
|
} catch (err) {
|
|
15299
15795
|
fail(err, "alation", "folder-get", start);
|
|
@@ -15309,16 +15805,16 @@ function registerAlationCommands(program2) {
|
|
|
15309
15805
|
if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
|
|
15310
15806
|
if (opts.search) params.search = opts.search;
|
|
15311
15807
|
if (opts.deleted) params.deleted = true;
|
|
15312
|
-
const data = await
|
|
15808
|
+
const data = await getHttp7(program2).alation("integration/v2/document/", { params });
|
|
15313
15809
|
success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
|
|
15314
15810
|
} catch (err) {
|
|
15315
15811
|
fail(err, "alation", "document-list", start);
|
|
15316
15812
|
}
|
|
15317
15813
|
});
|
|
15318
|
-
document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (
|
|
15814
|
+
document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id2) => {
|
|
15319
15815
|
const start = Date.now();
|
|
15320
15816
|
try {
|
|
15321
|
-
const data = await getOne(
|
|
15817
|
+
const data = await getOne(getHttp7(program2), "integration/v2/document/", parsePositiveId(id2, "document ID"), "Document");
|
|
15322
15818
|
success(data, "alation", "document-get", start);
|
|
15323
15819
|
} catch (err) {
|
|
15324
15820
|
fail(err, "alation", "document-get", start);
|
|
@@ -15339,16 +15835,16 @@ function registerAlationCommands(program2) {
|
|
|
15339
15835
|
for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
|
|
15340
15836
|
if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
|
|
15341
15837
|
}
|
|
15342
|
-
const data = await
|
|
15838
|
+
const data = await getHttp7(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
|
|
15343
15839
|
success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
|
|
15344
15840
|
} catch (err) {
|
|
15345
15841
|
fail(err, "alation", "document-create", start);
|
|
15346
15842
|
}
|
|
15347
15843
|
});
|
|
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 (
|
|
15844
|
+
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
15845
|
const start = Date.now();
|
|
15350
15846
|
try {
|
|
15351
|
-
const documentId = parsePositiveId(
|
|
15847
|
+
const documentId = parsePositiveId(id2, "document ID");
|
|
15352
15848
|
const { payload, overrides } = buildDocumentPayload(opts);
|
|
15353
15849
|
if (payload.document_hub_id !== void 0) {
|
|
15354
15850
|
throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
|
|
@@ -15357,18 +15853,18 @@ function registerAlationCommands(program2) {
|
|
|
15357
15853
|
throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
|
|
15358
15854
|
}
|
|
15359
15855
|
const body = [{ id: documentId, ...payload }];
|
|
15360
|
-
const data = await
|
|
15856
|
+
const data = await getHttp7(program2).alation("integration/v2/document/", { method: "PUT", body });
|
|
15361
15857
|
success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
|
|
15362
15858
|
} catch (err) {
|
|
15363
15859
|
fail(err, "alation", "document-update", start);
|
|
15364
15860
|
}
|
|
15365
15861
|
});
|
|
15366
15862
|
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 (
|
|
15863
|
+
job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id2) => {
|
|
15368
15864
|
const start = Date.now();
|
|
15369
15865
|
try {
|
|
15370
|
-
const jobId = parsePositiveId(
|
|
15371
|
-
const data = await
|
|
15866
|
+
const jobId = parsePositiveId(id2, "job ID");
|
|
15867
|
+
const data = await getHttp7(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
|
|
15372
15868
|
success({ jobId, ...data }, "alation", "job-get", start);
|
|
15373
15869
|
} catch (err) {
|
|
15374
15870
|
fail(err, "alation", "job-get", start);
|
|
@@ -15377,9 +15873,7 @@ function registerAlationCommands(program2) {
|
|
|
15377
15873
|
}
|
|
15378
15874
|
|
|
15379
15875
|
// src/cli.ts
|
|
15380
|
-
|
|
15381
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
15382
|
-
}
|
|
15876
|
+
configureTls();
|
|
15383
15877
|
await configureProxy();
|
|
15384
15878
|
var TAGLINE = "Connectivity without MCP.";
|
|
15385
15879
|
var program = new Command3();
|
|
@@ -15422,6 +15916,7 @@ registerLogscaleCommands(program);
|
|
|
15422
15916
|
registerSplitioCommands(program);
|
|
15423
15917
|
registerFigmaCommands(program);
|
|
15424
15918
|
registerAlationCommands(program);
|
|
15919
|
+
registerSauceLabsCommands(program);
|
|
15425
15920
|
program.addHelpText("after", `
|
|
15426
15921
|
Services:
|
|
15427
15922
|
git Local git operations (status, diff, log, branch)
|
|
@@ -15444,6 +15939,7 @@ Services:
|
|
|
15444
15939
|
splitio Split.IO (feature flags, Change Requests)
|
|
15445
15940
|
figma Figma (files, comments, version history)
|
|
15446
15941
|
alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
|
|
15942
|
+
saucelabs Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms)
|
|
15447
15943
|
config Manage pncli configuration (and OS keychain storage for credentials)
|
|
15448
15944
|
doctor Diagnose pncli setup (config, credentials, keychain, git auth, agent skills, marketplaces)
|
|
15449
15945
|
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|