@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/README.md CHANGED
@@ -292,7 +292,7 @@ postman/collections/[Smoke] core-payments/
292
292
  <folder>.yaml
293
293
  <request>.yaml
294
294
  postman/environments/
295
- prod.postman_environment.json
295
+ core-payments - prod.environment.yaml
296
296
  .postman/
297
297
  resources.yaml
298
298
  ```
package/dist/action.cjs CHANGED
@@ -118041,6 +118041,102 @@ function resolvePostmanEndpointProfile(stack, region = "us", env = process.env)
118041
118041
  };
118042
118042
  }
118043
118043
 
118044
+ // src/lib/postman/environment-yaml.ts
118045
+ var import_fs_utils = __toESM(require_dist5(), 1);
118046
+ var EXTENSION = ".environment.yaml";
118047
+ var SEPARATOR = " - ";
118048
+ var MAX_BASENAME_BYTES = 64;
118049
+ var SANITIZE_OPTIONS = {
118050
+ replacement: "-",
118051
+ combineConsecutiveReplacements: true
118052
+ };
118053
+ function assertWellFormed(value, label) {
118054
+ for (let index = 0; index < value.length; index += 1) {
118055
+ const current2 = value.charCodeAt(index);
118056
+ if (current2 >= 55296 && current2 <= 56319) {
118057
+ const next = value.charCodeAt(index + 1);
118058
+ if (next < 56320 || next > 57343) throw new Error(`${label} must contain well-formed Unicode`);
118059
+ index += 1;
118060
+ } else if (current2 >= 56320 && current2 <= 57343) {
118061
+ throw new Error(`${label} must contain well-formed Unicode`);
118062
+ }
118063
+ }
118064
+ }
118065
+ function safeEnvironmentName(value) {
118066
+ assertWellFormed(value, "environment name");
118067
+ const hasInvalidCharacter = [...value].some((char) => {
118068
+ const code = char.charCodeAt(0);
118069
+ return char === "/" || char === "\\" || code <= 31 || code >= 127 && code <= 159;
118070
+ });
118071
+ if (!value || hasInvalidCharacter) {
118072
+ throw new Error("environment name must be non-empty and contain no path separators or control characters");
118073
+ }
118074
+ return (0, import_fs_utils.sanitizeFilename)(value, {
118075
+ ...SANITIZE_OPTIONS,
118076
+ maxLength: MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR)
118077
+ });
118078
+ }
118079
+ function environmentFileName(projectName, environmentName) {
118080
+ assertWellFormed(projectName, "project name");
118081
+ const suffix = safeEnvironmentName(environmentName);
118082
+ const projectBudget = Math.max(
118083
+ 0,
118084
+ MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR) - Buffer.byteLength(suffix)
118085
+ );
118086
+ const prefix = (0, import_fs_utils.sanitizeFilename)(projectName, { ...SANITIZE_OPTIONS, maxLength: projectBudget });
118087
+ return `${prefix ? `${prefix}${SEPARATOR}` : ""}${suffix}${EXTENSION}`;
118088
+ }
118089
+ function assertUniqueEnvironmentFileNames(projectName, environmentNames) {
118090
+ const owners = /* @__PURE__ */ new Map();
118091
+ for (const environmentName of environmentNames) {
118092
+ const fileName = environmentFileName(projectName, environmentName);
118093
+ const key = fileName.normalize("NFD").toUpperCase().toLowerCase();
118094
+ const owner = owners.get(key);
118095
+ if (owner) {
118096
+ throw new Error(
118097
+ `Environment identities "${owner}" and "${environmentName}" resolve to the same artifact filename ${fileName}`
118098
+ );
118099
+ }
118100
+ owners.set(key, environmentName);
118101
+ }
118102
+ }
118103
+ function environmentManifestRef(artifactDir, projectName, environmentName) {
118104
+ return `../${artifactDir}/environments/${environmentFileName(projectName, environmentName)}`;
118105
+ }
118106
+ function legacyEnvironmentManifestRef(artifactDir, environmentName) {
118107
+ return `../${artifactDir}/environments/${environmentName}.postman_environment.json`;
118108
+ }
118109
+ function serializeEnvironmentYaml(payload, displayName) {
118110
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
118111
+ throw new Error("sync-service environment must contain an object body");
118112
+ }
118113
+ const body = payload;
118114
+ if (!Array.isArray(body.values)) {
118115
+ throw new Error("sync-service environment must contain a values array");
118116
+ }
118117
+ const values = body.values.map((entry) => {
118118
+ const input = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {};
118119
+ const secret = input.secret === true || input.type === "secret";
118120
+ const output = {
118121
+ key: input.key == null ? "" : String(input.key)
118122
+ };
118123
+ if (secret) {
118124
+ output.secret = true;
118125
+ if (input.source != null) output.source = input.source;
118126
+ } else {
118127
+ output.value = input.value == null ? "" : String(input.value);
118128
+ }
118129
+ if (input.enabled === false) output.disabled = true;
118130
+ if (input.description != null) output.description = String(input.description);
118131
+ return output;
118132
+ });
118133
+ const environment = { name: displayName, values };
118134
+ if (Number.isInteger(body.color) && Number(body.color) >= 0 && Number(body.color) < 360) {
118135
+ environment.color = Number(body.color);
118136
+ }
118137
+ return dump(environment, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
118138
+ }
118139
+
118044
118140
  // src/lib/ci-workflow-template.ts
118045
118141
  var DEFAULT_POSTMAN_CLI_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliInstallUrl;
118046
118142
  var DEFAULT_POSTMAN_CLI_WINDOWS_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliWindowsInstallUrl;
@@ -118068,6 +118164,16 @@ function resolveCiRunnerOs(runnerOsOption) {
118068
118164
  }
118069
118165
  throw new Error("ci-runner-os must be one of: linux, windows; got: " + runnerOs);
118070
118166
  }
118167
+ function expectedEnvironmentFileName(projectName) {
118168
+ const name = String(projectName || "").trim();
118169
+ return name ? environmentFileName(name, "prod") : "";
118170
+ }
118171
+ function rubyStringLiteral(value) {
118172
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
118173
+ }
118174
+ function powershellStringLiteral(value) {
118175
+ return `'${value.replace(/'/g, "''")}'`;
118176
+ }
118071
118177
  function renderCiWorkflowTemplate(options = {}) {
118072
118178
  const runnerOs = resolveCiRunnerOs(options.runnerOs);
118073
118179
  if (runnerOs === "windows") {
@@ -118076,9 +118182,15 @@ function renderCiWorkflowTemplate(options = {}) {
118076
118182
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
118077
118183
  const installUrl = validateHttpsInstallUrl(rawUrl);
118078
118184
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
118079
- return buildCiWorkflowLines(installUrl, postmanRegion, options.privateMockAuth === true).join("\n");
118185
+ return buildCiWorkflowLines(
118186
+ installUrl,
118187
+ postmanRegion,
118188
+ options.privateMockAuth === true,
118189
+ options.projectName
118190
+ ).join("\n");
118080
118191
  }
118081
- function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118192
+ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118193
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118082
118194
  return [
118083
118195
  "name: CI/CD Pipeline",
118084
118196
  "on:",
@@ -118116,11 +118228,18 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118116
118228
  " environments = cloud.fetch('environments', {})",
118117
118229
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
118118
118230
  " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
118119
- " environment = environments.find { |path, _| path.end_with?('/prod.postman_environment.json') }&.last || environments.values.first",
118231
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
118232
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
118233
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
118234
+ // A repo whose environments are named only `stage` or `dev` has no prod file to
118235
+ // match, and aborting there would break generated CI that worked before the YAML
118236
+ // migration. One environment is unambiguous, so use it; two or more without a
118237
+ // prod match is a real ambiguity and still aborts.
118238
+ " environment ||= environments.values.first if environments.size == 1",
118120
118239
  " missing = []",
118121
- " missing << 'smoke collection' unless smoke",
118122
- " missing << 'contract collection' unless contract",
118123
- " missing << 'environment' unless environment",
118240
+ " missing << '[Smoke] collection' unless smoke",
118241
+ " missing << '[Contract] collection' unless contract",
118242
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
118124
118243
  ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
118125
118244
  " File.open(ENV.fetch('GITHUB_ENV'), 'a') do |file|",
118126
118245
  ' file.puts("POSTMAN_SMOKE_COLLECTION_UID=#{smoke}")',
@@ -118230,7 +118349,8 @@ function buildAdoWindowsCollectionRunLines(displayName, collectionEnvironmentNam
118230
118349
  ...privateMockAuth ? [" POSTMAN_API_KEY: $(POSTMAN_API_KEY)"] : []
118231
118350
  ];
118232
118351
  }
118233
- function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118352
+ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118353
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118234
118354
  return [
118235
118355
  "trigger:",
118236
118356
  " branches:",
@@ -118275,22 +118395,38 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
118275
118395
  " $contract = ''",
118276
118396
  " $environment = ''",
118277
118397
  " $fallbackEnvironment = ''",
118398
+ " $environmentCount = 0",
118399
+ ` $expectedEnvironment = ${powershellStringLiteral(expectedEnvironment)}`,
118278
118400
  " foreach ($line in Get-Content -LiteralPath '.postman/resources.yaml') {",
118279
- " if ($line -match '^ (collections|environments):\\s*$') { $section = $Matches[1]; continue }",
118401
+ " if ($line -match '^ (collections|environments|specs):\\s*$') { $section = $Matches[1]; continue }",
118280
118402
  " if ($line -notmatch '^ (.+?):\\s+(.+?)\\s*$') { continue }",
118281
- ` $key = $Matches[1].Trim().Trim("'").Trim('"')`,
118403
+ " $rawKey = $Matches[1].Trim()",
118404
+ ` if ($rawKey.StartsWith("'") -and $rawKey.EndsWith("'")) {`,
118405
+ ` $key = $rawKey.Substring(1, $rawKey.Length - 2) -replace "''", "'"`,
118406
+ ` } elseif ($rawKey.StartsWith('"') -and $rawKey.EndsWith('"')) {`,
118407
+ " $key = $rawKey.Substring(1, $rawKey.Length - 2)",
118408
+ " } else {",
118409
+ " $key = $rawKey",
118410
+ " }",
118282
118411
  ` $value = $Matches[2].Trim().Trim("'").Trim('"')`,
118283
118412
  " if ($section -eq 'collections' -and $key -match '\\[Smoke\\]') { $smoke = $value }",
118284
118413
  " if ($section -eq 'collections' -and $key -match '\\[Contract\\]') { $contract = $value }",
118285
118414
  " if ($section -eq 'environments') {",
118415
+ " $environmentCount += 1",
118286
118416
  " if ([string]::IsNullOrWhiteSpace($fallbackEnvironment)) { $fallbackEnvironment = $value }",
118287
- " if ($key -match 'prod\\.postman_environment\\.json$') { $environment = $value }",
118417
+ " $fileName = [IO.Path]::GetFileName($key)",
118418
+ " if ($fileName -eq $expectedEnvironment) { $environment = $value }",
118419
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $fileName -eq 'prod.postman_environment.json') { $environment = $value }",
118288
118420
  " }",
118289
118421
  " }",
118290
- " if ([string]::IsNullOrWhiteSpace($environment)) { $environment = $fallbackEnvironment }",
118291
- " if ([string]::IsNullOrWhiteSpace($smoke)) { throw 'Missing smoke collection UID in .postman/resources.yaml' }",
118292
- " if ([string]::IsNullOrWhiteSpace($contract)) { throw 'Missing contract collection UID in .postman/resources.yaml' }",
118293
- " if ([string]::IsNullOrWhiteSpace($environment)) { throw 'Missing environment UID in .postman/resources.yaml' }",
118422
+ // Matches the Ruby resolvers: a single environment is unambiguous, so use it
118423
+ // rather than aborting a pipeline that worked before the YAML migration.
118424
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $environmentCount -eq 1) { $environment = $fallbackEnvironment }",
118425
+ " $missing = @()",
118426
+ " if ([string]::IsNullOrWhiteSpace($smoke)) { $missing += '[Smoke] collection' }",
118427
+ " if ([string]::IsNullOrWhiteSpace($contract)) { $missing += '[Contract] collection' }",
118428
+ " if ([string]::IsNullOrWhiteSpace($environment)) { $missing += $(if ($expectedEnvironment) { $expectedEnvironment } else { 'environment' }) }",
118429
+ " if ($missing.Count) { throw ('Missing Postman resource IDs in .postman/resources.yaml: ' + ($missing -join ', ')) }",
118294
118430
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$smoke"',
118295
118431
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$contract"',
118296
118432
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$environment"',
@@ -118320,7 +118456,8 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
118320
118456
  ""
118321
118457
  ];
118322
118458
  }
118323
- function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118459
+ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118460
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118324
118461
  return [
118325
118462
  "trigger:",
118326
118463
  " branches:",
@@ -118347,16 +118484,29 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118347
118484
  " env:",
118348
118485
  " POSTMAN_API_KEY: $(POSTMAN_API_KEY)",
118349
118486
  " - script: |",
118350
- " SMOKE=$(grep '\\[Smoke\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118351
- " CONTRACT=$(grep '\\[Contract\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118352
- " ENV=$(grep 'prod\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118353
- " ENV=${ENV:-$(grep '\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')}",
118354
- ' [ -n "$SMOKE" ] || { echo "Missing smoke collection UID in .postman/resources.yaml"; exit 1; }',
118355
- ' [ -n "$CONTRACT" ] || { echo "Missing contract collection UID in .postman/resources.yaml"; exit 1; }',
118356
- ' [ -n "$ENV" ] || { echo "Missing environment UID in .postman/resources.yaml"; exit 1; }',
118357
- ' echo "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$SMOKE"',
118358
- ' echo "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$CONTRACT"',
118359
- ' echo "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$ENV"',
118487
+ " ruby <<'RUBY'",
118488
+ " require 'yaml'",
118489
+ " config = YAML.load_file('.postman/resources.yaml') || {}",
118490
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
118491
+ " collections = cloud.fetch('collections', {})",
118492
+ " environments = cloud.fetch('environments', {})",
118493
+ " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
118494
+ " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
118495
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
118496
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
118497
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
118498
+ // See the GitHub resolver: one environment is unambiguous, two or more without a
118499
+ // prod match still aborts.
118500
+ " environment ||= environments.values.first if environments.size == 1",
118501
+ " missing = []",
118502
+ " missing << '[Smoke] collection' unless smoke",
118503
+ " missing << '[Contract] collection' unless contract",
118504
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
118505
+ ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
118506
+ ' puts "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]#{smoke}"',
118507
+ ' puts "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]#{contract}"',
118508
+ ' puts "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]#{environment}"',
118509
+ " RUBY",
118360
118510
  " displayName: Resolve Postman Resource IDs",
118361
118511
  " - script: |",
118362
118512
  ' mkdir -p "$(Agent.TempDirectory)/postman-ssl"',
@@ -118512,7 +118662,7 @@ function getCiWorkflowTemplate(provider, options = {}) {
118512
118662
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
118513
118663
  const installUrl = validateHttpsInstallUrl(rawUrl);
118514
118664
  const privateMockAuth = options.privateMockAuth === true;
118515
- return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth)).join("\n");
118665
+ return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName)).join("\n");
118516
118666
  }
