@postman-cse/onboarding-repo-sync 0.13.3 → 0.14.2

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/action.cjs CHANGED
@@ -24054,12 +24054,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
24054
24054
  prod: {
24055
24055
  apiBaseUrl: "https://api.getpostman.com",
24056
24056
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
24057
- cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh"
24057
+ cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
24058
+ iapubBaseUrl: "https://iapub.postman.co"
24058
24059
  },
24059
24060
  beta: {
24060
24061
  apiBaseUrl: "https://api.getpostman-beta.com",
24061
24062
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
24062
- cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh"
24063
+ cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
24064
+ iapubBaseUrl: "https://iapub.postman.co"
24063
24065
  }
24064
24066
  };
24065
24067
  function parsePostmanStack(value) {
@@ -24511,6 +24513,301 @@ var HttpError = class _HttpError extends Error {
24511
24513
  }
24512
24514
  };
24513
24515
 
24516
+ // src/lib/postman/credential-identity.ts
24517
+ var sessionPath = "/api/sessions/current";
24518
+ var pmakMemo = /* @__PURE__ */ new Map();
24519
+ var sessionMemo = /* @__PURE__ */ new Map();
24520
+ var memoizedSessionIdentity;
24521
+ function getMemoizedSessionIdentity() {
24522
+ return memoizedSessionIdentity;
24523
+ }
24524
+ function asRecord(value) {
24525
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24526
+ return void 0;
24527
+ }
24528
+ return value;
24529
+ }
24530
+ function coerceId(raw) {
24531
+ return raw ? String(raw) : void 0;
24532
+ }
24533
+ function coerceText(raw) {
24534
+ if (typeof raw !== "string") {
24535
+ return void 0;
24536
+ }
24537
+ const trimmed = raw.trim();
24538
+ return trimmed ? trimmed : void 0;
24539
+ }
24540
+ function normalizeBaseUrl(raw) {
24541
+ return String(raw || "").replace(/\/+$/, "");
24542
+ }
24543
+ async function resolvePmakIdentity(opts) {
24544
+ const apiKey = String(opts.apiKey || "").trim();
24545
+ if (!apiKey) {
24546
+ return void 0;
24547
+ }
24548
+ const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
24549
+ const memoKey = `${baseUrl}::${apiKey}`;
24550
+ let pending = pmakMemo.get(memoKey);
24551
+ if (!pending) {
24552
+ pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
24553
+ pmakMemo.set(memoKey, pending);
24554
+ }
24555
+ return pending;
24556
+ }
24557
+ async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
24558
+ try {
24559
+ const response = await fetchImpl(`${baseUrl}/me`, {
24560
+ method: "GET",
24561
+ headers: { "X-Api-Key": apiKey }
24562
+ });
24563
+ if (!response.ok) {
24564
+ return void 0;
24565
+ }
24566
+ const payload = asRecord(await response.json());
24567
+ const user = asRecord(payload?.user);
24568
+ if (!user) {
24569
+ return void 0;
24570
+ }
24571
+ return {
24572
+ source: "pmak/me",
24573
+ userId: coerceId(user.id),
24574
+ fullName: coerceText(user.fullName) ?? coerceText(user.username),
24575
+ teamId: coerceId(user.teamId),
24576
+ teamName: coerceText(user.teamName),
24577
+ teamDomain: coerceText(user.teamDomain)
24578
+ };
24579
+ } catch {
24580
+ return void 0;
24581
+ }
24582
+ }
24583
+ async function resolveSessionIdentity(opts) {
24584
+ const accessToken = String(opts.accessToken || "").trim();
24585
+ if (!accessToken) {
24586
+ return void 0;
24587
+ }
24588
+ const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
24589
+ const memoKey = `${baseUrl}::${accessToken}`;
24590
+ let pending = sessionMemo.get(memoKey);
24591
+ if (!pending) {
24592
+ pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
24593
+ sessionMemo.set(memoKey, pending);
24594
+ }
24595
+ return pending;
24596
+ }
24597
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
24598
+ try {
24599
+ const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
24600
+ method: "GET",
24601
+ headers: { "x-access-token": accessToken }
24602
+ });
24603
+ if (!response.ok) {
24604
+ return void 0;
24605
+ }
24606
+ const payload = asRecord(await response.json());
24607
+ if (!payload) {
24608
+ return void 0;
24609
+ }
24610
+ const root = asRecord(payload.session) ?? payload;
24611
+ const identity = asRecord(root.identity);
24612
+ const data = asRecord(root.data);
24613
+ const user = asRecord(data?.user);
24614
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
24615
+ const singleRole = coerceText(user?.role);
24616
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
24617
+ const resolved = {
24618
+ source: "iapub/sessions",
24619
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
24620
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
24621
+ teamId: coerceId(identity?.team),
24622
+ teamName: coerceText(user?.teamName),
24623
+ teamDomain: coerceText(identity?.domain),
24624
+ ...roles ? { roles } : {},
24625
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
24626
+ };
24627
+ memoizedSessionIdentity = resolved;
24628
+ return resolved;
24629
+ } catch {
24630
+ return void 0;
24631
+ }
24632
+ }
24633
+ function describeTeam(id) {
24634
+ const label = id?.teamName ?? id?.teamDomain;
24635
+ return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
24636
+ }
24637
+ function formatIdentityLine(id, mask) {
24638
+ const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
24639
+ const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
24640
+ if (id.source === "pmak/me") {
24641
+ const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
24642
+ return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
24643
+ }
24644
+ return mask(
24645
+ `postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
24646
+ );
24647
+ }
24648
+ function crossCheckIdentities(args) {
24649
+ if (args.mode === "off") {
24650
+ return { ok: true, level: "ok", message: "" };
24651
+ }
24652
+ const pmakTeamId = args.pmak?.teamId;
24653
+ const sessionTeamId = args.session?.teamId;
24654
+ if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
24655
+ const level = args.mode === "enforce" ? "fail" : "note";
24656
+ const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
24657
+ const fix = level === "fail" ? "Use one credential pair from a single parent org: re-mint the access token from the same parent org as postman-api-key (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens with that team's PMAK), or set postman-api-key to the matching parent org." : "Use one credential pair from a single parent org. Set credential-preflight: enforce to fail the run on this condition.";
24658
+ return {
24659
+ ok: false,
24660
+ level,
24661
+ message: args.mask(
24662
+ `postman: ${lead} - PMAK belongs to ${describeTeam(args.pmak)} but the access token's session belongs to a different parent org, ${describeTeam(args.session)}. Assets would be created against one team while Bifrost linking and governance act under the other, producing duplicate-link 400s and workspaces not visible to the other credential. ` + fix
24663
+ )
24664
+ };
24665
+ }
24666
+ if (pmakTeamId && sessionTeamId) {
24667
+ const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
24668
+ const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
24669
+ return {
24670
+ ok: true,
24671
+ level: "ok",
24672
+ message: args.mask(
24673
+ `postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
24674
+ )
24675
+ };
24676
+ }
24677
+ const missing = [
24678
+ !pmakTeamId ? "PMAK identity" : void 0,
24679
+ !sessionTeamId ? "access-token session identity" : void 0
24680
+ ].filter(Boolean).join(" and ");
24681
+ return {
24682
+ ok: false,
24683
+ level: "note",
24684
+ message: args.mask(
24685
+ `postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
24686
+ )
24687
+ };
24688
+ }
24689
+ async function runCredentialPreflight(args) {
24690
+ if (args.mode === "off") {
24691
+ return;
24692
+ }
24693
+ const mask = args.mask;
24694
+ const apiKey = String(args.postmanApiKey || "").trim();
24695
+ const accessToken = String(args.postmanAccessToken || "").trim();
24696
+ let pmak;
24697
+ if (apiKey) {
24698
+ try {
24699
+ pmak = await resolvePmakIdentity({
24700
+ apiBaseUrl: args.apiBaseUrl,
24701
+ apiKey,
24702
+ fetchImpl: args.fetchImpl
24703
+ });
24704
+ } catch (error2) {
24705
+ args.log.warning(
24706
+ mask(
24707
+ `postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
24708
+ )
24709
+ );
24710
+ }
24711
+ if (pmak) {
24712
+ args.log.info(formatIdentityLine(pmak, mask));
24713
+ } else {
24714
+ args.log.warning(
24715
+ mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
24716
+ );
24717
+ }
24718
+ }
24719
+ if (!accessToken) {
24720
+ args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
24721
+ return;
24722
+ }
24723
+ let session;
24724
+ try {
24725
+ session = await resolveSessionIdentity({
24726
+ iapubBaseUrl: args.iapubBaseUrl,
24727
+ accessToken,
24728
+ fetchImpl: args.fetchImpl
24729
+ });
24730
+ } catch (error2) {
24731
+ args.log.warning(
24732
+ mask(
24733
+ `postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
24734
+ )
24735
+ );
24736
+ }
24737
+ if (session) {
24738
+ args.log.info(formatIdentityLine(session, mask));
24739
+ } else {
24740
+ args.log.warning(
24741
+ mask(
24742
+ "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
24743
+ )
24744
+ );
24745
+ }
24746
+ const result = crossCheckIdentities({
24747
+ pmak,
24748
+ session,
24749
+ workspaceTeamId: args.workspaceTeamId,
24750
+ explicitTeamId: args.explicitTeamId,
24751
+ mode: args.mode,
24752
+ mask
24753
+ });
24754
+ if (!result.message) {
24755
+ return;
24756
+ }
24757
+ if (result.level === "fail") {
24758
+ throw new Error(result.message);
24759
+ }
24760
+ if (result.level === "note") {
24761
+ args.log.warning(result.message);
24762
+ return;
24763
+ }
24764
+ args.log.info(result.message);
24765
+ }
24766
+
24767
+ // src/lib/postman/error-advice.ts
24768
+ var WORKSPACE_PERSONAL_ONLY_ADVICE = "Workspace creation failed: This may be an Org-mode account that requires a workspace-team-id input. The Postman API does not allow creating team workspaces at the organization level. Use the workspace-team-id input to specify which sub-team should own this workspace.";
24769
+ function expiryAdvice(code) {
24770
+ return `postman: Bifrost rejected the access token (${code}). Service-account access tokens expire after about 1 to 1.5 hours; this run likely outlived its token. Re-mint a fresh token (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens) and re-run. If it was just minted, confirm postman-access-token is the token for the same parent org as postman-api-key.`;
24771
+ }
24772
+ function forbiddenAdvice(ctx) {
24773
+ const sessionDetail = ctx.sessionTeamId ? ` while the access token is valid (it resolved to team ${ctx.sessionTeamId}${ctx.sessionRoles && ctx.sessionRoles.length > 0 ? `, roles [${ctx.sessionRoles.join(", ")}]` : ""}${ctx.sessionConsumerType ? `, consumerType ${ctx.sessionConsumerType}` : ""} at preflight)` : "";
24774
+ const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
24775
+ const teamClause = scopedTeamId ? `, or workspace-team-id ${scopedTeamId} names a sub-team it cannot act in` : ", or the workspace-team-id / POSTMAN_TEAM_ID in use names a sub-team it cannot act in";
24776
+ return `postman: Bifrost refused ${ctx.operation || "this operation"} with 403${sessionDetail}. The token's identity lacks permission for this endpoint${teamClause}. Verify the token's role and that workspace-team-id / POSTMAN_TEAM_ID matches a sub-team from GET https://api.getpostman.com/teams.`;
24777
+ }
24778
+ function buildAdvice(status, body, ctx) {
24779
+ if (body.includes("UNAUTHENTICATED")) {
24780
+ return expiryAdvice("UNAUTHENTICATED");
24781
+ }
24782
+ if (body.includes("authenticationError")) {
24783
+ return expiryAdvice("authenticationError");
24784
+ }
24785
+ if (body.includes("Only personal workspaces")) {
24786
+ return WORKSPACE_PERSONAL_ONLY_ADVICE;
24787
+ }
24788
+ if (body.includes("projectAlreadyConnected")) {
24789
+ return `postman: ${ctx.operation || "this operation"} reports projectAlreadyConnected with no workspace id in the error body. The repository is already linked to a workspace this credential cannot see, usually one created by a different credential pair or sub-team. Delete the stale link or its workspace, then re-run with one credential pair from a single parent org.`;
24790
+ }
24791
+ if (body.includes("invalidParamError") && body.includes("already exists")) {
24792
+ return `postman: ${ctx.operation || "this operation"} hit a duplicate resource error (invalidParamError: already exists). A matching resource already exists, possibly under another credential pair or sub-team where this credential cannot see it. Identify which workspace holds the existing resource and re-run with one credential pair from a single parent org.`;
24793
+ }
24794
+ if (body.includes("Team feature is not available for your organization")) {
24795
+ return `postman: ${ctx.operation || "this operation"} failed because the team feature is not available for this organization. The credential belongs to an account whose plan lacks team features; use credentials from the intended team and confirm the plan supports this operation.`;
24796
+ }
24797
+ if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
24798
+ return forbiddenAdvice(ctx);
24799
+ }
24800
+ return void 0;
24801
+ }
24802
+ function adviseFromHttpError(err, ctx) {
24803
+ const body = err.responseBody || err.message || "";
24804
+ const advice = buildAdvice(err.status, body, ctx);
24805
+ if (!advice) {
24806
+ return void 0;
24807
+ }
24808
+ return new Error(ctx.mask(advice), { cause: err });
24809
+ }
24810
+
24514
24811
  // src/lib/postman/internal-integration-adapter.ts
