@robinmordasiewicz/f5xc-xcsh 2.0.21-2601090318 → 2.0.21-2601090411

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.
@@ -47,7 +47,7 @@ _xcsh_completions() {
47
47
  return 0
48
48
  ;;
49
49
  login/profile)
50
- COMPREPLY=($(compgen -W "list show create use delete" -- "${cur}"))
50
+ COMPREPLY=($(compgen -W "list show create use edit delete" -- "${cur}"))
51
51
  return 0
52
52
  ;;
53
53
  login/context)
@@ -90,6 +90,7 @@ complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcomma
90
90
  complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from profile" -a "show" -d 'Show profile details (masked credentials)'
91
91
  complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from profile" -a "create" -d 'Create a new connection profile'
92
92
  complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from profile" -a "use" -d 'Switch to a different profile'
93
+ complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from profile" -a "edit" -d 'Edit profile in $EDITOR'
93
94
  complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from profile" -a "delete" -d 'Delete a saved profile'
94
95
  complete -c xcsh -n "__fish_seen_subcommand_from login" -a "context" -d 'Manage default namespace context'
95
96
  complete -c xcsh -n "__fish_seen_subcommand_from login; and __fish_seen_subcommand_from context" -a "show" -d 'Show current default namespace'
package/dist/index.js CHANGED
@@ -28134,7 +28134,7 @@ var require_command = __commonJS({
28134
28134
  var EventEmitter3 = __require("events").EventEmitter;
28135
28135
  var childProcess = __require("child_process");
28136
28136
  var path = __require("path");
28137
- var fs3 = __require("fs");
28137
+ var fs4 = __require("fs");
28138
28138
  var process13 = __require("process");
28139
28139
  var { Argument: Argument2, humanReadableArgName } = require_argument();
28140
28140
  var { CommanderError: CommanderError2 } = require_error();
@@ -29067,10 +29067,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
29067
29067
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
29068
29068
  function findFile(baseDir, baseName) {
29069
29069
  const localBin = path.resolve(baseDir, baseName);
29070
- if (fs3.existsSync(localBin)) return localBin;
29070
+ if (fs4.existsSync(localBin)) return localBin;
29071
29071
  if (sourceExt.includes(path.extname(baseName))) return void 0;
29072
29072
  const foundExt = sourceExt.find(
29073
- (ext) => fs3.existsSync(`${localBin}${ext}`)
29073
+ (ext) => fs4.existsSync(`${localBin}${ext}`)
29074
29074
  );
29075
29075
  if (foundExt) return `${localBin}${foundExt}`;
29076
29076
  return void 0;
@@ -29082,7 +29082,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
29082
29082
  if (this._scriptPath) {
29083
29083
  let resolvedScriptPath;
29084
29084
  try {
29085
- resolvedScriptPath = fs3.realpathSync(this._scriptPath);
29085
+ resolvedScriptPath = fs4.realpathSync(this._scriptPath);
29086
29086
  } catch (err) {
29087
29087
  resolvedScriptPath = this._scriptPath;
29088
29088
  }
@@ -37034,14 +37034,14 @@ var require_parser = __commonJS({
37034
37034
  case "scalar":
37035
37035
  case "single-quoted-scalar":
37036
37036
  case "double-quoted-scalar": {
37037
- const fs3 = this.flowScalar(this.type);
37037
+ const fs4 = this.flowScalar(this.type);
37038
37038
  if (atNextItem || it.value) {
37039
- map.items.push({ start, key: fs3, sep: [] });
37039
+ map.items.push({ start, key: fs4, sep: [] });
37040
37040
  this.onKeyLine = true;
37041
37041
  } else if (it.sep) {
37042
- this.stack.push(fs3);
37042
+ this.stack.push(fs4);
37043
37043
  } else {
37044
- Object.assign(it, { key: fs3, sep: [] });
37044
+ Object.assign(it, { key: fs4, sep: [] });
37045
37045
  this.onKeyLine = true;
37046
37046
  }
37047
37047
  return;
@@ -37169,13 +37169,13 @@ var require_parser = __commonJS({
37169
37169
  case "scalar":
37170
37170
  case "single-quoted-scalar":
37171
37171
  case "double-quoted-scalar": {
37172
- const fs3 = this.flowScalar(this.type);
37172
+ const fs4 = this.flowScalar(this.type);
37173
37173
  if (!it || it.value)
37174
- fc.items.push({ start: [], key: fs3, sep: [] });
37174
+ fc.items.push({ start: [], key: fs4, sep: [] });
37175
37175
  else if (it.sep)
37176
- this.stack.push(fs3);
37176
+ this.stack.push(fs4);
37177
37177
  else
37178
- Object.assign(it, { key: fs3, sep: [] });
37178
+ Object.assign(it, { key: fs4, sep: [] });
37179
37179
  return;
37180
37180
  }
37181
37181
  case "flow-map-end":
@@ -46994,8 +46994,8 @@ function getLogoModeFromEnv(envPrefix) {
46994
46994
  var CLI_NAME = "xcsh";
46995
46995
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
46996
46996
  function getVersion() {
46997
- if ("v2.0.21-2601090318") {
46998
- return "v2.0.21-2601090318";
46997
+ if ("v2.0.21-2601090411") {
46998
+ return "v2.0.21-2601090411";
46999
46999
  }
47000
47000
  if (process.env.XCSH_VERSION) {
47001
47001
  return process.env.XCSH_VERSION;
@@ -75423,6 +75423,203 @@ var deleteCommand = {
75423
75423
  }
75424
75424
  };
75425
75425
 
75426
+ // src/domains/login/profile/edit.ts
75427
+ import { spawnSync } from "child_process";
75428
+ import { promises as fs3 } from "fs";
75429
+ import { tmpdir } from "os";
75430
+ import { join as join4 } from "path";
75431
+ function getEditor() {
75432
+ return process.env.EDITOR || process.env.VISUAL || "vi";
75433
+ }
75434
+ function openInEditor(filePath) {
75435
+ const editor = getEditor();
75436
+ const result = spawnSync(editor, [filePath], {
75437
+ stdio: "inherit",
75438
+ shell: true
75439
+ });
75440
+ if (result.error) {
75441
+ throw new Error(
75442
+ `Failed to launch editor '${editor}': ${result.error.message}`
75443
+ );
75444
+ }
75445
+ if (result.status !== 0) {
75446
+ throw new Error(`Editor exited with code ${result.status}`);
75447
+ }
75448
+ }
75449
+ async function createTempFile(profile) {
75450
+ const tempDir = tmpdir();
75451
+ const tempFile = join4(
75452
+ tempDir,
75453
+ `xcsh-profile-${profile.name}-${Date.now()}.json`
75454
+ );
75455
+ const content = JSON.stringify(
75456
+ {
75457
+ name: profile.name,
75458
+ apiUrl: profile.apiUrl,
75459
+ apiToken: profile.apiToken || "",
75460
+ defaultNamespace: profile.defaultNamespace || "",
75461
+ // Include optional fields only if they exist
75462
+ ...profile.p12Bundle ? { p12Bundle: profile.p12Bundle } : {},
75463
+ ...profile.cert ? { cert: profile.cert } : {},
75464
+ ...profile.key ? { key: profile.key } : {}
75465
+ },
75466
+ null,
75467
+ 2
75468
+ );
75469
+ await fs3.writeFile(tempFile, content, { mode: 384 });
75470
+ return tempFile;
75471
+ }
75472
+ async function parseEditedProfile(filePath, originalName) {
75473
+ try {
75474
+ const content = await fs3.readFile(filePath, "utf-8");
75475
+ const parsed = JSON.parse(content);
75476
+ if (!parsed.name || typeof parsed.name !== "string") {
75477
+ return { profile: null, error: "Profile name is required" };
75478
+ }
75479
+ if (!parsed.apiUrl || typeof parsed.apiUrl !== "string") {
75480
+ return { profile: null, error: "API URL is required" };
75481
+ }
75482
+ if (parsed.name !== originalName) {
75483
+ return {
75484
+ profile: null,
75485
+ error: `Cannot change profile name from '${originalName}' to '${parsed.name}'. Use 'login profile create' to create a new profile instead.`
75486
+ };
75487
+ }
75488
+ const profile = {
75489
+ name: parsed.name,
75490
+ apiUrl: parsed.apiUrl
75491
+ };
75492
+ if (parsed.apiToken && typeof parsed.apiToken === "string" && parsed.apiToken.trim()) {
75493
+ profile.apiToken = parsed.apiToken.trim();
75494
+ }
75495
+ if (parsed.defaultNamespace && typeof parsed.defaultNamespace === "string" && parsed.defaultNamespace.trim()) {
75496
+ profile.defaultNamespace = parsed.defaultNamespace.trim();
75497
+ }
75498
+ if (parsed.p12Bundle && typeof parsed.p12Bundle === "string" && parsed.p12Bundle.trim()) {
75499
+ profile.p12Bundle = parsed.p12Bundle.trim();
75500
+ }
75501
+ if (parsed.cert && typeof parsed.cert === "string" && parsed.cert.trim()) {
75502
+ profile.cert = parsed.cert.trim();
75503
+ }
75504
+ if (parsed.key && typeof parsed.key === "string" && parsed.key.trim()) {
75505
+ profile.key = parsed.key.trim();
75506
+ }
75507
+ return { profile, error: null };
75508
+ } catch (error) {
75509
+ if (error instanceof SyntaxError) {
75510
+ return { profile: null, error: `Invalid JSON: ${error.message}` };
75511
+ }
75512
+ return {
75513
+ profile: null,
75514
+ error: `Failed to read edited file: ${error instanceof Error ? error.message : "Unknown error"}`
75515
+ };
75516
+ }
75517
+ }
75518
+ async function cleanupTempFile(filePath) {
75519
+ try {
75520
+ await fs3.unlink(filePath);
75521
+ } catch {
75522
+ }
75523
+ }
75524
+ var editCommand = {
75525
+ name: "edit",
75526
+ description: "Edit a profile configuration using your preferred text editor ($EDITOR). Opens the profile in JSON format for modification. After saving, the profile is validated and updated. If editing the active profile, the session is automatically refreshed with the new settings.",
75527
+ descriptionShort: "Edit profile in $EDITOR",
75528
+ descriptionMedium: "Open a profile in your text editor for modification, then validate and save changes.",
75529
+ usage: "[name]",
75530
+ aliases: ["modify", "vi"],
75531
+ async execute(args, session) {
75532
+ const manager = getProfileManager();
75533
+ let profileName = args[0];
75534
+ if (!profileName) {
75535
+ const active = await manager.getActive();
75536
+ if (!active) {
75537
+ return errorResult(
75538
+ "No profile specified and no active profile set.\nUsage: login profile edit <name>"
75539
+ );
75540
+ }
75541
+ profileName = active;
75542
+ }
75543
+ const existingProfile = await manager.get(profileName);
75544
+ if (!existingProfile) {
75545
+ return errorResult(
75546
+ `Profile '${profileName}' not found.
75547
+ Use 'login profile list' to see available profiles.`
75548
+ );
75549
+ }
75550
+ if (!process.stdin.isTTY) {
75551
+ return errorResult(
75552
+ "Edit command requires an interactive terminal.\nUse 'login profile show <name>' to view profile details."
75553
+ );
75554
+ }
75555
+ let tempFile = null;
75556
+ try {
75557
+ tempFile = await createTempFile(existingProfile);
75558
+ const statsBefore = await fs3.stat(tempFile);
75559
+ const editor = getEditor();
75560
+ const output = [
75561
+ `Opening profile '${profileName}' in ${editor}...`
75562
+ ];
75563
+ openInEditor(tempFile);
75564
+ const statsAfter = await fs3.stat(tempFile);
75565
+ if (statsBefore.mtimeMs === statsAfter.mtimeMs) {
75566
+ output.push("", "No changes made.");
75567
+ return successResult(output);
75568
+ }
75569
+ const { profile: editedProfile, error } = await parseEditedProfile(
75570
+ tempFile,
75571
+ profileName
75572
+ );
75573
+ if (error || !editedProfile) {
75574
+ return errorResult(`Validation failed: ${error}`);
75575
+ }
75576
+ const saveResult = await manager.save(editedProfile);
75577
+ if (!saveResult.success) {
75578
+ return errorResult(
75579
+ `Failed to save profile: ${saveResult.message}`
75580
+ );
75581
+ }
75582
+ output.push("", `Profile '${profileName}' updated successfully.`);
75583
+ const activeProfileName = await manager.getActive();
75584
+ const isActive = profileName === activeProfileName;
75585
+ if (isActive) {
75586
+ const success = await session.switchProfile(profileName);
75587
+ if (success) {
75588
+ output.push("", "Session refreshed with updated settings.");
75589
+ const profile = session.getActiveProfile();
75590
+ const connectionInfo = buildConnectionInfo(
75591
+ profileName,
75592
+ profile?.apiUrl || "",
75593
+ !!profile?.apiToken,
75594
+ profile?.defaultNamespace || session.getNamespace(),
75595
+ session.isAuthenticated(),
75596
+ session.isTokenValidated(),
75597
+ session.getValidationError() ?? void 0
75598
+ );
75599
+ const tableLines = formatConnectionTable(connectionInfo);
75600
+ output.push("", ...tableLines);
75601
+ return successResult(output, true);
75602
+ }
75603
+ }
75604
+ return successResult(output);
75605
+ } catch (error) {
75606
+ const message = error instanceof Error ? error.message : String(error);
75607
+ return errorResult(`Edit failed: ${message}`);
75608
+ } finally {
75609
+ if (tempFile) {
75610
+ await cleanupTempFile(tempFile);
75611
+ }
75612
+ }
75613
+ },
75614
+ async completion(partial, _args, _session) {
75615
+ const manager = getProfileManager();
75616
+ const profiles = await manager.list();
75617
+ return profiles.map((p) => p.name).filter(
75618
+ (name) => name.toLowerCase().startsWith(partial.toLowerCase())
75619
+ );
75620
+ }
75621
+ };
75622
+
75426
75623
  // src/domains/login/profile/active.ts
75427
75624
  var activeCommand = {
75428
75625
  name: "active",
@@ -76235,6 +76432,7 @@ var profileSubcommands = {
76235
76432
  ["show", showCommand],
76236
76433
  ["create", createCommand2],
76237
76434
  ["use", useCommand],
76435
+ ["edit", editCommand],
76238
76436
  ["delete", deleteCommand]
76239
76437
  ]),
76240
76438
  defaultCommand: activeCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "2.0.21-2601090318",
3
+ "version": "2.0.21-2601090411",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {