@postman-cse/onboarding-insights 0.7.2 → 0.8.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
@@ -166,8 +166,7 @@ steps:
166
166
  | `environment-id` | Yes | | Postman environment UID for the onboarding association. |
167
167
  | `system-environment-id` | No | | Postman system environment UUID for service-level Insights acknowledgment. Falls back to the value from the discovered service record. |
168
168
  | `cluster-name` | No | | Insights cluster name. When set, the action matches `{cluster-name}/{project-name}` exactly. When omitted, falls back to suffix matching. |
169
- | `git-owner` | No | `$GITHUB_REPOSITORY_OWNER` | GitHub organization or user for the repository URL. |
170
- | `git-repository-name` | No | `project-name` | GitHub repository name. Defaults to the project name. |
169
+ | `repo-url` | No | Auto-detected from CI when available | Repository URL used for Git onboarding. |
171
170
  | `postman-access-token` | Yes | | Postman session token for Bifrost API calls. See [Obtaining postman-access-token](#obtaining-postman-access-token-open-alpha). |
172
171
  | `postman-team-id` | No | | Explicit Postman team ID for org-mode Bifrost requests. When omitted, the action leaves `x-entity-team-id` unset so Bifrost resolves team context from the access token. |
173
172
  | `github-token` | No | ambient `GITHUB_TOKEN` env when exported by the workflow | Optional GitHub token passed as `git_api_key` only when repository auth is required by the onboarding endpoint. |
@@ -238,6 +237,17 @@ The action calls the following API endpoints in order:
238
237
  7. **Workspace acknowledge** -- `POST /v2/workspaces/{id}/onboarding/acknowledge` (Bifrost akita) to activate the Insights project.
239
238
  8. **Team verification token** -- `GET /v2/workspaces/{id}/team-verification-token` (Bifrost akita) to retrieve the DaemonSet telemetry token.
240
239
 
240
+ ## Contract smoke monitoring
241
+
242
+ This repo includes `.github/workflows/contract-smoke.yml`, a scheduled live contract check for the upstream APIs used by Insights onboarding.
243
+
244
+ Configure these repository secrets before enabling the workflow:
245
+
246
+ - `SMOKE_ORG_API_KEY`
247
+ - `SMOKE_ORG_ACCESS_TOKEN`
248
+
249
+ The smoke workflow verifies `/me`, `/teams`, `iapub.postman.co/api/sessions/current`, and Bifrost API key creation so auth or payload drift shows up in CI before it hits production onboarding runs.
250
+
241
251
  ## Local development
242
252
 
243
253
  ```bash
package/action.yml CHANGED
@@ -16,11 +16,8 @@ inputs:
16
16
  cluster-name:
17
17
  description: 'Insights cluster name. Matches {cluster}/{project-name} in discovered services'
18
18
  required: false
19
- git-owner:
20
- description: 'GitHub organization or user that owns the repository'
21
- required: false
22
- git-repository-name:
23
- description: 'GitHub repository name. Defaults to project-name'
19
+ repo-url:
20
+ description: 'Repository URL for Git onboarding. Auto-detected from CI context when omitted.'
24
21
  required: false
25
22
  postman-access-token:
26
23
  description: 'Postman access token for Bifrost API calls'
package/dist/cli.cjs CHANGED
@@ -27746,6 +27746,23 @@ async function validateApiKey(apiKey) {
27746
27746
  const teamId = data?.user?.teamId ? String(data.user.teamId) : void 0;
27747
27747
  return { valid: true, teamId };
27748
27748
  }
27749
+ async function getTeams(apiKey) {
27750
+ try {
27751
+ const res = await fetch("https://api.getpostman.com/teams", {
27752
+ method: "GET",
27753
+ headers: { "x-api-key": apiKey }
27754
+ });
27755
+ if (!res.ok) return [];
27756
+ const data = await res.json();
27757
+ return (data?.data ?? []).filter((t) => t?.id && t?.name).map((t) => ({
27758
+ id: Number(t.id),
27759
+ name: String(t.name),
27760
+ ...t.organizationId != null ? { organizationId: Number(t.organizationId) } : {}
27761
+ }));
27762
+ } catch {
27763
+ return [];
27764
+ }
27765
+ }
27749
27766
  function clamp(value, min, max, fallback) {
27750
27767
  const parsed = Number.isFinite(value) ? value : fallback;
27751
27768
  return Math.min(max, Math.max(min, parsed));
@@ -27758,16 +27775,21 @@ function resolveInputs(env = process.env) {
27758
27775
  if (!postmanAccessToken) throw new Error("postman-access-token is required");
27759
27776
  const postmanApiKey = get("postman-api-key");
27760
27777
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
27761
- const workspaceId = get("workspace-id");
27762
- if (!workspaceId) throw new Error("workspace-id is required");
27763
- const environmentId = get("environment-id");
27764
- if (!environmentId) throw new Error("environment-id is required");
27778
+ const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
27779
+ if (!workspaceId) {
27780
+ throw new Error(
27781
+ "workspace-id is required. Provide it as an input, or set the POSTMAN_WORKSPACE_ID environment variable."
27782
+ );
27783
+ }
27784
+ const environmentId = get("environment-id") || env.POSTMAN_ENVIRONMENT_ID?.trim() || "";
27785
+ if (!environmentId) {
27786
+ throw new Error(
27787
+ "environment-id is required. Provide it as an input, or set the POSTMAN_ENVIRONMENT_ID environment variable."
27788
+ );
27789
+ }
27765
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 || "";
27766
- const repoOwner = repoSlug.split("/")[0] || "";
27767
- const gitOwner = get("git-owner", repoOwner);
27768
- const gitRepositoryName = get("git-repository-name", projectName);
27769
27791
  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 || "";
27770
- const repoUrl = get("repo-url", detectedRepoUrl || `https://github.com/${gitOwner}/${gitRepositoryName}`);
27792
+ const repoUrl = get("repo-url", detectedRepoUrl);
27771
27793
  const rawTimeout = parseInt(get("poll-timeout-seconds", String(POLL_TIMEOUT_DEFAULT)), 10);
27772
27794
  const rawInterval = parseInt(get("poll-interval-seconds", String(POLL_INTERVAL_DEFAULT)), 10);
27773
27795
  return {
@@ -27776,8 +27798,6 @@ function resolveInputs(env = process.env) {
27776
27798
  environmentId,
27777
27799
  systemEnvironmentId: get("system-environment-id", ""),
27778
27800
  clusterName: get("cluster-name", ""),
27779
- gitOwner,
27780
- gitRepositoryName,
27781
27801
  repoUrl,
27782
27802
  postmanAccessToken,
27783
27803
  postmanApiKey,
@@ -27907,12 +27927,31 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27907
27927
  client.setApiKey(apiKey);
27908
27928
  reporter.info("New API key created successfully.");
27909
27929
  }
27910
- if (teamId) {
27911
- reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
27930
+ let resolvedTeamId = teamId;
27931
+ if (!resolvedTeamId && apiKey) {
27932
+ try {
27933
+ const teams = await getTeams(apiKey);
27934
+ if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
27935
+ reporter.warning(
27936
+ "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
+ );
27938
+ }
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.`);
27945
+ }
27946
+ } catch {
27947
+ }
27948
+ }
27949
+ if (resolvedTeamId) {
27950
+ reporter.info(`Using postman-team-id for Bifrost headers: ${resolvedTeamId}`);
27912
27951
  } else {
27913
- reporter.info("No explicit postman-team-id provided; omitting x-entity-team-id so Bifrost resolves team from the access token.");
27952
+ reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
27914
27953
  }
27915
- return { apiKey, teamId };
27954
+ return { apiKey, teamId: resolvedTeamId };
27916
27955
  }
27917
27956
  async function runAction() {
27918
27957
  const inputs = resolveInputs();
@@ -28014,8 +28053,6 @@ function parseCliArgs(argv, env = process.env) {
28014
28053
  "environment-id",
28015
28054
  "system-environment-id",
28016
28055
  "cluster-name",
28017
- "git-owner",
28018
- "git-repository-name",
28019
28056
  "repo-url",
28020
28057
  "postman-access-token",
28021
28058
  "postman-api-key",
package/dist/index.cjs CHANGED
@@ -25015,6 +25015,7 @@ __export(index_exports, {
25015
25015
  createPlannedOutputs: () => createPlannedOutputs,
25016
25016
  deriveTeamId: () => deriveTeamId,
25017
25017
  deriveTeamIdFromSession: () => deriveTeamIdFromSession,
25018
+ getTeams: () => getTeams,
25018
25019
  resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
25019
25020
  resolveInputs: () => resolveInputs,
25020
25021
  runOnboarding: () => runOnboarding,
@@ -27772,6 +27773,23 @@ async function deriveTeamIdFromSession(accessToken) {
27772
27773
  }
27773
27774
  return void 0;
27774
27775
  }
27776
+ async function getTeams(apiKey) {
27777
+ try {
27778
+ const res = await fetch("https://api.getpostman.com/teams", {
27779
+ method: "GET",
27780
+ headers: { "x-api-key": apiKey }
27781
+ });
27782
+ if (!res.ok) return [];
27783
+ const data = await res.json();
27784
+ return (data?.data ?? []).filter((t) => t?.id && t?.name).map((t) => ({
27785
+ id: Number(t.id),
27786
+ name: String(t.name),
27787
+ ...t.organizationId != null ? { organizationId: Number(t.organizationId) } : {}
27788
+ }));
27789
+ } catch {
27790
+ return [];
27791
+ }
27792
+ }
27775
27793
  function clamp(value, min, max, fallback) {
27776
27794
  const parsed = Number.isFinite(value) ? value : fallback;
27777
27795
  return Math.min(max, Math.max(min, parsed));
@@ -27784,16 +27802,21 @@ function resolveInputs(env = process.env) {
27784
27802
  if (!postmanAccessToken) throw new Error("postman-access-token is required");
27785
27803
  const postmanApiKey = get("postman-api-key");
27786
27804
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
27787
- const workspaceId = get("workspace-id");
27788
- if (!workspaceId) throw new Error("workspace-id is required");
27789
- const environmentId = get("environment-id");
27790
- if (!environmentId) throw new Error("environment-id is required");
27805
+ const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
27806
+ if (!workspaceId) {
27807
+ throw new Error(
27808
+ "workspace-id is required. Provide it as an input, or set the POSTMAN_WORKSPACE_ID environment variable."
27809
+ );
27810
+ }
27811
+ const environmentId = get("environment-id") || env.POSTMAN_ENVIRONMENT_ID?.trim() || "";
27812
+ if (!environmentId) {
27813
+ throw new Error(
27814
+ "environment-id is required. Provide it as an input, or set the POSTMAN_ENVIRONMENT_ID environment variable."
27815
+ );
27816
+ }
27791
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 || "";
27792
- const repoOwner = repoSlug.split("/")[0] || "";
27793
- const gitOwner = get("git-owner", repoOwner);
27794
- const gitRepositoryName = get("git-repository-name", projectName);
27795
27818
  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 || "";
27796
- const repoUrl = get("repo-url", detectedRepoUrl || `https://github.com/${gitOwner}/${gitRepositoryName}`);
27819
+ const repoUrl = get("repo-url", detectedRepoUrl);
27797
27820
  const rawTimeout = parseInt(get("poll-timeout-seconds", String(POLL_TIMEOUT_DEFAULT)), 10);
27798
27821
  const rawInterval = parseInt(get("poll-interval-seconds", String(POLL_INTERVAL_DEFAULT)), 10);
27799
27822
  return {
@@ -27802,8 +27825,6 @@ function resolveInputs(env = process.env) {
27802
27825
  environmentId,
27803
27826
  systemEnvironmentId: get("system-environment-id", ""),
27804
27827
  clusterName: get("cluster-name", ""),
27805
- gitOwner,
27806
- gitRepositoryName,
27807
27828
  repoUrl,
27808
27829
  postmanAccessToken,
27809
27830
  postmanApiKey,
@@ -27933,12 +27954,31 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
27933
27954
  client.setApiKey(apiKey);
27934
27955
  reporter.info("New API key created successfully.");
27935
27956
  }
27936
- if (teamId) {
27937
- reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
27957
+ let resolvedTeamId = teamId;
27958
+ if (!resolvedTeamId && apiKey) {
27959
+ try {
27960
+ const teams = await getTeams(apiKey);
27961
+ if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
27962
+ reporter.warning(
27963
+ "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
+ );
27965
+ }
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.`);
27972
+ }
27973
+ } catch {
27974
+ }
27975
+ }
27976
+ if (resolvedTeamId) {
27977
+ reporter.info(`Using postman-team-id for Bifrost headers: ${resolvedTeamId}`);
27938
27978
  } else {
27939
- reporter.info("No explicit postman-team-id provided; omitting x-entity-team-id so Bifrost resolves team from the access token.");
27979
+ reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
27940
27980
  }
27941
- return { apiKey, teamId };
27981
+ return { apiKey, teamId: resolvedTeamId };
27942
27982
  }
27943
27983
  async function runAction() {
27944
27984
  const inputs = resolveInputs();
@@ -27999,6 +28039,7 @@ runAction().catch((error2) => {
27999
28039
  createPlannedOutputs,
28000
28040
  deriveTeamId,
28001
28041
  deriveTeamIdFromSession,
28042
+ getTeams,
28002
28043
  resolveApiKeyAndTeamId,
28003
28044
  resolveInputs,
28004
28045
  runOnboarding,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-insights",
3
- "version": "0.7.2",
3
+ "version": "0.8.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",
@@ -31,7 +31,8 @@
31
31
  "@actions/core": "^3.0.0"
32
32
  },
33
33
  "overrides": {
34
- "undici": ">=6.24.0"
34
+ "undici": ">=6.24.0",
35
+ "picomatch": ">=4.0.4"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^25.3.5",