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

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
@@ -118067,6 +118067,102 @@ function resolvePostmanEndpointProfile(stack, region = "us", env = process.env)
118067
118067
  };
118068
118068
  }
118069
118069
 
118070
+ // src/lib/postman/environment-yaml.ts
118071
+ var import_fs_utils = __toESM(require_dist5(), 1);
118072
+ var EXTENSION = ".environment.yaml";
118073
+ var SEPARATOR = " - ";
118074
+ var MAX_BASENAME_BYTES = 64;
118075
+ var SANITIZE_OPTIONS = {
118076
+ replacement: "-",
118077
+ combineConsecutiveReplacements: true
118078
+ };
118079
+ function assertWellFormed(value, label) {
118080
+ for (let index = 0; index < value.length; index += 1) {
118081
+ const current2 = value.charCodeAt(index);
118082
+ if (current2 >= 55296 && current2 <= 56319) {
118083
+ const next = value.charCodeAt(index + 1);
118084
+ if (next < 56320 || next > 57343) throw new Error(`${label} must contain well-formed Unicode`);
118085
+ index += 1;
118086
+ } else if (current2 >= 56320 && current2 <= 57343) {
118087
+ throw new Error(`${label} must contain well-formed Unicode`);
118088
+ }
118089
+ }
118090
+ }
118091
+ function safeEnvironmentName(value) {
118092
+ assertWellFormed(value, "environment name");
118093
+ const hasInvalidCharacter = [...value].some((char) => {
118094
+ const code = char.charCodeAt(0);
118095
+ return char === "/" || char === "\\" || code <= 31 || code >= 127 && code <= 159;
118096
+ });
118097
+ if (!value || hasInvalidCharacter) {
118098
+ throw new Error("environment name must be non-empty and contain no path separators or control characters");
118099
+ }
118100
+ return (0, import_fs_utils.sanitizeFilename)(value, {
118101
+ ...SANITIZE_OPTIONS,
118102
+ maxLength: MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR)
118103
+ });
118104
+ }
118105
+ function environmentFileName(projectName, environmentName) {
118106
+ assertWellFormed(projectName, "project name");
118107
+ const suffix = safeEnvironmentName(environmentName);
118108
+ const projectBudget = Math.max(
118109
+ 0,
118110
+ MAX_BASENAME_BYTES - Buffer.byteLength(SEPARATOR) - Buffer.byteLength(suffix)
118111
+ );
118112
+ const prefix = (0, import_fs_utils.sanitizeFilename)(projectName, { ...SANITIZE_OPTIONS, maxLength: projectBudget });
118113
+ return `${prefix ? `${prefix}${SEPARATOR}` : ""}${suffix}${EXTENSION}`;
118114
+ }
118115
+ function assertUniqueEnvironmentFileNames(projectName, environmentNames) {
118116
+ const owners = /* @__PURE__ */ new Map();
118117
+ for (const environmentName of environmentNames) {
118118
+ const fileName = environmentFileName(projectName, environmentName);
118119
+ const key = fileName.normalize("NFD").toUpperCase().toLowerCase();
118120
+ const owner = owners.get(key);
118121
+ if (owner) {
118122
+ throw new Error(
118123
+ `Environment identities "${owner}" and "${environmentName}" resolve to the same artifact filename ${fileName}`
118124
+ );
118125
+ }
118126
+ owners.set(key, environmentName);
118127
+ }
118128
+ }
118129
+ function environmentManifestRef(artifactDir, projectName, environmentName) {
118130
+ return `../${artifactDir}/environments/${environmentFileName(projectName, environmentName)}`;
118131
+ }
118132
+ function legacyEnvironmentManifestRef(artifactDir, environmentName) {
118133
+ return `../${artifactDir}/environments/${environmentName}.postman_environment.json`;
118134
+ }
118135
+ function serializeEnvironmentYaml(payload, displayName) {
118136
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
118137
+ throw new Error("sync-service environment must contain an object body");
118138
+ }
118139
+ const body = payload;
118140
+ if (!Array.isArray(body.values)) {
118141
+ throw new Error("sync-service environment must contain a values array");
118142
+ }
118143
+ const values = body.values.map((entry) => {
118144
+ const input = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {};
118145
+ const secret = input.secret === true || input.type === "secret";
118146
+ const output = {
118147
+ key: input.key == null ? "" : String(input.key)
118148
+ };
118149
+ if (secret) {
118150
+ output.secret = true;
118151
+ if (input.source != null) output.source = input.source;
118152
+ } else {
118153
+ output.value = input.value == null ? "" : String(input.value);
118154
+ }
118155
+ if (input.enabled === false) output.disabled = true;
118156
+ if (input.description != null) output.description = String(input.description);
118157
+ return output;
118158
+ });
118159
+ const environment = { name: displayName, values };
118160
+ if (Number.isInteger(body.color) && Number(body.color) >= 0 && Number(body.color) < 360) {
118161
+ environment.color = Number(body.color);
118162
+ }
118163
+ return dump(environment, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
118164
+ }
118165
+
118070
118166
  // src/lib/ci-workflow-template.ts
