@postman-cse/onboarding-repo-sync 2.1.1 → 2.1.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
@@ -252,6 +252,8 @@ For `commit-and-push`, the push target is resolved from `current-ref`, then `GIT
252
252
 
253
253
  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.
254
254
 
255
+ 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.
256
+
255
257
  Deeper reference:
256
258
 
257
259
  - [Artifact layout and Collection v3 format](docs/artifact-layout.md), including sync modes and versioned releases.
package/dist/action.cjs CHANGED
@@ -127291,12 +127291,20 @@ async function retry(operation, options = {}) {
127291
127291
  }
127292
127292
 
127293
127293
  // src/lib/postman/postman-gateway-assets-client.ts
127294
+ var MAX_CREATE_FLIGHTS = 256;
127295
+ var createFlights = /* @__PURE__ */ new Map();
127294
127296
  var PostmanGatewayAssetsClient = class {
127295
127297
  gateway;
127296
127298
  workspaceId;
127299
+ sleep;
127300
+ reconcileAttempts;
127301
+ reconcileDelayMs;
127297
127302
  constructor(options) {
127298
127303
  this.gateway = options.gateway;
127299
127304
  this.workspaceId = String(options.workspaceId || "").trim();
127305
+ this.sleep = options.sleep ?? sleep;
127306
+ this.reconcileAttempts = Math.max(1, options.reconcileAttempts ?? 3);
127307
+ this.reconcileDelayMs = Math.max(0, options.reconcileDelayMs ?? 500);
127300
127308
  }
127301
127309
  asRecord(value) {
127302
127310
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -127326,8 +127334,64 @@ var PostmanGatewayAssetsClient = class {
127326
127334
  const parts = trimmed.split("-");
127327
127335
  return parts.length >= 6 ? parts.slice(1).join("-") : trimmed;
127328
127336
  }
127329
- isTransient(error2) {
127330
- return error2 instanceof HttpError && (error2.status === 429 || error2.status >= 500);
127337
+ isAmbiguousCreateOutcome(error2) {
127338
+ if (error2 instanceof HttpError) {
127339
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
127340
+ }
127341
+ return error2 instanceof Error;
127342
+ }
127343
+ isRetryableIdempotentWriteOutcome(error2) {
127344
+ if (error2 instanceof HttpError) {
127345
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
127346
+ }
127347
+ return error2 instanceof Error;
127348
+ }
127349
+ selectExactMatch(kind, identity, matches) {
127350
+ if (matches.length > 1) {
127351
+ const ids = matches.map((match) => match.uid).join(", ");
127352
+ throw new Error(
127353
+ `Multiple ${kind}s match ${identity}: ${ids}. Refusing to choose one; remove duplicates or pass an explicit asset ID.`
127354
+ );
127355
+ }
127356
+ return matches[0] ?? null;
127357
+ }
127358
+ async singleFlight(key, fingerprint, kind, operation) {
127359
+ const existing = createFlights.get(key);
127360
+ if (existing) {
127361
+ if (existing.fingerprint !== fingerprint) {
127362
+ throw new Error(`Incompatible concurrent ${kind} create for ${key}`);
127363
+ }
127364
+ return existing.promise;
127365
+ }
127366
+ if (createFlights.size >= MAX_CREATE_FLIGHTS) {
127367
+ throw new Error(`Too many concurrent Postman asset creates (limit ${MAX_CREATE_FLIGHTS})`);
127368
+ }
127369
+ const pending = operation().finally(() => {
127370
+ if (createFlights.get(key)?.promise === pending) {
127371
+ createFlights.delete(key);
127372
+ }
127373
+ });
127374
+ createFlights.set(key, { fingerprint, promise: pending });
127375
+ return pending;
127376
+ }
127377
+ async discoverAfterAmbiguousCreate(discover, error2) {
127378
+ if (!this.isAmbiguousCreateOutcome(error2)) {
127379
+ return null;
127380
+ }
127381
+ for (let attempt = 0; attempt < this.reconcileAttempts; attempt += 1) {
127382
+ if (attempt > 0) {
127383
+ await this.sleep(this.reconcileDelayMs);
127384
+ }
127385
+ const found = await discover();
127386
+ if (found) {
127387
+ return found;
127388
+ }
127389
+ }
127390
+ return null;
127391
+ }
127392
+ publicEnvironmentUid(data, bareId) {
127393
+ const owner = String(data?.owner ?? "").trim();
127394
+ return owner && !bareId.startsWith(`${owner}-`) ? `${owner}-${bareId}` : bareId;
127331
127395
  }
127332
127396
  // --- environments (service: sync) ---
127333
127397
  //
@@ -127338,9 +127402,10 @@ var PostmanGatewayAssetsClient = class {
127338
127402
  /**
127339
127403
  * Create/upsert an environment through the sync service.
127340
127404
  * POST /environment/import?workspace=:ws { id:<uuid>, name, values } ->
127341
- * { data:{ id:<bare uuid>, owner } }. The id is generated once and reused
127342
- * across retries so the import is idempotent (a retry upserts the same
127343
- * environment instead of duplicating it).
127405
+ * { data:{ id:<bare uuid>, owner } }. The id is generated once for the
127406
+ * operation. Unsafe creates submit once (no blind retry); on an ambiguous
127407
+ * response the client discovers by exact workspace-scoped name before
127408
+ * failing.
127344
127409
  *
127345
127410
  * Returns the PUBLIC uid (`<owner>-<uuid>`), not the bare model id the sync
127346
127411
  * import echoes: mock/monitor create reference the environment by its public
@@ -127350,33 +127415,54 @@ var PostmanGatewayAssetsClient = class {
127350
127415
  */
127351
127416
  async createEnvironment(workspaceId, name, values) {
127352
127417
  const ws = workspaceId || this.workspaceId;
127353
- const id = crypto.randomUUID();
127354
- const body = {
127355
- id,
127356
- name,
127357
- values: values.map((v) => ({
127358
- key: v.key,
127359
- value: v.value,
127360
- type: v.type ?? "default",
127361
- enabled: v.enabled ?? true
127362
- }))
127363
- };
127364
- const response = await retry(
127365
- () => this.gateway.requestJson({
127366
- service: "sync",
127367
- method: "post",
127368
- path: `/environment/import?workspace=${ws}`,
127369
- body
127370
- }),
127371
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127372
- );
127373
- const data = this.dataOf(response);
127374
- const bareId = this.idOf(data);
127375
- if (!bareId) {
127376
- throw new Error("Environment import did not return a UID");
127377
- }
127378
- const owner = String(data?.owner ?? "").trim();
127379
- return owner && !bareId.startsWith(`${owner}-`) ? `${owner}-${bareId}` : bareId;
127418
+ const envName = String(name ?? "").trim();
127419
+ const flightKey = `environment:${ws}:${envName}`;
127420
+ const normalizedValues = values.map((v) => ({
127421
+ key: v.key,
127422
+ value: v.value,
127423
+ type: v.type ?? "default",
127424
+ enabled: v.enabled ?? true
127425
+ }));
127426
+ return this.singleFlight(flightKey, JSON.stringify(normalizedValues), "environment", async () => {
127427
+ const existing = await this.findEnvironmentByName(ws, envName);
127428
+ if (existing?.uid) {
127429
+ await this.updateEnvironment(existing.uid, envName, values);
127430
+ return existing.uid;
127431
+ }
127432
+ const id = crypto.randomUUID();
127433
+ const body = {
127434
+ id,
127435
+ name: envName,
127436
+ values: normalizedValues
127437
+ };
127438
+ try {
127439
+ const response = await this.gateway.requestJson(
127440
+ {
127441
+ service: "sync",
127442
+ method: "post",
127443
+ path: `/environment/import?workspace=${ws}`,
127444
+ body
127445
+ },
127446
+ { retryTransient: false }
127447
+ );
127448
+ const data = this.dataOf(response);
127449
+ const bareId = this.idOf(data);
127450
+ if (!bareId) {
127451
+ throw new Error("Environment import did not return a UID");
127452
+ }
127453
+ return this.publicEnvironmentUid(data, bareId);
127454
+ } catch (error2) {
127455
+ const adopted = await this.discoverAfterAmbiguousCreate(async () => {
127456
+ const match = await this.findEnvironmentByName(ws, envName);
127457
+ return match?.uid ?? null;
127458
+ }, error2);
127459
+ if (adopted) {
127460
+ await this.updateEnvironment(adopted, envName, values);
127461
+ return adopted;
127462
+ }
127463
+ throw error2;
127464
+ }
127465
+ });
127380
127466
  }
127381
127467
  /**
127382
127468
  * Update an existing environment through the sync service.
@@ -127406,7 +127492,7 @@ var PostmanGatewayAssetsClient = class {
127406
127492
  path: `/environment/${id}`,
127407
127493
  body
127408
127494
  }),
127409
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127495
+ { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isRetryableIdempotentWriteOutcome(e) }
127410
127496
  );
127411
127497
  }
127412
127498
  /**
@@ -127430,13 +127516,37 @@ var PostmanGatewayAssetsClient = class {
127430
127516
  */
127431
127517
  async listEnvironments(workspaceId) {
127432
127518
  const ws = workspaceId || this.workspaceId;
127433
- const response = await this.gateway.requestJson({
127434
- service: "sync",
127435
- method: "post",
127436
- path: `/list/environment?workspace=${ws}`
127437
- });
127519
+ const response = await this.gateway.requestJson(
127520
+ {
127521
+ service: "sync",
127522
+ method: "post",
127523
+ path: `/list/environment?workspace=${ws}`
127524
+ },
127525
+ { retryTransient: true }
127526
+ );
127438
127527
  const items = Array.isArray(response?.data) ? response.data : [];
127439
- return items.map((raw) => this.asRecord(raw)).filter((e) => e !== null).map((e) => ({ name: String(e.name ?? ""), uid: this.idOf(e) }));
127528
+ return items.map((raw) => this.asRecord(raw)).filter((e) => e !== null).map((e) => {
127529
+ const bareOrPublic = this.idOf(e);
127530
+ return {
127531
+ name: String(e.name ?? ""),
127532
+ uid: this.publicEnvironmentUid(e, bareOrPublic)
127533
+ };
127534
+ });
127535
+ }
127536
+ /** Exact name match within a workspace. Prefer tracked UIDs before calling. */
127537
+ async findEnvironmentByName(workspaceId, name) {
127538
+ const want = String(name ?? "").trim();
127539
+ if (!want) {
127540
+ return null;
127541
+ }
127542
+ const environments = await this.listEnvironments(workspaceId);
127543
+ const matches = environments.filter((entry) => entry.name === want);
127544
+ const match = this.selectExactMatch(
127545
+ "environment",
127546
+ `workspace ${workspaceId} and name "${want}"`,
127547
+ matches
127548
+ );
127549
+ return match?.uid ? { uid: match.uid, name: match.name } : null;
127440
127550
  }
127441
127551
  // --- collection read (service: collection, v3 export) ---
127442
127552
  //
@@ -127466,32 +127576,51 @@ var PostmanGatewayAssetsClient = class {
127466
127576
  // --- mocks (service: mock) ---
127467
127577
  async createMock(workspaceId, name, collectionUid, environmentUid) {
127468
127578
  const ws = workspaceId || this.workspaceId;
127579
+ const mockName = String(name ?? "").trim();
127469
127580
  const collection = String(collectionUid ?? "").trim();
127470
127581
  const environment = String(environmentUid ?? "").trim();
127471
- const body = {
127472
- name,
127473
- collection,
127474
- private: false,
127475
- ...environment ? { environment } : {}
127476
- };
127477
- const response = await retry(
127478
- () => this.gateway.requestJson({
127479
- service: "mock",
127480
- method: "post",
127481
- path: `/mocks?workspace=${ws}`,
127482
- body
127483
- }),
127484
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127485
- );
127486
- const record = this.dataOf(response);
127487
- const uid = this.idOf(record);
127488
- if (!uid) {
127489
- throw new Error("Mock create did not return a UID");
127490
- }
127491
- return {
127492
- uid,
127493
- url: String(record?.url ?? record?.mockUrl ?? "").trim()
127494
- };
127582
+ const flightKey = `mock:${ws}:${collection}:${environment}:${mockName}`;
127583
+ return this.singleFlight(flightKey, flightKey, "mock", async () => {
127584
+ const existing = await this.findMockByCollection(collection, environment, mockName);
127585
+ if (existing) {
127586
+ return { uid: existing.uid, url: existing.mockUrl };
127587
+ }
127588
+ const body = {
127589
+ name: mockName,
127590
+ collection,
127591
+ private: false,
127592
+ ...environment ? { environment } : {}
127593
+ };
127594
+ try {
127595
+ const response = await this.gateway.requestJson(
127596
+ {
127597
+ service: "mock",
127598
+ method: "post",
127599
+ path: `/mocks?workspace=${ws}`,
127600
+ body
127601
+ },
127602
+ { retryTransient: false }
127603
+ );
127604
+ const record = this.dataOf(response);
127605
+ const uid = this.idOf(record);
127606
+ if (!uid) {
127607
+ throw new Error("Mock create did not return a UID");
127608
+ }
127609
+ return {
127610
+ uid,
127611
+ url: String(record?.url ?? record?.mockUrl ?? "").trim()
127612
+ };
127613
+ } catch (error2) {
127614
+ const adopted = await this.discoverAfterAmbiguousCreate(
127615
+ () => this.findMockByCollection(collection, environment, mockName),
127616
+ error2
127617
+ );
127618
+ if (adopted) {
127619
+ return { uid: adopted.uid, url: adopted.mockUrl };
127620
+ }
127621
+ throw error2;
127622
+ }
127623
+ });
127495
127624
  }
127496
127625
  async listMocks() {
127497
127626
  const response = await this.gateway.requestJson({
@@ -127520,10 +127649,19 @@ var PostmanGatewayAssetsClient = class {
127520
127649
  return false;
127521
127650
  }
127522
127651
  }
127523
- async findMockByCollection(collectionUid) {
127652
+ async findMockByCollection(collectionUid, environmentUid, name) {
127524
127653
  const mocks = await this.listMocks();
127525
127654
  const want = String(collectionUid ?? "").trim();
127526
- const match = mocks.find((m) => m.collection === want);
127655
+ const environment = String(environmentUid ?? "").trim();
127656
+ const mockName = String(name ?? "").trim();
127657
+ const matches = mocks.filter(
127658
+ (mock) => mock.collection === want && mock.environment === environment && mock.name === mockName
127659
+ );
127660
+ const match = this.selectExactMatch(
127661
+ "mock",
127662
+ `workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
127663
+ matches
127664
+ );
127527
127665
  return match ? { uid: match.uid, mockUrl: match.mockUrl } : null;
127528
127666
  }
127529
127667
  // --- monitors (service: monitors; collection-based = jobTemplates) ---
@@ -127540,32 +127678,51 @@ var PostmanGatewayAssetsClient = class {
127540
127678
  async createMonitor(workspaceId, name, collectionUid, environmentUid, cronSchedule) {
127541
127679
  const ws = workspaceId || this.workspaceId;
127542
127680
  const effectiveCron = cronSchedule && cronSchedule.trim() ? cronSchedule.trim() : "0 0 * * 0";
127681
+ const monitorName = String(name ?? "").trim();
127543
127682
  const collection = String(collectionUid ?? "").trim();
127544
127683
  const environment = String(environmentUid ?? "").trim();
127545
- const body = {
127546
- name,
127547
- collection,
127548
- options: { strictSSL: false, followRedirects: true, requestTimeout: null, requestDelay: 0 },
127549
- notifications: { onFailure: [], onError: [] },
127550
- retry: {},
127551
- schedule: { cronPattern: effectiveCron, timeZone: "UTC" },
127552
- distribution: null,
127553
- ...environment ? { environment } : {}
127554
- };
127555
- const response = await retry(
127556
- () => this.gateway.requestJson({
127557
- service: "monitors",
127558
- method: "post",
127559
- path: `/jobTemplates?workspace=${ws}`,
127560
- body
127561
- }),
127562
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127563
- );
127564
- const uid = this.idOf(this.dataOf(response));
127565
- if (!uid) {
127566
- throw new Error("Monitor create did not return a UID");
127567
- }
127568
- return uid;
127684
+ const flightKey = `monitor:${ws}:${collection}:${environment}:${monitorName}`;
127685
+ return this.singleFlight(flightKey, effectiveCron, "monitor", async () => {
127686
+ const existing = await this.findMonitorByCollection(collection, environment, monitorName);
127687
+ if (existing?.uid) {
127688
+ return existing.uid;
127689
+ }
127690
+ const body = {
127691
+ name: monitorName,
127692
+ collection,
127693
+ options: { strictSSL: false, followRedirects: true, requestTimeout: null, requestDelay: 0 },
127694
+ notifications: { onFailure: [], onError: [] },
127695
+ retry: {},
127696
+ schedule: { cronPattern: effectiveCron, timeZone: "UTC" },
127697
+ distribution: null,
127698
+ ...environment ? { environment } : {}
127699
+ };
127700
+ try {
127701
+ const response = await this.gateway.requestJson(
127702
+ {
127703
+ service: "monitors",
127704
+ method: "post",
127705
+ path: `/jobTemplates?workspace=${ws}`,
127706
+ body
127707
+ },
127708
+ { retryTransient: false }
127709
+ );
127710
+ const uid = this.idOf(this.dataOf(response));
127711
+ if (!uid) {
127712
+ throw new Error("Monitor create did not return a UID");
127713
+ }
127714
+ return uid;
127715
+ } catch (error2) {
127716
+ const adopted = await this.discoverAfterAmbiguousCreate(
127717
+ () => this.findMonitorByCollection(collection, environment, monitorName),
127718
+ error2
127719
+ );
127720
+ if (adopted?.uid) {
127721
+ return adopted.uid;
127722
+ }
127723
+ throw error2;
127724
+ }
127725
+ });
127569
127726
  }
127570
127727
  async listMonitors() {
127571
127728
  const response = await this.gateway.requestJson({
@@ -127588,24 +127745,30 @@ var PostmanGatewayAssetsClient = class {
127588
127745
  return false;
127589
127746
  }
127590
127747
  }
127591
- async findMonitorByCollection(collectionUid) {
127748
+ async findMonitorByCollection(collectionUid, environmentUid, name) {
127592
127749
  const want = String(collectionUid ?? "").trim();
127593
- const response = await this.gateway.requestJson({
127594
- service: "monitors",
127595
- method: "get",
127596
- path: `/collections/${want}/jobTemplates?_etc=true`
127597
- });
127598
- const data = response?.data;
127599
- const monitors = this.mapJobTemplates(Array.isArray(data) ? data : []);
127600
- const match = monitors.find((m) => m.collectionUid === want) ?? monitors[0];
127750
+ const environment = String(environmentUid ?? "").trim();
127751
+ const monitorName = String(name ?? "").trim();
127752
+ const monitors = await this.listMonitors();
127753
+ const matches = monitors.filter(
127754
+ (monitor) => monitor.collectionUid === want && monitor.environmentUid === environment && monitor.name === monitorName
127755
+ );
127756
+ const match = this.selectExactMatch(
127757
+ "monitor",
127758
+ `workspace ${this.workspaceId}, name "${monitorName}", collection ${want}, and environment ${environment || "(none)"}`,
127759
+ matches
127760
+ );
127601
127761
  return match?.uid ? { uid: match.uid, name: match.name } : null;
127602
127762
  }
127603
127763
  async runMonitor(uid) {
127604
- await this.gateway.requestJson({
127605
- service: "monitors",
127606
- method: "post",
127607
- path: `/jobTemplates/${uid}/jobs`
127608
- });
127764
+ await this.gateway.requestJson(
127765
+ {
127766
+ service: "monitors",
127767
+ method: "post",
127768
+ path: `/jobTemplates/${uid}/jobs`
127769
+ },
127770
+ { retryTransient: false }
127771
+ );
127609
127772
  }
127610
127773
  };
127611
127774
 
@@ -127771,12 +127934,8 @@ async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetc
127771
127934
  function isExpiredAuthError(status, body) {
127772
127935
  return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
127773
127936
  }
127774
- function isTransientGatewayError(status, body) {
127775
- if (status === 502 || status === 503 || status === 504) return true;
127776
- if (status >= 500 && (body.includes("ESOCKETTIMEDOUT") || body.includes("ETIMEDOUT") || body.includes("ECONNRESET") || body.includes("serverError") || body.includes("downstream"))) {
127777
- return true;
127778
- }
127779
- return false;
127937
+ function isRetryableSafeReadResponse(status) {
127938
+ return status === 408 || status === 429 || status >= 500;
127780
127939
  }
127781
127940
  function defaultSleep(ms) {
127782
127941
  return new Promise((resolve3) => setTimeout(resolve3, ms));
@@ -127835,14 +127994,28 @@ var AccessTokenGatewayClient = class {
127835
127994
  }
127836
127995
  /**
127837
127996
  * Send a gateway request, refreshing the token once on an auth failure and
127838
- * retrying transient downstream failures (5xx / Bifrost read timeouts) with
127839
- * exponential backoff. The auth-refresh-once path is independent of the
127840
- * transient-retry budget.
127997
+ * optionally retrying transient downstream failures (5xx / Bifrost read
127998
+ * timeouts) with exponential backoff. Safe reads keep transient retries;
127999
+ * unsafe creates pass `{ retryTransient: false }` and reconcile after an
128000
+ * ambiguous response instead of re-POSTing. Auth refresh remains independent
128001
+ * of the transient-retry budget.
127841
128002
  */
127842
- async request(request) {
128003
+ async request(request, options = {}) {
128004
+ const retryTransient = options.retryTransient ?? request.method === "get";
127843
128005
  let attempt = 0;
127844
128006
  for (; ; ) {
127845
- let response = await this.send(request);
128007
+ let response;
128008
+ try {
128009
+ response = await this.send(request);
128010
+ } catch (error2) {
128011
+ if (retryTransient && attempt < this.maxRetries) {
128012
+ const delay = this.retryBaseDelayMs * 2 ** attempt;
128013
+ attempt += 1;
128014
+ await this.sleepImpl(delay);
128015
+ continue;
128016
+ }
128017
+ throw error2;
128018
+ }
127846
128019
  if (response.ok) {
127847
128020
  return response;
127848
128021
  }
@@ -127856,7 +128029,7 @@ var AccessTokenGatewayClient = class {
127856
128029
  const retryBody = await response.text().catch(() => "");
127857
128030
  throw this.toHttpError(request, response, retryBody);
127858
128031
  }
127859
- if (isTransientGatewayError(response.status, body) && attempt < this.maxRetries) {
128032
+ if (retryTransient && isRetryableSafeReadResponse(response.status) && attempt < this.maxRetries) {
127860
128033
  const delay = this.retryBaseDelayMs * 2 ** attempt;
127861
128034
  attempt += 1;
127862
128035
  await this.sleepImpl(delay);
@@ -127866,8 +128039,8 @@ var AccessTokenGatewayClient = class {
127866
128039
  }
127867
128040
  }
127868
128041
  /** Send a gateway request and parse the JSON body, or null when empty. */
127869
- async requestJson(request) {
127870
- const response = await this.request(request);
128042
+ async requestJson(request, options = {}) {
128043
+ const response = await this.request(request, options);
127871
128044
  const text = await response.text().catch(() => "");
127872
128045
  if (!text.trim()) {
127873
128046
  return null;
@@ -128459,18 +128632,28 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128459
128632
  for (const envName of inputs.environments) {
128460
128633
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
128461
128634
  const values = buildEnvironmentValues(envName, runtimeUrl);
128462
- const existingUid = envUids[envName];
128463
- if (existingUid) {
128464
- await dependencies.postman.updateEnvironment(
128465
- existingUid,
128466
- `${inputs.projectName} - ${envName}`,
128467
- values
128635
+ const displayName = `${inputs.projectName} - ${envName}`;
128636
+ let existingUid = String(envUids[envName] || "").trim();
128637
+ if (!existingUid) {
128638
+ const discovered = await dependencies.postman.findEnvironmentByName(
128639
+ inputs.workspaceId,
128640
+ displayName
128468
128641
  );
128642
+ if (discovered?.uid) {
128643
+ existingUid = discovered.uid;
128644
+ dependencies.core.info(
128645
+ `Discovered existing environment for ${displayName}: ${existingUid}`
128646
+ );
128647
+ }
128648
+ }
128649
+ if (existingUid) {
128650
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values);
128651
+ envUids[envName] = existingUid;
128469
128652
  continue;
128470
128653
  }
128471
128654
  envUids[envName] = await dependencies.postman.createEnvironment(
128472
128655
  inputs.workspaceId,
128473
- `${inputs.projectName} - ${envName}`,
128656
+ displayName,
128474
128657
  values
128475
128658
  );
128476
128659
  }
@@ -128841,12 +129024,17 @@ async function runRepoSyncInner(inputs, dependencies) {
128841
129024
  const mockEnvUid = envUids.dev || envUids.prod || Object.values(envUids)[0];
128842
129025
  if (mockEnvUid) {
128843
129026
  let resolvedMockUrl = "";
129027
+ const mockName = `${assetProjectName} Mock`;
128844
129028
  if (inputs.mockUrl) {
128845
129029
  resolvedMockUrl = inputs.mockUrl;
128846
129030
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
128847
129031
  }
128848
129032
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
128849
- const discovered = await dependencies.postman.findMockByCollection(inputs.baselineCollectionId);
129033
+ const discovered = await dependencies.postman.findMockByCollection(
129034
+ inputs.baselineCollectionId,
129035
+ mockEnvUid,
129036
+ mockName
129037
+ );
128850
129038
  if (discovered) {
128851
129039
  resolvedMockUrl = discovered.mockUrl;
128852
129040
  dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
@@ -128855,7 +129043,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128855
129043
  if (!resolvedMockUrl) {
128856
129044
  const mock = await dependencies.postman.createMock(
128857
129045
  inputs.workspaceId,
128858
- `${assetProjectName} Mock`,
129046
+ mockName,
128859
129047
  inputs.baselineCollectionId,
128860
129048
  mockEnvUid
128861
129049
  );
@@ -128870,6 +129058,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128870
129058
  const effectiveCron = inputs.monitorCron && inputs.monitorCron.trim() ? inputs.monitorCron.trim() : "";
128871
129059
  if (monitorEnvUid && inputs.monitorType !== "cli") {
128872
129060
  let resolvedMonitorId = "";
129061
+ const monitorName = `${assetProjectName} - Smoke Monitor`;
128873
129062
  if (inputs.monitorId) {
128874
129063
  const valid = await dependencies.postman.monitorExists(inputs.monitorId);
128875
129064
  if (valid) {
@@ -128880,7 +129069,11 @@ async function runRepoSyncInner(inputs, dependencies) {
128880
129069
  }
128881
129070
  }
128882
129071
  if (!resolvedMonitorId && inputs.smokeCollectionId) {
128883
- const discovered = await dependencies.postman.findMonitorByCollection(inputs.smokeCollectionId);
129072
+ const discovered = await dependencies.postman.findMonitorByCollection(
129073
+ inputs.smokeCollectionId,
129074
+ monitorEnvUid,
129075
+ monitorName
129076
+ );
128884
129077
  if (discovered) {
128885
129078
  resolvedMonitorId = discovered.uid;
128886
129079
  dependencies.core.info(`Discovered existing monitor for collection ${inputs.smokeCollectionId}: ${resolvedMonitorId}`);
@@ -128889,7 +129082,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128889
129082
  if (!resolvedMonitorId) {
128890
129083
  resolvedMonitorId = await dependencies.postman.createMonitor(
128891
129084
  inputs.workspaceId,
128892
- `${assetProjectName} - Smoke Monitor`,
129085
+ monitorName,
128893
129086
  inputs.smokeCollectionId,
128894
129087
  monitorEnvUid,
128895
129088
  effectiveCron || void 0
@@ -129102,6 +129295,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129102
129295
  createEnvironment: gatewayAssets.createEnvironment.bind(gatewayAssets),
129103
129296
  getEnvironment: gatewayAssets.getEnvironment.bind(gatewayAssets),
129104
129297
  updateEnvironment: gatewayAssets.updateEnvironment.bind(gatewayAssets),
129298
+ findEnvironmentByName: gatewayAssets.findEnvironmentByName.bind(gatewayAssets),
129105
129299
  // Collection read via the v3 export endpoint — returns canonical v3 IR,
129106
129300
  // written to disk by `convertAndSplitAnyCollection`. PMAK is never used for
129107
129301
  // collection reads.