@postman-cs/onboarding-repo-sync 2.10.9 → 2.11.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/README.md CHANGED
@@ -69,6 +69,15 @@ The example permissions let `GITHUB_TOKEN` commit generated artifacts and update
69
69
 
70
70
  `postman-access-token` is required: every asset operation (environment create/get/update, collection read, mock, monitor) plus workspace-to-repository linking and system environment association runs through the access-token gateway. Use `postman-resolve-service-token-action` to mint it at runtime from a [Postman service account](https://learning.postman.com/docs/administration/service-accounts/) PMAK. Without it the action fails fast — the PMAK is not an asset-routing fallback; it only mints/re-mints the access token, powers the generated CI workflow's `postman login --with-api-key`, and mints the CI `POSTMAN_API_KEY` secret. See [docs/credentials.md](docs/credentials.md).
71
71
 
72
+ To manage the complete values for an environment, use a rich entry in the same input. String entries remain backward-compatible and receive generated values; rich entries replace the environment values exactly as supplied (plus the action's branch-ownership marker when applicable).
73
+
74
+ ```yaml
75
+ environments-json: >-
76
+ [{"slug":"dev","values":[{"key":"baseUrl","value":"https://dev.example.com"},{"key":"jwtToken","value":"","type":"secret"}]}]
77
+ ```
78
+
79
+ Secret-typed values must be empty. Supply the real secret as a masked runtime variable from the customer's vault when running the collection; repo-sync neither reads nor persists that value. `value`, `type`, and `enabled` default to `""`, `"default"`, and `true`. `env-runtime-urls-json` and generated credential slots apply only to string entries because a rich entry is the complete desired definition.
80
+
72
81
  ### Disable CI workflow generation
73
82
 
74
83
  For existing repositories that already own their CI workflow, disable workflow generation:
@@ -178,7 +187,7 @@ with:
178
187
  | `mock-visibility` | Required mock access policy. Public is anonymous; private requires a runtime x-api-key supplied by the caller and is never persisted by repo-sync. | no | `private` |
179
188
  | `mock-environment-enabled` | Create or update a dedicated manual-validation environment whose baseUrl is the validated mock URL. This environment is excluded from runtime CI selection and never contains a mock credential. | no | `false` |
180
189
  | `monitor-cron` | Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run). | no | `""` |
181
- | `environments-json` | JSON array of environment slugs to create or update. | no | `["prod"]` |
190
+ | `environments-json` | JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots. | no | `["prod"]` |
182
191
  | `git-provider` | Git provider override ('github', 'gitlab', 'bitbucket', 'azure-devops'). Auto-detected from environment when omitted. | no | |
183
192
  | `ado-token` | Azure DevOps personal access token or system token used to push commits in Azure Pipelines. Defaults to SYSTEM_ACCESSTOKEN when available. | no | |
184
193
  | `repo-url` | Explicit repository URL (GitHub, GitLab, or Azure DevOps). Defaults to the URL inferred from runner environment when omitted. For commit-and-push it must identify the checked-out origin. | no | |
package/action.yml CHANGED
@@ -76,7 +76,7 @@ inputs:
76
76
  required: false
77
77
  default: ""
78
78
  environments-json:
79
- description: JSON array of environment slugs to create or update.
79
+ description: JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.
80
80
  required: false
81
81
  default: '["prod"]'
82
82
  git-provider:
package/dist/action.cjs CHANGED
@@ -118094,6 +118094,8 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118094
118094
  "jobs:",
118095
118095
  " test:",
118096
118096
  " runs-on: ubuntu-latest",
118097
+ " env:",
118098
+ " HAS_SSL_CERT: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
118097
118099
  " steps:",
118098
118100
  " - uses: actions/checkout@v5",
118099
118101
  " - name: Install Postman CLI",
@@ -118127,7 +118129,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118127
118129
  " end",
118128
118130
  " RUBY",
118129
118131
  " - name: Decode SSL certificates",
118130
- " if: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
118132
+ " if: ${{ env.HAS_SSL_CERT == 'true' }}",
118131
118133
  " env:",
118132
118134
  " POSTMAN_SSL_CLIENT_CERT_B64: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 }}",
118133
118135
  " POSTMAN_SSL_CLIENT_KEY_B64: ${{ secrets.POSTMAN_SSL_CLIENT_KEY_B64 }}",
@@ -118452,8 +118454,11 @@ function renderGcWorkflowTemplate() {
118452
118454
  return [
118453
118455
  "name: Postman Preview GC",
118454
118456
  "on:",
118457
+ // The delete event takes no branches/tags filter (GitHub only allows one on
118458
+ // merge_group, push, pull_request, pull_request_target, and workflow_run).
118459
+ // The previous ["**"] was a match-everything wildcard, so dropping it leaves
118460
+ // the trigger behaviour unchanged.
118455
118461
  " delete:",
118456
- ' branches: ["**"]',
118457
118462
  " pull_request:",
118458
118463
  " types: [closed]",
118459
118464
  " schedule:",
@@ -121357,7 +121362,7 @@ var postmanRepoSyncActionContract = {
121357
121362
  default: ""
121358
121363
  },
121359
121364
  "environments-json": {
121360
- description: "JSON array of environment slugs to create or update.",
121365
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
121361
121366
  required: false,
121362
121367
  default: '["prod"]'
121363
121368
  },
@@ -129598,13 +129603,117 @@ function parseJsonMap(raw) {
129598
129603
  ])
129599
129604
  );
129600
129605
  }
