@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/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/secrets.ts
21629
- var REDACTED = "[REDACTED]";
21630
- var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
21631
- "authorization",
21632
- "cookie",
21633
- "proxy-authorization",
21634
- "set-cookie",
21635
- "x-access-token",
21636
- "x-api-key"
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 appendSecretValues(value, results) {
21642
- if (value === null || value === void 0) {
21643
- return;
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
- if (typeof value === "string") {
21646
- const normalized = value.trim();
21647
- if (normalized) {
21648
- results.push(normalized);
21818
+ try {
21819
+ return await options.mutate();
21820
+ } catch (error2) {
21821
+ if (!isAmbiguousMutationFailure(error2)) {
21822
+ throw error2;
21649
21823
  }
21650
- return;
21651
- }
21652
- if (typeof value === "number" || typeof value === "boolean") {
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
- function normalizeSecretValues(secretValues) {
21663
- const values = [];
21664
- appendSecretValues(secretValues, values);
21665
- return [...new Set(values)].sort((left, right) => right.length - left.length);
21666
- }
21667
- function redactSecrets(input, secretValues, replacement = REDACTED) {
21668
- const source = String(input ?? "");
21669
- const secrets = normalizeSecretValues(secretValues);
21670
- if (!source || secrets.length === 0) {
21671
- return source;
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
- return secrets.reduce((sanitized, secret) => {
21674
- if (!secret) {
21675
- return sanitized;
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
- return Object.entries(headers).map(([name, value]) => [name, String(value)]);
21691
- }
21692
- function sanitizeHeaders(headers, secretValues) {
21693
- if (!headers) {
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 (org-mode tokens).
21809
- * Non-org-mode tokens must OMIT it so Bifrost resolves team from the access token.
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
- { maxAttempts: 3, delayMs: 2e3, backoffMultiplier: 2 }
22043
+ SAFE_READ_RETRY
21973
22044
  );
21974
22045
  }
21975
- async prepareCollection(serviceId, workspaceId) {
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
- { maxAttempts: 3, delayMs: 2e3, backoffMultiplier: 2 }
21987
- );
22110
+ }
22111
+ });
21988
22112
  }
21989
22113
  async onboardGit(params) {
21990
- await retry(
21991
- async () => {
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
- { maxAttempts: 2, delayMs: 3e3 }
22011
- );
22135
+ return true;
22136
+ }
22137
+ });
22012
22138
  }
22013
- async resolveProviderServiceId(projectName, clusterName) {
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?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
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) return null;
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 finalSegmentMatch = allServices.find(
22180
+ const finalSegmentMatches = allServices.filter(
22041
22181
  (s) => getFinalServiceSegment(s.name) === projectName
22042
22182
  );
22043
- if (finalSegmentMatch) return finalSegmentMatch.id;
22044
- const bracketedMatch = allServices.find(
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
- return bracketedMatch?.id || null;
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
- const result = await this.akitaProxyRequest(
22051
- "POST",
22052
- "/v2/api-catalog/services/onboard",
22053
- {
22054
- services: [{
22055
- service_id: providerServiceId,
22056
- workspace_id: workspaceId,
22057
- system_env: systemEnvironmentId
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
- "Insights onboarding acknowledgment"
22254
+ SAFE_READ_RETRY
22061
22255
  );
22062
- if (!result.ok) {
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
- const result = await this.akitaProxyRequest(
22068
- "POST",
22069
- `/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
22070
- {},
22071
- "workspace onboarding acknowledgment"
22072
- );
22073
- if (!result.ok) {
22074
- throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
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
- // PMAK-only by proven exception. A live probe against the observability
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: "POST",
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: "POST",
22101
- url: `observability:createApplication(${workspaceId})`,
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
- return response.json();
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 this.akitaProxyRequest(
22111
- "GET",
22112
- `/v2/workspaces/${workspaceId}/team-verification-token`,
22113
- {},
22114
- "team verification token retrieval"
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
- return services.find((s) => s.name === fullName);
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 finalSegmentMatch = services.find(
22438
+ const finalSegmentMatches = services.filter(
22153
22439
  (service) => getFinalServiceSegment(service.name) === projectName
22154
22440
  );
22155
- if (finalSegmentMatch) return finalSegmentMatch;
22156
- return services.find(
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 normalizeRepoUrl(url) {
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 = normalizeRepoUrl(input.repoUrl) ?? normalizeRepoUrl(env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : void 0) ?? normalizeRepoUrl(env.CI_PROJECT_URL) ?? normalizeRepoUrl(env.BITBUCKET_GIT_HTTP_ORIGIN) ?? normalizeRepoUrl(env.BUILD_REPOSITORY_URI);
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 || "warn").trim().toLowerCase();
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 = "") => env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`]?.trim() || 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", "warn")),
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
- reporter.warning(`Service "${inputs.projectName}" not found in discovered services after ${inputs.pollTimeoutSeconds}s`);
22686
- return {
22687
- discoveredServiceId: 0,
22688
- discoveredServiceName: "",
22689
- collectionId: "",
22690
- applicationId: "",
22691
- verificationToken: null,
22692
- status: "not-found"
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
- const providerServiceId = await client.resolveProviderServiceId(
22714
- inputs.projectName,
22715
- inputs.clusterName || void 0
22716
- );
22717
- let applicationId = "";
22718
- if (providerServiceId) {
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
- reporter.info("Generating a new Postman API key via Bifrost identity service...");
22771
- const keyName = `insights-onboarding-action-${Date.now()}`;
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
- reporter.info("New API key created successfully.");
22776
- }
22777
- let resolvedTeamId = teamId;
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 (resolvedTeamId) {
22808
- reporter.info(`Using postman-team-id for Bifrost headers: ${resolvedTeamId}`);
23144
+ if (teamId) {
23145
+ reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
22809
23146
  } else {
22810
- reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
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: resolvedTeamId, pmakIdentity };
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 readFlag(argv, name) {
22871
- const prefix = `--${name}=`;
22872
- for (let index = 0; index < argv.length; index += 1) {
22873
- const arg = argv[index];
22874
- if (arg === `--${name}`) {
22875
- return argv[index + 1];
22876
- }
22877
- if (arg?.startsWith(prefix)) {
22878
- return arg.slice(prefix.length);
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 void 0;
23247
+ return resolveActionVersion2();
22882
23248
  }
22883
- function normalizeCliFlag(name) {
22884
- return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
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
- const inputNames = [
22888
- "project-name",
22889
- "workspace-id",
22890
- "environment-id",
22891
- "system-environment-id",
22892
- "cluster-name",
22893
- "repo-url",
22894
- "postman-access-token",
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
- for (const name of inputNames) {
22906
- const value = readFlag(argv, name);
22907
- if (value !== void 0) {
22908
- inputEnv[normalizeCliFlag(name)] = value;
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: readFlag(argv, "result-json") ?? "postman-insights-onboarding-result.json",
22914
- dotenvPath: readFlag(argv, "dotenv-path")
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
- async function writeOptionalFile(filePath, content) {
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 = import_node_path2.default.resolve(process.cwd());
23359
+ const workspaceRoot = await (0, import_promises.realpath)(process.cwd());
22928
23360
  const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
22929
- const relative2 = import_node_path2.default.relative(workspaceRoot, resolved);
22930
- if (relative2.startsWith("..") || import_node_path2.default.isAbsolute(relative2)) {
22931
- throw new Error(`Output path must stay within workspace: ${filePath}`);
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.writeFile)(resolved, content, "utf8");
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 config = parseCliArgs(argv, env);
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 || pmakIdentity?.teamId);
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
- pmakIdentity,
23463
+ preflightPmakIdentity,
22993
23464
  reporter,
22994
23465
  void 0,
22995
- activeTokenProvider.current()
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
  }