@postman-cse/onboarding-repo-sync 2.1.1 → 2.1.3

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/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
  }
@@ -128516,10 +128699,33 @@ function writeJsonFile(path9, content, normalize3 = false) {
128516
128699
  const data = normalize3 ? stripVolatileFields(content) : content;
128517
128700
  (0, import_node_fs3.writeFileSync)(path9, JSON.stringify(data, null, 2));
128518
128701
  }
128519
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId) {
128520
- const manifest = {
128521
- workspace: { id: workspaceId }
128522
- };
128702
+ function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
128703
+ if (specSyncMode !== "version") {
128704
+ return mappedSource;
128705
+ }
128706
+ const normalized = normalizeReleaseLabel(releaseLabel || "");
128707
+ if (!normalized) {
128708
+ return void 0;
128709
+ }
128710
+ return `${mappedSource}#release=${normalized}`;
128711
+ }
128712
+ function resolveDurableWorkspaceId(options) {
128713
+ const { candidateId, priorId, workspaceLinkEnabled, workspaceLinkStatus } = options;
128714
+ const candidate = candidateId.trim();
128715
+ const prior = priorId?.trim() || void 0;
128716
+ if (!workspaceLinkEnabled) {
128717
+ return candidate || prior;
128718
+ }
128719
+ if (workspaceLinkStatus === "success") {
128720
+ return candidate || void 0;
128721
+ }
128722
+ return prior === candidate ? prior : void 0;
128723
+ }
128724
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
128725
+ const manifest = {};
128726
+ if (workspaceId) {
128727
+ manifest.workspace = { id: workspaceId };
128728
+ }
128523
128729
  const localResources = {};
128524
128730
  const cloudResources = {};
128525
128731
  const collectionKeys = Object.keys(collectionMap);
@@ -128540,8 +128746,12 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128540
128746
  if (localSpecRefs.length > 0) {
128541
128747
  localResources.specs = localSpecRefs;
128542
128748
  }
128749
+ const specs = { ...existingSpecs || {} };
128543
128750
  if (mappedSpecRef && specId) {
128544
- cloudResources.specs = { [mappedSpecRef]: specId };
128751
+ specs[mappedSpecRef] = specId;
128752
+ }
128753
+ if (Object.keys(specs).length > 0) {
128754
+ cloudResources.specs = specs;
128545
128755
  }
128546
128756
  if (Object.keys(localResources).length > 0) {
128547
128757
  manifest.localResources = localResources;
@@ -128608,7 +128818,7 @@ function assertPathWithinCwd(targetPath, fieldName) {
128608
128818
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128609
128819
  }
128610
128820
  }
128611
- async function exportArtifacts(inputs, dependencies, envUids, assetProjectName) {
128821
+ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName, options) {
128612
128822
  if (!inputs.workspaceId) {
128613
128823
  return;
128614
128824
  }
@@ -128642,6 +128852,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
128642
128852
  const manifestCollections = {};
128643
128853
  const discoveredSpecs = scanLocalSpecReferences();
128644
128854
  const mappedSpec = resolveMappedSpecReference(inputs.specPath, discoveredSpecs);
128855
+ const mappedSpecCloudKey = mappedSpec && inputs.specId ? buildMappedSpecCloudKey(
128856
+ mappedSpec.configRelativePath,
128857
+ inputs.specSyncMode,
128858
+ options.releaseLabel
128859
+ ) : void 0;
128645
128860
  if (inputs.baselineCollectionId) {
128646
128861
  const col = await dependencies.postman.getCollection(inputs.baselineCollectionId);
128647
128862
  const dirName = getCollectionDirectoryName("Baseline", assetProjectName);
@@ -128667,14 +128882,21 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
128667
128882
  true
128668
128883
  );
128669
128884
  }
128885
+ const durableWorkspaceId = resolveDurableWorkspaceId({
128886
+ candidateId: inputs.workspaceId,
128887
+ priorId: options.priorWorkspaceId,
128888
+ workspaceLinkEnabled: inputs.workspaceLinkEnabled,
128889
+ workspaceLinkStatus: options.workspaceLinkStatus
128890
+ });
128670
128891
  (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
128671
- inputs.workspaceId,
128892
+ durableWorkspaceId,
128672
128893
  manifestCollections,
128673
128894
  envUids,
128674
128895
  inputs.artifactDir,
128675
128896
  discoveredSpecs.map((spec) => spec.configRelativePath),
128676
- mappedSpec?.configRelativePath,
128677
- inputs.specId || void 0
128897
+ mappedSpecCloudKey,
128898
+ inputs.specId || void 0,
128899
+ options.existingSpecs
128678
128900
  ));