129601
- function parseJsonArray(raw) {
129602
- if (!raw.trim()) return [];
129603
- const parsed = JSON.parse(raw);
129606
+ function parseEnvironmentInputs(raw) {
129607
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
129608
+ let parsed;
129609
+ try {
129610
+ parsed = JSON.parse(raw);
129611
+ } catch {
129612
+ throw new Error("environments-json must contain valid JSON");
129613
+ }
129604
129614
  if (!Array.isArray(parsed)) {
129605
- throw new Error("Expected JSON array");
129615
+ throw new Error("environments-json must be a JSON array");
129616
+ }
129617
+ const environments = [];
129618
+ const definitions = /* @__PURE__ */ Object.create(null);
129619
+ const seen = /* @__PURE__ */ new Map();
129620
+ parsed.forEach((entry, environmentIndex) => {
129621
+ const label = `environments-json[${environmentIndex}]`;
129622
+ let slug;
129623
+ let rich = false;
129624
+ if (typeof entry === "string") {
129625
+ slug = entry;
129626
+ } else {
129627
+ if (!isPlainObject2(entry)) {
129628
+ throw new Error(`${label} must be a slug string or an environment definition`);
129629
+ }
129630
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
129631
+ if (unknownFields.length > 0) {
129632
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
129633
+ }
129634
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
129635
+ throw new Error(`${label} must contain a string slug and a values array`);
129636
+ }
129637
+ slug = entry.slug;
129638
+ rich = true;
129639
+ const keys = /* @__PURE__ */ new Set();
129640
+ definitions[slug] = entry.values.map((value, valueIndex) => {
129641
+ const valueLabel = `${label}.values[${valueIndex}]`;
129642
+ if (!isPlainObject2(value)) {
129643
+ throw new Error(`${valueLabel} must be an object`);
129644
+ }
129645
+ const unknownValueFields = Object.keys(value).filter(
129646
+ (key) => !["key", "value", "type", "enabled"].includes(key)
129647
+ );
129648
+ if (unknownValueFields.length > 0) {
129649
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
129650
+ }
129651
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
129652
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
129653
+ }
129654
+ if (value.key === "x-pm-onboarding") {
129655
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
129656
+ }
129657
+ if (keys.has(value.key)) {
129658
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
129659
+ }
129660
+ keys.add(value.key);
129661
+ if (value.value !== void 0 && typeof value.value !== "string") {
129662
+ throw new Error(`${valueLabel}.value must be a string when provided`);
129663
+ }
129664
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
129665
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
129666
+ }
129667
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
129668
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
129669
+ }
129670
+ const type = value.type ?? "default";
129671
+ const normalizedValue = value.value ?? "";
129672
+ if (type === "secret" && normalizedValue) {
129673
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
129674
+ }
129675
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
129676
+ });
129677
+ }
129678
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
129679
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
129680
+ }
129681
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
129682
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
129683
+ }
129684
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
129685
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
129686
+ }
129687
+ if (!rich && !slug.trim()) {
129688
+ throw new Error(`${label} slug must be a non-empty string`);
129689
+ }
129690
+ const priorWasRich = seen.get(slug);
129691
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
129692
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
129693
+ }
129694
+ if (priorWasRich === void 0) seen.set(slug, rich);
129695
+ environments.push(slug);
129696
+ });
129697
+ return { environments, definitions };
129698
+ }
129699
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
129700
+ const definitions = inputs.environmentDefinitions;
129701
+ if (!definitions) return inputs;
129702
+ const prototype = Object.getPrototypeOf(definitions);
129703
+ if (prototype !== Object.prototype && prototype !== null) {
129704
+ throw new Error("environmentDefinitions must be a plain object");
129705
+ }
129706
+ const entries = inputs.environments.map(
129707
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
129708
+ );
129709
+ let raw;
129710
+ try {
129711
+ raw = JSON.stringify(entries);
129712
+ } catch {
129713
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
129606
129714
  }
