@postman-cs/onboarding-repo-sync 2.10.9 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
@@ -121357,7 +121357,7 @@ var postmanRepoSyncActionContract = {
121357
121357
  default: ""
121358
121358
  },
121359
121359
  "environments-json": {
121360
- description: "JSON array of environment slugs to create or update.",
121360
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
121361
121361
  required: false,
121362
121362
  default: '["prod"]'
121363
121363
  },
@@ -129598,13 +129598,117 @@ function parseJsonMap(raw) {
129598
129598
  ])
129599
129599
  );
129600
129600
  }
129601
- function parseJsonArray(raw) {
129602
- if (!raw.trim()) return [];
129603
- const parsed = JSON.parse(raw);
129601
+ function parseEnvironmentInputs(raw) {
129602
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
129603
+ let parsed;
129604
+ try {
129605
+ parsed = JSON.parse(raw);
129606
+ } catch {
129607
+ throw new Error("environments-json must contain valid JSON");
129608
+ }
129604
129609
  if (!Array.isArray(parsed)) {
129605
- throw new Error("Expected JSON array");
129610
+ throw new Error("environments-json must be a JSON array");
129611
+ }
129612
+ const environments = [];
129613
+ const definitions = /* @__PURE__ */ Object.create(null);
129614
+ const seen = /* @__PURE__ */ new Map();
129615
+ parsed.forEach((entry, environmentIndex) => {
129616
+ const label = `environments-json[${environmentIndex}]`;
129617
+ let slug;
129618
+ let rich = false;
129619
+ if (typeof entry === "string") {
129620
+ slug = entry;
129621
+ } else {
129622
+ if (!isPlainObject2(entry)) {
129623
+ throw new Error(`${label} must be a slug string or an environment definition`);
129624
+ }
129625
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
129626
+ if (unknownFields.length > 0) {
129627
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
129628
+ }
129629
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
129630
+ throw new Error(`${label} must contain a string slug and a values array`);
129631
+ }
129632
+ slug = entry.slug;
129633
+ rich = true;
129634
+ const keys = /* @__PURE__ */ new Set();
129635
+ definitions[slug] = entry.values.map((value, valueIndex) => {
129636
+ const valueLabel = `${label}.values[${valueIndex}]`;
129637
+ if (!isPlainObject2(value)) {
129638
+ throw new Error(`${valueLabel} must be an object`);
129639
+ }
129640
+ const unknownValueFields = Object.keys(value).filter(
129641
+ (key) => !["key", "value", "type", "enabled"].includes(key)
129642
+ );
129643
+ if (unknownValueFields.length > 0) {
129644
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
129645
+ }
129646
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
129647
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
129648
+ }
129649
+ if (value.key === "x-pm-onboarding") {
129650
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
129651
+ }
129652
+ if (keys.has(value.key)) {
129653
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
129654
+ }
129655
+ keys.add(value.key);
129656
+ if (value.value !== void 0 && typeof value.value !== "string") {
129657
+ throw new Error(`${valueLabel}.value must be a string when provided`);
129658
+ }
129659
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
129660
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
129661
+ }
129662
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
129663
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
129664
+ }
129665
+ const type = value.type ?? "default";
129666
+ const normalizedValue = value.value ?? "";
129667
+ if (type === "secret" && normalizedValue) {
129668
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
129669
+ }
129670
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
129671
+ });
129672
+ }
129673
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
129674
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
129675
+ }
129676
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
129677
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
129678
+ }
129679
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
129680
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
129681
+ }
129682
+ if (!rich && !slug.trim()) {
129683
+ throw new Error(`${label} slug must be a non-empty string`);
129684
+ }
129685
+ const priorWasRich = seen.get(slug);
129686
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
129687
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
129688
+ }
129689
+ if (priorWasRich === void 0) seen.set(slug, rich);
129690
+ environments.push(slug);
129691
+ });
129692
+ return { environments, definitions };
129693
+ }
129694
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
129695
+ const definitions = inputs.environmentDefinitions;
129696
+ if (!definitions) return inputs;
129697
+ const prototype = Object.getPrototypeOf(definitions);
129698
+ if (prototype !== Object.prototype && prototype !== null) {
129699
+ throw new Error("environmentDefinitions must be a plain object");
129700
+ }
129701
+ const entries = inputs.environments.map(
129702
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
129703
+ );
129704
+ let raw;
129705
+ try {
129706
+ raw = JSON.stringify(entries);
129707
+ } catch {
129708
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
129606
129709
  }
