@postman-cse/onboarding-repo-sync 2.1.18 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,6 +113,17 @@ with:
113
113
  monitor-id: 1e2f3a4b-monitor-id
114
114
  ```
115
115
 
116
+ Repo-sync supports public mocks for anonymous validation. An explicit `mock-url` must match one mock in the resolved workspace and reference the expected baseline collection and environment. Discovered and newly created mocks pass the same live visibility check. Private mocks, responses without a recognized visibility field, stale URLs, and identity mismatches fail before `mock-url` is emitted; repo-sync never places a Postman API key in a collection or environment to make a private mock callable.
117
+
118
+ For manual collection validation against that public mock, opt in to a dedicated Postman environment:
119
+
120
+ ```yaml
121
+ with:
122
+ mock-environment-enabled: true
123
+ ```
124
+
125
+ On canonical and legacy runs, the action creates or reuses `<project> - Mock`, sets its `baseUrl` to the validated mock URL, and emits `mock-environment-uid`. Preview and channel runs skip it so branch retention cleanup cannot leak an untracked environment. Its exported representation lives at `postman/mocks/manual-validation.postman_environment.json`; it is deliberately excluded from `environment-uids-json`, system-environment associations, monitors, and generated CI environment selection. Select it explicitly when running baseline, Smoke, or Contract collections manually. Repo-sync never replaces the runtime `prod` or `dev` `baseUrl` with a mock URL.
126
+
116
127
  ### mTLS certificates for Postman CLI runs
117
128
 
118
129
  The generated CI workflow can run [Postman CLI collection runs](https://learning.postman.com/docs/postman-cli/postman-cli-collections/) 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:
@@ -144,6 +155,7 @@ with:
144
155
  | `release-label` | Optional release label used for versioned naming. | no | |
145
156
  | `monitor-id` | Existing smoke monitor ID. When set, the action validates and reuses this monitor instead of creating a new one. | no | |
146
157
  | `mock-url` | Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one. | no | |
158
+ | `mock-environment-enabled` | Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection. | no | `false` |
147
159
  | `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 | `""` |
148
160
  | `environments-json` | JSON array of environment slugs to create or update. | no | `["prod"]` |
149
161
  | `git-provider` | Git provider override ('github', 'gitlab', 'bitbucket', 'azure-devops'). Auto-detected from environment when omitted. | no | |
@@ -191,6 +203,8 @@ with:
191
203
  | `environment-sync-status` | Whether environment sync succeeded, was skipped, or failed. |
192
204
  | `environment-uids-json` | JSON map of environment slug to Postman environment uid. |
193
205
  | `mock-url` | Created or reused mock server URL. |
206
+ | `mock-environment-uid` | Dedicated manual-validation environment UID when mock-environment-enabled succeeds. |
207
+ | `mock-environment-status` | Whether the optional manual-validation mock environment succeeded, was skipped, or failed. |
194
208
  | `monitor-id` | Created or reused smoke monitor ID. |
195
209
  | `repo-sync-summary-json` | JSON summary of repo materialization and workspace sync outputs. |
196
210
  | `commit-sha` | Commit SHA produced by repo-write-mode, if any. |
@@ -283,7 +297,7 @@ For `commit-and-push`, the push target is resolved from `current-ref`, then `GIT
283
297
 
284
298
  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.
285
299
 
286
- Asset reuse priority is explicit inputs (`environment-uids-json`, `mock-url`, `monitor-id`), then live discovery by exact workspace-scoped name, collection UID, and environment UID, then create. Creates submit once and reconcile on ambiguous gateway errors; overlapping compatible creates inside one process share a single in-flight promise. Adopted environments are updated to the requested values. Mock environment assignment has no verified patch route, so a mock is reused only when its live environment already matches; mismatches are never claimed as converged. Concurrent jobs against the same workspace can still race because Postman does not expose create idempotency keys—serialize those workflows with GitHub Actions `concurrency` (or an equivalent CI lock) when duplicate mocks/monitors/environments would be harmful.
300
+ Asset reuse priority is explicit inputs (`environment-uids-json`, `mock-url`, `monitor-id`), then live discovery by exact workspace-scoped name, collection UID, and environment UID, then create. Mock reuse is public-only: the gateway's live `published` field must confirm public visibility, and explicit URLs must match the expected workspace, collection, and environment. Private or unknown visibility fails closed without injecting credentials. Creates submit once and reconcile on ambiguous gateway errors; overlapping compatible creates inside one process share a single in-flight promise. Adopted environments are updated to the requested values. Mock environment assignment has no verified patch route, so a mock is reused only when its live environment already matches; mismatches are never claimed as converged. Concurrent jobs against the same workspace can still race because Postman does not expose create idempotency keys—serialize those workflows with GitHub Actions `concurrency` (or an equivalent CI lock) when duplicate mocks/monitors/environments would be harmful.
287
301
 
288
302
  Deeper reference:
289
303
 
package/action.yml CHANGED
@@ -57,6 +57,10 @@ inputs:
57
57
  mock-url:
58
58
  description: Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.
59
59
  required: false
60
+ mock-environment-enabled:
61
+ description: Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.
62
+ required: false
63
+ default: "false"
60
64
  monitor-cron:
61
65
  description: 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).
62
66
  required: false
@@ -200,6 +204,10 @@ outputs:
200
204
  description: JSON map of environment slug to Postman environment uid.
201
205
  mock-url:
202
206
  description: Created or reused mock server URL.
207
+ mock-environment-uid:
208
+ description: Dedicated manual-validation environment UID when mock-environment-enabled succeeds.
209
+ mock-environment-status:
210
+ description: Whether the optional manual-validation mock environment succeeded, was skipped, or failed.
203
211
  monitor-id:
204
212
  description: Created or reused smoke monitor ID.
205
213
  repo-sync-summary-json:
package/dist/action.cjs CHANGED
@@ -136660,6 +136660,11 @@ var postmanRepoSyncActionContract = {
136660
136660
  description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
136661
136661
  required: false
136662
136662
  },
136663
+ "mock-environment-enabled": {
136664
+ description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
136665
+ required: false,
136666
+ default: "false"
136667
+ },
136663
136668
  "monitor-cron": {
136664
136669
  description: "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).",
136665
136670
  required: false,
@@ -136853,6 +136858,12 @@ var postmanRepoSyncActionContract = {
136853
136858
  "mock-url": {
136854
136859
  description: "Created or reused mock server URL."
136855
136860
  },
136861
+ "mock-environment-uid": {
136862
+ description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
136863
+ },
136864
+ "mock-environment-status": {
136865
+ description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
136866
+ },
136856
136867
  "monitor-id": {
136857
136868
  description: "Created or reused smoke monitor ID."
136858
136869
  },
@@ -136941,6 +136952,25 @@ var PostmanAssetsClient = class {
136941
136952
  };
136942
136953
 
136943
136954
  // src/lib/postman/postman-gateway-assets-client.ts
136955
+ var MockContractError = class extends Error {
136956
+ constructor(message) {
136957
+ super(message);
136958
+ this.name = "MockContractError";
136959
+ }
136960
+ };
136961
+ function requirePublicMock(mock) {
136962
+ if (mock.visibility === "private") {
136963
+ throw new MockContractError(
136964
+ `MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
136965
+ );
136966
+ }
136967
+ if (mock.visibility !== "public") {
136968
+ throw new MockContractError(
136969
+ `MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
136970
+ );
136971
+ }
136972
+ return mock;
136973
+ }
136944
136974
  var MAX_CREATE_FLIGHTS = 256;
136945
136975
  var createFlights = /* @__PURE__ */ new Map();
136946
136976
  var PostmanGatewayAssetsClient = class {
@@ -137122,6 +137152,41 @@ var PostmanGatewayAssetsClient = class {
137122
137152
  const id = record.uid ?? record.id;
137123
137153
  return typeof id === "string" ? id.trim() : String(id ?? "").trim();
137124
137154
  }
137155
+ decodeMockRecord(value, operation) {
137156
+ const record = this.asRecord(value);
137157
+ if (!record) {
137158
+ throw new MockContractError(
137159
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
137160
+ );
137161
+ }
137162
+ const uid = this.idOf(record);
137163
+ const name = typeof record.name === "string" ? record.name.trim() : "";
137164
+ const collection = typeof record.collection === "string" ? record.collection.trim() : "";
137165
+ const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
137166
+ const environment = typeof record.environment === "string" ? record.environment.trim() : "";
137167
+ if (!uid) {
137168
+ throw new MockContractError(
137169
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
137170
+ );
137171
+ }
137172
+ if (!name) {
137173
+ throw new MockContractError(
137174
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
137175
+ );
137176
+ }
137177
+ if (!collection) {
137178
+ throw new MockContractError(
137179
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
137180
+ );
137181
+ }
137182
+ if (!mockUrl) {
137183
+ throw new MockContractError(
137184
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
137185
+ );
137186
+ }
137187
+ const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
137188
+ return { uid, name, collection, mockUrl, environment, visibility };
137189
+ }
137125
137190
  /**
137126
137191
  * Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
137127
137192
  * model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
@@ -137422,7 +137487,8 @@ var PostmanGatewayAssetsClient = class {
137422
137487
  return this.singleFlight(flightKey, flightKey, "mock", async () => {
137423
137488
  const existing = await this.findMockByCollection(collection, environment, mockName);
137424
137489
  if (existing) {
137425
- return { uid: existing.uid, url: existing.mockUrl };
137490
+ const reusable = requirePublicMock(existing);
137491
+ return { uid: reusable.uid, url: reusable.mockUrl };
137426
137492
  }
137427
137493
  const body = {
137428
137494
  name: mockName,
@@ -137445,13 +137511,12 @@ var PostmanGatewayAssetsClient = class {
137445
137511
  );
137446
137512
  const parseMock = (response) => {
137447
137513
  const record = this.dataOf(response);
137448
- const uid = this.idOf(record);
137449
- if (!uid) {
137450
- throw new Error("Mock create did not return a UID");
137451
- }
137514
+ const created = requirePublicMock(
137515
+ this.decodeMockRecord(record, "Mock create")
137516
+ );
137452
137517
  return {
137453
- uid,
137454
- url: String(record?.url ?? record?.mockUrl ?? "").trim()
137518
+ uid: created.uid,
137519
+ url: created.mockUrl
137455
137520
  };
137456
137521
  };
137457
137522
  try {
@@ -137462,7 +137527,8 @@ var PostmanGatewayAssetsClient = class {
137462
137527
  error2
137463
137528
  );
137464
137529
  if (adopted) {
137465
- return { uid: adopted.uid, url: adopted.mockUrl };
137530
+ const reusable = requirePublicMock(adopted);
137531
+ return { uid: reusable.uid, url: reusable.mockUrl };
137466
137532
  }
137467
137533
  if (adopted === void 0) {
137468
137534
  const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
@@ -137478,14 +137544,14 @@ var PostmanGatewayAssetsClient = class {
137478
137544
  method: "get",
137479
137545
  path: `/mocks?workspace=${this.workspaceId}`
137480
137546
  });
137481
- const items = Array.isArray(response) ? response : Array.isArray(this.asRecord(response)?.data) ? this.asRecord(response).data : [];
137482
- return items.map((raw) => this.asRecord(raw)).filter((m) => m !== null).map((m) => ({
137483
- uid: this.idOf(m),
137484
- name: String(m.name ?? ""),
137485
- collection: String(m.collection ?? ""),
137486
- mockUrl: String(m.url ?? m.mockUrl ?? ""),
137487
- environment: String(m.environment ?? "")
137488
- }));
137547
+ const envelope = this.asRecord(response);
137548
+ const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
137549
+ if (!items) {
137550
+ throw new MockContractError(
137551
+ "CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
137552
+ );
137553
+ }
137554
+ return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
137489
137555
  }
137490
137556
  /**
137491
137557
  * Delete an environment through the sync service (GC path). The path id is
@@ -137539,7 +137605,7 @@ var PostmanGatewayAssetsClient = class {
137539
137605
  `workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
137540
137606
  matches
137541
137607
  );
137542
- return match ? { uid: match.uid, mockUrl: match.mockUrl } : null;
137608
+ return match;
137543
137609
  }
137544
137610
  // --- monitors (service: monitors; collection-based = jobTemplates) ---