128679
128901
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
128680
128902
  (0, import_node_fs3.writeFileSync)(
@@ -128841,12 +129063,17 @@ async function runRepoSyncInner(inputs, dependencies) {
128841
129063
  const mockEnvUid = envUids.dev || envUids.prod || Object.values(envUids)[0];
128842
129064
  if (mockEnvUid) {
128843
129065
  let resolvedMockUrl = "";
129066
+ const mockName = `${assetProjectName} Mock`;
128844
129067
  if (inputs.mockUrl) {
128845
129068
  resolvedMockUrl = inputs.mockUrl;
128846
129069
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
128847
129070
  }
128848
129071
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
128849
- const discovered = await dependencies.postman.findMockByCollection(inputs.baselineCollectionId);
129072
+ const discovered = await dependencies.postman.findMockByCollection(
129073
+ inputs.baselineCollectionId,
129074
+ mockEnvUid,
129075
+ mockName
129076
+ );
128850
129077
  if (discovered) {
128851
129078
  resolvedMockUrl = discovered.mockUrl;
128852
129079
  dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
@@ -128855,7 +129082,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128855
129082
  if (!resolvedMockUrl) {
128856
129083
  const mock = await dependencies.postman.createMock(
128857
129084
  inputs.workspaceId,
128858
- `${assetProjectName} Mock`,
129085
+ mockName,
128859
129086
  inputs.baselineCollectionId,
128860
129087
  mockEnvUid
128861
129088
  );
@@ -128870,6 +129097,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128870
129097
  const effectiveCron = inputs.monitorCron && inputs.monitorCron.trim() ? inputs.monitorCron.trim() : "";
128871
129098
  if (monitorEnvUid && inputs.monitorType !== "cli") {
128872
129099
  let resolvedMonitorId = "";
129100
+ const monitorName = `${assetProjectName} - Smoke Monitor`;
128873
129101
  if (inputs.monitorId) {
128874
129102
  const valid = await dependencies.postman.monitorExists(inputs.monitorId);
128875
129103
  if (valid) {
@@ -128880,7 +129108,11 @@ async function runRepoSyncInner(inputs, dependencies) {
128880
129108
  }
128881
129109
  }
128882
129110
  if (!resolvedMonitorId && inputs.smokeCollectionId) {
128883
- const discovered = await dependencies.postman.findMonitorByCollection(inputs.smokeCollectionId);
129111
+ const discovered = await dependencies.postman.findMonitorByCollection(
129112
+ inputs.smokeCollectionId,
129113
+ monitorEnvUid,
129114
+ monitorName
129115
+ );
128884
129116
  if (discovered) {
128885
129117
  resolvedMonitorId = discovered.uid;
128886
129118
  dependencies.core.info(`Discovered existing monitor for collection ${inputs.smokeCollectionId}: ${resolvedMonitorId}`);
@@ -128889,7 +129121,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128889
129121
  if (!resolvedMonitorId) {
128890
129122
  resolvedMonitorId = await dependencies.postman.createMonitor(
128891
129123
  inputs.workspaceId,
128892
- `${assetProjectName} - Smoke Monitor`,
129124
+ monitorName,
128893
129125
  inputs.smokeCollectionId,
128894
129126
  monitorEnvUid,
128895
129127
  effectiveCron || void 0
@@ -128923,7 +129155,12 @@ async function runRepoSyncInner(inputs, dependencies) {
128923
129155
  );
128924
129156
  }
128925
129157
  }
128926
- await exportArtifacts(inputs, dependencies, envUids, assetProjectName);
129158
+ await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
129159
+ workspaceLinkStatus: outputs["workspace-link-status"],
129160
+ priorWorkspaceId: resourcesState?.workspace?.id,
129161
+ existingSpecs: resourcesState?.cloudResources?.specs,
129162
+ releaseLabel
129163
+ });
128927
129164
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
128928
129165
  outputs["commit-sha"] = commit.commitSha;
128929
129166
  if (commit.resolvedCurrentRef) {
@@ -129102,6 +129339,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129102
129339
  createEnvironment: gatewayAssets.createEnvironment.bind(gatewayAssets),
129103
129340
  getEnvironment: gatewayAssets.getEnvironment.bind(gatewayAssets),
129104
129341
  updateEnvironment: gatewayAssets.updateEnvironment.bind(gatewayAssets),
129342
+ findEnvironmentByName: gatewayAssets.findEnvironmentByName.bind(gatewayAssets),
129105
129343
  // Collection read via the v3 export endpoint — returns canonical v3 IR,
129106
129344
  // written to disk by `convertAndSplitAnyCollection`. PMAK is never used for
129107
129345
  // collection reads.