129607
- return parsed.map((entry) => String(entry));
129710
+ const parsed = parseEnvironmentInputs(raw);
129711
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
129608
129712
  }
129609
129713
  function readInput(actionCore, name, required = false) {
129610
129714
  return actionCore.getInput(name, { required }).trim();
@@ -129701,7 +129805,7 @@ function resolveInputs(env = process.env) {
129701
129805
  },
129702
129806
  env
129703
129807
  );
129704
- const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
129808
+ const parsedEnvironments = parseEnvironmentInputs(getInput2("environments-json", env) || '["prod"]');
129705
129809
  const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
129706
129810
  const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
129707
129811
  const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
@@ -129739,7 +129843,8 @@ function resolveInputs(env = process.env) {
129739
129843
  secretsResolverProvider,
129740
129844
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
129741
129845
  releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
129742
- environments: environments.length > 0 ? environments : ["prod"],
129846
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
129847
+ environmentDefinitions: parsedEnvironments.definitions,
129743
129848
  repoUrl: repoContext.repoUrl || "",
129744
129849
  integrationBackend: getInput2("integration-backend", env) || "bifrost",
129745
129850
  workspaceLinkEnabled: parseBooleanInput(getInput2("workspace-link-enabled", env), true),
@@ -130407,7 +130512,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130407
130512
  }
130408
130513
  } catch {
130409
130514
  }
130410
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
130515
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
130516
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130411
130517
  secretsResolverProvider: inputs.secretsResolverProvider,
130412
130518
  preservedCredentialValues: priorValues
130413
130519
  });
@@ -130430,7 +130536,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130430
130536
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
130431
130537
  continue;
130432
130538
  }
130433
- const values = buildEnvironmentValues(envName, runtimeUrl, {
130539
+ const definedValues = inputs.environmentDefinitions?.[envName];
130540
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130434
130541
  secretsResolverProvider: inputs.secretsResolverProvider
130435
130542
  });
130436
130543
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -131621,6 +131728,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
131621
131728
  };
131622
131729
  }
131623
131730
  async function runRepoSync(inputs, dependencies, executionContext) {
131731
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
131624
131732
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
131625
131733
  telemetry.setTeamId(dependencies.teamId);
131626
131734
  const logger = resolveRepoSyncLogger(dependencies);
package/dist/cli.cjs CHANGED
@@ -119462,7 +119462,7 @@ var postmanRepoSyncActionContract = {
119462
119462
  default: ""
119463
119463
  },
119464
119464
  "environments-json": {
119465
- description: "JSON array of environment slugs to create or update.",
119465
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
119466
119466
  required: false,
119467
119467
  default: '["prod"]'
119468
119468
  },
@@ -127651,13 +127651,117 @@ function parseJsonMap(raw) {
127651
127651
  ])
127652
127652
  );
127653
127653
  }