129607
- return parsed.map((entry) => String(entry));
129715
+ const parsed = parseEnvironmentInputs(raw);
129716
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
129608
129717
  }
129609
129718
  function readInput(actionCore, name, required = false) {
129610
129719
  return actionCore.getInput(name, { required }).trim();
@@ -129701,7 +129810,7 @@ function resolveInputs(env = process.env) {
129701
129810
  },
129702
129811
  env
129703
129812
  );
129704
- const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
129813
+ const parsedEnvironments = parseEnvironmentInputs(getInput2("environments-json", env) || '["prod"]');
129705
129814
  const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
129706
129815
  const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
129707
129816
  const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
@@ -129739,7 +129848,8 @@ function resolveInputs(env = process.env) {
129739
129848
  secretsResolverProvider,
129740
129849
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
129741
129850
  releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
129742
- environments: environments.length > 0 ? environments : ["prod"],
129851
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
129852
+ environmentDefinitions: parsedEnvironments.definitions,
129743
129853
  repoUrl: repoContext.repoUrl || "",
129744
129854
  integrationBackend: getInput2("integration-backend", env) || "bifrost",
129745
129855
  workspaceLinkEnabled: parseBooleanInput(getInput2("workspace-link-enabled", env), true),
@@ -130407,7 +130517,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130407
130517
  }
130408
130518
  } catch {
130409
130519
  }
130410
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
130520
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
130521
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130411
130522
  secretsResolverProvider: inputs.secretsResolverProvider,
130412
130523
  preservedCredentialValues: priorValues
130413
130524
  });
@@ -130430,7 +130541,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130430
130541
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
130431
130542
  continue;
130432
130543
  }
130433
- const values = buildEnvironmentValues(envName, runtimeUrl, {
130544
+ const definedValues = inputs.environmentDefinitions?.[envName];
130545
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130434
130546
  secretsResolverProvider: inputs.secretsResolverProvider
130435
130547
  });
130436
130548
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -131621,6 +131733,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
131621
131733
  };
131622
131734
  }
131623
131735
  async function runRepoSync(inputs, dependencies, executionContext) {
131736
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
131624
131737
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
131625
131738
  telemetry.setTeamId(dependencies.teamId);
131626
131739
  const logger = resolveRepoSyncLogger(dependencies);
package/dist/cli.cjs CHANGED
@@ -116199,6 +116199,8 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116199
116199
  "jobs:",
116200
116200
  " test:",
116201
116201
  " runs-on: ubuntu-latest",
116202
+ " env:",
116203
+ " HAS_SSL_CERT: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
116202
116204
  " steps:",
116203
116205
  " - uses: actions/checkout@v5",
116204
116206
  " - name: Install Postman CLI",
@@ -116232,7 +116234,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
116232
116234
  " end",
116233
116235
  " RUBY",
116234
116236
  " - name: Decode SSL certificates",
116235
- " if: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
116237
+ " if: ${{ env.HAS_SSL_CERT == 'true' }}",
116236
116238
  " env:",
116237
116239
  " POSTMAN_SSL_CLIENT_CERT_B64: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 }}",
116238
116240
  " POSTMAN_SSL_CLIENT_KEY_B64: ${{ secrets.POSTMAN_SSL_CLIENT_KEY_B64 }}",
@@ -116557,8 +116559,11 @@ function renderGcWorkflowTemplate() {
116557
116559
  return [
116558
116560
  "name: Postman Preview GC",
116559
116561
  "on:",
116562
+ // The delete event takes no branches/tags filter (GitHub only allows one on
116563
+ // merge_group, push, pull_request, pull_request_target, and workflow_run).
116564
+ // The previous ["**"] was a match-everything wildcard, so dropping it leaves
116565
+ // the trigger behaviour unchanged.
116560
116566
  " delete:",
116561
- ' branches: ["**"]',
116562
116567
  " pull_request:",
116563
116568
  " types: [closed]",
116564
116569
  " schedule:",
@@ -119462,7 +119467,7 @@ var postmanRepoSyncActionContract = {
119462
119467
  default: ""
119463
119468
  },
119464
119469
  "environments-json": {
119465
- description: "JSON array of environment slugs to create or update.",
119470
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
119466
119471
  required: false,
119467
119472
  default: '["prod"]'
119468
119473
  },
@@ -127651,13 +127656,117 @@ function parseJsonMap(raw) {
127651
127656
  ])
127652
127657
  );
127653
127658
  }
