@robinmordasiewicz/f5xc-xcsh 2.0.21-2601101304 → 2.0.21-2601122136

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.
Files changed (2) hide show
  1. package/dist/index.js +219 -74
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49215,8 +49215,8 @@ function getLogoModeFromEnv(envPrefix) {
49215
49215
  var CLI_NAME = "xcsh";
49216
49216
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
49217
49217
  function getVersion() {
49218
- if ("v2.0.21-2601101304") {
49219
- return "v2.0.21-2601101304";
49218
+ if ("v2.0.21-2601122136") {
49219
+ return "v2.0.21-2601122136";
49220
49220
  }
49221
49221
  if (process.env.XCSH_VERSION) {
49222
49222
  return process.env.XCSH_VERSION;
@@ -50512,15 +50512,33 @@ var DEFAULT_RESOURCE_COLUMNS = [
50512
50512
  { header: "NAME", accessor: "name", minWidth: 10, maxWidth: 40 },
50513
50513
  { header: "LABELS", accessor: "labels", minWidth: 10, maxWidth: 35 }
50514
50514
  ];
50515
+ var TOP_LEVEL_RESOURCE_COLUMNS = [
50516
+ { header: "NAME", accessor: "name", minWidth: 10, maxWidth: 30 },
50517
+ {
50518
+ header: "DESCRIPTION",
50519
+ accessor: "description",
50520
+ minWidth: 15,
50521
+ maxWidth: 50
50522
+ },
50523
+ { header: "LABELS", accessor: "labels", minWidth: 10, maxWidth: 35 }
50524
+ ];
50525
+ function isTopLevelResource(items) {
50526
+ if (items.length === 0) return false;
50527
+ return items.every((item) => {
50528
+ const ns = item.namespace;
50529
+ return ns === "" || ns === null || ns === void 0;
50530
+ });
50531
+ }
50515
50532
  function formatResourceTable(data, noColor = false) {
50516
50533
  const items = extractItems(data);
50517
50534
  if (items.length === 0) {
50518
50535
  return "";
50519
50536
  }
50537
+ const columns = isTopLevelResource(items) ? TOP_LEVEL_RESOURCE_COLUMNS : DEFAULT_RESOURCE_COLUMNS;
50520
50538
  return formatBeautifulTable(
50521
50539
  items,
50522
50540
  {
50523
- columns: DEFAULT_RESOURCE_COLUMNS,
50541
+ columns,
50524
50542
  wrapText: true
50525
50543
  },
50526
50544
  noColor
@@ -75942,6 +75960,8 @@ var REPLSession = class {
75942
75960
  _fallbackReason = null;
75943
75961
  // Connection status for graceful offline handling
75944
75962
  _connectionStatus = "unknown";
75963
+ // Track if environment variables are present (for display purposes)
75964
+ _envVarsPresent = false;
75945
75965
  constructor(config = {}) {
75946
75966
  this._namespace = config.namespace ?? this.getDefaultNamespace();
75947
75967
  this._contextPath = new ContextPath();
@@ -75949,6 +75969,7 @@ var REPLSession = class {
75949
75969
  this._profileManager = getProfileManager();
75950
75970
  const envUrl = process.env[`${ENV_PREFIX}_API_URL`];
75951
75971
  const envToken = process.env[`${ENV_PREFIX}_API_TOKEN`];
75972
+ this._envVarsPresent = !!(envUrl || envToken);
75952
75973
  this._serverUrl = config.serverUrl ?? envUrl ?? "";
75953
75974
  this._apiToken = config.apiToken ?? envToken ?? "";
75954
75975
  if (envUrl && envToken) {
@@ -76147,8 +76168,9 @@ var REPLSession = class {
76147
76168
  }
76148
76169
  /**
76149
76170
  * Load the active profile from profile manager
76150
- * Note: Environment variables take priority over profile settings
76151
- * If no active profile is set but exactly one profile exists, auto-activate it
76171
+ * Profile credentials ALWAYS take priority when a profile is active.
76172
+ * Environment variables are only used when NO profile is active (fallback for scripts/CI).
76173
+ * If no active profile is set but exactly one profile exists, auto-activate it.
76152
76174
  */
76153
76175
  async loadActiveProfile() {
76154
76176
  try {
@@ -76166,28 +76188,18 @@ var REPLSession = class {
76166
76188
  if (profile) {
76167
76189
  this._activeProfileName = activeName;
76168
76190
  this._activeProfile = profile;
76169
- const envUrl = process.env[`${ENV_PREFIX}_API_URL`];
76170
- const envToken = process.env[`${ENV_PREFIX}_API_TOKEN`];
76171
- const envNamespace = process.env[`${ENV_PREFIX}_NAMESPACE`];
76172
- let usingProfileUrl = false;
76173
- let usingProfileToken = false;
76174
- if (!envUrl && profile.apiUrl) {
76191
+ if (profile.apiUrl) {
76175
76192
  this._serverUrl = profile.apiUrl;
76176
76193
  this._tenant = this.extractTenant(profile.apiUrl);
76177
- usingProfileUrl = true;
76178
76194
  }
76179
- if (!envToken && profile.apiToken) {
76195
+ if (profile.apiToken) {
76180
76196
  this._apiToken = profile.apiToken;
76181
- usingProfileToken = true;
76182
76197
  }
76198
+ const envNamespace = process.env[`${ENV_PREFIX}_NAMESPACE`];
76183
76199
  if (!envNamespace && profile.defaultNamespace) {
76184
76200
  this._namespace = profile.defaultNamespace;
76185
76201
  }
76186
- if (usingProfileUrl && usingProfileToken) {
76187
- this._authSource = "profile";
76188
- } else if (usingProfileUrl || usingProfileToken) {
76189
- this._authSource = "mixed";
76190
- }
76202
+ this._authSource = "profile";
76191
76203
  if (this._serverUrl) {
76192
76204
  this._apiClient = new APIClient({
76193
76205
  serverUrl: this._serverUrl,
@@ -76322,6 +76334,13 @@ var REPLSession = class {
76322
76334
  getAuthSource() {
76323
76335
  return this._authSource;
76324
76336
  }
76337
+ /**
76338
+ * Check if environment variables are present
76339
+ * Used for display purposes to show when env vars are being ignored
76340
+ */
76341
+ getEnvVarsPresent() {
76342
+ return this._envVarsPresent;
76343
+ }
76325
76344
  /**
76326
76345
  * Check if a credential fallback was attempted
76327
76346
  */
@@ -76390,6 +76409,7 @@ var REPLSession = class {
76390
76409
  }
76391
76410
  /**
76392
76411
  * Switch to a different profile
76412
+ * Profile credentials always take priority over environment variables.
76393
76413
  */
76394
76414
  async switchProfile(profileName) {
76395
76415
  const profile = await this._profileManager.get(profileName);
@@ -76417,6 +76437,10 @@ var REPLSession = class {
76417
76437
  if (profile.defaultNamespace) {
76418
76438
  this._namespace = profile.defaultNamespace;
76419
76439
  }
76440
+ this._authSource = "profile";
76441
+ const envUrl = process.env[`${ENV_PREFIX}_API_URL`];
76442
+ const envToken = process.env[`${ENV_PREFIX}_API_TOKEN`];
76443
+ this._envVarsPresent = !!(envUrl || envToken);
76420
76444
  if (this._serverUrl) {
76421
76445
  this._apiClient = new APIClient({
76422
76446
  serverUrl: this._serverUrl,
@@ -77924,17 +77948,32 @@ function extractTenantFromUrl(url) {
77924
77948
  return "unknown";
77925
77949
  }
77926
77950
  }
77927
- function getAuthStatusValue(info, colorStatus) {
77928
- if (!info.hasToken) {
77929
- return colorStatus("\u2717 No token", false);
77951
+ function getAuthSourceDisplay(info) {
77952
+ if (info.authSource === "env") {
77953
+ return "Environment variables";
77930
77954
  }
77931
- if (info.isValidated) {
77932
- return colorStatus("\u2713 Authenticated", true);
77955
+ if (info.authSource === "profile") {
77956
+ if (info.envVarsPresent) {
77957
+ return "Profile (env vars ignored)";
77958
+ }
77959
+ return "Profile";
77933
77960
  }
77934
- if (info.validationError) {
77935
- return colorStatus(`\u2717 ${info.validationError}`, false);
77961
+ if (info.authSource === "profile-fallback") {
77962
+ return "Profile (fallback)";
77963
+ }
77964
+ if (info.authSource === "mixed") {
77965
+ return "Mixed (env + profile)";
77966
+ }
77967
+ return "Not configured";
77968
+ }
77969
+ function getStatusValue(info, colorStatus) {
77970
+ if (info.isConnected && info.isValidated) {
77971
+ return colorStatus("\u25CF Connected & Authenticated", true);
77972
+ }
77973
+ if (info.hasToken && !info.isConnected) {
77974
+ return colorStatus("\u25CB Offline (token not verified)", false);
77936
77975
  }
77937
- return colorStatus("\u26A0 Token not verified", false);
77976
+ return colorStatus("\u25CB Not authenticated", false);
77938
77977
  }
77939
77978
  function formatConnectionTable(info, noColor = false) {
77940
77979
  const useColors = shouldUseColors(void 0, noColor);
@@ -77945,17 +77984,14 @@ function formatConnectionTable(info, noColor = false) {
77945
77984
  const colorBorder = (text) => useColors ? `${borderColor}${text}${colors.reset}` : text;
77946
77985
  const colorStatus = (text, isGood) => useColors ? `${isGood ? successColor : errorColor}${text}${colors.reset}` : text;
77947
77986
  const rows = [
77948
- { label: "Profile", value: info.profileName },
77987
+ { label: "Login Profile", value: info.profileName },
77949
77988
  { label: "Tenant", value: info.tenant },
77950
77989
  { label: "API URL", value: info.apiUrl },
77951
- {
77952
- label: "Auth",
77953
- value: getAuthStatusValue(info, colorStatus)
77954
- },
77955
- { label: "Namespace", value: info.namespace || "default" },
77990
+ { label: "Default Namespace", value: info.namespace || "default" },
77991
+ { label: "Auth Source", value: getAuthSourceDisplay(info) },
77956
77992
  {
77957
77993
  label: "Status",
77958
- value: info.isConnected ? colorStatus("\u25CF Connected", true) : colorStatus("\u25CB Not connected", false)
77994
+ value: getStatusValue(info, colorStatus)
77959
77995
  }
77960
77996
  ];
77961
77997
  const labelWidth = Math.max(...rows.map((r) => r.label.length));
@@ -77991,7 +78027,7 @@ function formatConnectionTable(info, noColor = false) {
77991
78027
  function stripAnsi2(str) {
77992
78028
  return str.replace(/\x1b\[[0-9;]*m/g, "");
77993
78029
  }
77994
- function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnected, isValidated, validationError) {
78030
+ function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnected, isValidated, validationError, authSource, envVarsPresent) {
77995
78031
  const info = {
77996
78032
  profileName,
77997
78033
  tenant: extractTenantFromUrl(apiUrl),
@@ -78006,6 +78042,12 @@ function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnect
78006
78042
  if (validationError) {
78007
78043
  info.validationError = validationError;
78008
78044
  }
78045
+ if (authSource !== void 0) {
78046
+ info.authSource = authSource;
78047
+ }
78048
+ if (envVarsPresent !== void 0) {
78049
+ info.envVarsPresent = envVarsPresent;
78050
+ }
78009
78051
  return info;
78010
78052
  }
78011
78053
 
@@ -78479,7 +78521,9 @@ var createCommand2 = {
78479
78521
  defaultNamespace || session.getNamespace(),
78480
78522
  session.isAuthenticated(),
78481
78523
  session.isTokenValidated(),
78482
- session.getValidationError() ?? void 0
78524
+ session.getValidationError() ?? void 0,
78525
+ session.getAuthSource(),
78526
+ session.getEnvVarsPresent()
78483
78527
  );
78484
78528
  const tableLines = formatConnectionTable(connectionInfo);
78485
78529
  return successResult(
@@ -78516,7 +78560,9 @@ var useCommand = {
78516
78560
  profile?.defaultNamespace || session.getNamespace(),
78517
78561
  session.isAuthenticated(),
78518
78562
  session.isTokenValidated(),
78519
- session.getValidationError() ?? void 0
78563
+ session.getValidationError() ?? void 0,
78564
+ session.getAuthSource(),
78565
+ session.getEnvVarsPresent()
78520
78566
  );
78521
78567
  const tableLines = formatConnectionTable(connectionInfo);
78522
78568
  return successResult(
@@ -78765,7 +78811,9 @@ Use 'login profile list' to see available profiles.`
78765
78811
  profile?.defaultNamespace || session.getNamespace(),
78766
78812
  session.isAuthenticated(),
78767
78813
  session.isTokenValidated(),
78768
- session.getValidationError() ?? void 0
78814
+ session.getValidationError() ?? void 0,
78815
+ session.getAuthSource(),
78816
+ session.getEnvVarsPresent()
78769
78817
  );
78770
78818
  const tableLines = formatConnectionTable(connectionInfo);
78771
78819
  output.push("", ...tableLines);
@@ -84444,6 +84492,37 @@ function useGitStatus(options = {}) {
84444
84492
  return { gitInfo, refresh, lastRefresh };
84445
84493
  }
84446
84494
 
84495
+ // src/operations/resolver.ts
84496
+ function getOperationDefinition(domain, action, resourceType) {
84497
+ const domainOps = generatedOperations.get(domain);
84498
+ if (!domainOps?.operations) return null;
84499
+ if (resourceType) {
84500
+ const exactMatch = domainOps.operations.find(
84501
+ (op) => op.action === action && op.resourceType === resourceType
84502
+ );
84503
+ if (exactMatch) return exactMatch;
84504
+ }
84505
+ return domainOps.operations.find((op) => op.action === action) ?? null;
84506
+ }
84507
+ function substitutePathParams(pathTemplate, params) {
84508
+ let path = pathTemplate;
84509
+ if (params.namespace) {
84510
+ path = path.replace(/\{namespace\}/g, params.namespace);
84511
+ path = path.replace(/\{metadata\.namespace\}/g, params.namespace);
84512
+ }
84513
+ if (params.name) {
84514
+ path = path.replace(/\{name\}/g, params.name);
84515
+ path = path.replace(/\{metadata\.name\}/g, params.name);
84516
+ }
84517
+ if (params.site) {
84518
+ path = path.replace(/\{site\}/g, params.site);
84519
+ }
84520
+ return path;
84521
+ }
84522
+ function hasUnsubstitutedParams(path) {
84523
+ return /\{[^}]+\}/.test(path);
84524
+ }
84525
+
84447
84526
  // src/repl/executor.ts
84448
84527
  var WRITE_OPERATIONS = /* @__PURE__ */ new Set([
84449
84528
  "create",
@@ -84855,6 +84934,18 @@ async function handleDirectNavigation(cmd, ctx, session) {
84855
84934
  error: "Unknown domain"
84856
84935
  };
84857
84936
  }
84937
+ if (cmd.targetAction && cmd.args.length > 0) {
84938
+ ctx.reset();
84939
+ ctx.setDomain(cmd.targetDomain);
84940
+ ctx.setAction(cmd.targetAction);
84941
+ const apiCmd = {
84942
+ raw: [cmd.targetDomain, cmd.targetAction, ...cmd.args].join(" "),
84943
+ args: cmd.args,
84944
+ isBuiltin: false,
84945
+ isDirectNavigation: false
84946
+ };
84947
+ return await executeAPICommand(session, ctx, apiCmd);
84948
+ }
84858
84949
  ctx.reset();
84859
84950
  ctx.setDomain(cmd.targetDomain);
84860
84951
  if (cmd.targetAction) {
@@ -85294,8 +85385,35 @@ Suggestion: Use --namespace ${nsValidation.suggestion}` : "";
85294
85385
  contextChanged: false
85295
85386
  };
85296
85387
  }
85297
- const resourcePath = domainToResourcePath(effectiveResource);
85298
- let apiPath = `/api/config/namespaces/${effectiveNamespace}/${resourcePath}`;
85388
+ let effectiveResourceType = resourceType;
85389
+ if (!effectiveResourceType && args.length > 0) {
85390
+ effectiveResourceType = args[0]?.toLowerCase();
85391
+ }
85392
+ const operation = getOperationDefinition(
85393
+ canonicalDomain,
85394
+ action,
85395
+ effectiveResourceType
85396
+ );
85397
+ let apiPath;
85398
+ let usedOperationPath = false;
85399
+ if (operation?.path) {
85400
+ const pathParams = {
85401
+ namespace: effectiveNamespace
85402
+ };
85403
+ if (name) {
85404
+ pathParams.name = name;
85405
+ }
85406
+ apiPath = substitutePathParams(operation.path, pathParams);
85407
+ usedOperationPath = true;
85408
+ if (hasUnsubstitutedParams(apiPath)) {
85409
+ const resourcePath = domainToResourcePath(effectiveResource);
85410
+ apiPath = `/api/config/namespaces/${effectiveNamespace}/${resourcePath}`;
85411
+ usedOperationPath = false;
85412
+ }
85413
+ } else {
85414
+ const resourcePath = domainToResourcePath(effectiveResource);
85415
+ apiPath = `/api/config/namespaces/${effectiveNamespace}/${resourcePath}`;
85416
+ }
85299
85417
  try {
85300
85418
  let result;
85301
85419
  switch (action) {
@@ -85316,7 +85434,9 @@ Suggestion: Use --namespace ${nsValidation.suggestion}` : "";
85316
85434
  error: "Usage: get <name>"
85317
85435
  };
85318
85436
  }
85319
- apiPath += `/${name}`;
85437
+ if (!usedOperationPath) {
85438
+ apiPath += `/${name}`;
85439
+ }
85320
85440
  const response = await client.get(apiPath);
85321
85441
  result = response.data;
85322
85442
  break;
@@ -85333,7 +85453,9 @@ Suggestion: Use --namespace ${nsValidation.suggestion}` : "";
85333
85453
  error: "Usage: delete <name>"
85334
85454
  };
85335
85455
  }
85336
- apiPath += `/${name}`;
85456
+ if (!usedOperationPath) {
85457
+ apiPath += `/${name}`;
85458
+ }
85337
85459
  await client.delete(apiPath);
85338
85460
  result = { message: `Deleted ${canonicalDomain} '${name}'` };
85339
85461
  break;
@@ -85363,7 +85485,9 @@ Suggestion: Use --namespace ${nsValidation.suggestion}` : "";
85363
85485
  error: "Usage: status <name>"
85364
85486
  };
85365
85487
  }
85366
- apiPath += `/${name}/status`;
85488
+ if (!usedOperationPath) {
85489
+ apiPath += `/${name}/status`;
85490
+ }
85367
85491
  const response = await client.get(apiPath);
85368
85492
  result = response.data;
85369
85493
  break;
@@ -86302,47 +86426,68 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
86302
86426
  profiler.memorySnapshot("pre_banner");
86303
86427
  printProfileReport();
86304
86428
  renderBanner(cliLogoMode, "startup");
86305
- if (session.isOfflineMode()) {
86429
+ const activeProfile = session.getActiveProfile();
86430
+ const envConfigured = process.env[`${ENV_PREFIX}_API_URL`] && process.env[`${ENV_PREFIX}_API_TOKEN`];
86431
+ if (activeProfile) {
86306
86432
  console.log("");
86307
- console.log(
86308
- `${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
86433
+ const connectionInfo = buildConnectionInfo(
86434
+ session.getActiveProfileName() || activeProfile.name || "default",
86435
+ activeProfile.apiUrl || session.getServerUrl() || "",
86436
+ !!activeProfile.apiToken || session.isAuthenticated(),
86437
+ activeProfile.defaultNamespace || session.getNamespace(),
86438
+ session.isAuthenticated() && !session.isOfflineMode(),
86439
+ session.isTokenValidated(),
86440
+ session.getValidationError() ?? void 0,
86441
+ session.getAuthSource(),
86442
+ session.getEnvVarsPresent()
86309
86443
  );
86310
- console.log(
86311
- `${colors.dim} Commands requiring API access will fail. Check network and try again.${colors.reset}`
86312
- );
86313
- }
86314
- if (session.getAuthSource() === "profile-fallback") {
86315
- const profileName = session.getActiveProfileName();
86316
- console.log("");
86317
- console.log(
86318
- `${colors.blue}Info: Using credentials from profile '${profileName}' (environment variables were invalid)${colors.reset}`
86319
- );
86320
- }
86321
- if (session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError()) {
86322
- const authSource = session.getAuthSource();
86323
- const fallbackReason = session.getFallbackReason();
86324
- console.log("");
86325
- if (authSource === "env" || authSource === "mixed") {
86444
+ const tableLines = formatConnectionTable(connectionInfo);
86445
+ tableLines.forEach((line) => console.log(line));
86446
+ if (session.isOfflineMode()) {
86447
+ console.log("");
86326
86448
  console.log(
86327
- `${colors.yellow}Warning: Environment variable credentials are invalid or expired${colors.reset}`
86449
+ `${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
86328
86450
  );
86329
- if (fallbackReason) {
86330
- console.log(
86331
- `${colors.dim} ${fallbackReason}${colors.reset}`
86332
- );
86333
- }
86334
86451
  console.log(
86335
- `${colors.dim} Run 'login' to authenticate or update your F5XC_API_TOKEN environment variable${colors.reset}`
86452
+ `${colors.dim} Commands requiring API access will fail.${colors.reset}`
86336
86453
  );
86337
- } else {
86454
+ }
86455
+ if (session.getAuthSource() === "profile-fallback") {
86456
+ console.log("");
86457
+ console.log(
86458
+ `${colors.blue}Info: Using credentials from profile '${session.getActiveProfileName()}' (environment variables were invalid)${colors.reset}`
86459
+ );
86460
+ }
86461
+ } else if (envConfigured) {
86462
+ console.log("");
86463
+ const connectionInfo = buildConnectionInfo(
86464
+ "(environment)",
86465
+ process.env[`${ENV_PREFIX}_API_URL`] || "",
86466
+ !!process.env[`${ENV_PREFIX}_API_TOKEN`],
86467
+ session.getNamespace(),
86468
+ session.isAuthenticated() && !session.isOfflineMode(),
86469
+ session.isTokenValidated(),
86470
+ session.getValidationError() ?? void 0,
86471
+ "env",
86472
+ // authSource - using environment variables
86473
+ true
86474
+ // envVarsPresent - by definition, this flow means env vars exist
86475
+ );
86476
+ const tableLines = formatConnectionTable(connectionInfo);
86477
+ tableLines.forEach((line) => console.log(line));
86478
+ if (session.isOfflineMode()) {
86479
+ console.log("");
86480
+ console.log(
86481
+ `${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
86482
+ );
86483
+ }
86484
+ if (!session.isTokenValidated() && session.getValidationError()) {
86485
+ console.log("");
86338
86486
  console.log(
86339
86487
  `${colors.yellow}Warning: ${session.getValidationError()}${colors.reset}`
86340
86488
  );
86341
86489
  }
86342
- }
86343
- const profiles = await session.getProfileManager().list();
86344
- const envConfigured = process.env[`${ENV_PREFIX}_API_URL`] && process.env[`${ENV_PREFIX}_API_TOKEN`];
86345
- if (profiles.length === 0 && !envConfigured) {
86490
+ } else {
86346
86491
  console.log("");
86347
86492
  console.log(
86348
86493
  `${colors.yellow}No connection profiles found.${colors.reset}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "2.0.21-2601101304",
3
+ "version": "2.0.21-2601122136",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {