@postman-cse/onboarding-insights 0.8.0 → 0.9.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 CHANGED
@@ -122,7 +122,7 @@ Output is JSON to stdout. Use `--result-json` to write the same payload to a fil
122
122
 
123
123
  ```yaml
124
124
  onboarding:
125
- image: node:20
125
+ image: node:24
126
126
  script:
127
127
  - npm install -g postman-insights-onboarding-action
128
128
  - postman-insights-onboard --project-name af-cards-activation --workspace-id "$WORKSPACE_ID" --environment-id "$ENVIRONMENT_ID" --postman-access-token "$POSTMAN_ACCESS_TOKEN" --postman-api-key "$POSTMAN_API_KEY" --cluster-name "$CLUSTER_NAME" --repo-url "$CI_PROJECT_URL" --poll-timeout-seconds 180 --result-json insights-result.json --dotenv-path insights.env
@@ -131,7 +131,7 @@ onboarding:
131
131
  ### Bitbucket Pipelines
132
132
 
133
133
  ```yaml
134
- image: node:20
134
+ image: node:24
135
135
 
136
136
  pipelines:
137
137
  default:
package/action.yml CHANGED
@@ -39,6 +39,10 @@ inputs:
39
39
  description: 'Seconds between discovery polling attempts'
40
40
  required: false
41
41
  default: '10'
42
+ postman-stack:
43
+ description: 'Postman stack profile.'
44
+ required: false
45
+ default: 'prod'
42
46
  outputs:
43
47
  discovered-service-id:
44
48
  description: 'Numeric ID from the API Catalog discovered-services list'
@@ -53,5 +57,5 @@ outputs:
53
57
  status:
54
58
  description: 'Onboarding result: success, not-found, or error'
55
59
  runs:
56
- using: 'node20'
60
+ using: 'node24'
57
61
  main: 'dist/index.cjs'
package/dist/cli.cjs CHANGED
@@ -27347,9 +27347,6 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
27347
27347
  return sanitized.split(secret).join(replacement);
27348
27348
  }, source);
27349
27349
  }