137545
137611
  /** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
@@ -138469,6 +138535,61 @@ function resolveRepoSyncMasker(dependencies) {
138469
138535
  function causeText(cause) {
138470
138536
  return cause instanceof Error ? cause.message : String(cause);
138471
138537
  }
138538
+ function normalizeMockUrl(value, source) {
138539
+ let url;
138540
+ try {
138541
+ url = new URL(String(value || "").trim());
138542
+ } catch {
138543
+ throw new MockContractError(
138544
+ `EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
138545
+ );
138546
+ }
138547
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
138548
+ throw new MockContractError(
138549
+ `EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
138550
+ );
138551
+ }
138552
+ url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
138553
+ return url.toString();
138554
+ }
138555
+ function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
138556
+ const normalized = normalizeMockUrl(explicitUrl, "mock-url");
138557
+ const matches = mocks.filter(
138558
+ (mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
138559
+ );
138560
+ if (matches.length === 0) {
138561
+ throw new MockContractError(
138562
+ `EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
138563
+ );
138564
+ }
138565
+ if (matches.length > 1) {
138566
+ throw new MockContractError(
138567
+ `EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
138568
+ );
138569
+ }
138570
+ const match = matches[0];
138571
+ if (match.collection !== collectionUid) {
138572
+ throw new MockContractError(
138573
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
138574
+ );
138575
+ }
138576
+ if (match.environment !== environmentUid) {
138577
+ throw new MockContractError(
138578
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
138579
+ );
138580
+ }
138581
+ return requirePublicMock(match);
138582
+ }
138583
+ function shouldRetryMockCreate(error2) {
138584
+ if (error2 instanceof MockContractError) return false;
138585
+ if (error2 instanceof HttpError) {
138586
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
138587
+ }
138588
+ if (error2 instanceof Error && error2.message.startsWith("Multiple mocks match ")) {
138589
+ return false;
138590
+ }
138591
+ return true;
138592
+ }
138472
138593
  function toOneLineDisplay(value) {
138473
138594
  return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
138474
138595
  }
@@ -138675,6 +138796,7 @@ function resolveInputs(env = process.env) {
138675
138796
  orgMode: parseBooleanInput(getInput2("org-mode", env), false),
138676
138797
  monitorId: getInput2("monitor-id", env),
138677
138798
  mockUrl: getInput2("mock-url", env),
138799
+ mockEnvironmentEnabled: parseBooleanInput(getInput2("mock-environment-enabled", env), false),
138678
138800
  monitorCron: getInput2("monitor-cron", env),
138679
138801
  sslClientCert: getInput2("ssl-client-cert", env),
138680
138802
  sslClientKey: getInput2("ssl-client-key", env),
@@ -138901,6 +139023,8 @@ function createOutputs(inputs) {
138901
139023
  "environment-sync-status": "skipped",
138902
139024
  "environment-uids-json": JSON.stringify(inputs.environmentUids),
138903
139025
  "mock-url": "",
139026
+ "mock-environment-uid": "",
139027
+ "mock-environment-status": "skipped",
138904
139028
  "monitor-id": "",
138905
139029
  "repo-sync-summary-json": "{}",
138906
139030
  "commit-sha": "",
@@ -138968,6 +139092,7 @@ function readActionInputs(actionCore) {
138968
139092
  INPUT_ORG_MODE: readInput(actionCore, "org-mode"),
138969
139093
  INPUT_MONITOR_ID: readInput(actionCore, "monitor-id"),
138970
139094
  INPUT_MOCK_URL: readInput(actionCore, "mock-url"),
139095
+ INPUT_MOCK_ENVIRONMENT_ENABLED: readInput(actionCore, "mock-environment-enabled"),
138971
139096
  INPUT_MONITOR_CRON: readInput(actionCore, "monitor-cron"),
138972
139097
  INPUT_SSL_CLIENT_CERT: sslClientCert,
138973
139098
  INPUT_SSL_CLIENT_KEY: sslClientKey,
@@ -139169,6 +139294,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
139169
139294
  }
139170
139295
  return envUids;
139171
139296
  }
139297
+ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
139298
+ if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
139299
+ return "";
139300
+ }
139301
+ const displayName = `${assetProjectName} - Mock`;
139302
+ const values = buildEnvironmentValues("mock", mockUrl);
139303
+ const mask = resolveRepoSyncMasker(dependencies);
139304
+ try {
139305
+ const discovered = await dependencies.postman.findEnvironmentByName(
139306
+ inputs.workspaceId,
139307
+ displayName
139308
+ );
139309
+ if (discovered?.uid) {
139310
+ try {
139311
+ const existing = await dependencies.postman.getEnvironment(discovered.uid);
139312
+ const currentValues = existing.data?.values ?? existing.values ?? [];
139313
+ if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
139314
+ dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
139315
+ return discovered.uid;
139316
+ }
139317
+ } catch {
139318
+ }
139319
+ await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
139320
+ dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
139321
+ return discovered.uid;
139322
+ }
139323
+ const uid = await dependencies.postman.createEnvironment(
139324
+ inputs.workspaceId,
139325
+ displayName,
139326
+ values
139327
+ );
139328
+ dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
139329
+ return uid;
139330
+ } catch (error2) {
139331
+ dependencies.core.warning(
139332
+ formatOrchestrationIssue({
139333
+ operation: "Mock environment upsert",
139334
+ entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
139335
+ cause: error2,
139336
+ remediation: "verify environment access or disable mock-environment-enabled then rerun",
139337
+ mask
139338
+ })
139339
+ );
139340
+ return "";
139341
+ }
139342
+ }
139172
139343
  function ensureDir(path9) {
139173
139344
  (0, import_node_fs5.mkdirSync)(path9, { recursive: true });
139174
139345
  }
@@ -139807,6 +139978,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
139807
139978
  true
139808
139979
  );
139809
139980
  }
139981
+ if (options.mockEnvironmentUid) {
139982
+ writeJsonFile(
139983
+ `${mocksDir}/manual-validation.postman_environment.json`,
139984
+ await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
139985
+ true
139986
+ );
139987
+ }
139810
139988
  const durableWorkspaceId = resolveDurableWorkspaceId({
139811
139989
  candidateId: inputs.workspaceId,
139812
139990
  priorId: options.priorWorkspaceId,
@@ -139865,6 +140043,8 @@ function createRepoSummary(outputs, envUids, pushed) {
139865
140043
  commitSha: outputs["commit-sha"],
139866
140044
  environmentCount: Object.keys(envUids).length,
139867
140045
  environmentSyncStatus: outputs["environment-sync-status"],
140046
+ mockEnvironmentStatus: outputs["mock-environment-status"],
140047
+ mockEnvironmentUid: outputs["mock-environment-uid"],
139868
140048
  mockUrl: outputs["mock-url"],
139869
140049
  monitorId: outputs["monitor-id"],
139870
140050
  pushed,
@@ -140095,20 +140275,37 @@ async function runRepoSyncInner(inputs, dependencies) {
140095
140275
  let resolvedMockUrl = "";
140096
140276
  const mockName = `${assetProjectName} Mock`;
140097
140277
  if (inputs.mockUrl) {
140098
- resolvedMockUrl = inputs.mockUrl;
140278
+ let mocks;
140279
+ try {
140280
+ mocks = await dependencies.postman.listMocks();
140281
+ } catch (error2) {
140282
+ throw new Error(
140283
+ formatOrchestrationIssue({
140284
+ operation: "Explicit mock-url lookup",
140285
+ entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
140286
+ cause: error2,
140287
+ remediation: "verify the URL and mock access then rerun",
140288
+ mask
140289
+ }),
140290
+ { cause: error2 }
140291
+ );
140292
+ }
140293
+ resolvedMockUrl = resolveExplicitPublicMock(
140294
+ mocks,
140295
+ inputs.mockUrl,
140296
+ inputs.baselineCollectionId,
140297
+ mockEnvUid
140298
+ ).mockUrl;
140099
140299
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
140100
140300
  }
140101
140301
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
140302
+ let discovered;
140102
140303
  try {
140103
- const discovered = await dependencies.postman.findMockByCollection(
140304
+ discovered = await dependencies.postman.findMockByCollection(
140104
140305
  inputs.baselineCollectionId,
140105
140306
  mockEnvUid,
140106
140307
  mockName
140107
140308
  );
140108
- if (discovered) {
140109
- resolvedMockUrl = discovered.mockUrl;
140110
- dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
140111
- }
140112
140309
  } catch (error2) {
140113
140310
  throw new Error(
140114
140311
  formatOrchestrationIssue({
@@ -140121,6 +140318,10 @@ async function runRepoSyncInner(inputs, dependencies) {
140121
140318
  { cause: error2 }
140122
140319
  );
140123
140320
  }
140321
+ if (discovered) {
140322
+ resolvedMockUrl = requirePublicMock(discovered).mockUrl;
140323
+ dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
140324
+ }
140124
140325
  }
140125
140326
  if (!resolvedMockUrl) {
140126
140327
  const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
@@ -140137,6 +140338,7 @@ async function runRepoSyncInner(inputs, dependencies) {
140137
140338
  maxAttempts: 3,
140138
140339
  delayMs: 2e3,
140139
140340
  backoffMultiplier: 2,
140341
+ shouldRetry: (error2) => shouldRetryMockCreate(error2),
140140
140342
  onRetry: ({ attempt, maxAttempts, error: error2 }) => {
140141
140343
  dependencies.core.warning(
140142
140344
  formatOrchestrationIssue({
@@ -140168,6 +140370,20 @@ async function runRepoSyncInner(inputs, dependencies) {
140168
140370
  }
140169
140371
  outputs["mock-url"] = resolvedMockUrl;
140170
140372
  dependencies.core.setOutput("mock-url", resolvedMockUrl);
140373
+ if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
140374
+ const mockEnvironmentUid = await upsertMockEnvironment(
140375
+ inputs,
140376
+ dependencies,
140377
+ assetProjectName,
140378
+ resolvedMockUrl
140379
+ );
140380
+ outputs["mock-environment-uid"] = mockEnvironmentUid;
140381
+ outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
140382
+ } else if (inputs.mockEnvironmentEnabled) {
140383
+ dependencies.core.warning(
140384
+ "mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
140385
+ );
140386
+ }
140171
140387
  }
140172
140388
  }
140173
140389
  if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
@@ -140300,6 +140516,7 @@ async function runRepoSyncInner(inputs, dependencies) {
140300
140516
  workspaceLinkStatus: outputs["workspace-link-status"],
140301
140517
  priorWorkspaceId: resourcesState?.workspace?.id,
140302
140518
  existingSpecs: resourcesState?.cloudResources?.specs,
140519
+ mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
140303
140520
  releaseLabel,
140304
140521
  priorState: resourcesState,
140305
140522
  preparedPrebuiltCollections
package/dist/cli.cjs CHANGED
@@ -134765,6 +134765,11 @@ var postmanRepoSyncActionContract = {
134765
134765
  description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
134766
134766
  required: false
134767
134767
  },
134768
+ "mock-environment-enabled": {
134769
+ description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
134770
+ required: false,
134771
+ default: "false"
134772
+ },
134768
134773
  "monitor-cron": {
134769
134774
  description: "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).",
134770
134775
  required: false,
@@ -134958,6 +134963,12 @@ var postmanRepoSyncActionContract = {
134958
134963
  "mock-url": {
134959
134964
  description: "Created or reused mock server URL."
134960
134965
  },
134966
+ "mock-environment-uid": {
134967
+ description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
134968
+ },
134969
+ "mock-environment-status": {
134970
+ description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
134971
+ },
134961
134972
  "monitor-id": {
134962
134973
  description: "Created or reused smoke monitor ID."
134963
134974
  },
@@ -135046,6 +135057,25 @@ var PostmanAssetsClient = class {
135046
135057
  };
135047
135058
 
135048
135059
  // src/lib/postman/postman-gateway-assets-client.ts
135060
+ var MockContractError = class extends Error {
135061
+ constructor(message) {
135062
+ super(message);
135063
+ this.name = "MockContractError";
135064
+ }
135065
+ };
135066
+ function requirePublicMock(mock) {
135067
+ if (mock.visibility === "private") {
135068
+ throw new MockContractError(
135069
+ `MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
135070
+ );
135071
+ }
135072
+ if (mock.visibility !== "public") {
135073
+ throw new MockContractError(
135074
+ `MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
135075
+ );
135076
+ }
135077
+ return mock;
135078
+ }
135049
135079
  var MAX_CREATE_FLIGHTS = 256;
135050
135080
  var createFlights = /* @__PURE__ */ new Map();
