@saptools/cf-events 0.1.1 → 0.1.3

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/dist/cli.js CHANGED
@@ -5,10 +5,6 @@ import { readFileSync, realpathSync } from "fs";
5
5
  import { dirname, join as join2, resolve } from "path";
6
6
  import process2 from "process";
7
7
  import { fileURLToPath } from "url";
8
- import {
9
- formatCurrentCfAppSelector,
10
- readCurrentCfTarget
11
- } from "@saptools/cf-sync";
12
8
  import { Command } from "commander";
13
9
 
14
10
  // src/types.ts
@@ -397,6 +393,110 @@ import { execFile } from "child_process";
397
393
  import { mkdtemp, rm } from "fs/promises";
398
394
  import { tmpdir } from "os";
399
395
  import { join } from "path";
396
+ import { promisify } from "util";
397
+ var execFileAsync = promisify(execFile);
398
+ var REGION_API_MAP = {
399
+ ae01: "https://api.cf.ae01.hana.ondemand.com",
400
+ ap01: "https://api.cf.ap01.hana.ondemand.com",
401
+ ap10: "https://api.cf.ap10.hana.ondemand.com",
402
+ ap11: "https://api.cf.ap11.hana.ondemand.com",
403
+ ap12: "https://api.cf.ap12.hana.ondemand.com",
404
+ ap20: "https://api.cf.ap20.hana.ondemand.com",
405
+ ap21: "https://api.cf.ap21.hana.ondemand.com",
406
+ ap30: "https://api.cf.ap30.hana.ondemand.com",
407
+ br10: "https://api.cf.br10.hana.ondemand.com",
408
+ br20: "https://api.cf.br20.hana.ondemand.com",
409
+ br30: "https://api.cf.br30.hana.ondemand.com",
410
+ ca10: "https://api.cf.ca10.hana.ondemand.com",
411
+ ca20: "https://api.cf.ca20.hana.ondemand.com",
412
+ ch20: "https://api.cf.ch20.hana.ondemand.com",
413
+ cn20: "https://api.cf.cn20.platform.sapcloud.cn",
414
+ cn40: "https://api.cf.cn40.platform.sapcloud.cn",
415
+ eu01: "https://api.cf.eu01.hana.ondemand.com",
416
+ eu02: "https://api.cf.eu02.hana.ondemand.com",
417
+ eu10: "https://api.cf.eu10.hana.ondemand.com",
418
+ "eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
419
+ "eu10-003": "https://api.cf.eu10-003.hana.ondemand.com",
420
+ "eu10-004": "https://api.cf.eu10-004.hana.ondemand.com",
421
+ "eu10-005": "https://api.cf.eu10-005.hana.ondemand.com",
422
+ "eu10-006": "https://api.cf.eu10-006.hana.ondemand.com",
423
+ eu11: "https://api.cf.eu11.hana.ondemand.com",
424
+ eu13: "https://api.cf.eu13.hana.ondemand.com",
425
+ eu20: "https://api.cf.eu20.hana.ondemand.com",
426
+ "eu20-001": "https://api.cf.eu20-001.hana.ondemand.com",
427
+ eu30: "https://api.cf.eu30.hana.ondemand.com",
428
+ in30: "https://api.cf.in30.hana.ondemand.com",
429
+ jp10: "https://api.cf.jp10.hana.ondemand.com",
430
+ jp20: "https://api.cf.jp20.hana.ondemand.com",
431
+ jp30: "https://api.cf.jp30.hana.ondemand.com",
432
+ jp31: "https://api.cf.jp31.hana.ondemand.com",
433
+ sa30: "https://api.cf.sa30.hana.ondemand.com",
434
+ us10: "https://api.cf.us10.hana.ondemand.com",
435
+ "us10-001": "https://api.cf.us10-001.hana.ondemand.com",
436
+ "us10-002": "https://api.cf.us10-002.hana.ondemand.com",
437
+ us20: "https://api.cf.us20.hana.ondemand.com",
438
+ us21: "https://api.cf.us21.hana.ondemand.com",
439
+ us30: "https://api.cf.us30.hana.ondemand.com"
440
+ // add more if needed, this covers common SAP BTP regions
441
+ };
442
+ function getApiEndpointForRegion(regionKey) {
443
+ return REGION_API_MAP[regionKey];
444
+ }
445
+ function getRegionKeyForApi(apiEndpoint) {
446
+ const normalized = apiEndpoint.trim().replace(/\/+$/, "").toLowerCase();
447
+ for (const [key, api] of Object.entries(REGION_API_MAP)) {
448
+ if (api.toLowerCase() === normalized) {
449
+ return key;
450
+ }
451
+ }
452
+ return void 0;
453
+ }
454
+ async function readCurrentCfTarget() {
455
+ const cfBin = process.env["CF_EVENTS_CF_BIN"] ?? "cf";
456
+ const isScript = cfBin.endsWith(".mjs") || cfBin.endsWith(".js");
457
+ const file = isScript ? "node" : cfBin;
458
+ const args = isScript ? [cfBin, "target"] : ["target"];
459
+ try {
460
+ const { stdout } = await execFileAsync(file, args, {
461
+ maxBuffer: 1024 * 1024,
462
+ timeout: 1e4
463
+ });
464
+ return parseCfTargetOutput(stdout);
465
+ } catch {
466
+ return void 0;
467
+ }
468
+ }
469
+ function parseCfTargetOutput(stdout) {
470
+ const fields = parseCfTargetFields(stdout);
471
+ const apiEndpoint = fields.get("api endpoint");
472
+ const orgName = fields.get("org");
473
+ const spaceName = fields.get("space");
474
+ if (!apiEndpoint || !orgName || !spaceName) {
475
+ return void 0;
476
+ }
477
+ const regionKey = getRegionKeyForApi(apiEndpoint);
478
+ return {
479
+ apiEndpoint,
480
+ orgName,
481
+ spaceName,
482
+ ...regionKey ? { regionKey } : {}
483
+ };
484
+ }
485
+ function parseCfTargetFields(stdout) {
486
+ const fields = /* @__PURE__ */ new Map();
487
+ for (const line of stdout.split(/\r?\n/)) {
488
+ const sep = line.indexOf(":");
489
+ if (sep < 0) {
490
+ continue;
491
+ }
492
+ const key = line.slice(0, sep).trim().toLowerCase();
493
+ const value = line.slice(sep + 1).trim();
494
+ if (key && value) {
495
+ fields.set(key, value);
496
+ }
497
+ }
498
+ return fields;
499
+ }
400
500
  var CF_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
