@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/cli.cjs
CHANGED
|
@@ -20958,6 +20958,310 @@ function getIDToken(aud) {
|
|
|
20958
20958
|
});
|
|
20959
20959
|
}
|
|
20960
20960
|
|
|
20961
|
+
// src/lib/credential-identity.ts
|
|
20962
|
+
var sessionPath = "/api/sessions/current";
|
|
20963
|
+
var pmakMemo = /* @__PURE__ */ new Map();
|
|
20964
|
+
var sessionMemo = /* @__PURE__ */ new Map();
|
|
20965
|
+
var memoizedSessionIdentity;
|
|
20966
|
+
function getMemoizedSessionIdentity() {
|
|
20967
|
+
return memoizedSessionIdentity;
|
|
20968
|
+
}
|
|
20969
|
+
function asRecord(value) {
|
|
20970
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20971
|
+
return void 0;
|
|
20972
|
+
}
|
|
20973
|
+
return value;
|
|
20974
|
+
}
|
|
20975
|
+
function coerceId(raw) {
|
|
20976
|
+
return raw ? String(raw) : void 0;
|
|
20977
|
+
}
|
|
20978
|
+
function coerceText(raw) {
|
|
20979
|
+
if (typeof raw !== "string") {
|
|
20980
|
+
return void 0;
|
|
20981
|
+
}
|
|
20982
|
+
const trimmed = raw.trim();
|
|
20983
|
+
return trimmed ? trimmed : void 0;
|
|
20984
|
+
}
|
|
20985
|
+
function normalizeBaseUrl(raw) {
|
|
20986
|
+
return String(raw || "").replace(/\/+$/, "");
|
|
20987
|
+
}
|
|
20988
|
+
async function resolvePmakIdentity(opts) {
|
|
20989
|
+
const apiKey = String(opts.apiKey || "").trim();
|
|
20990
|
+
if (!apiKey) {
|
|
20991
|
+
return void 0;
|
|
20992
|
+
}
|
|
20993
|
+
const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
|
|
20994
|
+
const memoKey = `${baseUrl}::${apiKey}`;
|
|
20995
|
+
let pending = pmakMemo.get(memoKey);
|
|
20996
|
+
if (!pending) {
|
|
20997
|
+
pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
|
|
20998
|
+
pmakMemo.set(memoKey, pending);
|
|
20999
|
+
}
|
|
21000
|
+
return pending;
|
|
21001
|
+
}
|
|
21002
|
+
async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
|
|
21003
|
+
try {
|
|
21004
|
+
const response = await fetchImpl(`${baseUrl}/me`, {
|
|
21005
|
+
method: "GET",
|
|
21006
|
+
headers: { "X-Api-Key": apiKey }
|
|
21007
|
+
});
|
|
21008
|
+
if (!response.ok) {
|
|
21009
|
+
return void 0;
|
|
21010
|
+
}
|
|
21011
|
+
const payload = asRecord(await response.json());
|
|
21012
|
+
const user = asRecord(payload?.user);
|
|
21013
|
+
if (!user) {
|
|
21014
|
+
return void 0;
|
|
21015
|
+
}
|
|
21016
|
+
return {
|
|
21017
|
+
source: "pmak/me",
|
|
21018
|
+
userId: coerceId(user.id),
|
|
21019
|
+
fullName: coerceText(user.fullName) ?? coerceText(user.username),
|
|
21020
|
+
teamId: coerceId(user.teamId),
|
|
21021
|
+
teamName: coerceText(user.teamName),
|
|
21022
|
+
teamDomain: coerceText(user.teamDomain)
|
|
21023
|
+
};
|
|
21024
|
+
} catch {
|
|
21025
|
+
return void 0;
|
|
21026
|
+
}
|
|
21027
|
+
}
|
|
21028
|
+
async function resolveSessionIdentity(opts) {
|
|
21029
|
+
const accessToken = String(opts.accessToken || "").trim();
|
|
21030
|
+
if (!accessToken) {
|
|
21031
|
+
return void 0;
|
|
21032
|
+
}
|
|
21033
|
+
const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
|
|
21034
|
+
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21035
|
+
let pending = sessionMemo.get(memoKey);
|
|
21036
|
+
if (!pending) {
|
|
21037
|
+
pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
|
|
21038
|
+
sessionMemo.set(memoKey, pending);
|
|
21039
|
+
}
|
|
21040
|
+
return pending;
|
|
21041
|
+
}
|
|
21042
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
|
|
21043
|
+
try {
|
|
21044
|
+
const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21045
|
+
method: "GET",
|
|
21046
|
+
headers: { "x-access-token": accessToken }
|
|
21047
|
+
});
|
|
21048
|
+
if (!response.ok) {
|
|
21049
|
+
return void 0;
|
|
21050
|
+
}
|
|
21051
|
+
const payload = asRecord(await response.json());
|
|
21052
|
+
if (!payload) {
|
|
21053
|
+
return void 0;
|
|
21054
|
+
}
|
|
21055
|
+
const identity = asRecord(payload.identity);
|
|
21056
|
+
const data = asRecord(payload.data);
|
|
21057
|
+
const user = asRecord(data?.user);
|
|
21058
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21059
|
+
const singleRole = coerceText(user?.role);
|
|
21060
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21061
|
+
const resolved = {
|
|
21062
|
+
source: "iapub/sessions",
|
|
21063
|
+
userId: coerceId(user?.id),
|
|
21064
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21065
|
+
teamId: coerceId(identity?.team),
|
|
21066
|
+
teamDomain: coerceText(identity?.domain),
|
|
21067
|
+
...roles ? { roles } : {},
|
|
21068
|
+
consumerType: coerceText(payload.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21069
|
+
};
|
|
21070
|
+
memoizedSessionIdentity = resolved;
|
|
21071
|
+
return resolved;
|
|
21072
|
+
} catch {
|
|
21073
|
+
return void 0;
|
|
21074
|
+
}
|
|
21075
|
+
}
|
|
21076
|
+
function describeTeam(id) {
|
|
21077
|
+
const label = id?.teamName ?? id?.teamDomain;
|
|
21078
|
+
return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
|
|
21079
|
+
}
|
|
21080
|
+
function formatIdentityLine(id, mask) {
|
|
21081
|
+
const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
|
|
21082
|
+
const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
|
|
21083
|
+
if (id.source === "pmak/me") {
|
|
21084
|
+
const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
|
|
21085
|
+
return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
|
|
21086
|
+
}
|
|
21087
|
+
return mask(
|
|
21088
|
+
`postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
|
|
21089
|
+
);
|
|
21090
|
+
}
|
|
21091
|
+
function crossCheckIdentities(args) {
|
|
21092
|
+
if (args.mode === "off") {
|
|
21093
|
+
return { ok: true, level: "ok", message: "" };
|
|
21094
|
+
}
|
|
21095
|
+
const pmakTeamId = args.pmak?.teamId;
|
|
21096
|
+
const sessionTeamId = args.session?.teamId;
|
|
21097
|
+
if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
|
|
21098
|
+
const level = args.mode === "enforce" ? "fail" : "note";
|
|
21099
|
+
const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
|
|
21100
|
+
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.";
|
|
21101
|
+
return {
|
|
21102
|
+
ok: false,
|
|
21103
|
+
level,
|
|
21104
|
+
message: args.mask(
|
|
21105
|
+
`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
|
|
21106
|
+
)
|
|
21107
|
+
};
|
|
21108
|
+
}
|
|
21109
|
+
if (pmakTeamId && sessionTeamId) {
|
|
21110
|
+
const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
|
|
21111
|
+
const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
|
|
21112
|
+
return {
|
|
21113
|
+
ok: true,
|
|
21114
|
+
level: "ok",
|
|
21115
|
+
message: args.mask(
|
|
21116
|
+
`postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
|
|
21117
|
+
)
|
|
21118
|
+
};
|
|
21119
|
+
}
|
|
21120
|
+
const missing = [
|
|
21121
|
+
!pmakTeamId ? "PMAK identity" : void 0,
|
|
21122
|
+
!sessionTeamId ? "access-token session identity" : void 0
|
|
21123
|
+
].filter(Boolean).join(" and ");
|
|
21124
|
+
return {
|
|
21125
|
+
ok: false,
|
|
21126
|
+
level: "note",
|
|
21127
|
+
message: args.mask(
|
|
21128
|
+
`postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
|
|
21129
|
+
)
|
|
21130
|
+
};
|
|
21131
|
+
}
|
|
21132
|
+
async function runCredentialPreflight(args) {
|
|
21133
|
+
if (args.mode === "off") {
|
|
21134
|
+
return;
|
|
21135
|
+
}
|
|
21136
|
+
const mask = args.mask;
|
|
21137
|
+
const apiKey = String(args.postmanApiKey || "").trim();
|
|
21138
|
+
const accessToken = String(args.postmanAccessToken || "").trim();
|
|
21139
|
+
let pmak = args.pmak;
|
|
21140
|
+
if (pmak) {
|
|
21141
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21142
|
+
} else if (apiKey) {
|
|
21143
|
+
try {
|
|
21144
|
+
pmak = await resolvePmakIdentity({
|
|
21145
|
+
apiBaseUrl: args.apiBaseUrl,
|
|
21146
|
+
apiKey,
|
|
21147
|
+
fetchImpl: args.fetchImpl
|
|
21148
|
+
});
|
|
21149
|
+
} catch (error2) {
|
|
21150
|
+
args.log.warning(
|
|
21151
|
+
mask(
|
|
21152
|
+
`postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21153
|
+
)
|
|
21154
|
+
);
|
|
21155
|
+
}
|
|
21156
|
+
if (pmak) {
|
|
21157
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21158
|
+
} else {
|
|
21159
|
+
args.log.warning(
|
|
21160
|
+
mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
|
|
21161
|
+
);
|
|
21162
|
+
}
|
|
21163
|
+
}
|
|
21164
|
+
if (!accessToken) {
|
|
21165
|
+
args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
|
|
21166
|
+
return;
|
|
21167
|
+
}
|
|
21168
|
+
let session;
|
|
21169
|
+
try {
|
|
21170
|
+
session = await resolveSessionIdentity({
|
|
21171
|
+
iapubBaseUrl: args.iapubBaseUrl,
|
|
21172
|
+
accessToken,
|
|
21173
|
+
fetchImpl: args.fetchImpl
|
|
21174
|
+
});
|
|
21175
|
+
} catch (error2) {
|
|
21176
|
+
args.log.warning(
|
|
21177
|
+
mask(
|
|
21178
|
+
`postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21179
|
+
)
|
|
21180
|
+
);
|
|
21181
|
+
}
|
|
21182
|
+
if (session) {
|
|
21183
|
+
args.log.info(formatIdentityLine(session, mask));
|
|
21184
|
+
} else {
|
|
21185
|
+
args.log.warning(
|
|
21186
|
+
mask(
|
|
21187
|
+
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21188
|
+
)
|
|
21189
|
+
);
|
|
21190
|
+
}
|
|
21191
|
+
const result = crossCheckIdentities({
|
|
21192
|
+
pmak,
|
|
21193
|
+
session,
|
|
21194
|
+
workspaceTeamId: args.workspaceTeamId,
|
|
21195
|
+
explicitTeamId: args.explicitTeamId,
|
|
21196
|
+
mode: args.mode,
|
|
21197
|
+
mask
|
|
21198
|
+
});
|
|
21199
|
+
if (!result.message) {
|
|
21200
|
+
return;
|
|
21201
|
+
}
|
|
21202
|
+
if (result.level === "fail") {
|
|
21203
|
+
throw new Error(result.message);
|
|
21204
|
+
}
|
|
21205
|
+
if (result.level === "note") {
|
|
21206
|
+
args.log.warning(result.message);
|
|
21207
|
+
return;
|
|
21208
|
+
}
|
|
21209
|
+
args.log.info(result.message);
|
|
21210
|
+
}
|
|
21211
|
+
|
|
21212
|
+
// src/lib/error-advice.ts
|
|
21213
|
+
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.";
|
|
21214
|
+
function expiryAdvice(code) {
|
|
21215
|
+
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.`;
|
|
21216
|
+
}
|
|
21217
|
+
function forbiddenAdvice(ctx) {
|
|
21218
|
+
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)` : "";
|
|
21219
|
+
const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
|
|
21220
|
+
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";
|
|
21221
|
+
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.`;
|
|
21222
|
+
}
|
|
21223
|
+
function buildAdvice(status, body, ctx) {
|
|
21224
|
+
if (body.includes("UNAUTHENTICATED")) {
|
|
21225
|
+
return expiryAdvice("UNAUTHENTICATED");
|
|
21226
|
+
}
|
|
21227
|
+
if (body.includes("authenticationError")) {
|
|
21228
|
+
return expiryAdvice("authenticationError");
|
|
21229
|
+
}
|
|
21230
|
+
if (body.includes("Only personal workspaces")) {
|
|
21231
|
+
return WORKSPACE_PERSONAL_ONLY_ADVICE;
|
|
21232
|
+
}
|
|
21233
|
+
if (body.includes("projectAlreadyConnected")) {
|
|
21234
|
+
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.`;
|
|
21235
|
+
}
|
|
21236
|
+
if (body.includes("invalidParamError") && body.includes("already exists")) {
|
|
21237
|
+
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.`;
|
|
21238
|
+
}
|
|
21239
|
+
if (body.includes("Team feature is not available for your organization")) {
|
|
21240
|
+
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.`;
|
|
21241
|
+
}
|
|
21242
|
+
if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
|
|
21243
|
+
return forbiddenAdvice(ctx);
|
|
21244
|
+
}
|
|
21245
|
+
return void 0;
|
|
21246
|
+
}
|
|
21247
|
+
function adviseFromHttpError(err, ctx) {
|
|
21248
|
+
const body = err.responseBody || err.message || "";
|
|
21249
|
+
const advice = buildAdvice(err.status, body, ctx);
|
|
21250
|
+
if (!advice) {
|
|
21251
|
+
return void 0;
|
|
21252
|
+
}
|
|
21253
|
+
return new Error(ctx.mask(advice), { cause: err });
|
|
21254
|
+
}
|
|
21255
|
+
function adviseFromBifrostBody(status, body, ctx) {
|
|
21256
|
+
const advice = buildAdvice(status, String(body || ""), ctx);
|
|
21257
|
+
if (!advice) {
|
|
21258
|
+
return void 0;
|
|
21259
|
+
}
|
|
21260
|
+
return new Error(ctx.mask(advice), {
|
|
21261
|
+
cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
|
|
21262
|
+
});
|
|
21263
|
+
}
|
|
21264
|
+
|
|
20961
21265
|
// src/lib/secrets.ts
|
|
20962
21266
|
var REDACTED = "[REDACTED]";
|
|
20963
21267
|
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -21010,6 +21314,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
|
|
|
21010
21314
|
return sanitized.split(secret).join(replacement);
|
|
21011
21315
|
}, source);
|
|
21012
21316
|
}
|
|
21317
|
+
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
21318
|
+
return (input) => redactSecrets(input, secretValues, replacement);
|
|
21319
|
+
}
|
|
21013
21320
|
function headerEntries(headers) {
|
|
21014
21321
|
if (headers instanceof Headers) {
|
|
21015
21322
|
return Array.from(headers.entries());
|
|
@@ -21141,12 +21448,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
|
|
|
21141
21448
|
prod: {
|
|
21142
21449
|
apiBaseUrl: "https://api.getpostman.com",
|
|
21143
21450
|
bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
|
|
21451
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21144
21452
|
observabilityBaseUrl: "https://api.observability.postman.com",
|
|
21145
21453
|
observabilityEnv: "production"
|
|
21146
21454
|
},
|
|
21147
21455
|
beta: {
|
|
21148
21456
|
apiBaseUrl: "https://api.getpostman-beta.com",
|
|
21149
21457
|
bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
|
|
21458
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21150
21459
|
observabilityBaseUrl: "https://api.observability.postman-beta.com",
|
|
21151
21460
|
observabilityEnv: "beta"
|
|
21152
21461
|
}
|
|
@@ -21210,7 +21519,23 @@ var BifrostCatalogClient = class {
|
|
|
21210
21519
|
}
|
|
21211
21520
|
return h;
|
|
21212
21521
|
}
|
|
21213
|
-
|
|
21522
|
+
/**
|
|
21523
|
+
* Reactive error-advice context. The session identity comes from the credential
|
|
21524
|
+
* preflight's in-process memo when it ran; the advice degrades gracefully without it.
|
|
21525
|
+
*/
|
|
21526
|
+
adviceContext(operation) {
|
|
21527
|
+
const session = getMemoizedSessionIdentity();
|
|
21528
|
+
return {
|
|
21529
|
+
operation,
|
|
21530
|
+
hasAccessToken: Boolean(this.accessToken),
|
|
21531
|
+
sessionTeamId: session?.teamId,
|
|
21532
|
+
sessionRoles: session?.roles,
|
|
21533
|
+
sessionConsumerType: session?.consumerType,
|
|
21534
|
+
explicitTeamId: this.teamId || void 0,
|
|
21535
|
+
mask: createSecretMasker(this.secretValues)
|
|
21536
|
+
};
|
|
21537
|
+
}
|
|
21538
|
+
async proxyRequest(method, path7, body = {}, operation = "api-catalog request") {
|
|
21214
21539
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21215
21540
|
method: "POST",
|
|
21216
21541
|
headers: this.headers(),
|
|
@@ -21222,20 +21547,27 @@ var BifrostCatalogClient = class {
|
|
|
21222
21547
|
})
|
|
21223
21548
|
});
|
|
21224
21549
|
if (!response.ok) {
|
|
21225
|
-
|
|
21550
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21226
21551
|
method: "POST",
|
|
21227
21552
|
url: `bifrost:api-catalog:${method} ${path7}`,
|
|
21228
21553
|
secretValues: this.secretValues
|
|
21229
21554
|
});
|
|
21555
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
21556
|
+
throw advised ?? httpErr;
|
|
21230
21557
|
}
|
|
21231
21558
|
const data = await response.json();
|
|
21232
21559
|
if (data && typeof data === "object" && "error" in data && data.error) {
|
|
21233
21560
|
const errObj = data.error;
|
|
21234
|
-
|
|
21561
|
+
const advised = adviseFromBifrostBody(
|
|
21562
|
+
response.status,
|
|
21563
|
+
JSON.stringify({ error: errObj }),
|
|
21564
|
+
this.adviceContext(operation)
|
|
21565
|
+
);
|
|
21566
|
+
throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
|
|
21235
21567
|
}
|
|
21236
21568
|
return data;
|
|
21237
21569
|
}
|
|
21238
|
-
async akitaProxyRequest(method, path7, body = {}) {
|
|
21570
|
+
async akitaProxyRequest(method, path7, body = {}, operation = "Insights request") {
|
|
21239
21571
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21240
21572
|
method: "POST",
|
|
21241
21573
|
headers: this.headers(),
|
|
@@ -21248,7 +21580,10 @@ var BifrostCatalogClient = class {
|
|
|
21248
21580
|
});
|
|
21249
21581
|
if (!response.ok) {
|
|
21250
21582
|
const text = await response.text().catch(() => "");
|
|
21251
|
-
|
|
21583
|
+
const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
|
|
21584
|
+
const errorText = advised ? text ? `${text}
|
|
21585
|
+
${advised.message}` : advised.message : text;
|
|
21586
|
+
return { ok: false, status: response.status, data: null, errorText };
|
|
21252
21587
|
}
|
|
21253
21588
|
const data = await response.json();
|
|
21254
21589
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
@@ -21263,7 +21598,9 @@ var BifrostCatalogClient = class {
|
|
|
21263
21598
|
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
|
|
21264
21599
|
const data = await this.proxyRequest(
|
|
21265
21600
|
"GET",
|
|
21266
|
-
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}
|
|
21601
|
+
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
|
|
21602
|
+
{},
|
|
21603
|
+
"discovered-services listing"
|
|
21267
21604
|
);
|
|
21268
21605
|
allItems.push(...data.items || []);
|
|
21269
21606
|
if (data.total && allItems.length >= data.total) {
|
|
@@ -21287,7 +21624,8 @@ var BifrostCatalogClient = class {
|
|
|
21287
21624
|
const data = await this.proxyRequest(
|
|
21288
21625
|
"POST",
|
|
21289
21626
|
"/api/v1/onboarding/prepare-collection",
|
|
21290
|
-
{ service_id: String(serviceId), workspace_id: workspaceId }
|
|
21627
|
+
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21628
|
+
"collection preparation"
|
|
21291
21629
|
);
|
|
21292
21630
|
return data.id;
|
|
21293
21631
|
},
|
|
@@ -21311,7 +21649,8 @@ var BifrostCatalogClient = class {
|
|
|
21311
21649
|
await this.proxyRequest(
|
|
21312
21650
|
"POST",
|
|
21313
21651
|
"/api/v1/onboarding/git",
|
|
21314
|
-
body
|
|
21652
|
+
body,
|
|
21653
|
+
"git onboarding"
|
|
21315
21654
|
);
|
|
21316
21655
|
},
|
|
21317
21656
|
{ maxAttempts: 2, delayMs: 3e3 }
|
|
@@ -21324,7 +21663,9 @@ var BifrostCatalogClient = class {
|
|
|
21324
21663
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
21325
21664
|
const result = await this.akitaProxyRequest(
|
|
21326
21665
|
"GET",
|
|
21327
|
-
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}
|
|
21666
|
+
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
21667
|
+
{},
|
|
21668
|
+
"provider service resolution"
|
|
21328
21669
|
);
|
|
21329
21670
|
if (!result.ok || !result.data) return null;
|
|
21330
21671
|
const services = result.data.services || [];
|
|
@@ -21354,7 +21695,8 @@ var BifrostCatalogClient = class {
|
|
|
21354
21695
|
workspace_id: workspaceId,
|
|
21355
21696
|
system_env: systemEnvironmentId
|
|
21356
21697
|
}]
|
|
21357
|
-
}
|
|
21698
|
+
},
|
|
21699
|
+
"Insights onboarding acknowledgment"
|
|
21358
21700
|
);
|
|
21359
21701
|
if (!result.ok) {
|
|
21360
21702
|
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21363,7 +21705,9 @@ var BifrostCatalogClient = class {
|
|
|
21363
21705
|
async acknowledgeWorkspace(workspaceId) {
|
|
21364
21706
|
const result = await this.akitaProxyRequest(
|
|
21365
21707
|
"POST",
|
|
21366
|
-
`/v2/workspaces/${workspaceId}/onboarding/acknowledge
|
|
21708
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
21709
|
+
{},
|
|
21710
|
+
"workspace onboarding acknowledgment"
|
|
21367
21711
|
);
|
|
21368
21712
|
if (!result.ok) {
|
|
21369
21713
|
throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21383,18 +21727,22 @@ var BifrostCatalogClient = class {
|
|
|
21383
21727
|
}
|
|
21384
21728
|
);
|
|
21385
21729
|
if (!response.ok) {
|
|
21386
|
-
|
|
21730
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21387
21731
|
method: "POST",
|
|
21388
21732
|
url: `observability:createApplication(${workspaceId})`,
|
|
21389
21733
|
secretValues: this.secretValues
|
|
21390
21734
|
});
|
|
21735
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
21736
|
+
throw advised ?? httpErr;
|
|
21391
21737
|
}
|
|
21392
21738
|
return response.json();
|
|
21393
21739
|
}
|
|
21394
21740
|
async getTeamVerificationToken(workspaceId) {
|
|
21395
21741
|
const result = await this.akitaProxyRequest(
|
|
21396
21742
|
"GET",
|
|
21397
|
-
`/v2/workspaces/${workspaceId}/team-verification-token
|
|
21743
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
21744
|
+
{},
|
|
21745
|
+
"team verification token retrieval"
|
|
21398
21746
|
);
|
|
21399
21747
|
if (!result.ok || !result.data) return null;
|
|
21400
21748
|
return result.data.team_verification_token || null;
|
|
@@ -21411,11 +21759,13 @@ var BifrostCatalogClient = class {
|
|
|
21411
21759
|
})
|
|
21412
21760
|
});
|
|
21413
21761
|
if (!response.ok) {
|
|
21414
|
-
|
|
21762
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21415
21763
|
method: "POST",
|
|
21416
21764
|
url: "bifrost:identity:POST /api/keys",
|
|
21417
21765
|
secretValues: this.secretValues
|
|
21418
21766
|
});
|
|
21767
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
|
|
21768
|
+
throw advised ?? httpErr;
|
|
21419
21769
|
}
|
|
21420
21770
|
const data = await response.json();
|
|
21421
21771
|
const apikey = data?.apikey;
|
|
@@ -21443,7 +21793,17 @@ var POLL_INTERVAL_DEFAULT = 10;
|
|
|
21443
21793
|
var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
|
|
21444
21794
|
var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
|
|
21445
21795
|
var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
21796
|
+
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
21446
21797
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
21798
|
+
function parsePreflightMode(value) {
|
|
21799
|
+
const normalized = String(value || "warn").trim().toLowerCase();
|
|
21800
|
+
if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
|
|
21801
|
+
return normalized;
|
|
21802
|
+
}
|
|
21803
|
+
throw new Error(
|
|
21804
|
+
`Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
|
|
21805
|
+
);
|
|
21806
|
+
}
|
|
21447
21807
|
function trimTrailingSlash(value) {
|
|
21448
21808
|
return value.replace(/\/+$/, "");
|
|
21449
21809
|
}
|
|
@@ -21520,11 +21880,13 @@ function resolveInputs(env = process.env) {
|
|
|
21520
21880
|
postmanApiKey,
|
|
21521
21881
|
postmanTeamId,
|
|
21522
21882
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
21883
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
|
|
21523
21884
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
21524
21885
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
21525
21886
|
postmanStack,
|
|
21526
21887
|
postmanApiBase: endpointProfile.apiBaseUrl,
|
|
21527
21888
|
postmanBifrostBase: endpointProfile.bifrostBaseUrl,
|
|
21889
|
+
postmanIapubBase: endpointProfile.iapubBaseUrl,
|
|
21528
21890
|
postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
|
|
21529
21891
|
postmanObservabilityEnv: endpointProfile.observabilityEnv
|
|
21530
21892
|
};
|
|
@@ -21620,11 +21982,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21620
21982
|
let apiKey = inputs.postmanApiKey;
|
|
21621
21983
|
const teamId = inputs.postmanTeamId;
|
|
21622
21984
|
let keyValid = false;
|
|
21985
|
+
let pmakIdentity;
|
|
21623
21986
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
21624
21987
|
if (apiKey) {
|
|
21625
21988
|
const result = await validateApiKey(apiKey, apiBase);
|
|
21626
21989
|
keyValid = result.valid;
|
|
21627
|
-
if (
|
|
21990
|
+
if (keyValid) {
|
|
21991
|
+
pmakIdentity = { source: "pmak/me", teamId: result.teamId };
|
|
21992
|
+
} else {
|
|
21628
21993
|
reporter.warning("Provided postman-api-key is invalid or expired.");
|
|
21629
21994
|
}
|
|
21630
21995
|
}
|
|
@@ -21671,7 +22036,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21671
22036
|
} else {
|
|
21672
22037
|
reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
|
|
21673
22038
|
}
|
|
21674
|
-
return { apiKey, teamId: resolvedTeamId };
|
|
22039
|
+
return { apiKey, teamId: resolvedTeamId, pmakIdentity };
|
|
22040
|
+
}
|
|
22041
|
+
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
|
|
22042
|
+
await runCredentialPreflight({
|
|
22043
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22044
|
+
iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
|
|
22045
|
+
pmak,
|
|
22046
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22047
|
+
explicitTeamId: inputs.postmanTeamId || void 0,
|
|
22048
|
+
mode: inputs.credentialPreflight,
|
|
22049
|
+
mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
|
|
22050
|
+
log: reporter,
|
|
22051
|
+
fetchImpl
|
|
22052
|
+
});
|
|
21675
22053
|
}
|
|
21676
22054
|
|
|
21677
22055
|
// src/cli.ts
|
|
@@ -21722,6 +22100,7 @@ function parseCliArgs(argv, env = process.env) {
|
|
|
21722
22100
|
"repo-url",
|
|
21723
22101
|
"postman-access-token",
|
|
21724
22102
|
"postman-api-key",
|
|
22103
|
+
"credential-preflight",
|
|
21725
22104
|
"postman-team-id",
|
|
21726
22105
|
"github-token",
|
|
21727
22106
|
"poll-timeout-seconds",
|
|
@@ -21790,10 +22169,15 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
21790
22169
|
observabilityBaseUrl: inputs.postmanObservabilityBase,
|
|
21791
22170
|
observabilityEnv: inputs.postmanObservabilityEnv
|
|
21792
22171
|
});
|
|
21793
|
-
const { apiKey, teamId } = await resolveApiKeyAndTeamId(
|
|
22172
|
+
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(
|
|
22173
|
+
inputs,
|
|
22174
|
+
preliminaryClient,
|
|
22175
|
+
reporter
|
|
22176
|
+
);
|
|
21794
22177
|
if (apiKey) {
|
|
21795
22178
|
reporter.setSecret(apiKey);
|
|
21796
22179
|
}
|
|
22180
|
+
await runCredentialPreflightForInputs(inputs, pmakIdentity, reporter);
|
|
21797
22181
|
const client = new BifrostCatalogClient({
|
|
21798
22182
|
accessToken: inputs.postmanAccessToken,
|
|
21799
22183
|
teamId,
|