@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/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) {
@@ -130752,7 +130979,21 @@ function resolveDurableWorkspaceId(options) {
130752
130979
  }
130753
130980
  return prior === candidate ? prior : void 0;
130754
130981
  }
130755
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState, preserveGeneratedAssets = false) {
130982
+ function buildResourcesManifest(options) {
130983
+ const {
130984
+ workspaceId,
130985
+ collectionMap,
130986
+ envMap,
130987
+ artifactDir,
130988
+ projectName,
130989
+ localSpecRefs,
130990
+ mappedSpecRef,
130991
+ specId,
130992
+ existingSpecs,
130993
+ priorState,
130994
+ preserveGeneratedAssets = false,
130995
+ preservePriorEnvironmentAssets = preserveGeneratedAssets
130996
+ } = options;
130756
130997
  const manifest = { ...priorState ?? {} };
130757
130998
  delete manifest.version;
130758
130999
  delete manifest.workspace;
@@ -130769,15 +131010,26 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
130769
131010
  if (collectionKeys.length > 0) {
130770
131011
  cloudResources.collections = effectiveCollectionMap;
130771
131012
  }
130772
- const priorEnvironmentMap = preserveGeneratedAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
131013
+ const priorEnvironmentMap = preservePriorEnvironmentAssets ? { ...priorState?.cloudResources?.environments ?? {} } : {};
130773
131014
  const envEntries = Object.entries(envMap);
131015
+ for (const [envName] of envEntries) {
131016
+ const replacedRefs = /* @__PURE__ */ new Set([
131017
+ canonicalizeRelativePath(environmentManifestRef(artifactDir, projectName, envName)),
131018
+ canonicalizeRelativePath(legacyEnvironmentManifestRef(artifactDir, envName))
131019
+ ]);
131020
+ for (const filePath of Object.keys(priorEnvironmentMap)) {
131021
+ if (replacedRefs.has(canonicalizeRelativePath(filePath))) {
131022
+ delete priorEnvironmentMap[filePath];
131023
+ }
131024
+ }
131025
+ }
130774
131026
  if (Object.keys(priorEnvironmentMap).length > 0 || envEntries.length > 0) {
130775
131027
  cloudResources.environments = priorEnvironmentMap;
130776
131028
  }
130777
131029
  if (envEntries.length > 0) {
130778
131030
  cloudResources.environments ??= {};
130779
131031
  for (const [envName, envUid] of envEntries) {
130780
- cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
131032
+ cloudResources.environments[environmentManifestRef(artifactDir, projectName, envName)] = envUid;
130781
131033
  }
130782
131034
  }
130783
131035
  void localSpecRefs;
@@ -131513,18 +131765,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131513
131765
  }
131514
131766
  ensureDir(".postman");
131515
131767
  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
- ));
131768
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131769
+ workspaceId: durableWorkspaceId,
131770
+ collectionMap: {},
131771
+ envMap: {},
131772
+ artifactDir: inputs.artifactDir,
131773
+ projectName: inputs.projectName,
131774
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131775
+ mappedSpecRef: mappedSpecCloudKey,
131776
+ specId: inputs.specId || void 0,
131777
+ existingSpecs: preservePriorWorkspaceResources ? options.existingSpecs : void 0,
131778
+ priorState: options.priorState,
131779
+ preserveGeneratedAssets: preservePriorWorkspaceResources
131780
+ }));
131528
131781
  dependencies.core.info(
131529
131782
  "onboarding-scope=spec-only; updated only workspace/spec state in .postman/resources.yaml."
131530
131783
  );
@@ -131590,15 +131843,31 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131590
131843
  }
131591
131844
  }
131592
131845
  const environmentSpecs = [
131593
- ...Object.entries(envUids).map(([envName, envUid]) => ({
131594
- envName,
131595
- envUid,
131596
- filePath: `${environmentsDir}/${envName}.postman_environment.json`
131597
- })),
131846
+ ...Object.entries(envUids).map(([envName, envUid]) => {
131847
+ const ownership = options.environmentOwnership[envName];
131848
+ if (!ownership) {
131849
+ throw new Error(
131850
+ `environment "${envName}" has a UID but no manifest ownership entry; environment sync and artifact export disagree`
131851
+ );
131852
+ }
131853
+ return {
131854
+ kind: "environment",
131855
+ envName,
131856
+ envUid,
131857
+ filePath: `${environmentsDir}/${environmentFileName(inputs.projectName, envName)}`,
131858
+ currentUid: ownership.currentUid,
131859
+ legacyUid: ownership.legacyUid,
131860
+ legacyFilePath: ownership.legacyUid === envUid ? `${environmentsDir}/${envName}.postman_environment.json` : void 0
131861
+ };
131862
+ }),
131598
131863
  ...options.mockEnvironmentUid ? [{
131864
+ kind: "mock",
131599
131865
  envName: "manual-validation",
131600
131866
  envUid: options.mockEnvironmentUid,
131601
- filePath: `${mocksDir}/manual-validation.postman_environment.json`
131867
+ filePath: `${mocksDir}/manual-validation.postman_environment.json`,
131868
+ currentUid: void 0,
131869
+ legacyUid: void 0,
131870
+ legacyFilePath: void 0
131602
131871
  }] : []
131603
131872
  ];