118071
118167
  var DEFAULT_POSTMAN_CLI_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliInstallUrl;
118072
118168
  var DEFAULT_POSTMAN_CLI_WINDOWS_INSTALL_URL = POSTMAN_ENDPOINT_PROFILES.prod.cliWindowsInstallUrl;
@@ -118094,6 +118190,16 @@ function resolveCiRunnerOs(runnerOsOption) {
118094
118190
  }
118095
118191
  throw new Error("ci-runner-os must be one of: linux, windows; got: " + runnerOs);
118096
118192
  }
118193
+ function expectedEnvironmentFileName(projectName) {
118194
+ const name = String(projectName || "").trim();
118195
+ return name ? environmentFileName(name, "prod") : "";
118196
+ }
118197
+ function rubyStringLiteral(value) {
118198
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
118199
+ }
118200
+ function powershellStringLiteral(value) {
118201
+ return `'${value.replace(/'/g, "''")}'`;
118202
+ }
118097
118203
  function renderCiWorkflowTemplate(options = {}) {
118098
118204
  const runnerOs = resolveCiRunnerOs(options.runnerOs);
118099
118205
  if (runnerOs === "windows") {
@@ -118102,9 +118208,15 @@ function renderCiWorkflowTemplate(options = {}) {
118102
118208
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
118103
118209
  const installUrl = validateHttpsInstallUrl(rawUrl);
118104
118210
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
118105
- return buildCiWorkflowLines(installUrl, postmanRegion, options.privateMockAuth === true).join("\n");
118211
+ return buildCiWorkflowLines(
118212
+ installUrl,
118213
+ postmanRegion,
118214
+ options.privateMockAuth === true,
118215
+ options.projectName
118216
+ ).join("\n");
118106
118217
  }
118107
- function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118218
+ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118219
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118108
118220
  return [
118109
118221
  "name: CI/CD Pipeline",
118110
118222
  "on:",
@@ -118142,11 +118254,18 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118142
118254
  " environments = cloud.fetch('environments', {})",
118143
118255
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
118144
118256
  " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
118145
- " environment = environments.find { |path, _| path.end_with?('/prod.postman_environment.json') }&.last || environments.values.first",
118257
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
118258
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
118259
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
118260
+ // A repo whose environments are named only `stage` or `dev` has no prod file to
118261
+ // match, and aborting there would break generated CI that worked before the YAML
118262
+ // migration. One environment is unambiguous, so use it; two or more without a
118263
+ // prod match is a real ambiguity and still aborts.
118264
+ " environment ||= environments.values.first if environments.size == 1",
118146
118265
  " missing = []",
118147
- " missing << 'smoke collection' unless smoke",
118148
- " missing << 'contract collection' unless contract",
118149
- " missing << 'environment' unless environment",
118266
+ " missing << '[Smoke] collection' unless smoke",
118267
+ " missing << '[Contract] collection' unless contract",
118268
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
118150
118269
  ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
118151
118270
  " File.open(ENV.fetch('GITHUB_ENV'), 'a') do |file|",
118152
118271
  ' file.puts("POSTMAN_SMOKE_COLLECTION_UID=#{smoke}")',
@@ -118256,7 +118375,8 @@ function buildAdoWindowsCollectionRunLines(displayName, collectionEnvironmentNam
118256
118375
  ...privateMockAuth ? [" POSTMAN_API_KEY: $(POSTMAN_API_KEY)"] : []
118257
118376
  ];
118258
118377
  }
118259
- function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118378
+ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118379
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118260
118380
  return [
118261
118381
  "trigger:",
118262
118382
  " branches:",
@@ -118301,22 +118421,38 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
118301
118421
  " $contract = ''",
118302
118422
  " $environment = ''",
118303
118423
  " $fallbackEnvironment = ''",
118424
+ " $environmentCount = 0",
118425
+ ` $expectedEnvironment = ${powershellStringLiteral(expectedEnvironment)}`,
118304
118426
  " foreach ($line in Get-Content -LiteralPath '.postman/resources.yaml') {",
118305
- " if ($line -match '^ (collections|environments):\\s*$') { $section = $Matches[1]; continue }",
118427
+ " if ($line -match '^ (collections|environments|specs):\\s*$') { $section = $Matches[1]; continue }",
118306
118428
  " if ($line -notmatch '^ (.+?):\\s+(.+?)\\s*$') { continue }",
118307
- ` $key = $Matches[1].Trim().Trim("'").Trim('"')`,
118429
+ " $rawKey = $Matches[1].Trim()",
118430
+ ` if ($rawKey.StartsWith("'") -and $rawKey.EndsWith("'")) {`,
118431
+ ` $key = $rawKey.Substring(1, $rawKey.Length - 2) -replace "''", "'"`,
118432
+ ` } elseif ($rawKey.StartsWith('"') -and $rawKey.EndsWith('"')) {`,
118433
+ " $key = $rawKey.Substring(1, $rawKey.Length - 2)",
118434
+ " } else {",
118435
+ " $key = $rawKey",
118436
+ " }",
118308
118437
  ` $value = $Matches[2].Trim().Trim("'").Trim('"')`,
118309
118438
  " if ($section -eq 'collections' -and $key -match '\\[Smoke\\]') { $smoke = $value }",
118310
118439
  " if ($section -eq 'collections' -and $key -match '\\[Contract\\]') { $contract = $value }",
118311
118440
  " if ($section -eq 'environments') {",
118441
+ " $environmentCount += 1",
118312
118442
  " if ([string]::IsNullOrWhiteSpace($fallbackEnvironment)) { $fallbackEnvironment = $value }",
118313
- " if ($key -match 'prod\\.postman_environment\\.json$') { $environment = $value }",
118443
+ " $fileName = [IO.Path]::GetFileName($key)",
118444
+ " if ($fileName -eq $expectedEnvironment) { $environment = $value }",
118445
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $fileName -eq 'prod.postman_environment.json') { $environment = $value }",
118314
118446
  " }",
118315
118447
  " }",
118316
- " if ([string]::IsNullOrWhiteSpace($environment)) { $environment = $fallbackEnvironment }",
118317
- " if ([string]::IsNullOrWhiteSpace($smoke)) { throw 'Missing smoke collection UID in .postman/resources.yaml' }",
118318
- " if ([string]::IsNullOrWhiteSpace($contract)) { throw 'Missing contract collection UID in .postman/resources.yaml' }",
118319
- " if ([string]::IsNullOrWhiteSpace($environment)) { throw 'Missing environment UID in .postman/resources.yaml' }",
118448
+ // Matches the Ruby resolvers: a single environment is unambiguous, so use it
118449
+ // rather than aborting a pipeline that worked before the YAML migration.
118450
+ " if ([string]::IsNullOrWhiteSpace($environment) -and $environmentCount -eq 1) { $environment = $fallbackEnvironment }",
118451
+ " $missing = @()",
118452
+ " if ([string]::IsNullOrWhiteSpace($smoke)) { $missing += '[Smoke] collection' }",
118453
+ " if ([string]::IsNullOrWhiteSpace($contract)) { $missing += '[Contract] collection' }",
118454
+ " if ([string]::IsNullOrWhiteSpace($environment)) { $missing += $(if ($expectedEnvironment) { $expectedEnvironment } else { 'environment' }) }",
118455
+ " if ($missing.Count) { throw ('Missing Postman resource IDs in .postman/resources.yaml: ' + ($missing -join ', ')) }",
118320
118456
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$smoke"',
118321
118457
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$contract"',
118322
118458
  ' Write-Host "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$environment"',
@@ -118346,7 +118482,8 @@ function buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAu
118346
118482
  ""
118347
118483
  ];
118348
118484
  }
118349
- function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118485
+ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, projectName) {
118486
+ const expectedEnvironment = expectedEnvironmentFileName(projectName);
118350
118487
  return [
118351
118488
  "trigger:",
118352
118489
  " branches:",
@@ -118373,16 +118510,29 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118373
118510
  " env:",
118374
118511
  " POSTMAN_API_KEY: $(POSTMAN_API_KEY)",
118375
118512
  " - script: |",
118376
- " SMOKE=$(grep '\\[Smoke\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118377
- " CONTRACT=$(grep '\\[Contract\\]' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118378
- " ENV=$(grep 'prod\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')",
118379
- " ENV=${ENV:-$(grep '\\.postman_environment\\.json' .postman/resources.yaml | grep -v '^ *-' | head -1 | awk -F': ' '{print $NF}')}",
118380
- ' [ -n "$SMOKE" ] || { echo "Missing smoke collection UID in .postman/resources.yaml"; exit 1; }',
118381
- ' [ -n "$CONTRACT" ] || { echo "Missing contract collection UID in .postman/resources.yaml"; exit 1; }',
118382
- ' [ -n "$ENV" ] || { echo "Missing environment UID in .postman/resources.yaml"; exit 1; }',
118383
- ' echo "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]$SMOKE"',
118384
- ' echo "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]$CONTRACT"',
118385
- ' echo "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]$ENV"',
118513
+ " ruby <<'RUBY'",
118514
+ " require 'yaml'",
118515
+ " config = YAML.load_file('.postman/resources.yaml') || {}",
118516
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
118517
+ " collections = cloud.fetch('collections', {})",
118518
+ " environments = cloud.fetch('environments', {})",
118519
+ " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
118520
+ " contract = collections.find { |path, _| path.include?('[Contract]') }&.last",
118521
+ ` expected_environment = ${rubyStringLiteral(expectedEnvironment)}`,
118522
+ " environment = environments.find { |path, _| File.basename(path) == expected_environment }&.last unless expected_environment.empty?",
118523
+ " environment ||= environments.find { |path, _| File.basename(path) == 'prod.postman_environment.json' }&.last",
118524
+ // See the GitHub resolver: one environment is unambiguous, two or more without a
118525
+ // prod match still aborts.
118526
+ " environment ||= environments.values.first if environments.size == 1",
118527
+ " missing = []",
118528
+ " missing << '[Smoke] collection' unless smoke",
118529
+ " missing << '[Contract] collection' unless contract",
118530
+ " missing << (expected_environment.empty? ? 'environment' : expected_environment) unless environment",
118531
+ ` abort("Missing Postman resource IDs in .postman/resources.yaml: #{missing.join(', ')}") unless missing.empty?`,
118532
+ ' puts "##vso[task.setvariable variable=POSTMAN_SMOKE_COLLECTION_UID]#{smoke}"',
118533
+ ' puts "##vso[task.setvariable variable=POSTMAN_CONTRACT_COLLECTION_UID]#{contract}"',
118534
+ ' puts "##vso[task.setvariable variable=POSTMAN_ENVIRONMENT_UID]#{environment}"',
118535
+ " RUBY",
118386
118536
  " displayName: Resolve Postman Resource IDs",
118387
118537
  " - script: |",
118388
118538
  ' mkdir -p "$(Agent.TempDirectory)/postman-ssl"',
@@ -118538,7 +118688,7 @@ function getCiWorkflowTemplate(provider, options = {}) {
118538
118688
  const postmanRegion = resolvePostmanRegion(options.postmanRegion);
118539
118689
  const installUrl = validateHttpsInstallUrl(rawUrl);
118540
118690
  const privateMockAuth = options.privateMockAuth === true;
118541
- return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth)).join("\n");
118691
+ return (runnerOs === "windows" ? buildAdoWindowsCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName) : buildAdoCiWorkflowLines(installUrl, postmanRegion, privateMockAuth, options.projectName)).join("\n");
118542
118692
  }
