@postman-cse/onboarding-insights 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/action.yml +11 -3
- package/dist/action.cjs +538 -171
- package/dist/cli.cjs +879 -391
- package/dist/index.cjs +544 -154
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -18685,10 +18686,145 @@ __export(cli_exports, {
|
|
|
18685
18686
|
toDotenv: () => toDotenv
|
|
18686
18687
|
});
|
|
18687
18688
|
module.exports = __toCommonJS(cli_exports);
|
|
18689
|
+
var import_node_crypto2 = require("node:crypto");
|
|
18688
18690
|
var import_node_fs2 = require("node:fs");
|
|
18689
18691
|
var import_promises = require("node:fs/promises");
|
|
18690
18692
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
18691
18693
|
|
|
18694
|
+
// src/lib/secrets.ts
|
|
18695
|
+
var REDACTED = "[REDACTED]";
|
|
18696
|
+
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
18697
|
+
"authorization",
|
|
18698
|
+
"cookie",
|
|
18699
|
+
"proxy-authorization",
|
|
18700
|
+
"set-cookie",
|
|
18701
|
+
"x-access-token",
|
|
18702
|
+
"x-api-key"
|
|
18703
|
+
]);
|
|
18704
|
+
function isIterable(value) {
|
|
18705
|
+
return value !== null && value !== void 0 && typeof value !== "string" && typeof value[Symbol.iterator] === "function";
|
|
18706
|
+
}
|
|
18707
|
+
function appendSecretValues(value, results) {
|
|
18708
|
+
if (value === null || value === void 0) {
|
|
18709
|
+
return;
|
|
18710
|
+
}
|
|
18711
|
+
if (typeof value === "string") {
|
|
18712
|
+
const normalized = value.trim();
|
|
18713
|
+
if (normalized) {
|
|
18714
|
+
results.push(normalized);
|
|
18715
|
+
}
|
|
18716
|
+
return;
|
|
18717
|
+
}
|
|
18718
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
18719
|
+
results.push(String(value));
|
|
18720
|
+
return;
|
|
18721
|
+
}
|
|
18722
|
+
if (Array.isArray(value) || isIterable(value)) {
|
|
18723
|
+
for (const entry of value) {
|
|
18724
|
+
appendSecretValues(entry, results);
|
|
18725
|
+
}
|
|
18726
|
+
}
|
|
18727
|
+
}
|
|
18728
|
+
function normalizeSecretValues(secretValues) {
|
|
18729
|
+
const values = [];
|
|
18730
|
+
appendSecretValues(secretValues, values);
|
|
18731
|
+
return [...new Set(values)].sort((left, right) => right.length - left.length);
|
|
18732
|
+
}
|
|
18733
|
+
function redactSecrets(input, secretValues, replacement = REDACTED) {
|
|
18734
|
+
const source = String(input ?? "");
|
|
18735
|
+
const secrets = normalizeSecretValues(secretValues);
|
|
18736
|
+
if (!source || secrets.length === 0) {
|
|
18737
|
+
return source;
|
|
18738
|
+
}
|
|
18739
|
+
return secrets.reduce((sanitized, secret) => {
|
|
18740
|
+
if (!secret) {
|
|
18741
|
+
return sanitized;
|
|
18742
|
+
}
|
|
18743
|
+
return sanitized.split(secret).join(replacement);
|
|
18744
|
+
}, source);
|
|
18745
|
+
}
|
|
18746
|
+
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
18747
|
+
return (input) => redactSecrets(input, secretValues, replacement);
|
|
18748
|
+
}
|
|
18749
|
+
function headerEntries(headers) {
|
|
18750
|
+
if (headers instanceof Headers) {
|
|
18751
|
+
return Array.from(headers.entries());
|
|
18752
|
+
}
|
|
18753
|
+
if (Array.isArray(headers)) {
|
|
18754
|
+
return headers.map(([name, value]) => [name, String(value)]);
|
|
18755
|
+
}
|
|
18756
|
+
return Object.entries(headers).map(([name, value]) => [name, String(value)]);
|
|
18757
|
+
}
|
|
18758
|
+
function sanitizeHeaders(headers, secretValues) {
|
|
18759
|
+
if (!headers) {
|
|
18760
|
+
return {};
|
|
18761
|
+
}
|
|
18762
|
+
const sanitized = {};
|
|
18763
|
+
for (const [name, value] of headerEntries(headers)) {
|
|
18764
|
+
const normalizedName = name.toLowerCase();
|
|
18765
|
+
sanitized[normalizedName] = SENSITIVE_HEADER_NAMES.has(normalizedName) ? REDACTED : redactSecrets(value, secretValues);
|
|
18766
|
+
}
|
|
18767
|
+
return sanitized;
|
|
18768
|
+
}
|
|
18769
|
+
|
|
18770
|
+
// src/lib/http-error.ts
|
|
18771
|
+
function truncate(value, limit) {
|
|
18772
|
+
if (value.length <= limit) {
|
|
18773
|
+
return value;
|
|
18774
|
+
}
|
|
18775
|
+
return `${value.slice(0, limit)}...[truncated]`;
|
|
18776
|
+
}
|
|
18777
|
+
function buildMessage(init) {
|
|
18778
|
+
const method = String(init.method || "GET").toUpperCase();
|
|
18779
|
+
const status = `${init.status}${init.statusText ? ` ${init.statusText}` : ""}`;
|
|
18780
|
+
const url = redactSecrets(init.url, init.secretValues);
|
|
18781
|
+
const body = truncate(
|
|
18782
|
+
redactSecrets(init.responseBody || "", init.secretValues),
|
|
18783
|
+
Math.max(0, init.bodyLimit ?? 800)
|
|
18784
|
+
);
|
|
18785
|
+
return body ? `${method} ${url} failed: ${status} - ${body}` : `${method} ${url} failed: ${status}`;
|
|
18786
|
+
}
|
|
18787
|
+
var HttpError = class _HttpError extends Error {
|
|
18788
|
+
method;
|
|
18789
|
+
requestHeaders;
|
|
18790
|
+
responseBody;
|
|
18791
|
+
secretValues;
|
|
18792
|
+
status;
|
|
18793
|
+
statusText;
|
|
18794
|
+
url;
|
|
18795
|
+
constructor(init) {
|
|
18796
|
+
super(buildMessage(init));
|
|
18797
|
+
this.name = "HttpError";
|
|
18798
|
+
this.method = String(init.method || "GET").toUpperCase();
|
|
18799
|
+
this.requestHeaders = init.requestHeaders;
|
|
18800
|
+
this.responseBody = init.responseBody || "";
|
|
18801
|
+
this.secretValues = init.secretValues;
|
|
18802
|
+
this.status = init.status;
|
|
18803
|
+
this.statusText = init.statusText;
|
|
18804
|
+
this.url = init.url;
|
|
18805
|
+
}
|
|
18806
|
+
static async fromResponse(response, init) {
|
|
18807
|
+
const responseBody = init.responseBody ?? await response.text().catch(() => "");
|
|
18808
|
+
return new _HttpError({
|
|
18809
|
+
...init,
|
|
18810
|
+
responseBody,
|
|
18811
|
+
status: response.status,
|
|
18812
|
+
statusText: response.statusText
|
|
18813
|
+
});
|
|
18814
|
+
}
|
|
18815
|
+
toJSON() {
|
|
18816
|
+
return {
|
|
18817
|
+
method: this.method,
|
|
18818
|
+
name: this.name,
|
|
18819
|
+
requestHeaders: sanitizeHeaders(this.requestHeaders, this.secretValues),
|
|
18820
|
+
responseBody: redactSecrets(this.responseBody, this.secretValues),
|
|
18821
|
+
status: this.status,
|
|
18822
|
+
statusText: this.statusText,
|
|
18823
|
+
url: redactSecrets(this.url, this.secretValues)
|
|
18824
|
+
};
|
|
18825
|
+
}
|
|
18826
|
+
};
|
|
18827
|
+
|
|
18692
18828
|
// src/lib/retry.ts
|
|
18693
18829
|
function sleep(delayMs) {
|
|
18694
18830
|
return new Promise((resolve2) => {
|
|
@@ -18735,6 +18871,42 @@ async function retry(operation, options = {}) {
|
|
|
18735
18871
|
}
|
|
18736
18872
|
throw new Error("Retry exhausted without returning or throwing");
|
|
18737
18873
|
}
|
|
18874
|
+
function isTransientHttpStatus(status) {
|
|
18875
|
+
return status === 408 || status === 429 || status >= 500;
|
|
18876
|
+
}
|
|
18877
|
+
function extractStatus(error2) {
|
|
18878
|
+
if (error2 instanceof HttpError) {
|
|
18879
|
+
return error2.status;
|
|
18880
|
+
}
|
|
18881
|
+
if (error2 && typeof error2 === "object" && "status" in error2) {
|
|
18882
|
+
const status = error2.status;
|
|
18883
|
+
return typeof status === "number" ? status : void 0;
|
|
18884
|
+
}
|
|
18885
|
+
if (error2 && typeof error2 === "object" && "cause" in error2) {
|
|
18886
|
+
return extractStatus(error2.cause);
|
|
18887
|
+
}
|
|
18888
|
+
return void 0;
|
|
18889
|
+
}
|
|
18890
|
+
function shouldRetryReadError(error2) {
|
|
18891
|
+
const status = extractStatus(error2);
|
|
18892
|
+
if (status === void 0) {
|
|
18893
|
+
return true;
|
|
18894
|
+
}
|
|
18895
|
+
return isTransientHttpStatus(status);
|
|
18896
|
+
}
|
|
18897
|
+
function isAmbiguousMutationFailure(error2) {
|
|
18898
|
+
const status = extractStatus(error2);
|
|
18899
|
+
if (status === void 0) {
|
|
18900
|
+
return true;
|
|
18901
|
+
}
|
|
18902
|
+
return isTransientHttpStatus(status);
|
|
18903
|
+
}
|
|
18904
|
+
var SAFE_READ_RETRY = {
|
|
18905
|
+
maxAttempts: 3,
|
|
18906
|
+
delayMs: 2e3,
|
|
18907
|
+
backoffMultiplier: 2,
|
|
18908
|
+
shouldRetry: (error2) => shouldRetryReadError(error2)
|
|
18909
|
+
};
|
|
18738
18910
|
|
|
18739
18911
|
// src/lib/postman/base-urls.ts
|
|
18740
18912
|
var POSTMAN_ENDPOINT_PROFILES = {
|
|
@@ -21625,188 +21797,76 @@ function adviseFromBifrostBody(status, body, ctx) {
|
|
|
21625
21797
|
});
|
|
21626
21798
|
}
|
|
21627
21799
|
|
|
21628
|
-
// src/lib/
|
|
21629
|
-
var
|
|
21630
|
-
var
|
|
21631
|
-
|
|
21632
|
-
|
|
21633
|
-
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
"
|
|
21637
|
-
]);
|
|
21638
|
-
function isIterable(value) {
|
|
21639
|
-
return value !== null && value !== void 0 && typeof value !== "string" && typeof value[Symbol.iterator] === "function";
|
|
21800
|
+
// src/lib/bifrost-client.ts
|
|
21801
|
+
var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
|
|
21802
|
+
var BIFROST_PROXY_PATH = "/ws/proxy";
|
|
21803
|
+
var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilityBaseUrl;
|
|
21804
|
+
var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
|
|
21805
|
+
var MAX_DISCOVERED_SERVICE_PAGES = 100;
|
|
21806
|
+
var MAX_PROVIDER_SERVICE_PAGES = 100;
|
|
21807
|
+
function isExpiredAuthError(status, body) {
|
|
21808
|
+
return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
|
|
21640
21809
|
}
|
|
21641
|
-
function
|
|
21642
|
-
|
|
21643
|
-
|
|
21810
|
+
function normalizeRepoUrl(url) {
|
|
21811
|
+
return url.trim().replace(/\/+$/, "").toLowerCase();
|
|
21812
|
+
}
|
|
21813
|
+
async function mutateOnceThenReconcile(options) {
|
|
21814
|
+
const existing = await options.findExisting();
|
|
21815
|
+
if (existing !== null) {
|
|
21816
|
+
return existing;
|
|
21644
21817
|
}
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21818
|
+
try {
|
|
21819
|
+
return await options.mutate();
|
|
21820
|
+
} catch (error2) {
|
|
21821
|
+
if (!isAmbiguousMutationFailure(error2)) {
|
|
21822
|
+
throw error2;
|
|
21649
21823
|
}
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
results.push(String(value));
|
|
21654
|
-
return;
|
|
21655
|
-
}
|
|
21656
|
-
if (Array.isArray(value) || isIterable(value)) {
|
|
21657
|
-
for (const entry of value) {
|
|
21658
|
-
appendSecretValues(entry, results);
|
|
21824
|
+
const adopted = await options.findExisting();
|
|
21825
|
+
if (adopted !== null) {
|
|
21826
|
+
return adopted;
|
|
21659
21827
|
}
|
|
21828
|
+
throw error2;
|
|
21660
21829
|
}
|
|
21661
21830
|
}
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
|
|
21831
|
+
var BifrostCatalogClient = class {
|
|
21832
|
+
tokenProvider;
|
|
21833
|
+
teamId;
|
|
21834
|
+
apiKey;
|
|
21835
|
+
fetchFn;
|
|
21836
|
+
secretValues;
|
|
21837
|
+
bifrostProxyUrl;
|
|
21838
|
+
observabilityBaseUrl;
|
|
21839
|
+
observabilityEnv;
|
|
21840
|
+
constructor(options) {
|
|
21841
|
+
this.tokenProvider = options.tokenProvider ?? new AccessTokenProvider({
|
|
21842
|
+
accessToken: options.accessToken,
|
|
21843
|
+
apiKey: options.apiKey
|
|
21844
|
+
});
|
|
21845
|
+
this.teamId = options.teamId;
|
|
21846
|
+
this.apiKey = options.apiKey;
|
|
21847
|
+
this.fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
21848
|
+
this.secretValues = [this.tokenProvider.current(), options.apiKey].filter(Boolean);
|
|
21849
|
+
const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
|
|
21850
|
+
this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
|
|
21851
|
+
this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
|
|
21852
|
+
this.observabilityEnv = options.observabilityEnv || DEFAULT_OBSERVABILITY_ENV;
|
|
21672
21853
|
}
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21854
|
+
setApiKey(apiKey) {
|
|
21855
|
+
this.apiKey = apiKey;
|
|
21856
|
+
if (apiKey && !this.secretValues.includes(apiKey)) {
|
|
21857
|
+
this.secretValues.push(apiKey);
|
|
21676
21858
|
}
|
|
21677
|
-
return sanitized.split(secret).join(replacement);
|
|
21678
|
-
}, source);
|
|
21679
|
-
}
|
|
21680
|
-
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
21681
|
-
return (input) => redactSecrets(input, secretValues, replacement);
|
|
21682
|
-
}
|
|
21683
|
-
function headerEntries(headers) {
|
|
21684
|
-
if (headers instanceof Headers) {
|
|
21685
|
-
return Array.from(headers.entries());
|
|
21686
|
-
}
|
|
21687
|
-
if (Array.isArray(headers)) {
|
|
21688
|
-
return headers.map(([name, value]) => [name, String(value)]);
|
|
21689
21859
|
}
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
return {};
|
|
21695
|
-
}
|
|
21696
|
-
const sanitized = {};
|
|
21697
|
-
for (const [name, value] of headerEntries(headers)) {
|
|
21698
|
-
const normalizedName = name.toLowerCase();
|
|
21699
|
-
sanitized[normalizedName] = SENSITIVE_HEADER_NAMES.has(normalizedName) ? REDACTED : redactSecrets(value, secretValues);
|
|
21700
|
-
}
|
|
21701
|
-
return sanitized;
|
|
21702
|
-
}
|
|
21703
|
-
|
|
21704
|
-
// src/lib/http-error.ts
|
|
21705
|
-
function truncate(value, limit) {
|
|
21706
|
-
if (value.length <= limit) {
|
|
21707
|
-
return value;
|
|
21708
|
-
}
|
|
21709
|
-
return `${value.slice(0, limit)}...[truncated]`;
|
|
21710
|
-
}
|
|
21711
|
-
function buildMessage(init) {
|
|
21712
|
-
const method = String(init.method || "GET").toUpperCase();
|
|
21713
|
-
const status = `${init.status}${init.statusText ? ` ${init.statusText}` : ""}`;
|
|
21714
|
-
const url = redactSecrets(init.url, init.secretValues);
|
|
21715
|
-
const body = truncate(
|
|
21716
|
-
redactSecrets(init.responseBody || "", init.secretValues),
|
|
21717
|
-
Math.max(0, init.bodyLimit ?? 800)
|
|
21718
|
-
);
|
|
21719
|
-
return body ? `${method} ${url} failed: ${status} - ${body}` : `${method} ${url} failed: ${status}`;
|
|
21720
|
-
}
|
|
21721
|
-
var HttpError = class _HttpError extends Error {
|
|
21722
|
-
method;
|
|
21723
|
-
requestHeaders;
|
|
21724
|
-
responseBody;
|
|
21725
|
-
secretValues;
|
|
21726
|
-
status;
|
|
21727
|
-
statusText;
|
|
21728
|
-
url;
|
|
21729
|
-
constructor(init) {
|
|
21730
|
-
super(buildMessage(init));
|
|
21731
|
-
this.name = "HttpError";
|
|
21732
|
-
this.method = String(init.method || "GET").toUpperCase();
|
|
21733
|
-
this.requestHeaders = init.requestHeaders;
|
|
21734
|
-
this.responseBody = init.responseBody || "";
|
|
21735
|
-
this.secretValues = init.secretValues;
|
|
21736
|
-
this.status = init.status;
|
|
21737
|
-
this.statusText = init.statusText;
|
|
21738
|
-
this.url = init.url;
|
|
21739
|
-
}
|
|
21740
|
-
static async fromResponse(response, init) {
|
|
21741
|
-
const responseBody = init.responseBody ?? await response.text().catch(() => "");
|
|
21742
|
-
return new _HttpError({
|
|
21743
|
-
...init,
|
|
21744
|
-
responseBody,
|
|
21745
|
-
status: response.status,
|
|
21746
|
-
statusText: response.statusText
|
|
21747
|
-
});
|
|
21748
|
-
}
|
|
21749
|
-
toJSON() {
|
|
21750
|
-
return {
|
|
21751
|
-
method: this.method,
|
|
21752
|
-
name: this.name,
|
|
21753
|
-
requestHeaders: sanitizeHeaders(this.requestHeaders, this.secretValues),
|
|
21754
|
-
responseBody: redactSecrets(this.responseBody, this.secretValues),
|
|
21755
|
-
status: this.status,
|
|
21756
|
-
statusText: this.statusText,
|
|
21757
|
-
url: redactSecrets(this.url, this.secretValues)
|
|
21758
|
-
};
|
|
21759
|
-
}
|
|
21760
|
-
};
|
|
21761
|
-
|
|
21762
|
-
// src/lib/bifrost-client.ts
|
|
21763
|
-
var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
|
|
21764
|
-
var BIFROST_PROXY_PATH = "/ws/proxy";
|
|
21765
|
-
var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilityBaseUrl;
|
|
21766
|
-
var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
|
|
21767
|
-
var MAX_DISCOVERED_SERVICE_PAGES = 100;
|
|
21768
|
-
var MAX_PROVIDER_SERVICE_PAGES = 100;
|
|
21769
|
-
function isExpiredAuthError(status, body) {
|
|
21770
|
-
return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
|
|
21771
|
-
}
|
|
21772
|
-
var BifrostCatalogClient = class {
|
|
21773
|
-
tokenProvider;
|
|
21774
|
-
teamId;
|
|
21775
|
-
apiKey;
|
|
21776
|
-
fetchFn;
|
|
21777
|
-
secretValues;
|
|
21778
|
-
bifrostProxyUrl;
|
|
21779
|
-
observabilityBaseUrl;
|
|
21780
|
-
observabilityEnv;
|
|
21781
|
-
constructor(options) {
|
|
21782
|
-
this.tokenProvider = options.tokenProvider ?? new AccessTokenProvider({
|
|
21783
|
-
accessToken: options.accessToken,
|
|
21784
|
-
apiKey: options.apiKey
|
|
21785
|
-
});
|
|
21786
|
-
this.teamId = options.teamId;
|
|
21787
|
-
this.apiKey = options.apiKey;
|
|
21788
|
-
this.fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
21789
|
-
this.secretValues = [this.tokenProvider.current(), options.apiKey].filter(Boolean);
|
|
21790
|
-
const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
|
|
21791
|
-
this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
|
|
21792
|
-
this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
|
|
21793
|
-
this.observabilityEnv = options.observabilityEnv || DEFAULT_OBSERVABILITY_ENV;
|
|
21794
|
-
}
|
|
21795
|
-
setApiKey(apiKey) {
|
|
21796
|
-
this.apiKey = apiKey;
|
|
21797
|
-
if (apiKey && !this.secretValues.includes(apiKey)) {
|
|
21798
|
-
this.secretValues.push(apiKey);
|
|
21799
|
-
}
|
|
21800
|
-
}
|
|
21801
|
-
registerAccessToken(token) {
|
|
21802
|
-
if (token && !this.secretValues.includes(token)) {
|
|
21803
|
-
this.secretValues.push(token);
|
|
21804
|
-
}
|
|
21860
|
+
registerAccessToken(token) {
|
|
21861
|
+
if (token && !this.secretValues.includes(token)) {
|
|
21862
|
+
this.secretValues.push(token);
|
|
21863
|
+
}
|
|
21805
21864
|
}
|
|
21806
21865
|
/**
|
|
21807
21866
|
* Build Bifrost proxy headers.
|
|
21808
|
-
* x-entity-team-id is ONLY included when teamId is present (
|
|
21809
|
-
* Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21867
|
+
* x-entity-team-id is ONLY included when teamId is present (explicit input /
|
|
21868
|
+
* POSTMAN_TEAM_ID). Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21869
|
+
* from the access token. Team is never inferred from PMAK.
|
|
21810
21870
|
*/
|
|
21811
21871
|
headers() {
|
|
21812
21872
|
const h = {
|
|
@@ -21834,7 +21894,7 @@ var BifrostCatalogClient = class {
|
|
|
21834
21894
|
mask: createSecretMasker(this.secretValues)
|
|
21835
21895
|
};
|
|
21836
21896
|
}
|
|
21837
|
-
async proxyRequest(method, path7, body = {}, operation = "api-catalog request") {
|
|
21897
|
+
async proxyRequest(method, path7, body = {}, operation = "api-catalog request", allowAuthReplay = false) {
|
|
21838
21898
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21839
21899
|
method: "POST",
|
|
21840
21900
|
headers: this.headers(),
|
|
@@ -21848,7 +21908,7 @@ var BifrostCatalogClient = class {
|
|
|
21848
21908
|
let response = await send2();
|
|
21849
21909
|
if (!response.ok) {
|
|
21850
21910
|
const bodyText = await response.text().catch(() => "");
|
|
21851
|
-
if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21911
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21852
21912
|
try {
|
|
21853
21913
|
const refreshed = await this.tokenProvider.refresh();
|
|
21854
21914
|
this.registerAccessToken(refreshed);
|
|
@@ -21899,7 +21959,7 @@ var BifrostCatalogClient = class {
|
|
|
21899
21959
|
}
|
|
21900
21960
|
return data;
|
|
21901
21961
|
}
|
|
21902
|
-
async akitaProxyRequest(method, path7, body = {}, operation = "Insights request") {
|
|
21962
|
+
async akitaProxyRequest(method, path7, body = {}, operation = "Insights request", allowAuthReplay = false) {
|
|
21903
21963
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21904
21964
|
method: "POST",
|
|
21905
21965
|
headers: this.headers(),
|
|
@@ -21913,7 +21973,7 @@ var BifrostCatalogClient = class {
|
|
|
21913
21973
|
let response = await send2();
|
|
21914
21974
|
if (!response.ok) {
|
|
21915
21975
|
const text = await response.text().catch(() => "");
|
|
21916
|
-
if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21976
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21917
21977
|
try {
|
|
21918
21978
|
const refreshed = await this.tokenProvider.refresh();
|
|
21919
21979
|
this.registerAccessToken(refreshed);
|
|
@@ -21942,6 +22002,17 @@ ${advised.message}` : advised.message : text;
|
|
|
21942
22002
|
const data = await response.json();
|
|
21943
22003
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
21944
22004
|
}
|
|
22005
|
+
throwAkitaFailure(status, errorText, operation, path7) {
|
|
22006
|
+
const httpErr = new HttpError({
|
|
22007
|
+
method: "POST",
|
|
22008
|
+
url: `bifrost:akita:${path7}`,
|
|
22009
|
+
status,
|
|
22010
|
+
statusText: status >= 500 ? "Error" : "Client Error",
|
|
22011
|
+
responseBody: errorText
|
|
22012
|
+
});
|
|
22013
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
22014
|
+
throw advised ?? httpErr;
|
|
22015
|
+
}
|
|
21945
22016
|
async listDiscoveredServices() {
|
|
21946
22017
|
return retry(
|
|
21947
22018
|
async () => {
|
|
@@ -21969,26 +22040,80 @@ ${advised.message}` : advised.message : text;
|
|
|
21969
22040
|
}
|
|
21970
22041
|
return allItems;
|
|
21971
22042
|
},
|
|
21972
|
-
|
|
22043
|
+
SAFE_READ_RETRY
|
|
21973
22044
|
);
|
|
21974
22045
|
}
|
|
21975
|
-
|
|
22046
|
+
/** List discovered + integrated services for exact link reconciliation. */
|
|
22047
|
+
async listServicesForReconcile() {
|
|
21976
22048
|
return retry(
|
|
21977
22049
|
async () => {
|
|
22050
|
+
const allItems = [];
|
|
22051
|
+
let cursor = null;
|
|
22052
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
22053
|
+
for (let page = 0; page < MAX_DISCOVERED_SERVICE_PAGES; page += 1) {
|
|
22054
|
+
const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
|
|
22055
|
+
const data = await this.proxyRequest(
|
|
22056
|
+
"GET",
|
|
22057
|
+
`/api/v1/onboarding/discovered-services${query}`,
|
|
22058
|
+
{},
|
|
22059
|
+
"service link reconciliation"
|
|
22060
|
+
);
|
|
22061
|
+
allItems.push(...data.items || []);
|
|
22062
|
+
if (data.total && allItems.length >= data.total) {
|
|
22063
|
+
break;
|
|
22064
|
+
}
|
|
22065
|
+
const nextCursor = data.nextCursor || null;
|
|
22066
|
+
if (!nextCursor || seenCursors.has(nextCursor)) {
|
|
22067
|
+
break;
|
|
22068
|
+
}
|
|
22069
|
+
seenCursors.add(nextCursor);
|
|
22070
|
+
cursor = nextCursor;
|
|
22071
|
+
}
|
|
22072
|
+
return allItems;
|
|
22073
|
+
},
|
|
22074
|
+
SAFE_READ_RETRY
|
|
22075
|
+
);
|
|
22076
|
+
}
|
|
22077
|
+
async findPreparedCollection(serviceId, workspaceId) {
|
|
22078
|
+
const services = await this.listServicesForReconcile();
|
|
22079
|
+
const match = services.find(
|
|
22080
|
+
(service) => service.id === serviceId && Boolean(service.collectionId) && service.workspaceId === workspaceId
|
|
22081
|
+
);
|
|
22082
|
+
return match?.collectionId ? String(match.collectionId) : null;
|
|
22083
|
+
}
|
|
22084
|
+
async findGitLink(params) {
|
|
22085
|
+
const services = await this.listServicesForReconcile();
|
|
22086
|
+
const match = services.find((service) => {
|
|
22087
|
+
if (service.id !== params.serviceId) return false;
|
|
22088
|
+
if (service.workspaceId !== params.workspaceId) return false;
|
|
22089
|
+
if (service.environmentId !== params.environmentId) return false;
|
|
22090
|
+
if (!service.gitRepositoryUrl) return false;
|
|
22091
|
+
return normalizeRepoUrl(service.gitRepositoryUrl) === normalizeRepoUrl(params.gitRepositoryUrl);
|
|
22092
|
+
});
|
|
22093
|
+
return match ? true : null;
|
|
22094
|
+
}
|
|
22095
|
+
async prepareCollection(serviceId, workspaceId) {
|
|
22096
|
+
return mutateOnceThenReconcile({
|
|
22097
|
+
findExisting: () => this.findPreparedCollection(serviceId, workspaceId),
|
|
22098
|
+
mutate: async () => {
|
|
21978
22099
|
const data = await this.proxyRequest(
|
|
21979
22100
|
"POST",
|
|
21980
22101
|
"/api/v1/onboarding/prepare-collection",
|
|
21981
22102
|
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21982
|
-
"collection preparation"
|
|
22103
|
+
"collection preparation",
|
|
22104
|
+
false
|
|
21983
22105
|
);
|
|
22106
|
+
if (!data?.id) {
|
|
22107
|
+
throw new Error("prepare-collection succeeded without a collection id");
|
|
22108
|
+
}
|
|
21984
22109
|
return data.id;
|
|
21985
|
-
}
|
|
21986
|
-
|
|
21987
|
-
);
|
|
22110
|
+
}
|
|
22111
|
+
});
|
|
21988
22112
|
}
|
|
21989
22113
|
async onboardGit(params) {
|
|
21990
|
-
await
|
|
21991
|
-
|
|
22114
|
+
await mutateOnceThenReconcile({
|
|
22115
|
+
findExisting: () => this.findGitLink(params),
|
|
22116
|
+
mutate: async () => {
|
|
21992
22117
|
const body = {
|
|
21993
22118
|
via_integrations: false,
|
|
21994
22119
|
git_service_name: "github",
|
|
@@ -22004,24 +22129,32 @@ ${advised.message}` : advised.message : text;
|
|
|
22004
22129
|
"POST",
|
|
22005
22130
|
"/api/v1/onboarding/git",
|
|
22006
22131
|
body,
|
|
22007
|
-
"git onboarding"
|
|
22132
|
+
"git onboarding",
|
|
22133
|
+
false
|
|
22008
22134
|
);
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
);
|
|
22135
|
+
return true;
|
|
22136
|
+
}
|
|
22137
|
+
});
|
|
22012
22138
|
}
|
|
22013
|
-
async
|
|
22139
|
+
async listProviderServices() {
|
|
22014
22140
|
const allServices = [];
|
|
22015
22141
|
let page = 1;
|
|
22016
22142
|
const pageSize = 100;
|
|
22017
22143
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
22018
22144
|
const result = await this.akitaProxyRequest(
|
|
22019
22145
|
"GET",
|
|
22020
|
-
`/v2/api-catalog/services?
|
|
22146
|
+
`/v2/api-catalog/services?populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
22021
22147
|
{},
|
|
22022
22148
|
"provider service resolution"
|
|
22023
22149
|
);
|
|
22024
|
-
if (!result.ok || !result.data)
|
|
22150
|
+
if (!result.ok || !result.data) {
|
|
22151
|
+
this.throwAkitaFailure(
|
|
22152
|
+
result.status,
|
|
22153
|
+
result.errorText,
|
|
22154
|
+
"provider service resolution",
|
|
22155
|
+
"GET /v2/api-catalog/services"
|
|
22156
|
+
);
|
|
22157
|
+
}
|
|
22025
22158
|
const services = result.data.services || [];
|
|
22026
22159
|
allServices.push(...services);
|
|
22027
22160
|
if (result.data.total && allServices.length >= result.data.total) {
|
|
@@ -22032,86 +22165,225 @@ ${advised.message}` : advised.message : text;
|
|
|
22032
22165
|
}
|
|
22033
22166
|
page++;
|
|
22034
22167
|
}
|
|
22168
|
+
return allServices;
|
|
22169
|
+
}
|
|
22170
|
+
async resolveProviderServiceId(projectName, clusterName) {
|
|
22171
|
+
const allServices = await retry(
|
|
22172
|
+
async () => this.listProviderServices(),
|
|
22173
|
+
SAFE_READ_RETRY
|
|
22174
|
+
);
|
|
22035
22175
|
if (clusterName) {
|
|
22036
22176
|
const fullName = `${clusterName}/${projectName}`;
|
|
22037
22177
|
const exactMatch = allServices.find((s) => s.name === fullName);
|
|
22038
22178
|
return exactMatch?.id || null;
|
|
22039
22179
|
}
|
|
22040
|
-
const
|
|
22180
|
+
const finalSegmentMatches = allServices.filter(
|
|
22041
22181
|
(s) => getFinalServiceSegment(s.name) === projectName
|
|
22042
22182
|
);
|
|
22043
|
-
if (
|
|
22044
|
-
|
|
22183
|
+
if (finalSegmentMatches.length > 1) {
|
|
22184
|
+
throw new Error(
|
|
22185
|
+
`Ambiguous Insights provider service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). Provide cluster-name to select the canonical service identity.`
|
|
22186
|
+
);
|
|
22187
|
+
}
|
|
22188
|
+
if (finalSegmentMatches.length === 1) return finalSegmentMatches[0].id;
|
|
22189
|
+
const bracketedMatches = allServices.filter(
|
|
22045
22190
|
(s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
|
|
22046
22191
|
);
|
|
22047
|
-
|
|
22192
|
+
if (bracketedMatches.length > 1) {
|
|
22193
|
+
throw new Error(
|
|
22194
|
+
`Ambiguous Insights provider service "${projectName}": multiple bracketed matches. Provide cluster-name to select the canonical service identity.`
|
|
22195
|
+
);
|
|
22196
|
+
}
|
|
22197
|
+
return bracketedMatches[0]?.id || null;
|
|
22198
|
+
}
|
|
22199
|
+
async findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22200
|
+
const services = await this.listProviderServices();
|
|
22201
|
+
const match = services.find(
|
|
22202
|
+
(service) => service.id === providerServiceId && service.workspace_id === workspaceId && service.system_env === systemEnvironmentId && (service.status === "onboarded" || service.status === "integrated" || Boolean(service.workspace_id))
|
|
22203
|
+
);
|
|
22204
|
+
return match ? true : null;
|
|
22048
22205
|
}
|
|
22049
22206
|
async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22050
|
-
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22207
|
+
await mutateOnceThenReconcile({
|
|
22208
|
+
findExisting: () => this.findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId),
|
|
22209
|
+
mutate: async () => {
|
|
22210
|
+
const result = await this.akitaProxyRequest(
|
|
22211
|
+
"POST",
|
|
22212
|
+
"/v2/api-catalog/services/onboard",
|
|
22213
|
+
{
|
|
22214
|
+
services: [{
|
|
22215
|
+
service_id: providerServiceId,
|
|
22216
|
+
workspace_id: workspaceId,
|
|
22217
|
+
system_env: systemEnvironmentId
|
|
22218
|
+
}]
|
|
22219
|
+
},
|
|
22220
|
+
"Insights onboarding acknowledgment",
|
|
22221
|
+
false
|
|
22222
|
+
);
|
|
22223
|
+
if (!result.ok) {
|
|
22224
|
+
this.throwAkitaFailure(
|
|
22225
|
+
result.status,
|
|
22226
|
+
result.errorText,
|
|
22227
|
+
"Insights onboarding acknowledgment",
|
|
22228
|
+
"POST /v2/api-catalog/services/onboard"
|
|
22229
|
+
);
|
|
22230
|
+
}
|
|
22231
|
+
return true;
|
|
22232
|
+
}
|
|
22233
|
+
});
|
|
22234
|
+
}
|
|
22235
|
+
async findWorkspaceAcknowledged(workspaceId) {
|
|
22236
|
+
const result = await retry(
|
|
22237
|
+
async () => {
|
|
22238
|
+
const response = await this.akitaProxyRequest(
|
|
22239
|
+
"GET",
|
|
22240
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22241
|
+
{},
|
|
22242
|
+
"workspace onboarding acknowledgment status"
|
|
22243
|
+
);
|
|
22244
|
+
if (!response.ok) {
|
|
22245
|
+
this.throwAkitaFailure(
|
|
22246
|
+
response.status,
|
|
22247
|
+
response.errorText,
|
|
22248
|
+
"workspace onboarding acknowledgment status",
|
|
22249
|
+
`GET /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22250
|
+
);
|
|
22251
|
+
}
|
|
22252
|
+
return response;
|
|
22059
22253
|
},
|
|
22060
|
-
|
|
22254
|
+
SAFE_READ_RETRY
|
|
22061
22255
|
);
|
|
22062
|
-
|
|
22063
|
-
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
22064
|
-
}
|
|
22256
|
+
return result.data?.onboarding_acknowledged ? true : null;
|
|
22065
22257
|
}
|
|
22066
22258
|
async acknowledgeWorkspace(workspaceId) {
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22259
|
+
await mutateOnceThenReconcile({
|
|
22260
|
+
findExisting: () => this.findWorkspaceAcknowledged(workspaceId),
|
|
22261
|
+
mutate: async () => {
|
|
22262
|
+
const result = await this.akitaProxyRequest(
|
|
22263
|
+
"POST",
|
|
22264
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22265
|
+
{},
|
|
22266
|
+
"workspace onboarding acknowledgment",
|
|
22267
|
+
false
|
|
22268
|
+
);
|
|
22269
|
+
if (!result.ok) {
|
|
22270
|
+
this.throwAkitaFailure(
|
|
22271
|
+
result.status,
|
|
22272
|
+
result.errorText,
|
|
22273
|
+
"workspace onboarding acknowledgment",
|
|
22274
|
+
`POST /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22275
|
+
);
|
|
22276
|
+
}
|
|
22277
|
+
return true;
|
|
22278
|
+
}
|
|
22279
|
+
});
|
|
22076
22280
|
}
|
|
22077
|
-
|
|
22078
|
-
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22079
|
-
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22080
|
-
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22081
|
-
// service-account credential, because the observability service has no
|
|
22082
|
-
// "Postman User" for a service account. The access token offers no improvement
|
|
22083
|
-
// over the API key here, so this route is not migrated to access-token-primary
|
|
22084
|
-
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22085
|
-
async createApplication(workspaceId, systemEnv) {
|
|
22281
|
+
async findApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22086
22282
|
const response = await this.fetchFn(
|
|
22087
22283
|
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22088
22284
|
{
|
|
22089
|
-
method: "
|
|
22285
|
+
method: "GET",
|
|
22090
22286
|
headers: {
|
|
22091
22287
|
"x-api-key": this.apiKey,
|
|
22092
22288
|
"x-postman-env": this.observabilityEnv,
|
|
22093
22289
|
"Content-Type": "application/json"
|
|
22094
|
-
}
|
|
22095
|
-
body: JSON.stringify({ system_env: systemEnv })
|
|
22290
|
+
}
|
|
22096
22291
|
}
|
|
22097
22292
|
);
|
|
22098
22293
|
if (!response.ok) {
|
|
22099
22294
|
const httpErr = await HttpError.fromResponse(response, {
|
|
22100
|
-
method: "
|
|
22101
|
-
url: `observability:
|
|
22295
|
+
method: "GET",
|
|
22296
|
+
url: `observability:listApplications(${workspaceId})`,
|
|
22102
22297
|
secretValues: this.secretValues
|
|
22103
22298
|
});
|
|
22104
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22299
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding lookup"));
|
|
22105
22300
|
throw advised ?? httpErr;
|
|
22106
22301
|
}
|
|
22107
|
-
|
|
22302
|
+
const data = await response.json();
|
|
22303
|
+
const matches = (data.applications || []).filter(
|
|
22304
|
+
(app) => app.application_id && app.service_id && app.system_env === systemEnv && (!expectedServiceId || app.service_id === expectedServiceId)
|
|
22305
|
+
);
|
|
22306
|
+
if (matches.length > 1) {
|
|
22307
|
+
throw new Error(
|
|
22308
|
+
`Multiple application bindings match workspace ${workspaceId}, system environment ${systemEnv}, and service ${expectedServiceId || "(unspecified)"}`
|
|
22309
|
+
);
|
|
22310
|
+
}
|
|
22311
|
+
const match = matches[0];
|
|
22312
|
+
if (!match?.application_id || !match.service_id) {
|
|
22313
|
+
return null;
|
|
22314
|
+
}
|
|
22315
|
+
return {
|
|
22316
|
+
application_id: String(match.application_id),
|
|
22317
|
+
service_id: String(match.service_id)
|
|
22318
|
+
};
|
|
22319
|
+
}
|
|
22320
|
+
// PMAK-only by proven exception. A live probe against the observability
|
|
22321
|
+
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22322
|
+
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22323
|
+
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22324
|
+
// service-account credential, because the observability service has no
|
|
22325
|
+
// "Postman User" for a service account. The access token offers no improvement
|
|
22326
|
+
// over the API key here, so this route is not migrated to access-token-primary
|
|
22327
|
+
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22328
|
+
async createApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22329
|
+
return mutateOnceThenReconcile({
|
|
22330
|
+
findExisting: () => retry(
|
|
22331
|
+
() => this.findApplication(workspaceId, systemEnv, expectedServiceId),
|
|
22332
|
+
SAFE_READ_RETRY
|
|
22333
|
+
),
|
|
22334
|
+
mutate: async () => {
|
|
22335
|
+
const response = await this.fetchFn(
|
|
22336
|
+
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22337
|
+
{
|
|
22338
|
+
method: "POST",
|
|
22339
|
+
headers: {
|
|
22340
|
+
"x-api-key": this.apiKey,
|
|
22341
|
+
"x-postman-env": this.observabilityEnv,
|
|
22342
|
+
"Content-Type": "application/json"
|
|
22343
|
+
},
|
|
22344
|
+
body: JSON.stringify({ system_env: systemEnv })
|
|
22345
|
+
}
|
|
22346
|
+
);
|
|
22347
|
+
if (!response.ok) {
|
|
22348
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
22349
|
+
method: "POST",
|
|
22350
|
+
url: `observability:createApplication(${workspaceId})`,
|
|
22351
|
+
secretValues: this.secretValues
|
|
22352
|
+
});
|
|
22353
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22354
|
+
throw advised ?? httpErr;
|
|
22355
|
+
}
|
|
22356
|
+
const created = await response.json();
|
|
22357
|
+
if (expectedServiceId && created.service_id !== expectedServiceId) {
|
|
22358
|
+
throw new Error(
|
|
22359
|
+
`Application binding credential/scope mismatch: expected service ${expectedServiceId}, received ${created.service_id}`
|
|
22360
|
+
);
|
|
22361
|
+
}
|
|
22362
|
+
return created;
|
|
22363
|
+
}
|
|
22364
|
+
});
|
|
22108
22365
|
}
|
|
22109
22366
|
async getTeamVerificationToken(workspaceId) {
|
|
22110
|
-
const result = await
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22367
|
+
const result = await retry(
|
|
22368
|
+
async () => {
|
|
22369
|
+
const page = await this.akitaProxyRequest(
|
|
22370
|
+
"GET",
|
|
22371
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22372
|
+
{},
|
|
22373
|
+
"team verification token retrieval"
|
|
22374
|
+
);
|
|
22375
|
+
if (!page.ok && (page.status === 408 || page.status === 429 || page.status >= 500)) {
|
|
22376
|
+
throw new HttpError({
|
|
22377
|
+
method: "GET",
|
|
22378
|
+
url: `bifrost:akita:GET /v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22379
|
+
status: page.status,
|
|
22380
|
+
statusText: "Error",
|
|
22381
|
+
responseBody: page.errorText
|
|
22382
|
+
});
|
|
22383
|
+
}
|
|
22384
|
+
return page;
|
|
22385
|
+
},
|
|
22386
|
+
SAFE_READ_RETRY
|
|
22115
22387
|
);
|
|
22116
22388
|
if (!result.ok || !result.data) return null;
|
|
22117
22389
|
return result.data.team_verification_token || null;
|
|
@@ -22144,24 +22416,87 @@ ${advised.message}` : advised.message : text;
|
|
|
22144
22416
|
return String(apikey.key);
|
|
22145
22417
|
}
|
|
22146
22418
|
};
|
|
22419
|
+
function buildCanonicalServiceIdentity(service, projectName, clusterName, providerServiceId) {
|
|
22420
|
+
const derivedCluster = clusterName || (service.name.includes("/") ? service.name.slice(0, service.name.lastIndexOf("/")) : null);
|
|
22421
|
+
return {
|
|
22422
|
+
serviceId: service.id,
|
|
22423
|
+
serviceName: service.name,
|
|
22424
|
+
clusterName: derivedCluster,
|
|
22425
|
+
projectName,
|
|
22426
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
22427
|
+
};
|
|
22428
|
+
}
|
|
22147
22429
|
function findDiscoveredService(services, projectName, clusterName) {
|
|
22148
22430
|
if (clusterName) {
|
|
22149
22431
|
const fullName = `${clusterName}/${projectName}`;
|
|
22150
|
-
|
|
22432
|
+
const match = services.find((s) => s.name === fullName);
|
|
22433
|
+
if (match) {
|
|
22434
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName, clusterName);
|
|
22435
|
+
}
|
|
22436
|
+
return match;
|
|
22151
22437
|
}
|
|
22152
|
-
const
|
|
22438
|
+
const finalSegmentMatches = services.filter(
|
|
22153
22439
|
(service) => getFinalServiceSegment(service.name) === projectName
|
|
22154
22440
|
);
|
|
22155
|
-
if (
|
|
22156
|
-
|
|
22441
|
+
if (finalSegmentMatches.length > 1) {
|
|
22442
|
+
throw new Error(
|
|
22443
|
+
`Ambiguous discovered service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). cluster-name is required to select the canonical service identity.`
|
|
22444
|
+
);
|
|
22445
|
+
}
|
|
22446
|
+
if (finalSegmentMatches.length === 1) {
|
|
22447
|
+
const match = finalSegmentMatches[0];
|
|
22448
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22449
|
+
return match;
|
|
22450
|
+
}
|
|
22451
|
+
const bracketedMatches = services.filter(
|
|
22157
22452
|
(service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
|
|
22158
22453
|
);
|
|
22454
|
+
if (bracketedMatches.length > 1) {
|
|
22455
|
+
throw new Error(
|
|
22456
|
+
`Ambiguous discovered service "${projectName}": multiple bracketed matches. cluster-name is required to select the canonical service identity.`
|
|
22457
|
+
);
|
|
22458
|
+
}
|
|
22459
|
+
if (bracketedMatches.length === 1) {
|
|
22460
|
+
const match = bracketedMatches[0];
|
|
22461
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22462
|
+
return match;
|
|
22463
|
+
}
|
|
22464
|
+
return void 0;
|
|
22159
22465
|
}
|
|
22160
22466
|
function getFinalServiceSegment(serviceName) {
|
|
22161
22467
|
const lastSlash = serviceName.lastIndexOf("/");
|
|
22162
22468
|
return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
|
|
22163
22469
|
}
|
|
22164
22470
|
|
|
22471
|
+
// src/lib/input.ts
|
|
22472
|
+
function normalizeInputValue(value) {
|
|
22473
|
+
return String(value ?? "").trim();
|
|
22474
|
+
}
|
|
22475
|
+
function runnerInputEnvName(name) {
|
|
22476
|
+
return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
|
|
22477
|
+
}
|
|
22478
|
+
function normalizedInputEnvName(name) {
|
|
22479
|
+
return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
|
|
22480
|
+
}
|
|
22481
|
+
function getInput2(name, env = process.env) {
|
|
22482
|
+
const normalizedName = normalizedInputEnvName(name);
|
|
22483
|
+
const runnerName = runnerInputEnvName(name);
|
|
22484
|
+
const normalizedRaw = env[normalizedName];
|
|
22485
|
+
const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
|
|
22486
|
+
const hasNormalized = normalizedRaw !== void 0;
|
|
22487
|
+
const hasRunner = runnerRaw !== void 0;
|
|
22488
|
+
if (hasNormalized && hasRunner) {
|
|
22489
|
+
const normalizedValue = normalizeInputValue(normalizedRaw);
|
|
22490
|
+
const runnerValue = normalizeInputValue(runnerRaw);
|
|
22491
|
+
if (normalizedValue !== runnerValue) {
|
|
22492
|
+
throw new Error(
|
|
22493
|
+
`Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
|
|
22494
|
+
);
|
|
22495
|
+
}
|
|
22496
|
+
}
|
|
22497
|
+
return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
|
|
22498
|
+
}
|
|
22499
|
+
|
|
22165
22500
|
// node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
|
|
22166
22501
|
function norm(value) {
|
|
22167
22502
|
const trimmed = (value ?? "").trim();
|
|
@@ -22318,7 +22653,7 @@ function normalize(value) {
|
|
|
22318
22653
|
const trimmed = (value ?? "").trim();
|
|
22319
22654
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
22320
22655
|
}
|
|
22321
|
-
function
|
|
22656
|
+
function normalizeRepoUrl2(url) {
|
|
22322
22657
|
const raw = normalize(url);
|
|
22323
22658
|
if (!raw) {
|
|
22324
22659
|
return void 0;
|
|
@@ -22386,7 +22721,7 @@ function classifyRefKind(env = process.env) {
|
|
|
22386
22721
|
return "unknown";
|
|
22387
22722
|
}
|
|
22388
22723
|
function detectRepoContext(input, env = process.env) {
|
|
22389
|
-
const repoUrl =
|
|
22724
|
+
const repoUrl = normalizeRepoUrl2(input.repoUrl) ?? normalizeRepoUrl2(env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : void 0) ?? normalizeRepoUrl2(env.CI_PROJECT_URL) ?? normalizeRepoUrl2(env.BITBUCKET_GIT_HTTP_ORIGIN) ?? normalizeRepoUrl2(env.BUILD_REPOSITORY_URI);
|
|
22390
22725
|
const repoSlug = normalize(input.repoSlug) ?? normalize(env.GITHUB_REPOSITORY) ?? normalize(env.CI_PROJECT_PATH) ?? (env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG ? normalize(`${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}`) : void 0) ?? normalize(env.BUILD_REPOSITORY_NAME);
|
|
22391
22726
|
const ref = normalize(input.ref) ?? normalize(env.GITHUB_REF_NAME) ?? normalize(env.CI_COMMIT_REF_NAME) ?? normalize(env.BITBUCKET_BRANCH) ?? normalize(env.BUILD_SOURCEBRANCHNAME);
|
|
22392
22727
|
const sha = normalize(input.sha) ?? normalize(env.GITHUB_SHA) ?? normalize(env.CI_COMMIT_SHA) ?? normalize(env.BITBUCKET_COMMIT) ?? normalize(env.BUILD_SOURCEVERSION);
|
|
@@ -22563,7 +22898,7 @@ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
|
22563
22898
|
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
22564
22899
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
22565
22900
|
function parsePreflightMode(value) {
|
|
22566
|
-
const normalized = String(value || "
|
|
22901
|
+
const normalized = String(value || "enforce").trim().toLowerCase();
|
|
22567
22902
|
if (normalized === "enforce" || normalized === "warn") {
|
|
22568
22903
|
return normalized;
|
|
22569
22904
|
}
|
|
@@ -22571,6 +22906,27 @@ function parsePreflightMode(value) {
|
|
|
22571
22906
|
`Unsupported credential-preflight "${value}". Supported values: enforce, warn`
|
|
22572
22907
|
);
|
|
22573
22908
|
}
|
|
22909
|
+
function parseServiceNotFoundPolicy(value) {
|
|
22910
|
+
const normalized = String(value || "fail").trim().toLowerCase();
|
|
22911
|
+
if (normalized === "fail" || normalized === "warn") {
|
|
22912
|
+
return normalized;
|
|
22913
|
+
}
|
|
22914
|
+
throw new Error(
|
|
22915
|
+
`Unsupported service-not-found-policy "${value}". Supported values: fail, warn`
|
|
22916
|
+
);
|
|
22917
|
+
}
|
|
22918
|
+
function parseCreateApiKey(value) {
|
|
22919
|
+
const normalized = String(value || "false").trim().toLowerCase();
|
|
22920
|
+
if (normalized === "true") {
|
|
22921
|
+
return true;
|
|
22922
|
+
}
|
|
22923
|
+
if (normalized === "false" || normalized === "") {
|
|
22924
|
+
return false;
|
|
22925
|
+
}
|
|
22926
|
+
throw new Error(
|
|
22927
|
+
`Unsupported create-api-key "${value}". Supported values: true, false`
|
|
22928
|
+
);
|
|
22929
|
+
}
|
|
22574
22930
|
function trimTrailingSlash(value) {
|
|
22575
22931
|
return value.replace(/\/+$/, "");
|
|
22576
22932
|
}
|
|
@@ -22589,29 +22945,12 @@ async function validateApiKey(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
|
|
|
22589
22945
|
const teamId = data?.user?.teamId ? String(data.user.teamId) : void 0;
|
|
22590
22946
|
return { valid: true, teamId };
|
|
22591
22947
|
}
|
|
22592
|
-
async function getTeams(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
|
|
22593
|
-
try {
|
|
22594
|
-
const res = await fetch(`${trimTrailingSlash(apiBase)}/teams`, {
|
|
22595
|
-
method: "GET",
|
|
22596
|
-
headers: { "x-api-key": apiKey }
|
|
22597
|
-
});
|
|
22598
|
-
if (!res.ok) return [];
|
|
22599
|
-
const data = await res.json();
|
|
22600
|
-
return (data?.data ?? []).filter((t) => t?.id && t?.name).map((t) => ({
|
|
22601
|
-
id: Number(t.id),
|
|
22602
|
-
name: String(t.name),
|
|
22603
|
-
...t.organizationId != null ? { organizationId: Number(t.organizationId) } : {}
|
|
22604
|
-
}));
|
|
22605
|
-
} catch {
|
|
22606
|
-
return [];
|
|
22607
|
-
}
|
|
22608
|
-
}
|
|
22609
22948
|
function clamp(value, min, max, fallback) {
|
|
22610
22949
|
const parsed = Number.isFinite(value) ? value : fallback;
|
|
22611
22950
|
return Math.min(max, Math.max(min, parsed));
|
|
22612
22951
|
}
|
|
22613
22952
|
function resolveInputs(env = process.env) {
|
|
22614
|
-
const get = (name, fallback = "") =>
|
|
22953
|
+
const get = (name, fallback = "") => getInput2(name, env) || fallback;
|
|
22615
22954
|
const projectName = get("project-name");
|
|
22616
22955
|
if (!projectName) throw new Error("project-name is required");
|
|
22617
22956
|
const postmanAccessToken = get("postman-access-token");
|
|
@@ -22652,7 +22991,9 @@ function resolveInputs(env = process.env) {
|
|
|
22652
22991
|
postmanApiKey,
|
|
22653
22992
|
postmanTeamId,
|
|
22654
22993
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
22655
|
-
credentialPreflight: parsePreflightMode(get("credential-preflight", "
|
|
22994
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "enforce")),
|
|
22995
|
+
createApiKey: parseCreateApiKey(get("create-api-key", "false")),
|
|
22996
|
+
serviceNotFoundPolicy: parseServiceNotFoundPolicy(get("service-not-found-policy", "fail")),
|
|
22656
22997
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
22657
22998
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
22658
22999
|
postmanRegion,
|
|
@@ -22668,6 +23009,7 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22668
23009
|
const timeoutMs = inputs.pollTimeoutSeconds * 1e3;
|
|
22669
23010
|
const intervalMs = inputs.pollIntervalSeconds * 1e3;
|
|
22670
23011
|
const startTime = Date.now();
|
|
23012
|
+
const policy = inputs.serviceNotFoundPolicy ?? "fail";
|
|
22671
23013
|
reporter.info(`Looking for discovered service matching "${inputs.clusterName ? `${inputs.clusterName}/` : ""}${inputs.projectName}"...`);
|
|
22672
23014
|
let match = void 0;
|
|
22673
23015
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -22682,15 +23024,35 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22682
23024
|
await sleepFn(intervalMs);
|
|
22683
23025
|
}
|
|
22684
23026
|
if (!match) {
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
23027
|
+
const message = `Service "${inputs.projectName}" not found in discovered services after ${inputs.pollTimeoutSeconds}s`;
|
|
23028
|
+
if (policy === "warn") {
|
|
23029
|
+
reporter.warning(message);
|
|
23030
|
+
return {
|
|
23031
|
+
discoveredServiceId: 0,
|
|
23032
|
+
discoveredServiceName: "",
|
|
23033
|
+
collectionId: "",
|
|
23034
|
+
applicationId: "",
|
|
23035
|
+
verificationToken: null,
|
|
23036
|
+
status: "not-found"
|
|
23037
|
+
};
|
|
23038
|
+
}
|
|
23039
|
+
throw new Error(`${message}. Full linking requires a discovered service (service-not-found-policy=fail).`);
|
|
23040
|
+
}
|
|
23041
|
+
const canonicalBase = match.canonicalIdentity ?? buildCanonicalServiceIdentity(match, inputs.projectName, inputs.clusterName || void 0);
|
|
23042
|
+
const providerServiceId = await client.resolveProviderServiceId(
|
|
23043
|
+
inputs.projectName,
|
|
23044
|
+
inputs.clusterName || void 0
|
|
23045
|
+
);
|
|
23046
|
+
if (!providerServiceId) {
|
|
23047
|
+
throw new Error(
|
|
23048
|
+
`Insights provider service "${canonicalBase.serviceName}" was not found. Full linking requires one exact canonical service identity.`
|
|
23049
|
+
);
|
|
23050
|
+
}
|
|
23051
|
+
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
23052
|
+
if (!sysEnvId) {
|
|
23053
|
+
throw new Error(
|
|
23054
|
+
`No system environment id is available for "${canonicalBase.serviceName}"; refusing partial linking writes.`
|
|
23055
|
+
);
|
|
22694
23056
|
}
|
|
22695
23057
|
reporter.info(`Preparing collection for service ${match.id} in workspace ${inputs.workspaceId}...`);
|
|
22696
23058
|
const collectionId = await client.prepareCollection(match.id, inputs.workspaceId);
|
|
@@ -22710,27 +23072,12 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22710
23072
|
} else {
|
|
22711
23073
|
reporter.info(`Skipping git onboarding for non-GitHub repo: ${repoUrl}`);
|
|
22712
23074
|
}
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
);
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
22720
|
-
if (sysEnvId) {
|
|
22721
|
-
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
22722
|
-
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
22723
|
-
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
22724
|
-
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
22725
|
-
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId);
|
|
22726
|
-
applicationId = appResult.application_id;
|
|
22727
|
-
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22728
|
-
} else {
|
|
22729
|
-
reporter.warning("No systemEnvironmentId available; skipping Insights acknowledgment and application binding");
|
|
22730
|
-
}
|
|
22731
|
-
} else {
|
|
22732
|
-
reporter.warning("Could not resolve Akita provider service ID; skipping acknowledgment and application binding");
|
|
22733
|
-
}
|
|
23075
|
+
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
23076
|
+
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
23077
|
+
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
23078
|
+
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
23079
|
+
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId, providerServiceId);
|
|
23080
|
+
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22734
23081
|
reporter.info(`Acknowledging workspace onboarding for ${inputs.workspaceId}...`);
|
|
22735
23082
|
await client.acknowledgeWorkspace(inputs.workspaceId);
|
|
22736
23083
|
reporter.info("Workspace onboarding acknowledged");
|
|
@@ -22746,9 +23093,13 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22746
23093
|
discoveredServiceId: match.id,
|
|
22747
23094
|
discoveredServiceName: match.name,
|
|
22748
23095
|
collectionId,
|
|
22749
|
-
applicationId,
|
|
23096
|
+
applicationId: appResult.application_id,
|
|
22750
23097
|
verificationToken,
|
|
22751
|
-
status: "success"
|
|
23098
|
+
status: "success",
|
|
23099
|
+
canonicalIdentity: {
|
|
23100
|
+
...canonicalBase,
|
|
23101
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
23102
|
+
}
|
|
22752
23103
|
};
|
|
22753
23104
|
}
|
|
22754
23105
|
async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
@@ -22757,6 +23108,7 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22757
23108
|
let keyValid = false;
|
|
22758
23109
|
let pmakIdentity;
|
|
22759
23110
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23111
|
+
const createApiKey = inputs.createApiKey === true;
|
|
22760
23112
|
if (apiKey) {
|
|
22761
23113
|
const result = await validateApiKey(apiKey, apiBase);
|
|
22762
23114
|
keyValid = result.valid;
|
|
@@ -22767,49 +23119,36 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22767
23119
|
}
|
|
22768
23120
|
}
|
|
22769
23121
|
if (!keyValid) {
|
|
22770
|
-
|
|
22771
|
-
|
|
23122
|
+
if (!createApiKey) {
|
|
23123
|
+
if (apiKey) {
|
|
23124
|
+
throw new Error(
|
|
23125
|
+
"postman-api-key is invalid or expired. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23126
|
+
);
|
|
23127
|
+
}
|
|
23128
|
+
throw new Error(
|
|
23129
|
+
"postman-api-key is required for application binding. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23130
|
+
);
|
|
23131
|
+
}
|
|
23132
|
+
reporter.info("create-api-key=true: generating a durable Postman API key via Bifrost identity service...");
|
|
23133
|
+
const keyName = `insights-onboarding-${inputs.projectName}`;
|
|
22772
23134
|
apiKey = await client.createApiKey(keyName);
|
|
22773
23135
|
reporter.setSecret(apiKey);
|
|
22774
23136
|
client.setApiKey(apiKey);
|
|
22775
|
-
|
|
22776
|
-
|
|
22777
|
-
|
|
22778
|
-
if (!resolvedTeamId && apiKey) {
|
|
22779
|
-
try {
|
|
22780
|
-
const teams = await getTeams(apiKey, apiBase);
|
|
22781
|
-
if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
|
|
22782
|
-
reporter.warning(
|
|
22783
|
-
"GET /teams returned multiple teams but none include organizationId. Org-mode auto-detection may be degraded due to an upstream API change. Set postman-team-id explicitly if Bifrost calls fail."
|
|
22784
|
-
);
|
|
22785
|
-
}
|
|
22786
|
-
const isOrgMode = teams.some((t) => t.organizationId != null);
|
|
22787
|
-
if (isOrgMode) {
|
|
22788
|
-
if (teams.length === 1) {
|
|
22789
|
-
resolvedTeamId = String(teams[0].id);
|
|
22790
|
-
reporter.info(
|
|
22791
|
-
`Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
|
|
22792
|
-
);
|
|
22793
|
-
} else {
|
|
22794
|
-
const meResult = await validateApiKey(apiKey, apiBase);
|
|
22795
|
-
const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
|
|
22796
|
-
if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
|
|
22797
|
-
resolvedTeamId = String(meTeamId);
|
|
22798
|
-
reporter.info(
|
|
22799
|
-
`Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
|
|
22800
|
-
);
|
|
22801
|
-
}
|
|
22802
|
-
}
|
|
22803
|
-
}
|
|
22804
|
-
} catch {
|
|
23137
|
+
const createdIdentity = await validateApiKey(apiKey, apiBase);
|
|
23138
|
+
if (!createdIdentity.valid) {
|
|
23139
|
+
throw new Error("The explicitly created postman-api-key could not be validated; refusing linking writes.");
|
|
22805
23140
|
}
|
|
23141
|
+
pmakIdentity = { source: "pmak/me", teamId: createdIdentity.teamId };
|
|
23142
|
+
reporter.info(`New API key created successfully (${keyName}).`);
|
|
22806
23143
|
}
|
|
22807
|
-
if (
|
|
22808
|
-
reporter.info(`Using postman-team-id for Bifrost headers: ${
|
|
23144
|
+
if (teamId) {
|
|
23145
|
+
reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
|
|
22809
23146
|
} else {
|
|
22810
|
-
reporter.info(
|
|
23147
|
+
reporter.info(
|
|
23148
|
+
"No postman-team-id / POSTMAN_TEAM_ID provided; omitting x-entity-team-id so Bifrost resolves team from the access token."
|
|
23149
|
+
);
|
|
22811
23150
|
}
|
|
22812
|
-
return { apiKey, teamId
|
|
23151
|
+
return { apiKey, teamId, pmakIdentity };
|
|
22813
23152
|
}
|
|
22814
23153
|
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
|
|
22815
23154
|
const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
|
|
@@ -22846,6 +23185,26 @@ function createInsightsBifrostClient(inputs, tokenProvider, teamId, apiKey) {
|
|
|
22846
23185
|
}
|
|
22847
23186
|
|
|
22848
23187
|
// src/cli.ts
|
|
23188
|
+
var INPUT_NAMES = [
|
|
23189
|
+
"project-name",
|
|
23190
|
+
"workspace-id",
|
|
23191
|
+
"environment-id",
|
|
23192
|
+
"system-environment-id",
|
|
23193
|
+
"cluster-name",
|
|
23194
|
+
"repo-url",
|
|
23195
|
+
"postman-access-token",
|
|
23196
|
+
"postman-api-key",
|
|
23197
|
+
"create-api-key",
|
|
23198
|
+
"credential-preflight",
|
|
23199
|
+
"service-not-found-policy",
|
|
23200
|
+
"postman-team-id",
|
|
23201
|
+
"github-token",
|
|
23202
|
+
"poll-timeout-seconds",
|
|
23203
|
+
"poll-interval-seconds",
|
|
23204
|
+
"postman-region",
|
|
23205
|
+
"postman-stack"
|
|
23206
|
+
];
|
|
23207
|
+
var OUTPUT_OPTION_NAMES = ["result-json", "dotenv-path"];
|
|
22849
23208
|
var ConsoleReporter = class {
|
|
22850
23209
|
secretValues = [];
|
|
22851
23210
|
info(message) {
|
|
@@ -22867,51 +23226,101 @@ var ConsoleReporter = class {
|
|
|
22867
23226
|
return masked;
|
|
22868
23227
|
}
|
|
22869
23228
|
};
|
|
22870
|
-
function
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
23229
|
+
function normalizeCliFlag(name) {
|
|
23230
|
+
return normalizedInputEnvName(name);
|
|
23231
|
+
}
|
|
23232
|
+
function resolvePackageVersion() {
|
|
23233
|
+
const candidates = [];
|
|
23234
|
+
if (typeof __filename === "string") {
|
|
23235
|
+
candidates.push(import_node_path2.default.join(import_node_path2.default.dirname(__filename), "..", "package.json"));
|
|
23236
|
+
}
|
|
23237
|
+
candidates.push(import_node_path2.default.join(process.cwd(), "package.json"));
|
|
23238
|
+
for (const candidate of candidates) {
|
|
23239
|
+
try {
|
|
23240
|
+
const packageJson = JSON.parse((0, import_node_fs2.readFileSync)(candidate, "utf8"));
|
|
23241
|
+
if (packageJson.name === "@postman-cse/onboarding-insights" && packageJson.version) {
|
|
23242
|
+
return String(packageJson.version).trim();
|
|
23243
|
+
}
|
|
23244
|
+
} catch {
|
|
22879
23245
|
}
|
|
22880
23246
|
}
|
|
22881
|
-
return
|
|
23247
|
+
return resolveActionVersion2();
|
|
22882
23248
|
}
|
|
22883
|
-
function
|
|
22884
|
-
|
|
23249
|
+
function renderHelp() {
|
|
23250
|
+
const inputFlags = INPUT_NAMES.map((name) => ` --${name} <value>`).join("\n");
|
|
23251
|
+
return [
|
|
23252
|
+
"Usage: postman-insights-onboard [options]",
|
|
23253
|
+
"",
|
|
23254
|
+
"Options:",
|
|
23255
|
+
inputFlags,
|
|
23256
|
+
" --result-json <path> Optional JSON output file (opt-in)",
|
|
23257
|
+
" --dotenv-path <path> Optional dotenv output file",
|
|
23258
|
+
" --help Show this help and exit",
|
|
23259
|
+
" --version Print version and exit",
|
|
23260
|
+
""
|
|
23261
|
+
].join("\n");
|
|
22885
23262
|
}
|
|
22886
23263
|
function parseCliArgs(argv, env = process.env) {
|
|
22887
|
-
|
|
22888
|
-
"
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
"
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
"postman-api-key",
|
|
22896
|
-
"credential-preflight",
|
|
22897
|
-
"postman-team-id",
|
|
22898
|
-
"github-token",
|
|
22899
|
-
"poll-timeout-seconds",
|
|
22900
|
-
"poll-interval-seconds",
|
|
22901
|
-
"postman-region",
|
|
22902
|
-
"postman-stack"
|
|
22903
|
-
];
|
|
23264
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
23265
|
+
return { kind: "help" };
|
|
23266
|
+
}
|
|
23267
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
23268
|
+
return { kind: "version" };
|
|
23269
|
+
}
|
|
23270
|
+
const allowed = /* @__PURE__ */ new Set([...INPUT_NAMES, ...OUTPUT_OPTION_NAMES]);
|
|
23271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22904
23272
|
const inputEnv = { ...env };
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
23273
|
+
let resultJsonPath;
|
|
23274
|
+
let dotenvPath;
|
|
23275
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
23276
|
+
const arg = argv[index];
|
|
23277
|
+
if (!arg) {
|
|
23278
|
+
continue;
|
|
23279
|
+
}
|
|
23280
|
+
if (!arg.startsWith("--")) {
|
|
23281
|
+
throw new Error(`Unexpected positional argument: ${arg}`);
|
|
23282
|
+
}
|
|
23283
|
+
const equalsIndex = arg.indexOf("=");
|
|
23284
|
+
const name = equalsIndex >= 0 ? arg.slice(2, equalsIndex) : arg.slice(2);
|
|
23285
|
+
if (!allowed.has(name)) {
|
|
23286
|
+
throw new Error(`Unknown option: --${name}`);
|
|
22909
23287
|
}
|
|
23288
|
+
if (seen.has(name)) {
|
|
23289
|
+
throw new Error(`Duplicate option: --${name}`);
|
|
23290
|
+
}
|
|
23291
|
+
let value;
|
|
23292
|
+
if (equalsIndex >= 0) {
|
|
23293
|
+
value = arg.slice(equalsIndex + 1);
|
|
23294
|
+
} else {
|
|
23295
|
+
const next = argv[index + 1];
|
|
23296
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
23297
|
+
throw new Error(`Missing value for --${name}`);
|
|
23298
|
+
}
|
|
23299
|
+
value = next;
|
|
23300
|
+
index += 1;
|
|
23301
|
+
}
|
|
23302
|
+
if (value.length === 0) {
|
|
23303
|
+
throw new Error(`Missing value for --${name}`);
|
|
23304
|
+
}
|
|
23305
|
+
seen.add(name);
|
|
23306
|
+
if (name === "result-json") {
|
|
23307
|
+
resultJsonPath = value;
|
|
23308
|
+
continue;
|
|
23309
|
+
}
|
|
23310
|
+
if (name === "dotenv-path") {
|
|
23311
|
+
dotenvPath = value;
|
|
23312
|
+
continue;
|
|
23313
|
+
}
|
|
23314
|
+
const normalizedName = normalizedInputEnvName(name);
|
|
23315
|
+
delete inputEnv[runnerInputEnvName(name)];
|
|
23316
|
+
delete inputEnv[normalizedName];
|
|
23317
|
+
inputEnv[normalizedName] = value;
|
|
22910
23318
|
}
|
|
22911
23319
|
return {
|
|
23320
|
+
kind: "run",
|
|
22912
23321
|
inputEnv,
|
|
22913
|
-
resultJsonPath
|
|
22914
|
-
dotenvPath
|
|
23322
|
+
resultJsonPath,
|
|
23323
|
+
dotenvPath
|
|
22915
23324
|
};
|
|
22916
23325
|
}
|
|
22917
23326
|
function toDotenv(outputs) {
|
|
@@ -22920,18 +23329,63 @@ function toDotenv(outputs) {
|
|
|
22920
23329
|
value
|
|
22921
23330
|
]).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
|
|
22922
23331
|
}
|
|
22923
|
-
|
|
23332
|
+
function assertWithinWorkspace(workspaceRoot, resolved, filePath) {
|
|
23333
|
+
const relative2 = import_node_path2.default.relative(workspaceRoot, resolved);
|
|
23334
|
+
if (relative2 === ".." || relative2.startsWith(`..${import_node_path2.default.sep}`) || import_node_path2.default.isAbsolute(relative2)) {
|
|
23335
|
+
throw new Error(`Output path must stay within workspace: ${filePath}`);
|
|
23336
|
+
}
|
|
23337
|
+
}
|
|
23338
|
+
async function findExistingAncestor(candidate) {
|
|
23339
|
+
let current = candidate;
|
|
23340
|
+
while (true) {
|
|
23341
|
+
try {
|
|
23342
|
+
return await (0, import_promises.realpath)(current);
|
|
23343
|
+
} catch (error2) {
|
|
23344
|
+
if (error2.code !== "ENOENT") {
|
|
23345
|
+
throw error2;
|
|
23346
|
+
}
|
|
23347
|
+
const parent = import_node_path2.default.dirname(current);
|
|
23348
|
+
if (parent === current) {
|
|
23349
|
+
throw error2;
|
|
23350
|
+
}
|
|
23351
|
+
current = parent;
|
|
23352
|
+
}
|
|
23353
|
+
}
|
|
23354
|
+
}
|
|
23355
|
+
async function validateOutputPath(filePath) {
|
|
22924
23356
|
if (!filePath) {
|
|
22925
23357
|
return;
|
|
22926
23358
|
}
|
|
22927
|
-
const workspaceRoot =
|
|
23359
|
+
const workspaceRoot = await (0, import_promises.realpath)(process.cwd());
|
|
22928
23360
|
const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
22932
|
-
|
|
23361
|
+
assertWithinWorkspace(workspaceRoot, resolved, filePath);
|
|
23362
|
+
const existingParent = await findExistingAncestor(import_node_path2.default.dirname(resolved));
|
|
23363
|
+
assertWithinWorkspace(workspaceRoot, existingParent, filePath);
|
|
23364
|
+
}
|
|
23365
|
+
async function writeAtomicFile(filePath, content) {
|
|
23366
|
+
const workspaceRoot = await (0, import_promises.realpath)(process.cwd());
|
|
23367
|
+
const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
|
|
23368
|
+
assertWithinWorkspace(workspaceRoot, resolved, filePath);
|
|
22933
23369
|
await (0, import_promises.mkdir)(import_node_path2.default.dirname(resolved), { recursive: true });
|
|
22934
|
-
await (0, import_promises.
|
|
23370
|
+
const resolvedParent = await (0, import_promises.realpath)(import_node_path2.default.dirname(resolved));
|
|
23371
|
+
assertWithinWorkspace(workspaceRoot, resolvedParent, filePath);
|
|
23372
|
+
const safeTarget = import_node_path2.default.join(resolvedParent, import_node_path2.default.basename(resolved));
|
|
23373
|
+
const tempPath = import_node_path2.default.join(
|
|
23374
|
+
resolvedParent,
|
|
23375
|
+
`.${import_node_path2.default.basename(resolved)}.${process.pid}.${(0, import_node_crypto2.randomUUID)()}.tmp`
|
|
23376
|
+
);
|
|
23377
|
+
try {
|
|
23378
|
+
await (0, import_promises.writeFile)(tempPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
23379
|
+
await (0, import_promises.rename)(tempPath, safeTarget);
|
|
23380
|
+
} finally {
|
|
23381
|
+
await (0, import_promises.rm)(tempPath, { force: true });
|
|
23382
|
+
}
|
|
23383
|
+
}
|
|
23384
|
+
async function writeOptionalFile(filePath, content) {
|
|
23385
|
+
if (!filePath) {
|
|
23386
|
+
return;
|
|
23387
|
+
}
|
|
23388
|
+
await writeAtomicFile(filePath, content);
|
|
22935
23389
|
}
|
|
22936
23390
|
function toOutputs(result) {
|
|
22937
23391
|
return {
|
|
@@ -22945,7 +23399,20 @@ function toOutputs(result) {
|
|
|
22945
23399
|
}
|
|
22946
23400
|
async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
22947
23401
|
const env = runtime.env ?? process.env;
|
|
22948
|
-
const
|
|
23402
|
+
const writeStdout = runtime.writeStdout ?? ((chunk) => process.stdout.write(chunk));
|
|
23403
|
+
const parsed = parseCliArgs(argv, env);
|
|
23404
|
+
if (parsed.kind === "help") {
|
|
23405
|
+
writeStdout(renderHelp());
|
|
23406
|
+
return;
|
|
23407
|
+
}
|
|
23408
|
+
if (parsed.kind === "version") {
|
|
23409
|
+
writeStdout(`${resolvePackageVersion()}
|
|
23410
|
+
`);
|
|
23411
|
+
return;
|
|
23412
|
+
}
|
|
23413
|
+
const config = parsed;
|
|
23414
|
+
await validateOutputPath(config.resultJsonPath);
|
|
23415
|
+
await validateOutputPath(config.dotenvPath);
|
|
22949
23416
|
const inputs = resolveInputs(config.inputEnv);
|
|
22950
23417
|
const reporter = new ConsoleReporter();
|
|
22951
23418
|
const mintHolder = {
|
|
@@ -22969,31 +23436,53 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
22969
23436
|
inputs.postmanTeamId,
|
|
22970
23437
|
inputs.postmanApiKey
|
|
22971
23438
|
);
|
|
22972
|
-
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(
|
|
22973
|
-
inputs,
|
|
22974
|
-
preliminaryClient,
|
|
22975
|
-
reporter
|
|
22976
|
-
);
|
|
22977
|
-
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
22978
|
-
accessToken: tokenProvider.current(),
|
|
22979
|
-
apiKey,
|
|
22980
|
-
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22981
|
-
onToken: (token) => reporter.setSecret(token)
|
|
22982
|
-
}) : tokenProvider;
|
|
22983
23439
|
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: reporter });
|
|
22984
|
-
telemetry.setTeamId(inputs.postmanTeamId
|
|
22985
|
-
if (apiKey) {
|
|
22986
|
-
reporter.setSecret(apiKey);
|
|
22987
|
-
}
|
|
23440
|
+
telemetry.setTeamId(inputs.postmanTeamId);
|
|
22988
23441
|
let result;
|
|
22989
23442
|
try {
|
|
23443
|
+
let preflightPmakIdentity;
|
|
23444
|
+
if (inputs.postmanApiKey) {
|
|
23445
|
+
const validated = await validateApiKey(
|
|
23446
|
+
inputs.postmanApiKey,
|
|
23447
|
+
inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE
|
|
23448
|
+
);
|
|
23449
|
+
if (validated.valid) {
|
|
23450
|
+
preflightPmakIdentity = { source: "pmak/me", teamId: validated.teamId };
|
|
23451
|
+
} else if (!inputs.createApiKey) {
|
|
23452
|
+
throw new Error(
|
|
23453
|
+
"postman-api-key is invalid or expired. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23454
|
+
);
|
|
23455
|
+
}
|
|
23456
|
+
} else if (!inputs.createApiKey) {
|
|
23457
|
+
throw new Error(
|
|
23458
|
+
"postman-api-key is required for application binding. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23459
|
+
);
|
|
23460
|
+
}
|
|
22990
23461
|
await runCredentialPreflightForInputs(
|
|
22991
23462
|
inputs,
|
|
22992
|
-
|
|
23463
|
+
preflightPmakIdentity,
|
|
22993
23464
|
reporter,
|
|
22994
23465
|
void 0,
|
|
22995
|
-
|
|
23466
|
+
tokenProvider.current()
|
|
22996
23467
|
);
|
|
23468
|
+
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, reporter);
|
|
23469
|
+
telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
|
|
23470
|
+
reporter.setSecret(apiKey);
|
|
23471
|
+
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
23472
|
+
accessToken: tokenProvider.current(),
|
|
23473
|
+
apiKey,
|
|
23474
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
23475
|
+
onToken: (token) => reporter.setSecret(token)
|
|
23476
|
+
}) : tokenProvider;
|
|
23477
|
+
if (pmakIdentity?.teamId !== preflightPmakIdentity?.teamId) {
|
|
23478
|
+
await runCredentialPreflightForInputs(
|
|
23479
|
+
inputs,
|
|
23480
|
+
pmakIdentity,
|
|
23481
|
+
reporter,
|
|
23482
|
+
void 0,
|
|
23483
|
+
activeTokenProvider.current()
|
|
23484
|
+
);
|
|
23485
|
+
}
|
|
22997
23486
|
const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
|
|
22998
23487
|
result = await (runtime.executeOnboarding ?? runOnboarding)(
|
|
22999
23488
|
inputs,
|
|
@@ -23014,7 +23503,6 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
23014
23503
|
const jsonOutput = JSON.stringify(outputs, null, 2);
|
|
23015
23504
|
await writeOptionalFile(config.resultJsonPath, jsonOutput);
|
|
23016
23505
|
await writeOptionalFile(config.dotenvPath, toDotenv(outputs));
|
|
23017
|
-
const writeStdout = runtime.writeStdout ?? ((chunk) => process.stdout.write(chunk));
|
|
23018
23506
|
writeStdout(`${jsonOutput}
|
|
23019
23507
|
`);
|
|
23020
23508
|
}
|