@postman-cs/onboarding-repo-sync 2.11.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -116146,6 +116146,102 @@ function resolvePostmanEndpointProfile(stack, region = "us", env = process.env)
116146
116146
  };
116147
116147
  }
116148
116148
 
116149
+ // src/lib/postman/environment-yaml.ts
116150
+ var import_fs_utils = __toESM(require_dist5(), 1);
116151
+ var EXTENSION = ".environment.yaml";
116152
+ var SEPARATOR = " - ";
116153
+ var MAX_BASENAME_BYTES = 64;
116154
+ var SANITIZE_OPTIONS = {
116155
+ replacement: "-",
116156
+ combineConsecutiveReplacements: true
116157
+ };
116158
+ function assertWellFormed(value, label) {
116159
+ for (let index = 0; index < value.length; index += 1) {
116160
+ const current2 = value.charCodeAt(index);
116161
+ if (current2 >= 55296 && current2 <= 56319) {
116162
+ const next = value.charCodeAt(index + 1);
116163
+ if (next < 56320 || next > 57343) throw new Error(`${label} must contain well-formed Unicode`);
116164
+ index += 1;
116165
+ } else if (current2 >= 56320 && current2 <= 57343) {
116166
+ throw new Error(`${label} must contain well-formed Unicode`);
116167
+ }
116168
+ }
116169
+ }
116170
+ function safeEnvironmentName(value) {
116171
+ assertWellFormed(value, "environment name");
116172
+ const hasInvalidCharacter = [...value].some((char) => {
116173
+ const code = char.charCodeAt(0);
116174
+ return char === "/" || char === "\\" || code <= 31 || code >= 127 && code <= 159;
116175
+ });
116176
+ if (!value || hasInvalidCharacter) {
116177
+ throw new Error("environment name must be non-empty and contain no path separators or control characters");
116178
+ }
116179
+ return (0, import_fs_utils.sanitizeFilename)(value, {
116180
+ ...SANITIZE_OPTIONS,
116181
+ maxLength: MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR)
116182
+ });
116183
+ }
116184
+ function environmentFileName(projectName, environmentName) {
116185
+ assertWellFormed(projectName, "project name");
116186
+ const suffix = safeEnvironmentName(environmentName);
116187
+ const projectBudget = Math.max(
116188
+ 0,
116189
+ MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR) - Buffer.byteLength(suffix)
116190
+ );
116191
+ const prefix = (0, import_fs_utils.sanitizeFilename)(projectName, { ...SANITIZE_OPTIONS, maxLength: projectBudget });
116192
+ return `${prefix ? `${prefix}${SEPARATOR}` : ""}${suffix}${EXTENSION}`;
116193
+ }
116194
+ function assertUniqueEnvironmentFileNames(projectName, environmentNames) {
116195
+ const owners = /* @__PURE__ */ new Map();
116196
+ for (const environmentName of environmentNames) {
116197
+ const fileName = environmentFileName(projectName, environmentName);
116198
+ const key = fileName.normalize("NFD").toUpperCase().toLowerCase();
116199
+ const owner = owners.get(key);
116200
+ if (owner) {
116201
+ throw new Error(
116202
+ `Environment identities "${owner}" and "${environmentName}" resolve to the same artifact filename ${fileName}`
116203
+ );
116204
+ }
116205
+ owners.set(key, environmentName);
116206
+ }
116207
+ }
116208
+ function environmentManifestRef(artifactDir, projectName, environmentName) {
116209
+ return `../${artifactDir}/environments/${environmentFileName(projectName, environmentName)}`;
116210
+ }
116211
+ function legacyEnvironmentManifestRef(artifactDir, environmentName) {
116212
+ return `../${artifactDir}/environments/${environmentName}.postman_environment.json`;
116213
+ }
116214
+ function serializeEnvironmentYaml(payload, displayName) {
116215
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
116216
+ throw new Error("sync-service environment must contain an object body");
116217
+ }
116218
+ const body = payload;
116219
+ if (!Array.isArray(body.values)) {
116220
+ throw new Error("sync-service environment must contain a values array");
116221
+ }
116222
+ const values = body.values.map((entry) => {
116223
+ const input = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {};
116224
+ const secret = input.secret === true || input.type === "secret";
116225
+ const output = {
116226
+ key: input.key == null ? "" : String(input.key)
116227
+ };
116228
+ if (secret) {
116229
+ output.secret = true;
116230
+ if (input.source != null) output.source = input.source;
116231
+ } else {
116232
+ output.value = input.value == null ? "" : String(input.value);
116233
+ }
116234
+ if (input.enabled === false) output.disabled = true;
116235
+ if (input.description != null) output.description = String(input.description);
116236
+ return output;
116237
+ });
116238
+ const environment = { name: displayName, values };
116239
+ if (Number.isInteger(body.color) && Number(body.color) >= 0 && Number(body.color) < 360) {
116240
+ environment.color = Number(body.color);
116241
+ }
116242
+ return dump(environment, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
116243
+ }
116244
+
116149
116245
  // src/lib/ci-workflow-template.ts
