@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/action.yml +4 -0
- package/dist/action.cjs +607 -8
- package/dist/cli.cjs +607 -8
- package/dist/index.cjs +607 -8
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|