118543
118693
  return renderCiWorkflowTemplate(options);
118544
118694
  }
@@ -130051,16 +130201,71 @@ function matchesCollectionDirectory(filePath, directoryName) {
130051
130201
  function matchesBaselineCollectionResource(filePath, assetProjectName) {
130052
130202
  return matchesCollectionDirectory(filePath, assetProjectName) || matchesCollectionDirectory(filePath, `${LEGACY_BASELINE_COLLECTION_PREFIX} ${assetProjectName}`);
130053
130203
  }
130054
- function getEnvironmentUidsFromResources(resourcesState) {
130204
+ function getEnvironmentOwnershipFromResources(resourcesState, artifactDir, projectName, environmentNames) {
130205
+ const result = {};
130206
+ for (const environmentName of environmentNames) {
130207
+ result[environmentName] = {
130208
+ currentRef: canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, environmentName)),
130209
+ legacyRef: canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, environmentName))
130210
+ };
130211
+ }
130055
130212
  const cloudEnvironments = resourcesState?.cloudResources?.environments;
130056
130213
  if (!cloudEnvironments) {
130057
- return {};
130214
+ return result;
130058
130215
  }
130059
- return Object.fromEntries(
130060
- Object.entries(cloudEnvironments).map(([filePath, uid]) => {
130061
- const match = filePath.match(/\/environments\/(.+)\.postman_environment\.json$/);
130062
- return match ? [match[1], uid] : null;
130063
- }).filter((entry) => Boolean(entry))
130216
+ const normalizedEntries = Object.entries(cloudEnvironments).map(([filePath, uid]) => [
130217
+ canonicalizeRelativePath(filePath),
130218
+ uid
130219
+ ]);
130220
+ for (const environmentName of environmentNames) {
130221
+ const ownership = result[environmentName];
130222
+ const currentUids = new Set(
130223
+ normalizedEntries.filter(([filePath]) => filePath === ownership.currentRef).map(([, uid]) => uid)
130224
+ );
130225
+ const legacyUids = new Set(
130226
+ normalizedEntries.filter(([filePath]) => filePath === ownership.legacyRef).map(([, uid]) => uid)
130227
+ );
130228
+ if (currentUids.size > 1 || legacyUids.size > 1) {
130229
+ throw new StateUnreadableError(
130230
+ `.postman/resources.yaml maps environment "${environmentName}" artifact to multiple UIDs. Reconcile the entries before rerunning.`
130231
+ );
130232
+ }
130233
+ ownership.currentUid = [...currentUids][0];
130234
+ ownership.legacyUid = [...legacyUids][0];
130235
+ const uids = new Set([ownership.currentUid, ownership.legacyUid].filter(Boolean));
130236
+ if (uids.size > 1) {
130237
+ throw new StateUnreadableError(
130238
+ `.postman/resources.yaml maps canonical and legacy artifacts for environment "${environmentName}" to different UIDs. Reconcile the entries before rerunning.`
130239
+ );
130240
+ }
130241
+ }
130242
+ return result;
130243
+ }
130244
+ function getEnvironmentUidsFromOwnership(ownership) {
130245
+ const result = {};
130246
+ for (const [environmentName, entry] of Object.entries(ownership)) {
130247
+ const uid = entry.currentUid || entry.legacyUid;
130248
+ if (uid) result[environmentName] = uid;
130249
+ }
130250
+ return result;
130251
+ }
130252
+ function assertEnvironmentTargetAvailable(options) {
130253
+ assertPathWithinCwd(options.filePath, "environment target");
130254
+ let stats;
130255
+ try {
130256
+ stats = (0, import_node_fs6.lstatSync)(options.filePath);
130257
+ } catch (error2) {
130258
+ if (error2.code === "ENOENT") return;
130259
+ throw error2;
130260
+ }
130261
+ if (stats.isSymbolicLink() || !stats.isFile()) {
130262
+ throw new StateUnreadableError(
130263
+ `${options.filePath} exists but is not a regular owned environment file. Remove it before rerunning; no cloud environment was changed.`
130264
+ );
130265
+ }
130266
+ if (options.currentUid) return;
130267
+ throw new StateUnreadableError(
130268
+ `${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.`
130064
130269
  );
130065
130270
  }
130066
130271
  function normalizeToPosix(filePath) {
@@ -130485,12 +130690,34 @@ async function persistSslSecrets(inputs, actionCore, _actionExec, repository, en
130485
130690
  }
130486
130691
  }
130487
130692
  async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
130693
+ const environmentNames = /* @__PURE__ */ new Set([
130694
+ ...inputs.environments,
130695
+ ...Object.keys(inputs.environmentUids)
130696
+ ]);
130697
+ assertUniqueEnvironmentFileNames(inputs.projectName, environmentNames);
130698
+ const trackedOwnership = getEnvironmentOwnershipFromResources(
130699
+ resourcesState,
130700
+ inputs.artifactDir,
130701
+ inputs.projectName,
130702
+ environmentNames
130703
+ );
130704
+ const trackedUids = getEnvironmentUidsFromOwnership(trackedOwnership);
130705
+ for (const environmentName of environmentNames) {
130706
+ assertEnvironmentTargetAvailable({
130707
+ filePath: path9.join(
130708
+ inputs.artifactDir,
130709
+ "environments",
130710
+ environmentFileName(inputs.projectName, environmentName)
130711
+ ),
130712
+ currentUid: trackedOwnership[environmentName].currentUid
130713
+ });
130714
+ }
130488
130715
  const envUids = {
130489
- ...getEnvironmentUidsFromResources(resourcesState),
130716
+ ...trackedUids,
130490
130717
  ...inputs.environmentUids
130491
130718
  };
130492
130719
  if (!inputs.workspaceId) {
130493
- return envUids;
130720
+ return { envUids, ownership: trackedOwnership };
130494
130721
  }
130495
130722
  const mask = resolveRepoSyncMasker(dependencies);
130496
130723
  const envRemediation = "verify access-token/team/workspace permissions then rerun";
@@ -130592,7 +130819,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130592
130819
  );
130593
130820
  }
130594
130821
  }
130595
- return envUids;
130822
+ return { envUids, ownership: trackedOwnership };
130596
130823
  }
130597
130824
  async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl, privateMockAuth) {
130598
130825
  if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
@@ -130675,7 +130902,8 @@ function getCollectionDirectoryName(kind, projectName) {
130675
130902
  var VOLATILE_KEYS = /* @__PURE__ */ new Set([
130676
130903
  "createdAt",
130677
130904
  "updatedAt",
130678
- "lastUpdatedBy"
130905
+ "lastUpdatedBy",
130906
+ "lastRevision"
130679
130907
  ]);
130680
130908
  function stripVolatileFields(obj) {
130681
130909
  if (Array.isArray(obj)) {
@@ -130752,7 +130980,21 @@ function resolveDurableWorkspaceId(options) {
130752
130980
  }
130753
130981
  return prior === candidate ? prior : void 0;
130754
130982
  }
130755
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState, preserveGeneratedAssets = false) {
130983
+ function buildResourcesManifest(options) {
130984
+ const {
130985
+ workspaceId,
130986
+ collectionMap,
130987
+ envMap,
130988
+ artifactDir,
130989
+ projectName,
130990
+ localSpecRefs,
130991
+ mappedSpecRef,
130992
+ specId,
130993
+ existingSpecs,
130994
+ priorState,
130995
+ preserveGeneratedAssets = false,
130996
+ preservePriorEnvironmentAssets = preserveGeneratedAssets
130997
+ } = options;
130756
130998
  const manifest = { ...priorState ?? {} };
130757
130999
  delete manifest.version;
130758
131000
  delete manifest.workspace;
@@ -130769,15 +131011,26 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
130769
131011
  if (collectionKeys.length > 0) {
130770
131012
  cloudResources.collections = effectiveCollectionMap;
130771
131013
  }
130772
- const priorEnvironmentMap = preserveGeneratedAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
131014
+ const priorEnvironmentMap = preservePriorEnvironmentAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
130773
131015
  const envEntries = Object.entries(envMap);
131016
+ for (const [envName] of envEntries) {
131017
+ const replacedRefs = /* @__PURE__ */ new Set([
131018
+ canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, envName)),
131019
+ canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, envName))
131020
+ ]);
131021
+ for (const filePath of Object.keys(priorEnvironmentMap)) {
131022
+ if (replacedRefs.has(canonicalizeRelativePath(filePath))) {
131023
+ delete priorEnvironmentMap[filePath];
131024
+ }
131025
+ }
131026
+ }
130774
131027
  if (Object.keys(priorEnvironmentMap).length > 0 || envEntries.length > 0) {
130775
131028
  cloudResources.environments = priorEnvironmentMap;
130776
131029
  }
130777
131030
  if (envEntries.length > 0) {
130778
131031
  cloudResources.environments ??= {};
130779
131032
  for (const [envName, envUid] of envEntries) {
130780
- cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
131033
+ cloudResources.environments[environmentManifestRef(artifactDir, projectName, envName)] = envUid;
130781
131034
  }
130782
131035
  }
130783
131036
  void localSpecRefs;
@@ -131513,18 +131766,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131513
131766
  }
131514
131767
  ensureDir(".postman");
131515
131768
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
131516
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
131517
- durableWorkspaceId,
131518
- {},
131519
- {},
131520
- inputs.artifactDir,
131521
- discoveredSpecs.map((spec) => spec.configRelativePath),
131522
- mappedSpecCloudKey,
131523
- inputs.specId || void 0,
131524
- preservePriorWorkspaceResources ? options.existingSpecs : void 0,
131525
- options.priorState,
131526
- preservePriorWorkspaceResources
131527
- ));
131769
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131770
+ workspaceId: durableWorkspaceId,
131771
+ collectionMap: {},
131772
+ envMap: {},
131773
+ artifactDir: inputs.artifactDir,
131774
+ projectName: inputs.projectName,
131775
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131776
+ mappedSpecRef: mappedSpecCloudKey,
131777
+ specId: inputs.specId || void 0,
131778
+ existingSpecs: preservePriorWorkspaceResources ? options.existingSpecs : void 0,
131779
+ priorState: options.priorState,
131780
+ preserveGeneratedAssets: preservePriorWorkspaceResources
131781
+ }));
131528
131782
  dependencies.core.info(
131529
131783
  "onboarding-scope=spec-only; updated only workspace/spec state in .postman/resources.yaml."
131530
131784
  );
@@ -131590,15 +131844,31 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131590
131844
  }
131591
131845
  }
131592
131846
  const environmentSpecs = [
131593
- ...Object.entries(envUids).map(([envName, envUid]) => ({
131594
- envName,
131595
- envUid,
131596
- filePath: `${environmentsDir}/${envName}.postman_environment.json`
131597
- })),
131847
+ ...Object.entries(envUids).map(([envName, envUid]) => {
131848
+ const ownership = options.environmentOwnership[envName];
131849
+ if (!ownership) {
131850
+ throw new Error(
131851
+ `environment "${envName}" has a UID but no manifest ownership entry; environment sync and artifact export disagree`
131852
+ );
131853
+ }
131854
+ return {
131855
+ kind: "environment",
131856
+ envName,
131857
+ envUid,
131858
+ filePath: `${environmentsDir}/${environmentFileName(inputs.projectName, envName)}`,
131859
+ currentUid: ownership.currentUid,
131860
+ legacyUid: ownership.legacyUid,
131861
+ legacyFilePath: ownership.legacyUid === envUid ? `${environmentsDir}/${envName}.postman_environment.json` : void 0
131862
+ };
131863
+ }),
131598
131864
  ...options.mockEnvironmentUid ? [{
131865
+ kind: "mock",
131599
131866
  envName: "manual-validation",
131600
131867
  envUid: options.mockEnvironmentUid,
131601
- filePath: `${mocksDir}/manual-validation.postman_environment.json`
131868
+ filePath: `${mocksDir}/manual-validation.postman_environment.json`,
131869
+ currentUid: void 0,
131870
+ legacyUid: void 0,
131871
+ legacyFilePath: void 0
131602
131872
  }] : []
131603
131873
  ];
131604
131874
  const environmentStartedAt = Date.now();
@@ -131618,26 +131888,50 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131618
131888
  `environment-artifact-acquisition count=${environmentSpecs.length} width=${ARTIFACT_ACQUISITION_WIDTH} ms=${Date.now() - environmentStartedAt} status=${environmentStatus}`
131619
131889
  );
131620
131890
  }