118517
118667
  return renderCiWorkflowTemplate(options);
118518
118668
  }
@@ -130025,16 +130175,71 @@ function matchesCollectionDirectory(filePath, directoryName) {
130025
130175
  function matchesBaselineCollectionResource(filePath, assetProjectName) {
130026
130176
  return matchesCollectionDirectory(filePath, assetProjectName) || matchesCollectionDirectory(filePath, `${LEGACY_BASELINE_COLLECTION_PREFIX} ${assetProjectName}`);
130027
130177
  }
130028
- function getEnvironmentUidsFromResources(resourcesState) {
130178
+ function getEnvironmentOwnershipFromResources(resourcesState, artifactDir, projectName, environmentNames) {
130179
+ const result = {};
130180
+ for (const environmentName of environmentNames) {
130181
+ result[environmentName] = {
130182
+ currentRef: canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, environmentName)),
130183
+ legacyRef: canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, environmentName))
130184
+ };
130185
+ }
130029
130186
  const cloudEnvironments = resourcesState?.cloudResources?.environments;
130030
130187
  if (!cloudEnvironments) {
130031
- return {};
130188
+ return result;
130032
130189
  }
130033
- return Object.fromEntries(
130034
- Object.entries(cloudEnvironments).map(([filePath, uid]) => {
130035
- const match = filePath.match(/\/environments\/(.+)\.postman_environment\.json$/);
130036
- return match ? [match[1], uid] : null;
130037
- }).filter((entry) => Boolean(entry))
130190
+ const normalizedEntries = Object.entries(cloudEnvironments).map(([filePath, uid]) => [
130191
+ canonicalizeRelativePath(filePath),
130192
+ uid
130193
+ ]);
130194
+ for (const environmentName of environmentNames) {
130195
+ const ownership = result[environmentName];
130196
+ const currentUids = new Set(
130197
+ normalizedEntries.filter(([filePath]) => filePath === ownership.currentRef).map(([, uid]) => uid)
130198
+ );
130199
+ const legacyUids = new Set(
130200
+ normalizedEntries.filter(([filePath]) => filePath === ownership.legacyRef).map(([, uid]) => uid)
130201
+ );
130202
+ if (currentUids.size > 1 || legacyUids.size > 1) {
130203
+ throw new StateUnreadableError(
130204
+ `.postman/resources.yaml maps environment "${environmentName}" artifact to multiple UIDs. Reconcile the entries before rerunning.`
130205
+ );
130206
+ }
130207
+ ownership.currentUid = [...currentUids][0];
130208
+ ownership.legacyUid = [...legacyUids][0];
130209
+ const uids = new Set([ownership.currentUid, ownership.legacyUid].filter(Boolean));
130210
+ if (uids.size > 1) {
130211
+ throw new StateUnreadableError(
130212
+ `.postman/resources.yaml maps canonical and legacy artifacts for environment "${environmentName}" to different UIDs. Reconcile the entries before rerunning.`
130213
+ );
130214
+ }
130215
+ }
130216
+ return result;
130217
+ }
130218
+ function getEnvironmentUidsFromOwnership(ownership) {
130219
+ const result = {};
130220
+ for (const [environmentName, entry] of Object.entries(ownership)) {
130221
+ const uid = entry.currentUid || entry.legacyUid;
130222
+ if (uid) result[environmentName] = uid;
130223
+ }
130224
+ return result;
130225
+ }
130226
+ function assertEnvironmentTargetAvailable(options) {
130227
+ assertPathWithinCwd(options.filePath, "environment target");
130228
+ let stats;
130229
+ try {
130230
+ stats = (0, import_node_fs6.lstatSync)(options.filePath);
130231
+ } catch (error2) {
130232
+ if (error2.code === "ENOENT") return;
130233
+ throw error2;
130234
+ }
130235
+ if (stats.isSymbolicLink() || !stats.isFile()) {
130236
+ throw new StateUnreadableError(
130237
+ `${options.filePath} exists but is not a regular owned environment file. Remove it before rerunning; no cloud environment was changed.`
130238
+ );
130239
+ }
130240
+ if (options.currentUid) return;
130241
+ throw new StateUnreadableError(
130242
+ `${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.`
130038
130243
  );
130039
130244
  }
130040
130245
  function normalizeToPosix(filePath) {
@@ -130459,12 +130664,34 @@ async function persistSslSecrets(inputs, actionCore, _actionExec, repository, en
130459
130664
  }
130460
130665
  }
130461
130666
  async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
130667
+ const environmentNames = /* @__PURE__ */ new Set([
130668
+ ...inputs.environments,
130669
+ ...Object.keys(inputs.environmentUids)
130670
+ ]);
130671
+ assertUniqueEnvironmentFileNames(inputs.projectName, environmentNames);
130672
+ const trackedOwnership = getEnvironmentOwnershipFromResources(
130673
+ resourcesState,
130674
+ inputs.artifactDir,
130675
+ inputs.projectName,
130676
+ environmentNames
130677
+ );
130678
+ const trackedUids = getEnvironmentUidsFromOwnership(trackedOwnership);
130679
+ for (const environmentName of environmentNames) {
130680
+ assertEnvironmentTargetAvailable({
130681
+ filePath: path9.join(
130682
+ inputs.artifactDir,
130683
+ "environments",
130684
+ environmentFileName(inputs.projectName, environmentName)
130685
+ ),
130686
+ currentUid: trackedOwnership[environmentName].currentUid
130687
+ });
130688
+ }
130462
130689
  const envUids = {
130463
- ...getEnvironmentUidsFromResources(resourcesState),
130690
+ ...trackedUids,
130464
130691
  ...inputs.environmentUids
130465
130692
  };
130466
130693
  if (!inputs.workspaceId) {
130467
- return envUids;
130694
+ return { envUids, ownership: trackedOwnership };
130468
130695
  }
130469
130696
  const mask = resolveRepoSyncMasker(dependencies);
130470
130697
  const envRemediation = "verify access-token/team/workspace permissions then rerun";
@@ -130566,7 +130793,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130566
130793
  );
130567
130794
  }
130568
130795
  }
130569
- return envUids;
130796
+ return { envUids, ownership: trackedOwnership };
130570
130797
  }
130571
130798
  async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl, privateMockAuth) {
130572
130799
  if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
@@ -130726,7 +130953,21 @@ function resolveDurableWorkspaceId(options) {
130726
130953
  }
130727
130954
  return prior === candidate ? prior : void 0;
130728
130955
  }
130729
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState, preserveGeneratedAssets = false) {
130956
+ function buildResourcesManifest(options) {
130957
+ const {
130958
+ workspaceId,
130959
+ collectionMap,
130960
+ envMap,
130961
+ artifactDir,
130962
+ projectName,
130963
+ localSpecRefs,
130964
+ mappedSpecRef,
130965
+ specId,
130966
+ existingSpecs,
130967
+ priorState,
130968
+ preserveGeneratedAssets = false,
130969
+ preservePriorEnvironmentAssets = preserveGeneratedAssets
130970
+ } = options;
130730
130971
  const manifest = { ...priorState ?? {} };
130731
130972
  delete manifest.version;
130732
130973
  delete manifest.workspace;
@@ -130743,15 +130984,26 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
130743
130984
  if (collectionKeys.length > 0) {
130744
130985
  cloudResources.collections = effectiveCollectionMap;
130745
130986
  }
130746
- const priorEnvironmentMap = preserveGeneratedAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
130987
+ const priorEnvironmentMap = preservePriorEnvironmentAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
130747
130988
  const envEntries = Object.entries(envMap);
130989
+ for (const [envName] of envEntries) {
130990
+ const replacedRefs = /* @__PURE__ */ new Set([
130991
+ canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, envName)),
130992
+ canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, envName))
130993
+ ]);
130994
+ for (const filePath of Object.keys(priorEnvironmentMap)) {
130995
+ if (replacedRefs.has(canonicalizeRelativePath(filePath))) {
130996
+ delete priorEnvironmentMap[filePath];
130997
+ }
130998
+ }
130999
+ }
130748
131000
  if (Object.keys(priorEnvironmentMap).length > 0 || envEntries.length > 0) {
130749
131001
  cloudResources.environments = priorEnvironmentMap;
130750
131002
  }
130751
131003
  if (envEntries.length > 0) {
130752
131004
  cloudResources.environments ??= {};
130753
131005
  for (const [envName, envUid] of envEntries) {
130754
- cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
131006
+ cloudResources.environments[environmentManifestRef(artifactDir, projectName, envName)] = envUid;
130755
131007
  }
130756
131008
  }
130757
131009
  void localSpecRefs;
@@ -131487,18 +131739,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131487
131739
  }
131488
131740
  ensureDir(".postman");
131489
131741
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
131490
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
131491
- durableWorkspaceId,
131492
- {},
131493
- {},
131494
- inputs.artifactDir,
131495
- discoveredSpecs.map((spec) => spec.configRelativePath),
131496
- mappedSpecCloudKey,
131497
- inputs.specId || void 0,
131498
- preservePriorWorkspaceResources ? options.existingSpecs : void 0,
131499
- options.priorState,
131500
- preservePriorWorkspaceResources
131501
- ));
131742
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131743
+ workspaceId: durableWorkspaceId,
131744
+ collectionMap: {},
131745
+ envMap: {},
131746
+ artifactDir: inputs.artifactDir,
131747
+ projectName: inputs.projectName,
131748
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131749
+ mappedSpecRef: mappedSpecCloudKey,
131750
+ specId: inputs.specId || void 0,
131751
+ existingSpecs: preservePriorWorkspaceResources ? options.existingSpecs : void 0,
131752
+ priorState: options.priorState,
131753
+ preserveGeneratedAssets: preservePriorWorkspaceResources
131754
+ }));
131502
131755
  dependencies.core.info(
131503
131756
  "onboarding-scope=spec-only; updated only workspace/spec state in .postman/resources.yaml."
131504
131757
  );
@@ -131564,15 +131817,31 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131564
131817
  }
131565
131818
  }
131566
131819
  const environmentSpecs = [
131567
- ...Object.entries(envUids).map(([envName, envUid]) => ({
131568
- envName,
131569
- envUid,
131570
- filePath: `${environmentsDir}/${envName}.postman_environment.json`
131571
- })),
131820
+ ...Object.entries(envUids).map(([envName, envUid]) => {
131821
+ const ownership = options.environmentOwnership[envName];
131822
+ if (!ownership) {
131823
+ throw new Error(
131824
+ `environment "${envName}" has a UID but no manifest ownership entry; environment sync and artifact export disagree`
131825
+ );
131826
+ }
131827
+ return {
131828
+ kind: "environment",
131829
+ envName,
131830
+ envUid,
131831
+ filePath: `${environmentsDir}/${environmentFileName(inputs.projectName, envName)}`,
131832
+ currentUid: ownership.currentUid,
131833
+ legacyUid: ownership.legacyUid,
131834
+ legacyFilePath: ownership.legacyUid === envUid ? `${environmentsDir}/${envName}.postman_environment.json` : void 0
131835
+ };
131836
+ }),
131572
131837
  ...options.mockEnvironmentUid ? [{
131838
+ kind: "mock",
131573
131839
  envName: "manual-validation",
131574
131840
  envUid: options.mockEnvironmentUid,
131575
- filePath: `${mocksDir}/manual-validation.postman_environment.json`
131841
+ filePath: `${mocksDir}/manual-validation.postman_environment.json`,
131842
+ currentUid: void 0,
131843
+ legacyUid: void 0,
131844
+ legacyFilePath: void 0
131576
131845
  }] : []
131577
131846
  ];
131578
131847
  const environmentStartedAt = Date.now();
@@ -131592,26 +131861,50 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131592
131861
  `environment-artifact-acquisition count=${environmentSpecs.length} width=${ARTIFACT_ACQUISITION_WIDTH} ms=${Date.now() - environmentStartedAt} status=${environmentStatus}`
131593
131862
  );
131594
131863
  }
131864
+ const legacyEnvironmentCleanupPaths = [];
131595
131865
  for (const [index, spec] of environmentSpecs.entries()) {
131596
- assertPathWithinCwd(spec.filePath, "environment target");
131597
- writeJsonFile(
131866
+ if (spec.kind === "mock") {
131867
+ assertPathWithinCwd(spec.filePath, "environment target");
131868
+ writeJsonFile(spec.filePath, sanitizeMockEnvironmentArtifact(environmentPayloads[index]), true);
131869
+ continue;
131870
+ }
131871
+ assertEnvironmentTargetAvailable({
131872
+ filePath: spec.filePath,
131873
+ currentUid: spec.currentUid
131874
+ });
131875
+ (0, import_node_fs6.writeFileSync)(
131598
131876
  spec.filePath,
131599
- spec.envName === "manual-validation" ? sanitizeMockEnvironmentArtifact(environmentPayloads[index]) : environmentPayloads[index],
131600
- true
131877
+ serializeEnvironmentYaml(environmentPayloads[index], `${inputs.projectName} - ${spec.envName}`)
131601
131878
  );
131879
+ if (spec.legacyFilePath) {
131880
+ legacyEnvironmentCleanupPaths.push(spec.legacyFilePath);
131881
+ }
131602
131882
  }
131603
131883
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
131604
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
131605
- durableWorkspaceId,
131606
- manifestCollections,
131607
- envUids,
131608
- inputs.artifactDir,
131609
- discoveredSpecs.map((spec) => spec.configRelativePath),
131610
- mappedSpecCloudKey,
131611
- inputs.specId || void 0,
131612
- options.existingSpecs,
131613
- options.priorState
131614
- ));
131884
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131885
+ workspaceId: durableWorkspaceId,
131886
+ collectionMap: manifestCollections,
131887
+ envMap: envUids,
131888
+ artifactDir: inputs.artifactDir,
131889
+ projectName: inputs.projectName,
131890
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131891
+ mappedSpecRef: mappedSpecCloudKey,
131892
+ specId: inputs.specId || void 0,
131893
+ existingSpecs: options.existingSpecs,
131894
+ priorState: options.priorState,
131895
+ // The export path always rewrites collections from this run, but keeps prior
131896
+ // environments so an environment tracked by an earlier run is not dropped.
131897
+ preserveGeneratedAssets: false,
131898
+ preservePriorEnvironmentAssets: preservePriorWorkspaceResources
131899
+ }));
131900
+ for (const legacyFilePath of legacyEnvironmentCleanupPaths) {
131901
+ assertPathWithinCwd(legacyFilePath, "legacy environment target");
131902
+ try {
131903
+ (0, import_node_fs6.unlinkSync)(legacyFilePath);
131904
+ } catch (error2) {
131905
+ if (error2.code !== "ENOENT") throw error2;
131906
+ }
131907
+ }
131615
131908
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
131616
131909
  let existingWorkflows;
131617
131910
  try {
@@ -131636,6 +131929,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131636
131929
  }
131637
131930
  if (inputs.provider === "azure-devops") {
131638
131931
  return getCiWorkflowTemplate(inputs.provider, {
131932
+ projectName: inputs.projectName,
131639
131933
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131640
131934
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131641
131935
  runnerOs: inputs.ciRunnerOs,
@@ -131644,6 +131938,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131644
131938
  });
131645
131939
  }
131646
131940
  return renderCiWorkflowTemplate({
131941
+ projectName: inputs.projectName,
131647
131942
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131648
131943
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131649
131944
  runnerOs: inputs.ciRunnerOs,
@@ -131892,10 +132187,10 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
131892
132187
  }
131893
132188
  }
131894
132189
  const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
131895
- const envUids = onboardingScope === "full" ? await logger.phase(
132190
+ const { envUids, ownership: environmentOwnership } = onboardingScope === "full" ? await logger.phase(
131896
132191
  "sync-environments",
131897
132192
  async () => upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker)
131898
- ) : {};
132193
+ ) : { envUids: {}, ownership: {} };
131899
132194
  outputs["environment-uids-json"] = JSON.stringify(envUids);
131900
132195
  dependencies.core.setOutput("environment-uids-json", outputs["environment-uids-json"]);
131901
132196
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
@@ -132295,6 +132590,7 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
132295
132590
  mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
132296
132591
  releaseLabel,
132297
132592
  priorState: resourcesState,
132593
+ environmentOwnership,
132298
132594
  preparedPrebuiltCollections,
132299
132595
  privateMockAuth: outputs["mock-auth-required"] === "true"
132300
132596
  })