401
501
  var CF_COMMAND_TIMEOUT_MS = 3e4;
402
502
  var CF_RETRY_MAX_ATTEMPTS = 3;
@@ -526,7 +626,6 @@ function extractSafeCliDetail(error) {
526
626
  }
527
627
 
528
628
  // src/selector.ts
529
- import { readStructure } from "@saptools/cf-sync";
530
629
  var SELECTOR_USAGE = 'use "region/org/space/app" or a bare app name';
531
630
  function parseSelector(raw) {
532
631
  const trimmed = raw.trim();
@@ -550,92 +649,39 @@ function parseSelector(raw) {
550
649
  }
551
650
  throw new Error(`Invalid selector "${raw}": ${SELECTOR_USAGE}.`);
552
651
  }
553
- function resolveExplicit(raw, parsed, structure) {
554
- const region = structure.regions.find((entry) => entry.key === parsed.regionKey);
555
- if (region === void 0) {
556
- throw new Error(
557
- `Region "${parsed.regionKey}" is not in the CF topology snapshot. Run \`cf-sync sync\` first (or \`cf-sync regions\` to list valid region keys).`
558
- );
559
- }
560
- const org = region.orgs.find((entry) => entry.name === parsed.orgName);
561
- if (org === void 0) {
562
- throw new Error(
563
- `Org "${parsed.orgName}" was not found in region ${parsed.regionKey}. Run \`cf-sync orgs ${parsed.regionKey}\` to refresh it.`
564
- );
565
- }
566
- const space = org.spaces.find((entry) => entry.name === parsed.spaceName);
567
- if (space === void 0) {
568
- throw new Error(
569
- `Space "${parsed.spaceName}" was not found in ${parsed.regionKey}/${parsed.orgName}. Run \`cf-sync org ${parsed.regionKey} ${parsed.orgName}\` to refresh it.`
570
- );
571
- }
572
- const app = space.apps.find((entry) => entry.name === parsed.appName);
573
- if (app === void 0) {
652
+ async function resolveSelector(raw) {
653
+ const parsed = parseSelector(raw);
654
+ if (parsed.kind === "appName") {
655
+ const current = await readCurrentCfTarget();
656
+ if (!current) {
657
+ throw new Error(
658
+ "No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector."
659
+ );
660
+ }
661
+ return {
662
+ raw,
663
+ regionKey: current.regionKey ?? "",
664
+ apiEndpoint: current.apiEndpoint,
665
+ orgName: current.orgName,
666
+ spaceName: current.spaceName,
667
+ appName: parsed.appName
668
+ };
669
+ }
670
+ const apiEndpoint = getApiEndpointForRegion(parsed.regionKey);
671
+ if (!apiEndpoint) {
574
672
  throw new Error(
575
- `App "${parsed.appName}" was not found in ${parsed.regionKey}/${parsed.orgName}/${parsed.spaceName}. Run \`cf-sync space ${parsed.regionKey} ${parsed.orgName} ${parsed.spaceName}\` to refresh it.`
673
+ `Unknown region "${parsed.regionKey}". Use a bare app name (requires current CF target) or a known region key.`
576
674
  );
577
675
  }
578
676
  return {
579
677
  raw,
580
- regionKey: region.key,
581
- apiEndpoint: region.apiEndpoint,
582
- orgName: org.name,
583
- spaceName: space.name,
584
- appName: app.name
678
+ regionKey: parsed.regionKey,
679
+ apiEndpoint,
680
+ orgName: parsed.orgName,
681
+ spaceName: parsed.spaceName,
682
+ appName: parsed.appName
585
683
  };
586
684
  }
587
- function resolveByAppName(raw, appName, structure) {
588
- const matches = [];
589
- for (const region of structure.regions) {
590
- for (const org of region.orgs) {
591
- for (const space of org.spaces) {
592
- for (const app of space.apps) {
593
- if (app.name === appName) {
594
- matches.push({
595
- raw,
596
- regionKey: region.key,
597
- apiEndpoint: region.apiEndpoint,
598
- orgName: org.name,
599
- spaceName: space.name,
600
- appName: app.name
601
- });
602
- }
603
- }
604
- }
605
- }
606
- }
607
- if (matches.length === 0) {
608
- throw new Error(
609
- `App "${appName}" was not found in the CF topology snapshot. Run \`cf-sync sync\` first, or pass a full region/org/space/app selector.`
610
- );
611
- }
612
- if (matches.length > 1) {
613
- const candidates = matches.map((match) => ` ${match.regionKey}/${match.orgName}/${match.spaceName}/${match.appName}`).join("\n");
614
- throw new Error(
615
- `App "${appName}" is ambiguous - it exists in multiple spaces:
616
- ${candidates}
617
- Pass a full region/org/space/app selector to disambiguate.`
618
- );
619
- }
620
- const onlyMatch = matches[0];
621
- if (onlyMatch === void 0) {
622
- throw new Error(`App "${appName}" could not be resolved.`);
623
- }
624
- return onlyMatch;
625
- }
626
- async function resolveSelector(raw) {
627
- const parsed = parseSelector(raw);
628
- const structure = await readStructure();
629
- if (structure === void 0) {
630
- throw new Error(
631
- "No CF topology snapshot found. Run `cf-sync sync` (or `cf-sync space ...`) first."
632
- );
633
- }
634
- if (parsed.kind === "explicit") {
635
- return resolveExplicit(raw, parsed, structure);
636
- }
637
- return resolveByAppName(raw, parsed.appName, structure);
638
- }
639
685
 
640
686
  // src/ssh.ts
641
687
  var SSH_AUTHORIZED_TYPE = "audit.app.ssh-authorized";
@@ -847,26 +893,8 @@ function parsePositiveInteger(value, optionName) {
847
893
  function appLabel(selector) {
848
894
  return (selector.split("/").at(-1) ?? selector).trim();
849
895
  }
850
- async function resolveSelectorArgument(selector) {
851
- if (selector.includes("/")) {
852
- return selector;
853
- }
854
- const current = await readCurrentCfTarget(currentCfContext()).catch((error) => {
855
- throw new Error(
856
- "No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector.",
857
- { cause: error }
858
- );
859
- });
860
- if (current === void 0) {
861
- throw new Error(
862
- "No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector."
863
- );
864
- }
865
- return formatCurrentCfAppSelector(current, selector);
866
- }
867
- function currentCfContext() {
868
- const command = process2.env["CF_EVENTS_CF_BIN"];
869
- return command === void 0 ? void 0 : { command };
896
+ function resolveSelectorArgument(selector) {
897
+ return selector;
870
898
  }
871
899
  function writeOut(text) {
872
900
  process2.stdout.write(text.endsWith("\n") ? text : `${text}
@@ -881,7 +909,7 @@ function writeJsonLine(value) {
881
909
  `);
882
910
  }
883
911
  async function runEvents(selector, flags) {
884
- const resolvedSelector = await resolveSelectorArgument(selector);
912
+ const resolvedSelector = resolveSelectorArgument(selector);
885
913
  const runtime = new CfEventsRuntime();
886
914
  const events = await runtime.fetchEvents(resolvedSelector, buildCredentials(flags), {
887
915
  limit: flags.limit ?? DEFAULT_EVENT_LIMIT,
@@ -895,7 +923,7 @@ async function runEvents(selector, flags) {
895
923
  writeOut(formatEventsReport(appLabel(resolvedSelector), events, /* @__PURE__ */ new Date()));
896
924
  }
897
925
  async function runSshStatus(selector, flags) {
898
- const resolvedSelector = await resolveSelectorArgument(selector);
926
+ const resolvedSelector = resolveSelectorArgument(selector);
899
927
  const runtime = new CfEventsRuntime();
900
928
  const status = await runtime.getSshStatus(
901
929
  resolvedSelector,
@@ -909,7 +937,7 @@ async function runSshStatus(selector, flags) {
909
937
  writeOut(formatSshStatusReport(status, /* @__PURE__ */ new Date()));
910
938
  }
911
939
  async function runCrashes(selector, flags) {
912
- const resolvedSelector = await resolveSelectorArgument(selector);
940
+ const resolvedSelector = resolveSelectorArgument(selector);
913
941
  const runtime = new CfEventsRuntime();
914
942
  const summary = await runtime.getCrashes(resolvedSelector, buildCredentials(flags), {
915
943
  limit: flags.limit ?? DEFAULT_CRASH_LIMIT,
@@ -922,7 +950,7 @@ async function runCrashes(selector, flags) {
922
950
  writeOut(formatCrashReport(summary, /* @__PURE__ */ new Date()));
923
951
  }
924
952
  async function runStatus(selector, flags) {
925
- const resolvedSelector = await resolveSelectorArgument(selector);
953
+ const resolvedSelector = resolveSelectorArgument(selector);
926
954
  const runtime = new CfEventsRuntime();
927
955
  const health = await runtime.getStatus(resolvedSelector, buildCredentials(flags));
928
956
  if (flags.json === true) {
@@ -939,7 +967,7 @@ function emitWatchEvent(event, asJson) {
939
967
  writeOut(formatEventLine(event, /* @__PURE__ */ new Date()));
940
968
  }
941
969
  async function runWatch(selector, flags) {
942
- const resolvedSelector = await resolveSelectorArgument(selector);
970
+ const resolvedSelector = resolveSelectorArgument(selector);
943
971
  const interval = flags.interval ?? DEFAULT_WATCH_INTERVAL_MS;
944
972
  if (interval < MIN_WATCH_INTERVAL_MS) {
945
973
  throw new Error(`--interval must be at least ${MIN_WATCH_INTERVAL_MS.toString()}ms.`);