131891
+ const legacyEnvironmentCleanupPaths = [];
131621
131892
  for (const [index, spec] of environmentSpecs.entries()) {
131622
- assertPathWithinCwd(spec.filePath, "environment target");
131623
- writeJsonFile(
131893
+ if (spec.kind === "mock") {
131894
+ assertPathWithinCwd(spec.filePath, "environment target");
131895
+ writeJsonFile(spec.filePath, sanitizeMockEnvironmentArtifact(environmentPayloads[index]), true);
131896
+ continue;
131897
+ }
131898
+ assertEnvironmentTargetAvailable({
131899
+ filePath: spec.filePath,
131900
+ currentUid: spec.currentUid
131901
+ });
131902
+ (0, import_node_fs6.writeFileSync)(
131624
131903
  spec.filePath,
131625
- spec.envName === "manual-validation" ? sanitizeMockEnvironmentArtifact(environmentPayloads[index]) : environmentPayloads[index],
131626
- true
131904
+ serializeEnvironmentYaml(environmentPayloads[index], `${inputs.projectName} - ${spec.envName}`)
131627
131905
  );
131906
+ if (spec.legacyFilePath) {
131907
+ legacyEnvironmentCleanupPaths.push(spec.legacyFilePath);
131908
+ }
131628
131909
  }
131629
131910
  assertPathWithinCwd(".postman/resources.yaml", "resources state target");
131630
- (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
131631
- durableWorkspaceId,
131632
- manifestCollections,
131633
- envUids,
131634
- inputs.artifactDir,
131635
- discoveredSpecs.map((spec) => spec.configRelativePath),
131636
- mappedSpecCloudKey,
131637
- inputs.specId || void 0,
131638
- options.existingSpecs,
131639
- options.priorState
131640
- ));
131911
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131912
+ workspaceId: durableWorkspaceId,
131913
+ collectionMap: manifestCollections,
131914
+ envMap: envUids,
131915
+ artifactDir: inputs.artifactDir,
131916
+ projectName: inputs.projectName,
131917
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131918
+ mappedSpecRef: mappedSpecCloudKey,
131919
+ specId: inputs.specId || void 0,
131920
+ existingSpecs: options.existingSpecs,
131921
+ priorState: options.priorState,
131922
+ // The export path always rewrites collections from this run, but keeps prior
131923
+ // environments so an environment tracked by an earlier run is not dropped.
131924
+ preserveGeneratedAssets: false,
131925
+ preservePriorEnvironmentAssets: preservePriorWorkspaceResources
131926
+ }));
131927
+ for (const legacyFilePath of legacyEnvironmentCleanupPaths) {
131928
+ assertPathWithinCwd(legacyFilePath, "legacy environment target");
131929
+ try {
131930
+ (0, import_node_fs6.unlinkSync)(legacyFilePath);
131931
+ } catch (error2) {
131932
+ if (error2.code !== "ENOENT") throw error2;
131933
+ }
131934
+ }
131641
131935
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
131642
131936
  let existingWorkflows;
131643
131937
  try {
@@ -131662,6 +131956,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131662
131956
  }
131663
131957
  if (inputs.provider === "azure-devops") {
131664
131958
  return getCiWorkflowTemplate(inputs.provider, {
131959
+ projectName: inputs.projectName,
131665
131960
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131666
131961
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131667
131962
  runnerOs: inputs.ciRunnerOs,
@@ -131670,6 +131965,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131670
131965
  });
131671
131966
  }
131672
131967
  return renderCiWorkflowTemplate({
131968
+ projectName: inputs.projectName,
131673
131969
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131674
131970
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131675
131971
  runnerOs: inputs.ciRunnerOs,
@@ -131918,10 +132214,10 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
131918
132214
  }
131919
132215
  }
131920
132216
  const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
131921
- const envUids = onboardingScope === "full" ? await logger.phase(
132217
+ const { envUids, ownership: environmentOwnership } = onboardingScope === "full" ? await logger.phase(
131922
132218
  "sync-environments",
131923
132219
  async () => upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker)
131924
- ) : {};
132220
+ ) : { envUids: {}, ownership: {} };
131925
132221
  outputs["environment-uids-json"] = JSON.stringify(envUids);
131926
132222
  dependencies.core.setOutput("environment-uids-json", outputs["environment-uids-json"]);
131927
132223
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
@@ -132321,6 +132617,7 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
132321
132617
  mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
132322
132618
  releaseLabel,
132323
132619
  priorState: resourcesState,
132620
+ environmentOwnership,
132324
132621
  preparedPrebuiltCollections,
132325
132622
  privateMockAuth: outputs["mock-auth-required"] === "true"
132326
132623
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cs/onboarding-repo-sync",
3
- "version": "2.11.2",
3
+ "version": "3.0.1",
4
4
  "description": "Postman repo sync GitHub Action.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -55,6 +55,7 @@
55
55
  "@actions/core": "^3.0.1",
56
56
  "@actions/exec": "^3.0.0",
57
57
  "@postman-cs/automation-core": "^1.8.1",
58
+ "@postman/fs-utils": "0.3.1",
58
59
  "js-yaml": "^5.2.1",
59
60
  "libsodium-wrappers": "0.8.4",
60
61
  "undici": "^8.10.0"