127654
- function parseJsonArray(raw) {
127655
- if (!raw.trim()) return [];
127656
- const parsed = JSON.parse(raw);
127659
+ function parseEnvironmentInputs(raw) {
127660
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
127661
+ let parsed;
127662
+ try {
127663
+ parsed = JSON.parse(raw);
127664
+ } catch {
127665
+ throw new Error("environments-json must contain valid JSON");
127666
+ }
127657
127667
  if (!Array.isArray(parsed)) {
127658
- throw new Error("Expected JSON array");
127668
+ throw new Error("environments-json must be a JSON array");
127669
+ }
127670
+ const environments = [];
127671
+ const definitions = /* @__PURE__ */ Object.create(null);
127672
+ const seen = /* @__PURE__ */ new Map();
127673
+ parsed.forEach((entry, environmentIndex) => {
127674
+ const label = `environments-json[${environmentIndex}]`;
127675
+ let slug;
127676
+ let rich = false;
127677
+ if (typeof entry === "string") {
127678
+ slug = entry;
127679
+ } else {
127680
+ if (!isPlainObject2(entry)) {
127681
+ throw new Error(`${label} must be a slug string or an environment definition`);
127682
+ }
127683
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
127684
+ if (unknownFields.length > 0) {
127685
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
127686
+ }
127687
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
127688
+ throw new Error(`${label} must contain a string slug and a values array`);
127689
+ }
127690
+ slug = entry.slug;
127691
+ rich = true;
127692
+ const keys = /* @__PURE__ */ new Set();
127693
+ definitions[slug] = entry.values.map((value, valueIndex) => {
127694
+ const valueLabel = `${label}.values[${valueIndex}]`;
127695
+ if (!isPlainObject2(value)) {
127696
+ throw new Error(`${valueLabel} must be an object`);
127697
+ }
127698
+ const unknownValueFields = Object.keys(value).filter(
127699
+ (key) => !["key", "value", "type", "enabled"].includes(key)
127700
+ );
127701
+ if (unknownValueFields.length > 0) {
127702
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
127703
+ }
127704
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
127705
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
127706
+ }
127707
+ if (value.key === "x-pm-onboarding") {
127708
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
127709
+ }
127710
+ if (keys.has(value.key)) {
127711
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
127712
+ }
127713
+ keys.add(value.key);
127714
+ if (value.value !== void 0 && typeof value.value !== "string") {
127715
+ throw new Error(`${valueLabel}.value must be a string when provided`);
127716
+ }
127717
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
127718
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
127719
+ }
127720
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
127721
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
127722
+ }
127723
+ const type = value.type ?? "default";
127724
+ const normalizedValue = value.value ?? "";
127725
+ if (type === "secret" && normalizedValue) {
127726
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
127727
+ }
127728
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
127729
+ });
127730
+ }
127731
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
127732
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
127733
+ }
127734
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
127735
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
127736
+ }
127737
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
127738
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
127739
+ }
127740
+ if (!rich && !slug.trim()) {
127741
+ throw new Error(`${label} slug must be a non-empty string`);
127742
+ }
127743
+ const priorWasRich = seen.get(slug);
127744
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
127745
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
127746
+ }
127747
+ if (priorWasRich === void 0) seen.set(slug, rich);
127748
+ environments.push(slug);
127749
+ });
127750
+ return { environments, definitions };
127751
+ }
127752
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
127753
+ const definitions = inputs.environmentDefinitions;
127754
+ if (!definitions) return inputs;
127755
+ const prototype = Object.getPrototypeOf(definitions);
127756
+ if (prototype !== Object.prototype && prototype !== null) {
127757
+ throw new Error("environmentDefinitions must be a plain object");
127758
+ }
127759
+ const entries = inputs.environments.map(
127760
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
127761
+ );
127762
+ let raw;
127763
+ try {
127764
+ raw = JSON.stringify(entries);
127765
+ } catch {
127766
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
127659
127767
  }