116150
116246
  var DEFAULT_POSTMAN_CLI_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliInstallUrl;
116151
116247
  var DEFAULT_POSTMAN_CLI_WINDOWS_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliWindowsInstallUrl;
@@ -116173,6 +116269,16 @@ function resolveCiRunnerOs(runnerOsOption) {
116173
116269
  }
116174
116270
  throw new Error("ci-runner-os must be one of: linux, windows; got: " + runnerOs);
116175
116271
  }
116272
+ function expectedEnvironmentFileName(projectName) {
116273
+ const name = String(projectName || "").trim();
116274
+ return name ? environmentFileName(name, "prod") : "";
116275
+ }
116276
+ function rubyStringLiteral(value) {
116277
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
116278
+ }
116279
+ function powershellStringLiteral(value) {
116280
+ return `'${value.replace(/'/g, "''")}'`;
116281
+ }
116176
116282
  function renderCiWorkflowTemplate(options = {}) {
116177
116283
  const runnerOs = resolveCiRunnerOs(options.runnerOs);
116178
116284
  if (runnerOs === "windows") {
@@ -116181,9 +116287,15 @@ function renderCiWorkflowTemplate(options = {}) {
116181
116287
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
116182
116288
  const installUrl = validateHttpsInstallUrl(rawUrl);
116183
116289
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
116184
- return buildCiWorkflowLines(installUrl, postmanRegion, options.privateMockAuth === true).join("\n");
116290
+ return buildCiWorkflowLines(
116291
+ installUrl,
116292
+ postmanRegion,
116293
+ options.privateMockAuth === true,
116294
+ options.projectName
116295
+ ).join("\n");
116185
116296
  }
116186
- function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116297
+ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
116298
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
116187
116299
  return [
116188
116300
  "name: CI/CD Pipeline",
116189
116301
  "on:",
@@ -116221,11 +116333,18 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116221
116333
  " environments = cloud.fetch('environments', {})",
116222
116334
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
116223
116335
  " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
116224
- " environment = environments.find { |path, _| path.end_with?('/prod.postman_environment.json') }&.last || environments.values.first",
116336
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
116337
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
116338
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
116339
+ // A repo whose environments are named only `stage` or `dev` has no prod file to
116340
+ // match, and aborting there would break generated CI that worked before the YAML
116341
+ // migration. One environment is unambiguous, so use it; two or more without a
116342
+ // prod match is a real ambiguity and still aborts.
116343
+ " environment ||= environments.values.first if environments.size == 1",
116225
116344
  " missing = []",
116226
- " missing << 'smoke collection' unless smoke",
116227
- " missing << 'contract collection' unless contract",
116228
- " missing << 'environment' unless environment",
116345
+ " missing << '[Smoke] collection' unless smoke",
116346
+ " missing << '[Contract] collection' unless contract",
116347
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
116229
116348
  ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
116230
116349
  " File.open(ENV.fetch('GITHUB_ENV'), 'a') do |file|",
116231
116350
  ' file.puts("POSTMAN_SMOKE_COLLECTION_UID=#{smoke}")',
@@ -116335,7 +116454,8 @@ function buildAdoWindowsCollectionRunLines(displayName, collectionEnvironmentNam
116335
116454
  ...privateMockAuth ? [" POSTMAN_API_KEY: $(POSTMAN_API_KEY)"] : []
116336
116455
  ];
116337
116456
  }
116338
- function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116457
+ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
116458
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
116339
116459
  return [
116340
116460
  "trigger:",
116341
116461
  " branches:",
@@ -116380,22 +116500,38 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
116380
116500
  " $contract = ''",
116381
116501
  " $environment = ''",
116382
116502
  " $fallbackEnvironment = ''",
116503
+ " $environmentCount = 0",
116504
+ ` $expectedEnvironment = ${powershellStringLiteral(expectedEnvironment)}`,
116383
116505
  " foreach ($line in Get-Content -LiteralPath '.postman/resources.yaml') {",
116384
- " if ($line -match '^ (collections|environments):\\s*$') { $section = $Matches[1]; continue }",
116506
+ " if ($line -match '^ (collections|environments|specs):\\s*$') { $section = $Matches[1]; continue }",
116385
116507
  " if ($line -notmatch '^ (.+?):\\s+(.+?)\\s*$') { continue }",
116386
- ` $key = $Matches[1].Trim().Trim("'").Trim('"')`,
116508
+ " $rawKey = $Matches[1].Trim()",
116509
+ ` if ($rawKey.StartsWith("'") -and $rawKey.EndsWith("'")) {`,
116510
+ ` $key = $rawKey.Substring(1, $rawKey.Length - 2) -replace "''", "'"`,
116511
+ ` } elseif ($rawKey.StartsWith('"') -and $rawKey.EndsWith('"')) {`,
116512
+ " $key = $rawKey.Substring(1, $rawKey.Length - 2)",
116513
+ " } else {",
116514
+ " $key = $rawKey",
116515
+ " }",
116387
116516
  ` $value = $Matches[2].Trim().Trim("'").Trim('"')`,
116388
116517
  " if ($section -eq 'collections' -and $key -match '\\[Smoke\\]') { $smoke = $value }",
116389
116518
  " if ($section -eq 'collections' -and $key -match '\\[Contract\\]') { $contract = $value }",
116390
116519
  " if ($section -eq 'environments') {",
116520
+ " $environmentCount += 1",
116391
116521
  " if ([string]::IsNullOrWhiteSpace($fallbackEnvironment)) { $fallbackEnvironment = $value }",
116392
- " if ($key -match 'prod\\.postman_environment\\.json$') { $environment = $value }",
116522
+ " $fileName = [IO.Path]::GetFileName($key)",
116523
+ " if ($fileName -eq $expectedEnvironment) { $environment = $value }",
116524
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $fileName -eq 'prod.postman_environment.json') { $environment = $value }",
116393
116525
  " }",
116394
116526
  " }",
116395
- " if ([string]::IsNullOrWhiteSpace($environment)) { $environment = $fallbackEnvironment }",
116396
- " if ([string]::IsNullOrWhiteSpace($smoke)) { throw 'Missing smoke collection UID in .postman/resources.yaml' }",
116397
- " if ([string]::IsNullOrWhiteSpace($contract)) { throw 'Missing contract collection UID in .postman/resources.yaml' }",
116398
- " if ([string]::IsNullOrWhiteSpace($environment)) { throw 'Missing environment UID in .postman/resources.yaml' }",
116527
+ // Matches the Ruby resolvers: a single environment is unambiguous, so use it
116528
+ // rather than aborting a pipeline that worked before the YAML migration.
116529
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $environmentCount -eq 1) { $environment = $fallbackEnvironment }",
116530
+ " $missing = @()",
116531
+ " if ([string]::IsNullOrWhiteSpace($smoke)) { $missing += '[Smoke] collection' }",
116532
+ " if ([string]::IsNullOrWhiteSpace($contract)) { $missing += '[Contract] collection' }",
116533
+ " if ([string]::IsNullOrWhiteSpace($environment)) { $missing += $(if ($expectedEnvironment) { $expectedEnvironment } else { 'environment' }) }",
116534
+ " if ($missing.Count) { throw ('Missing Postman resource IDs in .postman/resources.yaml: ' + ($missing -join ', ')) }",
116399
116535
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$smoke"',
116400
116536
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$contract"',
116401
116537
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$environment"',
@@ -116425,7 +116561,8 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
116425
116561
  ""
116426
116562
  ];
116427
116563
  }
116428
- function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116564
+ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
116565
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
116429
116566
  return [
116430
116567
  "trigger:",
116431
116568
  " branches:",
@@ -116452,16 +116589,29 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116452
116589
  " env:",
116453
116590
  " POSTMAN_API_KEY: $(POSTMAN_API_KEY)",
116454
116591
  " - script: |",
116455
- " SMOKE=$(grep '\\[Smoke\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
116456
- " CONTRACT=$(grep '\\[Contract\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
116457
- " ENV=$(grep 'prod\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
116458
- " ENV=${ENV:-$(grep '\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')}",
116459
- ' [ -n "$SMOKE" ] || { echo "Missing smoke collection UID in .postman/resources.yaml"; exit 1; }',
116460
- ' [ -n "$CONTRACT" ] || { echo "Missing contract collection UID in .postman/resources.yaml"; exit 1; }',
116461
- ' [ -n "$ENV" ] || { echo "Missing environment UID in .postman/resources.yaml"; exit 1; }',
116462
- ' echo "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$SMOKE"',
116463
- ' echo "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$CONTRACT"',
116464
- ' echo "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$ENV"',
116592
+ " ruby <<'RUBY'",
116593
+ " require 'yaml'",
116594
+ " config = YAML.load_file('.postman/resources.yaml') || {}",
116595
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
116596
+ " collections = cloud.fetch('collections', {})",
116597
+ " environments = cloud.fetch('environments', {})",
116598
+ " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
116599
+ " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
116600
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
116601
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
116602
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
116603
+ // See the GitHub resolver: one environment is unambiguous, two or more without a
116604
+ // prod match still aborts.
116605
+ " environment ||= environments.values.first if environments.size == 1",
116606
+ " missing = []",
116607
+ " missing << '[Smoke] collection' unless smoke",
116608
+ " missing << '[Contract] collection' unless contract",
116609
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
116610
+ ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
116611
+ ' puts "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]#{smoke}"',
116612
+ ' puts "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]#{contract}"',
116613
+ ' puts "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]#{environment}"',
116614
+ " RUBY",
116465
116615
  " displayName: Resolve Postman Resource IDs",
116466
116616
  " - script: |",
116467
116617
  ' mkdir -p "$(Agent.TempDirectory)/postman-ssl"',
@@ -116617,7 +116767,7 @@ function getCiWorkflowTemplate(provider, options = {}) {
116617
116767
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
116618
116768
  const installUrl = validateHttpsInstallUrl(rawUrl);
116619
116769
  const privateMockAuth = options.privateMockAuth === true;
116620
- return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth)).join("\n");
116770
+ return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName)).join("\n");
116621
116771
  }
116622
116772
  return renderCiWorkflowTemplate(options);
116623
116773
  }
@@ -128075,16 +128225,71 @@ function matchesCollectionDirectory(filePath, directoryName) {
128075
128225
  function matchesBaselineCollectionResource(filePath, assetProjectName) {
128076
128226
  return matchesCollectionDirectory(filePath, assetProjectName) || matchesCollectionDirectory(filePath, `${LEGACY_BASELINE_COLLECTION_PREFIX} ${assetProjectName}`);
128077
128227
  }
128078
- function getEnvironmentUidsFromResources(resourcesState) {
128228
+ function getEnvironmentOwnershipFromResources(resourcesState, artifactDir, projectName, environmentNames) {
128229
+ const result = {};
128230
+ for (const environmentName of environmentNames) {
128231
+ result[environmentName] = {
128232
+ currentRef: canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, environmentName)),
128233
+ legacyRef: canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, environmentName))
128234
+ };
128235
+ }
128079
128236
  const cloudEnvironments = resourcesState?.cloudResources?.environments;
128080
128237
  if (!cloudEnvironments) {
128081
- return {};
128238
+ return result;
128082
128239
  }
128083
- return Object.fromEntries(
128084
- Object.entries(cloudEnvironments).map(([filePath, uid]) => {
128085
- const match = filePath.match(/\/environments\/(.+)\.postman_environment\.json$/);
128086
- return match ? [match[1], uid] : null;
128087
- }).filter((entry) => Boolean(entry))
128240
+ const normalizedEntries = Object.entries(cloudEnvironments).map(([filePath, uid]) => [
128241
+ canonicalizeRelativePath(filePath),
128242
+ uid
128243
+ ]);
128244
+ for (const environmentName of environmentNames) {
128245
+ const ownership = result[environmentName];
128246
+ const currentUids = new Set(
128247
+ normalizedEntries.filter(([filePath]) => filePath === ownership.currentRef).map(([, uid]) => uid)
128248
+ );
128249
+ const legacyUids = new Set(
128250
+ normalizedEntries.filter(([filePath]) => filePath === ownership.legacyRef).map(([, uid]) => uid)
128251
+ );
128252
+ if (currentUids.size > 1 || legacyUids.size > 1) {
128253
+ throw new StateUnreadableError(
128254
+ `.postman/resources.yaml maps environment "${environmentName}" artifact to multiple UIDs. Reconcile the entries before rerunning.`
128255
+ );
128256
+ }
128257
+ ownership.currentUid = [...currentUids][0];
128258
+ ownership.legacyUid = [...legacyUids][0];
128259
+ const uids = new Set([ownership.currentUid, ownership.legacyUid].filter(Boolean));
128260
+ if (uids.size > 1) {
128261
+ throw new StateUnreadableError(
128262
+ `.postman/resources.yaml maps canonical and legacy artifacts for environment "${environmentName}" to different UIDs. Reconcile the entries before rerunning.`
128263
+ );
128264
+ }
128265
+ }
128266
+ return result;
128267
+ }
128268
+ function getEnvironmentUidsFromOwnership(ownership) {
128269
+ const result = {};
128270
+ for (const [environmentName, entry] of Object.entries(ownership)) {
128271
+ const uid = entry.currentUid || entry.legacyUid;
128272
+ if (uid) result[environmentName] = uid;
128273
+ }
128274
+ return result;
128275
+ }
128276
+ function assertEnvironmentTargetAvailable(options) {
128277
+ assertPathWithinCwd(options.filePath, "environment target");
128278
+ let stats;
128279
+ try {
128280
+ stats = (0, import_node_fs6.lstatSync)(options.filePath);
128281
+ } catch (error) {
128282
+ if (error.code === "ENOENT") return;
128283
+ throw error;
128284
+ }
128285
+ if (stats.isSymbolicLink() || !stats.isFile()) {
128286
+ throw new StateUnreadableError(
128287
+ `${options.filePath} exists but is not a regular owned environment file. Remove it before rerunning; no cloud environment was changed.`
128288
+ );
128289
+ }
128290
+ if (options.currentUid) return;
128291
+ throw new StateUnreadableError(
128292
+ `${options.filePath} exists but is not owned by .postman/resources.yaml. Adopt its UID into the manifest or remove the file before rerunning; no cloud environment was changed.`
128088
128293
  );
128089
128294
  }
128090
128295
  function normalizeToPosix(filePath) {
@@ -128368,12 +128573,34 @@ async function writeGitHubRepositorySecrets(repository, token, secrets, env = pr
128368
128573
  }
128369
128574
  }
128370
128575
  async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
128576
+ const environmentNames = /* @__PURE__ */ new Set([
128577
+ ...inputs.environments,
128578
+ ...Object.keys(inputs.environmentUids)
128579
+ ]);
128580
+ assertUniqueEnvironmentFileNames(inputs.projectName, environmentNames);
128581
+ const trackedOwnership = getEnvironmentOwnershipFromResources(
128582
+ resourcesState,
128583
+ inputs.artifactDir,
128584
+ inputs.projectName,
128585
+ environmentNames
128586
+ );
128587
+ const trackedUids = getEnvironmentUidsFromOwnership(trackedOwnership);
128588
+ for (const environmentName of environmentNames) {
128589
+ assertEnvironmentTargetAvailable({
128590
+ filePath: path4.join(
128591
+ inputs.artifactDir,
128592
+ "environments",
128593
+ environmentFileName(inputs.projectName, environmentName)
128594
+ ),
128595
+ currentUid: trackedOwnership[environmentName].currentUid
128596
+ });
128597
+ }
128371
128598
  const envUids = {
128372
- ...getEnvironmentUidsFromResources(resourcesState),
128599
+ ...trackedUids,
128373
128600
  ...inputs.environmentUids
128374
128601
  };
128375
128602
  if (!inputs.workspaceId) {
128376
- return envUids;
128603
+ return { envUids, ownership: trackedOwnership };
128377
128604
  }
128378
128605
  const mask = resolveRepoSyncMasker(dependencies);
128379
128606
  const envRemediation = "verify access-token/team/workspace permissions then rerun";
@@ -128475,7 +128702,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
128475
128702
  );
128476
128703
  }
128477
128704
  }
128478
- return envUids;
128705
+ return { envUids, ownership: trackedOwnership };
128479
128706
  }
128480
128707
  async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl, privateMockAuth) {
128481
128708
  if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
@@ -128635,7 +128862,21 @@ function resolveDurableWorkspaceId(options) {
128635
128862
  }
128636
128863
  return prior === candidate ? prior : void 0;
128637
128864
  }
128638
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState, preserveGeneratedAssets = false) {
128865
+ function buildResourcesManifest(options) {
128866
+ const {
128867
+ workspaceId,
128868
+ collectionMap,
128869
+ envMap,
128870
+ artifactDir,
128871
+ projectName,
128872
+ localSpecRefs,
128873
+ mappedSpecRef,
128874
+ specId,
128875
+ existingSpecs,
128876
+ priorState,
128877
+ preserveGeneratedAssets = false,
128878
+ preservePriorEnvironmentAssets = preserveGeneratedAssets
128879
+ } = options;
128639
128880
  const manifest = { ...priorState ?? {} };
128640
128881
  delete manifest.version;
128641
128882
  delete manifest.workspace;
@@ -128652,15 +128893,26 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128652
128893
  if (collectionKeys.length > 0) {
128653
128894
  cloudResources.collections = effectiveCollectionMap;
128654
128895
  }
128655
- const priorEnvironmentMap = preserveGeneratedAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
128896
+ const priorEnvironmentMap = preservePriorEnvironmentAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
128656
128897
  const envEntries = Object.entries(envMap);
128898
+ for (const [envName] of envEntries) {
128899
+ const replacedRefs = /* @__PURE__ */ new Set([
128900
+ canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, envName)),
128901
+ canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, envName))
128902
+ ]);
128903
+ for (const filePath of Object.keys(priorEnvironmentMap)) {
128904
+ if (replacedRefs.has(canonicalizeRelativePath(filePath))) {
128905
+ delete priorEnvironmentMap[filePath];
128906
+ }
128907
+ }
128908
+ }
128657
128909
  if (Object.keys(priorEnvironmentMap).length > 0 || envEntries.length > 0) {
128658
128910
  cloudResources.environments = priorEnvironmentMap;
128659
128911
  }
128660
128912
  if (envEntries.length > 0) {
128661
128913
  cloudResources.environments ??= {};
128662
128914
  for (const [envName, envUid] of envEntries) {
128663
- cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
128915
+ cloudResources.environments[environmentManifestRef(artifactDir, projectName, envName)] = envUid;
128664
128916
  }
128665
128917
  }
128666
128918
  void localSpecRefs;