27350
- function createSecretMasker(secretValues, replacement = REDACTED) {
27351
- return (input) => redactSecrets(input, secretValues, replacement);
27352
- }
27353
27350
  function headerEntries(headers) {
27354
27351
  if (headers instanceof Headers) {
27355
27352
  return Array.from(headers.entries());
@@ -27476,20 +27473,58 @@ async function retry(operation, options = {}) {
27476
27473
  throw new Error("Retry exhausted without returning or throwing");
27477
27474
  }
27478
27475
 
27476
+ // src/lib/postman/base-urls.ts
27477
+ var POSTMAN_ENDPOINT_PROFILES = {
27478
+ prod: {
27479
+ apiBaseUrl: "https://api.getpostman.com",
27480
+ bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
27481
+ observabilityBaseUrl: "https://api.observability.postman.com",
27482
+ observabilityEnv: "production"
27483
+ },
27484
+ beta: {
27485
+ apiBaseUrl: "https://api.getpostman-beta.com",
27486
+ bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
27487
+ observabilityBaseUrl: "https://api.observability.postman-beta.com",
27488
+ observabilityEnv: "beta"
27489
+ }
27490
+ };
27491
+ function parsePostmanStack(value) {
27492
+ const normalized = String(value || "prod").trim().toLowerCase();
27493
+ if (normalized === "prod" || normalized === "beta") {
27494
+ return normalized;
27495
+ }
27496
+ throw new Error(`Unsupported postman-stack "${value}". Supported values: prod, beta`);
27497
+ }
27498
+ function resolvePostmanEndpointProfile(stack) {
27499
+ return POSTMAN_ENDPOINT_PROFILES[stack];
27500
+ }
27501
+
27479
27502
  // src/lib/bifrost-client.ts
27480
- var BIFROST_BASE = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
27503
+ var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
27504
+ var BIFROST_PROXY_PATH = "/ws/proxy";
27505
+ var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilityBaseUrl;
27506
+ var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
27507
+ var MAX_DISCOVERED_SERVICE_PAGES = 100;
27508
+ var MAX_PROVIDER_SERVICE_PAGES = 100;
27481
27509
  var BifrostCatalogClient = class {
27482
27510
  accessToken;
27483
27511
  teamId;
27484
27512
  apiKey;
27485
27513
  fetchFn;
27486
27514
  secretValues;
27515
+ bifrostProxyUrl;
27516
+ observabilityBaseUrl;
27517
+ observabilityEnv;
27487
27518
  constructor(options) {
27488
27519
  this.accessToken = options.accessToken;
27489
27520
  this.teamId = options.teamId;
27490
27521
  this.apiKey = options.apiKey;
27491
27522
  this.fetchFn = options.fetchFn ?? globalThis.fetch;
27492
27523
  this.secretValues = [options.accessToken, options.apiKey].filter(Boolean);
27524
+ const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
27525
+ this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
27526
+ this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
27527
+ this.observabilityEnv = options.observabilityEnv || DEFAULT_OBSERVABILITY_ENV;
27493
27528
  }
27494
27529
  setApiKey(apiKey) {
27495
27530
  this.apiKey = apiKey;
@@ -27513,7 +27548,7 @@ var BifrostCatalogClient = class {
27513
27548
  return h;
27514
27549
  }
27515
27550
  async proxyRequest(method, path7, body = {}) {
27516
- const response = await this.fetchFn(BIFROST_BASE, {
27551
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27517
27552
  method: "POST",
27518
27553
  headers: this.headers(),
27519
27554
  body: JSON.stringify({
@@ -27538,7 +27573,7 @@ var BifrostCatalogClient = class {
27538
27573
  return data;
27539
27574
  }
27540
27575
  async akitaProxyRequest(method, path7, body = {}) {
27541
- const response = await this.fetchFn(BIFROST_BASE, {
27576
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27542
27577
  method: "POST",
27543
27578
  headers: this.headers(),
27544
27579
  body: JSON.stringify({
@@ -27560,16 +27595,23 @@ var BifrostCatalogClient = class {
27560
27595
  async () => {
27561
27596
  const allItems = [];
27562
27597
  let cursor = null;
27563
- let hasMore = true;
27564
- while (hasMore) {
27598
+ const seenCursors = /* @__PURE__ */ new Set();
27599
+ for (let page = 0; page < MAX_DISCOVERED_SERVICE_PAGES; page += 1) {
27565
27600
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
27566
27601
  const data = await this.proxyRequest(
27567
27602
  "GET",
27568
27603
  `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
27569
27604
  );
27570
27605
  allItems.push(...data.items || []);
27571
- cursor = data.nextCursor || null;
27572
- hasMore = cursor !== null;
27606
+ if (data.total && allItems.length >= data.total) {
27607
+ break;
27608
+ }
27609
+ const nextCursor = data.nextCursor || null;
27610
+ if (!nextCursor || seenCursors.has(nextCursor)) {
27611
+ break;
27612
+ }
27613
+ seenCursors.add(nextCursor);
27614
+ cursor = nextCursor;
27573
27615
  }
27574
27616
  return allItems;
27575
27617
  },
@@ -27616,8 +27658,7 @@ var BifrostCatalogClient = class {
27616
27658
  const allServices = [];
27617
27659
  let page = 1;
27618
27660
  const pageSize = 100;
27619
- let hasMore = true;
27620
- while (hasMore) {
27661
+ for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
27621
27662
  const result = await this.akitaProxyRequest(
27622
27663
  "GET",
27623
27664
  `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
@@ -27625,7 +27666,12 @@ var BifrostCatalogClient = class {
27625
27666
  if (!result.ok || !result.data) return null;
27626
27667
  const services = result.data.services || [];
27627
27668
  allServices.push(...services);
27628
- hasMore = services.length >= pageSize;
27669
+ if (result.data.total && allServices.length >= result.data.total) {
27670
+ break;
27671
+ }
27672
+ if (services.length < pageSize) {
27673
+ break;
27674
+ }
27629
27675
  page++;
27630
27676
  }
27631
27677
  const fullName = clusterName ? `${clusterName}/${projectName}` : projectName;
@@ -27662,12 +27708,12 @@ var BifrostCatalogClient = class {
27662
27708
  }
27663
27709
  async createApplication(workspaceId, systemEnv) {
27664
27710
  const response = await this.fetchFn(
27665
- `https://api.observability.postman.com/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
27711
+ `${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
27666
27712
  {
27667
27713
  method: "POST",
27668
27714
  headers: {
27669
27715
  "x-api-key": this.apiKey,
27670
- "x-postman-env": "production",
27716
+ "x-postman-env": this.observabilityEnv,
27671
27717
  "Content-Type": "application/json"
27672
27718
  },
27673
27719
  body: JSON.stringify({ system_env: systemEnv })
@@ -27691,7 +27737,7 @@ var BifrostCatalogClient = class {
27691
27737
  return result.data.team_verification_token || null;
27692
27738
  }
27693
27739
  async createApiKey(name) {
27694
- const response = await this.fetchFn(BIFROST_BASE, {
27740
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27695
27741
  method: "POST",
27696
27742
  headers: this.headers(),
27697
27743
  body: JSON.stringify({
@@ -27731,8 +27777,15 @@ var POLL_TIMEOUT_DEFAULT = 120;
27731
27777
  var POLL_INTERVAL_MIN = 2;
27732
27778
  var POLL_INTERVAL_MAX = 60;
27733
27779
  var POLL_INTERVAL_DEFAULT = 10;
27734
- async function validateApiKey(apiKey) {
27735
- const res = await fetch("https://api.getpostman.com/me", {
27780
+ var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
27781
+ var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
27782
+ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
27783
+ var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
27784
+ function trimTrailingSlash(value) {
27785
+ return value.replace(/\/+$/, "");
27786
+ }
27787
+ async function validateApiKey(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
27788
+ const res = await fetch(`${trimTrailingSlash(apiBase)}/me`, {
27736
27789
  method: "GET",
27737
27790
  headers: { "x-api-key": apiKey }
27738
27791
  });
@@ -27746,9 +27799,9 @@ async function validateApiKey(apiKey) {
27746
27799
  const teamId = data?.user?.teamId ? String(data.user.teamId) : void 0;
27747
27800
  return { valid: true, teamId };
27748
27801
  }
27749
- async function getTeams(apiKey) {
27802
+ async function getTeams(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
27750
27803
  try {
27751
- const res = await fetch("https://api.getpostman.com/teams", {
27804
+ const res = await fetch(`${trimTrailingSlash(apiBase)}/teams`, {
27752
27805
  method: "GET",
27753
27806
  headers: { "x-api-key": apiKey }
27754
27807
  });
@@ -27787,11 +27840,12 @@ function resolveInputs(env = process.env) {
27787
27840
  "environment-id is required. Provide it as an input, or set the POSTMAN_ENVIRONMENT_ID environment variable."
27788
27841
  );
27789
27842
  }
27790
- const repoSlug = env.GITHUB_REPOSITORY || env.CI_PROJECT_PATH || (env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG ? `${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}` : "") || env.BUILD_REPOSITORY_NAME || "";
27791
27843
  const detectedRepoUrl = (env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : "") || env.CI_PROJECT_URL || env.BITBUCKET_GIT_HTTP_ORIGIN || env.BUILD_REPOSITORY_URI || "";
27792
27844
  const repoUrl = get("repo-url", detectedRepoUrl);
27793
27845
  const rawTimeout = parseInt(get("poll-timeout-seconds", String(POLL_TIMEOUT_DEFAULT)), 10);
27794
27846
  const rawInterval = parseInt(get("poll-interval-seconds", String(POLL_INTERVAL_DEFAULT)), 10);
27847
+ const postmanStack = parsePostmanStack(get("postman-stack"));
27848
+ const endpointProfile = resolvePostmanEndpointProfile(postmanStack);
27795
27849
  return {
27796
27850
  projectName,
27797
27851
  workspaceId,
@@ -27804,7 +27858,12 @@ function resolveInputs(env = process.env) {
27804
27858
  postmanTeamId,
27805
27859
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
27806
27860
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
27807
- pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT)
27861
+ pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
27862
+ postmanStack,
27863
+ postmanApiBase: endpointProfile.apiBaseUrl,
27864
+ postmanBifrostBase: endpointProfile.bifrostBaseUrl,
27865
+ postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
27866
+ postmanObservabilityEnv: endpointProfile.observabilityEnv
27808
27867
  };
27809
27868
  }
27810
27869
  function createPlannedOutputs(inputs) {
@@ -27908,15 +27967,12 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27908
27967
  let apiKey = inputs.postmanApiKey;
27909
27968
  const teamId = inputs.postmanTeamId;
27910
27969
  let keyValid = false;
27970
+ const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
27911
27971
  if (apiKey) {
27912
- try {
27913
- const result = await validateApiKey(apiKey);
27914
- keyValid = result.valid;
27915
- if (!keyValid) {
27916
- reporter.warning("Provided postman-api-key is invalid or expired.");
27917
- }
27918
- } catch (error2) {
27919
- throw error2;
27972
+ const result = await validateApiKey(apiKey, apiBase);
27973
+ keyValid = result.valid;
27974
+ if (!keyValid) {
27975
+ reporter.warning("Provided postman-api-key is invalid or expired.");
27920
27976
  }
27921
27977
  }
27922
27978
  if (!keyValid) {
@@ -27930,18 +27986,29 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27930
27986
  let resolvedTeamId = teamId;
27931
27987
  if (!resolvedTeamId && apiKey) {
27932
27988
  try {
27933
- const teams = await getTeams(apiKey);
27989
+ const teams = await getTeams(apiKey, apiBase);
27934
27990
  if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
27935
27991
  reporter.warning(
27936
27992
  "GET /teams returned multiple teams but none include organizationId. Org-mode auto-detection may be degraded due to an upstream API change. Set postman-team-id explicitly if Bifrost calls fail."
27937
27993
  );
27938
27994
  }
27939
- const orgIds = new Set(teams.filter((t) => t.organizationId != null).map((t) => t.organizationId));
27940
- const meResult = await validateApiKey(apiKey);
27941
- const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
27942
- if (teams.length > 1 && orgIds.size === 1 && !Number.isNaN(meTeamId) && orgIds.has(meTeamId)) {
27943
- resolvedTeamId = String(meTeamId);
27944
- reporter.info(`Org-mode auto-detected (${teams.length} sub-teams). Using team ID ${resolvedTeamId} for Bifrost headers.`);
27995
+ const isOrgMode = teams.some((t) => t.organizationId != null);
27996
+ if (isOrgMode) {
27997
+ if (teams.length === 1) {
27998
+ resolvedTeamId = String(teams[0].id);
27999
+ reporter.info(
28000
+ `Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
28001
+ );
28002
+ } else {
28003
+ const meResult = await validateApiKey(apiKey, apiBase);
28004
+ const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
28005
+ if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
28006
+ resolvedTeamId = String(meTeamId);
28007
+ reporter.info(
28008
+ `Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
28009
+ );
28010
+ }
28011
+ }
27945
28012
  }
27946
28013
  } catch {
27947
28014
  }
@@ -27959,11 +28026,6 @@ async function runAction() {
27959
28026
  for (const [key, value] of Object.entries(planned)) {
27960
28027
  setOutput(key, value);
27961
28028
  }
27962
- const maskSecret = createSecretMasker([
27963
- inputs.postmanAccessToken,
27964
- inputs.postmanApiKey,
27965
- inputs.githubToken
27966
- ].filter(Boolean));
27967
28029
  setSecret(inputs.postmanAccessToken);
27968
28030
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
27969
28031
  if (inputs.githubToken) setSecret(inputs.githubToken);
@@ -27971,14 +28033,18 @@ async function runAction() {
27971
28033
  accessToken: inputs.postmanAccessToken,
27972
28034
  teamId: inputs.postmanTeamId,
27973
28035
  apiKey: inputs.postmanApiKey,
27974
- maskSecret
28036
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28037
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28038
+ observabilityEnv: inputs.postmanObservabilityEnv
27975
28039
  });
27976
28040
  const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
27977
28041
  const client = new BifrostCatalogClient({
27978
28042
  accessToken: inputs.postmanAccessToken,
27979
28043
  teamId,
27980
28044
  apiKey,
27981
- maskSecret
28045
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28046
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28047
+ observabilityEnv: inputs.postmanObservabilityEnv
27982
28048
  });
27983
28049
  let result;
27984
28050
  try {
@@ -28059,7 +28125,8 @@ function parseCliArgs(argv, env = process.env) {
28059
28125
  "postman-team-id",
28060
28126
  "github-token",
28061
28127
  "poll-timeout-seconds",
28062
- "poll-interval-seconds"
28128
+ "poll-interval-seconds",
28129
+ "postman-stack"
28063
28130
  ];
28064
28131
  const inputEnv = { ...env };
28065
28132
  for (const name of inputNames) {
@@ -28115,31 +28182,25 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
28115
28182
  if (inputs.githubToken) {
28116
28183
  reporter.setSecret(inputs.githubToken);
28117
28184
  }
28118
- const preliminaryMaskSecret = createSecretMasker([
28119
- inputs.postmanAccessToken,
28120
- inputs.postmanApiKey,
28121
- inputs.githubToken
28122
- ]);
28123
28185
  const preliminaryClient = new BifrostCatalogClient({
28124
28186
  accessToken: inputs.postmanAccessToken,
28125
28187
  teamId: inputs.postmanTeamId,
28126
28188
  apiKey: inputs.postmanApiKey,
28127
- maskSecret: preliminaryMaskSecret
28189
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28190
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28191
+ observabilityEnv: inputs.postmanObservabilityEnv
28128
28192
  });
28129
28193
  const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, reporter);
28130
28194
  if (apiKey) {
28131
28195
  reporter.setSecret(apiKey);
28132
28196
  }
28133
- const maskSecret = createSecretMasker([
28134
- inputs.postmanAccessToken,
28135
- inputs.githubToken,
28136
- apiKey
28137
- ]);
28138
28197
  const client = new BifrostCatalogClient({
28139
28198
  accessToken: inputs.postmanAccessToken,
28140
28199
  teamId,
28141
28200
  apiKey,
28142
- maskSecret
28201
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28202
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28203
+ observabilityEnv: inputs.postmanObservabilityEnv
28143
28204
  });
28144
28205
  const result = await (runtime.executeOnboarding ?? runOnboarding)(
28145
28206
  inputs,
package/dist/index.cjs CHANGED
@@ -25012,9 +25012,10 @@ ${captureLines}` : capture.stack;
25012
25012
  // src/index.ts
25013
25013
  var index_exports = {};
25014
25014
  __export(index_exports, {
25015
+ DEFAULT_POSTMAN_API_BASE: () => DEFAULT_POSTMAN_API_BASE,
25016
+ DEFAULT_POSTMAN_BIFROST_BASE: () => DEFAULT_POSTMAN_BIFROST_BASE,
25017
+ DEFAULT_POSTMAN_OBSERVABILITY_BASE: () => DEFAULT_POSTMAN_OBSERVABILITY_BASE,
25015
25018
  createPlannedOutputs: () => createPlannedOutputs,
25016
- deriveTeamId: () => deriveTeamId,
25017
- deriveTeamIdFromSession: () => deriveTeamIdFromSession,
25018
25019
  getTeams: () => getTeams,
25019
25020
  resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
25020
25021
  resolveInputs: () => resolveInputs,
@@ -27348,9 +27349,6 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
27348
27349
  return sanitized.split(secret).join(replacement);
27349
27350
  }, source);
27350
27351
  }
27351
- function createSecretMasker(secretValues, replacement = REDACTED) {
27352
- return (input) => redactSecrets(input, secretValues, replacement);
27353
- }
27354
27352
  function headerEntries(headers) {
27355
27353
  if (headers instanceof Headers) {
27356
27354
  return Array.from(headers.entries());
@@ -27477,20 +27475,58 @@ async function retry(operation, options = {}) {
27477
27475
  throw new Error("Retry exhausted without returning or throwing");
27478
27476
  }
27479
27477
 
27478
+ // src/lib/postman/base-urls.ts
27479
+ var POSTMAN_ENDPOINT_PROFILES = {
27480
+ prod: {
27481
+ apiBaseUrl: "https://api.getpostman.com",
27482
+ bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
27483
+ observabilityBaseUrl: "https://api.observability.postman.com",
27484
+ observabilityEnv: "production"
27485
+ },
27486
+ beta: {
27487
+ apiBaseUrl: "https://api.getpostman-beta.com",
27488
+ bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
27489
+ observabilityBaseUrl: "https://api.observability.postman-beta.com",
27490
+ observabilityEnv: "beta"
27491
+ }
27492
+ };
27493
+ function parsePostmanStack(value) {
27494
+ const normalized = String(value || "prod").trim().toLowerCase();
27495
+ if (normalized === "prod" || normalized === "beta") {
27496
+ return normalized;
27497
+ }
27498
+ throw new Error(`Unsupported postman-stack "${value}". Supported values: prod, beta`);
27499
+ }
27500
+ function resolvePostmanEndpointProfile(stack) {
27501
+ return POSTMAN_ENDPOINT_PROFILES[stack];
27502
+ }
27503
+
27480
27504
  // src/lib/bifrost-client.ts
27481
- var BIFROST_BASE = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
27505
+ var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
27506
+ var BIFROST_PROXY_PATH = "/ws/proxy";
27507
+ var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilityBaseUrl;
27508
+ var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
27509
+ var MAX_DISCOVERED_SERVICE_PAGES = 100;
27510
+ var MAX_PROVIDER_SERVICE_PAGES = 100;
27482
27511
  var BifrostCatalogClient = class {
27483
27512
  accessToken;
27484
27513
  teamId;
27485
27514
  apiKey;
27486
27515
  fetchFn;
27487
27516
  secretValues;
27517
+ bifrostProxyUrl;
27518
+ observabilityBaseUrl;
27519
+ observabilityEnv;
27488
27520
  constructor(options) {
27489
27521
  this.accessToken = options.accessToken;
27490
27522
  this.teamId = options.teamId;
27491
27523
  this.apiKey = options.apiKey;
27492
27524
  this.fetchFn = options.fetchFn ?? globalThis.fetch;
27493
27525
  this.secretValues = [options.accessToken, options.apiKey].filter(Boolean);
27526
+ const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
27527
+ this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
27528
+ this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
27529
+ this.observabilityEnv = options.observabilityEnv || DEFAULT_OBSERVABILITY_ENV;
27494
27530
  }
27495
27531
  setApiKey(apiKey) {
27496
27532
  this.apiKey = apiKey;
@@ -27514,7 +27550,7 @@ var BifrostCatalogClient = class {
27514
27550
  return h;
27515
27551
  }
27516
27552
  async proxyRequest(method, path6, body = {}) {
27517
- const response = await this.fetchFn(BIFROST_BASE, {
27553
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27518
27554
  method: "POST",
27519
27555
  headers: this.headers(),
27520
27556
  body: JSON.stringify({
@@ -27539,7 +27575,7 @@ var BifrostCatalogClient = class {
27539
27575
  return data;
27540
27576
  }
27541
27577
  async akitaProxyRequest(method, path6, body = {}) {
27542
- const response = await this.fetchFn(BIFROST_BASE, {
27578
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27543
27579
  method: "POST",
27544
27580
  headers: this.headers(),
27545
27581
  body: JSON.stringify({
@@ -27561,16 +27597,23 @@ var BifrostCatalogClient = class {
27561
27597
  async () => {
27562
27598
  const allItems = [];
27563
27599
  let cursor = null;
27564
- let hasMore = true;
27565
- while (hasMore) {
27600
+ const seenCursors = /* @__PURE__ */ new Set();
27601
+ for (let page = 0; page < MAX_DISCOVERED_SERVICE_PAGES; page += 1) {
27566
27602
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
27567
27603
  const data = await this.proxyRequest(
27568
27604
  "GET",
27569
27605
  `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
27570
27606
  );
27571
27607
  allItems.push(...data.items || []);
27572
- cursor = data.nextCursor || null;
27573
- hasMore = cursor !== null;
27608
+ if (data.total && allItems.length >= data.total) {
27609
+ break;
27610
+ }
27611
+ const nextCursor = data.nextCursor || null;
27612
+ if (!nextCursor || seenCursors.has(nextCursor)) {
27613
+ break;
27614
+ }
27615
+ seenCursors.add(nextCursor);
27616
+ cursor = nextCursor;
27574
27617
  }
27575
27618
  return allItems;
27576
27619
  },
@@ -27617,8 +27660,7 @@ var BifrostCatalogClient = class {
27617
27660
  const allServices = [];
27618
27661
  let page = 1;
27619
27662
  const pageSize = 100;
27620
- let hasMore = true;
27621
- while (hasMore) {
27663
+ for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
27622
27664
  const result = await this.akitaProxyRequest(
27623
27665
  "GET",
27624
27666
  `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
@@ -27626,7 +27668,12 @@ var BifrostCatalogClient = class {
27626
27668
  if (!result.ok || !result.data) return null;
27627
27669
  const services = result.data.services || [];
27628
27670
  allServices.push(...services);
27629
- hasMore = services.length >= pageSize;
27671
+ if (result.data.total && allServices.length >= result.data.total) {
27672
+ break;
27673
+ }
27674
+ if (services.length < pageSize) {
27675
+ break;
27676
+ }
27630
27677
  page++;
27631
27678
  }
27632
27679
  const fullName = clusterName ? `${clusterName}/${projectName}` : projectName;
@@ -27663,12 +27710,12 @@ var BifrostCatalogClient = class {
27663
27710
  }
27664
27711
  async createApplication(workspaceId, systemEnv) {
27665
27712
  const response = await this.fetchFn(
27666
- `https://api.observability.postman.com/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
27713
+ `${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
27667
27714
  {
27668
27715
  method: "POST",
27669
27716
  headers: {
27670
27717
  "x-api-key": this.apiKey,
27671
- "x-postman-env": "production",
27718
+ "x-postman-env": this.observabilityEnv,
27672
27719
  "Content-Type": "application/json"
27673
27720
  },
27674
27721
  body: JSON.stringify({ system_env: systemEnv })
@@ -27692,7 +27739,7 @@ var BifrostCatalogClient = class {
27692
27739
  return result.data.team_verification_token || null;
27693
27740
  }
27694
27741
  async createApiKey(name) {
27695
- const response = await this.fetchFn(BIFROST_BASE, {
27742
+ const response = await this.fetchFn(this.bifrostProxyUrl, {
27696
27743
  method: "POST",
27697
27744
  headers: this.headers(),
27698
27745
  body: JSON.stringify({
@@ -27732,21 +27779,15 @@ var POLL_TIMEOUT_DEFAULT = 120;
27732
27779
  var POLL_INTERVAL_MIN = 2;
27733
27780
  var POLL_INTERVAL_MAX = 60;
27734
27781
  var POLL_INTERVAL_DEFAULT = 10;
27735
- async function deriveTeamId(apiKey) {
27736
- try {
27737
- const res = await fetch("https://api.getpostman.com/me", {
27738
- method: "GET",
27739
- headers: { "x-api-key": apiKey }
27740
- });
27741
- if (!res.ok) return void 0;
27742
- const data = await res.json();
27743
- if (data?.user?.teamId) return String(data.user.teamId);
27744
- } catch {
27745
- }
27746
- return void 0;
27782
+ var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
27783
+ var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
27784
+ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
27785
+ var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
27786
+ function trimTrailingSlash(value) {
27787
+ return value.replace(/\/+$/, "");
27747
27788
  }
27748
- async function validateApiKey(apiKey) {
27749
- const res = await fetch("https://api.getpostman.com/me", {
27789
+ async function validateApiKey(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
27790
+ const res = await fetch(`${trimTrailingSlash(apiBase)}/me`, {
27750
27791
  method: "GET",
27751
27792
  headers: { "x-api-key": apiKey }
27752
27793
  });
@@ -27760,22 +27801,9 @@ async function validateApiKey(apiKey) {
27760
27801
  const teamId = data?.user?.teamId ? String(data.user.teamId) : void 0;
27761
27802
  return { valid: true, teamId };
27762
27803
  }
27763
- async function deriveTeamIdFromSession(accessToken) {
27764
- try {
27765
- const res = await fetch("https://iapub.postman.co/api/sessions/current", {
27766
- method: "GET",
27767
- headers: { "x-access-token": accessToken }
27768
- });
27769
- if (!res.ok) return void 0;
27770
- const data = await res.json();
27771
- if (data?.session?.identity?.team) return String(data.session.identity.team);
27772
- } catch {
27773
- }
27774
- return void 0;
27775
- }
27776
- async function getTeams(apiKey) {
27804
+ async function getTeams(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
27777
27805
  try {
27778
- const res = await fetch("https://api.getpostman.com/teams", {
27806
+ const res = await fetch(`${trimTrailingSlash(apiBase)}/teams`, {
27779
27807
  method: "GET",
27780
27808
  headers: { "x-api-key": apiKey }
27781
27809
  });
@@ -27814,11 +27842,12 @@ function resolveInputs(env = process.env) {
27814
27842
  "environment-id is required. Provide it as an input, or set the POSTMAN_ENVIRONMENT_ID environment variable."
27815
27843
  );
27816
27844
  }
27817
- const repoSlug = env.GITHUB_REPOSITORY || env.CI_PROJECT_PATH || (env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG ? `${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}` : "") || env.BUILD_REPOSITORY_NAME || "";
27818
27845
  const detectedRepoUrl = (env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : "") || env.CI_PROJECT_URL || env.BITBUCKET_GIT_HTTP_ORIGIN || env.BUILD_REPOSITORY_URI || "";
27819
27846
  const repoUrl = get("repo-url", detectedRepoUrl);
27820
27847
  const rawTimeout = parseInt(get("poll-timeout-seconds", String(POLL_TIMEOUT_DEFAULT)), 10);
27821
27848
  const rawInterval = parseInt(get("poll-interval-seconds", String(POLL_INTERVAL_DEFAULT)), 10);
27849
+ const postmanStack = parsePostmanStack(get("postman-stack"));
27850
+ const endpointProfile = resolvePostmanEndpointProfile(postmanStack);
27822
27851
  return {
27823
27852
  projectName,
27824
27853
  workspaceId,
@@ -27831,7 +27860,12 @@ function resolveInputs(env = process.env) {
27831
27860
  postmanTeamId,
27832
27861
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
27833
27862
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
27834
- pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT)
27863
+ pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
27864
+ postmanStack,
27865
+ postmanApiBase: endpointProfile.apiBaseUrl,
27866
+ postmanBifrostBase: endpointProfile.bifrostBaseUrl,
27867
+ postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
27868
+ postmanObservabilityEnv: endpointProfile.observabilityEnv
27835
27869
  };
27836
27870
  }
27837
27871
  function createPlannedOutputs(inputs) {
@@ -27935,15 +27969,12 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27935
27969
  let apiKey = inputs.postmanApiKey;
27936
27970
  const teamId = inputs.postmanTeamId;
27937
27971
  let keyValid = false;
27972
+ const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
27938
27973
  if (apiKey) {
27939
- try {
27940
- const result = await validateApiKey(apiKey);
27941
- keyValid = result.valid;
27942
- if (!keyValid) {
27943
- reporter.warning("Provided postman-api-key is invalid or expired.");
27944
- }
27945
- } catch (error2) {
27946
- throw error2;
27974
+ const result = await validateApiKey(apiKey, apiBase);
27975
+ keyValid = result.valid;
27976
+ if (!keyValid) {
27977
+ reporter.warning("Provided postman-api-key is invalid or expired.");
27947
27978
  }
27948
27979
  }
27949
27980
  if (!keyValid) {
@@ -27957,18 +27988,29 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27957
27988
  let resolvedTeamId = teamId;
27958
27989
  if (!resolvedTeamId && apiKey) {
27959
27990
  try {
27960
- const teams = await getTeams(apiKey);
27991
+ const teams = await getTeams(apiKey, apiBase);
27961
27992
  if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
27962
27993
  reporter.warning(
27963
27994
  "GET /teams returned multiple teams but none include organizationId. Org-mode auto-detection may be degraded due to an upstream API change. Set postman-team-id explicitly if Bifrost calls fail."
27964
27995
  );
27965
27996
  }
27966
- const orgIds = new Set(teams.filter((t) => t.organizationId != null).map((t) => t.organizationId));
27967
- const meResult = await validateApiKey(apiKey);
27968
- const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
27969
- if (teams.length > 1 && orgIds.size === 1 && !Number.isNaN(meTeamId) && orgIds.has(meTeamId)) {
27970
- resolvedTeamId = String(meTeamId);
27971
- reporter.info(`Org-mode auto-detected (${teams.length} sub-teams). Using team ID ${resolvedTeamId} for Bifrost headers.`);
27997
+ const isOrgMode = teams.some((t) => t.organizationId != null);
27998
+ if (isOrgMode) {
27999
+ if (teams.length === 1) {
28000
+ resolvedTeamId = String(teams[0].id);
28001
+ reporter.info(
28002
+ `Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
28003
+ );
28004
+ } else {
28005
+ const meResult = await validateApiKey(apiKey, apiBase);
28006
+ const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
28007
+ if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
28008
+ resolvedTeamId = String(meTeamId);
28009
+ reporter.info(
28010
+ `Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
28011
+ );
28012
+ }
28013
+ }
27972
28014
  }
27973
28015
  } catch {
27974
28016
  }
@@ -27986,11 +28028,6 @@ async function runAction() {
27986
28028
  for (const [key, value] of Object.entries(planned)) {
27987
28029
  setOutput(key, value);
27988
28030
  }
27989
- const maskSecret = createSecretMasker([
27990
- inputs.postmanAccessToken,
27991
- inputs.postmanApiKey,
27992
- inputs.githubToken
27993
- ].filter(Boolean));
27994
28031
  setSecret(inputs.postmanAccessToken);
27995
28032
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
27996
28033
  if (inputs.githubToken) setSecret(inputs.githubToken);
@@ -27998,14 +28035,18 @@ async function runAction() {
27998
28035
  accessToken: inputs.postmanAccessToken,
27999
28036
  teamId: inputs.postmanTeamId,
28000
28037
  apiKey: inputs.postmanApiKey,
28001
- maskSecret
28038
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28039
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28040
+ observabilityEnv: inputs.postmanObservabilityEnv
28002
28041
  });
28003
28042
  const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
28004
28043
  const client = new BifrostCatalogClient({
28005
28044
  accessToken: inputs.postmanAccessToken,
28006
28045
  teamId,
28007
28046
  apiKey,
28008
- maskSecret
28047
+ bifrostBaseUrl: inputs.postmanBifrostBase,
28048
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
28049
+ observabilityEnv: inputs.postmanObservabilityEnv
28009
28050
  });
28010
28051
  let result;
28011
28052
  try {
@@ -28036,9 +28077,10 @@ runAction().catch((error2) => {
28036
28077
  });
28037
28078
  // Annotate the CommonJS export names for ESM import in node:
28038
28079
  0 && (module.exports = {
28080
+ DEFAULT_POSTMAN_API_BASE,
28081
+ DEFAULT_POSTMAN_BIFROST_BASE,
28082
+ DEFAULT_POSTMAN_OBSERVABILITY_BASE,
28039
28083
  createPlannedOutputs,
28040
- deriveTeamId,
28041
- deriveTeamIdFromSession,
28042
28084
  getTeams,
28043
28085
  resolveApiKeyAndTeamId,
28044
28086
  resolveInputs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-insights",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "GitHub Action to onboard Postman Insights discovered services to API Catalog workspaces.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -11,9 +11,14 @@
11
11
  "action.yml",
12
12
  "dist"
13
13
  ],
14
+ "engines": {
15
+ "node": ">=24"
16
+ },
14
17
  "scripts": {
15
- "build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.cjs && esbuild src/cli.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/cli.cjs",
18
+ "build": "rm -rf dist && esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/index.cjs && esbuild src/cli.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/cli.cjs",
16
19
  "check:dist": "npm run build && git diff --exit-code -- dist",
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
17
22
  "test": "vitest run",
18
23
  "typecheck": "tsc --noEmit -p tsconfig.json"
19
24
  },
@@ -35,9 +40,14 @@
35
40
  "picomatch": ">=4.0.4"
36
41
  },
37
42
  "devDependencies": {
43
+ "@commitlint/cli": "^20.5.0",
44
+ "@commitlint/config-conventional": "^20.5.0",
45
+ "@eslint/js": "^10.0.1",
38
46
  "@types/node": "^25.3.5",
39
47
  "esbuild": "^0.27.3",
48
+ "eslint": "^10.1.0",
40
49
  "typescript": "^5.9.3",
50
+ "typescript-eslint": "^8.57.2",
41
51
  "vitest": "^4.0.18",
42
52
  "yaml": "^2.8.2"
43
53
  },