127660
- return parsed.map((entry) => String(entry));
127768
+ const parsed = parseEnvironmentInputs(raw);
127769
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
127661
127770
  }
127662
127771
  function normalizeRepoWriteMode(value) {
127663
127772
  if (value === "none" || value === "commit-only" || value === "commit-and-push") {
@@ -127751,7 +127860,7 @@ function resolveInputs(env = process.env) {
127751
127860
  },
127752
127861
  env
127753
127862
  );
127754
- const environments = parseJsonArray(getInput("environments-json", env) || '["prod"]');
127863
+ const parsedEnvironments = parseEnvironmentInputs(getInput("environments-json", env) || '["prod"]');
127755
127864
  const secretsResolverProvider = parseSecretsResolverProvider(getInput("secrets-resolver", env));
127756
127865
  const systemEnvMap = parseJsonMap(getInput("system-env-map-json", env) || "{}");
127757
127866
  const environmentUids = parseJsonMap(getInput("environment-uids-json", env) || "{}");
@@ -127789,7 +127898,8 @@ function resolveInputs(env = process.env) {
127789
127898
  secretsResolverProvider,
127790
127899
  specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
127791
127900
  releaseLabel: normalizeReleaseLabel(getInput("release-label", env)) || void 0,
127792
- environments: environments.length > 0 ? environments : ["prod"],
127901
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
127902
+ environmentDefinitions: parsedEnvironments.definitions,
127793
127903
  repoUrl: repoContext.repoUrl || "",
127794
127904
  integrationBackend: getInput("integration-backend", env) || "bifrost",
127795
127905
  workspaceLinkEnabled: parseBooleanInput(getInput("workspace-link-enabled", env), true),
@@ -128316,7 +128426,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
128316
128426
  }
128317
128427
  } catch {
128318
128428
  }
128319
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
128429
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
128430
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
128320
128431
  secretsResolverProvider: inputs.secretsResolverProvider,
128321
128432
  preservedCredentialValues: priorValues
128322
128433
  });
@@ -128339,7 +128450,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
128339
128450
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
128340
128451
  continue;
128341
128452
  }
128342
- const values = buildEnvironmentValues(envName, runtimeUrl, {
128453
+ const definedValues = inputs.environmentDefinitions?.[envName];
128454
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
128343
128455
  secretsResolverProvider: inputs.secretsResolverProvider
128344
128456
  });
128345
128457
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -129530,6 +129642,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
129530
129642
  };
129531
129643
  }