@@ -129396,18 +129648,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
129396
129648
  }
129397
129649
  ensureDir(".postman");
129398
129650
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
129399
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
129400
- durableWorkspaceId,
129401
- {},
129402
- {},
129403
- inputs.artifactDir,
129404
- discoveredSpecs.map((spec) => spec.configRelativePath),
129405
- mappedSpecCloudKey,
129406
- inputs.specId || void 0,
129407
- preservePriorWorkspaceResources ? options.existingSpecs : void 0,
129408
- options.priorState,
129409
- preservePriorWorkspaceResources
129410
- ));
129651
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
129652
+ workspaceId: durableWorkspaceId,
129653
+ collectionMap: {},
129654
+ envMap: {},
129655
+ artifactDir: inputs.artifactDir,
129656
+ projectName: inputs.projectName,
129657
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
129658
+ mappedSpecRef: mappedSpecCloudKey,
129659
+ specId: inputs.specId || void 0,
129660
+ existingSpecs: preservePriorWorkspaceResources ? options.existingSpecs : void 0,
129661
+ priorState: options.priorState,
129662
+ preserveGeneratedAssets: preservePriorWorkspaceResources
129663
+ }));
129411
129664
  dependencies.core.info(
129412
129665
  "onboarding-scope=spec-only; updated only workspace/spec state in .postman/resources.yaml."
129413
129666
  );
@@ -129473,15 +129726,31 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
129473
129726
  }
129474
129727
  }
129475
129728
  const environmentSpecs = [
129476
- ...Object.entries(envUids).map(([envName, envUid]) => ({
129477
- envName,
129478
- envUid,
129479
- filePath: `${environmentsDir}/${envName}.postman_environment.json`
129480
- })),
129729
+ ...Object.entries(envUids).map(([envName, envUid]) => {
129730
+ const ownership = options.environmentOwnership[envName];
129731
+ if (!ownership) {
129732
+ throw new Error(
129733
+ `environment "${envName}" has a UID but no manifest ownership entry; environment sync and artifact export disagree`
129734
+ );
129735
+ }
129736
+ return {
129737
+ kind: "environment",
129738
+ envName,
129739
+ envUid,
129740
+ filePath: `${environmentsDir}/${environmentFileName(inputs.projectName, envName)}`,
129741
+ currentUid: ownership.currentUid,
129742
+ legacyUid: ownership.legacyUid,
129743
+ legacyFilePath: ownership.legacyUid === envUid ? `${environmentsDir}/${envName}.postman_environment.json` : void 0
129744
+ };
129745
+ }),
129481
129746
  ...options.mockEnvironmentUid ? [{
129747
+ kind: "mock",
129482
129748
  envName: "manual-validation",
129483
129749
  envUid: options.mockEnvironmentUid,
129484
- filePath: `${mocksDir}/manual-validation.postman_environment.json`
129750
+ filePath: `${mocksDir}/manual-validation.postman_environment.json`,
129751
+ currentUid: void 0,
129752
+ legacyUid: void 0,
129753
+ legacyFilePath: void 0
129485
129754
  }] : []
129486
129755
  ];
129487
129756
  const environmentStartedAt = Date.now();
@@ -129501,26 +129770,50 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
129501
129770
  `environment-artifact-acquisition count=${environmentSpecs.length} width=${ARTIFACT_ACQUISITION_WIDTH} ms=${Date.now() - environmentStartedAt} status=${environmentStatus}`
129502
129771
  );
129503
129772
  }