24515
24812
  function extractDuplicateWorkspaceId(body) {
24516
24813
  try {
@@ -24526,6 +24823,7 @@ var BifrostInternalIntegrationAdapter = class {
24526
24823
  bifrostBaseUrl;
24527
24824
  fetchImpl;
24528
24825
  orgMode;
24826
+ secretMasker;
24529
24827
  teamId;
24530
24828
  workerBaseUrl;
24531
24829
  constructor(options) {
@@ -24539,7 +24837,7 @@ var BifrostInternalIntegrationAdapter = class {
24539
24837
  this.workerBaseUrl = String(
24540
24838
  options.workerBaseUrl || "https://catalog-admin.postman-account2009.workers.dev"
24541
24839
  ).replace(/\/+$/, "");
24542
- void (options.secretMasker ?? createSecretMasker([this.accessToken]));
24840
+ this.secretMasker = options.secretMasker ?? createSecretMasker([this.accessToken]);
24543
24841
  }
24544
24842
  /** Build Bifrost proxy headers. Only includes x-entity-team-id for org-mode teams. */
24545
24843
  bifrostHeaders() {
@@ -24552,6 +24850,19 @@ var BifrostInternalIntegrationAdapter = class {
24552
24850
  }
24553
24851
  return headers;
24554
24852
  }
24853
+ /** Reactive error-advice context, enriched with the preflight session memo when present. */
24854
+ adviceContext(operation) {
24855
+ const session = getMemoizedSessionIdentity();
24856
+ return {
24857
+ operation,
24858
+ hasAccessToken: Boolean(this.accessToken),
24859
+ sessionTeamId: session?.teamId,
24860
+ sessionRoles: session?.roles,
24861
+ sessionConsumerType: session?.consumerType,
24862
+ explicitTeamId: this.teamId || void 0,
24863
+ mask: this.secretMasker
24864
+ };
24865
+ }
24555
24866
  async associateSystemEnvironments(workspaceId, associations) {
24556
24867
  if (associations.length === 0) {
24557
24868
  return;
@@ -24574,7 +24885,7 @@ var BifrostInternalIntegrationAdapter = class {
24574
24885
  }
24575
24886
  );
24576
24887
  if (!response.ok) {
24577
- throw await HttpError.fromResponse(response, {
24888
+ const httpErr = await HttpError.fromResponse(response, {
24578
24889
  method: "POST",
24579
24890
  requestHeaders: {
24580
24891
  Authorization: `Bearer ${this.accessToken}`,
@@ -24583,6 +24894,11 @@ var BifrostInternalIntegrationAdapter = class {
24583
24894
  secretValues: [this.accessToken],
24584
24895
  url: `${this.workerBaseUrl}/api/internal/system-envs/associate`
24585
24896
  });
24897
+ const advised = adviseFromHttpError(
24898
+ httpErr,
24899
+ this.adviceContext("system environment association")
24900
+ );
24901
+ throw advised ?? httpErr;
24586
24902
  }
24587
24903
  }
24588
24904
  async connectWorkspaceToRepository(workspaceId, repoUrl) {
@@ -24604,8 +24920,10 @@ var BifrostInternalIntegrationAdapter = class {
24604
24920
  body: JSON.stringify(payload)
24605
24921
  });
24606
24922
  if (!response.ok) {
24923
+ let consumedBody;
24607
24924
  if (response.status === 400) {
24608
24925
  const body = await response.text();
24926
+ consumedBody = body;
24609
24927
  const isDuplicate = body.includes("invalidParamError") && body.includes("already exists") || body.includes("projectAlreadyConnected");
24610
24928
  if (isDuplicate) {
24611
24929
  const existingWorkspaceId = extractDuplicateWorkspaceId(body);
@@ -24617,12 +24935,18 @@ var BifrostInternalIntegrationAdapter = class {
24617
24935
  return;
24618
24936
  }
24619
24937
  }
24620
- throw await HttpError.fromResponse(response, {
24938
+ const httpErr = await HttpError.fromResponse(response, {
24621
24939
  method: "POST",
24622
24940
  requestHeaders: headers,
24623
24941
  secretValues: [this.accessToken],
24624
- url
24942
+ url,
24943
+ ...consumedBody !== void 0 ? { responseBody: consumedBody } : {}
24625
24944
  });
24945
+ const advised = adviseFromHttpError(
24946
+ httpErr,
24947
+ this.adviceContext("workspace repository linking")
24948
+ );
24949
+ throw advised ?? httpErr;
24626
24950
  }
24627
24951
  }
24628
24952
  /**
@@ -24683,12 +25007,14 @@ var BifrostInternalIntegrationAdapter = class {
24683
25007
  body: JSON.stringify(payload)
24684
25008
  });
24685
25009
  if (!response.ok) {
24686
- throw await HttpError.fromResponse(response, {
25010
+ const httpErr = await HttpError.fromResponse(response, {
24687
25011
  method: "POST",
24688
25012
  requestHeaders: headers,
24689
25013
  secretValues: [this.accessToken],
24690
25014
  url
24691
25015
  });
25016
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("API key generation"));
25017
+ throw advised ?? httpErr;
24692
25018
  }
24693
25019
  const data = await response.json();
24694
25020
  if (!data?.apikey?.key) {
@@ -24707,6 +25033,255 @@ function createInternalIntegrationAdapter(options) {
24707
25033
  return new BifrostInternalIntegrationAdapter(options);
24708
25034
  }
24709
25035
 
25036
+ // src/contracts.ts
25037
+ var postmanRepoSyncActionContract = {
25038
+ name: "postman-repo-sync-action",
25039
+ description: "Public customer preview contract for syncing exported Postman assets into a repository and keeping workspace-link concerns separate from provisioning.",
25040
+ defaults: {
25041
+ integrationBackend: "bifrost",
25042
+ artifactDir: "postman",
25043
+ repoWriteMode: "commit-and-push",
25044
+ collectionSyncMode: "refresh",
25045
+ specSyncMode: "update",
25046
+ workspaceLinkEnabled: true,
25047
+ environmentSyncEnabled: true,
25048
+ committerName: "Postman CSE",
25049
+ committerEmail: "help@postman.com"
25050
+ },
25051
+ inputs: {
25052
+ "generate-ci-workflow": {
25053
+ description: "Whether to generate the CI workflow file",
25054
+ required: false,
25055
+ default: "true"
25056
+ },
25057
+ "ci-workflow-path": {
25058
+ description: "Path to write the generated CI workflow file",
25059
+ required: false,
25060
+ default: ".github/workflows/ci.yml"
25061
+ },
25062
+ "project-name": {
25063
+ description: "Service project name used for environment, mock, and monitor naming.",
25064
+ required: true
25065
+ },
25066
+ "workspace-id": {
25067
+ description: "Postman workspace ID used for workspace-link and export metadata.",
25068
+ required: false
25069
+ },
25070
+ "baseline-collection-id": {
25071
+ description: "Baseline collection ID used for exported artifacts and mock server creation.",
25072
+ required: false
25073
+ },
25074
+ "monitor-type": {
25075
+ description: 'Type of monitor to create ("cloud" or "cli"). "cli" will skip cloud monitor creation and rely on the CI workflow.',
25076
+ required: false,
25077
+ default: "cloud"
25078
+ },
25079
+ "smoke-collection-id": {
25080
+ description: "Smoke collection ID used for monitor creation.",
25081
+ required: false
25082
+ },
25083
+ "contract-collection-id": {
25084
+ description: "Contract collection ID used for exported artifacts.",
25085
+ required: false
25086
+ },
25087
+ "collection-sync-mode": {
25088
+ description: "Collection sync lifecycle mode (refresh or version).",
25089
+ required: false,
25090
+ default: "refresh"
25091
+ },
25092
+ "spec-sync-mode": {
25093
+ description: "Spec sync lifecycle mode (update or version).",
25094
+ required: false,
25095
+ default: "update"
25096
+ },
25097
+ "release-label": {
25098
+ description: "Optional release label used for versioned naming.",
25099
+ required: false
25100
+ },
25101
+ "monitor-id": {
25102
+ description: "Existing smoke monitor ID. When set, the action validates and reuses this monitor instead of creating a new one.",
25103
+ required: false
25104
+ },
25105
+ "mock-url": {
25106
+ description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
25107
+ required: false
25108
+ },
25109
+ "monitor-cron": {
25110
+ description: "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).",
25111
+ required: false,
25112
+ default: ""
25113
+ },
25114
+ "environments-json": {
25115
+ description: "JSON array of environment slugs to create or update.",
25116
+ required: false,
25117
+ default: '["prod"]'
25118
+ },
25119
+ "repo-url": {
25120
+ description: "Explicit repository URL. Defaults to the workflow repository URL.",
25121
+ required: false
25122
+ },
25123
+ "integration-backend": {
25124
+ description: "Integration backend for workspace linking and environment sync.",
25125
+ required: false,
25126
+ default: "bifrost"
25127
+ },
25128
+ "workspace-link-enabled": {
25129
+ description: "Enable workspace linking.",
25130
+ required: false,
25131
+ default: "true"
25132
+ },
25133
+ "environment-sync-enabled": {
25134
+ description: "Enable system environment association.",
25135
+ required: false,
25136
+ default: "true"
25137
+ },
25138
+ "system-env-map-json": {
25139
+ description: "JSON map of environment slug to system environment id.",
25140
+ required: false,
25141
+ default: "{}"
25142
+ },
25143
+ "environment-uids-json": {
25144
+ description: "JSON map of environment slug to Postman environment uid.",
25145
+ required: false,
25146
+ default: "{}"
25147
+ },
25148
+ "env-runtime-urls-json": {
25149
+ description: "JSON map of environment slug to runtime base URL.",
25150
+ required: false,
25151
+ default: "{}"
25152
+ },
25153
+ "artifact-dir": {
25154
+ description: "Root directory for exported Postman artifacts.",
25155
+ required: false,
25156
+ default: "postman"
25157
+ },
25158
+ "repo-write-mode": {
25159
+ description: "Repo mutation mode for generated artifacts and workflow files.",
25160
+ required: false,
25161
+ default: "commit-and-push"
25162
+ },
25163
+ "current-ref": {
25164
+ description: "Explicit ref override for push-changes when checkout is detached.",
25165
+ required: false
25166
+ },
25167
+ "committer-name": {
25168
+ description: "Git committer name for sync commits.",
25169
+ required: false,
25170
+ default: "Postman CSE"
25171
+ },
25172
+ "committer-email": {
25173
+ description: "Git committer email for sync commits.",
25174
+ required: false,
25175
+ default: "help@postman.com"
25176
+ },
25177
+ "postman-api-key": {
25178
+ description: "Postman API key used for environment, mock, and monitor operations.",
25179
+ required: false
25180
+ },
25181
+ "postman-access-token": {
25182
+ description: "Postman access token used for Bifrost and system environment association.",
25183
+ required: false
25184
+ },
25185
+ "credential-preflight": {
25186
+ description: "Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created; off skips the identity probes entirely (the reactive error guidance still applies). Promotion of the default to enforce is planned once the live e2e legs prove both directions.",
25187
+ required: false,
25188
+ default: "warn",
25189
+ allowedValues: ["enforce", "warn", "off"]
25190
+ },
25191
+ "github-token": {
25192
+ description: "GitHub token used for repo variable persistence and commits.",
25193
+ required: false
25194
+ },
25195
+ "gh-fallback-token": {
25196
+ description: "Fallback token for repository variable APIs and workflow-file pushes.",
25197
+ required: false
25198
+ },
25199
+ "org-mode": {
25200
+ description: "Whether the Postman team uses org-mode. When true, x-entity-team-id header is included in Bifrost proxy calls. Non-org teams must omit this header.",
25201
+ required: false,
25202
+ default: "false"
25203
+ },
25204
+ "ci-workflow-base64": {
25205
+ description: "Optional base64-encoded ci.yml content. Defaults to the built-in template.",
25206
+ required: false
25207
+ },
25208
+ "ssl-client-cert": {
25209
+ description: "Base64-encoded PEM client certificate for Postman CLI mTLS runs.",
25210
+ required: false
25211
+ },
25212
+ "ssl-client-key": {
25213
+ description: "Base64-encoded PEM client private key for Postman CLI mTLS runs.",
25214
+ required: false
25215
+ },
25216
+ "ssl-client-passphrase": {
25217
+ description: "Optional passphrase for encrypted ssl-client-key.",
25218
+ required: false
25219
+ },
25220
+ "ssl-extra-ca-certs": {
25221
+ description: "Optional base64-encoded PEM CA certificate bundle for custom trust.",
25222
+ required: false
25223
+ },
25224
+ "spec-id": {
25225
+ description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
25226
+ required: false
25227
+ },
25228
+ "spec-path": {
25229
+ description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
25230
+ required: false
25231
+ },
25232
+ "postman-stack": {
25233
+ description: "Postman stack profile.",
25234
+ required: false,
25235
+ default: "prod",
25236
+ allowedValues: ["prod", "beta"]
25237
+ }
25238
+ },
25239
+ outputs: {
25240
+ "integration-backend": {
25241
+ description: "Resolved integration backend for the customer preview run."
25242
+ },
25243
+ "resolved-current-ref": {
25244
+ description: "Resolved push target based on current-ref semantics."
25245
+ },
25246
+ "workspace-link-status": {
25247
+ description: "Whether workspace linking succeeded, was skipped, or failed."
25248
+ },
25249
+ "environment-sync-status": {
25250
+ description: "Whether environment sync succeeded, was skipped, or failed."
25251
+ },
25252
+ "environment-uids-json": {
25253
+ description: "JSON map of environment slug to Postman environment uid."
25254
+ },
25255
+ "mock-url": {
25256
+ description: "Created or reused mock server URL."
25257
+ },
25258
+ "monitor-id": {
25259
+ description: "Created or reused smoke monitor ID."
25260
+ },
25261
+ "repo-sync-summary-json": {
25262
+ description: "JSON summary of repo materialization and workspace sync outputs."
25263
+ },
25264
+ "commit-sha": {
25265
+ description: "Commit SHA produced by repo-write-mode, if any."
25266
+ }
25267
+ },
25268
+ behavior: {
25269
+ retainedFromFinalize: [
25270
+ "Create or update Postman environments from runtime URLs.",
25271
+ "Associate Postman environments to system environments through Bifrost.",
25272
+ "Create mock servers and smoke monitors from generated collections.",
25273
+ "Export Postman collections in the Collection v3 multi-file YAML directory structure under `postman/collections/` (e.g., `[Baseline] <name>/collection.yaml`, nested folder and request YAML files), and export environments plus `.postman/resources.yaml` into the repository.",
25274
+ "Link the Postman workspace to the repository (GitHub or GitLab) through Bifrost.",
25275
+ "Commit synced artifacts and push them back to the current checked out ref."
25276
+ ],
25277
+ removedFromFinalize: [
25278
+ "Generate Fern docs or write documentation URLs back to GitHub.",
25279
+ "Store AWS deployment orchestration concerns in the public action interface.",
25280
+ "Push directly to `main`."
25281
+ ]
25282
+ }
25283
+ };
25284
+
24710
25285
  // src/lib/postman/postman-assets-client.ts
24711
25286
  var PostmanAssetsClient = class {
24712
25287
  apiKey;
@@ -25038,6 +25613,17 @@ function normalizeSpecSyncMode(value) {
25038
25613
  }
25039
25614
  return "update";
25040
25615
  }
25616
+ function parseCredentialPreflight(value) {
25617
+ const definition = postmanRepoSyncActionContract.inputs["credential-preflight"];
25618
+ const allowed = definition.allowedValues ?? [];
25619
+ const normalized = String(value || "").trim() || (definition.default ?? "warn");
25620
+ if (allowed.includes(normalized)) {
25621
+ return normalized;
25622
+ }
25623
+ throw new Error(
25624
+ `Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
25625
+ );
25626
+ }
25041
25627
  function normalizeReleaseLabel(value) {
25042
25628
  const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
25043
25629
  return cleaned;
@@ -25096,6 +25682,7 @@ function resolveInputs(env = process.env) {
25096
25682
  committerEmail: getInput2("committer-email", env) || "help@postman.com",
25097
25683
  postmanApiKey: getInput2("postman-api-key", env),
25098
25684
  postmanAccessToken: getInput2("postman-access-token", env),
25685
+ credentialPreflight: parseCredentialPreflight(getInput2("credential-preflight", env)),
25099
25686
  githubToken: getInput2("github-token", env),
25100
25687
  ghFallbackToken: getInput2("gh-fallback-token", env),
25101
25688
  ciWorkflowBase64: getInput2("ci-workflow-base64", env),
@@ -25115,7 +25702,8 @@ function resolveInputs(env = process.env) {
25115
25702
  postmanStack,
25116
25703
  postmanApiBase: endpointProfile.apiBaseUrl,
25117
25704
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
25118
- postmanCliInstallUrl: endpointProfile.cliInstallUrl
25705
+ postmanCliInstallUrl: endpointProfile.cliInstallUrl,
25706
+ postmanIapubBase: endpointProfile.iapubBaseUrl
25119
25707
  };
25120
25708
  }
25121
25709
  function buildEnvironmentValues(envName, baseUrl) {
@@ -25305,6 +25893,7 @@ function readActionInputs(actionCore) {
25305
25893
  INPUT_COMMITTER_EMAIL: readInput(actionCore, "committer-email") || "help@postman.com",
25306
25894
  INPUT_POSTMAN_API_KEY: postmanApiKey,
25307
25895
  INPUT_POSTMAN_ACCESS_TOKEN: postmanAccessToken,
25896
+ INPUT_CREDENTIAL_PREFLIGHT: readInput(actionCore, "credential-preflight") || "warn",
25308
25897
  INPUT_GITHUB_TOKEN: githubToken,
25309
25898
  INPUT_GH_FALLBACK_TOKEN: ghFallbackToken,
25310
25899
  INPUT_CI_WORKFLOW_BASE64: readInput(actionCore, "ci-workflow-base64"),
@@ -25986,6 +26575,16 @@ async function runAction(actionCore = core_exports, actionExec = exec_exports) {
25986
26575
  inputs.sslClientPassphrase,
25987
26576
  inputs.sslExtraCaCerts
25988
26577
  ]);
26578
+ await runCredentialPreflight({
26579
+ apiBaseUrl: inputs.postmanApiBase,
26580
+ iapubBaseUrl: inputs.postmanIapubBase,
26581
+ postmanApiKey: inputs.postmanApiKey,
26582
+ postmanAccessToken: inputs.postmanAccessToken,
26583
+ explicitTeamId: inputs.teamId || void 0,
26584
+ mode: inputs.credentialPreflight,
26585
+ mask: masker,
26586
+ log: actionCore
26587
+ });
25989
26588
  const resolved = await resolvePostmanApiKeyAndTeamId(inputs, actionCore, actionExec, masker, {
25990
26589
  env: process.env
25991
26590
  });