129532
129644
  async function runRepoSync(inputs, dependencies, executionContext) {
129645
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
129533
129646
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
129534
129647
  telemetry.setTeamId(dependencies.teamId);
129535
129648
  const logger = resolveRepoSyncLogger(dependencies);
package/dist/index.cjs CHANGED
@@ -118120,6 +118120,8 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118120
118120
  "jobs:",
118121
118121
  " test:",
118122
118122
  " runs-on: ubuntu-latest",
118123
+ " env:",
118124
+ " HAS_SSL_CERT: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
118123
118125
  " steps:",
118124
118126
  " - uses: actions/checkout@v5",
118125
118127
  " - name: Install Postman CLI",
@@ -118153,7 +118155,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion, privateMockAuth) {
118153
118155
  " end",
118154
118156
  " RUBY",
118155
118157
  " - name: Decode SSL certificates",
118156
- " if: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 != '' }}",
118158
+ " if: ${{ env.HAS_SSL_CERT == 'true' }}",
118157
118159
  " env:",
118158
118160
  " POSTMAN_SSL_CLIENT_CERT_B64: ${{ secrets.POSTMAN_SSL_CLIENT_CERT_B64 }}",
118159
118161
  " POSTMAN_SSL_CLIENT_KEY_B64: ${{ secrets.POSTMAN_SSL_CLIENT_KEY_B64 }}",
@@ -118478,8 +118480,11 @@ function renderGcWorkflowTemplate() {
118478
118480
  return [
118479
118481
  "name: Postman Preview GC",
118480
118482
  "on:",
118483
+ // The delete event takes no branches/tags filter (GitHub only allows one on
118484
+ // merge_group, push, pull_request, pull_request_target, and workflow_run).
118485
+ // The previous ["**"] was a match-everything wildcard, so dropping it leaves
118486
+ // the trigger behaviour unchanged.
118481
118487
  " delete:",
118482
- ' branches: ["**"]',
118483
118488
  " pull_request:",
118484
118489
  " types: [closed]",
118485
118490
  " schedule:",
@@ -121383,7 +121388,7 @@ var postmanRepoSyncActionContract = {
121383
121388
  default: ""
121384
121389
  },
121385
121390
  "environments-json": {
121386
- description: "JSON array of environment slugs to create or update.",
121391
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
121387
121392
  required: false,
121388
121393
  default: '["prod"]'
121389
121394
  },
@@ -129624,13 +129629,117 @@ function parseJsonMap(raw) {
129624
129629
  ])
129625
129630
  );
129626
129631
  }
129627
- function parseJsonArray(raw) {
129628
- if (!raw.trim()) return [];
129629
- const parsed = JSON.parse(raw);
129632
+ function parseEnvironmentInputs(raw) {
129633
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
129634
+ let parsed;
129635
+ try {
129636
+ parsed = JSON.parse(raw);
129637
+ } catch {
129638
+ throw new Error("environments-json must contain valid JSON");
129639
+ }
129630
129640
  if (!Array.isArray(parsed)) {
129631
- throw new Error("Expected JSON array");
129641
+ throw new Error("environments-json must be a JSON array");
129642
+ }
129643
+ const environments = [];
129644
+ const definitions = /* @__PURE__ */ Object.create(null);
129645
+ const seen = /* @__PURE__ */ new Map();
129646
+ parsed.forEach((entry, environmentIndex) => {
129647
+ const label = `environments-json[${environmentIndex}]`;
129648
+ let slug;
129649
+ let rich = false;
129650
+ if (typeof entry === "string") {
129651
+ slug = entry;
129652
+ } else {
129653
+ if (!isPlainObject2(entry)) {
129654
+ throw new Error(`${label} must be a slug string or an environment definition`);
129655
+ }
129656
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
129657
+ if (unknownFields.length > 0) {
129658
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
129659
+ }
129660
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
129661
+ throw new Error(`${label} must contain a string slug and a values array`);
129662
+ }
129663
+ slug = entry.slug;
129664
+ rich = true;
129665
+ const keys = /* @__PURE__ */ new Set();
129666
+ definitions[slug] = entry.values.map((value, valueIndex) => {
129667
+ const valueLabel = `${label}.values[${valueIndex}]`;
129668
+ if (!isPlainObject2(value)) {
129669
+ throw new Error(`${valueLabel} must be an object`);
129670
+ }
129671
+ const unknownValueFields = Object.keys(value).filter(
129672
+ (key) => !["key", "value", "type", "enabled"].includes(key)
129673
+ );
129674
+ if (unknownValueFields.length > 0) {
129675
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
129676
+ }
129677
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
129678
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
129679
+ }
129680
+ if (value.key === "x-pm-onboarding") {
129681
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
129682
+ }
129683
+ if (keys.has(value.key)) {
129684
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
129685
+ }
129686
+ keys.add(value.key);
129687
+ if (value.value !== void 0 && typeof value.value !== "string") {
129688
+ throw new Error(`${valueLabel}.value must be a string when provided`);
129689
+ }
129690
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
129691
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
129692
+ }
129693
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
129694
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
129695
+ }
129696
+ const type = value.type ?? "default";
129697
+ const normalizedValue = value.value ?? "";
129698
+ if (type === "secret" && normalizedValue) {
129699
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
129700
+ }
129701
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
129702
+ });
129703
+ }
129704
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
129705
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
129706
+ }
129707
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
129708
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
129709
+ }
129710
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
129711
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
129712
+ }
129713
+ if (!rich && !slug.trim()) {
129714
+ throw new Error(`${label} slug must be a non-empty string`);
129715
+ }
129716
+ const priorWasRich = seen.get(slug);
129717
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
129718
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
129719
+ }
129720
+ if (priorWasRich === void 0) seen.set(slug, rich);
129721
+ environments.push(slug);
129722
+ });
129723
+ return { environments, definitions };
129724
+ }
129725
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
129726
+ const definitions = inputs.environmentDefinitions;
129727
+ if (!definitions) return inputs;
129728
+ const prototype = Object.getPrototypeOf(definitions);
129729
+ if (prototype !== Object.prototype && prototype !== null) {
129730
+ throw new Error("environmentDefinitions must be a plain object");
129731
+ }
129732
+ const entries = inputs.environments.map(
129733
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
129734
+ );
129735
+ let raw;
129736
+ try {
129737
+ raw = JSON.stringify(entries);
129738
+ } catch {
129739
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
129632
129740
  }