127654
- function parseJsonArray(raw) {
127655
- if (!raw.trim()) return [];
127656
- const parsed = JSON.parse(raw);
127654
+ function parseEnvironmentInputs(raw) {
127655
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
127656
+ let parsed;
127657
+ try {
127658
+ parsed = JSON.parse(raw);
127659
+ } catch {
127660
+ throw new Error("environments-json must contain valid JSON");
127661
+ }
127657
127662
  if (!Array.isArray(parsed)) {
127658
- throw new Error("Expected JSON array");
127663
+ throw new Error("environments-json must be a JSON array");
127664
+ }
127665
+ const environments = [];
127666
+ const definitions = /* @__PURE__ */ Object.create(null);
127667
+ const seen = /* @__PURE__ */ new Map();
127668
+ parsed.forEach((entry, environmentIndex) => {
127669
+ const label = `environments-json[${environmentIndex}]`;
127670
+ let slug;
127671
+ let rich = false;
127672
+ if (typeof entry === "string") {
127673
+ slug = entry;
127674
+ } else {
127675
+ if (!isPlainObject2(entry)) {
127676
+ throw new Error(`${label} must be a slug string or an environment definition`);
127677
+ }
127678
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
127679
+ if (unknownFields.length > 0) {
127680
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
127681
+ }
127682
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
127683
+ throw new Error(`${label} must contain a string slug and a values array`);
127684
+ }
127685
+ slug = entry.slug;
127686
+ rich = true;
127687
+ const keys = /* @__PURE__ */ new Set();
127688
+ definitions[slug] = entry.values.map((value, valueIndex) => {
127689
+ const valueLabel = `${label}.values[${valueIndex}]`;
127690
+ if (!isPlainObject2(value)) {
127691
+ throw new Error(`${valueLabel} must be an object`);
127692
+ }
127693
+ const unknownValueFields = Object.keys(value).filter(
127694
+ (key) => !["key", "value", "type", "enabled"].includes(key)
127695
+ );
127696
+ if (unknownValueFields.length > 0) {
127697
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
127698
+ }
127699
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
127700
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
127701
+ }
127702
+ if (value.key === "x-pm-onboarding") {
127703
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
127704
+ }
127705
+ if (keys.has(value.key)) {
127706
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
127707
+ }
127708
+ keys.add(value.key);
127709
+ if (value.value !== void 0 && typeof value.value !== "string") {
127710
+ throw new Error(`${valueLabel}.value must be a string when provided`);
127711
+ }
127712
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
127713
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
127714
+ }
127715
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
127716
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
127717
+ }
127718
+ const type = value.type ?? "default";
127719
+ const normalizedValue = value.value ?? "";
127720
+ if (type === "secret" && normalizedValue) {
127721
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
127722
+ }
127723
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
127724
+ });
127725
+ }
127726
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
127727
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
127728
+ }
127729
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
127730
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
127731
+ }
127732
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
127733
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
127734
+ }
127735
+ if (!rich && !slug.trim()) {
127736
+ throw new Error(`${label} slug must be a non-empty string`);
127737
+ }
127738
+ const priorWasRich = seen.get(slug);
127739
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
127740
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
127741
+ }
127742
+ if (priorWasRich === void 0) seen.set(slug, rich);
127743
+ environments.push(slug);
127744
+ });
127745
+ return { environments, definitions };
127746
+ }
127747
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
127748
+ const definitions = inputs.environmentDefinitions;
127749
+ if (!definitions) return inputs;
127750
+ const prototype = Object.getPrototypeOf(definitions);
127751
+ if (prototype !== Object.prototype && prototype !== null) {
127752
+ throw new Error("environmentDefinitions must be a plain object");
127753
+ }
127754
+ const entries = inputs.environments.map(
127755
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
127756
+ );
127757
+ let raw;
127758
+ try {
127759
+ raw = JSON.stringify(entries);
127760
+ } catch {
127761
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
127659
127762
  }
127660
- return parsed.map((entry) => String(entry));
127763
+ const parsed = parseEnvironmentInputs(raw);
127764
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
127661
127765
  }
