@postman-cse/onboarding-insights 0.9.1 → 0.10.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/action.yml +4 -0
- package/dist/action.cjs +396 -17
- package/dist/cli.cjs +401 -17
- package/dist/index.cjs +402 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -18676,12 +18676,15 @@ var index_exports = {};
|
|
|
18676
18676
|
__export(index_exports, {
|
|
18677
18677
|
DEFAULT_POSTMAN_API_BASE: () => DEFAULT_POSTMAN_API_BASE,
|
|
18678
18678
|
DEFAULT_POSTMAN_BIFROST_BASE: () => DEFAULT_POSTMAN_BIFROST_BASE,
|
|
18679
|
+
DEFAULT_POSTMAN_IAPUB_BASE: () => DEFAULT_POSTMAN_IAPUB_BASE,
|
|
18679
18680
|
DEFAULT_POSTMAN_OBSERVABILITY_BASE: () => DEFAULT_POSTMAN_OBSERVABILITY_BASE,
|
|
18680
18681
|
createPlannedOutputs: () => createPlannedOutputs,
|
|
18681
18682
|
getTeams: () => getTeams,
|
|
18683
|
+
parsePreflightMode: () => parsePreflightMode,
|
|
18682
18684
|
resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
|
|
18683
18685
|
resolveInputs: () => resolveInputs,
|
|
18684
18686
|
runAction: () => runAction,
|
|
18687
|
+
runCredentialPreflightForInputs: () => runCredentialPreflightForInputs,
|
|
18685
18688
|
runOnboarding: () => runOnboarding,
|
|
18686
18689
|
validateApiKey: () => validateApiKey
|
|
18687
18690
|
});
|
|
@@ -20960,6 +20963,310 @@ function getIDToken(aud) {
|
|
|
20960
20963
|
});
|
|
20961
20964
|
}
|
|
20962
20965
|
|
|
20966
|
+
// src/lib/credential-identity.ts
|
|
20967
|
+
var sessionPath = "/api/sessions/current";
|
|
20968
|
+
var pmakMemo = /* @__PURE__ */ new Map();
|
|
20969
|
+
var sessionMemo = /* @__PURE__ */ new Map();
|
|
20970
|
+
var memoizedSessionIdentity;
|
|
20971
|
+
function getMemoizedSessionIdentity() {
|
|
20972
|
+
return memoizedSessionIdentity;
|
|
20973
|
+
}
|
|
20974
|
+
function asRecord(value) {
|
|
20975
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20976
|
+
return void 0;
|
|
20977
|
+
}
|
|
20978
|
+
return value;
|
|
20979
|
+
}
|
|
20980
|
+
function coerceId(raw) {
|
|
20981
|
+
return raw ? String(raw) : void 0;
|
|
20982
|
+
}
|
|
20983
|
+
function coerceText(raw) {
|
|
20984
|
+
if (typeof raw !== "string") {
|
|
20985
|
+
return void 0;
|
|
20986
|
+
}
|
|
20987
|
+
const trimmed = raw.trim();
|
|
20988
|
+
return trimmed ? trimmed : void 0;
|
|
20989
|
+
}
|
|
20990
|
+
function normalizeBaseUrl(raw) {
|
|
20991
|
+
return String(raw || "").replace(/\/+$/, "");
|
|
20992
|
+
}
|
|
20993
|
+
async function resolvePmakIdentity(opts) {
|
|
20994
|
+
const apiKey = String(opts.apiKey || "").trim();
|
|
20995
|
+
if (!apiKey) {
|
|
20996
|
+
return void 0;
|
|
20997
|
+
}
|
|
20998
|
+
const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
|
|
20999
|
+
const memoKey = `${baseUrl}::${apiKey}`;
|
|
21000
|
+
let pending = pmakMemo.get(memoKey);
|
|
21001
|
+
if (!pending) {
|
|
21002
|
+
pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
|
|
21003
|
+
pmakMemo.set(memoKey, pending);
|
|
21004
|
+
}
|
|
21005
|
+
return pending;
|
|
21006
|
+
}
|
|
21007
|
+
async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
|
|
21008
|
+
try {
|
|
21009
|
+
const response = await fetchImpl(`${baseUrl}/me`, {
|
|
21010
|
+
method: "GET",
|
|
21011
|
+
headers: { "X-Api-Key": apiKey }
|
|
21012
|
+
});
|
|
21013
|
+
if (!response.ok) {
|
|
21014
|
+
return void 0;
|
|
21015
|
+
}
|
|
21016
|
+
const payload = asRecord(await response.json());
|
|
21017
|
+
const user = asRecord(payload?.user);
|
|
21018
|
+
if (!user) {
|
|
21019
|
+
return void 0;
|
|
21020
|
+
}
|
|
21021
|
+
return {
|
|
21022
|
+
source: "pmak/me",
|
|
21023
|
+
userId: coerceId(user.id),
|
|
21024
|
+
fullName: coerceText(user.fullName) ?? coerceText(user.username),
|
|
21025
|
+
teamId: coerceId(user.teamId),
|
|
21026
|
+
teamName: coerceText(user.teamName),
|
|
21027
|
+
teamDomain: coerceText(user.teamDomain)
|
|
21028
|
+
};
|
|
21029
|
+
} catch {
|
|
21030
|
+
return void 0;
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
async function resolveSessionIdentity(opts) {
|
|
21034
|
+
const accessToken = String(opts.accessToken || "").trim();
|
|
21035
|
+
if (!accessToken) {
|
|
21036
|
+
return void 0;
|
|
21037
|
+
}
|
|
21038
|
+
const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
|
|
21039
|
+
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21040
|
+
let pending = sessionMemo.get(memoKey);
|
|
21041
|
+
if (!pending) {
|
|
21042
|
+
pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
|
|
21043
|
+
sessionMemo.set(memoKey, pending);
|
|
21044
|
+
}
|
|
21045
|
+
return pending;
|
|
21046
|
+
}
|
|
21047
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
|
|
21048
|
+
try {
|
|
21049
|
+
const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21050
|
+
method: "GET",
|
|
21051
|
+
headers: { "x-access-token": accessToken }
|
|
21052
|
+
});
|
|
21053
|
+
if (!response.ok) {
|
|
21054
|
+
return void 0;
|
|
21055
|
+
}
|
|
21056
|
+
const payload = asRecord(await response.json());
|
|
21057
|
+
if (!payload) {
|
|
21058
|
+
return void 0;
|
|
21059
|
+
}
|
|
21060
|
+
const identity = asRecord(payload.identity);
|
|
21061
|
+
const data = asRecord(payload.data);
|
|
21062
|
+
const user = asRecord(data?.user);
|
|
21063
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21064
|
+
const singleRole = coerceText(user?.role);
|
|
21065
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21066
|
+
const resolved = {
|
|
21067
|
+
source: "iapub/sessions",
|
|
21068
|
+
userId: coerceId(user?.id),
|
|
21069
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21070
|
+
teamId: coerceId(identity?.team),
|
|
21071
|
+
teamDomain: coerceText(identity?.domain),
|
|
21072
|
+
...roles ? { roles } : {},
|
|
21073
|
+
consumerType: coerceText(payload.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21074
|
+
};
|
|
21075
|
+
memoizedSessionIdentity = resolved;
|
|
21076
|
+
return resolved;
|
|
21077
|
+
} catch {
|
|
21078
|
+
return void 0;
|
|
21079
|
+
}
|
|
21080
|
+
}
|
|
21081
|
+
function describeTeam(id) {
|
|
21082
|
+
const label = id?.teamName ?? id?.teamDomain;
|
|
21083
|
+
return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
|
|
21084
|
+
}
|
|
21085
|
+
function formatIdentityLine(id, mask) {
|
|
21086
|
+
const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
|
|
21087
|
+
const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
|
|
21088
|
+
if (id.source === "pmak/me") {
|
|
21089
|
+
const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
|
|
21090
|
+
return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
|
|
21091
|
+
}
|
|
21092
|
+
return mask(
|
|
21093
|
+
`postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
|
|
21094
|
+
);
|
|
21095
|
+
}
|
|
21096
|
+
function crossCheckIdentities(args) {
|
|
21097
|
+
if (args.mode === "off") {
|
|
21098
|
+
return { ok: true, level: "ok", message: "" };
|
|
21099
|
+
}
|
|
21100
|
+
const pmakTeamId = args.pmak?.teamId;
|
|
21101
|
+
const sessionTeamId = args.session?.teamId;
|
|
21102
|
+
if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
|
|
21103
|
+
const level = args.mode === "enforce" ? "fail" : "note";
|
|
21104
|
+
const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
|
|
21105
|
+
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.";
|
|
21106
|
+
return {
|
|
21107
|
+
ok: false,
|
|
21108
|
+
level,
|
|
21109
|
+
message: args.mask(
|
|
21110
|
+
`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
|
|
21111
|
+
)
|
|
21112
|
+
};
|
|
21113
|
+
}
|
|
21114
|
+
if (pmakTeamId && sessionTeamId) {
|
|
21115
|
+
const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
|
|
21116
|
+
const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
|
|
21117
|
+
return {
|
|
21118
|
+
ok: true,
|
|
21119
|
+
level: "ok",
|
|
21120
|
+
message: args.mask(
|
|
21121
|
+
`postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
|
|
21122
|
+
)
|
|
21123
|
+
};
|
|
21124
|
+
}
|
|
21125
|
+
const missing = [
|
|
21126
|
+
!pmakTeamId ? "PMAK identity" : void 0,
|
|
21127
|
+
!sessionTeamId ? "access-token session identity" : void 0
|
|
21128
|
+
].filter(Boolean).join(" and ");
|
|
21129
|
+
return {
|
|
21130
|
+
ok: false,
|
|
21131
|
+
level: "note",
|
|
21132
|
+
message: args.mask(
|
|
21133
|
+
`postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
|
|
21134
|
+
)
|
|
21135
|
+
};
|
|
21136
|
+
}
|
|
21137
|
+
async function runCredentialPreflight(args) {
|
|
21138
|
+
if (args.mode === "off") {
|
|
21139
|
+
return;
|
|
21140
|
+
}
|
|
21141
|
+
const mask = args.mask;
|
|
21142
|
+
const apiKey = String(args.postmanApiKey || "").trim();
|
|
21143
|
+
const accessToken = String(args.postmanAccessToken || "").trim();
|
|
21144
|
+
let pmak = args.pmak;
|
|
21145
|
+
if (pmak) {
|
|
21146
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21147
|
+
} else if (apiKey) {
|
|
21148
|
+
try {
|
|
21149
|
+
pmak = await resolvePmakIdentity({
|
|
21150
|
+
apiBaseUrl: args.apiBaseUrl,
|
|
21151
|
+
apiKey,
|
|
21152
|
+
fetchImpl: args.fetchImpl
|
|
21153
|
+
});
|
|
21154
|
+
} catch (error2) {
|
|
21155
|
+
args.log.warning(
|
|
21156
|
+
mask(
|
|
21157
|
+
`postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21158
|
+
)
|
|
21159
|
+
);
|
|
21160
|
+
}
|
|
21161
|
+
if (pmak) {
|
|
21162
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21163
|
+
} else {
|
|
21164
|
+
args.log.warning(
|
|
21165
|
+
mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
|
|
21166
|
+
);
|
|
21167
|
+
}
|
|
21168
|
+
}
|
|
21169
|
+
if (!accessToken) {
|
|
21170
|
+
args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
|
|
21171
|
+
return;
|
|
21172
|
+
}
|
|
21173
|
+
let session;
|
|
21174
|
+
try {
|
|
21175
|
+
session = await resolveSessionIdentity({
|
|
21176
|
+
iapubBaseUrl: args.iapubBaseUrl,
|
|
21177
|
+
accessToken,
|
|
21178
|
+
fetchImpl: args.fetchImpl
|
|
21179
|
+
});
|
|
21180
|
+
} catch (error2) {
|
|
21181
|
+
args.log.warning(
|
|
21182
|
+
mask(
|
|
21183
|
+
`postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21184
|
+
)
|
|
21185
|
+
);
|
|
21186
|
+
}
|
|
21187
|
+
if (session) {
|
|
21188
|
+
args.log.info(formatIdentityLine(session, mask));
|
|
21189
|
+
} else {
|
|
21190
|
+
args.log.warning(
|
|
21191
|
+
mask(
|
|
21192
|
+
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21193
|
+
)
|
|
21194
|
+
);
|
|
21195
|
+
}
|
|
21196
|
+
const result = crossCheckIdentities({
|
|
21197
|
+
pmak,
|
|
21198
|
+
session,
|
|
21199
|
+
workspaceTeamId: args.workspaceTeamId,
|
|
21200
|
+
explicitTeamId: args.explicitTeamId,
|
|
21201
|
+
mode: args.mode,
|
|
21202
|
+
mask
|
|
21203
|
+
});
|
|
21204
|
+
if (!result.message) {
|
|
21205
|
+
return;
|
|
21206
|
+
}
|
|
21207
|
+
if (result.level === "fail") {
|
|
21208
|
+
throw new Error(result.message);
|
|
21209
|
+
}
|
|
21210
|
+
if (result.level === "note") {
|
|
21211
|
+
args.log.warning(result.message);
|
|
21212
|
+
return;
|
|
21213
|
+
}
|
|
21214
|
+
args.log.info(result.message);
|
|
21215
|
+
}
|
|
21216
|
+
|
|
21217
|
+
// src/lib/error-advice.ts
|
|
21218
|
+
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.";
|
|
21219
|
+
function expiryAdvice(code) {
|
|
21220
|
+
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.`;
|
|
21221
|
+
}
|
|
21222
|
+
function forbiddenAdvice(ctx) {
|
|
21223
|
+
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)` : "";
|
|
21224
|
+
const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
|
|
21225
|
+
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";
|
|
21226
|
+
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.`;
|
|
21227
|
+
}
|
|
21228
|
+
function buildAdvice(status, body, ctx) {
|
|
21229
|
+
if (body.includes("UNAUTHENTICATED")) {
|
|
21230
|
+
return expiryAdvice("UNAUTHENTICATED");
|
|
21231
|
+
}
|
|
21232
|
+
if (body.includes("authenticationError")) {
|
|
21233
|
+
return expiryAdvice("authenticationError");
|
|
21234
|
+
}
|
|
21235
|
+
if (body.includes("Only personal workspaces")) {
|
|
21236
|
+
return WORKSPACE_PERSONAL_ONLY_ADVICE;
|
|
21237
|
+
}
|
|
21238
|
+
if (body.includes("projectAlreadyConnected")) {
|
|
21239
|
+
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.`;
|
|
21240
|
+
}
|
|
21241
|
+
if (body.includes("invalidParamError") && body.includes("already exists")) {
|
|
21242
|
+
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.`;
|
|
21243
|
+
}
|
|
21244
|
+
if (body.includes("Team feature is not available for your organization")) {
|
|
21245
|
+
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.`;
|
|
21246
|
+
}
|
|
21247
|
+
if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
|
|
21248
|
+
return forbiddenAdvice(ctx);
|
|
21249
|
+
}
|
|
21250
|
+
return void 0;
|
|
21251
|
+
}
|
|
21252
|
+
function adviseFromHttpError(err, ctx) {
|
|
21253
|
+
const body = err.responseBody || err.message || "";
|
|
21254
|
+
const advice = buildAdvice(err.status, body, ctx);
|
|
21255
|
+
if (!advice) {
|
|
21256
|
+
return void 0;
|
|
21257
|
+
}
|
|
21258
|
+
return new Error(ctx.mask(advice), { cause: err });
|
|
21259
|
+
}
|
|
21260
|
+
function adviseFromBifrostBody(status, body, ctx) {
|
|
21261
|
+
const advice = buildAdvice(status, String(body || ""), ctx);
|
|
21262
|
+
if (!advice) {
|
|
21263
|
+
return void 0;
|
|
21264
|
+
}
|
|
21265
|
+
return new Error(ctx.mask(advice), {
|
|
21266
|
+
cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
|
|
21267
|
+
});
|
|
21268
|
+
}
|
|
21269
|
+
|
|
20963
21270
|
// src/lib/secrets.ts
|
|
20964
21271
|
var REDACTED = "[REDACTED]";
|
|
20965
21272
|
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -21012,6 +21319,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
|
|
|
21012
21319
|
return sanitized.split(secret).join(replacement);
|
|
21013
21320
|
}, source);
|
|
21014
21321
|
}
|
|
21322
|
+
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
21323
|
+
return (input) => redactSecrets(input, secretValues, replacement);
|
|
21324
|
+
}
|
|
21015
21325
|
function headerEntries(headers) {
|
|
21016
21326
|
if (headers instanceof Headers) {
|
|
21017
21327
|
return Array.from(headers.entries());
|
|
@@ -21143,12 +21453,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
|
|
|
21143
21453
|
prod: {
|
|
21144
21454
|
apiBaseUrl: "https://api.getpostman.com",
|
|
21145
21455
|
bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
|
|
21456
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21146
21457
|
observabilityBaseUrl: "https://api.observability.postman.com",
|
|
21147
21458
|
observabilityEnv: "production"
|
|
21148
21459
|
},
|
|
21149
21460
|
beta: {
|
|
21150
21461
|
apiBaseUrl: "https://api.getpostman-beta.com",
|
|
21151
21462
|
bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
|
|
21463
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21152
21464
|
observabilityBaseUrl: "https://api.observability.postman-beta.com",
|
|
21153
21465
|
observabilityEnv: "beta"
|
|
21154
21466
|
}
|
|
@@ -21212,7 +21524,23 @@ var BifrostCatalogClient = class {
|
|
|
21212
21524
|
}
|
|
21213
21525
|
return h;
|
|
21214
21526
|
}
|
|
21215
|
-
|
|
21527
|
+
/**
|
|
21528
|
+
* Reactive error-advice context. The session identity comes from the credential
|
|
21529
|
+
* preflight's in-process memo when it ran; the advice degrades gracefully without it.
|
|
21530
|
+
*/
|
|
21531
|
+
adviceContext(operation) {
|
|
21532
|
+
const session = getMemoizedSessionIdentity();
|
|
21533
|
+
return {
|
|
21534
|
+
operation,
|
|
21535
|
+
hasAccessToken: Boolean(this.accessToken),
|
|
21536
|
+
sessionTeamId: session?.teamId,
|
|
21537
|
+
sessionRoles: session?.roles,
|
|
21538
|
+
sessionConsumerType: session?.consumerType,
|
|
21539
|
+
explicitTeamId: this.teamId || void 0,
|
|
21540
|
+
mask: createSecretMasker(this.secretValues)
|
|
21541
|
+
};
|
|
21542
|
+
}
|
|
21543
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21216
21544
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21217
21545
|
method: "POST",
|
|
21218
21546
|
headers: this.headers(),
|
|
@@ -21224,20 +21552,27 @@ var BifrostCatalogClient = class {
|
|
|
21224
21552
|
})
|
|
21225
21553
|
});
|
|
21226
21554
|
if (!response.ok) {
|
|
21227
|
-
|
|
21555
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21228
21556
|
method: "POST",
|
|
21229
21557
|
url: `bifrost:api-catalog:${method} ${path6}`,
|
|
21230
21558
|
secretValues: this.secretValues
|
|
21231
21559
|
});
|
|
21560
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
21561
|
+
throw advised ?? httpErr;
|
|
21232
21562
|
}
|
|
21233
21563
|
const data = await response.json();
|
|
21234
21564
|
if (data && typeof data === "object" && "error" in data && data.error) {
|
|
21235
21565
|
const errObj = data.error;
|
|
21236
|
-
|
|
21566
|
+
const advised = adviseFromBifrostBody(
|
|
21567
|
+
response.status,
|
|
21568
|
+
JSON.stringify({ error: errObj }),
|
|
21569
|
+
this.adviceContext(operation)
|
|
21570
|
+
);
|
|
21571
|
+
throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
|
|
21237
21572
|
}
|
|
21238
21573
|
return data;
|
|
21239
21574
|
}
|
|
21240
|
-
async akitaProxyRequest(method, path6, body = {}) {
|
|
21575
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21241
21576
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21242
21577
|
method: "POST",
|
|
21243
21578
|
headers: this.headers(),
|
|
@@ -21250,7 +21585,10 @@ var BifrostCatalogClient = class {
|
|
|
21250
21585
|
});
|
|
21251
21586
|
if (!response.ok) {
|
|
21252
21587
|
const text = await response.text().catch(() => "");
|
|
21253
|
-
|
|
21588
|
+
const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
|
|
21589
|
+
const errorText = advised ? text ? `${text}
|
|
21590
|
+
${advised.message}` : advised.message : text;
|
|
21591
|
+
return { ok: false, status: response.status, data: null, errorText };
|
|
21254
21592
|
}
|
|
21255
21593
|
const data = await response.json();
|
|
21256
21594
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
@@ -21265,7 +21603,9 @@ var BifrostCatalogClient = class {
|
|
|
21265
21603
|
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
|
|
21266
21604
|
const data = await this.proxyRequest(
|
|
21267
21605
|
"GET",
|
|
21268
|
-
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}
|
|
21606
|
+
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
|
|
21607
|
+
{},
|
|
21608
|
+
"discovered-services listing"
|
|
21269
21609
|
);
|
|
21270
21610
|
allItems.push(...data.items || []);
|
|
21271
21611
|
if (data.total && allItems.length >= data.total) {
|
|
@@ -21289,7 +21629,8 @@ var BifrostCatalogClient = class {
|
|
|
21289
21629
|
const data = await this.proxyRequest(
|
|
21290
21630
|
"POST",
|
|
21291
21631
|
"/api/v1/onboarding/prepare-collection",
|
|
21292
|
-
{ service_id: String(serviceId), workspace_id: workspaceId }
|
|
21632
|
+
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21633
|
+
"collection preparation"
|
|
21293
21634
|
);
|
|
21294
21635
|
return data.id;
|
|
21295
21636
|
},
|
|
@@ -21313,7 +21654,8 @@ var BifrostCatalogClient = class {
|
|
|
21313
21654
|
await this.proxyRequest(
|
|
21314
21655
|
"POST",
|
|
21315
21656
|
"/api/v1/onboarding/git",
|
|
21316
|
-
body
|
|
21657
|
+
body,
|
|
21658
|
+
"git onboarding"
|
|
21317
21659
|
);
|
|
21318
21660
|
},
|
|
21319
21661
|
{ maxAttempts: 2, delayMs: 3e3 }
|
|
@@ -21326,7 +21668,9 @@ var BifrostCatalogClient = class {
|
|
|
21326
21668
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
21327
21669
|
const result = await this.akitaProxyRequest(
|
|
21328
21670
|
"GET",
|
|
21329
|
-
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}
|
|
21671
|
+
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
21672
|
+
{},
|
|
21673
|
+
"provider service resolution"
|
|
21330
21674
|
);
|
|
21331
21675
|
if (!result.ok || !result.data) return null;
|
|
21332
21676
|
const services = result.data.services || [];
|
|
@@ -21356,7 +21700,8 @@ var BifrostCatalogClient = class {
|
|
|
21356
21700
|
workspace_id: workspaceId,
|
|
21357
21701
|
system_env: systemEnvironmentId
|
|
21358
21702
|
}]
|
|
21359
|
-
}
|
|
21703
|
+
},
|
|
21704
|
+
"Insights onboarding acknowledgment"
|
|
21360
21705
|
);
|
|
21361
21706
|
if (!result.ok) {
|
|
21362
21707
|
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21365,7 +21710,9 @@ var BifrostCatalogClient = class {
|
|
|
21365
21710
|
async acknowledgeWorkspace(workspaceId) {
|
|
21366
21711
|
const result = await this.akitaProxyRequest(
|
|
21367
21712
|
"POST",
|
|
21368
|
-
`/v2/workspaces/${workspaceId}/onboarding/acknowledge
|
|
21713
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
21714
|
+
{},
|
|
21715
|
+
"workspace onboarding acknowledgment"
|
|
21369
21716
|
);
|
|
21370
21717
|
if (!result.ok) {
|
|
21371
21718
|
throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21385,18 +21732,22 @@ var BifrostCatalogClient = class {
|
|
|
21385
21732
|
}
|
|
21386
21733
|
);
|
|
21387
21734
|
if (!response.ok) {
|
|
21388
|
-
|
|
21735
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21389
21736
|
method: "POST",
|
|
21390
21737
|
url: `observability:createApplication(${workspaceId})`,
|
|
21391
21738
|
secretValues: this.secretValues
|
|
21392
21739
|
});
|
|
21740
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
21741
|
+
throw advised ?? httpErr;
|
|
21393
21742
|
}
|
|
21394
21743
|
return response.json();
|
|
21395
21744
|
}
|
|
21396
21745
|
async getTeamVerificationToken(workspaceId) {
|
|
21397
21746
|
const result = await this.akitaProxyRequest(
|
|
21398
21747
|
"GET",
|
|
21399
|
-
`/v2/workspaces/${workspaceId}/team-verification-token
|
|
21748
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
21749
|
+
{},
|
|
21750
|
+
"team verification token retrieval"
|
|
21400
21751
|
);
|
|
21401
21752
|
if (!result.ok || !result.data) return null;
|
|
21402
21753
|
return result.data.team_verification_token || null;
|
|
@@ -21413,11 +21764,13 @@ var BifrostCatalogClient = class {
|
|
|
21413
21764
|
})
|
|
21414
21765
|
});
|
|
21415
21766
|
if (!response.ok) {
|
|
21416
|
-
|
|
21767
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21417
21768
|
method: "POST",
|
|
21418
21769
|
url: "bifrost:identity:POST /api/keys",
|
|
21419
21770
|
secretValues: this.secretValues
|
|
21420
21771
|
});
|
|
21772
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
|
|
21773
|
+
throw advised ?? httpErr;
|
|
21421
21774
|
}
|
|
21422
21775
|
const data = await response.json();
|
|
21423
21776
|
const apikey = data?.apikey;
|
|
@@ -21445,7 +21798,17 @@ var POLL_INTERVAL_DEFAULT = 10;
|
|
|
21445
21798
|
var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
|
|
21446
21799
|
var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
|
|
21447
21800
|
var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
21801
|
+
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
21448
21802
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
21803
|
+
function parsePreflightMode(value) {
|
|
21804
|
+
const normalized = String(value || "warn").trim().toLowerCase();
|
|
21805
|
+
if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
|
|
21806
|
+
return normalized;
|
|
21807
|
+
}
|
|
21808
|
+
throw new Error(
|
|
21809
|
+
`Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
|
|
21810
|
+
);
|
|
21811
|
+
}
|
|
21449
21812
|
function trimTrailingSlash(value) {
|
|
21450
21813
|
return value.replace(/\/+$/, "");
|
|
21451
21814
|
}
|
|
@@ -21522,11 +21885,13 @@ function resolveInputs(env = process.env) {
|
|
|
21522
21885
|
postmanApiKey,
|
|
21523
21886
|
postmanTeamId,
|
|
21524
21887
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
21888
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
|
|
21525
21889
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
21526
21890
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
21527
21891
|
postmanStack,
|
|
21528
21892
|
postmanApiBase: endpointProfile.apiBaseUrl,
|
|
21529
21893
|
postmanBifrostBase: endpointProfile.bifrostBaseUrl,
|
|
21894
|
+
postmanIapubBase: endpointProfile.iapubBaseUrl,
|
|
21530
21895
|
postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
|
|
21531
21896
|
postmanObservabilityEnv: endpointProfile.observabilityEnv
|
|
21532
21897
|
};
|
|
@@ -21632,11 +21997,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21632
21997
|
let apiKey = inputs.postmanApiKey;
|
|
21633
21998
|
const teamId = inputs.postmanTeamId;
|
|
21634
21999
|
let keyValid = false;
|
|
22000
|
+
let pmakIdentity;
|
|
21635
22001
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
21636
22002
|
if (apiKey) {
|
|
21637
22003
|
const result = await validateApiKey(apiKey, apiBase);
|
|
21638
22004
|
keyValid = result.valid;
|
|
21639
|
-
if (
|
|
22005
|
+
if (keyValid) {
|
|
22006
|
+
pmakIdentity = { source: "pmak/me", teamId: result.teamId };
|
|
22007
|
+
} else {
|
|
21640
22008
|
reporter.warning("Provided postman-api-key is invalid or expired.");
|
|
21641
22009
|
}
|
|
21642
22010
|
}
|
|
@@ -21683,7 +22051,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21683
22051
|
} else {
|
|
21684
22052
|
reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
|
|
21685
22053
|
}
|
|
21686
|
-
return { apiKey, teamId: resolvedTeamId };
|
|
22054
|
+
return { apiKey, teamId: resolvedTeamId, pmakIdentity };
|
|
22055
|
+
}
|
|
22056
|
+
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
|
|
22057
|
+
await runCredentialPreflight({
|
|
22058
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22059
|
+
iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
|
|
22060
|
+
pmak,
|
|
22061
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22062
|
+
explicitTeamId: inputs.postmanTeamId || void 0,
|
|
22063
|
+
mode: inputs.credentialPreflight,
|
|
22064
|
+
mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
|
|
22065
|
+
log: reporter,
|
|
22066
|
+
fetchImpl
|
|
22067
|
+
});
|
|
21687
22068
|
}
|
|
21688
22069
|
async function runAction() {
|
|
21689
22070
|
const inputs = resolveInputs();
|
|
@@ -21702,7 +22083,8 @@ async function runAction() {
|
|
|
21702
22083
|
observabilityBaseUrl: inputs.postmanObservabilityBase,
|
|
21703
22084
|
observabilityEnv: inputs.postmanObservabilityEnv
|
|
21704
22085
|
});
|
|
21705
|
-
const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22086
|
+
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22087
|
+
await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
|
|
21706
22088
|
const client = new BifrostCatalogClient({
|
|
21707
22089
|
accessToken: inputs.postmanAccessToken,
|
|
21708
22090
|
teamId,
|
|
@@ -21736,12 +22118,15 @@ async function runAction() {
|
|
|
21736
22118
|
0 && (module.exports = {
|
|
21737
22119
|
DEFAULT_POSTMAN_API_BASE,
|
|
21738
22120
|
DEFAULT_POSTMAN_BIFROST_BASE,
|
|
22121
|
+
DEFAULT_POSTMAN_IAPUB_BASE,
|
|
21739
22122
|
DEFAULT_POSTMAN_OBSERVABILITY_BASE,
|
|
21740
22123
|
createPlannedOutputs,
|
|
21741
22124
|
getTeams,
|
|
22125
|
+
parsePreflightMode,
|
|
21742
22126
|
resolveApiKeyAndTeamId,
|
|
21743
22127
|
resolveInputs,
|
|
21744
22128
|
runAction,
|
|
22129
|
+
runCredentialPreflightForInputs,
|
|
21745
22130
|
runOnboarding,
|
|
21746
22131
|
validateApiKey
|
|
21747
22132
|
});
|
package/package.json
CHANGED