@kolatts/pncli 1.25.1 → 2.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 +10 -7
- package/copilot-instructions.md +1 -36
- package/dist/chunk-HZF6WQPU.js +152 -0
- package/dist/chunk-HZF6WQPU.js.map +1 -0
- package/dist/{chunk-JOAWUILY.js → chunk-PXKUQPPF.js} +32 -17
- package/dist/chunk-PXKUQPPF.js.map +1 -0
- package/dist/{chunk-ABHLES5M.js → chunk-ZDJYOI3W.js} +106 -216
- package/dist/chunk-ZDJYOI3W.js.map +1 -0
- package/dist/cli.js +1245 -368
- package/dist/cli.js.map +1 -1
- package/dist/{config-SQ3YHJHI.js → config-YHYQTGKT.js} +4 -2
- package/dist/{http-WCK6AZW6.js → http-6QJU2SWP.js} +3 -2
- package/dist/output-XT3WT5L5.js +24 -0
- package/dist/output-XT3WT5L5.js.map +1 -0
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +5 -2
- package/skills/pncli/figma.md +62 -0
- package/skills/pncli/jenkins.md +47 -0
- package/skills/pncli/openshift.md +23 -1
- package/skills/pncli/splitio.md +178 -0
- package/dist/chunk-ABHLES5M.js.map +0 -1
- package/dist/chunk-JOAWUILY.js.map +0 -1
- package/skills/pncli/udeploy.md +0 -48
- /package/dist/{config-SQ3YHJHI.js.map → config-YHYQTGKT.js.map} +0 -0
- /package/dist/{http-WCK6AZW6.js.map → http-6QJU2SWP.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -4,15 +4,18 @@ import {
|
|
|
4
4
|
loadConfig,
|
|
5
5
|
loadJsonFile,
|
|
6
6
|
maskConfig,
|
|
7
|
+
normalizeBaseUrl,
|
|
7
8
|
setConfigValue,
|
|
8
9
|
setRepoConfigValue,
|
|
9
10
|
writeGlobalConfig,
|
|
10
11
|
writeRepoConfig
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PXKUQPPF.js";
|
|
13
|
+
import {
|
|
14
|
+
createHttpClient
|
|
15
|
+
} from "./chunk-ZDJYOI3W.js";
|
|
12
16
|
import {
|
|
13
17
|
ExitCode,
|
|
14
18
|
PncliError,
|
|
15
|
-
createHttpClient,
|
|
16
19
|
fail,
|
|
17
20
|
log,
|
|
18
21
|
setGlobalOptions,
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
success,
|
|
21
24
|
warn,
|
|
22
25
|
writeRawOutput
|
|
23
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-HZF6WQPU.js";
|
|
24
27
|
import {
|
|
25
28
|
AdoCoreClient
|
|
26
29
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -2368,6 +2371,232 @@ var GitHubClient = class {
|
|
|
2368
2371
|
const data = await this.http.githubGraphQL(mutation, { threadId });
|
|
2369
2372
|
return data.resolveReviewThread.thread;
|
|
2370
2373
|
}
|
|
2374
|
+
// ── Auto-merge ─────────────────────────────────────────────────────
|
|
2375
|
+
/** Returns the GraphQL node ID of a PR, needed for mutations. */
|
|
2376
|
+
async getPRNodeId(owner, repo, pullNumber) {
|
|
2377
|
+
const pr = await this.getPR(owner, repo, pullNumber);
|
|
2378
|
+
return pr.node_id;
|
|
2379
|
+
}
|
|
2380
|
+
async enableAutoMerge(opts) {
|
|
2381
|
+
const nodeId = await this.getPRNodeId(opts.owner, opts.repo, opts.pullNumber);
|
|
2382
|
+
const mutation = `
|
|
2383
|
+
mutation($pullRequestId: ID!, $mergeMethod: PullRequestMergeMethod, $expectedHeadOid: GitObjectID) {
|
|
2384
|
+
enablePullRequestAutoMerge(input: {
|
|
2385
|
+
pullRequestId: $pullRequestId
|
|
2386
|
+
mergeMethod: $mergeMethod
|
|
2387
|
+
expectedHeadOid: $expectedHeadOid
|
|
2388
|
+
}) {
|
|
2389
|
+
pullRequest {
|
|
2390
|
+
number
|
|
2391
|
+
autoMergeRequest {
|
|
2392
|
+
mergeMethod
|
|
2393
|
+
enabledAt
|
|
2394
|
+
enabledBy { login }
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
`;
|
|
2400
|
+
const variables = { pullRequestId: nodeId };
|
|
2401
|
+
if (opts.mergeMethod) variables.mergeMethod = opts.mergeMethod;
|
|
2402
|
+
if (opts.expectedHeadOid) variables.expectedHeadOid = opts.expectedHeadOid;
|
|
2403
|
+
const data = await this.http.githubGraphQL(mutation, variables);
|
|
2404
|
+
return data.enablePullRequestAutoMerge.pullRequest;
|
|
2405
|
+
}
|
|
2406
|
+
async disableAutoMerge(owner, repo, pullNumber) {
|
|
2407
|
+
const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
|
|
2408
|
+
const mutation = `
|
|
2409
|
+
mutation($pullRequestId: ID!) {
|
|
2410
|
+
disablePullRequestAutoMerge(input: { pullRequestId: $pullRequestId }) {
|
|
2411
|
+
pullRequest {
|
|
2412
|
+
number
|
|
2413
|
+
autoMergeRequest {
|
|
2414
|
+
mergeMethod
|
|
2415
|
+
enabledAt
|
|
2416
|
+
enabledBy { login }
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
`;
|
|
2422
|
+
const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
|
|
2423
|
+
return data.disablePullRequestAutoMerge.pullRequest;
|
|
2424
|
+
}
|
|
2425
|
+
async enqueuePR(opts) {
|
|
2426
|
+
const nodeId = await this.getPRNodeId(opts.owner, opts.repo, opts.pullNumber);
|
|
2427
|
+
const mutation = `
|
|
2428
|
+
mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID, $mergeMethod: PullRequestMergeMethod) {
|
|
2429
|
+
addPullRequestToMergeQueue(input: {
|
|
2430
|
+
pullRequestId: $pullRequestId
|
|
2431
|
+
expectedHeadOid: $expectedHeadOid
|
|
2432
|
+
mergeMethod: $mergeMethod
|
|
2433
|
+
}) {
|
|
2434
|
+
mergeQueue {
|
|
2435
|
+
url
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
`;
|
|
2440
|
+
const variables = { pullRequestId: nodeId };
|
|
2441
|
+
if (opts.expectedHeadOid) variables.expectedHeadOid = opts.expectedHeadOid;
|
|
2442
|
+
if (opts.mergeMethod) variables.mergeMethod = opts.mergeMethod;
|
|
2443
|
+
const data = await this.http.githubGraphQL(mutation, variables);
|
|
2444
|
+
return { mergeQueueUrl: data.addPullRequestToMergeQueue.mergeQueue.url };
|
|
2445
|
+
}
|
|
2446
|
+
// ── PR status aggregation ──────────────────────────────────────────
|
|
2447
|
+
async getPRStatus(owner, repo, pullNumber) {
|
|
2448
|
+
const query = `
|
|
2449
|
+
query($owner: String!, $repo: String!, $number: Int!) {
|
|
2450
|
+
repository(owner: $owner, name: $repo) {
|
|
2451
|
+
pullRequest(number: $number) {
|
|
2452
|
+
number
|
|
2453
|
+
title
|
|
2454
|
+
state
|
|
2455
|
+
isDraft
|
|
2456
|
+
merged
|
|
2457
|
+
mergeable
|
|
2458
|
+
mergeStateStatus
|
|
2459
|
+
reviewDecision
|
|
2460
|
+
autoMergeRequest {
|
|
2461
|
+
mergeMethod
|
|
2462
|
+
enabledAt
|
|
2463
|
+
enabledBy { login }
|
|
2464
|
+
}
|
|
2465
|
+
commits(last: 1) {
|
|
2466
|
+
nodes {
|
|
2467
|
+
commit {
|
|
2468
|
+
statusCheckRollup {
|
|
2469
|
+
contexts(first: 100) {
|
|
2470
|
+
nodes {
|
|
2471
|
+
... on CheckRun {
|
|
2472
|
+
__typename
|
|
2473
|
+
name
|
|
2474
|
+
status
|
|
2475
|
+
conclusion
|
|
2476
|
+
}
|
|
2477
|
+
... on StatusContext {
|
|
2478
|
+
__typename
|
|
2479
|
+
context
|
|
2480
|
+
state
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
`;
|
|
2492
|
+
const data = await this.http.githubGraphQL(query, { owner, repo, number: pullNumber });
|
|
2493
|
+
const pr = data.repository.pullRequest;
|
|
2494
|
+
const checkNodes = pr.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
|
|
2495
|
+
const details = checkNodes.map((n) => {
|
|
2496
|
+
if (n.__typename === "CheckRun") {
|
|
2497
|
+
return { name: n.name, status: n.status, conclusion: n.conclusion };
|
|
2498
|
+
}
|
|
2499
|
+
return { name: n.context, status: n.state, conclusion: null };
|
|
2500
|
+
});
|
|
2501
|
+
const passed = details.filter((d) => d.conclusion === "success" || d.status === "success").length;
|
|
2502
|
+
const failed = details.filter(
|
|
2503
|
+
(d) => d.conclusion === "failure" || d.conclusion === "timed_out" || d.conclusion === "action_required" || d.status === "failure" || d.status === "error"
|
|
2504
|
+
).length;
|
|
2505
|
+
const pending = details.filter(
|
|
2506
|
+
(d) => d.status === "queued" || d.status === "in_progress" || d.status === "pending"
|
|
2507
|
+
).length;
|
|
2508
|
+
const other = details.length - passed - failed - pending;
|
|
2509
|
+
return {
|
|
2510
|
+
number: pr.number,
|
|
2511
|
+
title: pr.title,
|
|
2512
|
+
state: pr.state,
|
|
2513
|
+
isDraft: pr.isDraft,
|
|
2514
|
+
merged: pr.merged,
|
|
2515
|
+
mergeable: pr.mergeable,
|
|
2516
|
+
mergeStateStatus: pr.mergeStateStatus,
|
|
2517
|
+
reviewDecision: pr.reviewDecision,
|
|
2518
|
+
autoMergeRequest: pr.autoMergeRequest ? {
|
|
2519
|
+
mergeMethod: pr.autoMergeRequest.mergeMethod,
|
|
2520
|
+
enabledAt: pr.autoMergeRequest.enabledAt,
|
|
2521
|
+
enabledBy: pr.autoMergeRequest.enabledBy.login
|
|
2522
|
+
} : null,
|
|
2523
|
+
checks: { total: details.length, passed, failed, pending, other, details }
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
// ── Draft / ready-for-review ───────────────────────────────────────
|
|
2527
|
+
async convertToDraft(owner, repo, pullNumber) {
|
|
2528
|
+
const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
|
|
2529
|
+
const mutation = `
|
|
2530
|
+
mutation($pullRequestId: ID!) {
|
|
2531
|
+
convertPullRequestToDraft(input: { pullRequestId: $pullRequestId }) {
|
|
2532
|
+
pullRequest { number isDraft }
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
`;
|
|
2536
|
+
const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
|
|
2537
|
+
return data.convertPullRequestToDraft.pullRequest;
|
|
2538
|
+
}
|
|
2539
|
+
async markReadyForReview(owner, repo, pullNumber) {
|
|
2540
|
+
const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
|
|
2541
|
+
const mutation = `
|
|
2542
|
+
mutation($pullRequestId: ID!) {
|
|
2543
|
+
markPullRequestReadyForReview(input: { pullRequestId: $pullRequestId }) {
|
|
2544
|
+
pullRequest { number isDraft }
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
`;
|
|
2548
|
+
const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
|
|
2549
|
+
return data.markPullRequestReadyForReview.pullRequest;
|
|
2550
|
+
}
|
|
2551
|
+
// ── Reopen ─────────────────────────────────────────────────────────
|
|
2552
|
+
async reopenPR(owner, repo, pullNumber) {
|
|
2553
|
+
return this.updatePR({ owner, repo, pullNumber, state: "open" });
|
|
2554
|
+
}
|
|
2555
|
+
// ── Labels ─────────────────────────────────────────────────────────
|
|
2556
|
+
async addLabels(owner, repo, issueNumber, labels) {
|
|
2557
|
+
return this.http.github(
|
|
2558
|
+
`/repos/${owner}/${repo}/issues/${issueNumber}/labels`,
|
|
2559
|
+
{ method: "POST", body: { labels } }
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
async removeLabel(owner, repo, issueNumber, label) {
|
|
2563
|
+
await this.http.github(
|
|
2564
|
+
`/repos/${owner}/${repo}/issues/${issueNumber}/labels/${label}`,
|
|
2565
|
+
{ method: "DELETE" }
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
// ── Reviewers ──────────────────────────────────────────────────────
|
|
2569
|
+
async addReviewers(opts) {
|
|
2570
|
+
const body = {};
|
|
2571
|
+
if (opts.reviewers?.length) body.reviewers = opts.reviewers;
|
|
2572
|
+
if (opts.teamReviewers?.length) body.team_reviewers = opts.teamReviewers;
|
|
2573
|
+
return this.http.github(
|
|
2574
|
+
`/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/requested_reviewers`,
|
|
2575
|
+
{ method: "POST", body }
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
async removeReviewers(opts) {
|
|
2579
|
+
const body = {};
|
|
2580
|
+
if (opts.reviewers?.length) body.reviewers = opts.reviewers;
|
|
2581
|
+
if (opts.teamReviewers?.length) body.team_reviewers = opts.teamReviewers;
|
|
2582
|
+
await this.http.github(
|
|
2583
|
+
`/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/requested_reviewers`,
|
|
2584
|
+
{ method: "DELETE", body }
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
// ── Assignees ─────────────────────────────────────────────────────
|
|
2588
|
+
async addAssignees(owner, repo, issueNumber, assignees) {
|
|
2589
|
+
return this.http.github(
|
|
2590
|
+
`/repos/${owner}/${repo}/issues/${issueNumber}/assignees`,
|
|
2591
|
+
{ method: "POST", body: { assignees } }
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
async removeAssignees(owner, repo, issueNumber, assignees) {
|
|
2595
|
+
return this.http.github(
|
|
2596
|
+
`/repos/${owner}/${repo}/issues/${issueNumber}/assignees`,
|
|
2597
|
+
{ method: "DELETE", body: { assignees } }
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2371
2600
|
};
|
|
2372
2601
|
|
|
2373
2602
|
// src/services/github/commands.ts
|
|
@@ -2506,7 +2735,7 @@ function registerGitHubCommands(program2) {
|
|
|
2506
2735
|
fail(err, "github", "update-pr", start);
|
|
2507
2736
|
}
|
|
2508
2737
|
});
|
|
2509
|
-
gh.command("merge-pr").description("Merge a pull request").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"]).default("merge")).option("--commit-title <title>", "Commit title for merge/squash").option("--commit-message <msg>", "Commit message for merge/squash").action(async (opts) => {
|
|
2738
|
+
gh.command("merge-pr").description("Merge a pull request (immediate merge \u2014 use enable-auto-merge for auto-merge)").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"]).default("merge")).option("--commit-title <title>", "Commit title for merge/squash").option("--commit-message <msg>", "Commit message for merge/squash").action(async (opts) => {
|
|
2510
2739
|
const start = Date.now();
|
|
2511
2740
|
try {
|
|
2512
2741
|
const { client, owner, repo } = getClient3(gh);
|
|
@@ -2523,6 +2752,48 @@ function registerGitHubCommands(program2) {
|
|
|
2523
2752
|
fail(err, "github", "merge-pr", start);
|
|
2524
2753
|
}
|
|
2525
2754
|
});
|
|
2755
|
+
gh.command("enable-auto-merge").description("Enable auto-merge on a pull request (merges automatically when all requirements are met)").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"])).option("--match-head-commit <sha>", "Only enable if the PR head matches this SHA (prevents race conditions)").action(async (opts) => {
|
|
2756
|
+
const start = Date.now();
|
|
2757
|
+
try {
|
|
2758
|
+
const { client, owner, repo } = getClient3(gh);
|
|
2759
|
+
const data = await client.enableAutoMerge({
|
|
2760
|
+
owner,
|
|
2761
|
+
repo,
|
|
2762
|
+
pullNumber: parseInt(opts.number, 10),
|
|
2763
|
+
mergeMethod: opts.method?.toUpperCase(),
|
|
2764
|
+
expectedHeadOid: opts.matchHeadCommit
|
|
2765
|
+
});
|
|
2766
|
+
success(data, "github", "enable-auto-merge", start);
|
|
2767
|
+
} catch (err) {
|
|
2768
|
+
fail(err, "github", "enable-auto-merge", start);
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
gh.command("disable-auto-merge").description("Disable auto-merge on a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
2772
|
+
const start = Date.now();
|
|
2773
|
+
try {
|
|
2774
|
+
const { client, owner, repo } = getClient3(gh);
|
|
2775
|
+
const data = await client.disableAutoMerge(owner, repo, parseInt(opts.number, 10));
|
|
2776
|
+
success(data, "github", "disable-auto-merge", start);
|
|
2777
|
+
} catch (err) {
|
|
2778
|
+
fail(err, "github", "disable-auto-merge", start);
|
|
2779
|
+
}
|
|
2780
|
+
});
|
|
2781
|
+
gh.command("enqueue-pr").description("Add a pull request to the repository merge queue").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"])).option("--match-head-commit <sha>", "Only enqueue if the PR head matches this SHA (prevents race conditions)").action(async (opts) => {
|
|
2782
|
+
const start = Date.now();
|
|
2783
|
+
try {
|
|
2784
|
+
const { client, owner, repo } = getClient3(gh);
|
|
2785
|
+
const data = await client.enqueuePR({
|
|
2786
|
+
owner,
|
|
2787
|
+
repo,
|
|
2788
|
+
pullNumber: parseInt(opts.number, 10),
|
|
2789
|
+
mergeMethod: opts.method?.toUpperCase(),
|
|
2790
|
+
expectedHeadOid: opts.matchHeadCommit
|
|
2791
|
+
});
|
|
2792
|
+
success(data, "github", "enqueue-pr", start);
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
fail(err, "github", "enqueue-pr", start);
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2526
2797
|
gh.command("close-pr").description("Close a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
2527
2798
|
const start = Date.now();
|
|
2528
2799
|
try {
|
|
@@ -2724,6 +2995,148 @@ function registerGitHubCommands(program2) {
|
|
|
2724
2995
|
fail(err, "github", "list-checks", start);
|
|
2725
2996
|
}
|
|
2726
2997
|
});
|
|
2998
|
+
gh.command("pr-status").description("Aggregated PR status: review decision, checks, mergeability, draft state, and auto-merge").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
2999
|
+
const start = Date.now();
|
|
3000
|
+
try {
|
|
3001
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3002
|
+
const data = await client.getPRStatus(owner, repo, parseInt(opts.number, 10));
|
|
3003
|
+
success(data, "github", "pr-status", start);
|
|
3004
|
+
} catch (err) {
|
|
3005
|
+
fail(err, "github", "pr-status", start);
|
|
3006
|
+
}
|
|
3007
|
+
});
|
|
3008
|
+
gh.command("convert-to-draft").description("Convert an open pull request to a draft").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
3009
|
+
const start = Date.now();
|
|
3010
|
+
try {
|
|
3011
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3012
|
+
const data = await client.convertToDraft(owner, repo, parseInt(opts.number, 10));
|
|
3013
|
+
success(data, "github", "convert-to-draft", start);
|
|
3014
|
+
} catch (err) {
|
|
3015
|
+
fail(err, "github", "convert-to-draft", start);
|
|
3016
|
+
}
|
|
3017
|
+
});
|
|
3018
|
+
gh.command("ready-for-review").description("Mark a draft pull request as ready for review").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
3019
|
+
const start = Date.now();
|
|
3020
|
+
try {
|
|
3021
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3022
|
+
const data = await client.markReadyForReview(owner, repo, parseInt(opts.number, 10));
|
|
3023
|
+
success(data, "github", "ready-for-review", start);
|
|
3024
|
+
} catch (err) {
|
|
3025
|
+
fail(err, "github", "ready-for-review", start);
|
|
3026
|
+
}
|
|
3027
|
+
});
|
|
3028
|
+
gh.command("reopen-pr").description("Reopen a closed pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
|
|
3029
|
+
const start = Date.now();
|
|
3030
|
+
try {
|
|
3031
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3032
|
+
const data = await client.reopenPR(owner, repo, parseInt(opts.number, 10));
|
|
3033
|
+
success(data, "github", "reopen-pr", start);
|
|
3034
|
+
} catch (err) {
|
|
3035
|
+
fail(err, "github", "reopen-pr", start);
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
3038
|
+
gh.command("add-labels").description("Add labels to a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--label <label>", "Label to add (repeatable)", (v, acc) => {
|
|
3039
|
+
acc.push(v);
|
|
3040
|
+
return acc;
|
|
3041
|
+
}, []).action(async (opts) => {
|
|
3042
|
+
const start = Date.now();
|
|
3043
|
+
try {
|
|
3044
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3045
|
+
if (!opts.label.length) throw new PncliError("At least one --label is required", 1);
|
|
3046
|
+
const data = await client.addLabels(owner, repo, parseInt(opts.number, 10), opts.label);
|
|
3047
|
+
success(data, "github", "add-labels", start);
|
|
3048
|
+
} catch (err) {
|
|
3049
|
+
fail(err, "github", "add-labels", start);
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
gh.command("remove-label").description("Remove a label from a pull request or issue").requiredOption("--number <n>", "PR or issue number").requiredOption("--label <label>", "Label name to remove").action(async (opts) => {
|
|
3053
|
+
const start = Date.now();
|
|
3054
|
+
try {
|
|
3055
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3056
|
+
await client.removeLabel(owner, repo, parseInt(opts.number, 10), opts.label);
|
|
3057
|
+
success({ removed: true, label: opts.label }, "github", "remove-label", start);
|
|
3058
|
+
} catch (err) {
|
|
3059
|
+
fail(err, "github", "remove-label", start);
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
gh.command("add-reviewers").description("Request reviewers on a pull request").requiredOption("--number <n>", "Pull request number").option("--reviewer <login>", "User reviewer login (repeatable)", (v, acc) => {
|
|
3063
|
+
acc.push(v);
|
|
3064
|
+
return acc;
|
|
3065
|
+
}, []).option("--team-reviewer <slug>", "Team slug to request (repeatable)", (v, acc) => {
|
|
3066
|
+
acc.push(v);
|
|
3067
|
+
return acc;
|
|
3068
|
+
}, []).action(async (opts) => {
|
|
3069
|
+
const start = Date.now();
|
|
3070
|
+
try {
|
|
3071
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3072
|
+
if (!opts.reviewer.length && !opts.teamReviewer.length) {
|
|
3073
|
+
throw new PncliError("At least one --reviewer or --team-reviewer is required", 1);
|
|
3074
|
+
}
|
|
3075
|
+
const data = await client.addReviewers({
|
|
3076
|
+
owner,
|
|
3077
|
+
repo,
|
|
3078
|
+
pullNumber: parseInt(opts.number, 10),
|
|
3079
|
+
reviewers: opts.reviewer.length ? opts.reviewer : void 0,
|
|
3080
|
+
teamReviewers: opts.teamReviewer.length ? opts.teamReviewer : void 0
|
|
3081
|
+
});
|
|
3082
|
+
success(data, "github", "add-reviewers", start);
|
|
3083
|
+
} catch (err) {
|
|
3084
|
+
fail(err, "github", "add-reviewers", start);
|
|
3085
|
+
}
|
|
3086
|
+
});
|
|
3087
|
+
gh.command("remove-reviewers").description("Remove requested reviewers from a pull request").requiredOption("--number <n>", "Pull request number").option("--reviewer <login>", "User reviewer login to remove (repeatable)", (v, acc) => {
|
|
3088
|
+
acc.push(v);
|
|
3089
|
+
return acc;
|
|
3090
|
+
}, []).option("--team-reviewer <slug>", "Team slug to remove (repeatable)", (v, acc) => {
|
|
3091
|
+
acc.push(v);
|
|
3092
|
+
return acc;
|
|
3093
|
+
}, []).action(async (opts) => {
|
|
3094
|
+
const start = Date.now();
|
|
3095
|
+
try {
|
|
3096
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3097
|
+
if (!opts.reviewer.length && !opts.teamReviewer.length) {
|
|
3098
|
+
throw new PncliError("At least one --reviewer or --team-reviewer is required", 1);
|
|
3099
|
+
}
|
|
3100
|
+
await client.removeReviewers({
|
|
3101
|
+
owner,
|
|
3102
|
+
repo,
|
|
3103
|
+
pullNumber: parseInt(opts.number, 10),
|
|
3104
|
+
reviewers: opts.reviewer.length ? opts.reviewer : void 0,
|
|
3105
|
+
teamReviewers: opts.teamReviewer.length ? opts.teamReviewer : void 0
|
|
3106
|
+
});
|
|
3107
|
+
success({ removed: true }, "github", "remove-reviewers", start);
|
|
3108
|
+
} catch (err) {
|
|
3109
|
+
fail(err, "github", "remove-reviewers", start);
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
gh.command("add-assignees").description("Add assignees to a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--assignee <login>", "Assignee login (repeatable)", (v, acc) => {
|
|
3113
|
+
acc.push(v);
|
|
3114
|
+
return acc;
|
|
3115
|
+
}, []).action(async (opts) => {
|
|
3116
|
+
const start = Date.now();
|
|
3117
|
+
try {
|
|
3118
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3119
|
+
if (!opts.assignee.length) throw new PncliError("At least one --assignee is required", 1);
|
|
3120
|
+
const data = await client.addAssignees(owner, repo, parseInt(opts.number, 10), opts.assignee);
|
|
3121
|
+
success(data, "github", "add-assignees", start);
|
|
3122
|
+
} catch (err) {
|
|
3123
|
+
fail(err, "github", "add-assignees", start);
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
gh.command("remove-assignees").description("Remove assignees from a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--assignee <login>", "Assignee login to remove (repeatable)", (v, acc) => {
|
|
3127
|
+
acc.push(v);
|
|
3128
|
+
return acc;
|
|
3129
|
+
}, []).action(async (opts) => {
|
|
3130
|
+
const start = Date.now();
|
|
3131
|
+
try {
|
|
3132
|
+
const { client, owner, repo } = getClient3(gh);
|
|
3133
|
+
if (!opts.assignee.length) throw new PncliError("At least one --assignee is required", 1);
|
|
3134
|
+
const data = await client.removeAssignees(owner, repo, parseInt(opts.number, 10), opts.assignee);
|
|
3135
|
+
success(data, "github", "remove-assignees", start);
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
fail(err, "github", "remove-assignees", start);
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
2727
3140
|
}
|
|
2728
3141
|
|
|
2729
3142
|
// src/services/confluence/commands.ts
|
|
@@ -5901,16 +6314,6 @@ function registerConfigCommands(program2) {
|
|
|
5901
6314
|
} else {
|
|
5902
6315
|
results.artifactory = { ok: null, message: "not configured" };
|
|
5903
6316
|
}
|
|
5904
|
-
if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
|
|
5905
|
-
try {
|
|
5906
|
-
await http.udeploy("/cli/application");
|
|
5907
|
-
results.udeploy = { ok: true, message: "connected" };
|
|
5908
|
-
} catch (err) {
|
|
5909
|
-
results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5910
|
-
}
|
|
5911
|
-
} else {
|
|
5912
|
-
results.udeploy = { ok: null, message: "not configured" };
|
|
5913
|
-
}
|
|
5914
6317
|
if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
|
|
5915
6318
|
try {
|
|
5916
6319
|
await http.checkmarx("projects", { params: { limit: 1 } });
|
|
@@ -6003,6 +6406,26 @@ function registerConfigCommands(program2) {
|
|
|
6003
6406
|
} else {
|
|
6004
6407
|
results.logscale = { ok: null, message: "not configured" };
|
|
6005
6408
|
}
|
|
6409
|
+
if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
|
|
6410
|
+
try {
|
|
6411
|
+
await http.splitio("/internal/api/v2/workspaces");
|
|
6412
|
+
results.splitio = { ok: true, message: "connected" };
|
|
6413
|
+
} catch (err) {
|
|
6414
|
+
results.splitio = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6415
|
+
}
|
|
6416
|
+
} else {
|
|
6417
|
+
results.splitio = { ok: null, message: "not configured" };
|
|
6418
|
+
}
|
|
6419
|
+
if (cfg.figma.baseUrl && cfg.figma.token) {
|
|
6420
|
+
try {
|
|
6421
|
+
await http.figma("/v1/me");
|
|
6422
|
+
results.figma = { ok: true, message: "connected" };
|
|
6423
|
+
} catch (err) {
|
|
6424
|
+
results.figma = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6425
|
+
}
|
|
6426
|
+
} else {
|
|
6427
|
+
results.figma = { ok: null, message: "not configured" };
|
|
6428
|
+
}
|
|
6006
6429
|
success(results, "config", "test", start);
|
|
6007
6430
|
} catch (err) {
|
|
6008
6431
|
fail(err, "config", "test", start);
|
|
@@ -6127,18 +6550,6 @@ function registerConfigCommands(program2) {
|
|
|
6127
6550
|
results.jenkins = categorize2(err);
|
|
6128
6551
|
}
|
|
6129
6552
|
}
|
|
6130
|
-
if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
|
|
6131
|
-
results.udeploy = { status: "blank", message: "not configured" };
|
|
6132
|
-
} else if (!cfg.udeploy.baseUrl) {
|
|
6133
|
-
results.udeploy = { status: "error", message: "baseUrl not configured" };
|
|
6134
|
-
} else {
|
|
6135
|
-
try {
|
|
6136
|
-
await http.udeploy("/cli/application", { timeoutMs: 1e4 });
|
|
6137
|
-
results.udeploy = { status: "valid", message: "ok" };
|
|
6138
|
-
} catch (err) {
|
|
6139
|
-
results.udeploy = categorize2(err);
|
|
6140
|
-
}
|
|
6141
|
-
}
|
|
6142
6553
|
const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
|
|
6143
6554
|
if (!artResult.configured) {
|
|
6144
6555
|
results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
|
|
@@ -6259,11 +6670,35 @@ function registerConfigCommands(program2) {
|
|
|
6259
6670
|
results.logscale = categorize2(err);
|
|
6260
6671
|
}
|
|
6261
6672
|
}
|
|
6673
|
+
if (!cfg.splitio.adminApiKey) {
|
|
6674
|
+
results.splitio = { status: "blank", message: "not configured" };
|
|
6675
|
+
} else if (!cfg.splitio.baseUrl) {
|
|
6676
|
+
results.splitio = { status: "error", message: "baseUrl not configured" };
|
|
6677
|
+
} else {
|
|
6678
|
+
try {
|
|
6679
|
+
await http.splitio("/internal/api/v2/workspaces", { timeoutMs: 1e4 });
|
|
6680
|
+
results.splitio = { status: "valid", message: "ok" };
|
|
6681
|
+
} catch (err) {
|
|
6682
|
+
results.splitio = categorize2(err);
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
if (!cfg.figma.token) {
|
|
6686
|
+
results.figma = { status: "blank", message: "not configured" };
|
|
6687
|
+
} else if (!cfg.figma.baseUrl) {
|
|
6688
|
+
results.figma = { status: "error", message: "baseUrl not configured" };
|
|
6689
|
+
} else {
|
|
6690
|
+
try {
|
|
6691
|
+
await http.figma("/v1/me", { timeoutMs: 1e4 });
|
|
6692
|
+
results.figma = { status: "valid", message: "ok" };
|
|
6693
|
+
} catch (err) {
|
|
6694
|
+
results.figma = categorize2(err);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6262
6697
|
const statuses = Object.values(results).map((r) => r.status);
|
|
6263
6698
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
6264
6699
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
6265
6700
|
if (cmdOpts.output === "table") {
|
|
6266
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "
|
|
6701
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
|
|
6267
6702
|
const labelWidth = 14;
|
|
6268
6703
|
const statusWidth = 9;
|
|
6269
6704
|
for (const svc of services) {
|
|
@@ -6277,7 +6712,7 @@ function registerConfigCommands(program2) {
|
|
|
6277
6712
|
process.stdout.write("\n");
|
|
6278
6713
|
} else {
|
|
6279
6714
|
if (opts.pretty) {
|
|
6280
|
-
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "
|
|
6715
|
+
const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
|
|
6281
6716
|
const labelWidth = 14;
|
|
6282
6717
|
const statusWidth = 9;
|
|
6283
6718
|
for (const svc of services) {
|
|
@@ -6297,11 +6732,6 @@ function registerConfigCommands(program2) {
|
|
|
6297
6732
|
}
|
|
6298
6733
|
});
|
|
6299
6734
|
}
|
|
6300
|
-
function normalizeBaseUrl(raw) {
|
|
6301
|
-
const trimmed = raw.trim();
|
|
6302
|
-
if (!trimmed) return trimmed;
|
|
6303
|
-
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
6304
|
-
}
|
|
6305
6735
|
async function initGlobalConfig(start) {
|
|
6306
6736
|
process.stderr.write("pncli config init \u2014 Global configuration\n\n");
|
|
6307
6737
|
process.stderr.write("\u2500\u2500 Identity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
|
|
@@ -6529,7 +6959,7 @@ async function initGlobalConfig(start) {
|
|
|
6529
6959
|
}
|
|
6530
6960
|
process.stderr.write("\n\u2500\u2500 Jenkins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
6531
6961
|
const useJenkins = await confirm({
|
|
6532
|
-
message: "Configure Jenkins
|
|
6962
|
+
message: "Configure Jenkins for pipeline operations?",
|
|
6533
6963
|
default: false
|
|
6534
6964
|
});
|
|
6535
6965
|
let jenkinsBaseUrl = "";
|
|
@@ -6548,66 +6978,6 @@ async function initGlobalConfig(start) {
|
|
|
6548
6978
|
message: "Jenkins API token:"
|
|
6549
6979
|
});
|
|
6550
6980
|
}
|
|
6551
|
-
process.stderr.write("\n\u2500\u2500 IBM UrbanCode Deploy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
6552
|
-
const useUdeploy = await confirm({
|
|
6553
|
-
message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
|
|
6554
|
-
default: false
|
|
6555
|
-
});
|
|
6556
|
-
let udeployBaseUrl = "";
|
|
6557
|
-
let udeployPat = "";
|
|
6558
|
-
let udeployUsername = "";
|
|
6559
|
-
let udeployPassword = "";
|
|
6560
|
-
let udeployDefaultApp = "";
|
|
6561
|
-
let udeployDefaultEnv = "";
|
|
6562
|
-
if (useUdeploy) {
|
|
6563
|
-
udeployBaseUrl = await input({
|
|
6564
|
-
message: "UDeploy base URL (e.g. ucd.imagile.dev:8443):",
|
|
6565
|
-
default: ""
|
|
6566
|
-
});
|
|
6567
|
-
udeployUsername = await input({
|
|
6568
|
-
message: "UDeploy username (leave blank to use a PAT instead):",
|
|
6569
|
-
default: ""
|
|
6570
|
-
});
|
|
6571
|
-
if (udeployUsername) {
|
|
6572
|
-
udeployPassword = await password({
|
|
6573
|
-
message: "UDeploy password:",
|
|
6574
|
-
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
6575
|
-
});
|
|
6576
|
-
} else {
|
|
6577
|
-
udeployPat = await password({
|
|
6578
|
-
message: "UDeploy personal access token:"
|
|
6579
|
-
});
|
|
6580
|
-
}
|
|
6581
|
-
udeployDefaultApp = await input({
|
|
6582
|
-
message: "Default application name (optional):",
|
|
6583
|
-
default: ""
|
|
6584
|
-
});
|
|
6585
|
-
udeployDefaultEnv = await input({
|
|
6586
|
-
message: "Default environment name (optional):",
|
|
6587
|
-
default: ""
|
|
6588
|
-
});
|
|
6589
|
-
const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
|
|
6590
|
-
if (hasCredentials) {
|
|
6591
|
-
process.stderr.write("\n Verifying connection...\n");
|
|
6592
|
-
try {
|
|
6593
|
-
const tempConfig = {
|
|
6594
|
-
...loadConfig(),
|
|
6595
|
-
udeploy: {
|
|
6596
|
-
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
6597
|
-
pat: udeployPat || void 0,
|
|
6598
|
-
username: udeployUsername || void 0,
|
|
6599
|
-
password: udeployPassword || void 0
|
|
6600
|
-
}
|
|
6601
|
-
};
|
|
6602
|
-
const tempHttp = createHttpClient(tempConfig);
|
|
6603
|
-
await tempHttp.udeploy("/cli/application");
|
|
6604
|
-
process.stderr.write(" Connected.\n");
|
|
6605
|
-
} catch (err) {
|
|
6606
|
-
warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
6607
|
-
warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
|
|
6608
|
-
}
|
|
6609
|
-
}
|
|
6610
|
-
}
|
|
6611
6981
|
process.stderr.write("\n\u2500\u2500 Checkmarx \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
6612
6982
|
const useCheckmarx = await confirm({
|
|
6613
6983
|
message: "Configure Checkmarx One for vulnerability scanning?",
|
|
@@ -6934,16 +7304,80 @@ async function initGlobalConfig(start) {
|
|
|
6934
7304
|
}
|
|
6935
7305
|
}
|
|
6936
7306
|
}
|
|
6937
|
-
process.stderr.write("\n\u2500\u2500
|
|
6938
|
-
const
|
|
6939
|
-
message: "
|
|
6940
|
-
default:
|
|
7307
|
+
process.stderr.write("\n\u2500\u2500 Split.IO \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
7308
|
+
const useSplitio = await confirm({
|
|
7309
|
+
message: "Configure Split.IO for feature flag administration?",
|
|
7310
|
+
default: false
|
|
6941
7311
|
});
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
7312
|
+
let splitioBaseUrl = "";
|
|
7313
|
+
let splitioAdminApiKey = "";
|
|
7314
|
+
if (useSplitio) {
|
|
7315
|
+
splitioBaseUrl = await input({
|
|
7316
|
+
message: "Split.IO Admin API base URL (e.g. https://api.split.io):",
|
|
7317
|
+
default: ""
|
|
7318
|
+
});
|
|
7319
|
+
splitioAdminApiKey = await password({
|
|
7320
|
+
message: "Split.IO Admin API key:"
|
|
7321
|
+
});
|
|
7322
|
+
if (splitioBaseUrl && splitioAdminApiKey) {
|
|
7323
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
7324
|
+
try {
|
|
7325
|
+
const tempConfig = {
|
|
7326
|
+
...loadConfig(),
|
|
7327
|
+
splitio: {
|
|
7328
|
+
baseUrl: normalizeBaseUrl(splitioBaseUrl),
|
|
7329
|
+
adminApiKey: splitioAdminApiKey
|
|
7330
|
+
}
|
|
7331
|
+
};
|
|
7332
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
7333
|
+
await tempHttp.splitio("/internal/api/v2/workspaces");
|
|
7334
|
+
process.stderr.write(" Connected.\n");
|
|
7335
|
+
} catch (err) {
|
|
7336
|
+
warn(`Could not connect to Split.IO: ${err instanceof Error ? err.message : String(err)}`);
|
|
7337
|
+
warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
process.stderr.write("\n\u2500\u2500 Figma \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
|
|
7342
|
+
const useFigma = await confirm({
|
|
7343
|
+
message: "Configure Figma for reading design files and comments?",
|
|
7344
|
+
default: false
|
|
7345
|
+
});
|
|
7346
|
+
let figmaToken = "";
|
|
7347
|
+
if (useFigma) {
|
|
7348
|
+
process.stderr.write(" Generate a personal access token in Figma under Account Settings \u2192 Personal access tokens.\n");
|
|
7349
|
+
figmaToken = await password({
|
|
7350
|
+
message: "Figma personal access token:"
|
|
7351
|
+
});
|
|
7352
|
+
if (figmaToken) {
|
|
7353
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
7354
|
+
try {
|
|
7355
|
+
const tempConfig = {
|
|
7356
|
+
...loadConfig(),
|
|
7357
|
+
figma: {
|
|
7358
|
+
baseUrl: "https://api.figma.com",
|
|
7359
|
+
token: figmaToken
|
|
7360
|
+
}
|
|
7361
|
+
};
|
|
7362
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
7363
|
+
await tempHttp.figma("/v1/me");
|
|
7364
|
+
process.stderr.write(" Connected.\n");
|
|
7365
|
+
} catch (err) {
|
|
7366
|
+
warn(`Could not connect to Figma: ${err instanceof Error ? err.message : String(err)}`);
|
|
7367
|
+
warn("Config will be saved anyway. Check your token and re-run pncli config init or pncli config test.");
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
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");
|
|
7372
|
+
const jiraProject = await input({
|
|
7373
|
+
message: "Default Jira project key (optional):",
|
|
7374
|
+
default: ""
|
|
7375
|
+
});
|
|
7376
|
+
const sonarProject = useSonar ? await input({
|
|
7377
|
+
message: "Default SonarQube project key (optional):",
|
|
7378
|
+
default: ""
|
|
7379
|
+
}) : "";
|
|
7380
|
+
const sdeProject = useSde ? await input({
|
|
6947
7381
|
message: "Default SDElements project ID (optional, numeric):",
|
|
6948
7382
|
default: ""
|
|
6949
7383
|
}) : "";
|
|
@@ -6957,7 +7391,10 @@ async function initGlobalConfig(start) {
|
|
|
6957
7391
|
process.exitCode = ExitCode.SUCCESS;
|
|
6958
7392
|
return;
|
|
6959
7393
|
}
|
|
7394
|
+
const existingGlobal = loadJsonFile(getGlobalConfigPath());
|
|
7395
|
+
const existingInstances = Array.isArray(existingGlobal?.jenkinsInstances) ? existingGlobal.jenkinsInstances : [];
|
|
6960
7396
|
writeGlobalConfig({
|
|
7397
|
+
jenkinsInstances: existingInstances,
|
|
6961
7398
|
user: {
|
|
6962
7399
|
email: userEmail || void 0,
|
|
6963
7400
|
userId: userId || void 0
|
|
@@ -7018,14 +7455,6 @@ async function initGlobalConfig(start) {
|
|
|
7018
7455
|
...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
|
|
7019
7456
|
}
|
|
7020
7457
|
} : {},
|
|
7021
|
-
...useUdeploy && udeployBaseUrl ? {
|
|
7022
|
-
udeploy: {
|
|
7023
|
-
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
7024
|
-
...udeployPat ? { pat: udeployPat } : {},
|
|
7025
|
-
...udeployUsername ? { username: udeployUsername } : {},
|
|
7026
|
-
...udeployPassword ? { password: udeployPassword } : {}
|
|
7027
|
-
}
|
|
7028
|
-
} : {},
|
|
7029
7458
|
...useCheckmarx && checkmarxBaseUrl ? {
|
|
7030
7459
|
checkmarx: {
|
|
7031
7460
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
@@ -7079,6 +7508,18 @@ async function initGlobalConfig(start) {
|
|
|
7079
7508
|
token: logscaleToken || void 0
|
|
7080
7509
|
}
|
|
7081
7510
|
} : {},
|
|
7511
|
+
...useSplitio && splitioBaseUrl ? {
|
|
7512
|
+
splitio: {
|
|
7513
|
+
baseUrl: normalizeBaseUrl(splitioBaseUrl),
|
|
7514
|
+
adminApiKey: splitioAdminApiKey || void 0
|
|
7515
|
+
}
|
|
7516
|
+
} : {},
|
|
7517
|
+
...useFigma && figmaToken ? {
|
|
7518
|
+
figma: {
|
|
7519
|
+
baseUrl: "https://api.figma.com",
|
|
7520
|
+
token: figmaToken
|
|
7521
|
+
}
|
|
7522
|
+
} : {},
|
|
7082
7523
|
defaults: {
|
|
7083
7524
|
jira: {
|
|
7084
7525
|
project: jiraProject || void 0
|
|
@@ -7098,12 +7539,6 @@ async function initGlobalConfig(start) {
|
|
|
7098
7539
|
collection: adoCollection || void 0,
|
|
7099
7540
|
project: adoProject || void 0
|
|
7100
7541
|
}
|
|
7101
|
-
} : {},
|
|
7102
|
-
...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
|
|
7103
|
-
udeploy: {
|
|
7104
|
-
application: udeployDefaultApp || void 0,
|
|
7105
|
-
environment: udeployDefaultEnv || void 0
|
|
7106
|
-
}
|
|
7107
7542
|
} : {}
|
|
7108
7543
|
}
|
|
7109
7544
|
});
|
|
@@ -7835,10 +8270,10 @@ function registerAdoWorkCommands(ado) {
|
|
|
7835
8270
|
const types = await discoverTypes(workClient, collection, project);
|
|
7836
8271
|
if (opts.save) {
|
|
7837
8272
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
7838
|
-
const
|
|
7839
|
-
const existing = JSON.parse(
|
|
8273
|
+
const fs6 = await import("fs");
|
|
8274
|
+
const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
7840
8275
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
7841
|
-
|
|
8276
|
+
fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
7842
8277
|
warn(`Saved ${types.length} types to ${globalConfigPath}`);
|
|
7843
8278
|
}
|
|
7844
8279
|
success(types, "ado", "work-types", start);
|
|
@@ -7867,11 +8302,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
7867
8302
|
) : allFields;
|
|
7868
8303
|
if (opts.save) {
|
|
7869
8304
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
7870
|
-
const
|
|
7871
|
-
const existing = JSON.parse(
|
|
8305
|
+
const fs6 = await import("fs");
|
|
8306
|
+
const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
7872
8307
|
const aliases = buildDefaultAliases(allFields);
|
|
7873
8308
|
existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
|
|
7874
|
-
|
|
8309
|
+
fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
7875
8310
|
warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
|
|
7876
8311
|
}
|
|
7877
8312
|
success(fields, "ado", "work-fields", start);
|
|
@@ -8168,11 +8603,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
8168
8603
|
const start = Date.now();
|
|
8169
8604
|
try {
|
|
8170
8605
|
const globalOpts = ado.optsWithGlobals();
|
|
8171
|
-
const { loadConfig: lc } = await import("./config-
|
|
8606
|
+
const { loadConfig: lc } = await import("./config-YHYQTGKT.js");
|
|
8172
8607
|
const config = lc({ configPath: globalOpts.config });
|
|
8173
8608
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
8174
8609
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
8175
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
8610
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-6QJU2SWP.js");
|
|
8176
8611
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
8177
8612
|
const coreClient = new AdoCoreClient2(http);
|
|
8178
8613
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8430,11 +8865,22 @@ var JenkinsClient = class {
|
|
|
8430
8865
|
};
|
|
8431
8866
|
|
|
8432
8867
|
// src/services/jenkins/commands.ts
|
|
8433
|
-
function getClient7(
|
|
8434
|
-
const opts =
|
|
8868
|
+
function getClient7(cmd) {
|
|
8869
|
+
const opts = cmd.optsWithGlobals();
|
|
8435
8870
|
const config = loadConfig({ configPath: opts.config });
|
|
8436
|
-
|
|
8437
|
-
|
|
8871
|
+
let jenkins = config.jenkins;
|
|
8872
|
+
if (opts.instance) {
|
|
8873
|
+
const inst = config.jenkinsInstances.find((i) => i.name === opts.instance);
|
|
8874
|
+
if (!inst) throw new PncliError(`Jenkins instance "${opts.instance}" not found. Add it to your global config using pncli config set jenkinsInstances.`);
|
|
8875
|
+
jenkins = { baseUrl: inst.baseUrl, username: inst.username, apiToken: inst.apiToken };
|
|
8876
|
+
}
|
|
8877
|
+
if (!jenkins.baseUrl) {
|
|
8878
|
+
if (opts.instance) {
|
|
8879
|
+
throw new PncliError(`Jenkins instance "${opts.instance}" has no baseUrl configured. Add a baseUrl to that entry in jenkinsInstances.`);
|
|
8880
|
+
}
|
|
8881
|
+
throw new PncliError("Jenkins not configured. Run: pncli config init");
|
|
8882
|
+
}
|
|
8883
|
+
const http = createHttpClient({ ...config, jenkins }, Boolean(opts.dryRun));
|
|
8438
8884
|
return new JenkinsClient(http);
|
|
8439
8885
|
}
|
|
8440
8886
|
async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
|
|
@@ -8459,12 +8905,70 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
|
|
|
8459
8905
|
throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
|
|
8460
8906
|
}
|
|
8461
8907
|
function registerJenkinsCommands(program2) {
|
|
8462
|
-
const jenkins = program2.command("jenkins").description("Jenkins
|
|
8908
|
+
const jenkins = program2.command("jenkins").description("Jenkins operations").option("--instance <name>", "Named Jenkins instance from global config (overrides default jenkins config)");
|
|
8463
8909
|
const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
|
|
8910
|
+
const instance = jenkins.command("instance").description("Manage named Jenkins instances in the global config");
|
|
8911
|
+
instance.command("list").description("List configured Jenkins instances (API tokens masked)").action(() => {
|
|
8912
|
+
const start = Date.now();
|
|
8913
|
+
try {
|
|
8914
|
+
const opts = jenkins.optsWithGlobals();
|
|
8915
|
+
const config = loadConfig({ configPath: opts.config });
|
|
8916
|
+
const instances = config.jenkinsInstances.map((i) => ({
|
|
8917
|
+
...i,
|
|
8918
|
+
apiToken: i.apiToken ? "***" : void 0
|
|
8919
|
+
}));
|
|
8920
|
+
success({ instances }, "jenkins", "instance-list", start);
|
|
8921
|
+
} catch (err) {
|
|
8922
|
+
fail(err, "jenkins", "instance-list", start);
|
|
8923
|
+
}
|
|
8924
|
+
});
|
|
8925
|
+
instance.command("add").description("Add a named Jenkins instance to the global config (appends \u2014 existing instances are preserved)").requiredOption("--name <name>", "Instance name used with --instance").requiredOption("--base-url <url>", "Jenkins base URL (e.g. jenkins.imagile.dev)").option("--username <user>", "Jenkins username").option("--api-token <token>", "Jenkins API token (prompted for when omitted on an interactive terminal, keeping it out of shell history)").option("--force", "Overwrite an instance that already uses this name").action(async (o) => {
|
|
8926
|
+
const start = Date.now();
|
|
8927
|
+
try {
|
|
8928
|
+
const opts = jenkins.optsWithGlobals();
|
|
8929
|
+
const configPath = opts.config;
|
|
8930
|
+
const existing = loadConfig({ configPath }).jenkinsInstances;
|
|
8931
|
+
const idx = existing.findIndex((i) => i.name === o.name);
|
|
8932
|
+
if (idx >= 0 && !o.force) {
|
|
8933
|
+
throw new PncliError(`Jenkins instance "${o.name}" already exists. Pass --force to overwrite it, or run: pncli jenkins instance remove --name ${o.name}`, 1);
|
|
8934
|
+
}
|
|
8935
|
+
let apiToken = o.apiToken;
|
|
8936
|
+
if (!apiToken && process.stdin.isTTY) {
|
|
8937
|
+
const { default: password2 } = await import("@inquirer/password");
|
|
8938
|
+
apiToken = await password2({ message: `API token for "${o.name}" (leave blank to skip):` });
|
|
8939
|
+
}
|
|
8940
|
+
const entry = {
|
|
8941
|
+
name: o.name,
|
|
8942
|
+
baseUrl: normalizeBaseUrl(o.baseUrl),
|
|
8943
|
+
...o.username ? { username: o.username } : {},
|
|
8944
|
+
...apiToken ? { apiToken } : {}
|
|
8945
|
+
};
|
|
8946
|
+
const next = idx >= 0 ? existing.map((i, n) => n === idx ? entry : i) : [...existing, entry];
|
|
8947
|
+
setConfigValue("jenkinsInstances", JSON.stringify(next), configPath);
|
|
8948
|
+
success({ name: entry.name, baseUrl: entry.baseUrl, overwritten: idx >= 0 }, "jenkins", "instance-add", start);
|
|
8949
|
+
} catch (err) {
|
|
8950
|
+
fail(err, "jenkins", "instance-add", start);
|
|
8951
|
+
}
|
|
8952
|
+
});
|
|
8953
|
+
instance.command("remove").description("Remove a named Jenkins instance from the global config").requiredOption("--name <name>", "Instance name to remove").action((o) => {
|
|
8954
|
+
const start = Date.now();
|
|
8955
|
+
try {
|
|
8956
|
+
const opts = jenkins.optsWithGlobals();
|
|
8957
|
+
const configPath = opts.config;
|
|
8958
|
+
const existing = loadConfig({ configPath }).jenkinsInstances;
|
|
8959
|
+
if (!existing.some((i) => i.name === o.name)) {
|
|
8960
|
+
throw new PncliError(`Jenkins instance "${o.name}" not found. Run: pncli jenkins instance list`, 1);
|
|
8961
|
+
}
|
|
8962
|
+
setConfigValue("jenkinsInstances", JSON.stringify(existing.filter((i) => i.name !== o.name)), configPath);
|
|
8963
|
+
success({ removed: o.name }, "jenkins", "instance-remove", start);
|
|
8964
|
+
} catch (err) {
|
|
8965
|
+
fail(err, "jenkins", "instance-remove", start);
|
|
8966
|
+
}
|
|
8967
|
+
});
|
|
8464
8968
|
pipeline.command("list").description("List all jobs on the Jenkins instance (or within a folder)").option("--folder <name>", "Folder name to enumerate (supports nested folders: parentFolder/childFolder)").action(async (opts) => {
|
|
8465
8969
|
const start = Date.now();
|
|
8466
8970
|
try {
|
|
8467
|
-
const client = getClient7(
|
|
8971
|
+
const client = getClient7(jenkins);
|
|
8468
8972
|
const data = await client.listJobs(opts.folder);
|
|
8469
8973
|
success(data, "jenkins", "pipeline-list", start);
|
|
8470
8974
|
} catch (err) {
|
|
@@ -8474,7 +8978,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8474
8978
|
pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").action(async (opts) => {
|
|
8475
8979
|
const start = Date.now();
|
|
8476
8980
|
try {
|
|
8477
|
-
const client = getClient7(
|
|
8981
|
+
const client = getClient7(jenkins);
|
|
8478
8982
|
const data = await client.getJob(opts.name);
|
|
8479
8983
|
success(data, "jenkins", "pipeline-get", start);
|
|
8480
8984
|
} catch (err) {
|
|
@@ -8487,7 +8991,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8487
8991
|
}, []).option("--wait", "Wait for the build to complete before returning").option("--timeout <s>", "Max wait time in seconds per phase (queue-wait and build-wait each get this budget; default 600)", "600").option("--poll <s>", "Poll interval in seconds (default 10)", "10").action(async (opts) => {
|
|
8488
8992
|
const start = Date.now();
|
|
8489
8993
|
try {
|
|
8490
|
-
const client = getClient7(
|
|
8994
|
+
const client = getClient7(jenkins);
|
|
8491
8995
|
for (const p of opts.parameter) {
|
|
8492
8996
|
if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
|
|
8493
8997
|
}
|
|
@@ -8520,7 +9024,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8520
9024
|
pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
|
|
8521
9025
|
const start = Date.now();
|
|
8522
9026
|
try {
|
|
8523
|
-
const client = getClient7(
|
|
9027
|
+
const client = getClient7(jenkins);
|
|
8524
9028
|
const top = parseInt(opts.top, 10);
|
|
8525
9029
|
if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
|
|
8526
9030
|
const data = await client.listBuilds(opts.name, top);
|
|
@@ -8532,7 +9036,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8532
9036
|
pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
8533
9037
|
const start = Date.now();
|
|
8534
9038
|
try {
|
|
8535
|
-
const client = getClient7(
|
|
9039
|
+
const client = getClient7(jenkins);
|
|
8536
9040
|
const buildNumber = parseInt(opts.number, 10);
|
|
8537
9041
|
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
8538
9042
|
const data = await client.getBuild(opts.name, buildNumber);
|
|
@@ -8544,7 +9048,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8544
9048
|
pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
8545
9049
|
const start = Date.now();
|
|
8546
9050
|
try {
|
|
8547
|
-
const client = getClient7(
|
|
9051
|
+
const client = getClient7(jenkins);
|
|
8548
9052
|
const buildNumber = parseInt(opts.number, 10);
|
|
8549
9053
|
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
8550
9054
|
const text = await client.getConsoleLog(opts.name, buildNumber);
|
|
@@ -8621,7 +9125,7 @@ var ArtifactoryClient = class {
|
|
|
8621
9125
|
}));
|
|
8622
9126
|
const result = { builds, uri: raw.uri };
|
|
8623
9127
|
if (builds.length === 0) {
|
|
8624
|
-
result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline
|
|
9128
|
+
result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
|
|
8625
9129
|
}
|
|
8626
9130
|
return result;
|
|
8627
9131
|
}
|
|
@@ -8724,7 +9228,7 @@ function registerArtifactoryCommands(program2) {
|
|
|
8724
9228
|
fail(err, "artifactory", "search", start);
|
|
8725
9229
|
}
|
|
8726
9230
|
});
|
|
8727
|
-
art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline
|
|
9231
|
+
art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
|
|
8728
9232
|
const start = Date.now();
|
|
8729
9233
|
try {
|
|
8730
9234
|
const client = getClient8(program2);
|
|
@@ -8759,226 +9263,6 @@ function registerArtifactoryCommands(program2) {
|
|
|
8759
9263
|
});
|
|
8760
9264
|
}
|
|
8761
9265
|
|
|
8762
|
-
// src/services/udeploy/client.ts
|
|
8763
|
-
var UdeployClient = class {
|
|
8764
|
-
constructor(http) {
|
|
8765
|
-
this.http = http;
|
|
8766
|
-
}
|
|
8767
|
-
http;
|
|
8768
|
-
listApplications() {
|
|
8769
|
-
return this.http.udeploy("/cli/application");
|
|
8770
|
-
}
|
|
8771
|
-
listEnvironments(application) {
|
|
8772
|
-
return this.http.udeploy("/cli/application/environmentsInApplication", {
|
|
8773
|
-
params: { application }
|
|
8774
|
-
});
|
|
8775
|
-
}
|
|
8776
|
-
listComponents() {
|
|
8777
|
-
return this.http.udeploy("/cli/component");
|
|
8778
|
-
}
|
|
8779
|
-
listVersions(component) {
|
|
8780
|
-
return this.http.udeploy("/cli/component/versions", {
|
|
8781
|
-
params: { component }
|
|
8782
|
-
});
|
|
8783
|
-
}
|
|
8784
|
-
createVersion(component, name) {
|
|
8785
|
-
return this.http.udeploy("/cli/version/createVersion", {
|
|
8786
|
-
method: "POST",
|
|
8787
|
-
params: { component, name }
|
|
8788
|
-
});
|
|
8789
|
-
}
|
|
8790
|
-
finishImporting(component, version) {
|
|
8791
|
-
return this.http.udeploy("/cli/version/finishedImporting", {
|
|
8792
|
-
method: "POST",
|
|
8793
|
-
params: { component, version }
|
|
8794
|
-
});
|
|
8795
|
-
}
|
|
8796
|
-
runProcess(opts) {
|
|
8797
|
-
return this.http.udeploy("/cli/applicationProcessRequest/request", {
|
|
8798
|
-
method: "PUT",
|
|
8799
|
-
body: {
|
|
8800
|
-
application: opts.application,
|
|
8801
|
-
applicationProcess: opts.applicationProcess,
|
|
8802
|
-
environment: opts.environment,
|
|
8803
|
-
onlyChanged: opts.onlyChanged ?? false,
|
|
8804
|
-
...opts.versions ? { versions: opts.versions } : {},
|
|
8805
|
-
...opts.snapshot ? { snapshot: opts.snapshot } : {},
|
|
8806
|
-
...opts.properties ? { properties: opts.properties } : {}
|
|
8807
|
-
}
|
|
8808
|
-
});
|
|
8809
|
-
}
|
|
8810
|
-
getRequestStatus(requestId) {
|
|
8811
|
-
return this.http.udeploy("/cli/applicationProcessRequest/requestStatus", {
|
|
8812
|
-
params: { requestId }
|
|
8813
|
-
});
|
|
8814
|
-
}
|
|
8815
|
-
getRequestInfo(requestId) {
|
|
8816
|
-
return this.http.udeploy("/cli/applicationProcessRequest/requestInfo", {
|
|
8817
|
-
params: { requestId }
|
|
8818
|
-
});
|
|
8819
|
-
}
|
|
8820
|
-
};
|
|
8821
|
-
|
|
8822
|
-
// src/services/udeploy/commands.ts
|
|
8823
|
-
var POLL_INTERVAL_MS2 = 5e3;
|
|
8824
|
-
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8825
|
-
function getClient9(program2) {
|
|
8826
|
-
const opts = program2.optsWithGlobals();
|
|
8827
|
-
const config = loadConfig({ configPath: opts.config });
|
|
8828
|
-
if (!config.udeploy.baseUrl) {
|
|
8829
|
-
throw new PncliError("UDeploy baseUrl not configured. Run: pncli config init", 78);
|
|
8830
|
-
}
|
|
8831
|
-
const http = createHttpClient(config, opts.dryRun);
|
|
8832
|
-
return { client: new UdeployClient(http), config };
|
|
8833
|
-
}
|
|
8834
|
-
function resolveApplication(config, cliValue) {
|
|
8835
|
-
const app = cliValue ?? config.defaults.udeploy.application;
|
|
8836
|
-
if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
|
|
8837
|
-
return app;
|
|
8838
|
-
}
|
|
8839
|
-
function resolveEnvironment(config, cliValue) {
|
|
8840
|
-
const env = cliValue ?? config.defaults.udeploy.environment;
|
|
8841
|
-
if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
|
|
8842
|
-
return env;
|
|
8843
|
-
}
|
|
8844
|
-
function registerUdeployCommands(program2) {
|
|
8845
|
-
const udeploy = program2.command("udeploy").description("IBM UrbanCode Deploy \u2014 component versions and deployment processes").option("--application <name>", "Application name or ID").option("--environment <name>", "Environment name or ID");
|
|
8846
|
-
udeploy.command("apps").description("List all applications").action(async () => {
|
|
8847
|
-
const start = Date.now();
|
|
8848
|
-
try {
|
|
8849
|
-
const { client } = getClient9(program2);
|
|
8850
|
-
const apps = await client.listApplications();
|
|
8851
|
-
success(apps, "udeploy", "apps", start);
|
|
8852
|
-
} catch (err) {
|
|
8853
|
-
fail(err, "udeploy", "apps", start);
|
|
8854
|
-
}
|
|
8855
|
-
});
|
|
8856
|
-
udeploy.command("environments").description("List environments for an application").action(async () => {
|
|
8857
|
-
const start = Date.now();
|
|
8858
|
-
try {
|
|
8859
|
-
const { client, config } = getClient9(program2);
|
|
8860
|
-
const parentOpts = udeploy.opts();
|
|
8861
|
-
const application = resolveApplication(config, parentOpts.application);
|
|
8862
|
-
const envs = await client.listEnvironments(application);
|
|
8863
|
-
success(envs, "udeploy", "environments", start);
|
|
8864
|
-
} catch (err) {
|
|
8865
|
-
fail(err, "udeploy", "environments", start);
|
|
8866
|
-
}
|
|
8867
|
-
});
|
|
8868
|
-
udeploy.command("components").description("List all components").action(async () => {
|
|
8869
|
-
const start = Date.now();
|
|
8870
|
-
try {
|
|
8871
|
-
const { client } = getClient9(program2);
|
|
8872
|
-
const components = await client.listComponents();
|
|
8873
|
-
success(components, "udeploy", "components", start);
|
|
8874
|
-
} catch (err) {
|
|
8875
|
-
fail(err, "udeploy", "components", start);
|
|
8876
|
-
}
|
|
8877
|
-
});
|
|
8878
|
-
udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
|
|
8879
|
-
const start = Date.now();
|
|
8880
|
-
try {
|
|
8881
|
-
const { client } = getClient9(program2);
|
|
8882
|
-
const versions = await client.listVersions(cmdOpts.component);
|
|
8883
|
-
success(versions, "udeploy", "versions", start);
|
|
8884
|
-
} catch (err) {
|
|
8885
|
-
fail(err, "udeploy", "versions", start);
|
|
8886
|
-
}
|
|
8887
|
-
});
|
|
8888
|
-
udeploy.command("import-version").description("Create a component version and mark it ready for deployment").requiredOption("--component <name>", "Component name or ID").requiredOption("--name <name>", "Version name").option("--no-finish", "Skip marking the version as finished importing").action(async (cmdOpts) => {
|
|
8889
|
-
const start = Date.now();
|
|
8890
|
-
try {
|
|
8891
|
-
const { client } = getClient9(program2);
|
|
8892
|
-
const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
|
|
8893
|
-
if (cmdOpts.finish) {
|
|
8894
|
-
await client.finishImporting(cmdOpts.component, cmdOpts.name);
|
|
8895
|
-
}
|
|
8896
|
-
success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
|
|
8897
|
-
} catch (err) {
|
|
8898
|
-
fail(err, "udeploy", "import-version", start);
|
|
8899
|
-
}
|
|
8900
|
-
});
|
|
8901
|
-
udeploy.command("run").description("Run an application deployment process").requiredOption("--process <name>", "Application process name or ID").option("--component <name>", "Component name (repeatable)", collect, []).option("--component-version <name>", "Component version (repeatable; positionally paired with --component)", collect, []).option("--snapshot <name>", "Snapshot name or ID (alternative to specifying versions)").option("--only-changed", "Deploy only changed components", false).option("--wait", "Poll until the process completes", false).option("--timeout <ms>", "Max wait time in milliseconds", String(DEFAULT_TIMEOUT_MS)).action(async (cmdOpts) => {
|
|
8902
|
-
const start = Date.now();
|
|
8903
|
-
try {
|
|
8904
|
-
const { client, config } = getClient9(program2);
|
|
8905
|
-
const parentOpts = udeploy.opts();
|
|
8906
|
-
const application = resolveApplication(config, parentOpts.application);
|
|
8907
|
-
const environment = resolveEnvironment(config, parentOpts.environment);
|
|
8908
|
-
const components = cmdOpts.component;
|
|
8909
|
-
const versions = cmdOpts.componentVersion;
|
|
8910
|
-
const versionEntries = components.map((c, i) => ({
|
|
8911
|
-
component: c,
|
|
8912
|
-
version: versions[i] ?? "latest"
|
|
8913
|
-
}));
|
|
8914
|
-
const result = await client.runProcess({
|
|
8915
|
-
application,
|
|
8916
|
-
applicationProcess: cmdOpts.process,
|
|
8917
|
-
environment,
|
|
8918
|
-
onlyChanged: cmdOpts.onlyChanged,
|
|
8919
|
-
...versionEntries.length > 0 ? { versions: versionEntries } : {},
|
|
8920
|
-
...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
|
|
8921
|
-
});
|
|
8922
|
-
if (!cmdOpts.wait) {
|
|
8923
|
-
success(result, "udeploy", "run", start);
|
|
8924
|
-
return;
|
|
8925
|
-
}
|
|
8926
|
-
const timeoutMs = parseInt(cmdOpts.timeout, 10);
|
|
8927
|
-
const requestId = result.requestId;
|
|
8928
|
-
log(`Process started. Request ID: ${requestId}. Polling for completion...`);
|
|
8929
|
-
while (true) {
|
|
8930
|
-
const elapsed = Date.now() - start;
|
|
8931
|
-
if (elapsed >= timeoutMs) {
|
|
8932
|
-
throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
|
|
8933
|
-
}
|
|
8934
|
-
await sleep(POLL_INTERVAL_MS2);
|
|
8935
|
-
const status = await client.getRequestStatus(requestId);
|
|
8936
|
-
if (status.status === "CLOSED") {
|
|
8937
|
-
const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
|
|
8938
|
-
if (status.result === "SUCCEEDED") {
|
|
8939
|
-
success(outcome, "udeploy", "run", start);
|
|
8940
|
-
} else {
|
|
8941
|
-
throw new PncliError(
|
|
8942
|
-
`Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
|
|
8943
|
-
1
|
|
8944
|
-
);
|
|
8945
|
-
}
|
|
8946
|
-
return;
|
|
8947
|
-
}
|
|
8948
|
-
log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
|
|
8949
|
-
}
|
|
8950
|
-
} catch (err) {
|
|
8951
|
-
fail(err, "udeploy", "run", start);
|
|
8952
|
-
}
|
|
8953
|
-
});
|
|
8954
|
-
udeploy.command("request-status").description("Get the current status of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
|
|
8955
|
-
const start = Date.now();
|
|
8956
|
-
try {
|
|
8957
|
-
const { client } = getClient9(program2);
|
|
8958
|
-
const status = await client.getRequestStatus(cmdOpts.requestId);
|
|
8959
|
-
success(status, "udeploy", "request-status", start);
|
|
8960
|
-
} catch (err) {
|
|
8961
|
-
fail(err, "udeploy", "request-status", start);
|
|
8962
|
-
}
|
|
8963
|
-
});
|
|
8964
|
-
udeploy.command("request-info").description("Get full details of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
|
|
8965
|
-
const start = Date.now();
|
|
8966
|
-
try {
|
|
8967
|
-
const { client } = getClient9(program2);
|
|
8968
|
-
const info = await client.getRequestInfo(cmdOpts.requestId);
|
|
8969
|
-
success(info, "udeploy", "request-info", start);
|
|
8970
|
-
} catch (err) {
|
|
8971
|
-
fail(err, "udeploy", "request-info", start);
|
|
8972
|
-
}
|
|
8973
|
-
});
|
|
8974
|
-
}
|
|
8975
|
-
function collect(val, prev) {
|
|
8976
|
-
return [...prev, val];
|
|
8977
|
-
}
|
|
8978
|
-
function sleep(ms) {
|
|
8979
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8980
|
-
}
|
|
8981
|
-
|
|
8982
9266
|
// src/services/checkmarx/client.ts
|
|
8983
9267
|
var CheckmarxClient = class {
|
|
8984
9268
|
constructor(http) {
|
|
@@ -9019,7 +9303,7 @@ var CheckmarxClient = class {
|
|
|
9019
9303
|
};
|
|
9020
9304
|
|
|
9021
9305
|
// src/services/checkmarx/commands.ts
|
|
9022
|
-
function
|
|
9306
|
+
function getClient9(program2) {
|
|
9023
9307
|
const opts = program2.optsWithGlobals();
|
|
9024
9308
|
const config = loadConfig({ configPath: opts.config });
|
|
9025
9309
|
if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
|
|
@@ -9033,7 +9317,7 @@ function registerCheckmarxCommands(program2) {
|
|
|
9033
9317
|
project.command("list").description("List all projects").action(async () => {
|
|
9034
9318
|
const start = Date.now();
|
|
9035
9319
|
try {
|
|
9036
|
-
const data = await
|
|
9320
|
+
const data = await getClient9(program2).listProjects();
|
|
9037
9321
|
success(data, "checkmarx", "project-list", start);
|
|
9038
9322
|
} catch (err) {
|
|
9039
9323
|
fail(err, "checkmarx", "project-list", start);
|
|
@@ -9042,7 +9326,7 @@ function registerCheckmarxCommands(program2) {
|
|
|
9042
9326
|
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
|
|
9043
9327
|
const start = Date.now();
|
|
9044
9328
|
try {
|
|
9045
|
-
const data = await
|
|
9329
|
+
const data = await getClient9(program2).getProject(opts.id);
|
|
9046
9330
|
success(data, "checkmarx", "project-get", start);
|
|
9047
9331
|
} catch (err) {
|
|
9048
9332
|
fail(err, "checkmarx", "project-get", start);
|
|
@@ -9053,7 +9337,7 @@ function registerCheckmarxCommands(program2) {
|
|
|
9053
9337
|
try {
|
|
9054
9338
|
const last = opts.last ? parseInt(opts.last, 10) : void 0;
|
|
9055
9339
|
if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
|
|
9056
|
-
const data = await
|
|
9340
|
+
const data = await getClient9(program2).listScans({ projectId: opts.project, last });
|
|
9057
9341
|
success(data, "checkmarx", "scan-list", start);
|
|
9058
9342
|
} catch (err) {
|
|
9059
9343
|
fail(err, "checkmarx", "scan-list", start);
|
|
@@ -9062,7 +9346,7 @@ function registerCheckmarxCommands(program2) {
|
|
|
9062
9346
|
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9063
9347
|
const start = Date.now();
|
|
9064
9348
|
try {
|
|
9065
|
-
const data = await
|
|
9349
|
+
const data = await getClient9(program2).getScan(opts.id);
|
|
9066
9350
|
success(data, "checkmarx", "scan-get", start);
|
|
9067
9351
|
} catch (err) {
|
|
9068
9352
|
fail(err, "checkmarx", "scan-get", start);
|
|
@@ -9071,7 +9355,7 @@ function registerCheckmarxCommands(program2) {
|
|
|
9071
9355
|
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9072
9356
|
const start = Date.now();
|
|
9073
9357
|
try {
|
|
9074
|
-
const data = await
|
|
9358
|
+
const data = await getClient9(program2).getScanResultsStatistics(opts.id);
|
|
9075
9359
|
success(data, "checkmarx", "scan-stats", start);
|
|
9076
9360
|
} catch (err) {
|
|
9077
9361
|
fail(err, "checkmarx", "scan-stats", start);
|
|
@@ -10524,9 +10808,32 @@ function podAge(creationTimestamp) {
|
|
|
10524
10808
|
if (ageHours < 24) return `${ageHours}h`;
|
|
10525
10809
|
return `${Math.floor(ageHours / 24)}d`;
|
|
10526
10810
|
}
|
|
10811
|
+
function parseCpuMillicores(cpu) {
|
|
10812
|
+
if (!cpu) return null;
|
|
10813
|
+
if (cpu.endsWith("n")) return Math.round(Number(cpu.slice(0, -1)) / 1e6);
|
|
10814
|
+
if (cpu.endsWith("u")) return Math.round(Number(cpu.slice(0, -1)) / 1e3);
|
|
10815
|
+
if (cpu.endsWith("m")) return Number(cpu.slice(0, -1));
|
|
10816
|
+
const whole = Number(cpu);
|
|
10817
|
+
if (!Number.isNaN(whole)) return Math.round(whole * 1e3);
|
|
10818
|
+
return null;
|
|
10819
|
+
}
|
|
10820
|
+
function parseMemMiB(mem) {
|
|
10821
|
+
if (!mem) return null;
|
|
10822
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
10823
|
+
if (mem.endsWith("Ki")) return round2(Number(mem.slice(0, -2)) / 1024);
|
|
10824
|
+
if (mem.endsWith("Mi")) return Number(mem.slice(0, -2));
|
|
10825
|
+
if (mem.endsWith("Gi")) return Math.round(Number(mem.slice(0, -2)) * 1024);
|
|
10826
|
+
if (mem.endsWith("Ti")) return Math.round(Number(mem.slice(0, -2)) * 1024 * 1024);
|
|
10827
|
+
if (mem.endsWith("K")) return round2(Number(mem.slice(0, -1)) * 1e3 / (1024 * 1024));
|
|
10828
|
+
if (mem.endsWith("M")) return round2(Number(mem.slice(0, -1)) * 1e3 * 1e3 / (1024 * 1024));
|
|
10829
|
+
if (mem.endsWith("G")) return Math.round(Number(mem.slice(0, -1)) * 1e3 * 1e3 * 1e3 / (1024 * 1024));
|
|
10830
|
+
const bytes = Number(mem);
|
|
10831
|
+
if (!Number.isNaN(bytes)) return round2(bytes / (1024 * 1024));
|
|
10832
|
+
return null;
|
|
10833
|
+
}
|
|
10527
10834
|
function registerOpenShiftCommands(program2) {
|
|
10528
10835
|
const oc = program2.command("openshift").description("OpenShift / Kubernetes operations");
|
|
10529
|
-
oc.command("pods").description("List pod health summary for a namespace \u2014 pre-processed for LLM analysis").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").action(async (opts) => {
|
|
10836
|
+
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) => {
|
|
10530
10837
|
const start = Date.now();
|
|
10531
10838
|
try {
|
|
10532
10839
|
const http = getHttp2(program2);
|
|
@@ -10536,6 +10843,15 @@ function registerOpenShiftCommands(program2) {
|
|
|
10536
10843
|
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods`,
|
|
10537
10844
|
{ params }
|
|
10538
10845
|
);
|
|
10846
|
+
if (opts.raw) {
|
|
10847
|
+
success(
|
|
10848
|
+
{ namespace: opts.namespace, count: podList.items.length, pods: podList.items },
|
|
10849
|
+
"openshift",
|
|
10850
|
+
"pods",
|
|
10851
|
+
start
|
|
10852
|
+
);
|
|
10853
|
+
return;
|
|
10854
|
+
}
|
|
10539
10855
|
const summaryCounters = {
|
|
10540
10856
|
total: podList.items.length,
|
|
10541
10857
|
running: 0,
|
|
@@ -10712,6 +11028,71 @@ function registerOpenShiftCommands(program2) {
|
|
|
10712
11028
|
fail(err, "openshift", "pod-metrics", start);
|
|
10713
11029
|
}
|
|
10714
11030
|
});
|
|
11031
|
+
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) => {
|
|
11032
|
+
const start = Date.now();
|
|
11033
|
+
try {
|
|
11034
|
+
const http = getHttp2(program2);
|
|
11035
|
+
const nsEncoded = encodeURIComponent(opts.namespace);
|
|
11036
|
+
const podParams = {};
|
|
11037
|
+
if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
|
|
11038
|
+
const [podList, metricsList] = await Promise.all([
|
|
11039
|
+
http.openshift(`/api/v1/namespaces/${nsEncoded}/pods`, { params: podParams }),
|
|
11040
|
+
http.openshift(`/apis/metrics.k8s.io/v1beta1/namespaces/${nsEncoded}/pods`, { params: podParams })
|
|
11041
|
+
]);
|
|
11042
|
+
const metricsIndex = /* @__PURE__ */ new Map();
|
|
11043
|
+
for (const pm of metricsList.items) {
|
|
11044
|
+
const containerMap = /* @__PURE__ */ new Map();
|
|
11045
|
+
for (const c of pm.containers) {
|
|
11046
|
+
containerMap.set(c.name, c.usage);
|
|
11047
|
+
}
|
|
11048
|
+
metricsIndex.set(pm.metadata.name, containerMap);
|
|
11049
|
+
}
|
|
11050
|
+
const rows = [];
|
|
11051
|
+
for (const pod of podList.items) {
|
|
11052
|
+
const podName = pod.metadata.name;
|
|
11053
|
+
const podMetrics = metricsIndex.get(podName);
|
|
11054
|
+
const containers = pod.spec.containers ?? [];
|
|
11055
|
+
for (const container of containers) {
|
|
11056
|
+
const usage = podMetrics?.get(container.name);
|
|
11057
|
+
rows.push({
|
|
11058
|
+
pod: podName,
|
|
11059
|
+
container: container.name,
|
|
11060
|
+
cpuUsageMillicores: parseCpuMillicores(usage?.cpu),
|
|
11061
|
+
memUsageMiB: parseMemMiB(usage?.memory),
|
|
11062
|
+
cpuLimitsMillicores: parseCpuMillicores(container.resources?.limits?.cpu),
|
|
11063
|
+
memLimitsMiB: parseMemMiB(container.resources?.limits?.memory),
|
|
11064
|
+
cpuRequestsMillicores: parseCpuMillicores(container.resources?.requests?.cpu),
|
|
11065
|
+
memRequestsMiB: parseMemMiB(container.resources?.requests?.memory)
|
|
11066
|
+
});
|
|
11067
|
+
}
|
|
11068
|
+
}
|
|
11069
|
+
if (opts.csv) {
|
|
11070
|
+
const header = "Pod,Container,CPU Usage (m),Memory Usage (Mi),CPU Limits (m),Memory Limits (Mi),CPU Requests (m),Memory Requests (Mi)";
|
|
11071
|
+
const lines = rows.map(
|
|
11072
|
+
(r) => [
|
|
11073
|
+
r.pod,
|
|
11074
|
+
r.container,
|
|
11075
|
+
r.cpuUsageMillicores ?? "",
|
|
11076
|
+
r.memUsageMiB ?? "",
|
|
11077
|
+
r.cpuLimitsMillicores ?? "",
|
|
11078
|
+
r.memLimitsMiB ?? "",
|
|
11079
|
+
r.cpuRequestsMillicores ?? "",
|
|
11080
|
+
r.memRequestsMiB ?? ""
|
|
11081
|
+
].join(",")
|
|
11082
|
+
);
|
|
11083
|
+
writeRawOutput([header, ...lines].join("\n") + "\n");
|
|
11084
|
+
} else {
|
|
11085
|
+
success(
|
|
11086
|
+
{ namespace: opts.namespace, count: rows.length, units: { cpu: "m", memory: "Mi" }, rows },
|
|
11087
|
+
"openshift",
|
|
11088
|
+
"resource-usage",
|
|
11089
|
+
start
|
|
11090
|
+
);
|
|
11091
|
+
}
|
|
11092
|
+
} catch (err) {
|
|
11093
|
+
fail(err, "openshift", "resource-usage", start);
|
|
11094
|
+
}
|
|
11095
|
+
});
|
|
10715
11096
|
}
|
|
10716
11097
|
|
|
10717
11098
|
// src/services/dynatrace/commands.ts
|
|
@@ -10937,6 +11318,500 @@ function registerLogscaleCommands(program2) {
|
|
|
10937
11318
|
});
|
|
10938
11319
|
}
|
|
10939
11320
|
|
|
11321
|
+
// src/services/splitio/commands.ts
|
|
11322
|
+
import fs5 from "fs";
|
|
11323
|
+
import confirm2 from "@inquirer/confirm";
|
|
11324
|
+
function getHttp5(program2) {
|
|
11325
|
+
const opts = program2.optsWithGlobals();
|
|
11326
|
+
return createHttpClient(
|
|
11327
|
+
loadConfig({ configPath: opts.config }),
|
|
11328
|
+
Boolean(opts.dryRun)
|
|
11329
|
+
);
|
|
11330
|
+
}
|
|
11331
|
+
function loadInputFile(filePath) {
|
|
11332
|
+
let raw;
|
|
11333
|
+
if (filePath === "-") {
|
|
11334
|
+
raw = fs5.readFileSync(process.stdin.fd, "utf8");
|
|
11335
|
+
} else {
|
|
11336
|
+
raw = fs5.readFileSync(filePath, "utf8");
|
|
11337
|
+
}
|
|
11338
|
+
try {
|
|
11339
|
+
return JSON.parse(raw);
|
|
11340
|
+
} catch {
|
|
11341
|
+
throw new PncliError(`Could not parse JSON from ${filePath === "-" ? "stdin" : filePath}`);
|
|
11342
|
+
}
|
|
11343
|
+
}
|
|
11344
|
+
async function confirmWrite(opts, opLabel) {
|
|
11345
|
+
if (opts.yes) return;
|
|
11346
|
+
const proceed = await confirm2({
|
|
11347
|
+
message: `Submit Change Request "${opts.mnemonic}" (${opLabel})? This creates an approval request in Split.`,
|
|
11348
|
+
default: false
|
|
11349
|
+
});
|
|
11350
|
+
if (!proceed) throw new PncliError("Aborted by user.", 0);
|
|
11351
|
+
}
|
|
11352
|
+
function buildCrBase(opts) {
|
|
11353
|
+
const base = {
|
|
11354
|
+
title: opts.mnemonic,
|
|
11355
|
+
comment: opts.description,
|
|
11356
|
+
crNumber: opts.changeNumber
|
|
11357
|
+
};
|
|
11358
|
+
if (opts.approvers) {
|
|
11359
|
+
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
|
|
11360
|
+
}
|
|
11361
|
+
return base;
|
|
11362
|
+
}
|
|
11363
|
+
function registerSplitioCommands(program2) {
|
|
11364
|
+
const splitio = program2.command("splitio").description("Split.IO feature flag administration");
|
|
11365
|
+
const workspaces = splitio.command("workspaces").description("Workspace operations");
|
|
11366
|
+
workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
|
|
11367
|
+
const start = Date.now();
|
|
11368
|
+
try {
|
|
11369
|
+
const http = getHttp5(program2);
|
|
11370
|
+
const data = await http.splitio("/internal/api/v2/workspaces");
|
|
11371
|
+
success(
|
|
11372
|
+
{ count: data.objects.length, workspaces: data.objects },
|
|
11373
|
+
"splitio",
|
|
11374
|
+
"workspaces list",
|
|
11375
|
+
start
|
|
11376
|
+
);
|
|
11377
|
+
} catch (err) {
|
|
11378
|
+
fail(err, "splitio", "workspaces list", start);
|
|
11379
|
+
}
|
|
11380
|
+
});
|
|
11381
|
+
const environments = splitio.command("environments").description("Environment operations");
|
|
11382
|
+
environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
|
|
11383
|
+
const start = Date.now();
|
|
11384
|
+
try {
|
|
11385
|
+
const http = getHttp5(program2);
|
|
11386
|
+
const data = await http.splitio(
|
|
11387
|
+
`/internal/api/v2/workspaces/${encodeURIComponent(opts.workspace)}/environments`
|
|
11388
|
+
);
|
|
11389
|
+
success(
|
|
11390
|
+
{ count: data.objects.length, environments: data.objects },
|
|
11391
|
+
"splitio",
|
|
11392
|
+
"environments list",
|
|
11393
|
+
start
|
|
11394
|
+
);
|
|
11395
|
+
} catch (err) {
|
|
11396
|
+
fail(err, "splitio", "environments list", start);
|
|
11397
|
+
}
|
|
11398
|
+
});
|
|
11399
|
+
const flags = splitio.command("flags").description("Feature flag operations");
|
|
11400
|
+
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) => {
|
|
11401
|
+
const start = Date.now();
|
|
11402
|
+
try {
|
|
11403
|
+
const http = getHttp5(program2);
|
|
11404
|
+
const params = {
|
|
11405
|
+
wsId: opts.workspace,
|
|
11406
|
+
limit: parseInt(opts.limit, 10),
|
|
11407
|
+
offset: parseInt(opts.offset, 10)
|
|
11408
|
+
};
|
|
11409
|
+
if (opts.flagSet) params["flagSetName"] = opts.flagSet;
|
|
11410
|
+
const data = await http.splitio("/internal/api/v2/splits", { params });
|
|
11411
|
+
success(
|
|
11412
|
+
{ count: data.objects.length, totalCount: data.totalCount, flags: data.objects },
|
|
11413
|
+
"splitio",
|
|
11414
|
+
"flags list",
|
|
11415
|
+
start
|
|
11416
|
+
);
|
|
11417
|
+
} catch (err) {
|
|
11418
|
+
fail(err, "splitio", "flags list", start);
|
|
11419
|
+
}
|
|
11420
|
+
});
|
|
11421
|
+
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) => {
|
|
11422
|
+
const start = Date.now();
|
|
11423
|
+
try {
|
|
11424
|
+
const http = getHttp5(program2);
|
|
11425
|
+
if (opts.environment) {
|
|
11426
|
+
const data = await http.splitio(
|
|
11427
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
11428
|
+
{ params: { wsId: opts.workspace } }
|
|
11429
|
+
);
|
|
11430
|
+
success(data, "splitio", "flags get", start);
|
|
11431
|
+
} else {
|
|
11432
|
+
const data = await http.splitio(
|
|
11433
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}`,
|
|
11434
|
+
{ params: { wsId: opts.workspace } }
|
|
11435
|
+
);
|
|
11436
|
+
success(data, "splitio", "flags get", start);
|
|
11437
|
+
}
|
|
11438
|
+
} catch (err) {
|
|
11439
|
+
fail(err, "splitio", "flags get", start);
|
|
11440
|
+
}
|
|
11441
|
+
});
|
|
11442
|
+
flags.command("update").description("Submit a Change Request to replace a flag's full definition in an environment").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--input-file <path>", "JSON file with the complete flag definition body (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
|
|
11443
|
+
const start = Date.now();
|
|
11444
|
+
try {
|
|
11445
|
+
if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
|
|
11446
|
+
const definition = loadInputFile(opts.inputFile);
|
|
11447
|
+
await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
|
|
11448
|
+
const http = getHttp5(program2);
|
|
11449
|
+
const body = {
|
|
11450
|
+
...buildCrBase(opts),
|
|
11451
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
11452
|
+
split: {
|
|
11453
|
+
name: opts.flag,
|
|
11454
|
+
environment: { id: opts.environment },
|
|
11455
|
+
definition
|
|
11456
|
+
},
|
|
11457
|
+
workspace: { id: opts.workspace }
|
|
11458
|
+
};
|
|
11459
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11460
|
+
method: "POST",
|
|
11461
|
+
body
|
|
11462
|
+
});
|
|
11463
|
+
success(data, "splitio", "flags update", start);
|
|
11464
|
+
} catch (err) {
|
|
11465
|
+
fail(err, "splitio", "flags update", start);
|
|
11466
|
+
}
|
|
11467
|
+
});
|
|
11468
|
+
flags.command("set-rule").description("Change one targeting rule without altering the rest of the flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--rule-index <n>", "Zero-based index of the targeting rule to update").option("--treatment <name>", "Treatment to assign to the entire rule bucket").option("--weights <json>", "JSON array of {treatment, size} objects for weighted distribution").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
|
|
11469
|
+
const start = Date.now();
|
|
11470
|
+
try {
|
|
11471
|
+
if (!opts.treatment && !opts.weights) {
|
|
11472
|
+
throw new PncliError("Either --treatment or --weights must be specified");
|
|
11473
|
+
}
|
|
11474
|
+
const http = getHttp5(program2);
|
|
11475
|
+
const current = await http.splitio(
|
|
11476
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
11477
|
+
{ params: { wsId: opts.workspace } }
|
|
11478
|
+
);
|
|
11479
|
+
const rules = current.rules ?? [];
|
|
11480
|
+
const idx = parseInt(opts.ruleIndex, 10);
|
|
11481
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= rules.length) {
|
|
11482
|
+
throw new PncliError(`Rule index ${Number.isNaN(idx) ? JSON.stringify(opts.ruleIndex) : idx} is out of range (flag has ${rules.length} rules)`);
|
|
11483
|
+
}
|
|
11484
|
+
if (opts.treatment) {
|
|
11485
|
+
rules[idx] = { ...rules[idx], buckets: [{ treatment: opts.treatment, size: 1e5 }] };
|
|
11486
|
+
} else {
|
|
11487
|
+
const parsed = JSON.parse(opts.weights);
|
|
11488
|
+
rules[idx] = { ...rules[idx], buckets: parsed };
|
|
11489
|
+
}
|
|
11490
|
+
await confirmWrite(opts, `SET_RULE ${idx} on ${opts.flag} in ${opts.environment}`);
|
|
11491
|
+
const body = {
|
|
11492
|
+
...buildCrBase(opts),
|
|
11493
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
11494
|
+
split: {
|
|
11495
|
+
name: opts.flag,
|
|
11496
|
+
environment: { id: opts.environment },
|
|
11497
|
+
definition: { ...current, rules }
|
|
11498
|
+
},
|
|
11499
|
+
workspace: { id: opts.workspace }
|
|
11500
|
+
};
|
|
11501
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11502
|
+
method: "POST",
|
|
11503
|
+
body
|
|
11504
|
+
});
|
|
11505
|
+
success(data, "splitio", "flags set-rule", start);
|
|
11506
|
+
} catch (err) {
|
|
11507
|
+
fail(err, "splitio", "flags set-rule", start);
|
|
11508
|
+
}
|
|
11509
|
+
});
|
|
11510
|
+
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>", "ServiceNow 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) => {
|
|
11511
|
+
const start = Date.now();
|
|
11512
|
+
try {
|
|
11513
|
+
const http = getHttp5(program2);
|
|
11514
|
+
const current = await http.splitio(
|
|
11515
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
11516
|
+
{ params: { wsId: opts.workspace } }
|
|
11517
|
+
);
|
|
11518
|
+
await confirmWrite(opts, `SET_DEFAULT ${opts.treatment} on ${opts.flag} in ${opts.environment}`);
|
|
11519
|
+
const body = {
|
|
11520
|
+
...buildCrBase(opts),
|
|
11521
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
11522
|
+
split: {
|
|
11523
|
+
name: opts.flag,
|
|
11524
|
+
environment: { id: opts.environment },
|
|
11525
|
+
definition: {
|
|
11526
|
+
...current,
|
|
11527
|
+
defaultRule: [{ treatment: opts.treatment, size: 1e5 }]
|
|
11528
|
+
}
|
|
11529
|
+
},
|
|
11530
|
+
workspace: { id: opts.workspace }
|
|
11531
|
+
};
|
|
11532
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11533
|
+
method: "POST",
|
|
11534
|
+
body
|
|
11535
|
+
});
|
|
11536
|
+
success(data, "splitio", "flags set-default", start);
|
|
11537
|
+
} catch (err) {
|
|
11538
|
+
fail(err, "splitio", "flags set-default", start);
|
|
11539
|
+
}
|
|
11540
|
+
});
|
|
11541
|
+
flags.command("toggle").description("Enable or disable a single flag via a Change Request").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--enabled <bool>", "true to restore / false to kill the flag").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
|
|
11542
|
+
const start = Date.now();
|
|
11543
|
+
try {
|
|
11544
|
+
const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
|
|
11545
|
+
const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
11546
|
+
await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
|
|
11547
|
+
const http = getHttp5(program2);
|
|
11548
|
+
const body = {
|
|
11549
|
+
...buildCrBase(opts),
|
|
11550
|
+
operationType: opType,
|
|
11551
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
11552
|
+
workspace: { id: opts.workspace }
|
|
11553
|
+
};
|
|
11554
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11555
|
+
method: "POST",
|
|
11556
|
+
body
|
|
11557
|
+
});
|
|
11558
|
+
success(data, "splitio", "flags toggle", start);
|
|
11559
|
+
} catch (err) {
|
|
11560
|
+
fail(err, "splitio", "flags toggle", start);
|
|
11561
|
+
}
|
|
11562
|
+
});
|
|
11563
|
+
flags.command("batch-toggle").description("Enable or disable multiple flags from a JSON file (each creates its own Change Request)").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title prefix (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve each CR").option("--input-file <path>", "JSON file: array of {flag, enabled} objects (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
|
|
11564
|
+
const start = Date.now();
|
|
11565
|
+
try {
|
|
11566
|
+
if (!opts.inputFile) throw new PncliError("--input-file is required for batch-toggle");
|
|
11567
|
+
const entries = loadInputFile(opts.inputFile);
|
|
11568
|
+
if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
|
|
11569
|
+
await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
|
|
11570
|
+
const http = getHttp5(program2);
|
|
11571
|
+
const results = [];
|
|
11572
|
+
for (const entry of entries) {
|
|
11573
|
+
try {
|
|
11574
|
+
const opType = entry.enabled ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
11575
|
+
const body = {
|
|
11576
|
+
...buildCrBase(opts),
|
|
11577
|
+
title: `${opts.mnemonic} \u2014 ${entry.flag}`,
|
|
11578
|
+
operationType: opType,
|
|
11579
|
+
split: { name: entry.flag, environment: { id: opts.environment } },
|
|
11580
|
+
workspace: { id: opts.workspace }
|
|
11581
|
+
};
|
|
11582
|
+
const cr = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11583
|
+
method: "POST",
|
|
11584
|
+
body
|
|
11585
|
+
});
|
|
11586
|
+
results.push({ flag: entry.flag, ok: true, id: cr.id });
|
|
11587
|
+
} catch (err) {
|
|
11588
|
+
results.push({
|
|
11589
|
+
flag: entry.flag,
|
|
11590
|
+
ok: false,
|
|
11591
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11592
|
+
});
|
|
11593
|
+
}
|
|
11594
|
+
}
|
|
11595
|
+
success({ submitted: results.length, results }, "splitio", "flags batch-toggle", start);
|
|
11596
|
+
} catch (err) {
|
|
11597
|
+
fail(err, "splitio", "flags batch-toggle", start);
|
|
11598
|
+
}
|
|
11599
|
+
});
|
|
11600
|
+
flags.command("kill").description("Submit a Change Request to kill (disable) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
|
|
11601
|
+
const start = Date.now();
|
|
11602
|
+
try {
|
|
11603
|
+
await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
|
|
11604
|
+
const http = getHttp5(program2);
|
|
11605
|
+
const body = {
|
|
11606
|
+
...buildCrBase(opts),
|
|
11607
|
+
operationType: "KILL_SPLIT",
|
|
11608
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
11609
|
+
workspace: { id: opts.workspace }
|
|
11610
|
+
};
|
|
11611
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11612
|
+
method: "POST",
|
|
11613
|
+
body
|
|
11614
|
+
});
|
|
11615
|
+
success(data, "splitio", "flags kill", start);
|
|
11616
|
+
} catch (err) {
|
|
11617
|
+
fail(err, "splitio", "flags kill", start);
|
|
11618
|
+
}
|
|
11619
|
+
});
|
|
11620
|
+
flags.command("restore").description("Submit a Change Request to restore a killed flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
|
|
11621
|
+
const start = Date.now();
|
|
11622
|
+
try {
|
|
11623
|
+
await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
|
|
11624
|
+
const http = getHttp5(program2);
|
|
11625
|
+
const body = {
|
|
11626
|
+
...buildCrBase(opts),
|
|
11627
|
+
operationType: "RESTORE_SPLIT",
|
|
11628
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
11629
|
+
workspace: { id: opts.workspace }
|
|
11630
|
+
};
|
|
11631
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11632
|
+
method: "POST",
|
|
11633
|
+
body
|
|
11634
|
+
});
|
|
11635
|
+
success(data, "splitio", "flags restore", start);
|
|
11636
|
+
} catch (err) {
|
|
11637
|
+
fail(err, "splitio", "flags restore", start);
|
|
11638
|
+
}
|
|
11639
|
+
});
|
|
11640
|
+
flags.command("archive").description("Submit a Change Request to archive (delete definition of) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
|
|
11641
|
+
const start = Date.now();
|
|
11642
|
+
try {
|
|
11643
|
+
await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
|
|
11644
|
+
const http = getHttp5(program2);
|
|
11645
|
+
const body = {
|
|
11646
|
+
...buildCrBase(opts),
|
|
11647
|
+
operationType: "ARCHIVE_SPLIT",
|
|
11648
|
+
split: { name: opts.flag },
|
|
11649
|
+
workspace: { id: opts.workspace }
|
|
11650
|
+
};
|
|
11651
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
11652
|
+
method: "POST",
|
|
11653
|
+
body
|
|
11654
|
+
});
|
|
11655
|
+
success(data, "splitio", "flags archive", start);
|
|
11656
|
+
} catch (err) {
|
|
11657
|
+
fail(err, "splitio", "flags archive", start);
|
|
11658
|
+
}
|
|
11659
|
+
});
|
|
11660
|
+
const changeRequests = splitio.command("change-requests").description("Change Request tracking");
|
|
11661
|
+
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) => {
|
|
11662
|
+
const start = Date.now();
|
|
11663
|
+
try {
|
|
11664
|
+
const http = getHttp5(program2);
|
|
11665
|
+
const params = {
|
|
11666
|
+
wsId: opts.workspace,
|
|
11667
|
+
limit: parseInt(opts.limit, 10),
|
|
11668
|
+
offset: parseInt(opts.offset, 10)
|
|
11669
|
+
};
|
|
11670
|
+
if (opts.environment) params["environmentId"] = opts.environment;
|
|
11671
|
+
if (opts.status) params["status"] = opts.status;
|
|
11672
|
+
const data = await http.splitio(
|
|
11673
|
+
"/internal/api/v2/changeRequests",
|
|
11674
|
+
{ params }
|
|
11675
|
+
);
|
|
11676
|
+
success(
|
|
11677
|
+
{ count: data.objects.length, totalCount: data.totalCount, changeRequests: data.objects },
|
|
11678
|
+
"splitio",
|
|
11679
|
+
"change-requests list",
|
|
11680
|
+
start
|
|
11681
|
+
);
|
|
11682
|
+
} catch (err) {
|
|
11683
|
+
fail(err, "splitio", "change-requests list", start);
|
|
11684
|
+
}
|
|
11685
|
+
});
|
|
11686
|
+
changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
|
|
11687
|
+
const start = Date.now();
|
|
11688
|
+
try {
|
|
11689
|
+
const http = getHttp5(program2);
|
|
11690
|
+
const data = await http.splitio(
|
|
11691
|
+
`/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
|
|
11692
|
+
);
|
|
11693
|
+
success(data, "splitio", "change-requests get", start);
|
|
11694
|
+
} catch (err) {
|
|
11695
|
+
fail(err, "splitio", "change-requests get", start);
|
|
11696
|
+
}
|
|
11697
|
+
});
|
|
11698
|
+
}
|
|
11699
|
+
|
|
11700
|
+
// src/services/figma/commands.ts
|
|
11701
|
+
function parseFileKey(input3) {
|
|
11702
|
+
if (input3.startsWith("http://") || input3.startsWith("https://")) {
|
|
11703
|
+
const match = /figma\.com\/(?:design|file)\/([A-Za-z0-9]+)/i.exec(input3);
|
|
11704
|
+
if (!match?.[1]) {
|
|
11705
|
+
throw new Error(`Could not extract Figma file key from URL: ${input3}
|
|
11706
|
+
Expected format: https://www.figma.com/design/<file-key>/...`);
|
|
11707
|
+
}
|
|
11708
|
+
return match[1];
|
|
11709
|
+
}
|
|
11710
|
+
return input3;
|
|
11711
|
+
}
|
|
11712
|
+
function getHttp6(program2) {
|
|
11713
|
+
const opts = program2.optsWithGlobals();
|
|
11714
|
+
return createHttpClient(
|
|
11715
|
+
loadConfig({ configPath: opts.config }),
|
|
11716
|
+
Boolean(opts.dryRun)
|
|
11717
|
+
);
|
|
11718
|
+
}
|
|
11719
|
+
function registerFigmaCommands(program2) {
|
|
11720
|
+
const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
|
|
11721
|
+
figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...)").option("--document", "Include the full document node tree (can be large; omitted by default)").action(async (fileKeyOrUrl, opts) => {
|
|
11722
|
+
const start = Date.now();
|
|
11723
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
11724
|
+
try {
|
|
11725
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
11726
|
+
const params = {};
|
|
11727
|
+
if (!opts.document) {
|
|
11728
|
+
params["geometry"] = "paths";
|
|
11729
|
+
params["depth"] = 0;
|
|
11730
|
+
}
|
|
11731
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
|
|
11732
|
+
success2(
|
|
11733
|
+
{
|
|
11734
|
+
fileKey,
|
|
11735
|
+
name: data.name,
|
|
11736
|
+
lastModified: data.lastModified,
|
|
11737
|
+
version: data.version,
|
|
11738
|
+
thumbnailUrl: data.thumbnailUrl,
|
|
11739
|
+
role: data.role,
|
|
11740
|
+
editorType: data.editorType,
|
|
11741
|
+
schemaVersion: data.schemaVersion,
|
|
11742
|
+
...opts.document ? { document: data.document } : {},
|
|
11743
|
+
componentCount: data.components ? Object.keys(data.components).length : 0,
|
|
11744
|
+
styleCount: data.styles ? Object.keys(data.styles).length : 0
|
|
11745
|
+
},
|
|
11746
|
+
"figma",
|
|
11747
|
+
"file",
|
|
11748
|
+
start
|
|
11749
|
+
);
|
|
11750
|
+
} catch (err) {
|
|
11751
|
+
fail2(err, "figma", "file", start);
|
|
11752
|
+
}
|
|
11753
|
+
});
|
|
11754
|
+
figma.command("comments").description("Get comments on a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").option("--as-of <date>", "ISO 8601 timestamp \u2014 return only comments created before this time").action(async (fileKeyOrUrl, opts) => {
|
|
11755
|
+
const start = Date.now();
|
|
11756
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
11757
|
+
try {
|
|
11758
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
11759
|
+
const params = {};
|
|
11760
|
+
if (opts.asOf) params["as_of"] = opts.asOf;
|
|
11761
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
|
|
11762
|
+
success2(
|
|
11763
|
+
{ fileKey, count: data.comments.length, comments: data.comments },
|
|
11764
|
+
"figma",
|
|
11765
|
+
"comments",
|
|
11766
|
+
start
|
|
11767
|
+
);
|
|
11768
|
+
} catch (err) {
|
|
11769
|
+
fail2(err, "figma", "comments", start);
|
|
11770
|
+
}
|
|
11771
|
+
});
|
|
11772
|
+
figma.command("versions").description("Get the version history of a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").action(async (fileKeyOrUrl) => {
|
|
11773
|
+
const start = Date.now();
|
|
11774
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
11775
|
+
try {
|
|
11776
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
11777
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
|
|
11778
|
+
success2(
|
|
11779
|
+
{ fileKey, count: data.versions.length, versions: data.versions },
|
|
11780
|
+
"figma",
|
|
11781
|
+
"versions",
|
|
11782
|
+
start
|
|
11783
|
+
);
|
|
11784
|
+
} catch (err) {
|
|
11785
|
+
fail2(err, "figma", "versions", start);
|
|
11786
|
+
}
|
|
11787
|
+
});
|
|
11788
|
+
figma.command("project-files").description("List files in a Figma project").argument("<project-id>", "Figma project ID (visible in the project URL)").action(async (projectId) => {
|
|
11789
|
+
const start = Date.now();
|
|
11790
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
11791
|
+
try {
|
|
11792
|
+
const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
|
|
11793
|
+
success2(
|
|
11794
|
+
{ projectId, count: data.files.length, files: data.files },
|
|
11795
|
+
"figma",
|
|
11796
|
+
"project-files",
|
|
11797
|
+
start
|
|
11798
|
+
);
|
|
11799
|
+
} catch (err) {
|
|
11800
|
+
fail2(err, "figma", "project-files", start);
|
|
11801
|
+
}
|
|
11802
|
+
});
|
|
11803
|
+
figma.command("me").description("Get the current Figma user (useful for verifying credentials)").action(async () => {
|
|
11804
|
+
const start = Date.now();
|
|
11805
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
11806
|
+
try {
|
|
11807
|
+
const data = await getHttp6(program2).figma("/v1/me");
|
|
11808
|
+
success2(data, "figma", "me", start);
|
|
11809
|
+
} catch (err) {
|
|
11810
|
+
fail2(err, "figma", "me", start);
|
|
11811
|
+
}
|
|
11812
|
+
});
|
|
11813
|
+
}
|
|
11814
|
+
|
|
10940
11815
|
// src/cli.ts
|
|
10941
11816
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
10942
11817
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -10973,7 +11848,6 @@ registerConfigCommands(program);
|
|
|
10973
11848
|
registerAdoCommands(program);
|
|
10974
11849
|
registerJenkinsCommands(program);
|
|
10975
11850
|
registerArtifactoryCommands(program);
|
|
10976
|
-
registerUdeployCommands(program);
|
|
10977
11851
|
registerCheckmarxCommands(program);
|
|
10978
11852
|
registerServiceNowCommands(program);
|
|
10979
11853
|
registerContrastCommands(program);
|
|
@@ -10983,6 +11857,8 @@ registerJwtCommands(program);
|
|
|
10983
11857
|
registerOpenShiftCommands(program);
|
|
10984
11858
|
registerDynatraceCommands(program);
|
|
10985
11859
|
registerLogscaleCommands(program);
|
|
11860
|
+
registerSplitioCommands(program);
|
|
11861
|
+
registerFigmaCommands(program);
|
|
10986
11862
|
program.addHelpText("after", `
|
|
10987
11863
|
Services:
|
|
10988
11864
|
git Local git operations (status, diff, log, branch)
|
|
@@ -10994,9 +11870,8 @@ Services:
|
|
|
10994
11870
|
sonar SonarQube Server (quality gates, issues, metrics, hotspots)
|
|
10995
11871
|
sde SDElements (threat modeling, countermeasures, compliance)
|
|
10996
11872
|
ado Azure DevOps Server (work items, repos, PRs, pipelines)
|
|
10997
|
-
jenkins Jenkins
|
|
11873
|
+
jenkins Jenkins (jobs, builds, logs, named instances)
|
|
10998
11874
|
artifactory Artifactory (repos, artifact search, build info, properties)
|
|
10999
|
-
udeploy IBM UrbanCode Deploy (component versions, deployment processes)
|
|
11000
11875
|
checkmarx Checkmarx One (projects, scans, scan statistics)
|
|
11001
11876
|
servicenow ServiceNow (change requests)
|
|
11002
11877
|
contrast Contrast IAST (applications, vulnerability findings)
|
|
@@ -11004,6 +11879,8 @@ Services:
|
|
|
11004
11879
|
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
11005
11880
|
dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
|
|
11006
11881
|
logscale LogScale (log query, repositories)
|
|
11882
|
+
splitio Split.IO (feature flags, Change Requests)
|
|
11883
|
+
figma Figma (files, comments, version history)
|
|
11007
11884
|
config Manage pncli configuration
|
|
11008
11885
|
skills Download and manage Claude Code skills
|
|
11009
11886
|
jwt JWT token utilities (decode header and payload)
|