@postman-cse/onboarding-insights 2.0.0 → 2.1.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 +21 -76
- package/action.yml +2 -2
- package/dist/action.cjs +215 -40
- package/dist/cli.cjs +224 -49
- package/dist/index.cjs +215 -40
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
Links [Postman Insights](https://learning.postman.com/docs/insights/overview/) discovered services to [API Catalog](https://learning.postman.com/docs/api-catalog/overview/) workspaces and git repositories after deployment, so every service the Insights agent finds lands in your catalog with a collection, a repo link, and live telemetry.
|
|
6
6
|
|
|
7
|
-
Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman-api-onboarding-action).
|
|
7
|
+
Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman-api-onboarding-action); the composite action's README has the full [action-picker table](https://github.com/postman-cs/postman-api-onboarding-action#which-action-should-i-use).
|
|
8
|
+
|
|
9
|
+
- [Prerequisites](#prerequisites)
|
|
10
|
+
- [Usage](#usage)
|
|
11
|
+
- [Examples](#examples)
|
|
12
|
+
- [Inputs](#inputs) / [Outputs](#outputs)
|
|
13
|
+
- [How it works](#how-it-works)
|
|
8
14
|
|
|
9
15
|
## Prerequisites
|
|
10
16
|
|
|
@@ -16,19 +22,7 @@ Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman
|
|
|
16
22
|
|
|
17
23
|
This action does **not** deploy the Insights agent, create workspaces, create environments, upload OpenAPI specs, or sync repo artifacts. It only links a service that Insights has already discovered.
|
|
18
24
|
|
|
19
|
-
> **Credential requirement for the Insights linking steps.** The service discovery and git-link calls run on
|
|
20
|
-
|
|
21
|
-
## Which action should I use?
|
|
22
|
-
|
|
23
|
-
| Need | Action |
|
|
24
|
-
| --- | --- |
|
|
25
|
-
| Mint a service-account access token and team ID | [Postman Onboarding: Service Token](https://github.com/postman-cs/postman-resolve-service-token-action) |
|
|
26
|
-
| Run the full workspace, collection, repo sync, and optional Insights pipeline | [Postman API Onboarding](https://github.com/postman-cs/postman-api-onboarding-action) |
|
|
27
|
-
| Create a workspace, upload an OpenAPI spec, and generate collections | [Postman Onboarding: Workspace Bootstrap](https://github.com/postman-cs/postman-bootstrap-action) |
|
|
28
|
-
| Sync Postman artifacts, mocks, monitors, and CI files back to the repo | [Postman Onboarding: Repo Sync](https://github.com/postman-cs/postman-repo-sync-action) |
|
|
29
|
-
| Link an already discovered Insights service to an existing workspace | [Postman Onboarding: Insights Linking](https://github.com/postman-cs/postman-insights-onboarding-action) |
|
|
30
|
-
| Discover OpenAPI specs from AWS services | [Postman Onboarding: AWS Spec Discovery](https://github.com/postman-cs/postman-aws-spec-discovery-action) |
|
|
31
|
-
| Apply a curated smoke flow to an existing Smoke collection | [Postman Onboarding: Smoke Flow](https://github.com/postman-cs/postman-smoke-flow-action) |
|
|
25
|
+
> **Credential requirement for the Insights linking steps.** The service discovery and git-link calls run on the `api-catalog` service and accept a service-account access token. The Insights acknowledgment and application-binding steps run on the `akita` (Insights) service, which authenticates against a **Postman user identity** and answers `401 "Postman User not found"` for a service-account token. To complete the full link, supply a credential pair that carries a Postman user identity (a user's access token and user PMAK). A service-account-only pair from `postman-resolve-service-token-action` is sufficient for discovery and git linking but cannot finish the Insights acknowledgment.
|
|
32
26
|
|
|
33
27
|
## Usage
|
|
34
28
|
|
|
@@ -209,7 +203,7 @@ See [CLI Usage](docs/cli.md) for provider auto-detection, output formats, and Gi
|
|
|
209
203
|
| `system-environment-id` | Postman system environment UUID for service-level Insights acknowledgment | No | |
|
|
210
204
|
| `cluster-name` | Insights cluster name. When set, matches {cluster-name}/{project-name} exactly in discovered services | No | |
|
|
211
205
|
| `repo-url` | Repository URL for Git onboarding. Auto-detected from CI context when omitted. | No | |
|
|
212
|
-
| `postman-access-token` | Postman access token (x-access-token) for the Bifrost linking calls. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there. |
|
|
206
|
+
| `postman-access-token` | Postman access token (x-access-token) for the Bifrost linking calls. When omitted, the action mints a service-account token from postman-api-key. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there. | No | |
|
|
213
207
|
| `postman-team-id` | Explicit Postman team ID for org-mode integration request headers. When omitted, x-entity-team-id is not sent. | No | |
|
|
214
208
|
| `github-token` | Optional GitHub token passed as git_api_key when repository auth is required by onboarding/git | No | |
|
|
215
209
|
| `postman-api-key` | Service-account Postman API key (PMAK-*) for the application binding call. Auto-created from postman-access-token when omitted or invalid after a clear 401/403 validation failure. | No | |
|
|
@@ -238,11 +232,19 @@ Failures set `status=error` before the action exits.
|
|
|
238
232
|
|
|
239
233
|
## How it works
|
|
240
234
|
|
|
235
|
+
```mermaid
|
|
236
|
+
flowchart LR
|
|
237
|
+
D["Discovery poll<br/>discovered-services list"] --> P["Catalog prep<br/>prepare-collection"]
|
|
238
|
+
P --> G["Git link<br/>service to repository"]
|
|
239
|
+
G --> A["Acknowledgment<br/>onboard + workspace ack"]
|
|
240
|
+
A --> B["Binding<br/>application binding +<br/>team verification token"]
|
|
241
|
+
```
|
|
242
|
+
|
|
241
243
|
**Discovery poll.** The action polls the API Catalog discovered-services list at the configured interval until a service matching `{cluster-name}/{project-name}` appears (suffix matching when `cluster-name` is omitted) or the timeout is reached.
|
|
242
244
|
|
|
243
245
|
**Catalog prep.** It then calls `POST /api/v1/onboarding/prepare-collection` to create the API Catalog collection entry for the discovered service in your workspace.
|
|
244
246
|
|
|
245
|
-
**Git link.** `POST /api/v1/onboarding/git` with `via_integrations: false` links the service to the GitHub repository (`repo-url` input, auto-detected from CI context when omitted; `github-token` is passed as `git_api_key` only when the endpoint requires repository auth).
|
|
247
|
+
**Git link.** `POST /api/v1/onboarding/git` with `via_integrations: false` links the service to the GitHub repository (`repo-url` input, auto-detected from CI context when omitted; `github-token` is passed as `git_api_key` only when the endpoint requires repository auth). The [connect code](https://learning.postman.com/docs/api-catalog/connect/code/) docs cover the product workflow this binding supports.
|
|
246
248
|
|
|
247
249
|
**Acknowledgment.** The action resolves the `svc_*` Akita service ID, marks the service as managed (`POST /v2/api-catalog/services/onboard`), and acknowledges the workspace (`POST /v2/workspaces/{id}/onboarding/acknowledge`) to activate the Insights project.
|
|
248
250
|
|
|
@@ -252,71 +254,14 @@ For local builds, contract smoke monitoring, and release channels, see [Developm
|
|
|
252
254
|
|
|
253
255
|
## Resources
|
|
254
256
|
|
|
255
|
-
### The suite
|
|
256
|
-
|
|
257
|
-
| Action | Role |
|
|
258
|
-
| --- | --- |
|
|
259
|
-
| [Postman API Onboarding](https://github.com/postman-cs/postman-api-onboarding-action) | Entry point: chains workspace bootstrap, repo sync, and optional Insights linking |
|
|
260
|
-
| [Postman Onboarding: Service Token](https://github.com/postman-cs/postman-resolve-service-token-action) | Mints the service-account access token and team ID |
|
|
261
|
-
| [Postman Onboarding: AWS Spec Discovery](https://github.com/postman-cs/postman-aws-spec-discovery-action) | Discovers and exports API specs from AWS services |
|
|
262
|
-
| [Postman Onboarding: Workspace Bootstrap](https://github.com/postman-cs/postman-bootstrap-action) | Creates the workspace, uploads the spec, generates collections |
|
|
263
|
-
| [Postman Onboarding: Smoke Flow](https://github.com/postman-cs/postman-smoke-flow-action) | Applies a curated flow.yaml to the Smoke collection |
|
|
264
|
-
| [Postman Onboarding: Repo Sync](https://github.com/postman-cs/postman-repo-sync-action) | Exports artifacts into the repo and wires CI, mocks, and monitors |
|
|
265
|
-
| [Postman Onboarding: Insights Linking](https://github.com/postman-cs/postman-insights-onboarding-action) | Links Insights discovered services to the workspace |
|
|
266
|
-
|
|
267
|
-
- [postman-resolve-service-token-action](https://github.com/postman-cs/postman-resolve-service-token-action): mints a service-account access token and team ID.
|
|
268
|
-
- [postman-api-onboarding-action](https://github.com/postman-cs/postman-api-onboarding-action): composite action that orchestrates the onboarding pipeline.
|
|
269
|
-
- [postman-bootstrap-action](https://github.com/postman-cs/postman-bootstrap-action): workspace provisioning, spec upload, and collection generation.
|
|
270
|
-
- [postman-smoke-flow-action](https://github.com/postman-cs/postman-smoke-flow-action): applies a curated flow.yaml to the canonical Smoke collection.
|
|
271
|
-
- [postman-repo-sync-action](https://github.com/postman-cs/postman-repo-sync-action): artifact sync, environments, mocks, monitors, and CI templates.
|
|
272
|
-
- [postman-aws-spec-discovery-action](https://github.com/postman-cs/postman-aws-spec-discovery-action): discovers API specs from AWS.
|
|
273
257
|
- npm package: [@postman-cse/onboarding-insights](https://www.npmjs.com/package/@postman-cse/onboarding-insights)
|
|
258
|
+
- Docs in this repo: [Credentials and Identity](docs/credentials.md), [Development and Operations](docs/development.md), [CLI usage](docs/cli.md)
|
|
259
|
+
- Marketplace docs: [Support](SUPPORT.md), [Security Policy](SECURITY.md), [Release Policy](RELEASE_POLICY.md)
|
|
274
260
|
- Postman Learning Center: [Insights overview](https://learning.postman.com/docs/insights/overview/), [connect Insights](https://learning.postman.com/docs/api-catalog/connect/insights/), [Insights API Catalog agent reference](https://learning.postman.com/docs/insights/reference/agent/api-catalog/), [API Catalog overview](https://learning.postman.com/docs/api-catalog/overview/), [connect code](https://learning.postman.com/docs/api-catalog/connect/code/)
|
|
275
|
-
- [Credentials and Identity](docs/credentials.md)
|
|
276
|
-
- [Development and Operations](docs/development.md)
|
|
277
|
-
- [Support](SUPPORT.md)
|
|
278
|
-
- [Security Policy](SECURITY.md)
|
|
279
|
-
- [Release Policy](RELEASE_POLICY.md)
|
|
280
|
-
|
|
281
261
|
|
|
282
262
|
## Telemetry
|
|
283
263
|
|
|
284
|
-
|
|
285
|
-
Postman team can measure adoption across CI systems. The event contains the
|
|
286
|
-
action name and version, your Postman team ID, the detected CI provider and
|
|
287
|
-
runner kind, the run outcome, the CI run identifier, an event timestamp, and a one-way SHA-256 hash of the repository
|
|
288
|
-
identifier. Each event also carries a schema version and a constant event marker (always `completion`). The Postman team ID is sent in the clear on a legitimate-interest
|
|
289
|
-
basis to measure product adoption.
|
|
290
|
-
|
|
291
|
-
The `events.pm-cse.dev` endpoint is operated by the Postman Customer Success
|
|
292
|
-
Engineering team. Postman, Inc. processes these events only to measure
|
|
293
|
-
onboarding adoption in aggregate, retains them only as aggregated counts for
|
|
294
|
-
product-adoption trend analysis, and includes no payload field that identifies
|
|
295
|
-
an individual person.
|
|
296
|
-
|
|
297
|
-
It never sends API keys, access tokens, spec content, workspace or repository
|
|
298
|
-
names, or any personal data. It is fire-and-forget with a hard
|
|
299
|
-
timeout and can never block or fail your pipeline. Corporate HTTP and HTTPS
|
|
300
|
-
proxies are honored through the standard `HTTPS_PROXY`, `HTTP_PROXY`, and
|
|
301
|
-
`NO_PROXY` environment variables.
|
|
302
|
-
|
|
303
|
-
Disable it by setting either environment variable in your CI:
|
|
304
|
-
|
|
305
|
-
```sh
|
|
306
|
-
POSTMAN_ACTIONS_TELEMETRY=off
|
|
307
|
-
# or the cross-tool standard
|
|
308
|
-
DO_NOT_TRACK=1
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Telemetry is also skipped automatically when no Postman team ID can be resolved.
|
|
312
|
-
|
|
313
|
-
A run that ends in the `not-found` state (no matching Insights project) is
|
|
314
|
-
recorded with a `failure` outcome.
|
|
315
|
-
|
|
316
|
-
Events are sent over HTTPS to `https://events.pm-cse.dev/v1/events`. To
|
|
317
|
-
allowlist this destination on a restricted network, or to route events to a
|
|
318
|
-
collector you operate, set the `POSTMAN_ACTIONS_TELEMETRY_ENDPOINT` environment
|
|
319
|
-
variable to your own URL.
|
|
264
|
+
The action sends one anonymous usage event per run (action name/version, outcome, coarse CI metadata; never secrets, spec content, or repo names). A run ending in the `not-found` state is recorded with a `failure` outcome. Disable with `POSTMAN_ACTIONS_TELEMETRY=off` or `DO_NOT_TRACK=1`; route events to your own collector with `POSTMAN_ACTIONS_TELEMETRY_ENDPOINT`.
|
|
320
265
|
|
|
321
266
|
## License
|
|
322
267
|
|
package/action.yml
CHANGED
|
@@ -24,8 +24,8 @@ inputs:
|
|
|
24
24
|
description: 'Repository URL for Git onboarding. Auto-detected from CI context when omitted.'
|
|
25
25
|
required: false
|
|
26
26
|
postman-access-token:
|
|
27
|
-
description: 'Postman access token (x-access-token) for the Bifrost linking calls. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there.'
|
|
28
|
-
required:
|
|
27
|
+
description: 'Postman access token (x-access-token) for the Bifrost linking calls. When omitted, the action mints a service-account token from postman-api-key. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there.'
|
|
28
|
+
required: false
|
|
29
29
|
postman-team-id:
|
|
30
30
|
description: 'Explicit Postman team ID for org-mode integration request headers. When omitted, x-entity-team-id is not sent.'
|
|
31
31
|
required: false
|
package/dist/action.cjs
CHANGED
|
@@ -20949,12 +20949,62 @@ function getIDToken(aud) {
|
|
|
20949
20949
|
|
|
20950
20950
|
// src/lib/credential-identity.ts
|
|
20951
20951
|
var sessionPath = "/api/sessions/current";
|
|
20952
|
+
var SESSION_MAX_ATTEMPTS = 3;
|
|
20953
|
+
var SESSION_RETRY_BASE_DELAY_MS = 500;
|
|
20954
|
+
var SESSION_RETRY_MAX_DELAY_MS = 8e3;
|
|
20952
20955
|
var pmakMemo = /* @__PURE__ */ new Map();
|
|
20953
20956
|
var sessionMemo = /* @__PURE__ */ new Map();
|
|
20954
20957
|
var memoizedSessionIdentity;
|
|
20958
|
+
var memoizedSessionFailure;
|
|
20959
|
+
function defaultSessionSleep(ms) {
|
|
20960
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
20961
|
+
}
|
|
20962
|
+
function defaultRandom() {
|
|
20963
|
+
return Math.random();
|
|
20964
|
+
}
|
|
20965
|
+
function parseRetryAfterMs(value) {
|
|
20966
|
+
const trimmed = value?.trim();
|
|
20967
|
+
if (!trimmed) {
|
|
20968
|
+
return void 0;
|
|
20969
|
+
}
|
|
20970
|
+
if (/^\d+$/.test(trimmed)) {
|
|
20971
|
+
return Number(trimmed) * 1e3;
|
|
20972
|
+
}
|
|
20973
|
+
const dateMs = Date.parse(trimmed);
|
|
20974
|
+
if (!Number.isNaN(dateMs)) {
|
|
20975
|
+
return Math.max(0, dateMs - Date.now());
|
|
20976
|
+
}
|
|
20977
|
+
return void 0;
|
|
20978
|
+
}
|
|
20979
|
+
function parseRateLimitResetMs(value) {
|
|
20980
|
+
const trimmed = value?.trim();
|
|
20981
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) {
|
|
20982
|
+
return void 0;
|
|
20983
|
+
}
|
|
20984
|
+
const seconds = Number(trimmed);
|
|
20985
|
+
const nowSeconds = Date.now() / 1e3;
|
|
20986
|
+
if (seconds > nowSeconds) {
|
|
20987
|
+
return Math.max(0, (seconds - nowSeconds) * 1e3);
|
|
20988
|
+
}
|
|
20989
|
+
return seconds * 1e3;
|
|
20990
|
+
}
|
|
20991
|
+
function computeSessionRetryDelayMs(response, attempt, random) {
|
|
20992
|
+
const headers = response?.headers;
|
|
20993
|
+
const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
|
|
20994
|
+
headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
|
|
20995
|
+
);
|
|
20996
|
+
if (signal !== void 0) {
|
|
20997
|
+
return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
|
|
20998
|
+
}
|
|
20999
|
+
const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
21000
|
+
return Math.round(random() * ceiling);
|
|
21001
|
+
}
|
|
20955
21002
|
function getMemoizedSessionIdentity() {
|
|
20956
21003
|
return memoizedSessionIdentity;
|
|
20957
21004
|
}
|
|
21005
|
+
function getSessionResolutionFailure() {
|
|
21006
|
+
return memoizedSessionFailure;
|
|
21007
|
+
}
|
|
20958
21008
|
function asRecord(value) {
|
|
20959
21009
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20960
21010
|
return void 0;
|
|
@@ -21023,46 +21073,90 @@ async function resolveSessionIdentity(opts) {
|
|
|
21023
21073
|
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21024
21074
|
let pending = sessionMemo.get(memoKey);
|
|
21025
21075
|
if (!pending) {
|
|
21026
|
-
pending = probeSessionIdentity(
|
|
21076
|
+
pending = probeSessionIdentity(
|
|
21077
|
+
baseUrl,
|
|
21078
|
+
accessToken,
|
|
21079
|
+
opts.fetchImpl ?? fetch,
|
|
21080
|
+
Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
|
|
21081
|
+
opts.sleepImpl ?? defaultSessionSleep,
|
|
21082
|
+
opts.randomImpl ?? defaultRandom
|
|
21083
|
+
);
|
|
21027
21084
|
sessionMemo.set(memoKey, pending);
|
|
21028
21085
|
}
|
|
21029
21086
|
return pending;
|
|
21030
21087
|
}
|
|
21031
|
-
async function
|
|
21088
|
+
async function parseSessionResponse(response) {
|
|
21089
|
+
let payload;
|
|
21032
21090
|
try {
|
|
21033
|
-
|
|
21034
|
-
method: "GET",
|
|
21035
|
-
headers: { "x-access-token": accessToken }
|
|
21036
|
-
});
|
|
21037
|
-
if (!response.ok) {
|
|
21038
|
-
return void 0;
|
|
21039
|
-
}
|
|
21040
|
-
const payload = asRecord(await response.json());
|
|
21041
|
-
if (!payload) {
|
|
21042
|
-
return void 0;
|
|
21043
|
-
}
|
|
21044
|
-
const root = asRecord(payload.session) ?? payload;
|
|
21045
|
-
const identity = asRecord(root.identity);
|
|
21046
|
-
const data = asRecord(root.data);
|
|
21047
|
-
const user = asRecord(data?.user);
|
|
21048
|
-
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21049
|
-
const singleRole = coerceText(user?.role);
|
|
21050
|
-
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21051
|
-
const resolved = {
|
|
21052
|
-
source: "iapub/sessions",
|
|
21053
|
-
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21054
|
-
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21055
|
-
teamId: coerceId(identity?.team),
|
|
21056
|
-
teamName: coerceText(user?.teamName),
|
|
21057
|
-
teamDomain: coerceText(identity?.domain),
|
|
21058
|
-
...roles ? { roles } : {},
|
|
21059
|
-
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21060
|
-
};
|
|
21061
|
-
memoizedSessionIdentity = resolved;
|
|
21062
|
-
return resolved;
|
|
21091
|
+
payload = asRecord(await response.json());
|
|
21063
21092
|
} catch {
|
|
21064
21093
|
return void 0;
|
|
21065
21094
|
}
|
|
21095
|
+
if (!payload) {
|
|
21096
|
+
return void 0;
|
|
21097
|
+
}
|
|
21098
|
+
const root = asRecord(payload.session) ?? payload;
|
|
21099
|
+
const identity = asRecord(root.identity);
|
|
21100
|
+
const data = asRecord(root.data);
|
|
21101
|
+
const user = asRecord(data?.user);
|
|
21102
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21103
|
+
const singleRole = coerceText(user?.role);
|
|
21104
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21105
|
+
return {
|
|
21106
|
+
source: "iapub/sessions",
|
|
21107
|
+
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21108
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21109
|
+
teamId: coerceId(identity?.team),
|
|
21110
|
+
teamName: coerceText(user?.teamName),
|
|
21111
|
+
teamDomain: coerceText(identity?.domain),
|
|
21112
|
+
...roles ? { roles } : {},
|
|
21113
|
+
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21114
|
+
};
|
|
21115
|
+
}
|
|
21116
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
|
|
21117
|
+
let failure = "unavailable";
|
|
21118
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
21119
|
+
let response;
|
|
21120
|
+
try {
|
|
21121
|
+
response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21122
|
+
method: "GET",
|
|
21123
|
+
headers: { "x-access-token": accessToken }
|
|
21124
|
+
});
|
|
21125
|
+
} catch {
|
|
21126
|
+
failure = "unavailable";
|
|
21127
|
+
if (attempt < maxAttempts) {
|
|
21128
|
+
await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
|
|
21129
|
+
continue;
|
|
21130
|
+
}
|
|
21131
|
+
break;
|
|
21132
|
+
}
|
|
21133
|
+
if (response.ok) {
|
|
21134
|
+
const resolved = await parseSessionResponse(response);
|
|
21135
|
+
if (resolved) {
|
|
21136
|
+
memoizedSessionIdentity = resolved;
|
|
21137
|
+
memoizedSessionFailure = void 0;
|
|
21138
|
+
return resolved;
|
|
21139
|
+
}
|
|
21140
|
+
failure = "unavailable";
|
|
21141
|
+
break;
|
|
21142
|
+
}
|
|
21143
|
+
if (response.status === 401 || response.status === 403) {
|
|
21144
|
+
failure = "auth";
|
|
21145
|
+
break;
|
|
21146
|
+
}
|
|
21147
|
+
if (response.status === 429 || response.status >= 500) {
|
|
21148
|
+
failure = "unavailable";
|
|
21149
|
+
if (attempt < maxAttempts) {
|
|
21150
|
+
await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
|
|
21151
|
+
continue;
|
|
21152
|
+
}
|
|
21153
|
+
break;
|
|
21154
|
+
}
|
|
21155
|
+
failure = "unavailable";
|
|
21156
|
+
break;
|
|
21157
|
+
}
|
|
21158
|
+
memoizedSessionFailure = failure;
|
|
21159
|
+
return void 0;
|
|
21066
21160
|
}
|
|
21067
21161
|
function describeTeam(id) {
|
|
21068
21162
|
const label = id?.teamName ?? id?.teamDomain;
|
|
@@ -21155,7 +21249,9 @@ async function runCredentialPreflight(args) {
|
|
|
21155
21249
|
session = await resolveSessionIdentity({
|
|
21156
21250
|
iapubBaseUrl: args.iapubBaseUrl,
|
|
21157
21251
|
accessToken,
|
|
21158
|
-
fetchImpl: args.fetchImpl
|
|
21252
|
+
fetchImpl: args.fetchImpl,
|
|
21253
|
+
...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
|
|
21254
|
+
...args.randomImpl ? { randomImpl: args.randomImpl } : {}
|
|
21159
21255
|
});
|
|
21160
21256
|
} catch (error2) {
|
|
21161
21257
|
args.log.warning(
|
|
@@ -21175,11 +21271,20 @@ async function runCredentialPreflight(args) {
|
|
|
21175
21271
|
);
|
|
21176
21272
|
}
|
|
21177
21273
|
} else {
|
|
21274
|
+
const failure = getSessionResolutionFailure();
|
|
21275
|
+
const detail = failure === "auth" ? "the access token was rejected by iapub (401/403), so it is invalid or expired. Re-mint it with postman-resolve-service-token-action (or POST https://api.getpostman.com/service-account-tokens) and re-run." : "iapub was unreachable after retries (network or 5xx). This is usually transient; re-run the job.";
|
|
21276
|
+
const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
|
|
21277
|
+
if (args.mode === "enforce") {
|
|
21278
|
+
throw new Error(
|
|
21279
|
+
mask(
|
|
21280
|
+
`${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
|
|
21281
|
+
)
|
|
21282
|
+
);
|
|
21283
|
+
}
|
|
21178
21284
|
args.log.warning(
|
|
21179
|
-
mask(
|
|
21180
|
-
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21181
|
-
)
|
|
21285
|
+
mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
|
|
21182
21286
|
);
|
|
21287
|
+
return;
|
|
21183
21288
|
}
|
|
21184
21289
|
const result = crossCheckIdentities({
|
|
21185
21290
|
pmak,
|
|
@@ -21591,6 +21696,53 @@ var AccessTokenProvider = class {
|
|
|
21591
21696
|
return token;
|
|
21592
21697
|
}
|
|
21593
21698
|
};
|
|
21699
|
+
async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
|
|
21700
|
+
const raw = mintError instanceof Error ? mintError.message : String(mintError);
|
|
21701
|
+
const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
|
|
21702
|
+
if (!rejected) {
|
|
21703
|
+
return raw;
|
|
21704
|
+
}
|
|
21705
|
+
try {
|
|
21706
|
+
const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
|
|
21707
|
+
if (me.ok) {
|
|
21708
|
+
const body = await me.json().catch(() => void 0);
|
|
21709
|
+
const user = body?.user;
|
|
21710
|
+
const looksPersonal = Boolean(user && (user.username || user.email));
|
|
21711
|
+
if (looksPersonal) {
|
|
21712
|
+
return "Personal API key detected, cannot mint a service-account access token. POST /service-account-tokens only accepts a SERVICE-ACCOUNT API key; this postman-api-key belongs to a user account" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". Create a service account in Team Settings and use its PMAK, or mint the token elsewhere and pass postman-access-token.";
|
|
21713
|
+
}
|
|
21714
|
+
return "The postman-api-key authenticates (GET /me OK) but was rejected by POST /service-account-tokens" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". The service account likely lacks permission to mint access tokens, or service accounts are restricted for this team. Check the service account role in Team Settings, or pass a pre-minted postman-access-token.";
|
|
21715
|
+
}
|
|
21716
|
+
return "The postman-api-key is invalid, disabled, or expired (rejected by both POST /service-account-tokens and GET /me). Generate a fresh service-account PMAK in Team Settings and update the secret.";
|
|
21717
|
+
} catch {
|
|
21718
|
+
return raw;
|
|
21719
|
+
}
|
|
21720
|
+
}
|
|
21721
|
+
async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
|
|
21722
|
+
if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
|
|
21723
|
+
return;
|
|
21724
|
+
}
|
|
21725
|
+
const apiBaseUrl = String(
|
|
21726
|
+
inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
|
|
21727
|
+
).replace(/\/+$/, "");
|
|
21728
|
+
const provider = new AccessTokenProvider({
|
|
21729
|
+
apiKey: inputs.postmanApiKey,
|
|
21730
|
+
apiBaseUrl,
|
|
21731
|
+
fetchImpl,
|
|
21732
|
+
onToken: (token) => setSecret2?.(token)
|
|
21733
|
+
});
|
|
21734
|
+
try {
|
|
21735
|
+
inputs.postmanAccessToken = await provider.refresh();
|
|
21736
|
+
log.info(
|
|
21737
|
+
"postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
|
|
21738
|
+
);
|
|
21739
|
+
} catch (error2) {
|
|
21740
|
+
const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
|
|
21741
|
+
log.warning(
|
|
21742
|
+
"postman: could not mint an access token from the postman-api-key. " + diagnosis + " Continuing without an access token - access-token-only functionality will be unavailable unless postman-access-token is provided."
|
|
21743
|
+
);
|
|
21744
|
+
}
|
|
21745
|
+
}
|
|
21594
21746
|
|
|
21595
21747
|
// src/lib/bifrost-client.ts
|
|
21596
21748
|
var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
|
|
@@ -22249,7 +22401,7 @@ function resolveActionVersion(explicit) {
|
|
|
22249
22401
|
if (explicit) {
|
|
22250
22402
|
return explicit;
|
|
22251
22403
|
}
|
|
22252
|
-
return "
|
|
22404
|
+
return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
|
|
22253
22405
|
}
|
|
22254
22406
|
function telemetryDisabled(env) {
|
|
22255
22407
|
const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
|
|
@@ -22371,6 +22523,18 @@ function createTelemetryContext(options) {
|
|
|
22371
22523
|
};
|
|
22372
22524
|
}
|
|
22373
22525
|
|
|
22526
|
+
// src/action-version.ts
|
|
22527
|
+
var import_node_fs = require("node:fs");
|
|
22528
|
+
var import_node_path = require("node:path");
|
|
22529
|
+
function resolveActionVersion2() {
|
|
22530
|
+
try {
|
|
22531
|
+
const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
|
|
22532
|
+
return JSON.parse(raw).version ?? "unknown";
|
|
22533
|
+
} catch {
|
|
22534
|
+
return "unknown";
|
|
22535
|
+
}
|
|
22536
|
+
}
|
|
22537
|
+
|
|
22374
22538
|
// src/index.ts
|
|
22375
22539
|
var POLL_TIMEOUT_MIN = 10;
|
|
22376
22540
|
var POLL_TIMEOUT_MAX = 600;
|
|
@@ -22436,8 +22600,12 @@ function resolveInputs(env = process.env) {
|
|
|
22436
22600
|
const projectName = get("project-name");
|
|
22437
22601
|
if (!projectName) throw new Error("project-name is required");
|
|
22438
22602
|
const postmanAccessToken = get("postman-access-token");
|
|
22439
|
-
if (!postmanAccessToken) throw new Error("postman-access-token is required");
|
|
22440
22603
|
const postmanApiKey = get("postman-api-key");
|
|
22604
|
+
if (!postmanAccessToken && !postmanApiKey) {
|
|
22605
|
+
throw new Error(
|
|
22606
|
+
"postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
|
|
22607
|
+
);
|
|
22608
|
+
}
|
|
22441
22609
|
const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
|
|
22442
22610
|
const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
|
|
22443
22611
|
if (!workspaceId) {
|
|
@@ -22677,7 +22845,14 @@ async function runAction() {
|
|
|
22677
22845
|
for (const [key, value] of Object.entries(planned)) {
|
|
22678
22846
|
setOutput(key, value);
|
|
22679
22847
|
}
|
|
22680
|
-
|
|
22848
|
+
const mintHolder = {
|
|
22849
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22850
|
+
postmanApiKey: inputs.postmanApiKey,
|
|
22851
|
+
postmanApiBase: inputs.postmanApiBase
|
|
22852
|
+
};
|
|
22853
|
+
await mintAccessTokenIfNeeded(mintHolder, core_exports, (secret) => setSecret(secret));
|
|
22854
|
+
inputs.postmanAccessToken = mintHolder.postmanAccessToken;
|
|
22855
|
+
if (inputs.postmanAccessToken) setSecret(inputs.postmanAccessToken);
|
|
22681
22856
|
if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
|
|
22682
22857
|
if (inputs.githubToken) setSecret(inputs.githubToken);
|
|
22683
22858
|
const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
|
|
@@ -22694,7 +22869,7 @@ async function runAction() {
|
|
|
22694
22869
|
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22695
22870
|
onToken: (token) => setSecret(token)
|
|
22696
22871
|
}) : tokenProvider;
|
|
22697
|
-
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
|
|
22872
|
+
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
|
|
22698
22873
|
telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
|
|
22699
22874
|
let result;
|
|
22700
22875
|
try {
|
package/dist/cli.cjs
CHANGED
|
@@ -18685,9 +18685,9 @@ __export(cli_exports, {
|
|
|
18685
18685
|
toDotenv: () => toDotenv
|
|
18686
18686
|
});
|
|
18687
18687
|
module.exports = __toCommonJS(cli_exports);
|
|
18688
|
-
var
|
|
18688
|
+
var import_node_fs2 = require("node:fs");
|
|
18689
18689
|
var import_promises = require("node:fs/promises");
|
|
18690
|
-
var
|
|
18690
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
18691
18691
|
|
|
18692
18692
|
// src/lib/retry.ts
|
|
18693
18693
|
function sleep(delayMs) {
|
|
@@ -18891,6 +18891,53 @@ var AccessTokenProvider = class {
|
|
|
18891
18891
|
return token;
|
|
18892
18892
|
}
|
|
18893
18893
|
};
|
|
18894
|
+
async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
|
|
18895
|
+
const raw = mintError instanceof Error ? mintError.message : String(mintError);
|
|
18896
|
+
const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
|
|
18897
|
+
if (!rejected) {
|
|
18898
|
+
return raw;
|
|
18899
|
+
}
|
|
18900
|
+
try {
|
|
18901
|
+
const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
|
|
18902
|
+
if (me.ok) {
|
|
18903
|
+
const body = await me.json().catch(() => void 0);
|
|
18904
|
+
const user = body?.user;
|
|
18905
|
+
const looksPersonal = Boolean(user && (user.username || user.email));
|
|
18906
|
+
if (looksPersonal) {
|
|
18907
|
+
return "Personal API key detected, cannot mint a service-account access token. POST /service-account-tokens only accepts a SERVICE-ACCOUNT API key; this postman-api-key belongs to a user account" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". Create a service account in Team Settings and use its PMAK, or mint the token elsewhere and pass postman-access-token.";
|
|
18908
|
+
}
|
|
18909
|
+
return "The postman-api-key authenticates (GET /me OK) but was rejected by POST /service-account-tokens" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". The service account likely lacks permission to mint access tokens, or service accounts are restricted for this team. Check the service account role in Team Settings, or pass a pre-minted postman-access-token.";
|
|
18910
|
+
}
|
|
18911
|
+
return "The postman-api-key is invalid, disabled, or expired (rejected by both POST /service-account-tokens and GET /me). Generate a fresh service-account PMAK in Team Settings and update the secret.";
|
|
18912
|
+
} catch {
|
|
18913
|
+
return raw;
|
|
18914
|
+
}
|
|
18915
|
+
}
|
|
18916
|
+
async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
|
|
18917
|
+
if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
|
|
18918
|
+
return;
|
|
18919
|
+
}
|
|
18920
|
+
const apiBaseUrl = String(
|
|
18921
|
+
inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
|
|
18922
|
+
).replace(/\/+$/, "");
|
|
18923
|
+
const provider = new AccessTokenProvider({
|
|
18924
|
+
apiKey: inputs.postmanApiKey,
|
|
18925
|
+
apiBaseUrl,
|
|
18926
|
+
fetchImpl,
|
|
18927
|
+
onToken: (token) => setSecret2?.(token)
|
|
18928
|
+
});
|
|
18929
|
+
try {
|
|
18930
|
+
inputs.postmanAccessToken = await provider.refresh();
|
|
18931
|
+
log.info(
|
|
18932
|
+
"postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
|
|
18933
|
+
);
|
|
18934
|
+
} catch (error2) {
|
|
18935
|
+
const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
|
|
18936
|
+
log.warning(
|
|
18937
|
+
"postman: could not mint an access token from the postman-api-key. " + diagnosis + " Continuing without an access token - access-token-only functionality will be unavailable unless postman-access-token is provided."
|
|
18938
|
+
);
|
|
18939
|
+
}
|
|
18940
|
+
}
|
|
18894
18941
|
|
|
18895
18942
|
// node_modules/@actions/core/lib/core.js
|
|
18896
18943
|
var core_exports = {};
|
|
@@ -21167,12 +21214,62 @@ function getIDToken(aud) {
|
|
|
21167
21214
|
|
|
21168
21215
|
// src/lib/credential-identity.ts
|
|
21169
21216
|
var sessionPath = "/api/sessions/current";
|
|
21217
|
+
var SESSION_MAX_ATTEMPTS = 3;
|
|
21218
|
+
var SESSION_RETRY_BASE_DELAY_MS = 500;
|
|
21219
|
+
var SESSION_RETRY_MAX_DELAY_MS = 8e3;
|
|
21170
21220
|
var pmakMemo = /* @__PURE__ */ new Map();
|
|
21171
21221
|
var sessionMemo = /* @__PURE__ */ new Map();
|
|
21172
21222
|
var memoizedSessionIdentity;
|
|
21223
|
+
var memoizedSessionFailure;
|
|
21224
|
+
function defaultSessionSleep(ms) {
|
|
21225
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
21226
|
+
}
|
|
21227
|
+
function defaultRandom() {
|
|
21228
|
+
return Math.random();
|
|
21229
|
+
}
|
|
21230
|
+
function parseRetryAfterMs(value) {
|
|
21231
|
+
const trimmed = value?.trim();
|
|
21232
|
+
if (!trimmed) {
|
|
21233
|
+
return void 0;
|
|
21234
|
+
}
|
|
21235
|
+
if (/^\d+$/.test(trimmed)) {
|
|
21236
|
+
return Number(trimmed) * 1e3;
|
|
21237
|
+
}
|
|
21238
|
+
const dateMs = Date.parse(trimmed);
|
|
21239
|
+
if (!Number.isNaN(dateMs)) {
|
|
21240
|
+
return Math.max(0, dateMs - Date.now());
|
|
21241
|
+
}
|
|
21242
|
+
return void 0;
|
|
21243
|
+
}
|
|
21244
|
+
function parseRateLimitResetMs(value) {
|
|
21245
|
+
const trimmed = value?.trim();
|
|
21246
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) {
|
|
21247
|
+
return void 0;
|
|
21248
|
+
}
|
|
21249
|
+
const seconds = Number(trimmed);
|
|
21250
|
+
const nowSeconds = Date.now() / 1e3;
|
|
21251
|
+
if (seconds > nowSeconds) {
|
|
21252
|
+
return Math.max(0, (seconds - nowSeconds) * 1e3);
|
|
21253
|
+
}
|
|
21254
|
+
return seconds * 1e3;
|
|
21255
|
+
}
|
|
21256
|
+
function computeSessionRetryDelayMs(response, attempt, random) {
|
|
21257
|
+
const headers = response?.headers;
|
|
21258
|
+
const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
|
|
21259
|
+
headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
|
|
21260
|
+
);
|
|
21261
|
+
if (signal !== void 0) {
|
|
21262
|
+
return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
|
|
21263
|
+
}
|
|
21264
|
+
const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
21265
|
+
return Math.round(random() * ceiling);
|
|
21266
|
+
}
|
|
21173
21267
|
function getMemoizedSessionIdentity() {
|
|
21174
21268
|
return memoizedSessionIdentity;
|
|
21175
21269
|
}
|
|
21270
|
+
function getSessionResolutionFailure() {
|
|
21271
|
+
return memoizedSessionFailure;
|
|
21272
|
+
}
|
|
21176
21273
|
function asRecord(value) {
|
|
21177
21274
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
21178
21275
|
return void 0;
|
|
@@ -21241,46 +21338,90 @@ async function resolveSessionIdentity(opts) {
|
|
|
21241
21338
|
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21242
21339
|
let pending = sessionMemo.get(memoKey);
|
|
21243
21340
|
if (!pending) {
|
|
21244
|
-
pending = probeSessionIdentity(
|
|
21341
|
+
pending = probeSessionIdentity(
|
|
21342
|
+
baseUrl,
|
|
21343
|
+
accessToken,
|
|
21344
|
+
opts.fetchImpl ?? fetch,
|
|
21345
|
+
Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
|
|
21346
|
+
opts.sleepImpl ?? defaultSessionSleep,
|
|
21347
|
+
opts.randomImpl ?? defaultRandom
|
|
21348
|
+
);
|
|
21245
21349
|
sessionMemo.set(memoKey, pending);
|
|
21246
21350
|
}
|
|
21247
21351
|
return pending;
|
|
21248
21352
|
}
|
|
21249
|
-
async function
|
|
21353
|
+
async function parseSessionResponse(response) {
|
|
21354
|
+
let payload;
|
|
21250
21355
|
try {
|
|
21251
|
-
|
|
21252
|
-
method: "GET",
|
|
21253
|
-
headers: { "x-access-token": accessToken }
|
|
21254
|
-
});
|
|
21255
|
-
if (!response.ok) {
|
|
21256
|
-
return void 0;
|
|
21257
|
-
}
|
|
21258
|
-
const payload = asRecord(await response.json());
|
|
21259
|
-
if (!payload) {
|
|
21260
|
-
return void 0;
|
|
21261
|
-
}
|
|
21262
|
-
const root = asRecord(payload.session) ?? payload;
|
|
21263
|
-
const identity = asRecord(root.identity);
|
|
21264
|
-
const data = asRecord(root.data);
|
|
21265
|
-
const user = asRecord(data?.user);
|
|
21266
|
-
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21267
|
-
const singleRole = coerceText(user?.role);
|
|
21268
|
-
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21269
|
-
const resolved = {
|
|
21270
|
-
source: "iapub/sessions",
|
|
21271
|
-
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21272
|
-
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21273
|
-
teamId: coerceId(identity?.team),
|
|
21274
|
-
teamName: coerceText(user?.teamName),
|
|
21275
|
-
teamDomain: coerceText(identity?.domain),
|
|
21276
|
-
...roles ? { roles } : {},
|
|
21277
|
-
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21278
|
-
};
|
|
21279
|
-
memoizedSessionIdentity = resolved;
|
|
21280
|
-
return resolved;
|
|
21356
|
+
payload = asRecord(await response.json());
|
|
21281
21357
|
} catch {
|
|
21282
21358
|
return void 0;
|
|
21283
21359
|
}
|
|
21360
|
+
if (!payload) {
|
|
21361
|
+
return void 0;
|
|
21362
|
+
}
|
|
21363
|
+
const root = asRecord(payload.session) ?? payload;
|
|
21364
|
+
const identity = asRecord(root.identity);
|
|
21365
|
+
const data = asRecord(root.data);
|
|
21366
|
+
const user = asRecord(data?.user);
|
|
21367
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21368
|
+
const singleRole = coerceText(user?.role);
|
|
21369
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21370
|
+
return {
|
|
21371
|
+
source: "iapub/sessions",
|
|
21372
|
+
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21373
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21374
|
+
teamId: coerceId(identity?.team),
|
|
21375
|
+
teamName: coerceText(user?.teamName),
|
|
21376
|
+
teamDomain: coerceText(identity?.domain),
|
|
21377
|
+
...roles ? { roles } : {},
|
|
21378
|
+
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21379
|
+
};
|
|
21380
|
+
}
|
|
21381
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
|
|
21382
|
+
let failure = "unavailable";
|
|
21383
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
21384
|
+
let response;
|
|
21385
|
+
try {
|
|
21386
|
+
response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21387
|
+
method: "GET",
|
|
21388
|
+
headers: { "x-access-token": accessToken }
|
|
21389
|
+
});
|
|
21390
|
+
} catch {
|
|
21391
|
+
failure = "unavailable";
|
|
21392
|
+
if (attempt < maxAttempts) {
|
|
21393
|
+
await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
|
|
21394
|
+
continue;
|
|
21395
|
+
}
|
|
21396
|
+
break;
|
|
21397
|
+
}
|
|
21398
|
+
if (response.ok) {
|
|
21399
|
+
const resolved = await parseSessionResponse(response);
|
|
21400
|
+
if (resolved) {
|
|
21401
|
+
memoizedSessionIdentity = resolved;
|
|
21402
|
+
memoizedSessionFailure = void 0;
|
|
21403
|
+
return resolved;
|
|
21404
|
+
}
|
|
21405
|
+
failure = "unavailable";
|
|
21406
|
+
break;
|
|
21407
|
+
}
|
|
21408
|
+
if (response.status === 401 || response.status === 403) {
|
|
21409
|
+
failure = "auth";
|
|
21410
|
+
break;
|
|
21411
|
+
}
|
|
21412
|
+
if (response.status === 429 || response.status >= 500) {
|
|
21413
|
+
failure = "unavailable";
|
|
21414
|
+
if (attempt < maxAttempts) {
|
|
21415
|
+
await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
|
|
21416
|
+
continue;
|
|
21417
|
+
}
|
|
21418
|
+
break;
|
|
21419
|
+
}
|
|
21420
|
+
failure = "unavailable";
|
|
21421
|
+
break;
|
|
21422
|
+
}
|
|
21423
|
+
memoizedSessionFailure = failure;
|
|
21424
|
+
return void 0;
|
|
21284
21425
|
}
|
|
21285
21426
|
function describeTeam(id) {
|
|
21286
21427
|
const label = id?.teamName ?? id?.teamDomain;
|
|
@@ -21373,7 +21514,9 @@ async function runCredentialPreflight(args) {
|
|
|
21373
21514
|
session = await resolveSessionIdentity({
|
|
21374
21515
|
iapubBaseUrl: args.iapubBaseUrl,
|
|
21375
21516
|
accessToken,
|
|
21376
|
-
fetchImpl: args.fetchImpl
|
|
21517
|
+
fetchImpl: args.fetchImpl,
|
|
21518
|
+
...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
|
|
21519
|
+
...args.randomImpl ? { randomImpl: args.randomImpl } : {}
|
|
21377
21520
|
});
|
|
21378
21521
|
} catch (error2) {
|
|
21379
21522
|
args.log.warning(
|
|
@@ -21393,11 +21536,20 @@ async function runCredentialPreflight(args) {
|
|
|
21393
21536
|
);
|
|
21394
21537
|
}
|
|
21395
21538
|
} else {
|
|
21539
|
+
const failure = getSessionResolutionFailure();
|
|
21540
|
+
const detail = failure === "auth" ? "the access token was rejected by iapub (401/403), so it is invalid or expired. Re-mint it with postman-resolve-service-token-action (or POST https://api.getpostman.com/service-account-tokens) and re-run." : "iapub was unreachable after retries (network or 5xx). This is usually transient; re-run the job.";
|
|
21541
|
+
const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
|
|
21542
|
+
if (args.mode === "enforce") {
|
|
21543
|
+
throw new Error(
|
|
21544
|
+
mask(
|
|
21545
|
+
`${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
|
|
21546
|
+
)
|
|
21547
|
+
);
|
|
21548
|
+
}
|
|
21396
21549
|
args.log.warning(
|
|
21397
|
-
mask(
|
|
21398
|
-
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21399
|
-
)
|
|
21550
|
+
mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
|
|
21400
21551
|
);
|
|
21552
|
+
return;
|
|
21401
21553
|
}
|
|
21402
21554
|
const result = crossCheckIdentities({
|
|
21403
21555
|
pmak,
|
|
@@ -22264,7 +22416,7 @@ function resolveActionVersion(explicit) {
|
|
|
22264
22416
|
if (explicit) {
|
|
22265
22417
|
return explicit;
|
|
22266
22418
|
}
|
|
22267
|
-
return "
|
|
22419
|
+
return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
|
|
22268
22420
|
}
|
|
22269
22421
|
function telemetryDisabled(env) {
|
|
22270
22422
|
const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
|
|
@@ -22386,6 +22538,18 @@ function createTelemetryContext(options) {
|
|
|
22386
22538
|
};
|
|
22387
22539
|
}
|
|
22388
22540
|
|
|
22541
|
+
// src/action-version.ts
|
|
22542
|
+
var import_node_fs = require("node:fs");
|
|
22543
|
+
var import_node_path = require("node:path");
|
|
22544
|
+
function resolveActionVersion2() {
|
|
22545
|
+
try {
|
|
22546
|
+
const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
|
|
22547
|
+
return JSON.parse(raw).version ?? "unknown";
|
|
22548
|
+
} catch {
|
|
22549
|
+
return "unknown";
|
|
22550
|
+
}
|
|
22551
|
+
}
|
|
22552
|
+
|
|
22389
22553
|
// src/index.ts
|
|
22390
22554
|
var POLL_TIMEOUT_MIN = 10;
|
|
22391
22555
|
var POLL_TIMEOUT_MAX = 600;
|
|
@@ -22451,8 +22615,12 @@ function resolveInputs(env = process.env) {
|
|
|
22451
22615
|
const projectName = get("project-name");
|
|
22452
22616
|
if (!projectName) throw new Error("project-name is required");
|
|
22453
22617
|
const postmanAccessToken = get("postman-access-token");
|
|
22454
|
-
if (!postmanAccessToken) throw new Error("postman-access-token is required");
|
|
22455
22618
|
const postmanApiKey = get("postman-api-key");
|
|
22619
|
+
if (!postmanAccessToken && !postmanApiKey) {
|
|
22620
|
+
throw new Error(
|
|
22621
|
+
"postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
|
|
22622
|
+
);
|
|
22623
|
+
}
|
|
22456
22624
|
const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
|
|
22457
22625
|
const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
|
|
22458
22626
|
if (!workspaceId) {
|
|
@@ -22756,13 +22924,13 @@ async function writeOptionalFile(filePath, content) {
|
|
|
22756
22924
|
if (!filePath) {
|
|
22757
22925
|
return;
|
|
22758
22926
|
}
|
|
22759
|
-
const workspaceRoot =
|
|
22760
|
-
const resolved =
|
|
22761
|
-
const relative2 =
|
|
22762
|
-
if (relative2.startsWith("..") ||
|
|
22927
|
+
const workspaceRoot = import_node_path2.default.resolve(process.cwd());
|
|
22928
|
+
const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
|
|
22929
|
+
const relative2 = import_node_path2.default.relative(workspaceRoot, resolved);
|
|
22930
|
+
if (relative2.startsWith("..") || import_node_path2.default.isAbsolute(relative2)) {
|
|
22763
22931
|
throw new Error(`Output path must stay within workspace: ${filePath}`);
|
|
22764
22932
|
}
|
|
22765
|
-
await (0, import_promises.mkdir)(
|
|
22933
|
+
await (0, import_promises.mkdir)(import_node_path2.default.dirname(resolved), { recursive: true });
|
|
22766
22934
|
await (0, import_promises.writeFile)(resolved, content, "utf8");
|
|
22767
22935
|
}
|
|
22768
22936
|
function toOutputs(result) {
|
|
@@ -22780,7 +22948,14 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
22780
22948
|
const config = parseCliArgs(argv, env);
|
|
22781
22949
|
const inputs = resolveInputs(config.inputEnv);
|
|
22782
22950
|
const reporter = new ConsoleReporter();
|
|
22783
|
-
|
|
22951
|
+
const mintHolder = {
|
|
22952
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22953
|
+
postmanApiKey: inputs.postmanApiKey,
|
|
22954
|
+
postmanApiBase: inputs.postmanApiBase
|
|
22955
|
+
};
|
|
22956
|
+
await mintAccessTokenIfNeeded(mintHolder, reporter, (secret) => reporter.setSecret(secret));
|
|
22957
|
+
inputs.postmanAccessToken = mintHolder.postmanAccessToken;
|
|
22958
|
+
if (inputs.postmanAccessToken) reporter.setSecret(inputs.postmanAccessToken);
|
|
22784
22959
|
if (inputs.postmanApiKey) {
|
|
22785
22960
|
reporter.setSecret(inputs.postmanApiKey);
|
|
22786
22961
|
}
|
|
@@ -22805,7 +22980,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
22805
22980
|
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22806
22981
|
onToken: (token) => reporter.setSecret(token)
|
|
22807
22982
|
}) : tokenProvider;
|
|
22808
|
-
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: reporter });
|
|
22983
|
+
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: reporter });
|
|
22809
22984
|
telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
|
|
22810
22985
|
if (apiKey) {
|
|
22811
22986
|
reporter.setSecret(apiKey);
|
|
@@ -22850,9 +23025,9 @@ function isEntrypoint(currentPath, entrypointPath) {
|
|
|
22850
23025
|
return false;
|
|
22851
23026
|
}
|
|
22852
23027
|
try {
|
|
22853
|
-
return (0,
|
|
23028
|
+
return (0, import_node_fs2.realpathSync)(currentPath) === (0, import_node_fs2.realpathSync)(entrypointPath);
|
|
22854
23029
|
} catch {
|
|
22855
|
-
return
|
|
23030
|
+
return import_node_path2.default.resolve(currentPath) === import_node_path2.default.resolve(entrypointPath);
|
|
22856
23031
|
}
|
|
22857
23032
|
}
|
|
22858
23033
|
if (isEntrypoint(currentModulePath, entrypoint)) {
|
package/dist/index.cjs
CHANGED
|
@@ -20971,12 +20971,62 @@ function getIDToken(aud) {
|
|
|
20971
20971
|
|
|
20972
20972
|
// src/lib/credential-identity.ts
|
|
20973
20973
|
var sessionPath = "/api/sessions/current";
|
|
20974
|
+
var SESSION_MAX_ATTEMPTS = 3;
|
|
20975
|
+
var SESSION_RETRY_BASE_DELAY_MS = 500;
|
|
20976
|
+
var SESSION_RETRY_MAX_DELAY_MS = 8e3;
|
|
20974
20977
|
var pmakMemo = /* @__PURE__ */ new Map();
|
|
20975
20978
|
var sessionMemo = /* @__PURE__ */ new Map();
|
|
20976
20979
|
var memoizedSessionIdentity;
|
|
20980
|
+
var memoizedSessionFailure;
|
|
20981
|
+
function defaultSessionSleep(ms) {
|
|
20982
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
20983
|
+
}
|
|
20984
|
+
function defaultRandom() {
|
|
20985
|
+
return Math.random();
|
|
20986
|
+
}
|
|
20987
|
+
function parseRetryAfterMs(value) {
|
|
20988
|
+
const trimmed = value?.trim();
|
|
20989
|
+
if (!trimmed) {
|
|
20990
|
+
return void 0;
|
|
20991
|
+
}
|
|
20992
|
+
if (/^\d+$/.test(trimmed)) {
|
|
20993
|
+
return Number(trimmed) * 1e3;
|
|
20994
|
+
}
|
|
20995
|
+
const dateMs = Date.parse(trimmed);
|
|
20996
|
+
if (!Number.isNaN(dateMs)) {
|
|
20997
|
+
return Math.max(0, dateMs - Date.now());
|
|
20998
|
+
}
|
|
20999
|
+
return void 0;
|
|
21000
|
+
}
|
|
21001
|
+
function parseRateLimitResetMs(value) {
|
|
21002
|
+
const trimmed = value?.trim();
|
|
21003
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) {
|
|
21004
|
+
return void 0;
|
|
21005
|
+
}
|
|
21006
|
+
const seconds = Number(trimmed);
|
|
21007
|
+
const nowSeconds = Date.now() / 1e3;
|
|
21008
|
+
if (seconds > nowSeconds) {
|
|
21009
|
+
return Math.max(0, (seconds - nowSeconds) * 1e3);
|
|
21010
|
+
}
|
|
21011
|
+
return seconds * 1e3;
|
|
21012
|
+
}
|
|
21013
|
+
function computeSessionRetryDelayMs(response, attempt, random) {
|
|
21014
|
+
const headers = response?.headers;
|
|
21015
|
+
const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
|
|
21016
|
+
headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
|
|
21017
|
+
);
|
|
21018
|
+
if (signal !== void 0) {
|
|
21019
|
+
return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
|
|
21020
|
+
}
|
|
21021
|
+
const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
21022
|
+
return Math.round(random() * ceiling);
|
|
21023
|
+
}
|
|
20977
21024
|
function getMemoizedSessionIdentity() {
|
|
20978
21025
|
return memoizedSessionIdentity;
|
|
20979
21026
|
}
|
|
21027
|
+
function getSessionResolutionFailure() {
|
|
21028
|
+
return memoizedSessionFailure;
|
|
21029
|
+
}
|
|
20980
21030
|
function asRecord(value) {
|
|
20981
21031
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20982
21032
|
return void 0;
|
|
@@ -21045,46 +21095,90 @@ async function resolveSessionIdentity(opts) {
|
|
|
21045
21095
|
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21046
21096
|
let pending = sessionMemo.get(memoKey);
|
|
21047
21097
|
if (!pending) {
|
|
21048
|
-
pending = probeSessionIdentity(
|
|
21098
|
+
pending = probeSessionIdentity(
|
|
21099
|
+
baseUrl,
|
|
21100
|
+
accessToken,
|
|
21101
|
+
opts.fetchImpl ?? fetch,
|
|
21102
|
+
Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
|
|
21103
|
+
opts.sleepImpl ?? defaultSessionSleep,
|
|
21104
|
+
opts.randomImpl ?? defaultRandom
|
|
21105
|
+
);
|
|
21049
21106
|
sessionMemo.set(memoKey, pending);
|
|
21050
21107
|
}
|
|
21051
21108
|
return pending;
|
|
21052
21109
|
}
|
|
21053
|
-
async function
|
|
21110
|
+
async function parseSessionResponse(response) {
|
|
21111
|
+
let payload;
|
|
21054
21112
|
try {
|
|
21055
|
-
|
|
21056
|
-
method: "GET",
|
|
21057
|
-
headers: { "x-access-token": accessToken }
|
|
21058
|
-
});
|
|
21059
|
-
if (!response.ok) {
|
|
21060
|
-
return void 0;
|
|
21061
|
-
}
|
|
21062
|
-
const payload = asRecord(await response.json());
|
|
21063
|
-
if (!payload) {
|
|
21064
|
-
return void 0;
|
|
21065
|
-
}
|
|
21066
|
-
const root = asRecord(payload.session) ?? payload;
|
|
21067
|
-
const identity = asRecord(root.identity);
|
|
21068
|
-
const data = asRecord(root.data);
|
|
21069
|
-
const user = asRecord(data?.user);
|
|
21070
|
-
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21071
|
-
const singleRole = coerceText(user?.role);
|
|
21072
|
-
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21073
|
-
const resolved = {
|
|
21074
|
-
source: "iapub/sessions",
|
|
21075
|
-
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21076
|
-
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21077
|
-
teamId: coerceId(identity?.team),
|
|
21078
|
-
teamName: coerceText(user?.teamName),
|
|
21079
|
-
teamDomain: coerceText(identity?.domain),
|
|
21080
|
-
...roles ? { roles } : {},
|
|
21081
|
-
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21082
|
-
};
|
|
21083
|
-
memoizedSessionIdentity = resolved;
|
|
21084
|
-
return resolved;
|
|
21113
|
+
payload = asRecord(await response.json());
|
|
21085
21114
|
} catch {
|
|
21086
21115
|
return void 0;
|
|
21087
21116
|
}
|
|
21117
|
+
if (!payload) {
|
|
21118
|
+
return void 0;
|
|
21119
|
+
}
|
|
21120
|
+
const root = asRecord(payload.session) ?? payload;
|
|
21121
|
+
const identity = asRecord(root.identity);
|
|
21122
|
+
const data = asRecord(root.data);
|
|
21123
|
+
const user = asRecord(data?.user);
|
|
21124
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21125
|
+
const singleRole = coerceText(user?.role);
|
|
21126
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21127
|
+
return {
|
|
21128
|
+
source: "iapub/sessions",
|
|
21129
|
+
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21130
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21131
|
+
teamId: coerceId(identity?.team),
|
|
21132
|
+
teamName: coerceText(user?.teamName),
|
|
21133
|
+
teamDomain: coerceText(identity?.domain),
|
|
21134
|
+
...roles ? { roles } : {},
|
|
21135
|
+
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21136
|
+
};
|
|
21137
|
+
}
|
|
21138
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
|
|
21139
|
+
let failure = "unavailable";
|
|
21140
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
21141
|
+
let response;
|
|
21142
|
+
try {
|
|
21143
|
+
response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21144
|
+
method: "GET",
|
|
21145
|
+
headers: { "x-access-token": accessToken }
|
|
21146
|
+
});
|
|
21147
|
+
} catch {
|
|
21148
|
+
failure = "unavailable";
|
|
21149
|
+
if (attempt < maxAttempts) {
|
|
21150
|
+
await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
|
|
21151
|
+
continue;
|
|
21152
|
+
}
|
|
21153
|
+
break;
|
|
21154
|
+
}
|
|
21155
|
+
if (response.ok) {
|
|
21156
|
+
const resolved = await parseSessionResponse(response);
|
|
21157
|
+
if (resolved) {
|
|
21158
|
+
memoizedSessionIdentity = resolved;
|
|
21159
|
+
memoizedSessionFailure = void 0;
|
|
21160
|
+
return resolved;
|
|
21161
|
+
}
|
|
21162
|
+
failure = "unavailable";
|
|
21163
|
+
break;
|
|
21164
|
+
}
|
|
21165
|
+
if (response.status === 401 || response.status === 403) {
|
|
21166
|
+
failure = "auth";
|
|
21167
|
+
break;
|
|
21168
|
+
}
|
|
21169
|
+
if (response.status === 429 || response.status >= 500) {
|
|
21170
|
+
failure = "unavailable";
|
|
21171
|
+
if (attempt < maxAttempts) {
|
|
21172
|
+
await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
|
|
21173
|
+
continue;
|
|
21174
|
+
}
|
|
21175
|
+
break;
|
|
21176
|
+
}
|
|
21177
|
+
failure = "unavailable";
|
|
21178
|
+
break;
|
|
21179
|
+
}
|
|
21180
|
+
memoizedSessionFailure = failure;
|
|
21181
|
+
return void 0;
|
|
21088
21182
|
}
|
|
21089
21183
|
function describeTeam(id) {
|
|
21090
21184
|
const label = id?.teamName ?? id?.teamDomain;
|
|
@@ -21177,7 +21271,9 @@ async function runCredentialPreflight(args) {
|
|
|
21177
21271
|
session = await resolveSessionIdentity({
|
|
21178
21272
|
iapubBaseUrl: args.iapubBaseUrl,
|
|
21179
21273
|
accessToken,
|
|
21180
|
-
fetchImpl: args.fetchImpl
|
|
21274
|
+
fetchImpl: args.fetchImpl,
|
|
21275
|
+
...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
|
|
21276
|
+
...args.randomImpl ? { randomImpl: args.randomImpl } : {}
|
|
21181
21277
|
});
|
|
21182
21278
|
} catch (error2) {
|
|
21183
21279
|
args.log.warning(
|
|
@@ -21197,11 +21293,20 @@ async function runCredentialPreflight(args) {
|
|
|
21197
21293
|
);
|
|
21198
21294
|
}
|
|
21199
21295
|
} else {
|
|
21296
|
+
const failure = getSessionResolutionFailure();
|
|
21297
|
+
const detail = failure === "auth" ? "the access token was rejected by iapub (401/403), so it is invalid or expired. Re-mint it with postman-resolve-service-token-action (or POST https://api.getpostman.com/service-account-tokens) and re-run." : "iapub was unreachable after retries (network or 5xx). This is usually transient; re-run the job.";
|
|
21298
|
+
const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
|
|
21299
|
+
if (args.mode === "enforce") {
|
|
21300
|
+
throw new Error(
|
|
21301
|
+
mask(
|
|
21302
|
+
`${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
|
|
21303
|
+
)
|
|
21304
|
+
);
|
|
21305
|
+
}
|
|
21200
21306
|
args.log.warning(
|
|
21201
|
-
mask(
|
|
21202
|
-
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21203
|
-
)
|
|
21307
|
+
mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
|
|
21204
21308
|
);
|
|
21309
|
+
return;
|
|
21205
21310
|
}
|
|
21206
21311
|
const result = crossCheckIdentities({
|
|
21207
21312
|
pmak,
|
|
@@ -21613,6 +21718,53 @@ var AccessTokenProvider = class {
|
|
|
21613
21718
|
return token;
|
|
21614
21719
|
}
|
|
21615
21720
|
};
|
|
21721
|
+
async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
|
|
21722
|
+
const raw = mintError instanceof Error ? mintError.message : String(mintError);
|
|
21723
|
+
const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
|
|
21724
|
+
if (!rejected) {
|
|
21725
|
+
return raw;
|
|
21726
|
+
}
|
|
21727
|
+
try {
|
|
21728
|
+
const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
|
|
21729
|
+
if (me.ok) {
|
|
21730
|
+
const body = await me.json().catch(() => void 0);
|
|
21731
|
+
const user = body?.user;
|
|
21732
|
+
const looksPersonal = Boolean(user && (user.username || user.email));
|
|
21733
|
+
if (looksPersonal) {
|
|
21734
|
+
return "Personal API key detected, cannot mint a service-account access token. POST /service-account-tokens only accepts a SERVICE-ACCOUNT API key; this postman-api-key belongs to a user account" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". Create a service account in Team Settings and use its PMAK, or mint the token elsewhere and pass postman-access-token.";
|
|
21735
|
+
}
|
|
21736
|
+
return "The postman-api-key authenticates (GET /me OK) but was rejected by POST /service-account-tokens" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". The service account likely lacks permission to mint access tokens, or service accounts are restricted for this team. Check the service account role in Team Settings, or pass a pre-minted postman-access-token.";
|
|
21737
|
+
}
|
|
21738
|
+
return "The postman-api-key is invalid, disabled, or expired (rejected by both POST /service-account-tokens and GET /me). Generate a fresh service-account PMAK in Team Settings and update the secret.";
|
|
21739
|
+
} catch {
|
|
21740
|
+
return raw;
|
|
21741
|
+
}
|
|
21742
|
+
}
|
|
21743
|
+
async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
|
|
21744
|
+
if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
|
|
21745
|
+
return;
|
|
21746
|
+
}
|
|
21747
|
+
const apiBaseUrl = String(
|
|
21748
|
+
inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
|
|
21749
|
+
).replace(/\/+$/, "");
|
|
21750
|
+
const provider = new AccessTokenProvider({
|
|
21751
|
+
apiKey: inputs.postmanApiKey,
|
|
21752
|
+
apiBaseUrl,
|
|
21753
|
+
fetchImpl,
|
|
21754
|
+
onToken: (token) => setSecret2?.(token)
|
|
21755
|
+
});
|
|
21756
|
+
try {
|
|
21757
|
+
inputs.postmanAccessToken = await provider.refresh();
|
|
21758
|
+
log.info(
|
|
21759
|
+
"postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
|
|
21760
|
+
);
|
|
21761
|
+
} catch (error2) {
|
|
21762
|
+
const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
|
|
21763
|
+
log.warning(
|
|
21764
|
+
"postman: could not mint an access token from the postman-api-key. " + diagnosis + " Continuing without an access token - access-token-only functionality will be unavailable unless postman-access-token is provided."
|
|
21765
|
+
);
|
|
21766
|
+
}
|
|
21767
|
+
}
|
|
21616
21768
|
|
|
21617
21769
|
// src/lib/bifrost-client.ts
|
|
21618
21770
|
var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
|
|
@@ -22271,7 +22423,7 @@ function resolveActionVersion(explicit) {
|
|
|
22271
22423
|
if (explicit) {
|
|
22272
22424
|
return explicit;
|
|
22273
22425
|
}
|
|
22274
|
-
return "
|
|
22426
|
+
return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
|
|
22275
22427
|
}
|
|
22276
22428
|
function telemetryDisabled(env) {
|
|
22277
22429
|
const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
|
|
@@ -22393,6 +22545,18 @@ function createTelemetryContext(options) {
|
|
|
22393
22545
|
};
|
|
22394
22546
|
}
|
|
22395
22547
|
|
|
22548
|
+
// src/action-version.ts
|
|
22549
|
+
var import_node_fs = require("node:fs");
|
|
22550
|
+
var import_node_path = require("node:path");
|
|
22551
|
+
function resolveActionVersion2() {
|
|
22552
|
+
try {
|
|
22553
|
+
const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
|
|
22554
|
+
return JSON.parse(raw).version ?? "unknown";
|
|
22555
|
+
} catch {
|
|
22556
|
+
return "unknown";
|
|
22557
|
+
}
|
|
22558
|
+
}
|
|
22559
|
+
|
|
22396
22560
|
// src/index.ts
|
|
22397
22561
|
var POLL_TIMEOUT_MIN = 10;
|
|
22398
22562
|
var POLL_TIMEOUT_MAX = 600;
|
|
@@ -22458,8 +22622,12 @@ function resolveInputs(env = process.env) {
|
|
|
22458
22622
|
const projectName = get("project-name");
|
|
22459
22623
|
if (!projectName) throw new Error("project-name is required");
|
|
22460
22624
|
const postmanAccessToken = get("postman-access-token");
|
|
22461
|
-
if (!postmanAccessToken) throw new Error("postman-access-token is required");
|
|
22462
22625
|
const postmanApiKey = get("postman-api-key");
|
|
22626
|
+
if (!postmanAccessToken && !postmanApiKey) {
|
|
22627
|
+
throw new Error(
|
|
22628
|
+
"postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
|
|
22629
|
+
);
|
|
22630
|
+
}
|
|
22463
22631
|
const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
|
|
22464
22632
|
const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
|
|
22465
22633
|
if (!workspaceId) {
|
|
@@ -22699,7 +22867,14 @@ async function runAction() {
|
|
|
22699
22867
|
for (const [key, value] of Object.entries(planned)) {
|
|
22700
22868
|
setOutput(key, value);
|
|
22701
22869
|
}
|
|
22702
|
-
|
|
22870
|
+
const mintHolder = {
|
|
22871
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22872
|
+
postmanApiKey: inputs.postmanApiKey,
|
|
22873
|
+
postmanApiBase: inputs.postmanApiBase
|
|
22874
|
+
};
|
|
22875
|
+
await mintAccessTokenIfNeeded(mintHolder, core_exports, (secret) => setSecret(secret));
|
|
22876
|
+
inputs.postmanAccessToken = mintHolder.postmanAccessToken;
|
|
22877
|
+
if (inputs.postmanAccessToken) setSecret(inputs.postmanAccessToken);
|
|
22703
22878
|
if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
|
|
22704
22879
|
if (inputs.githubToken) setSecret(inputs.githubToken);
|
|
22705
22880
|
const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
|
|
@@ -22716,7 +22891,7 @@ async function runAction() {
|
|
|
22716
22891
|
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22717
22892
|
onToken: (token) => setSecret(token)
|
|
22718
22893
|
}) : tokenProvider;
|
|
22719
|
-
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
|
|
22894
|
+
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
|
|
22720
22895
|
telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
|
|
22721
22896
|
let result;
|
|
22722
22897
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postman-cse/onboarding-insights",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
"node": ">=24"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"prepare": "git config core.hooksPath .githooks",
|
|
19
|
+
"build": "npm run typecheck && rm -rf dist && esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/index.cjs && esbuild src/main.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/action.cjs && esbuild src/cli.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/cli.cjs",
|
|
20
|
+
"verify:dist": "npm run build && git diff --ignore-space-at-eol --text --exit-code -- dist",
|
|
20
21
|
"docs:tables": "node scripts/render-action-tables.mjs --write",
|
|
21
22
|
"lint": "eslint .",
|
|
22
23
|
"lint:fix": "eslint . --fix",
|
|
@@ -46,8 +47,8 @@
|
|
|
46
47
|
"@commitlint/cli": "^21.0.2",
|
|
47
48
|
"@commitlint/config-conventional": "^21.0.2",
|
|
48
49
|
"@eslint/js": "^10.0.1",
|
|
49
|
-
"@types/node": "^
|
|
50
|
-
"esbuild": "
|
|
50
|
+
"@types/node": "^26.1.0",
|
|
51
|
+
"esbuild": "0.28.1",
|
|
51
52
|
"eslint": "^10.4.1",
|
|
52
53
|
"typescript": "^6.0.3",
|
|
53
54
|
"typescript-eslint": "^8.60.0",
|