129633
- return parsed.map((entry) => String(entry));
129741
+ const parsed = parseEnvironmentInputs(raw);
129742
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
129634
129743
  }
129635
129744
  function readInput(actionCore, name, required = false) {
129636
129745
  return actionCore.getInput(name, { required }).trim();
@@ -129727,7 +129836,7 @@ function resolveInputs(env = process.env) {
129727
129836
  },
129728
129837
  env
129729
129838
  );
129730
- const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
129839
+ const parsedEnvironments = parseEnvironmentInputs(getInput2("environments-json", env) || '["prod"]');
129731
129840
  const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
129732
129841
  const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
129733
129842
  const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
@@ -129765,7 +129874,8 @@ function resolveInputs(env = process.env) {
129765
129874
  secretsResolverProvider,
129766
129875
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
129767
129876
  releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
129768
- environments: environments.length > 0 ? environments : ["prod"],
129877
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
129878
+ environmentDefinitions: parsedEnvironments.definitions,
129769
129879
  repoUrl: repoContext.repoUrl || "",
129770
129880
  integrationBackend: getInput2("integration-backend", env) || "bifrost",
129771
129881
  workspaceLinkEnabled: parseBooleanInput(getInput2("workspace-link-enabled", env), true),
@@ -130433,7 +130543,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130433
130543
  }
130434
130544
  } catch {
130435
130545
  }
130436
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
130546
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
130547
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130437
130548
  secretsResolverProvider: inputs.secretsResolverProvider,
130438
130549
  preservedCredentialValues: priorValues
130439
130550
  });
@@ -130456,7 +130567,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130456
130567
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
130457
130568
  continue;
130458
130569
  }
130459
- const values = buildEnvironmentValues(envName, runtimeUrl, {
130570
+ const definedValues = inputs.environmentDefinitions?.[envName];
130571
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130460
130572
  secretsResolverProvider: inputs.secretsResolverProvider
130461
130573
  });
130462
130574
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -131647,6 +131759,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
131647
131759
  };
131648
131760
  }
131649
131761
  async function runRepoSync(inputs, dependencies, executionContext) {
131762
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
131650
131763
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
131651
131764
  telemetry.setTeamId(dependencies.teamId);
131652
131765
  const logger = resolveRepoSyncLogger(dependencies);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cs/onboarding-repo-sync",
3
- "version": "2.10.9",
3
+ "version": "2.11.1",
4
4
  "description": "Postman repo sync GitHub Action.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -25,6 +25,7 @@
25
25
  "verify:dist:assert": "npm run verify:dist:shape && npm run verify:dist:parity",
26
26
  "verify:dist": "npm run build && npm run verify:dist:assert",
27
27
  "docs:tables": "node scripts/render-action-tables.mjs",
28
+ "docs:pins": "node scripts/check-doc-pins.mjs",
28
29
  "lint": "eslint .",
29
30
  "lint:fix": "eslint . --fix",
30
31
  "test": "vitest run --exclude tests/ci-workflow-template.test.ts && vitest run tests/ci-workflow-template.test.ts && node --test .github/scripts/dispatch-e2e-monitor.test.mjs .github/scripts/prefetch-vendored-deps.test.mjs .github/scripts/verify-e2e-release.test.mjs",