127662
127766
  function normalizeRepoWriteMode(value) {
127663
127767
  if (value === "none" || value === "commit-only" || value === "commit-and-push") {
@@ -127751,7 +127855,7 @@ function resolveInputs(env = process.env) {
127751
127855
  },
127752
127856
  env
127753
127857
  );
127754
- const environments = parseJsonArray(getInput("environments-json", env) || '["prod"]');
127858
+ const parsedEnvironments = parseEnvironmentInputs(getInput("environments-json", env) || '["prod"]');
127755
127859
  const secretsResolverProvider = parseSecretsResolverProvider(getInput("secrets-resolver", env));
127756
127860
  const systemEnvMap = parseJsonMap(getInput("system-env-map-json", env) || "{}");
127757
127861
  const environmentUids = parseJsonMap(getInput("environment-uids-json", env) || "{}");
@@ -127789,7 +127893,8 @@ function resolveInputs(env = process.env) {
127789
127893
  secretsResolverProvider,
127790
127894
  specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
127791
127895
  releaseLabel: normalizeReleaseLabel(getInput("release-label", env)) || void 0,
127792
- environments: environments.length > 0 ? environments : ["prod"],
127896
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
127897
+ environmentDefinitions: parsedEnvironments.definitions,
127793
127898
  repoUrl: repoContext.repoUrl || "",
127794
127899
  integrationBackend: getInput("integration-backend", env) || "bifrost",
127795
127900
  workspaceLinkEnabled: parseBooleanInput(getInput("workspace-link-enabled", env), true),
@@ -128316,7 +128421,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
128316
128421
  }
128317
128422
  } catch {
128318
128423
  }
128319
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
128424
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
128425
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
128320
128426
  secretsResolverProvider: inputs.secretsResolverProvider,
128321
128427
  preservedCredentialValues: priorValues
128322
128428
  });
@@ -128339,7 +128445,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
128339
128445
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
128340
128446
  continue;
128341
128447
  }
128342
- const values = buildEnvironmentValues(envName, runtimeUrl, {
128448
+ const definedValues = inputs.environmentDefinitions?.[envName];
128449
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
128343
128450
  secretsResolverProvider: inputs.secretsResolverProvider
128344
128451
  });
128345
128452
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -129530,6 +129637,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
129530
129637
  };
129531
129638
  }
129532
129639
  async function runRepoSync(inputs, dependencies, executionContext) {
129640
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
129533
129641
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
129534
129642
  telemetry.setTeamId(dependencies.teamId);
129535
129643
  const logger = resolveRepoSyncLogger(dependencies);
package/dist/index.cjs CHANGED
@@ -121383,7 +121383,7 @@ var postmanRepoSyncActionContract = {
121383
121383
  default: ""
121384
121384
  },
121385
121385
  "environments-json": {
121386
- description: "JSON array of environment slugs to create or update.",
121386
+ description: "JSON array of environment slugs or full definitions ({slug, values}) to create or replace. Secret-typed values must be empty runtime slots.",
121387
121387
  required: false,
121388
121388
  default: '["prod"]'
121389
121389
  },
@@ -129624,13 +129624,117 @@ function parseJsonMap(raw) {
129624
129624
  ])
129625
129625
  );
129626
129626
  }
