@postman-cse/onboarding-repo-sync 1.0.0 → 1.0.2

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
@@ -1,22 +1,10 @@
1
- # postman-repo-sync-action
1
+ # Postman Repo Sync
2
2
 
3
- [![CI](https://github.com/postman-cs/postman-repo-sync-action/actions/workflows/ci.yml/badge.svg)](https://github.com/postman-cs/postman-repo-sync-action/actions/workflows/ci.yml)
4
- [![Release](https://img.shields.io/github/v/release/postman-cs/postman-repo-sync-action?sort=semver)](https://github.com/postman-cs/postman-repo-sync-action/releases)
5
- [![npm](https://img.shields.io/npm/v/%40postman-cse%2Fonboarding-repo-sync)](https://www.npmjs.com/package/@postman-cse/onboarding-repo-sync)
6
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
3
+ [![CI](https://github.com/postman-cs/postman-repo-sync-action/actions/workflows/ci.yml/badge.svg)](https://github.com/postman-cs/postman-repo-sync-action/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/postman-cs/postman-repo-sync-action?sort=semver)](https://github.com/postman-cs/postman-repo-sync-action/releases) [![npm](https://img.shields.io/npm/v/%40postman-cse%2Fonboarding-repo-sync)](https://www.npmjs.com/package/@postman-cse/onboarding-repo-sync) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/postman-cs/postman-repo-sync-action/badge)](https://scorecard.dev/viewer/?uri=github.com/postman-cs/postman-repo-sync-action)
7
4
 
8
- Sync Postman workspace assets into a repository and optionally connect the workspace back to that repository.
5
+ Exports Postman collections and environments into your repository and wires CI, mocks, and monitors around them.
9
6
 
10
- Use this action when a repo should contain the Postman artifacts needed for CI, reviews, and repeatable API test runs:
11
-
12
- - Postman Collection v3 multi-file YAML exports under `postman/collections/`.
13
- - Postman environment exports under `postman/environments/`.
14
- - `.postman/resources.yaml` with local-to-cloud resource mappings.
15
- - Optional `.postman/workflows.yaml` spec-to-collection metadata.
16
- - Optional generated GitHub Actions workflow for Postman CLI smoke and contract runs.
17
- - Optional mock server, cloud monitor, workspace repository link, and system environment associations.
18
-
19
- ## Quick Start
7
+ ## Usage
20
8
 
21
9
  ```yaml
22
10
  jobs:
@@ -31,219 +19,199 @@ jobs:
31
19
  - uses: postman-cs/postman-repo-sync-action@v1
32
20
  with:
33
21
  project-name: core-payments
34
- workspace-id: ws-123
35
- baseline-collection-id: col-baseline
36
- smoke-collection-id: col-smoke
37
- contract-collection-id: col-contract
38
- environments-json: '["prod","stage"]'
39
- env-runtime-urls-json: '{"prod":"https://api.example.com","stage":"https://stage-api.example.com"}'
40
22
  postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
41
- postman-access-token: ${{ secrets.POSTMAN_ACCESS_TOKEN }}
42
23
  github-token: ${{ secrets.GITHUB_TOKEN }}
43
24
  ```
44
25
 
45
- For existing repositories that already own their CI workflow, disable workflow generation:
26
+ `actions/checkout` must run before this action. `project-name` is the only required input; workspace and collection IDs are resolved from `.postman/resources.yaml` when the repo already carries one.
46
27
 
47
- ```yaml
48
- with:
49
- generate-ci-workflow: false
50
- ```
28
+ ## Examples
51
29
 
52
- Or write the generated workflow somewhere other than `.github/workflows/ci.yml`:
30
+ ### Full sync with workspace assets
53
31
 
54
32
  ```yaml
55
- with:
56
- ci-workflow-path: .github/workflows/postman-sync.yml
33
+ - uses: postman-cs/postman-repo-sync-action@v1
34
+ with:
35
+ project-name: core-payments
36
+ workspace-id: ws-123
37
+ baseline-collection-id: col-baseline
38
+ smoke-collection-id: col-smoke
39
+ contract-collection-id: col-contract
40
+ environments-json: '["prod","stage"]'
41
+ env-runtime-urls-json: '{"prod":"https://api.example.com","stage":"https://stage-api.example.com"}'
42
+ postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
43
+ postman-access-token: ${{ secrets.POSTMAN_ACCESS_TOKEN }}
44
+ github-token: ${{ secrets.GITHUB_TOKEN }}
57
45
  ```
58
46
 
59
- ## Requirements
47
+ `postman-access-token` is required for workspace-to-repository linking, system environment association, and API key generation. If it is omitted, those integration steps are skipped and the remaining artifact sync can still run with `postman-api-key`. See [docs/credentials.md](docs/credentials.md).
60
48
 
61
- - `actions/checkout` must run before this action.
62
- - `project-name` is required.
63
- - Provide a valid `postman-api-key`, or provide `postman-access-token` so the action can generate one.
64
- - `contents: write` is required when `repo-write-mode` commits files.
65
- - `actions: write` is required when the action writes workflow files.
66
- - `gh-fallback-token` is recommended when the default `GITHUB_TOKEN` cannot update workflow files or repository secrets.
49
+ ### Disable CI workflow generation
67
50
 
68
- `postman-access-token` is required for workspace-to-repository linking, system environment association, and API key generation. If it is omitted, those integration steps are skipped and the remaining artifact sync can still run with `postman-api-key`.
69
-
70
- ## What Gets Written
51
+ For existing repositories that already own their CI workflow, disable workflow generation:
71
52
 
72
- The default artifact root is `postman/`. The action ensures these directories exist:
53
+ ```yaml
54
+ with:
55
+ generate-ci-workflow: false
56
+ ```
73
57
 
74
- - `postman/collections`
75
- - `postman/environments`
76
- - `postman/flows`
77
- - `postman/globals`
78
- - `postman/mocks`
79
- - `postman/specs`
58
+ ### Custom CI workflow path
80
59
 
81
- Collections are exported as Collection v3 multi-file YAML directories, for example:
60
+ Write the generated workflow somewhere other than `.github/workflows/ci.yml`:
82
61
 
83
- ```text
84
- postman/collections/[Smoke] core-payments/
85
- collection.yaml
86
- <folder>.yaml
87
- <request>.yaml
62
+ ```yaml
63
+ with:
64
+ ci-workflow-path: .github/workflows/postman-sync.yml
88
65
  ```
89
66
 
90
- The action also writes `.postman/resources.yaml`. The generated CI workflow reads that file to resolve smoke collection, contract collection, and environment IDs for Postman CLI runs.
91
-
92
- Long Postman folder and request names are truncated to 120 characters per path segment when files are written.
67
+ ### Commit-only mode for protected branches
93
68
 
94
- When a local OpenAPI spec is found, `.postman/resources.yaml` records it under `localResources.specs`. If `spec-id` and an unambiguous local spec are available, the action also maps the spec under `cloudResources.specs`. When a mapped spec and exported collections are both present, `.postman/workflows.yaml` is written with `syncSpecToCollection` metadata.
69
+ If branch protection requires pull requests, run on a temporary branch with commit-only writes, then create the PR in a later workflow step. Use this for customer-managed PR workflows.
95
70
 
96
- ## Repository Writes
71
+ ```yaml
72
+ with:
73
+ repo-write-mode: commit-only
74
+ ```
97
75
 
98
- `repo-write-mode` controls repository mutation:
76
+ `repo-write-mode` options:
99
77
 
100
78
  | Mode | Behavior |
101
79
  | --- | --- |
102
80
  | `commit-and-push` | Commit generated files and push them back to the current checked out ref. |
103
- | `commit-only` | Commit generated files without pushing. Use this for customer-managed PR workflows. |
81
+ | `commit-only` | Commit generated files without pushing. |
104
82
  | `none` | Write files in the workspace only. |
105
83
 
106
- For `commit-and-push`, the push target is resolved from `current-ref`, then `GITHUB_HEAD_REF`, then `GITHUB_REF_NAME`. Pull request merge refs are normalized to the PR head branch. Pushes use `HEAD:refs/heads/<resolved-branch>`.
107
-
108
- If branch protection requires pull requests, run on a temporary branch with `repo-write-mode: commit-only`, then create the PR in a later workflow step.
109
-
110
- ## Sync Modes
111
-
112
- `collection-sync-mode` controls collection lifecycle:
113
-
114
- | Mode | Behavior |
115
- | --- | --- |
116
- | `refresh` | Refresh exports and rewrite resource mappings for the current ref. |
117
- | `reuse` | Reuse explicit IDs or IDs already present in `.postman/resources.yaml`. |
118
- | `version` | Require a release label and suffix exported collection directories, mock names, and monitor names with that label. |
119
-
120
- `spec-sync-mode` supports:
121
-
122
- | Mode | Behavior |
123
- | --- | --- |
124
- | `update` | Keep the current spec mapping updated. |
125
- | `version` | Require a release label and use versioned metadata. |
126
-
127
- If either mode is `version`, provide `release-label` or run on a ref name that can be used as the release label.
128
-
129
- ## Monitoring And Mocks
130
-
131
- When `baseline-collection-id`, `workspace-id`, and at least one environment are available, the action creates or reuses a mock server.
132
-
133
- When `smoke-collection-id`, `workspace-id`, and at least one environment are available, the action creates or reuses a cloud smoke monitor unless `monitor-type: cli` is set. If `monitor-cron` is empty, a new cloud monitor is created disabled.
84
+ ### Reuse an existing mock server and monitor
134
85
 
135
- Use `mock-url` or `monitor-id` to force reuse of existing assets.
86
+ Pass `mock-url` or `monitor-id` to validate and reuse existing assets instead of creating new ones:
136
87
 
137
- ## mTLS
138
-
139
- The generated CI workflow can run Postman CLI with client certificates. Set these GitHub repository secrets:
140
-
141
- - `POSTMAN_SSL_CLIENT_CERT_B64`
142
- - `POSTMAN_SSL_CLIENT_KEY_B64`
143
- - `POSTMAN_SSL_CLIENT_PASSPHRASE` (optional)
144
- - `POSTMAN_SSL_EXTRA_CA_CERTS_B64` (optional)
145
-
146
- You can also pass the matching action inputs. When `ssl-client-cert` is provided and a GitHub token/repository context is available, the action attempts to persist those values as repository secrets for the generated workflow.
147
-
148
- ## CLI
88
+ ```yaml
89
+ with:
90
+ mock-url: https://abc123.mock.pstmn.io
91
+ monitor-id: 1e2f3a4b-monitor-id
92
+ ```
149
93
 
150
- The package also ships a `postman-repo-sync` binary for GitLab CI, Bitbucket Pipelines, Azure DevOps, and other CI systems.
94
+ ### mTLS certificates for Postman CLI runs
151
95
 
152
- ```bash
153
- npm install -g @postman-cse/onboarding-repo-sync
96
+ The generated CI workflow can run Postman CLI with client certificates. Pass the cert material as inputs; when a GitHub token and repository context are available, the action persists them as repository secrets (`POSTMAN_SSL_CLIENT_CERT_B64`, `POSTMAN_SSL_CLIENT_KEY_B64`, `POSTMAN_SSL_CLIENT_PASSPHRASE`, `POSTMAN_SSL_EXTRA_CA_CERTS_B64`) for the generated workflow:
154
97
 
155
- postman-repo-sync \
156
- --project-name core-payments \
157
- --workspace-id ws-123 \
158
- --baseline-collection-id col-baseline \
159
- --smoke-collection-id col-smoke \
160
- --contract-collection-id col-contract \
161
- --postman-api-key "$POSTMAN_API_KEY" \
162
- --result-json postman-repo-sync-result.json \
163
- --dotenv-path postman-repo-sync.env \
164
- --repo-write-mode commit-only
98
+ ```yaml
99
+ with:
100
+ ssl-client-cert: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 }}
101
+ ssl-client-key: ${{ secrets.POSTMAN_SSL_CLIENT_KEY_B64 }}
102
+ ssl-client-passphrase: ${{ secrets.POSTMAN_SSL_CLIENT_PASSPHRASE }}
165
103
  ```
166
104
 
167
- The CLI auto-detects repository URL, branch, and SHA from common CI environment variables. It writes JSON to stdout, writes the same JSON to `--result-json`, and optionally writes shell-sourceable `POSTMAN_REPO_SYNC_*` values to `--dotenv-path`. Logs go to stderr.
168
-
169
105
  ## Inputs
170
106
 
171
- | Input | Default | Notes |
172
- | --- | --- | --- |
173
- | `project-name` | | Required. Service name used for environments, mock servers, and monitors. |
174
- | `workspace-id` | | Postman workspace ID. Can be resolved from `.postman/resources.yaml` when available. |
175
- | `baseline-collection-id` | | Baseline collection exported into the repo and used for mock server creation. |
176
- | `smoke-collection-id` | | Smoke collection used for monitor creation and generated CI. |
177
- | `contract-collection-id` | | Contract collection exported into the repo and used for generated CI. |
178
- | `environments-json` | `["prod"]` | JSON array of environment slugs to create or update. |
179
- | `env-runtime-urls-json` | `{}` | JSON map of environment slug to runtime base URL. |
180
- | `environment-uids-json` | `{}` | JSON map of environment slug to existing Postman environment UID. |
181
- | `system-env-map-json` | `{}` | JSON map of environment slug to system environment ID. |
182
- | `repo-url` | Auto-detected | Explicit repository URL for workspace linking. Auto-detected from common GitHub, GitLab, Bitbucket, and Azure DevOps CI variables when available. |
183
- | `artifact-dir` | `postman` | Root directory for exported artifacts. |
184
- | `repo-write-mode` | `commit-and-push` | `commit-and-push`, `commit-only`, or `none`. |
185
- | `current-ref` | | Explicit branch/ref override for push target resolution. |
186
- | `generate-ci-workflow` | `true` | Whether to write the generated CI workflow. |
187
- | `ci-workflow-path` | `.github/workflows/ci.yml` | Path for the generated CI workflow. |
188
- | `ci-workflow-base64` | | Base64-encoded workflow content that replaces the built-in template. |
189
- | `collection-sync-mode` | `refresh` | `refresh`, `reuse`, or `version`. |
190
- | `spec-sync-mode` | `update` | `update` or `version`. |
191
- | `release-label` | | Label used for versioned collection/spec sync. |
192
- | `spec-id` | | Cloud spec UID to persist in `.postman/resources.yaml`. |
193
- | `spec-path` | | Repo-root-relative local spec path to use for metadata. |
194
- | `monitor-type` | `cloud` | `cloud` creates/reuses a cloud monitor; `cli` skips cloud monitor creation. |
195
- | `monitor-id` | | Existing smoke monitor ID to validate and reuse. |
196
- | `mock-url` | | Existing mock server URL to reuse. |
197
- | `monitor-cron` | | Cron expression for cloud monitor scheduling. Empty creates a disabled monitor. |
198
- | `workspace-link-enabled` | `true` | Enable workspace-to-repository linking. Requires `postman-access-token`. |
199
- | `environment-sync-enabled` | `true` | Enable system environment association. Requires `postman-access-token`. |
200
- | `integration-backend` | `bifrost` | Backend used for workspace linking and environment association. |
201
- | `org-mode` | `false` | Include `x-entity-team-id` for org-mode Bifrost calls. |
202
- | `postman-api-key` | | Postman API key for standard Postman API operations. |
203
- | `postman-access-token` | | Postman session access token for integration operations and API key generation. |
204
- | `github-token` | | Token for commits, pushes, workflow updates, and secret persistence. |
205
- | `gh-fallback-token` | | Preferred fallback token for workflow-file pushes and secret persistence. |
206
- | `committer-name` | `Postman CSE` | Git committer name for sync commits. |
207
- | `committer-email` | `help@postman.com` | Git committer email for sync commits. |
208
- | `ssl-client-cert` | | Base64-encoded PEM client certificate. |
209
- | `ssl-client-key` | | Base64-encoded PEM private key. Required with `ssl-client-cert`. |
210
- | `ssl-client-passphrase` | | Optional private-key passphrase. |
211
- | `ssl-extra-ca-certs` | | Optional base64-encoded PEM CA bundle. |
107
+ <!-- inputs-table:start -->
108
+ | Name | Description | Required | Default |
109
+ | --- | --- | --- | --- |
110
+ | `generate-ci-workflow` | Whether to generate the CI workflow file | no | `true` |
111
+ | `ci-workflow-path` | Path to write the generated CI workflow file | no | `.github/workflows/ci.yml` |
112
+ | `project-name` | Service project name used for environment, mock, and monitor naming. | yes | |
113
+ | `workspace-id` | Postman workspace ID used for workspace-link and export metadata. | no | |
114
+ | `baseline-collection-id` | Baseline collection ID used for exported artifacts and mock server creation. | no | |
115
+ | `monitor-type` | Type of monitor to create ("cloud" or "cli"). "cli" will skip cloud monitor creation and rely on the CI workflow. | no | `cloud` |
116
+ | `smoke-collection-id` | Smoke collection ID used for monitor creation. | no | |
117
+ | `contract-collection-id` | Contract collection ID used for exported artifacts. | no | |
118
+ | `collection-sync-mode` | Collection sync lifecycle mode (refresh or version). | no | `refresh` |
119
+ | `spec-sync-mode` | Spec sync lifecycle mode (update or version). | no | `update` |
120
+ | `release-label` | Optional release label used for versioned naming. | no | |
121
+ | `monitor-id` | Existing smoke monitor ID. When set, the action validates and reuses this monitor instead of creating a new one. | no | |
122
+ | `mock-url` | Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one. | no | |
123
+ | `monitor-cron` | Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run). | no | `""` |
124
+ | `environments-json` | JSON array of environment slugs to create or update. | no | `["prod"]` |
125
+ | `repo-url` | Explicit repository URL (GitHub or GitLab). Defaults to https://github.com/$GITHUB_REPOSITORY on GitHub Actions, or $CI_PROJECT_URL on GitLab CI, when omitted. | no | |
126
+ | `integration-backend` | Integration backend for workspace linking and environment sync. | no | `bifrost` |
127
+ | `workspace-link-enabled` | Enable workspace linking. | no | `true` |
128
+ | `environment-sync-enabled` | Enable association of Postman environments to system environments. | no | `true` |
129
+ | `system-env-map-json` | JSON map of environment slug to system environment id. | no | `{}` |
130
+ | `environment-uids-json` | JSON map of environment slug to Postman environment uid. | no | `{}` |
131
+ | `env-runtime-urls-json` | JSON map of environment slug to runtime base URL. | no | `{}` |
132
+ | `artifact-dir` | Root directory for exported Postman artifacts. | no | `postman` |
133
+ | `repo-write-mode` | Repo mutation mode for generated artifacts and workflow files. | no | `commit-and-push` |
134
+ | `current-ref` | Explicit ref override for push-changes when the checkout is detached. | no | |
135
+ | `committer-name` | Git committer name for sync commits. | no | `Postman CSE` |
136
+ | `committer-email` | Git committer email for sync commits. | no | `help@postman.com` |
137
+ | `postman-api-key` | Postman API key used for environment, mock, and monitor operations. | no | |
138
+ | `postman-access-token` | Postman access token used for Bifrost and system environment association. | no | |
139
+ | `credential-preflight` | Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created; off skips the identity probes entirely (the reactive error guidance still applies). Promotion of the default to enforce is planned once the live e2e legs prove both directions. | no | `warn` |
140
+ | `github-token` | GitHub token used for repo variable persistence and commits. | no | |
141
+ | `gh-fallback-token` | Fallback token for repository variable APIs and workflow-file pushes. | no | |
142
+ | `org-mode` | Whether the Postman team uses org-mode. When true, x-entity-team-id header is included in Bifrost proxy calls. Non-org teams must omit this header. | no | `false` |
143
+ | `ci-workflow-base64` | Optional base64-encoded ci.yml content. Defaults to the built-in template. | no | |
144
+ | `ssl-client-cert` | Base64-encoded PEM client certificate for Postman CLI mTLS runs. | no | |
145
+ | `ssl-client-key` | Base64-encoded PEM client private key for Postman CLI mTLS runs. | no | |
146
+ | `ssl-client-passphrase` | Optional passphrase for encrypted ssl-client-key. | no | |
147
+ | `ssl-extra-ca-certs` | Optional base64-encoded PEM CA certificate bundle for custom trust. | no | |
148
+ | `spec-id` | Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources. | no | |
149
+ | `spec-path` | Optional repo-root-relative path to the local spec file for resources/workflows metadata. | no | |
150
+ | `postman-stack` | Postman stack profile. | no | `prod` |
151
+ <!-- inputs-table:end -->
212
152
 
213
153
  ## Outputs
214
154
 
215
- | Output | Meaning |
155
+ <!-- outputs-table:start -->
156
+ | Name | Description |
216
157
  | --- | --- |
217
- | `integration-backend` | Resolved integration backend. |
218
- | `resolved-current-ref` | Branch used as the push target for `commit-and-push`. |
219
- | `workspace-link-status` | `success`, `skipped`, or `failed`. |
220
- | `environment-sync-status` | `success`, `skipped`, or `failed`. |
221
- | `environment-uids-json` | JSON map of environment slug to Postman environment UID. |
158
+ | `integration-backend` | Resolved integration backend for the customer preview run. |
159
+ | `resolved-current-ref` | Resolved push target based on current-ref semantics. |
160
+ | `workspace-link-status` | Whether workspace linking succeeded, was skipped, or failed. |
161
+ | `environment-sync-status` | Whether environment sync succeeded, was skipped, or failed. |
162
+ | `environment-uids-json` | JSON map of environment slug to Postman environment uid. |
222
163
  | `mock-url` | Created or reused mock server URL. |
223
164
  | `monitor-id` | Created or reused smoke monitor ID. |
224
- | `repo-sync-summary-json` | JSON summary of commit, environment, mock, monitor, push, and integration state. |
225
- | `commit-sha` | Commit SHA produced by `repo-write-mode`, when a commit is created. |
165
+ | `repo-sync-summary-json` | JSON summary of repo materialization and workspace sync outputs. |
166
+ | `commit-sha` | Commit SHA produced by repo-write-mode, if any. |
167
+ <!-- outputs-table:end -->
226
168
 
227
- ## Credentials
169
+ ## How it works
228
170
 
229
- Create a Postman API key in Postman under **Settings -> Account Settings -> API Keys**, then store it as `POSTMAN_API_KEY`.
171
+ The action syncs a Postman workspace into the checked-out repository and can connect the workspace back to that repository:
230
172
 
231
- The `postman-access-token` value is a session token used for integration APIs that are not covered by PMAK. To obtain it:
173
+ - Postman Collection v3 multi-file YAML exports under `postman/collections/`.
174
+ - Postman environment exports under `postman/environments/`.
175
+ - `.postman/resources.yaml` with local-to-cloud resource mappings.
176
+ - Optional `.postman/workflows.yaml` spec-to-collection metadata.
177
+ - Optional generated GitHub Actions workflow for Postman CLI smoke and contract runs.
178
+ - Optional mock server, cloud monitor, workspace repository link, and system environment associations.
232
179
 
233
- ```bash
234
- postman login
235
- cat ~/.postman/postmanrc | jq -r '.login._profiles[].accessToken'
180
+ A typical export looks like:
181
+
182
+ ```text
183
+ postman/collections/[Smoke] core-payments/
184
+ collection.yaml
185
+ <folder>.yaml
186
+ <request>.yaml
187
+ postman/environments/
188
+ prod.postman_environment.json
189
+ .postman/
190
+ resources.yaml
236
191
  ```
237
192
 
238
- Store that value as `POSTMAN_ACCESS_TOKEN`. It expires with the Postman session and must be refreshed when integration steps start skipping or failing because of authentication.
193
+ For `commit-and-push`, the push target is resolved from `current-ref`, then `GITHUB_HEAD_REF`, then `GITHUB_REF_NAME`. Pull request merge refs are normalized to the PR head branch.
239
194
 
240
- ## Local Development
195
+ Mocks and monitors: when `baseline-collection-id`, `workspace-id`, and at least one environment are available, the action creates or reuses a mock server. When `smoke-collection-id` is also available, it creates or reuses a cloud smoke monitor unless `monitor-type: cli` is set. With an empty `monitor-cron`, a new cloud monitor is created disabled and triggered once per workflow invocation.
241
196
 
242
- ```bash
243
- npm install
244
- npm test
245
- npm run typecheck
246
- npm run build
247
- ```
197
+ Deeper reference:
198
+
199
+ - [Artifact layout and Collection v3 format](docs/artifact-layout.md), including sync modes and versioned releases.
200
+ - [Credentials](docs/credentials.md): `postman-api-key`, `postman-access-token`, credential preflight, GitHub tokens.
201
+ - [CLI usage](docs/cli.md): the `postman-repo-sync` binary for GitLab CI, Bitbucket Pipelines, and Azure DevOps.
202
+
203
+ ## Resources
204
+
205
+ - [postman-resolve-service-token-action](https://github.com/postman-cs/postman-resolve-service-token-action): mints a service-account access token and team ID.
206
+ - [postman-api-onboarding-action](https://github.com/postman-cs/postman-api-onboarding-action): composite action that orchestrates the onboarding pipeline.
207
+ - [postman-bootstrap-action](https://github.com/postman-cs/postman-bootstrap-action): workspace provisioning, spec upload, and collection generation.
208
+ - [postman-smoke-flow-action](https://github.com/postman-cs/postman-smoke-flow-action): applies a curated flow.yaml to the Smoke collection.
209
+ - [postman-insights-onboarding-action](https://github.com/postman-cs/postman-insights-onboarding-action): links Postman Insights to a workspace.
210
+ - [postman-aws-spec-discovery-action](https://github.com/postman-cs/postman-aws-spec-discovery-action): discovers API specs in AWS accounts.
211
+ - npm package: [@postman-cse/onboarding-repo-sync](https://www.npmjs.com/package/@postman-cse/onboarding-repo-sync)
212
+ - [Postman API documentation](https://learning.postman.com/docs/developer/postman-api/intro-api/)
213
+ - [Postman CLI](https://learning.postman.com/docs/postman-cli/postman-cli-overview/)
214
+
215
+ ## License
248
216
 
249
- `npm run build` produces the committed `dist/index.cjs` action bundle used by `action.yml` and `dist/cli.cjs` for the CLI.
217
+ [MIT](LICENSE)
package/action.yml CHANGED
@@ -1,5 +1,5 @@
1
- name: postman-repo-sync-action
2
- description: Sync exported Postman artifacts into a repository and expose workspace-link and environment-sync outputs.
1
+ name: Postman Repo Sync
2
+ description: Export Postman collections and environments into your repo and wire CI, mocks, and monitors around them.
3
3
  author: Postman
4
4
  branding:
5
5
  icon: refresh-cw
package/dist/action.cjs CHANGED
@@ -9182,14 +9182,14 @@ var require_retry_agent = __commonJS({
9182
9182
  this.#options = options;
9183
9183
  }
9184
9184
  dispatch(opts, handler) {
9185
- const retry = new RetryHandler({
9185
+ const retry2 = new RetryHandler({
9186
9186
  ...opts,
9187
9187
  retryOptions: this.#options
9188
9188
  }, {
9189
9189
  dispatch: this.#agent.dispatch.bind(this.#agent),
9190
9190
  handler
9191
9191
  });
9192
- return this.#agent.dispatch(opts, retry);
9192
+ return this.#agent.dispatch(opts, retry2);
9193
9193
  }
9194
9194
  close() {
9195
9195
  return this.#agent.close();
@@ -25282,12 +25282,60 @@ var postmanRepoSyncActionContract = {
25282
25282
  }
25283
25283
  };
25284
25284
 
25285
+ // src/lib/retry.ts
25286
+ function sleep(delayMs) {
25287
+ return new Promise((resolve3) => {
25288
+ setTimeout(resolve3, delayMs);
25289
+ });
25290
+ }
25291
+ function normalizeRetryOptions(options) {
25292
+ return {
25293
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
25294
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
25295
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
25296
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
25297
+ onRetry: options.onRetry ?? (async () => void 0),
25298
+ shouldRetry: options.shouldRetry ?? (() => true),
25299
+ sleep: options.sleep ?? sleep
25300
+ };
25301
+ }
25302
+ async function retry(operation, options = {}) {
25303
+ const normalized = normalizeRetryOptions(options);
25304
+ let nextDelayMs = normalized.delayMs;
25305
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
25306
+ try {
25307
+ return await operation();
25308
+ } catch (error2) {
25309
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
25310
+ attempt,
25311
+ maxAttempts: normalized.maxAttempts
25312
+ });
25313
+ if (!shouldRetry) {
25314
+ throw error2;
25315
+ }
25316
+ await normalized.onRetry({
25317
+ attempt,
25318
+ maxAttempts: normalized.maxAttempts,
25319
+ delayMs: nextDelayMs,
25320
+ error: error2
25321
+ });
25322
+ await normalized.sleep(nextDelayMs);
25323
+ nextDelayMs = Math.min(
25324
+ normalized.maxDelayMs,
25325
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
25326
+ );
25327
+ }
25328
+ }
25329
+ throw new Error("Retry exhausted without returning or throwing");
25330
+ }
25331
+
25285
25332
  // src/lib/postman/postman-assets-client.ts
25286
25333
  var PostmanAssetsClient = class {
25287
25334
  apiKey;
25288
25335
  baseUrl;
25289
25336
  bifrostBaseUrl;
25290
25337
  fetchImpl;
25338
+ retrySleep;
25291
25339
  constructor(options) {
25292
25340
  this.apiKey = String(options.apiKey || "").trim();
25293
25341
  this.baseUrl = String(options.baseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl).replace(
@@ -25298,6 +25346,7 @@ var PostmanAssetsClient = class {
25298
25346
  options.bifrostBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl
25299
25347
  ).replace(/\/+$/, "");
25300
25348
  this.fetchImpl = options.fetchImpl ?? fetch;
25349
+ this.retrySleep = options.retrySleep;
25301
25350
  void (options.secretMasker ?? createSecretMasker([this.apiKey]));
25302
25351
  }
25303
25352
  getBaseUrl() {
@@ -25361,9 +25410,34 @@ var PostmanAssetsClient = class {
25361
25410
  })
25362
25411
  });
25363
25412
  }
25413
+ /**
25414
+ * Monitor and mock creation reference a collection that may have been
25415
+ * created moments earlier; the Postman backend is eventually consistent
25416
+ * and can reject the reference with a 400 "Unable to load collection"
25417
+ * until the collection becomes visible. Retry that specific 400 plus
25418
+ * generic transient statuses with backoff.
25419
+ */
25420
+ async requestWithCollectionRetry(path8, init) {
25421
+ return retry(() => this.request(path8, init), {
25422
+ maxAttempts: 5,
25423
+ delayMs: 2e3,
25424
+ backoffMultiplier: 2,
25425
+ maxDelayMs: 15e3,
25426
+ ...this.retrySleep ? { sleep: this.retrySleep } : {},
25427
+ shouldRetry: (error2) => {
25428
+ if (!(error2 instanceof HttpError)) {
25429
+ return false;
25430
+ }
25431
+ if (error2.status >= 500 || error2.status === 429) {
25432
+ return true;
25433
+ }
25434
+ return error2.status === 400 && /unable to load collection/i.test(error2.responseBody);
25435
+ }
25436
+ });
25437
+ }
25364
25438
  async createMonitor(workspaceId, name, collectionUid, environmentUid, cronSchedule) {
25365
25439
  const effectiveCron = cronSchedule && cronSchedule.trim() ? cronSchedule.trim() : "0 0 * * 0";
25366
- const response = await this.request(`/monitors?workspace=${workspaceId}`, {
25440
+ const response = await this.requestWithCollectionRetry(`/monitors?workspace=${workspaceId}`, {
25367
25441
  method: "POST",
25368
25442
  body: JSON.stringify({
25369
25443
  monitor: {
@@ -25393,7 +25467,7 @@ var PostmanAssetsClient = class {
25393
25467
  return uid;
25394
25468
  }
25395
25469
  async createMock(workspaceId, name, collectionUid, environmentUid) {
25396
- const response = await this.request(`/mocks?workspace=${workspaceId}`, {
25470
+ const response = await this.requestWithCollectionRetry(`/mocks?workspace=${workspaceId}`, {
25397
25471
  method: "POST",
25398
25472
  body: JSON.stringify({
25399
25473
  mock: {
package/dist/cli.cjs CHANGED
@@ -9183,14 +9183,14 @@ var require_retry_agent = __commonJS({
9183
9183
  this.#options = options;
9184
9184
  }
9185
9185
  dispatch(opts, handler) {
9186
- const retry = new RetryHandler({
9186
+ const retry2 = new RetryHandler({
9187
9187
  ...opts,
9188
9188
  retryOptions: this.#options
9189
9189
  }, {
9190
9190
  dispatch: this.#agent.dispatch.bind(this.#agent),
9191
9191
  handler
9192
9192
  });
9193
- return this.#agent.dispatch(opts, retry);
9193
+ return this.#agent.dispatch(opts, retry2);
9194
9194
  }
9195
9195
  close() {
9196
9196
  return this.#agent.close();
@@ -23385,12 +23385,60 @@ var postmanRepoSyncActionContract = {
23385
23385
  }
23386
23386
  };
23387
23387
 
23388
+ // src/lib/retry.ts
23389
+ function sleep(delayMs) {
23390
+ return new Promise((resolve2) => {
23391
+ setTimeout(resolve2, delayMs);
23392
+ });
23393
+ }
23394
+ function normalizeRetryOptions(options) {
23395
+ return {
23396
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
23397
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
23398
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
23399
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
23400
+ onRetry: options.onRetry ?? (async () => void 0),
23401
+ shouldRetry: options.shouldRetry ?? (() => true),
23402
+ sleep: options.sleep ?? sleep
23403
+ };
23404
+ }
23405
+ async function retry(operation, options = {}) {
23406
+ const normalized = normalizeRetryOptions(options);
23407
+ let nextDelayMs = normalized.delayMs;
23408
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
23409
+ try {
23410
+ return await operation();
23411
+ } catch (error) {
23412
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error, {
23413
+ attempt,
23414
+ maxAttempts: normalized.maxAttempts
23415
+ });
23416
+ if (!shouldRetry) {
23417
+ throw error;
23418
+ }
23419
+ await normalized.onRetry({
23420
+ attempt,
23421
+ maxAttempts: normalized.maxAttempts,
23422
+ delayMs: nextDelayMs,
23423
+ error
23424
+ });
23425
+ await normalized.sleep(nextDelayMs);
23426
+ nextDelayMs = Math.min(
23427
+ normalized.maxDelayMs,
23428
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
23429
+ );
23430
+ }
23431
+ }
23432
+ throw new Error("Retry exhausted without returning or throwing");
23433
+ }
23434
+
23388
23435
  // src/lib/postman/postman-assets-client.ts
23389
23436
  var PostmanAssetsClient = class {
23390
23437
  apiKey;
23391
23438
  baseUrl;
23392
23439
  bifrostBaseUrl;
23393
23440
  fetchImpl;
23441
+ retrySleep;
23394
23442
  constructor(options) {
23395
23443
  this.apiKey = String(options.apiKey || "").trim();
23396
23444
  this.baseUrl = String(options.baseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl).replace(
@@ -23401,6 +23449,7 @@ var PostmanAssetsClient = class {
23401
23449
  options.bifrostBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl
23402
23450
  ).replace(/\/+$/, "");
23403
23451
  this.fetchImpl = options.fetchImpl ?? fetch;
23452
+ this.retrySleep = options.retrySleep;
23404
23453
  void (options.secretMasker ?? createSecretMasker([this.apiKey]));
23405
23454
  }
23406
23455
  getBaseUrl() {
@@ -23464,9 +23513,34 @@ var PostmanAssetsClient = class {
23464
23513
  })
23465
23514
  });
23466
23515
  }
23516
+ /**
23517
+ * Monitor and mock creation reference a collection that may have been
23518
+ * created moments earlier; the Postman backend is eventually consistent
23519
+ * and can reject the reference with a 400 "Unable to load collection"
23520
+ * until the collection becomes visible. Retry that specific 400 plus
23521
+ * generic transient statuses with backoff.
23522
+ */
23523
+ async requestWithCollectionRetry(path4, init) {
23524
+ return retry(() => this.request(path4, init), {
23525
+ maxAttempts: 5,
23526
+ delayMs: 2e3,
23527
+ backoffMultiplier: 2,
23528
+ maxDelayMs: 15e3,
23529
+ ...this.retrySleep ? { sleep: this.retrySleep } : {},
23530
+ shouldRetry: (error) => {
23531
+ if (!(error instanceof HttpError)) {
23532
+ return false;
23533
+ }
23534
+ if (error.status >= 500 || error.status === 429) {
23535
+ return true;
23536
+ }
23537
+ return error.status === 400 && /unable to load collection/i.test(error.responseBody);
23538
+ }
23539
+ });
23540
+ }
23467
23541
  async createMonitor(workspaceId, name, collectionUid, environmentUid, cronSchedule) {
23468
23542
  const effectiveCron = cronSchedule && cronSchedule.trim() ? cronSchedule.trim() : "0 0 * * 0";
23469
- const response = await this.request(`/monitors?workspace=${workspaceId}`, {
23543
+ const response = await this.requestWithCollectionRetry(`/monitors?workspace=${workspaceId}`, {
23470
23544
  method: "POST",
23471
23545
  body: JSON.stringify({
23472
23546
  monitor: {
@@ -23496,7 +23570,7 @@ var PostmanAssetsClient = class {
23496
23570
  return uid;
23497
23571
  }
23498
23572
  async createMock(workspaceId, name, collectionUid, environmentUid) {
23499
- const response = await this.request(`/mocks?workspace=${workspaceId}`, {
23573
+ const response = await this.requestWithCollectionRetry(`/mocks?workspace=${workspaceId}`, {
23500
23574
  method: "POST",
23501
23575
  body: JSON.stringify({
23502
23576
  mock: {
package/dist/index.cjs CHANGED
@@ -9183,14 +9183,14 @@ var require_retry_agent = __commonJS({
9183
9183
  this.#options = options;
9184
9184
  }
9185
9185
  dispatch(opts, handler) {
9186
- const retry = new RetryHandler({
9186
+ const retry2 = new RetryHandler({
9187
9187
  ...opts,
9188
9188
  retryOptions: this.#options
9189
9189
  }, {
9190
9190
  dispatch: this.#agent.dispatch.bind(this.#agent),
9191
9191
  handler
9192
9192
  });
9193
- return this.#agent.dispatch(opts, retry);
9193
+ return this.#agent.dispatch(opts, retry2);
9194
9194
  }
9195
9195
  close() {
9196
9196
  return this.#agent.close();
@@ -25297,12 +25297,60 @@ var postmanRepoSyncActionContract = {
25297
25297
  }
25298
25298
  };
25299
25299
 
25300
+ // src/lib/retry.ts
25301
+ function sleep(delayMs) {
25302
+ return new Promise((resolve3) => {
25303
+ setTimeout(resolve3, delayMs);
25304
+ });
25305
+ }
25306
+ function normalizeRetryOptions(options) {
25307
+ return {
25308
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
25309
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
25310
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
25311
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
25312
+ onRetry: options.onRetry ?? (async () => void 0),
25313
+ shouldRetry: options.shouldRetry ?? (() => true),
25314
+ sleep: options.sleep ?? sleep
25315
+ };
25316
+ }
25317
+ async function retry(operation, options = {}) {
25318
+ const normalized = normalizeRetryOptions(options);
25319
+ let nextDelayMs = normalized.delayMs;
25320
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
25321
+ try {
25322
+ return await operation();
25323
+ } catch (error2) {
25324
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
25325
+ attempt,
25326
+ maxAttempts: normalized.maxAttempts
25327
+ });
25328
+ if (!shouldRetry) {
25329
+ throw error2;
25330
+ }
25331
+ await normalized.onRetry({
25332
+ attempt,
25333
+ maxAttempts: normalized.maxAttempts,
25334
+ delayMs: nextDelayMs,
25335
+ error: error2
25336
+ });
25337
+ await normalized.sleep(nextDelayMs);
25338
+ nextDelayMs = Math.min(
25339
+ normalized.maxDelayMs,
25340
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
25341
+ );
25342
+ }
25343
+ }
25344
+ throw new Error("Retry exhausted without returning or throwing");
25345
+ }
25346
+
25300
25347
  // src/lib/postman/postman-assets-client.ts
25301
25348
  var PostmanAssetsClient = class {
25302
25349
  apiKey;
25303
25350
  baseUrl;
25304
25351
  bifrostBaseUrl;
25305
25352
  fetchImpl;
25353
+ retrySleep;
25306
25354
  constructor(options) {
25307
25355
  this.apiKey = String(options.apiKey || "").trim();
25308
25356
  this.baseUrl = String(options.baseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl).replace(
@@ -25313,6 +25361,7 @@ var PostmanAssetsClient = class {
25313
25361
  options.bifrostBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl
25314
25362
  ).replace(/\/+$/, "");
25315
25363
  this.fetchImpl = options.fetchImpl ?? fetch;
25364
+ this.retrySleep = options.retrySleep;
25316
25365
  void (options.secretMasker ?? createSecretMasker([this.apiKey]));
25317
25366
  }
25318
25367
  getBaseUrl() {
@@ -25376,9 +25425,34 @@ var PostmanAssetsClient = class {
25376
25425
  })
25377
25426
  });
25378
25427
  }
25428
+ /**
25429
+ * Monitor and mock creation reference a collection that may have been
25430
+ * created moments earlier; the Postman backend is eventually consistent
25431
+ * and can reject the reference with a 400 "Unable to load collection"
25432
+ * until the collection becomes visible. Retry that specific 400 plus
25433
+ * generic transient statuses with backoff.
25434
+ */
25435
+ async requestWithCollectionRetry(path8, init) {
25436
+ return retry(() => this.request(path8, init), {
25437
+ maxAttempts: 5,
25438
+ delayMs: 2e3,
25439
+ backoffMultiplier: 2,
25440
+ maxDelayMs: 15e3,
25441
+ ...this.retrySleep ? { sleep: this.retrySleep } : {},
25442
+ shouldRetry: (error2) => {
25443
+ if (!(error2 instanceof HttpError)) {
25444
+ return false;
25445
+ }
25446
+ if (error2.status >= 500 || error2.status === 429) {
25447
+ return true;
25448
+ }
25449
+ return error2.status === 400 && /unable to load collection/i.test(error2.responseBody);
25450
+ }
25451
+ });
25452
+ }
25379
25453
  async createMonitor(workspaceId, name, collectionUid, environmentUid, cronSchedule) {
25380
25454
  const effectiveCron = cronSchedule && cronSchedule.trim() ? cronSchedule.trim() : "0 0 * * 0";
25381
- const response = await this.request(`/monitors?workspace=${workspaceId}`, {
25455
+ const response = await this.requestWithCollectionRetry(`/monitors?workspace=${workspaceId}`, {
25382
25456
  method: "POST",
25383
25457
  body: JSON.stringify({
25384
25458
  monitor: {
@@ -25408,7 +25482,7 @@ var PostmanAssetsClient = class {
25408
25482
  return uid;
25409
25483
  }
25410
25484
  async createMock(workspaceId, name, collectionUid, environmentUid) {
25411
- const response = await this.request(`/mocks?workspace=${workspaceId}`, {
25485
+ const response = await this.requestWithCollectionRetry(`/mocks?workspace=${workspaceId}`, {
25412
25486
  method: "POST",
25413
25487
  body: JSON.stringify({
25414
25488
  mock: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-repo-sync",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Public customer preview Postman repo sync GitHub Action.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -17,6 +17,7 @@
17
17
  "scripts": {
18
18
  "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",
19
19
  "check:dist": "npm run build && git diff --exit-code -- dist",
20
+ "docs:tables": "node scripts/render-action-tables.mjs",
20
21
  "lint": "eslint .",
21
22
  "lint:fix": "eslint . --fix",
22
23
  "test": "vitest run",