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