131604
131873
  const environmentStartedAt = Date.now();
@@ -131618,26 +131887,50 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
131618
131887
  `environment-artifact-acquisition count=${environmentSpecs.length} width=${ARTIFACT_ACQUISITION_WIDTH} ms=${Date.now() - environmentStartedAt} status=${environmentStatus}`
131619
131888
  );
131620
131889
  }
131890
+ const legacyEnvironmentCleanupPaths = [];
131621
131891
  for (const [index, spec] of environmentSpecs.entries()) {
131622
- assertPathWithinCwd(spec.filePath, "environment target");
131623
- writeJsonFile(
131892
+ if (spec.kind === "mock") {
131893
+ assertPathWithinCwd(spec.filePath, "environment target");
131894
+ writeJsonFile(spec.filePath, sanitizeMockEnvironmentArtifact(environmentPayloads[index]), true);
131895
+ continue;
131896
+ }
131897
+ assertEnvironmentTargetAvailable({
131898
+ filePath: spec.filePath,
131899
+ currentUid: spec.currentUid
131900
+ });
131901
+ (0, import_node_fs6.writeFileSync)(
131624
131902
  spec.filePath,
131625
- spec.envName === "manual-validation" ? sanitizeMockEnvironmentArtifact(environmentPayloads[index]) : environmentPayloads[index],
131626
- true
131903
+ serializeEnvironmentYaml(environmentPayloads[index], `${inputs.projectName} - ${spec.envName}`)
131627
131904
  );
131905
+ if (spec.legacyFilePath) {
131906
+ legacyEnvironmentCleanupPaths.push(spec.legacyFilePath);
131907
+ }
131628
131908
  }
131629
131909
  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
- ));
131910
+ (0, import_node_fs6.writeFileSync)(".postman/resources.yaml", buildResourcesManifest({
131911
+ workspaceId: durableWorkspaceId,
131912
+ collectionMap: manifestCollections,
131913
+ envMap: envUids,
131914
+ artifactDir: inputs.artifactDir,
131915
+ projectName: inputs.projectName,
131916
+ localSpecRefs: discoveredSpecs.map((spec) => spec.configRelativePath),
131917
+ mappedSpecRef: mappedSpecCloudKey,
131918
+ specId: inputs.specId || void 0,
131919
+ existingSpecs: options.existingSpecs,
131920
+ priorState: options.priorState,
131921
+ // The export path always rewrites collections from this run, but keeps prior
131922
+ // environments so an environment tracked by an earlier run is not dropped.
131923
+ preserveGeneratedAssets: false,
131924
+ preservePriorEnvironmentAssets: preservePriorWorkspaceResources
131925
+ }));
131926
+ for (const legacyFilePath of legacyEnvironmentCleanupPaths) {
131927
+ assertPathWithinCwd(legacyFilePath, "legacy environment target");
131928
+ try {
131929
+ (0, import_node_fs6.unlinkSync)(legacyFilePath);
131930
+ } catch (error2) {
131931
+ if (error2.code !== "ENOENT") throw error2;
131932
+ }
131933
+ }
131641
131934
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
131642
131935
  let existingWorkflows;
131643
131936
  try {
@@ -131662,6 +131955,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131662
131955
  }
131663
131956
  if (inputs.provider === "azure-devops") {
131664
131957
  return getCiWorkflowTemplate(inputs.provider, {
131958
+ projectName: inputs.projectName,
131665
131959
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131666
131960
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131667
131961
  runnerOs: inputs.ciRunnerOs,
@@ -131670,6 +131964,7 @@ function renderCiWorkflow(inputs, privateMockAuth) {
131670
131964
  });
131671
131965
  }
131672
131966
  return renderCiWorkflowTemplate({
131967
+ projectName: inputs.projectName,
131673
131968
  postmanCliInstallUrl: inputs.postmanCliInstallUrl,
131674
131969
  postmanCliWindowsInstallUrl: inputs.postmanCliWindowsInstallUrl,
131675
131970
  runnerOs: inputs.ciRunnerOs,
@@ -131918,10 +132213,10 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
131918
132213
  }
131919
132214
  }
131920
132215
  const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
131921
- const envUids = onboardingScope === "full" ? await logger.phase(
132216
+ const { envUids, ownership: environmentOwnership } = onboardingScope === "full" ? await logger.phase(
131922
132217
  "sync-environments",
131923
132218
  async () => upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker)
131924
- ) : {};
132219
+ ) : { envUids: {}, ownership: {} };
131925
132220
  outputs["environment-uids-json"] = JSON.stringify(envUids);
131926
132221
  dependencies.core.setOutput("environment-uids-json", outputs["environment-uids-json"]);
131927
132222
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
@@ -132321,6 +132616,7 @@ async function runRepoSyncInner(inputs, dependencies, executionContext) {
132321
132616
  mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
132322
132617
  releaseLabel,
132323
132618
  priorState: resourcesState,
132619
+ environmentOwnership,
132324
132620
  preparedPrebuiltCollections,
132325
132621
  privateMockAuth: outputs["mock-auth-required"] === "true"
132326
132622
  })
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.0",
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"