@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/index.cjs CHANGED
@@ -127306,12 +127306,20 @@ async function retry(operation, options = {}) {
127306
127306
  }
127307
127307
 
127308
127308
  // src/lib/postman/postman-gateway-assets-client.ts
127309
+ var MAX_CREATE_FLIGHTS = 256;
127310
+ var createFlights = /* @__PURE__ */ new Map();
127309
127311
  var PostmanGatewayAssetsClient = class {
127310
127312
  gateway;
127311
127313
  workspaceId;
127314
+ sleep;
127315
+ reconcileAttempts;
127316
+ reconcileDelayMs;
127312
127317
  constructor(options) {
127313
127318
  this.gateway = options.gateway;
127314
127319
  this.workspaceId = String(options.workspaceId || "").trim();
127320
+ this.sleep = options.sleep ?? sleep;
127321
+ this.reconcileAttempts = Math.max(1, options.reconcileAttempts ?? 3);
127322
+ this.reconcileDelayMs = Math.max(0, options.reconcileDelayMs ?? 500);
127315
127323
  }
127316
127324
  asRecord(value) {
127317
127325
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -127341,8 +127349,64 @@ var PostmanGatewayAssetsClient = class {
127341
127349
  const parts = trimmed.split("-");
127342
127350
  return parts.length >= 6 ? parts.slice(1).join("-") : trimmed;
127343
127351
  }
127344
- isTransient(error2) {
127345
- return error2 instanceof HttpError && (error2.status === 429 || error2.status >= 500);
127352
+ isAmbiguousCreateOutcome(error2) {
127353
+ if (error2 instanceof HttpError) {
127354
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
127355
+ }
127356
+ return error2 instanceof Error;
127357
+ }
127358
+ isRetryableIdempotentWriteOutcome(error2) {
127359
+ if (error2 instanceof HttpError) {
127360
+ return error2.status === 408 || error2.status === 429 || error2.status >= 500;
127361
+ }
127362
+ return error2 instanceof Error;
127363
+ }
127364
+ selectExactMatch(kind, identity, matches) {
127365
+ if (matches.length > 1) {
127366
+ const ids = matches.map((match) => match.uid).join(", ");
127367
+ throw new Error(
127368
+ `Multiple ${kind}s match ${identity}: ${ids}. Refusing to choose one; remove duplicates or pass an explicit asset ID.`
127369
+ );
127370
+ }
127371
+ return matches[0] ?? null;
127372
+ }
127373
+ async singleFlight(key, fingerprint, kind, operation) {
127374
+ const existing = createFlights.get(key);
127375
+ if (existing) {
127376
+ if (existing.fingerprint !== fingerprint) {
127377
+ throw new Error(`Incompatible concurrent ${kind} create for ${key}`);
127378
+ }
127379
+ return existing.promise;
127380
+ }
127381
+ if (createFlights.size >= MAX_CREATE_FLIGHTS) {
127382
+ throw new Error(`Too many concurrent Postman asset creates (limit ${MAX_CREATE_FLIGHTS})`);
127383
+ }
127384
+ const pending = operation().finally(() => {
127385
+ if (createFlights.get(key)?.promise === pending) {
127386
+ createFlights.delete(key);
127387
+ }
127388
+ });
127389
+ createFlights.set(key, { fingerprint, promise: pending });
127390
+ return pending;
127391
+ }
127392
+ async discoverAfterAmbiguousCreate(discover, error2) {
127393
+ if (!this.isAmbiguousCreateOutcome(error2)) {
127394
+ return null;
127395
+ }
127396
+ for (let attempt = 0; attempt < this.reconcileAttempts; attempt += 1) {
127397
+ if (attempt > 0) {
127398
+ await this.sleep(this.reconcileDelayMs);
127399
+ }
127400
+ const found = await discover();
127401
+ if (found) {
127402
+ return found;
127403
+ }
127404
+ }
127405
+ return null;
127406
+ }
127407
+ publicEnvironmentUid(data, bareId) {
127408
+ const owner = String(data?.owner ?? "").trim();
127409
+ return owner && !bareId.startsWith(`${owner}-`) ? `${owner}-${bareId}` : bareId;
127346
127410
  }
127347
127411
  // --- environments (service: sync) ---
127348
127412
  //
@@ -127353,9 +127417,10 @@ var PostmanGatewayAssetsClient = class {
127353
127417
  /**
127354
127418
  * Create/upsert an environment through the sync service.
127355
127419
  * POST /environment/import?workspace=:ws { id:<uuid>, name, values } ->
127356
- * { data:{ id:<bare uuid>, owner } }. The id is generated once and reused
127357
- * across retries so the import is idempotent (a retry upserts the same
127358
- * environment instead of duplicating it).
127420
+ * { data:{ id:<bare uuid>, owner } }. The id is generated once for the
127421
+ * operation. Unsafe creates submit once (no blind retry); on an ambiguous
127422
+ * response the client discovers by exact workspace-scoped name before
127423
+ * failing.
127359
127424
  *
127360
127425
  * Returns the PUBLIC uid (`<owner>-<uuid>`), not the bare model id the sync
127361
127426
  * import echoes: mock/monitor create reference the environment by its public
@@ -127365,33 +127430,54 @@ var PostmanGatewayAssetsClient = class {
127365
127430
  */
127366
127431
  async createEnvironment(workspaceId, name, values) {
127367
127432
  const ws = workspaceId || this.workspaceId;
127368
- const id = crypto.randomUUID();
127369
- const body = {
127370
- id,
127371
- name,
127372
- values: values.map((v) => ({
127373
- key: v.key,
127374
- value: v.value,
127375
- type: v.type ?? "default",
127376
- enabled: v.enabled ?? true
127377
- }))
127378
- };
127379
- const response = await retry(
127380
- () => this.gateway.requestJson({
127381
- service: "sync",
127382
- method: "post",
127383
- path: `/environment/import?workspace=${ws}`,
127384
- body
127385
- }),
127386
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127387
- );
127388
- const data = this.dataOf(response);
127389
- const bareId = this.idOf(data);
127390
- if (!bareId) {
127391
- throw new Error("Environment import did not return a UID");
127392
- }
127393
- const owner = String(data?.owner ?? "").trim();
127394
- return owner && !bareId.startsWith(`${owner}-`) ? `${owner}-${bareId}` : bareId;
127433
+ const envName = String(name ?? "").trim();
127434
+ const flightKey = `environment:${ws}:${envName}`;
127435
+ const normalizedValues = values.map((v) => ({
127436
+ key: v.key,
127437
+ value: v.value,
127438
+ type: v.type ?? "default",
127439
+ enabled: v.enabled ?? true
127440
+ }));
127441
+ return this.singleFlight(flightKey, JSON.stringify(normalizedValues), "environment", async () => {
127442
+ const existing = await this.findEnvironmentByName(ws, envName);
127443
+ if (existing?.uid) {
127444
+ await this.updateEnvironment(existing.uid, envName, values);
127445
+ return existing.uid;
127446
+ }
127447
+ const id = crypto.randomUUID();
127448
+ const body = {
127449
+ id,
127450
+ name: envName,
127451
+ values: normalizedValues
127452
+ };
127453
+ try {
127454
+ const response = await this.gateway.requestJson(
127455
+ {
127456
+ service: "sync",
127457
+ method: "post",
127458
+ path: `/environment/import?workspace=${ws}`,
127459
+ body
127460
+ },
127461
+ { retryTransient: false }
127462
+ );
127463
+ const data = this.dataOf(response);
127464
+ const bareId = this.idOf(data);
127465
+ if (!bareId) {
127466
+ throw new Error("Environment import did not return a UID");
127467
+ }
127468
+ return this.publicEnvironmentUid(data, bareId);
127469
+ } catch (error2) {
127470
+ const adopted = await this.discoverAfterAmbiguousCreate(async () => {
127471
+ const match = await this.findEnvironmentByName(ws, envName);
127472
+ return match?.uid ?? null;
127473
+ }, error2);
127474
+ if (adopted) {
127475
+ await this.updateEnvironment(adopted, envName, values);
127476
+ return adopted;
127477
+ }
127478
+ throw error2;
127479
+ }
127480
+ });
127395
127481
  }
127396
127482
  /**
127397
127483
  * Update an existing environment through the sync service.
@@ -127421,7 +127507,7 @@ var PostmanGatewayAssetsClient = class {
127421
127507
  path: `/environment/${id}`,
127422
127508
  body
127423
127509
  }),
127424
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127510
+ { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isRetryableIdempotentWriteOutcome(e) }
127425
127511
  );
127426
127512
  }
127427
127513
  /**
@@ -127445,13 +127531,37 @@ var PostmanGatewayAssetsClient = class {
127445
127531
  */
127446
127532
  async listEnvironments(workspaceId) {
127447
127533
  const ws = workspaceId || this.workspaceId;
127448
- const response = await this.gateway.requestJson({
127449
- service: "sync",
127450
- method: "post",
127451
- path: `/list/environment?workspace=${ws}`
127452
- });
127534
+ const response = await this.gateway.requestJson(
127535
+ {
127536
+ service: "sync",
127537
+ method: "post",
127538
+ path: `/list/environment?workspace=${ws}`
127539
+ },
127540
+ { retryTransient: true }
127541
+ );
127453
127542
  const items = Array.isArray(response?.data) ? response.data : [];
127454
- return items.map((raw) => this.asRecord(raw)).filter((e) => e !== null).map((e) => ({ name: String(e.name ?? ""), uid: this.idOf(e) }));
127543
+ return items.map((raw) => this.asRecord(raw)).filter((e) => e !== null).map((e) => {
127544
+ const bareOrPublic = this.idOf(e);
127545
+ return {
127546
+ name: String(e.name ?? ""),
127547
+ uid: this.publicEnvironmentUid(e, bareOrPublic)
127548
+ };
127549
+ });
127550
+ }
127551
+ /** Exact name match within a workspace. Prefer tracked UIDs before calling. */
127552
+ async findEnvironmentByName(workspaceId, name) {
127553
+ const want = String(name ?? "").trim();
127554
+ if (!want) {
127555
+ return null;
127556
+ }
127557
+ const environments = await this.listEnvironments(workspaceId);
127558
+ const matches = environments.filter((entry) => entry.name === want);
127559
+ const match = this.selectExactMatch(
127560
+ "environment",
127561
+ `workspace ${workspaceId} and name "${want}"`,
127562
+ matches
127563
+ );
127564
+ return match?.uid ? { uid: match.uid, name: match.name } : null;
127455
127565
  }
127456
127566
  // --- collection read (service: collection, v3 export) ---
127457
127567
  //
@@ -127481,32 +127591,51 @@ var PostmanGatewayAssetsClient = class {
127481
127591
  // --- mocks (service: mock) ---
127482
127592
  async createMock(workspaceId, name, collectionUid, environmentUid) {
127483
127593
  const ws = workspaceId || this.workspaceId;
127594
+ const mockName = String(name ?? "").trim();
127484
127595
  const collection = String(collectionUid ?? "").trim();
127485
127596
  const environment = String(environmentUid ?? "").trim();
127486
- const body = {
127487
- name,
127488
- collection,
127489
- private: false,
127490
- ...environment ? { environment } : {}
127491
- };
127492
- const response = await retry(
127493
- () => this.gateway.requestJson({
127494
- service: "mock",
127495
- method: "post",
127496
- path: `/mocks?workspace=${ws}`,
127497
- body
127498
- }),
127499
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127500
- );
127501
- const record = this.dataOf(response);
127502
- const uid = this.idOf(record);
127503
- if (!uid) {
127504
- throw new Error("Mock create did not return a UID");
127505
- }
127506
- return {
127507
- uid,
127508
- url: String(record?.url ?? record?.mockUrl ?? "").trim()
127509
- };
127597
+ const flightKey = `mock:${ws}:${collection}:${environment}:${mockName}`;
127598
+ return this.singleFlight(flightKey, flightKey, "mock", async () => {
127599
+ const existing = await this.findMockByCollection(collection, environment, mockName);
127600
+ if (existing) {
127601
+ return { uid: existing.uid, url: existing.mockUrl };
127602
+ }
127603
+ const body = {
127604
+ name: mockName,
127605
+ collection,
127606
+ private: false,
127607
+ ...environment ? { environment } : {}
127608
+ };
127609
+ try {
127610
+ const response = await this.gateway.requestJson(
127611
+ {
127612
+ service: "mock",
127613
+ method: "post",
127614
+ path: `/mocks?workspace=${ws}`,
127615
+ body
127616
+ },
127617
+ { retryTransient: false }
127618
+ );
127619
+ const record = this.dataOf(response);
127620
+ const uid = this.idOf(record);
127621
+ if (!uid) {
127622
+ throw new Error("Mock create did not return a UID");
127623
+ }
127624
+ return {
127625
+ uid,
127626
+ url: String(record?.url ?? record?.mockUrl ?? "").trim()
127627
+ };
127628
+ } catch (error2) {
127629
+ const adopted = await this.discoverAfterAmbiguousCreate(
127630
+ () => this.findMockByCollection(collection, environment, mockName),
127631
+ error2
127632
+ );
127633
+ if (adopted) {
127634
+ return { uid: adopted.uid, url: adopted.mockUrl };
127635
+ }
127636
+ throw error2;
127637
+ }
127638
+ });
127510
127639
  }
127511
127640
  async listMocks() {
127512
127641
  const response = await this.gateway.requestJson({
@@ -127535,10 +127664,19 @@ var PostmanGatewayAssetsClient = class {
127535
127664
  return false;
127536
127665
  }
127537
127666
  }
127538
- async findMockByCollection(collectionUid) {
127667
+ async findMockByCollection(collectionUid, environmentUid, name) {
127539
127668
  const mocks = await this.listMocks();
127540
127669
  const want = String(collectionUid ?? "").trim();
127541
- const match = mocks.find((m) => m.collection === want);
127670
+ const environment = String(environmentUid ?? "").trim();
127671
+ const mockName = String(name ?? "").trim();
127672
+ const matches = mocks.filter(
127673
+ (mock) => mock.collection === want && mock.environment === environment && mock.name === mockName
127674
+ );
127675
+ const match = this.selectExactMatch(
127676
+ "mock",
127677
+ `workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
127678
+ matches
127679
+ );
127542
127680
  return match ? { uid: match.uid, mockUrl: match.mockUrl } : null;
127543
127681
  }
127544
127682
  // --- monitors (service: monitors; collection-based = jobTemplates) ---
@@ -127555,32 +127693,51 @@ var PostmanGatewayAssetsClient = class {
127555
127693
  async createMonitor(workspaceId, name, collectionUid, environmentUid, cronSchedule) {
127556
127694
  const ws = workspaceId || this.workspaceId;
127557
127695
  const effectiveCron = cronSchedule && cronSchedule.trim() ? cronSchedule.trim() : "0 0 * * 0";
127696
+ const monitorName = String(name ?? "").trim();
127558
127697
  const collection = String(collectionUid ?? "").trim();
127559
127698
  const environment = String(environmentUid ?? "").trim();
127560
- const body = {
127561
- name,
127562
- collection,
127563
- options: { strictSSL: false, followRedirects: true, requestTimeout: null, requestDelay: 0 },
127564
- notifications: { onFailure: [], onError: [] },
127565
- retry: {},
127566
- schedule: { cronPattern: effectiveCron, timeZone: "UTC" },
127567
- distribution: null,
127568
- ...environment ? { environment } : {}
127569
- };
127570
- const response = await retry(
127571
- () => this.gateway.requestJson({
127572
- service: "monitors",
127573
- method: "post",
127574
- path: `/jobTemplates?workspace=${ws}`,
127575
- body
127576
- }),
127577
- { maxAttempts: 5, delayMs: 2e3, backoffMultiplier: 2, maxDelayMs: 15e3, shouldRetry: (e) => this.isTransient(e) }
127578
- );
127579
- const uid = this.idOf(this.dataOf(response));
127580
- if (!uid) {
127581
- throw new Error("Monitor create did not return a UID");
127582
- }
127583
- return uid;
127699
+ const flightKey = `monitor:${ws}:${collection}:${environment}:${monitorName}`;
127700
+ return this.singleFlight(flightKey, effectiveCron, "monitor", async () => {
127701
+ const existing = await this.findMonitorByCollection(collection, environment, monitorName);
127702
+ if (existing?.uid) {
127703
+ return existing.uid;
127704
+ }
127705
+ const body = {
127706
+ name: monitorName,
127707
+ collection,
127708
+ options: { strictSSL: false, followRedirects: true, requestTimeout: null, requestDelay: 0 },
127709
+ notifications: { onFailure: [], onError: [] },
127710
+ retry: {},
127711
+ schedule: { cronPattern: effectiveCron, timeZone: "UTC" },
127712
+ distribution: null,
127713
+ ...environment ? { environment } : {}
127714
+ };
127715
+ try {
127716
+ const response = await this.gateway.requestJson(
127717
+ {
127718
+ service: "monitors",
127719
+ method: "post",
127720
+ path: `/jobTemplates?workspace=${ws}`,
127721
+ body
127722
+ },
127723
+ { retryTransient: false }
127724
+ );
127725
+ const uid = this.idOf(this.dataOf(response));
127726
+ if (!uid) {
127727
+ throw new Error("Monitor create did not return a UID");
127728
+ }
127729
+ return uid;
127730
+ } catch (error2) {
127731
+ const adopted = await this.discoverAfterAmbiguousCreate(
127732
+ () => this.findMonitorByCollection(collection, environment, monitorName),
127733
+ error2
127734
+ );
127735
+ if (adopted?.uid) {
127736
+ return adopted.uid;
127737
+ }
127738
+ throw error2;
127739
+ }
127740
+ });
127584
127741
  }
127585
127742
  async listMonitors() {
127586
127743
  const response = await this.gateway.requestJson({
@@ -127603,24 +127760,30 @@ var PostmanGatewayAssetsClient = class {
127603
127760
  return false;
127604
127761
  }
127605
127762
  }
127606
- async findMonitorByCollection(collectionUid) {
127763
+ async findMonitorByCollection(collectionUid, environmentUid, name) {
127607
127764
  const want = String(collectionUid ?? "").trim();
127608
- const response = await this.gateway.requestJson({
127609
- service: "monitors",
127610
- method: "get",
127611
- path: `/collections/${want}/jobTemplates?_etc=true`
127612
- });
127613
- const data = response?.data;
127614
- const monitors = this.mapJobTemplates(Array.isArray(data) ? data : []);
127615
- const match = monitors.find((m) => m.collectionUid === want) ?? monitors[0];
127765
+ const environment = String(environmentUid ?? "").trim();
127766
+ const monitorName = String(name ?? "").trim();
127767
+ const monitors = await this.listMonitors();
127768
+ const matches = monitors.filter(
127769
+ (monitor) => monitor.collectionUid === want && monitor.environmentUid === environment && monitor.name === monitorName
127770
+ );
127771
+ const match = this.selectExactMatch(
127772
+ "monitor",
127773
+ `workspace ${this.workspaceId}, name "${monitorName}", collection ${want}, and environment ${environment || "(none)"}`,
127774
+ matches
127775
+ );
127616
127776
  return match?.uid ? { uid: match.uid, name: match.name } : null;
127617
127777
  }
127618
127778
  async runMonitor(uid) {
127619
- await this.gateway.requestJson({
127620
- service: "monitors",
127621
- method: "post",
127622
- path: `/jobTemplates/${uid}/jobs`
127623
- });
127779
+ await this.gateway.requestJson(
127780
+ {
127781
+ service: "monitors",
127782
+ method: "post",
127783
+ path: `/jobTemplates/${uid}/jobs`
127784
+ },
127785
+ { retryTransient: false }
127786
+ );
127624
127787
  }
127625
127788
  };
127626
127789
 
@@ -127786,12 +127949,8 @@ async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetc
127786
127949
  function isExpiredAuthError(status, body) {
127787
127950
  return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
127788
127951
  }
127789
- function isTransientGatewayError(status, body) {
127790
- if (status === 502 || status === 503 || status === 504) return true;
127791
- if (status >= 500 && (body.includes("ESOCKETTIMEDOUT") || body.includes("ETIMEDOUT") || body.includes("ECONNRESET") || body.includes("serverError") || body.includes("downstream"))) {
127792
- return true;
127793
- }
127794
- return false;
127952
+ function isRetryableSafeReadResponse(status) {
127953
+ return status === 408 || status === 429 || status >= 500;
127795
127954
  }
127796
127955
  function defaultSleep(ms) {
127797
127956
  return new Promise((resolve3) => setTimeout(resolve3, ms));
@@ -127850,14 +128009,28 @@ var AccessTokenGatewayClient = class {
127850
128009
  }
127851
128010
  /**
127852
128011
  * Send a gateway request, refreshing the token once on an auth failure and
127853
- * retrying transient downstream failures (5xx / Bifrost read timeouts) with
127854
- * exponential backoff. The auth-refresh-once path is independent of the
127855
- * transient-retry budget.
128012
+ * optionally retrying transient downstream failures (5xx / Bifrost read
128013
+ * timeouts) with exponential backoff. Safe reads keep transient retries;
128014
+ * unsafe creates pass `{ retryTransient: false }` and reconcile after an
128015
+ * ambiguous response instead of re-POSTing. Auth refresh remains independent
128016
+ * of the transient-retry budget.
127856
128017
  */
127857
- async request(request) {
128018
+ async request(request, options = {}) {
128019
+ const retryTransient = options.retryTransient ?? request.method === "get";
127858
128020
  let attempt = 0;
127859
128021
  for (; ; ) {
127860
- let response = await this.send(request);
128022
+ let response;
128023
+ try {
128024
+ response = await this.send(request);
128025
+ } catch (error2) {
128026
+ if (retryTransient && attempt < this.maxRetries) {
128027
+ const delay = this.retryBaseDelayMs * 2 ** attempt;
128028
+ attempt += 1;
128029
+ await this.sleepImpl(delay);
128030
+ continue;
128031
+ }
128032
+ throw error2;
128033
+ }
127861
128034
  if (response.ok) {
127862
128035
  return response;
127863
128036
  }
@@ -127871,7 +128044,7 @@ var AccessTokenGatewayClient = class {
127871
128044
  const retryBody = await response.text().catch(() => "");
127872
128045
  throw this.toHttpError(request, response, retryBody);
127873
128046
  }
127874
- if (isTransientGatewayError(response.status, body) && attempt < this.maxRetries) {
128047
+ if (retryTransient && isRetryableSafeReadResponse(response.status) && attempt < this.maxRetries) {
127875
128048
  const delay = this.retryBaseDelayMs * 2 ** attempt;
127876
128049
  attempt += 1;
127877
128050
  await this.sleepImpl(delay);
@@ -127881,8 +128054,8 @@ var AccessTokenGatewayClient = class {
127881
128054
  }
127882
128055
  }
127883
128056
  /** Send a gateway request and parse the JSON body, or null when empty. */
127884
- async requestJson(request) {
127885
- const response = await this.request(request);
128057
+ async requestJson(request, options = {}) {
128058
+ const response = await this.request(request, options);
127886
128059
  const text = await response.text().catch(() => "");
127887
128060
  if (!text.trim()) {
127888
128061
  return null;
@@ -128474,18 +128647,28 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128474
128647
  for (const envName of inputs.environments) {
128475
128648
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
128476
128649
  const values = buildEnvironmentValues(envName, runtimeUrl);
128477
- const existingUid = envUids[envName];
128478
- if (existingUid) {
128479
- await dependencies.postman.updateEnvironment(
128480
- existingUid,
128481
- `${inputs.projectName} - ${envName}`,
128482
- values
128650
+ const displayName = `${inputs.projectName} - ${envName}`;
128651
+ let existingUid = String(envUids[envName] || "").trim();
128652
+ if (!existingUid) {
128653
+ const discovered = await dependencies.postman.findEnvironmentByName(
128654
+ inputs.workspaceId,
128655
+ displayName
128483
128656
  );
128657
+ if (discovered?.uid) {
128658
+ existingUid = discovered.uid;
128659
+ dependencies.core.info(
128660
+ `Discovered existing environment for ${displayName}: ${existingUid}`
128661
+ );
128662
+ }
128663
+ }
128664
+ if (existingUid) {
128665
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values);
128666
+ envUids[envName] = existingUid;
128484
128667
  continue;
128485
128668
  }
128486
128669
  envUids[envName] = await dependencies.postman.createEnvironment(
128487
128670
  inputs.workspaceId,
128488
- `${inputs.projectName} - ${envName}`,
128671
+ displayName,
128489
128672
  values
128490
128673
  );
128491
128674
  }
@@ -128531,10 +128714,33 @@ function writeJsonFile(path9, content, normalize3 = false) {
128531
128714
  const data = normalize3 ? stripVolatileFields(content) : content;
128532
128715
  (0, import_node_fs3.writeFileSync)(path9, JSON.stringify(data, null, 2));
128533
128716
  }
128534
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId) {
128535
- const manifest = {
128536
- workspace: { id: workspaceId }
128537
- };
128717
+ function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
128718
+ if (specSyncMode !== "version") {
128719
+ return mappedSource;
128720
+ }
128721
+ const normalized = normalizeReleaseLabel(releaseLabel || "");
128722
+ if (!normalized) {
128723
+ return void 0;
128724
+ }
128725
+ return `${mappedSource}#release=${normalized}`;
128726
+ }
128727
+ function resolveDurableWorkspaceId(options) {
128728
+ const { candidateId, priorId, workspaceLinkEnabled, workspaceLinkStatus } = options;
128729
+ const candidate = candidateId.trim();
128730
+ const prior = priorId?.trim() || void 0;
128731
+ if (!workspaceLinkEnabled) {
128732
+ return candidate || prior;
128733
+ }
128734
+ if (workspaceLinkStatus === "success") {
128735
+ return candidate || void 0;
128736
+ }
128737
+ return prior === candidate ? prior : void 0;
128738
+ }
128739
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
128740
+ const manifest = {};
128741
+ if (workspaceId) {
128742
+ manifest.workspace = { id: workspaceId };
128743
+ }
128538
128744
  const localResources = {};
128539
128745
  const cloudResources = {};
128540
128746
  const collectionKeys = Object.keys(collectionMap);
@@ -128555,8 +128761,12 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128555
128761
  if (localSpecRefs.length > 0) {
128556
128762
  localResources.specs = localSpecRefs;
128557
128763
  }
128764
+ const specs = { ...existingSpecs || {} };
128558
128765
  if (mappedSpecRef && specId) {
128559
- cloudResources.specs = { [mappedSpecRef]: specId };
128766
+ specs[mappedSpecRef] = specId;
128767
+ }
128768
+ if (Object.keys(specs).length > 0) {
128769
+ cloudResources.specs = specs;
128560
128770
  }
128561
128771
  if (Object.keys(localResources).length > 0) {
128562
128772
  manifest.localResources = localResources;
@@ -128623,7 +128833,7 @@ function assertPathWithinCwd(targetPath, fieldName) {
128623
128833
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128624
128834
  }
128625
128835
  }
128626
- async function exportArtifacts(inputs, dependencies, envUids, assetProjectName) {
128836
+ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName, options) {
128627
128837
  if (!inputs.workspaceId) {
128628
128838
  return;
128629
128839
  }
@@ -128657,6 +128867,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
128657
128867
  const manifestCollections = {};
128658
128868
  const discoveredSpecs = scanLocalSpecReferences();
128659
128869
  const mappedSpec = resolveMappedSpecReference(inputs.specPath, discoveredSpecs);
128870
+ const mappedSpecCloudKey = mappedSpec && inputs.specId ? buildMappedSpecCloudKey(
128871
+ mappedSpec.configRelativePath,
128872
+ inputs.specSyncMode,
128873
+ options.releaseLabel
128874
+ ) : void 0;
128660
128875
  if (inputs.baselineCollectionId) {
128661
128876
  const col = await dependencies.postman.getCollection(inputs.baselineCollectionId);
128662
128877
  const dirName = getCollectionDirectoryName("Baseline", assetProjectName);
@@ -128682,14 +128897,21 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
128682
128897
  true
128683
128898
  );
128684
128899
  }
128900
+ const durableWorkspaceId = resolveDurableWorkspaceId({
128901
+ candidateId: inputs.workspaceId,
128902
+ priorId: options.priorWorkspaceId,
128903
+ workspaceLinkEnabled: inputs.workspaceLinkEnabled,
128904
+ workspaceLinkStatus: options.workspaceLinkStatus
128905
+ });
128685
128906
  (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
128686
- inputs.workspaceId,
128907
+ durableWorkspaceId,
128687
128908
  manifestCollections,
128688
128909
  envUids,
128689
128910
  inputs.artifactDir,
128690
128911
  discoveredSpecs.map((spec) => spec.configRelativePath),
128691
- mappedSpec?.configRelativePath,
128692
- inputs.specId || void 0
128912
+ mappedSpecCloudKey,
128913
+ inputs.specId || void 0,
128914
+ options.existingSpecs
128693
128915
  ));
128694
128916
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
128695
128917
  (0, import_node_fs3.writeFileSync)(
@@ -128856,12 +129078,17 @@ async function runRepoSyncInner(inputs, dependencies) {
128856
129078
  const mockEnvUid = envUids.dev || envUids.prod || Object.values(envUids)[0];
128857
129079
  if (mockEnvUid) {
128858
129080
  let resolvedMockUrl = "";
129081
+ const mockName = `${assetProjectName} Mock`;
128859
129082
  if (inputs.mockUrl) {
128860
129083
  resolvedMockUrl = inputs.mockUrl;
128861
129084
  dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
128862
129085
  }
128863
129086
  if (!resolvedMockUrl && inputs.baselineCollectionId) {
128864
- const discovered = await dependencies.postman.findMockByCollection(inputs.baselineCollectionId);
129087
+ const discovered = await dependencies.postman.findMockByCollection(
129088
+ inputs.baselineCollectionId,
129089
+ mockEnvUid,
129090
+ mockName
129091
+ );
128865
129092
  if (discovered) {
128866
129093
  resolvedMockUrl = discovered.mockUrl;
128867
129094
  dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
@@ -128870,7 +129097,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128870
129097
  if (!resolvedMockUrl) {
128871
129098
  const mock = await dependencies.postman.createMock(
128872
129099
  inputs.workspaceId,
128873
- `${assetProjectName} Mock`,
129100
+ mockName,
128874
129101
  inputs.baselineCollectionId,
128875
129102
  mockEnvUid
128876
129103
  );
@@ -128885,6 +129112,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128885
129112
  const effectiveCron = inputs.monitorCron && inputs.monitorCron.trim() ? inputs.monitorCron.trim() : "";
128886
129113
  if (monitorEnvUid && inputs.monitorType !== "cli") {
128887
129114
  let resolvedMonitorId = "";
129115
+ const monitorName = `${assetProjectName} - Smoke Monitor`;
128888
129116
  if (inputs.monitorId) {
128889
129117
  const valid = await dependencies.postman.monitorExists(inputs.monitorId);
128890
129118
  if (valid) {
@@ -128895,7 +129123,11 @@ async function runRepoSyncInner(inputs, dependencies) {
128895
129123
  }
128896
129124
  }
128897
129125
  if (!resolvedMonitorId && inputs.smokeCollectionId) {
128898
- const discovered = await dependencies.postman.findMonitorByCollection(inputs.smokeCollectionId);
129126
+ const discovered = await dependencies.postman.findMonitorByCollection(
129127
+ inputs.smokeCollectionId,
129128
+ monitorEnvUid,
129129
+ monitorName
129130
+ );
128899
129131
  if (discovered) {
128900
129132
  resolvedMonitorId = discovered.uid;
128901
129133
  dependencies.core.info(`Discovered existing monitor for collection ${inputs.smokeCollectionId}: ${resolvedMonitorId}`);
@@ -128904,7 +129136,7 @@ async function runRepoSyncInner(inputs, dependencies) {
128904
129136
  if (!resolvedMonitorId) {
128905
129137
  resolvedMonitorId = await dependencies.postman.createMonitor(
128906
129138
  inputs.workspaceId,
128907
- `${assetProjectName} - Smoke Monitor`,
129139
+ monitorName,
128908
129140
  inputs.smokeCollectionId,
128909
129141
  monitorEnvUid,
128910
129142
  effectiveCron || void 0
@@ -128938,7 +129170,12 @@ async function runRepoSyncInner(inputs, dependencies) {
128938
129170
  );
128939
129171
  }
128940
129172
  }
128941
- await exportArtifacts(inputs, dependencies, envUids, assetProjectName);
129173
+ await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
129174
+ workspaceLinkStatus: outputs["workspace-link-status"],
129175
+ priorWorkspaceId: resourcesState?.workspace?.id,
129176
+ existingSpecs: resourcesState?.cloudResources?.specs,
129177
+ releaseLabel
129178
+ });
128942
129179
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
128943
129180
  outputs["commit-sha"] = commit.commitSha;
128944
129181
  if (commit.resolvedCurrentRef) {
@@ -129117,6 +129354,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129117
129354
  createEnvironment: gatewayAssets.createEnvironment.bind(gatewayAssets),
129118
129355
  getEnvironment: gatewayAssets.getEnvironment.bind(gatewayAssets),
129119
129356
  updateEnvironment: gatewayAssets.updateEnvironment.bind(gatewayAssets),
129357
+ findEnvironmentByName: gatewayAssets.findEnvironmentByName.bind(gatewayAssets),
129120
129358
  // Collection read via the v3 export endpoint — returns canonical v3 IR,
129121
129359
  // written to disk by `convertAndSplitAnyCollection`. PMAK is never used for
129122
129360
  // collection reads.