135051
135081
  var PostmanGatewayAssetsClient = class {
@@ -135227,6 +135257,41 @@ var PostmanGatewayAssetsClient = class {
135227
135257
  const id = record.uid ?? record.id;
135228
135258
  return typeof id === "string" ? id.trim() : String(id ?? "").trim();
135229
135259
  }
135260
+ decodeMockRecord(value, operation) {
135261
+ const record = this.asRecord(value);
135262
+ if (!record) {
135263
+ throw new MockContractError(
135264
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
135265
+ );
135266
+ }
135267
+ const uid = this.idOf(record);
135268
+ const name = typeof record.name === "string" ? record.name.trim() : "";
135269
+ const collection = typeof record.collection === "string" ? record.collection.trim() : "";
135270
+ const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
135271
+ const environment = typeof record.environment === "string" ? record.environment.trim() : "";
135272
+ if (!uid) {
135273
+ throw new MockContractError(
135274
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
135275
+ );
135276
+ }
135277
+ if (!name) {
135278
+ throw new MockContractError(
135279
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
135280
+ );
135281
+ }
135282
+ if (!collection) {
135283
+ throw new MockContractError(
135284
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
135285
+ );
135286
+ }
135287
+ if (!mockUrl) {
135288
+ throw new MockContractError(
135289
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
135290
+ );
135291
+ }
135292
+ const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
135293
+ return { uid, name, collection, mockUrl, environment, visibility };
135294
+ }
135230
135295
  /**
135231
135296
  * Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
135232
135297
  * model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
@@ -135527,7 +135592,8 @@ var PostmanGatewayAssetsClient = class {
135527
135592
  return this.singleFlight(flightKey, flightKey, "mock", async () => {
135528
135593
  const existing = await this.findMockByCollection(collection, environment, mockName);
135529
135594
  if (existing) {
135530
- return { uid: existing.uid, url: existing.mockUrl };
135595
+ const reusable = requirePublicMock(existing);
135596
+ return { uid: reusable.uid, url: reusable.mockUrl };
135531
135597
  }
135532
135598
  const body = {
135533
135599
  name: mockName,
@@ -135550,13 +135616,12 @@ var PostmanGatewayAssetsClient = class {
135550
135616
  );
135551
135617
  const parseMock = (response) => {
135552
135618
  const record = this.dataOf(response);
135553
- const uid = this.idOf(record);
135554
- if (!uid) {
135555
- throw new Error("Mock create did not return a UID");
135556
- }
135619
+ const created = requirePublicMock(
135620
+ this.decodeMockRecord(record, "Mock create")
135621
+ );
135557
135622
  return {
135558
- uid,
135559
- url: String(record?.url ?? record?.mockUrl ?? "").trim()
135623
+ uid: created.uid,
135624
+ url: created.mockUrl
135560
135625
  };
135561
135626
  };
135562
135627
  try {
@@ -135567,7 +135632,8 @@ var PostmanGatewayAssetsClient = class {
135567
135632
  error
135568
135633
  );
135569
135634
  if (adopted) {
135570
- return { uid: adopted.uid, url: adopted.mockUrl };
135635
+ const reusable = requirePublicMock(adopted);
135636
+ return { uid: reusable.uid, url: reusable.mockUrl };
135571
135637
  }
135572
135638
  if (adopted === void 0) {
135573
135639
  const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
@@ -135583,14 +135649,14 @@ var PostmanGatewayAssetsClient = class {
135583
135649
  method: "get",
135584
135650
  path: `/mocks?workspace=${this.workspaceId}`
135585
135651
  });
135586
- const items = Array.isArray(response) ? response : Array.isArray(this.asRecord(response)?.data) ? this.asRecord(response).data : [];
135587
- return items.map((raw) => this.asRecord(raw)).filter((m) => m !== null).map((m) => ({
135588
- uid: this.idOf(m),
135589
- name: String(m.name ?? ""),
135590
- collection: String(m.collection ?? ""),
135591
- mockUrl: String(m.url ?? m.mockUrl ?? ""),
135592
- environment: String(m.environment ?? "")
135593
- }));
135652
+ const envelope = this.asRecord(response);
135653
+ const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
135654
+ if (!items) {
135655
+ throw new MockContractError(
135656
+ "CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
135657
+ );
135658
+ }
135659
+ return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
135594
135660
  }
135595
135661
  /**
135596
135662
  * Delete an environment through the sync service (GC path). The path id is
@@ -135644,7 +135710,7 @@ var PostmanGatewayAssetsClient = class {
135644
135710
  `workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
135645
135711
  matches
135646
135712
  );
135647
- return match ? { uid: match.uid, mockUrl: match.mockUrl } : null;
135713
+ return match;
135648
135714
  }
135649
135715
  // --- monitors (service: monitors; collection-based = jobTemplates) ---
135650
135716
  /** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
@@ -136522,6 +136588,61 @@ function resolveRepoSyncMasker(dependencies) {
136522
136588
  function causeText(cause) {
136523
136589
  return cause instanceof Error ? cause.message : String(cause);
136524
136590
  }
136591
+ function normalizeMockUrl(value, source) {
136592
+ let url;
136593
+ try {
136594
+ url = new URL(String(value || "").trim());
136595
+ } catch {
136596
+ throw new MockContractError(
136597
+ `EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
136598
+ );
136599
+ }
136600
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
136601
+ throw new MockContractError(
136602
+ `EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
136603
+ );
136604
+ }
136605
+ url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
136606
+ return url.toString();
136607
+ }
136608
+ function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
136609
+ const normalized = normalizeMockUrl(explicitUrl, "mock-url");
136610
+ const matches = mocks.filter(
136611
+ (mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
136612
+ );
136613
+ if (matches.length === 0) {
136614
+ throw new MockContractError(
136615
+ `EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
136616
+ );
136617
+ }
136618
+ if (matches.length > 1) {
136619
+ throw new MockContractError(
136620
+ `EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
136621
+ );
136622
+ }
136623
+ const match = matches[0];
136624
+ if (match.collection !== collectionUid) {
136625
+ throw new MockContractError(
136626
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
136627
+ );
136628
+ }
136629
+ if (match.environment !== environmentUid) {
136630
+ throw new MockContractError(
136631
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
136632
+ );
136633
+ }
136634
+ return requirePublicMock(match);
136635
+ }
136636
+ function shouldRetryMockCreate(error) {
136637
+ if (error instanceof MockContractError) return false;
136638
+ if (error instanceof HttpError) {
136639
+ return error.status === 408 || error.status === 429 || error.status >= 500;
136640
+ }
136641
+ if (error instanceof Error && error.message.startsWith("Multiple mocks match ")) {
136642
+ return false;
136643
+ }
136644
+ return true;
136645
+ }
136525
136646
  function toOneLineDisplay(value) {
136526
136647
  return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
136527
136648
  }
@@ -136725,6 +136846,7 @@ function resolveInputs(env = process.env) {
136725
136846
  orgMode: parseBooleanInput(getInput("org-mode", env), false),
136726
136847
  monitorId: getInput("monitor-id", env),
136727
136848
  mockUrl: getInput("mock-url", env),
136849
+ mockEnvironmentEnabled: parseBooleanInput(getInput("mock-environment-enabled", env), false),
136728
136850
  monitorCron: getInput("monitor-cron", env),
136729
136851
  sslClientCert: getInput("ssl-client-cert", env),
136730
136852
  sslClientKey: getInput("ssl-client-key", env),
@@ -136951,6 +137073,8 @@ function createOutputs(inputs) {
136951
137073
  "environment-sync-status": "skipped",
136952
137074
  "environment-uids-json": JSON.stringify(inputs.environmentUids),
136953
137075
  "mock-url": "",
137076
+ "mock-environment-uid": "",
137077
+ "mock-environment-status": "skipped",
136954
137078
  "monitor-id": "",
136955
137079
  "repo-sync-summary-json": "{}",
136956
137080
  "commit-sha": "",
@@ -137079,6 +137203,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
137079
137203
  }
137080
137204
  return envUids;
137081
137205
  }
137206
+ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
137207
+ if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
137208
+ return "";
137209
+ }
137210
+ const displayName = `${assetProjectName} - Mock`;
137211
+ const values = buildEnvironmentValues("mock", mockUrl);
137212
+ const mask = resolveRepoSyncMasker(dependencies);
137213
+ try {
137214
+ const discovered = await dependencies.postman.findEnvironmentByName(
137215
+ inputs.workspaceId,
137216
+ displayName
137217
+ );
137218
+ if (discovered?.uid) {
137219
+ try {
137220
+ const existing = await dependencies.postman.getEnvironment(discovered.uid);
137221
+ const currentValues = existing.data?.values ?? existing.values ?? [];
137222
+ if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
137223
+ dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
137224
+ return discovered.uid;
137225
+ }
137226
+ } catch {
137227
+ }
137228
+ await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
137229
+ dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
137230
+ return discovered.uid;
137231
+ }
137232
+ const uid = await dependencies.postman.createEnvironment(
137233
+ inputs.workspaceId,
137234
+ displayName,
137235
+ values
137236
+ );
137237
+ dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
137238
+ return uid;
137239
+ } catch (error) {
137240
+ dependencies.core.warning(
137241
+ formatOrchestrationIssue({
137242
+ operation: "Mock environment upsert",
137243
+ entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
137244
+ cause: error,
137245
+ remediation: "verify environment access or disable mock-environment-enabled then rerun",
137246
+ mask
137247
+ })
137248
+ );
137249
+ return "";
137250
+ }
137251
+ }
137082
137252
  function ensureDir(path5) {
137083
137253
  (0, import_node_fs5.mkdirSync)(path5, { recursive: true });
137084
137254
  }
@@ -137717,6 +137887,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
137717
137887
  true
137718
137888
  );
137719
137889
  }
137890
+ if (options.mockEnvironmentUid) {
137891
+ writeJsonFile(
137892
+ `${mocksDir}/manual-validation.postman_environment.json`,
137893
+ await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
137894
+ true
137895
+ );
137896
+ }
137720
137897
  const durableWorkspaceId = resolveDurableWorkspaceId({
137721
137898
  candidateId: inputs.workspaceId,
137722
137899
  priorId: options.priorWorkspaceId,
@@ -137775,6 +137952,8 @@ function createRepoSummary(outputs, envUids, pushed) {
137775
137952
  commitSha: outputs["commit-sha"],
137776
137953
  environmentCount: Object.keys(envUids).length,
137777
137954
  environmentSyncStatus: outputs["environment-sync-status"],
137955
+ mockEnvironmentStatus: outputs["mock-environment-status"],
137956
+ mockEnvironmentUid: outputs["mock-environment-uid"],
137778
137957
  mockUrl: outputs["mock-url"],
137779
137958
  monitorId: outputs["monitor-id"],
137780
137959
  pushed,
@@ -138005,20 +138184,37 @@ async function runRepoSyncInner(inputs, dependencies) {
138005
138184
  let resolvedMockUrl = "";
138006
138185
  const mockName = `${assetProjectName} Mock`;
138007
138186
  if (inputs.mockUrl) {
138008
- resolvedMockUrl = inputs.mockUrl;
138187
+ let mocks;
138188
+ try {
138189
+ mocks = await dependencies.postman.listMocks();
138190
+ } catch (error) {
138191
+ throw new Error(
138192
+ formatOrchestrationIssue({
138193
+ operation: "Explicit mock-url lookup",
138194
+ entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
138195
+ cause: error,
138196
+ remediation: "verify the URL and mock access then rerun",
138197
+ mask
138198
+ }),
138199
+ { cause: error }
138200
+ );
138201
+ }
138202
+ resolvedMockUrl = resolveExplicitPublicMock(
138203
+ mocks,
138204
+ inputs.mockUrl,
138205
+ inputs.baselineCollectionId,
138206
+ mockEnvUid
138207
+ ).mockUrl;
138009
138208
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
138010
138209
  }
138011
138210
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
138211
+ let discovered;
138012
138212
  try {
138013
- const discovered = await dependencies.postman.findMockByCollection(
138213
+ discovered = await dependencies.postman.findMockByCollection(
138014
138214
  inputs.baselineCollectionId,
138015
138215
  mockEnvUid,
138016
138216
  mockName
138017
138217
  );
138018
- if (discovered) {
138019
- resolvedMockUrl = discovered.mockUrl;
138020
- dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
138021
- }
138022
138218
  } catch (error) {
138023
138219
  throw new Error(
138024
138220
  formatOrchestrationIssue({
@@ -138031,6 +138227,10 @@ async function runRepoSyncInner(inputs, dependencies) {
138031
138227
  { cause: error }
138032
138228
  );
138033
138229
  }
138230
+ if (discovered) {
138231
+ resolvedMockUrl = requirePublicMock(discovered).mockUrl;
138232
+ dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
138233
+ }
138034
138234
  }
138035
138235
  if (!resolvedMockUrl) {
138036
138236
  const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
@@ -138047,6 +138247,7 @@ async function runRepoSyncInner(inputs, dependencies) {
138047
138247
  maxAttempts: 3,
138048
138248
  delayMs: 2e3,
138049
138249
  backoffMultiplier: 2,
138250
+ shouldRetry: (error) => shouldRetryMockCreate(error),
138050
138251
  onRetry: ({ attempt, maxAttempts, error }) => {
138051
138252
  dependencies.core.warning(
138052
138253
  formatOrchestrationIssue({
@@ -138078,6 +138279,20 @@ async function runRepoSyncInner(inputs, dependencies) {
138078
138279
  }
138079
138280
  outputs["mock-url"] = resolvedMockUrl;
138080
138281
  dependencies.core.setOutput("mock-url", resolvedMockUrl);
138282
+ if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
138283
+ const mockEnvironmentUid = await upsertMockEnvironment(
138284
+ inputs,
138285
+ dependencies,
138286
+ assetProjectName,
138287
+ resolvedMockUrl
138288
+ );
138289
+ outputs["mock-environment-uid"] = mockEnvironmentUid;
138290
+ outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
138291
+ } else if (inputs.mockEnvironmentEnabled) {
138292
+ dependencies.core.warning(
138293
+ "mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
138294
+ );
138295
+ }
138081
138296
  }
138082
138297
  }
138083
138298
  if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
@@ -138210,6 +138425,7 @@ async function runRepoSyncInner(inputs, dependencies) {
138210
138425
  workspaceLinkStatus: outputs["workspace-link-status"],
138211
138426
  priorWorkspaceId: resourcesState?.workspace?.id,
138212
138427
  existingSpecs: resourcesState?.cloudResources?.specs,
138428
+ mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
138213
138429
  releaseLabel,
138214
138430
  priorState: resourcesState,
138215
138431
  preparedPrebuiltCollections
@@ -139030,6 +139246,7 @@ var CLI_INPUT_NAMES = [
139030
139246
  "org-mode",
139031
139247
  "monitor-id",
139032
139248
  "mock-url",
139249
+ "mock-environment-enabled",
139033
139250
  "monitor-cron",
139034
139251
  "ssl-client-cert",
139035
139252
  "ssl-client-key",
package/dist/index.cjs CHANGED
@@ -136682,6 +136682,11 @@ var postmanRepoSyncActionContract = {
136682
136682
  description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
136683
136683
  required: false
136684
136684
  },
136685
+ "mock-environment-enabled": {
136686
+ description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
136687
+ required: false,
136688
+ default: "false"
136689
+ },
136685
136690
  "monitor-cron": {
136686
136691
  description: "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).",
136687
136692
  required: false,
@@ -136875,6 +136880,12 @@ var postmanRepoSyncActionContract = {
136875
136880
  "mock-url": {
136876
136881
  description: "Created or reused mock server URL."
136877
136882
  },
136883
+ "mock-environment-uid": {
136884
+ description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
136885
+ },
136886
+ "mock-environment-status": {
136887
+ description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
136888
+ },
136878
136889
  "monitor-id": {
136879
136890
  description: "Created or reused smoke monitor ID."
136880
136891
  },
@@ -136963,6 +136974,25 @@ var PostmanAssetsClient = class {
136963
136974
  };
136964
136975
 
136965
136976
  // src/lib/postman/postman-gateway-assets-client.ts
136977
+ var MockContractError = class extends Error {
136978
+ constructor(message) {
136979
+ super(message);
136980
+ this.name = "MockContractError";
136981
+ }
136982
+ };
136983
+ function requirePublicMock(mock) {
136984
+ if (mock.visibility === "private") {
136985
+ throw new MockContractError(
136986
+ `MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
136987
+ );
136988
+ }
136989
+ if (mock.visibility !== "public") {
136990
+ throw new MockContractError(
136991
+ `MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
136992
+ );
136993
+ }
136994
+ return mock;
136995
+ }
136966
136996
  var MAX_CREATE_FLIGHTS = 256;
136967
136997
  var createFlights = /* @__PURE__ */ new Map();
136968
136998
  var PostmanGatewayAssetsClient = class {
@@ -137144,6 +137174,41 @@ var PostmanGatewayAssetsClient = class {
137144
137174
  const id = record.uid ?? record.id;
137145
137175
  return typeof id === "string" ? id.trim() : String(id ?? "").trim();
137146
137176
  }
137177
+ decodeMockRecord(value, operation) {
137178
+ const record = this.asRecord(value);
137179
+ if (!record) {
137180
+ throw new MockContractError(
137181
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
137182
+ );
137183
+ }
137184
+ const uid = this.idOf(record);
137185
+ const name = typeof record.name === "string" ? record.name.trim() : "";
137186
+ const collection = typeof record.collection === "string" ? record.collection.trim() : "";
137187
+ const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
137188
+ const environment = typeof record.environment === "string" ? record.environment.trim() : "";
137189
+ if (!uid) {
137190
+ throw new MockContractError(
137191
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
137192
+ );
137193
+ }
137194
+ if (!name) {
137195
+ throw new MockContractError(
137196
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
137197
+ );
137198
+ }
137199
+ if (!collection) {
137200
+ throw new MockContractError(
137201
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
137202
+ );
137203
+ }
137204
+ if (!mockUrl) {
137205
+ throw new MockContractError(
137206
+ `CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
137207
+ );
137208
+ }
137209
+ const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
137210
+ return { uid, name, collection, mockUrl, environment, visibility };
137211
+ }
137147
137212
  /**
137148
137213
  * Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
137149
137214
  * model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
@@ -137444,7 +137509,8 @@ var PostmanGatewayAssetsClient = class {
137444
137509
  return this.singleFlight(flightKey, flightKey, "mock", async () => {
137445
137510
  const existing = await this.findMockByCollection(collection, environment, mockName);
137446
137511
  if (existing) {
137447
- return { uid: existing.uid, url: existing.mockUrl };
137512
+ const reusable = requirePublicMock(existing);
137513
+ return { uid: reusable.uid, url: reusable.mockUrl };
137448
137514
  }
137449
137515
  const body = {
137450
137516
  name: mockName,
@@ -137467,13 +137533,12 @@ var PostmanGatewayAssetsClient = class {
137467
137533
  );
137468
137534
  const parseMock = (response) => {
137469
137535
  const record = this.dataOf(response);
137470
- const uid = this.idOf(record);
137471
- if (!uid) {
137472
- throw new Error("Mock create did not return a UID");
137473
- }
137536
+ const created = requirePublicMock(
137537
+ this.decodeMockRecord(record, "Mock create")
137538
+ );
137474
137539
  return {
137475
- uid,
137476
- url: String(record?.url ?? record?.mockUrl ?? "").trim()
137540
+ uid: created.uid,
137541
+ url: created.mockUrl
137477
137542
  };
137478
137543
  };
137479
137544
  try {
@@ -137484,7 +137549,8 @@ var PostmanGatewayAssetsClient = class {
137484
137549
  error2
137485
137550
  );
137486
137551
  if (adopted) {
137487
- return { uid: adopted.uid, url: adopted.mockUrl };
137552
+ const reusable = requirePublicMock(adopted);
137553
+ return { uid: reusable.uid, url: reusable.mockUrl };
137488
137554
  }
137489
137555
  if (adopted === void 0) {
137490
137556
  const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
@@ -137500,14 +137566,14 @@ var PostmanGatewayAssetsClient = class {
137500
137566
  method: "get",
137501
137567
  path: `/mocks?workspace=${this.workspaceId}`
137502
137568
  });
137503
- const items = Array.isArray(response) ? response : Array.isArray(this.asRecord(response)?.data) ? this.asRecord(response).data : [];
137504
- return items.map((raw) => this.asRecord(raw)).filter((m) => m !== null).map((m) => ({
137505
- uid: this.idOf(m),
137506
- name: String(m.name ?? ""),
137507
- collection: String(m.collection ?? ""),
137508
- mockUrl: String(m.url ?? m.mockUrl ?? ""),
137509
- environment: String(m.environment ?? "")
137510
- }));
137569
+ const envelope = this.asRecord(response);
137570
+ const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
137571
+ if (!items) {
137572
+ throw new MockContractError(
137573
+ "CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
137574
+ );
137575
+ }
137576
+ return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
137511
137577
  }
137512
137578
  /**
137513
137579
  * Delete an environment through the sync service (GC path). The path id is
@@ -137561,7 +137627,7 @@ var PostmanGatewayAssetsClient = class {
137561
137627
  `workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
137562
137628
  matches
137563
137629
  );
137564
- return match ? { uid: match.uid, mockUrl: match.mockUrl } : null;
137630
+ return match;
137565
137631
  }
137566
137632
  // --- monitors (service: monitors; collection-based = jobTemplates) ---
137567
137633
  /** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
@@ -138491,6 +138557,61 @@ function resolveRepoSyncMasker(dependencies) {
138491
138557
  function causeText(cause) {
138492
138558
  return cause instanceof Error ? cause.message : String(cause);
138493
138559
  }
138560
+ function normalizeMockUrl(value, source) {
138561
+ let url;
138562
+ try {
138563
+ url = new URL(String(value || "").trim());
138564
+ } catch {
138565
+ throw new MockContractError(
138566
+ `EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
138567
+ );
138568
+ }
138569
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
138570
+ throw new MockContractError(
138571
+ `EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
138572
+ );
138573
+ }
138574
+ url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
138575
+ return url.toString();
138576
+ }
138577
+ function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
138578
+ const normalized = normalizeMockUrl(explicitUrl, "mock-url");
138579
+ const matches = mocks.filter(
138580
+ (mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
138581
+ );
138582
+ if (matches.length === 0) {
138583
+ throw new MockContractError(
138584
+ `EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
138585
+ );
138586
+ }
138587
+ if (matches.length > 1) {
138588
+ throw new MockContractError(
138589
+ `EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
138590
+ );
138591
+ }
138592
+ const match = matches[0];
138593
+ if (match.collection !== collectionUid) {
138594
+ throw new MockContractError(
138595
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
138596
+ );
138597
+ }
138598
+ if (match.environment !== environmentUid) {
138599
+ throw new MockContractError(
138600
+ `EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
138601
+ );
138602
+ }
138603
+ return requirePublicMock(match);
138604
+ }
138605
+ function shouldRetryMockCreate(error2) {
138606
+ if (error2 instanceof MockContractError) return false;
138607
+ if (error2 instanceof HttpError) {
138608
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
138609
+ }
138610
+ if (error2 instanceof Error && error2.message.startsWith("Multiple mocks match ")) {
138611
+ return false;
138612
+ }
138613
+ return true;
138614
+ }
138494
138615
  function toOneLineDisplay(value) {
138495
138616
  return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
138496
138617
  }
@@ -138697,6 +138818,7 @@ function resolveInputs(env = process.env) {
138697
138818
  orgMode: parseBooleanInput(getInput2("org-mode", env), false),
138698
138819
  monitorId: getInput2("monitor-id", env),
138699
138820
  mockUrl: getInput2("mock-url", env),
138821
+ mockEnvironmentEnabled: parseBooleanInput(getInput2("mock-environment-enabled", env), false),
138700
138822
  monitorCron: getInput2("monitor-cron", env),
138701
138823
  sslClientCert: getInput2("ssl-client-cert", env),
138702
138824
  sslClientKey: getInput2("ssl-client-key", env),
@@ -138923,6 +139045,8 @@ function createOutputs(inputs) {
138923
139045
  "environment-sync-status": "skipped",
138924
139046
  "environment-uids-json": JSON.stringify(inputs.environmentUids),
138925
139047
  "mock-url": "",
139048
+ "mock-environment-uid": "",
139049
+ "mock-environment-status": "skipped",
138926
139050
  "monitor-id": "",
138927
139051
  "repo-sync-summary-json": "{}",
138928
139052
  "commit-sha": "",
@@ -138990,6 +139114,7 @@ function readActionInputs(actionCore) {
138990
139114
  INPUT_ORG_MODE: readInput(actionCore, "org-mode"),
138991
139115
  INPUT_MONITOR_ID: readInput(actionCore, "monitor-id"),
138992
139116
  INPUT_MOCK_URL: readInput(actionCore, "mock-url"),
139117
+ INPUT_MOCK_ENVIRONMENT_ENABLED: readInput(actionCore, "mock-environment-enabled"),
138993
139118
  INPUT_MONITOR_CRON: readInput(actionCore, "monitor-cron"),
138994
139119
  INPUT_SSL_CLIENT_CERT: sslClientCert,
138995
139120
  INPUT_SSL_CLIENT_KEY: sslClientKey,
@@ -139191,6 +139316,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
139191
139316
  }
139192
139317
  return envUids;
139193
139318
  }
139319
+ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
139320
+ if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
139321
+ return "";
139322
+ }
139323
+ const displayName = `${assetProjectName} - Mock`;
139324
+ const values = buildEnvironmentValues("mock", mockUrl);
139325
+ const mask = resolveRepoSyncMasker(dependencies);
139326
+ try {
139327
+ const discovered = await dependencies.postman.findEnvironmentByName(
139328
+ inputs.workspaceId,
139329
+ displayName
139330
+ );
139331
+ if (discovered?.uid) {
139332
+ try {
139333
+ const existing = await dependencies.postman.getEnvironment(discovered.uid);
139334
+ const currentValues = existing.data?.values ?? existing.values ?? [];
139335
+ if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
139336
+ dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
139337
+ return discovered.uid;
139338
+ }
139339
+ } catch {
139340
+ }
139341
+ await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
139342
+ dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
139343
+ return discovered.uid;
139344
+ }
139345
+ const uid = await dependencies.postman.createEnvironment(
139346
+ inputs.workspaceId,
139347
+ displayName,
139348
+ values
139349
+ );
139350
+ dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
139351
+ return uid;
139352
+ } catch (error2) {
139353
+ dependencies.core.warning(
139354
+ formatOrchestrationIssue({
139355
+ operation: "Mock environment upsert",
139356
+ entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
139357
+ cause: error2,
139358
+ remediation: "verify environment access or disable mock-environment-enabled then rerun",
139359
+ mask
139360
+ })
139361
+ );
139362
+ return "";
139363
+ }
139364
+ }
139194
139365
  function ensureDir(path9) {
139195
139366
  (0, import_node_fs5.mkdirSync)(path9, { recursive: true });
139196
139367
  }
@@ -139829,6 +140000,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
139829
140000
  true
139830
140001
  );
139831
140002
  }
140003
+ if (options.mockEnvironmentUid) {
140004
+ writeJsonFile(
140005
+ `${mocksDir}/manual-validation.postman_environment.json`,
140006
+ await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
140007
+ true
140008
+ );
140009
+ }
139832
140010
  const durableWorkspaceId = resolveDurableWorkspaceId({
139833
140011
  candidateId: inputs.workspaceId,
139834
140012
  priorId: options.priorWorkspaceId,
@@ -139887,6 +140065,8 @@ function createRepoSummary(outputs, envUids, pushed) {
139887
140065
  commitSha: outputs["commit-sha"],
139888
140066
  environmentCount: Object.keys(envUids).length,
139889
140067
  environmentSyncStatus: outputs["environment-sync-status"],
140068
+ mockEnvironmentStatus: outputs["mock-environment-status"],
140069
+ mockEnvironmentUid: outputs["mock-environment-uid"],
139890
140070
  mockUrl: outputs["mock-url"],
139891
140071
  monitorId: outputs["monitor-id"],
139892
140072
  pushed,
@@ -140117,20 +140297,37 @@ async function runRepoSyncInner(inputs, dependencies) {
140117
140297
  let resolvedMockUrl = "";
140118
140298
  const mockName = `${assetProjectName} Mock`;
140119
140299
  if (inputs.mockUrl) {
140120
- resolvedMockUrl = inputs.mockUrl;
140300
+ let mocks;
140301
+ try {
140302
+ mocks = await dependencies.postman.listMocks();
140303
+ } catch (error2) {
140304
+ throw new Error(
140305
+ formatOrchestrationIssue({
140306
+ operation: "Explicit mock-url lookup",
140307
+ entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
140308
+ cause: error2,
140309
+ remediation: "verify the URL and mock access then rerun",
140310
+ mask
140311
+ }),
140312
+ { cause: error2 }
140313
+ );
140314
+ }
140315
+ resolvedMockUrl = resolveExplicitPublicMock(
140316
+ mocks,
140317
+ inputs.mockUrl,
140318
+ inputs.baselineCollectionId,
140319
+ mockEnvUid
140320
+ ).mockUrl;
140121
140321
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
140122
140322
  }
140123
140323
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
140324
+ let discovered;
140124
140325
  try {
140125
- const discovered = await dependencies.postman.findMockByCollection(
140326
+ discovered = await dependencies.postman.findMockByCollection(
140126
140327
  inputs.baselineCollectionId,
140127
140328
  mockEnvUid,
140128
140329
  mockName
140129
140330
  );
140130
- if (discovered) {
140131
- resolvedMockUrl = discovered.mockUrl;
140132
- dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
140133
- }
140134
140331
  } catch (error2) {
140135
140332
  throw new Error(
140136
140333
  formatOrchestrationIssue({
@@ -140143,6 +140340,10 @@ async function runRepoSyncInner(inputs, dependencies) {
140143
140340
  { cause: error2 }
140144
140341
  );
140145
140342
  }
140343
+ if (discovered) {
140344
+ resolvedMockUrl = requirePublicMock(discovered).mockUrl;
140345
+ dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
140346
+ }
140146
140347
  }
140147
140348
  if (!resolvedMockUrl) {
140148
140349
  const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
@@ -140159,6 +140360,7 @@ async function runRepoSyncInner(inputs, dependencies) {
140159
140360
  maxAttempts: 3,
140160
140361
  delayMs: 2e3,
140161
140362
  backoffMultiplier: 2,
140363
+ shouldRetry: (error2) => shouldRetryMockCreate(error2),
140162
140364
  onRetry: ({ attempt, maxAttempts, error: error2 }) => {
140163
140365
  dependencies.core.warning(
140164
140366
  formatOrchestrationIssue({
@@ -140190,6 +140392,20 @@ async function runRepoSyncInner(inputs, dependencies) {
140190
140392
  }
140191
140393
  outputs["mock-url"] = resolvedMockUrl;
140192
140394
  dependencies.core.setOutput("mock-url", resolvedMockUrl);
140395
+ if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
140396
+ const mockEnvironmentUid = await upsertMockEnvironment(
140397
+ inputs,
140398
+ dependencies,
140399
+ assetProjectName,
140400
+ resolvedMockUrl
140401
+ );
140402
+ outputs["mock-environment-uid"] = mockEnvironmentUid;
140403
+ outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
140404
+ } else if (inputs.mockEnvironmentEnabled) {
140405
+ dependencies.core.warning(
140406
+ "mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
140407
+ );
140408
+ }
140193
140409
  }
140194
140410
  }
140195
140411
  if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
@@ -140322,6 +140538,7 @@ async function runRepoSyncInner(inputs, dependencies) {
140322
140538
  workspaceLinkStatus: outputs["workspace-link-status"],
140323
140539
  priorWorkspaceId: resourcesState?.workspace?.id,
140324
140540
  existingSpecs: resourcesState?.cloudResources?.specs,
140541
+ mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
140325
140542
  releaseLabel,
140326
140543
  priorState: resourcesState,
140327
140544
  preparedPrebuiltCollections
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-repo-sync",
3
- "version": "2.1.18",
3
+ "version": "2.2.0",
4
4
  "description": "Postman repo sync GitHub Action.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",