129773
+ const legacyEnvironmentCleanupPaths = [];
129504
129774
  for (const [index, spec] of environmentSpecs.entries()) {
129505
- assertPathWithinCwd(spec.filePath, "environment target");
129506
- writeJsonFile(
129775
+ if (spec.kind === "mock") {
129776
+ assertPathWithinCwd(spec.filePath, "environment target");
129777
+ writeJsonFile(spec.filePath, sanitizeMockEnvironmentArtifact(environmentPayloads[index]), true);
129778
+ continue;
129779
+ }
129780
+ assertEnvironmentTargetAvailable({
129781
+ filePath: spec.filePath,
129782
+ currentUid: spec.currentUid
129783
+ });
129784
+ (0, import_node_fs6.writeFileSync)(
129507
129785
  spec.filePath,
129508
- spec.envName === "manual-validation" ? sanitizeMockEnvironmentArtifact(environmentPayloads[index]) : environmentPayloads[index],
129509
- true
129786
+ serializeEnvironmentYaml(environmentPayloads[index], `${inputs.projectName} - ${spec.envName}`)
129510
129787
  );
129788
+ if (spec.legacyFilePath) {
129789
+ legacyEnvironmentCleanupPaths.push(spec.legacyFilePath);
129790
+ }
129511
129791
  }
129512
129792
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
129513
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
129514
- durableWorkspaceId,
129515
- manifestCollections,
129516
- envUids,
129517
- inputs.artifactDir,
129518
- discoveredSpecs.map((spec) => spec.configRelativePath),
129519
- mappedSpecCloudKey,
129520
- inputs.specId || void 0,
129521
- options.existingSpecs,
129522
- options.priorState
129523
- ));
129793
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
129794
+ workspaceId: durableWorkspaceId,
129795
+ collectionMap: manifestCollections,
129796
+ envMap: envUids,
129797
+ artifactDir: inputs.artifactDir,
129798
+ projectName: inputs.projectName,
129799
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
129800
+ mappedSpecRef: mappedSpecCloudKey,
129801
+ specId: inputs.specId || void 0,
129802
+ existingSpecs: options.existingSpecs,
129803
+ priorState: options.priorState,
129804
+ // The export path always rewrites collections from this run, but keeps prior
129805
+ // environments so an environment tracked by an earlier run is not dropped.
129806
+ preserveGeneratedAssets: false,
129807
+ preservePriorEnvironmentAssets: preservePriorWorkspaceResources
129808
+ }));
129809
+ for (const legacyFilePath of legacyEnvironmentCleanupPaths) {
129810
+ assertPathWithinCwd(legacyFilePath, "legacy environment target");
129811
+ try {
129812
+ (0, import_node_fs6.unlinkSync)(legacyFilePath);
129813
+ } catch (error) {
129814
+ if (error.code !== "ENOENT") throw error;
129815
+ }
129816
+ }
129524
129817
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
129525
129818
  let existingWorkflows;
129526
129819
  try {
@@ -129545,6 +129838,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
129545
129838
  }
129546
129839
  if (inputs.provider === "azure-devops") {
129547
129840
  return getCiWorkflowTemplate(inputs.provider, {
129841
+ projectName: inputs.projectName,
129548
129842
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
129549
129843
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
129550
129844
  runnerOs: inputs.ciRunnerOs,
@@ -129553,6 +129847,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
129553
129847
  });
129554
129848
  }
129555
129849
  return renderCiWorkflowTemplate({
129850
+ projectName: inputs.projectName,
129556
129851
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
129557
129852
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
129558
129853
  runnerOs: inputs.ciRunnerOs,
@@ -129801,10 +130096,10 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
129801
130096
  }
129802
130097
  }
129803
130098
  const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
129804
- const envUids = onboardingScope === "full" ? await logger.phase(
130099
+ const { envUids, ownership: environmentOwnership } = onboardingScope === "full" ? await logger.phase(
129805
130100
  "sync-environments",
129806
130101
  async () => upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker)
129807
- ) : {};
130102
+ ) : { envUids: {}, ownership: {} };
129808
130103
  outputs["environment-uids-json"] = JSON.stringify(envUids);
129809
130104
  dependencies.core.setOutput("environment-uids-json", outputs["environment-uids-json"]);
129810
130105
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
@@ -130204,6 +130499,7 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
130204
130499
  mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
130205
130500
  releaseLabel,
130206
130501
  priorState: resourcesState,
130502
+ environmentOwnership,
130207
130503
  preparedPrebuiltCollections,
130208
130504
  privateMockAuth: outputs["mock-auth-required"] === "true"
130209
130505
  })