129627
- function parseJsonArray(raw) {
129628
- if (!raw.trim()) return [];
129629
- const parsed = JSON.parse(raw);
129627
+ function parseEnvironmentInputs(raw) {
129628
+ if (!raw.trim()) return { environments: [], definitions: /* @__PURE__ */ Object.create(null) };
129629
+ let parsed;
129630
+ try {
129631
+ parsed = JSON.parse(raw);
129632
+ } catch {
129633
+ throw new Error("environments-json must contain valid JSON");
129634
+ }
129630
129635
  if (!Array.isArray(parsed)) {
129631
- throw new Error("Expected JSON array");
129636
+ throw new Error("environments-json must be a JSON array");
129637
+ }
129638
+ const environments = [];
129639
+ const definitions = /* @__PURE__ */ Object.create(null);
129640
+ const seen = /* @__PURE__ */ new Map();
129641
+ parsed.forEach((entry, environmentIndex) => {
129642
+ const label = `environments-json[${environmentIndex}]`;
129643
+ let slug;
129644
+ let rich = false;
129645
+ if (typeof entry === "string") {
129646
+ slug = entry;
129647
+ } else {
129648
+ if (!isPlainObject2(entry)) {
129649
+ throw new Error(`${label} must be a slug string or an environment definition`);
129650
+ }
129651
+ const unknownFields = Object.keys(entry).filter((key) => key !== "slug" && key !== "values");
129652
+ if (unknownFields.length > 0) {
129653
+ throw new Error(`${label} contains unsupported field "${unknownFields[0]}"`);
129654
+ }
129655
+ if (typeof entry.slug !== "string" || !Array.isArray(entry.values)) {
129656
+ throw new Error(`${label} must contain a string slug and a values array`);
129657
+ }
129658
+ slug = entry.slug;
129659
+ rich = true;
129660
+ const keys = /* @__PURE__ */ new Set();
129661
+ definitions[slug] = entry.values.map((value, valueIndex) => {
129662
+ const valueLabel = `${label}.values[${valueIndex}]`;
129663
+ if (!isPlainObject2(value)) {
129664
+ throw new Error(`${valueLabel} must be an object`);
129665
+ }
129666
+ const unknownValueFields = Object.keys(value).filter(
129667
+ (key) => !["key", "value", "type", "enabled"].includes(key)
129668
+ );
129669
+ if (unknownValueFields.length > 0) {
129670
+ throw new Error(`${valueLabel} contains unsupported field "${unknownValueFields[0]}"`);
129671
+ }
129672
+ if (typeof value.key !== "string" || !value.key.trim() || value.key !== value.key.trim()) {
129673
+ throw new Error(`${valueLabel}.key must be a non-empty string without surrounding whitespace`);
129674
+ }
129675
+ if (value.key === "x-pm-onboarding") {
129676
+ throw new Error(`${valueLabel}.key is reserved by repo-sync`);
129677
+ }
129678
+ if (keys.has(value.key)) {
129679
+ throw new Error(`${label} contains duplicate variable key "${value.key}"`);
129680
+ }
129681
+ keys.add(value.key);
129682
+ if (value.value !== void 0 && typeof value.value !== "string") {
129683
+ throw new Error(`${valueLabel}.value must be a string when provided`);
129684
+ }
129685
+ if (value.type !== void 0 && value.type !== "default" && value.type !== "secret") {
129686
+ throw new Error(`${valueLabel}.type must be "default" or "secret"`);
129687
+ }
129688
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") {
129689
+ throw new Error(`${valueLabel}.enabled must be a boolean when provided`);
129690
+ }
129691
+ const type = value.type ?? "default";
129692
+ const normalizedValue = value.value ?? "";
129693
+ if (type === "secret" && normalizedValue) {
129694
+ throw new Error(`${valueLabel} cannot contain a populated secret value; inject it at runtime`);
129695
+ }
129696
+ return { key: value.key, value: normalizedValue, type, enabled: value.enabled ?? true };
129697
+ });
129698
+ }
129699
+ if (rich && !/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(slug)) {
129700
+ throw new Error(`${label} rich slug must use only letters, numbers, dots, hyphens, or underscores`);
129701
+ }
129702
+ if (rich && Object.prototype.hasOwnProperty.call(Object.prototype, slug)) {
129703
+ throw new Error(`${label} rich slug conflicts with a reserved object property`);
129704
+ }
129705
+ if (rich && /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(slug)) {
129706
+ throw new Error(`${label} rich slug conflicts with a Windows device name`);
129707
+ }
129708
+ if (!rich && !slug.trim()) {
129709
+ throw new Error(`${label} slug must be a non-empty string`);
129710
+ }
129711
+ const priorWasRich = seen.get(slug);
129712
+ if (priorWasRich !== void 0 && (rich || priorWasRich)) {
129713
+ throw new Error(`environments-json contains duplicate slug "${slug}"`);
129714
+ }
129715
+ if (priorWasRich === void 0) seen.set(slug, rich);
129716
+ environments.push(slug);
129717
+ });
129718
+ return { environments, definitions };
129719
+ }
129720
+ function normalizeProgrammaticEnvironmentDefinitions(inputs) {
129721
+ const definitions = inputs.environmentDefinitions;
129722
+ if (!definitions) return inputs;
129723
+ const prototype = Object.getPrototypeOf(definitions);
129724
+ if (prototype !== Object.prototype && prototype !== null) {
129725
+ throw new Error("environmentDefinitions must be a plain object");
129726
+ }
129727
+ const entries = inputs.environments.map(
129728
+ (slug) => Object.prototype.hasOwnProperty.call(definitions, slug) ? { slug, values: definitions[slug] } : slug
129729
+ );
129730
+ let raw;
129731
+ try {
129732
+ raw = JSON.stringify(entries);
129733
+ } catch {
129734
+ throw new Error("environmentDefinitions must contain JSON-serializable values");
129632
129735
  }
129633
- return parsed.map((entry) => String(entry));
129736
+ const parsed = parseEnvironmentInputs(raw);
129737
+ return { ...inputs, environments: parsed.environments, environmentDefinitions: parsed.definitions };
129634
129738
  }
129635
129739
  function readInput(actionCore, name, required = false) {
129636
129740
  return actionCore.getInput(name, { required }).trim();
@@ -129727,7 +129831,7 @@ function resolveInputs(env = process.env) {
129727
129831
  },
129728
129832
  env
129729
129833
  );
129730
- const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
129834
+ const parsedEnvironments = parseEnvironmentInputs(getInput2("environments-json", env) || '["prod"]');
129731
129835
  const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
129732
129836
  const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
129733
129837
  const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
@@ -129765,7 +129869,8 @@ function resolveInputs(env = process.env) {
129765
129869
  secretsResolverProvider,
129766
129870
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
129767
129871
  releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
129768
- environments: environments.length > 0 ? environments : ["prod"],
129872
+ environments: parsedEnvironments.environments.length > 0 ? parsedEnvironments.environments : ["prod"],
129873
+ environmentDefinitions: parsedEnvironments.definitions,
129769
129874
  repoUrl: repoContext.repoUrl || "",
129770
129875
  integrationBackend: getInput2("integration-backend", env) || "bifrost",
129771
129876
  workspaceLinkEnabled: parseBooleanInput(getInput2("workspace-link-enabled", env), true),
@@ -130433,7 +130538,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130433
130538
  }
130434
130539
  } catch {
130435
130540
  }
130436
- const values2 = buildEnvironmentValues(envName, runtimeUrl, {
130541
+ const definedValues2 = inputs.environmentDefinitions?.[envName];
130542
+ const values2 = definedValues2 ? definedValues2.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130437
130543
  secretsResolverProvider: inputs.secretsResolverProvider,
130438
130544
  preservedCredentialValues: priorValues
130439
130545
  });
@@ -130456,7 +130562,8 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
130456
130562
  dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
130457
130563
  continue;
130458
130564
  }
130459
- const values = buildEnvironmentValues(envName, runtimeUrl, {
130565
+ const definedValues = inputs.environmentDefinitions?.[envName];
130566
+ const values = definedValues ? definedValues.map((value) => ({ ...value })) : buildEnvironmentValues(envName, runtimeUrl, {
130460
130567
  secretsResolverProvider: inputs.secretsResolverProvider
130461
130568
  });
130462
130569
  if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
@@ -131647,6 +131754,7 @@ async function commitAndPushGeneratedFiles(inputs, dependencies, privateMockAuth
131647
131754
  };
131648
131755
  }
131649
131756
  async function runRepoSync(inputs, dependencies, executionContext) {
131757
+ inputs = normalizeProgrammaticEnvironmentDefinitions(inputs);
131650
131758
  const telemetry = createTelemetryContext({ action: "postman-repo-sync-action", actionVersion: resolveActionVersion2(), logger: dependencies.core });
131651
131759
  telemetry.setTeamId(dependencies.teamId);
131652
131760
  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.0",
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",