@postman-cse/onboarding-bootstrap 2.9.7 → 2.9.9

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/action.cjs CHANGED
@@ -258195,6 +258195,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
258195
258195
  prod: {
258196
258196
  apiBaseUrl: "https://api.getpostman.com",
258197
258197
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
258198
+ fallbackBaseUrl: "https://go.postman.co/_api",
258198
258199
  cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
258199
258200
  gatewayBaseUrl: "https://gateway.postman.com",
258200
258201
  iapubBaseUrl: "https://iapub.postman.co"
@@ -258202,6 +258203,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
258202
258203
  beta: {
258203
258204
  apiBaseUrl: "https://api.getpostman-beta.com",
258204
258205
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
258206
+ fallbackBaseUrl: "https://go.postman-beta.co/_api",
258205
258207
  cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
258206
258208
  gatewayBaseUrl: "https://gateway.postman-beta.com",
258207
258209
  iapubBaseUrl: "https://iapub.postman.co"
@@ -261015,6 +261017,13 @@ function normalizeResponseKey(status) {
261015
261017
  const raw = String(status);
261016
261018
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
261017
261019
  }
261020
+ function responseBodyExpectation(method, status, content) {
261021
+ const normalizedStatus = normalizeResponseKey(status);
261022
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
261023
+ return "forbidden";
261024
+ }
261025
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
261026
+ }
261018
261027
  function collectSecurityApiKeys(root, operation) {
261019
261028
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
261020
261029
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -261445,6 +261454,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
261445
261454
  const body2 = resolveInternalRef(root, operation.requestBody);
261446
261455
  if (!body2) return void 0;
261447
261456
  const content = asRecord5(body2.content);
261457
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
261458
+ if (asRecord5(mediaObject)?.schema === void 0) {
261459
+ warnings.push(
261460
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
261461
+ );
261462
+ }
261463
+ }
261448
261464
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
261449
261465
  if (fieldRules) {
261450
261466
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -262291,9 +262307,22 @@ function buildContractIndex(root) {
262291
262307
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
262292
262308
  const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
262293
262309
  const content = responseContent(root, version, response, responseContext, responseWarnings);
262294
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
262310
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
262311
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
262295
262312
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
262296
262313
  }
262314
+ if (bodyExpectation === "unknown") {
262315
+ responseWarnings.add(
262316
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
262317
+ );
262318
+ }
262319
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
262320
+ if (asRecord5(mediaObject)?.schema === void 0) {
262321
+ responseWarnings.add(
262322
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
262323
+ );
262324
+ }
262325
+ }
262297
262326
  for (const [contentType2, media] of Object.entries(content)) {
262298
262327
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
262299
262328
  const schemaType = asRecord5(media.schema)?.type;
@@ -262305,7 +262334,7 @@ function buildContractIndex(root) {
262305
262334
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
262306
262335
  contractResponses[normalizeResponseKey(status)] = {
262307
262336
  content,
262308
- hasBody: Object.keys(content).length > 0,
262337
+ bodyExpectation,
262309
262338
  headers,
262310
262339
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
262311
262340
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -262581,7 +262610,12 @@ function createContractScript(operation, warnings = []) {
262581
262610
  " return null;",
262582
262611
  "}",
262583
262612
  'function responseText() { return pm.response.text() || ""; }',
262584
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262613
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262614
+ "function selectedBodyExpectation() {",
262615
+ ' if (isBodyless()) return "forbidden";',
262616
+ ' if (!selected) return "unknown";',
262617
+ ' return selected.value.bodyExpectation || "unknown";',
262618
+ "}",
262585
262619
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
262586
262620
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
262587
262621
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -262621,6 +262655,7 @@ function createContractScript(operation, warnings = []) {
262621
262655
  " return matches[0];",
262622
262656
  "}",
262623
262657
  "var selected = selectedResponseContract();",
262658
+ "var bodyExpectation = selectedBodyExpectation();",
262624
262659
  ...skipped.length > 0 ? [
262625
262660
  // A schema schemasafe could not compile is NOT silently ignored: it is
262626
262661
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -262652,10 +262687,8 @@ function createContractScript(operation, warnings = []) {
262652
262687
  "});",
262653
262688
  "pm.test('Response body matches OpenAPI body contract', function () {",
262654
262689
  " if (!selected) return;",
262655
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
262656
- " var content = selected.value.content || {};",
262657
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
262658
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
262690
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
262691
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
262659
262692
  "});",
262660
262693
  "pm.test('Content-Type matches OpenAPI response content', function () {",
262661
262694
  " if (!selected || isBodyless()) return;",
@@ -263698,8 +263731,7 @@ function createContractScript(operation, warnings = []) {
263698
263731
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
263699
263732
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
263700
263733
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
263701
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
263702
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263734
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263703
263735
  "});",
263704
263736
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
263705
263737
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -265363,6 +265395,11 @@ ${error2.responseBody ?? ""}`
265363
265395
  method: "post",
265364
265396
  path: `/v3/collections/${cid}/items/`,
265365
265397
  retry: "none",
265398
+ // The transport's cold /_api fallback only fires after the primary
265399
+ // budget is exhausted; by then this loop has reconciled the item as
265400
+ // absent (match == null), so a fallback resend cannot duplicate a
265401
+ // committed create.
265402
+ fallback: "auto",
265366
265403
  headers: { "X-Entity-Type": kind },
265367
265404
  body: this.buildItemCreateBody(item, parentId)
265368
265405
  });
@@ -265740,6 +265777,7 @@ var AccessTokenGatewayClient = class {
265740
265777
  orgMode;
265741
265778
  fetchImpl;
265742
265779
  secretMasker;
265780
+ fallbackBaseUrl;
265743
265781
  maxRetries;
265744
265782
  retryBaseDelayMs;
265745
265783
  retryMaxDelayMs;
@@ -265755,6 +265793,8 @@ var AccessTokenGatewayClient = class {
265755
265793
  this.orgMode = options.orgMode ?? false;
265756
265794
  this.fetchImpl = options.fetchImpl ?? fetch;
265757
265795
  this.secretMasker = options.secretMasker ?? createSecretMasker([this.tokenProvider.current()]);
265796
+ const fallbackEnv = typeof process !== "undefined" ? process.env?.POSTMAN_ITEM_CREATE_FALLBACK : void 0;
265797
+ this.fallbackBaseUrl = fallbackEnv === "off" ? void 0 : options.fallbackBaseUrl?.replace(/\/+$/, "");
265758
265798
  this.maxRetries = options.maxRetries ?? 3;
265759
265799
  this.retryBaseDelayMs = options.retryBaseDelayMs ?? 400;
265760
265800
  this.retryMaxDelayMs = options.retryMaxDelayMs ?? 5e3;
@@ -265777,8 +265817,8 @@ var AccessTokenGatewayClient = class {
265777
265817
  }
265778
265818
  return headers;
265779
265819
  }
265780
- async send(request) {
265781
- const url = `${this.bifrostBaseUrl}/ws/proxy`;
265820
+ async send(request, baseUrl) {
265821
+ const url = `${baseUrl ?? this.bifrostBaseUrl}/ws/proxy`;
265782
265822
  const controller = new AbortController();
265783
265823
  const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
265784
265824
  try {
@@ -265808,6 +265848,50 @@ var AccessTokenGatewayClient = class {
265808
265848
  * 200 envelope carrying an inner collection-service error is treated as that
265809
265849
  * inner status. The auth-refresh-once path is independent of the retry budget.
265810
265850
  */
265851
+ /**
265852
+ * One cold, serial attempt against the fallback base URL after the primary
265853
+ * budget is exhausted on a transient failure. Never hedged in parallel with
265854
+ * the primary; only fires when the request would otherwise throw. Callers
265855
+ * with `retry: 'none'` still reconcile first — the fallback attempt here is
265856
+ * the resend, so it is only used for requests whose mutation is known
265857
+ * idempotent or already reconciled by the caller's adopt-on-ambiguous loop.
265858
+ */
265859
+ async tryFallback(request) {
265860
+ if (!this.fallbackBaseUrl) return null;
265861
+ try {
265862
+ return await this.send(request, this.fallbackBaseUrl);
265863
+ } catch {
265864
+ return null;
265865
+ }
265866
+ }
265867
+ /**
265868
+ * Run the fallback attempt and classify its response the same way the
265869
+ * primary path would. Returns the rebuilt success response, or null when the
265870
+ * fallback also failed transiently (caller then throws the original error).
265871
+ * Non-transient fallback failures (4xx, inner errors) surface as their own
265872
+ * HttpError since they are the freshest authoritative answer.
265873
+ */
265874
+ fallbackEligible(request) {
265875
+ if (!this.fallbackBaseUrl) return false;
265876
+ const retryMode = request.retry ?? (request.method === "get" ? "safe" : "none");
265877
+ return retryMode === "safe" || request.fallback === "auto";
265878
+ }
265879
+ async attemptFallback(request) {
265880
+ if (!this.fallbackEligible(request)) return null;
265881
+ const response = await this.tryFallback(request);
265882
+ if (!response) return null;
265883
+ const body2 = await response.text().catch(() => "");
265884
+ if (response.ok) {
265885
+ const innerStatus = detectInnerError(body2);
265886
+ if (innerStatus !== null) {
265887
+ if (isTransientGatewayError(innerStatus, body2)) return null;
265888
+ throw this.toInnerHttpError(request, innerStatus, body2);
265889
+ }
265890
+ return this.rebuildResponse(response, body2);
265891
+ }
265892
+ if (isTransientGatewayError(response.status, body2)) return null;
265893
+ throw this.toHttpError(request, response, body2);
265894
+ }
265811
265895
  async request(request) {
265812
265896
  if (!this.tokenProvider.current() && this.tokenProvider.canRefresh()) {
265813
265897
  await this.tokenProvider.refresh();
@@ -265825,6 +265909,8 @@ var AccessTokenGatewayClient = class {
265825
265909
  await this.sleepImpl(delay);
265826
265910
  continue;
265827
265911
  }
265912
+ const fallbackResponse2 = await this.attemptFallback(request);
265913
+ if (fallbackResponse2) return fallbackResponse2;
265828
265914
  throw error2;
265829
265915
  }
265830
265916
  if (response.ok) {
@@ -265837,6 +265923,8 @@ var AccessTokenGatewayClient = class {
265837
265923
  await this.sleepImpl(delay);
265838
265924
  continue;
265839
265925
  }
265926
+ const fallbackResponse2 = await this.attemptFallback(request);
265927
+ if (fallbackResponse2) return fallbackResponse2;
265840
265928
  throw this.toInnerHttpError(request, innerStatus, okBody);
265841
265929
  }
265842
265930
  return this.rebuildResponse(response, okBody);
@@ -265865,6 +265953,8 @@ var AccessTokenGatewayClient = class {
265865
265953
  await this.sleepImpl(delay);
265866
265954
  continue;
265867
265955
  }
265956
+ const fallbackResponse = await this.attemptFallback(request);
265957
+ if (fallbackResponse) return fallbackResponse;
265868
265958
  throw this.toHttpError(request, response, body2);
265869
265959
  }
265870
265960
  }
@@ -311990,6 +312080,7 @@ function resolveInputs(env = process.env) {
311990
312080
  postmanStack,
311991
312081
  postmanApiBase: endpointProfile.apiBaseUrl,
311992
312082
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
312083
+ postmanFallbackBase: endpointProfile.fallbackBaseUrl,
311993
312084
  postmanGatewayBase: endpointProfile.gatewayBaseUrl,
311994
312085
  postmanCliInstallUrl: endpointProfile.cliInstallUrl,
311995
312086
  postmanIapubBase: endpointProfile.iapubBaseUrl,
@@ -313744,6 +313835,7 @@ async function runAction(actionCore = core_exports, actionExec = exec_exports, a
313744
313835
  gateway: new AccessTokenGatewayClient({
313745
313836
  tokenProvider: probeProvider,
313746
313837
  bifrostBaseUrl: inputs.postmanBifrostBase,
313838
+ fallbackBaseUrl: inputs.postmanFallbackBase,
313747
313839
  teamId: inputs.teamId || "",
313748
313840
  orgMode: false,
313749
313841
  secretMasker: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken])
@@ -313866,6 +313958,7 @@ function createBootstrapDependencies(inputs, factories, orgMode = false) {
313866
313958
  const gatewayClient = inputs.postmanAccessToken || inputs.postmanApiKey ? new AccessTokenGatewayClient({
313867
313959
  tokenProvider,
313868
313960
  bifrostBaseUrl: inputs.postmanBifrostBase,
313961
+ fallbackBaseUrl: inputs.postmanFallbackBase,
313869
313962
  teamId: inputs.teamId || "",
313870
313963
  orgMode,
313871
313964
  secretMasker
package/dist/cli.cjs CHANGED
@@ -256513,6 +256513,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
256513
256513
  prod: {
256514
256514
  apiBaseUrl: "https://api.getpostman.com",
256515
256515
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
256516
+ fallbackBaseUrl: "https://go.postman.co/_api",
256516
256517
  cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
256517
256518
  gatewayBaseUrl: "https://gateway.postman.com",
256518
256519
  iapubBaseUrl: "https://iapub.postman.co"
@@ -256520,6 +256521,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
256520
256521
  beta: {
256521
256522
  apiBaseUrl: "https://api.getpostman-beta.com",
256522
256523
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
256524
+ fallbackBaseUrl: "https://go.postman-beta.co/_api",
256523
256525
  cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
256524
256526
  gatewayBaseUrl: "https://gateway.postman-beta.com",
256525
256527
  iapubBaseUrl: "https://iapub.postman.co"
@@ -259333,6 +259335,13 @@ function normalizeResponseKey(status) {
259333
259335
  const raw = String(status);
259334
259336
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
259335
259337
  }
259338
+ function responseBodyExpectation(method, status, content) {
259339
+ const normalizedStatus = normalizeResponseKey(status);
259340
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
259341
+ return "forbidden";
259342
+ }
259343
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
259344
+ }
259336
259345
  function collectSecurityApiKeys(root, operation) {
259337
259346
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
259338
259347
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -259763,6 +259772,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
259763
259772
  const body2 = resolveInternalRef(root, operation.requestBody);
259764
259773
  if (!body2) return void 0;
259765
259774
  const content = asRecord5(body2.content);
259775
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
259776
+ if (asRecord5(mediaObject)?.schema === void 0) {
259777
+ warnings.push(
259778
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
259779
+ );
259780
+ }
259781
+ }
259766
259782
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
259767
259783
  if (fieldRules) {
259768
259784
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -260609,9 +260625,22 @@ function buildContractIndex(root) {
260609
260625
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path8}`, responseWarnings, version, linkTargetSchemas);
260610
260626
  const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
260611
260627
  const content = responseContent(root, version, response, responseContext, responseWarnings);
260612
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
260628
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
260629
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
260613
260630
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path8} declares content for status ${status}, which RFC 9110 forbids on the wire`);
260614
260631
  }
260632
+ if (bodyExpectation === "unknown") {
260633
+ responseWarnings.add(
260634
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
260635
+ );
260636
+ }
260637
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
260638
+ if (asRecord5(mediaObject)?.schema === void 0) {
260639
+ responseWarnings.add(
260640
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
260641
+ );
260642
+ }
260643
+ }
260615
260644
  for (const [contentType2, media] of Object.entries(content)) {
260616
260645
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
260617
260646
  const schemaType = asRecord5(media.schema)?.type;
@@ -260623,7 +260652,7 @@ function buildContractIndex(root) {
260623
260652
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
260624
260653
  contractResponses[normalizeResponseKey(status)] = {
260625
260654
  content,
260626
- hasBody: Object.keys(content).length > 0,
260655
+ bodyExpectation,
260627
260656
  headers,
260628
260657
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
260629
260658
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -260899,7 +260928,12 @@ function createContractScript(operation, warnings = []) {
260899
260928
  " return null;",
260900
260929
  "}",
260901
260930
  'function responseText() { return pm.response.text() || ""; }',
260902
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
260931
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
260932
+ "function selectedBodyExpectation() {",
260933
+ ' if (isBodyless()) return "forbidden";',
260934
+ ' if (!selected) return "unknown";',
260935
+ ' return selected.value.bodyExpectation || "unknown";',
260936
+ "}",
260903
260937
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
260904
260938
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
260905
260939
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -260939,6 +260973,7 @@ function createContractScript(operation, warnings = []) {
260939
260973
  " return matches[0];",
260940
260974
  "}",
260941
260975
  "var selected = selectedResponseContract();",
260976
+ "var bodyExpectation = selectedBodyExpectation();",
260942
260977
  ...skipped.length > 0 ? [
260943
260978
  // A schema schemasafe could not compile is NOT silently ignored: it is
260944
260979
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -260970,10 +261005,8 @@ function createContractScript(operation, warnings = []) {
260970
261005
  "});",
260971
261006
  "pm.test('Response body matches OpenAPI body contract', function () {",
260972
261007
  " if (!selected) return;",
260973
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
260974
- " var content = selected.value.content || {};",
260975
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
260976
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
261008
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
261009
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
260977
261010
  "});",
260978
261011
  "pm.test('Content-Type matches OpenAPI response content', function () {",
260979
261012
  " if (!selected || isBodyless()) return;",
@@ -262016,8 +262049,7 @@ function createContractScript(operation, warnings = []) {
262016
262049
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
262017
262050
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
262018
262051
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
262019
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
262020
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
262052
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
262021
262053
  "});",
262022
262054
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
262023
262055
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -263681,6 +263713,11 @@ ${error.responseBody ?? ""}`
263681
263713
  method: "post",
263682
263714
  path: `/v3/collections/${cid}/items/`,
263683
263715
  retry: "none",
263716
+ // The transport's cold /_api fallback only fires after the primary
263717
+ // budget is exhausted; by then this loop has reconciled the item as
263718
+ // absent (match == null), so a fallback resend cannot duplicate a
263719
+ // committed create.
263720
+ fallback: "auto",
263684
263721
  headers: { "X-Entity-Type": kind },
263685
263722
  body: this.buildItemCreateBody(item, parentId)
263686
263723
  });
@@ -264058,6 +264095,7 @@ var AccessTokenGatewayClient = class {
264058
264095
  orgMode;
264059
264096
  fetchImpl;
264060
264097
  secretMasker;
264098
+ fallbackBaseUrl;
264061
264099
  maxRetries;
264062
264100
  retryBaseDelayMs;
264063
264101
  retryMaxDelayMs;
@@ -264073,6 +264111,8 @@ var AccessTokenGatewayClient = class {
264073
264111
  this.orgMode = options.orgMode ?? false;
264074
264112
  this.fetchImpl = options.fetchImpl ?? fetch;
264075
264113
  this.secretMasker = options.secretMasker ?? createSecretMasker([this.tokenProvider.current()]);
264114
+ const fallbackEnv = typeof process !== "undefined" ? process.env?.POSTMAN_ITEM_CREATE_FALLBACK : void 0;
264115
+ this.fallbackBaseUrl = fallbackEnv === "off" ? void 0 : options.fallbackBaseUrl?.replace(/\/+$/, "");
264076
264116
  this.maxRetries = options.maxRetries ?? 3;
264077
264117
  this.retryBaseDelayMs = options.retryBaseDelayMs ?? 400;
264078
264118
  this.retryMaxDelayMs = options.retryMaxDelayMs ?? 5e3;
@@ -264095,8 +264135,8 @@ var AccessTokenGatewayClient = class {
264095
264135
  }
264096
264136
  return headers;
264097
264137
  }
264098
- async send(request) {
264099
- const url = `${this.bifrostBaseUrl}/ws/proxy`;
264138
+ async send(request, baseUrl) {
264139
+ const url = `${baseUrl ?? this.bifrostBaseUrl}/ws/proxy`;
264100
264140
  const controller = new AbortController();
264101
264141
  const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
264102
264142
  try {
@@ -264126,6 +264166,50 @@ var AccessTokenGatewayClient = class {
264126
264166
  * 200 envelope carrying an inner collection-service error is treated as that
264127
264167
  * inner status. The auth-refresh-once path is independent of the retry budget.
264128
264168
  */
264169
+ /**
264170
+ * One cold, serial attempt against the fallback base URL after the primary
264171
+ * budget is exhausted on a transient failure. Never hedged in parallel with
264172
+ * the primary; only fires when the request would otherwise throw. Callers
264173
+ * with `retry: 'none'` still reconcile first — the fallback attempt here is
264174
+ * the resend, so it is only used for requests whose mutation is known
264175
+ * idempotent or already reconciled by the caller's adopt-on-ambiguous loop.
264176
+ */
264177
+ async tryFallback(request) {
264178
+ if (!this.fallbackBaseUrl) return null;
264179
+ try {
264180
+ return await this.send(request, this.fallbackBaseUrl);
264181
+ } catch {
264182
+ return null;
264183
+ }
264184
+ }
264185
+ /**
264186
+ * Run the fallback attempt and classify its response the same way the
264187
+ * primary path would. Returns the rebuilt success response, or null when the
264188
+ * fallback also failed transiently (caller then throws the original error).
264189
+ * Non-transient fallback failures (4xx, inner errors) surface as their own
264190
+ * HttpError since they are the freshest authoritative answer.
264191
+ */
264192
+ fallbackEligible(request) {
264193
+ if (!this.fallbackBaseUrl) return false;
264194
+ const retryMode = request.retry ?? (request.method === "get" ? "safe" : "none");
264195
+ return retryMode === "safe" || request.fallback === "auto";
264196
+ }
264197
+ async attemptFallback(request) {
264198
+ if (!this.fallbackEligible(request)) return null;
264199
+ const response = await this.tryFallback(request);
264200
+ if (!response) return null;
264201
+ const body2 = await response.text().catch(() => "");
264202
+ if (response.ok) {
264203
+ const innerStatus = detectInnerError(body2);
264204
+ if (innerStatus !== null) {
264205
+ if (isTransientGatewayError(innerStatus, body2)) return null;
264206
+ throw this.toInnerHttpError(request, innerStatus, body2);
264207
+ }
264208
+ return this.rebuildResponse(response, body2);
264209
+ }
264210
+ if (isTransientGatewayError(response.status, body2)) return null;
264211
+ throw this.toHttpError(request, response, body2);
264212
+ }
264129
264213
  async request(request) {
264130
264214
  if (!this.tokenProvider.current() && this.tokenProvider.canRefresh()) {
264131
264215
  await this.tokenProvider.refresh();
@@ -264143,6 +264227,8 @@ var AccessTokenGatewayClient = class {
264143
264227
  await this.sleepImpl(delay);
264144
264228
  continue;
264145
264229
  }
264230
+ const fallbackResponse2 = await this.attemptFallback(request);
264231
+ if (fallbackResponse2) return fallbackResponse2;
264146
264232
  throw error;
264147
264233
  }
264148
264234
  if (response.ok) {
@@ -264155,6 +264241,8 @@ var AccessTokenGatewayClient = class {
264155
264241
  await this.sleepImpl(delay);
264156
264242
  continue;
264157
264243
  }
264244
+ const fallbackResponse2 = await this.attemptFallback(request);
264245
+ if (fallbackResponse2) return fallbackResponse2;
264158
264246
  throw this.toInnerHttpError(request, innerStatus, okBody);
264159
264247
  }
264160
264248
  return this.rebuildResponse(response, okBody);
@@ -264183,6 +264271,8 @@ var AccessTokenGatewayClient = class {
264183
264271
  await this.sleepImpl(delay);
264184
264272
  continue;
264185
264273
  }
264274
+ const fallbackResponse = await this.attemptFallback(request);
264275
+ if (fallbackResponse) return fallbackResponse;
264186
264276
  throw this.toHttpError(request, response, body2);
264187
264277
  }
264188
264278
  }
@@ -310302,6 +310392,7 @@ function resolveInputs(env = process.env) {
310302
310392
  postmanStack,
310303
310393
  postmanApiBase: endpointProfile.apiBaseUrl,
310304
310394
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
310395
+ postmanFallbackBase: endpointProfile.fallbackBaseUrl,
310305
310396
  postmanGatewayBase: endpointProfile.gatewayBaseUrl,
310306
310397
  postmanCliInstallUrl: endpointProfile.cliInstallUrl,
310307
310398
  postmanIapubBase: endpointProfile.iapubBaseUrl,
@@ -312020,6 +312111,7 @@ function createBootstrapDependencies(inputs, factories, orgMode = false) {
312020
312111
  const gatewayClient = inputs.postmanAccessToken || inputs.postmanApiKey ? new AccessTokenGatewayClient({
312021
312112
  tokenProvider,
312022
312113
  bifrostBaseUrl: inputs.postmanBifrostBase,
312114
+ fallbackBaseUrl: inputs.postmanFallbackBase,
312023
312115
  teamId: inputs.teamId || "",
312024
312116
  orgMode,
312025
312117
  secretMasker
package/dist/index.cjs CHANGED
@@ -258220,6 +258220,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
258220
258220
  prod: {
258221
258221
  apiBaseUrl: "https://api.getpostman.com",
258222
258222
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
258223
+ fallbackBaseUrl: "https://go.postman.co/_api",
258223
258224
  cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
258224
258225
  gatewayBaseUrl: "https://gateway.postman.com",
258225
258226
  iapubBaseUrl: "https://iapub.postman.co"
@@ -258227,6 +258228,7 @@ var POSTMAN_ENDPOINT_PROFILES = {
258227
258228
  beta: {
258228
258229
  apiBaseUrl: "https://api.getpostman-beta.com",
258229
258230
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
258231
+ fallbackBaseUrl: "https://go.postman-beta.co/_api",
258230
258232
  cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
258231
258233
  gatewayBaseUrl: "https://gateway.postman-beta.com",
258232
258234
  iapubBaseUrl: "https://iapub.postman.co"
@@ -261040,6 +261042,13 @@ function normalizeResponseKey(status) {
261040
261042
  const raw = String(status);
261041
261043
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
261042
261044
  }
261045
+ function responseBodyExpectation(method, status, content) {
261046
+ const normalizedStatus = normalizeResponseKey(status);
261047
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
261048
+ return "forbidden";
261049
+ }
261050
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
261051
+ }
261043
261052
  function collectSecurityApiKeys(root, operation) {
261044
261053
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
261045
261054
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -261470,6 +261479,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
261470
261479
  const body2 = resolveInternalRef(root, operation.requestBody);
261471
261480
  if (!body2) return void 0;
261472
261481
  const content = asRecord5(body2.content);
261482
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
261483
+ if (asRecord5(mediaObject)?.schema === void 0) {
261484
+ warnings.push(
261485
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
261486
+ );
261487
+ }
261488
+ }
261473
261489
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
261474
261490
  if (fieldRules) {
261475
261491
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -262316,9 +262332,22 @@ function buildContractIndex(root) {
262316
262332
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
262317
262333
  const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
262318
262334
  const content = responseContent(root, version, response, responseContext, responseWarnings);
262319
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
262335
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
262336
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
262320
262337
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
262321
262338
  }
262339
+ if (bodyExpectation === "unknown") {
262340
+ responseWarnings.add(
262341
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
262342
+ );
262343
+ }
262344
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
262345
+ if (asRecord5(mediaObject)?.schema === void 0) {
262346
+ responseWarnings.add(
262347
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
262348
+ );
262349
+ }
262350
+ }
262322
262351
  for (const [contentType2, media] of Object.entries(content)) {
262323
262352
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
262324
262353
  const schemaType = asRecord5(media.schema)?.type;
@@ -262330,7 +262359,7 @@ function buildContractIndex(root) {
262330
262359
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
262331
262360
  contractResponses[normalizeResponseKey(status)] = {
262332
262361
  content,
262333
- hasBody: Object.keys(content).length > 0,
262362
+ bodyExpectation,
262334
262363
  headers,
262335
262364
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
262336
262365
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -262606,7 +262635,12 @@ function createContractScript(operation, warnings = []) {
262606
262635
  " return null;",
262607
262636
  "}",
262608
262637
  'function responseText() { return pm.response.text() || ""; }',
262609
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262638
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262639
+ "function selectedBodyExpectation() {",
262640
+ ' if (isBodyless()) return "forbidden";',
262641
+ ' if (!selected) return "unknown";',
262642
+ ' return selected.value.bodyExpectation || "unknown";',
262643
+ "}",
262610
262644
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
262611
262645
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
262612
262646
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -262646,6 +262680,7 @@ function createContractScript(operation, warnings = []) {
262646
262680
  " return matches[0];",
262647
262681
  "}",
262648
262682
  "var selected = selectedResponseContract();",
262683
+ "var bodyExpectation = selectedBodyExpectation();",
262649
262684
  ...skipped.length > 0 ? [
262650
262685
  // A schema schemasafe could not compile is NOT silently ignored: it is
262651
262686
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -262677,10 +262712,8 @@ function createContractScript(operation, warnings = []) {
262677
262712
  "});",
262678
262713
  "pm.test('Response body matches OpenAPI body contract', function () {",
262679
262714
  " if (!selected) return;",
262680
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
262681
- " var content = selected.value.content || {};",
262682
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
262683
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
262715
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
262716
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
262684
262717
  "});",
262685
262718
  "pm.test('Content-Type matches OpenAPI response content', function () {",
262686
262719
  " if (!selected || isBodyless()) return;",
@@ -263723,8 +263756,7 @@ function createContractScript(operation, warnings = []) {
263723
263756
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
263724
263757
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
263725
263758
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
263726
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
263727
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263759
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263728
263760
  "});",
263729
263761
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
263730
263762
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -265388,6 +265420,11 @@ ${error2.responseBody ?? ""}`
265388
265420
  method: "post",
265389
265421
  path: `/v3/collections/${cid}/items/`,
265390
265422
  retry: "none",
265423
+ // The transport's cold /_api fallback only fires after the primary
265424
+ // budget is exhausted; by then this loop has reconciled the item as
265425
+ // absent (match == null), so a fallback resend cannot duplicate a
265426
+ // committed create.
265427
+ fallback: "auto",
265391
265428
  headers: { "X-Entity-Type": kind },
265392
265429
  body: this.buildItemCreateBody(item, parentId)
265393
265430
  });
@@ -265765,6 +265802,7 @@ var AccessTokenGatewayClient = class {
265765
265802
  orgMode;
265766
265803
  fetchImpl;
265767
265804
  secretMasker;
265805
+ fallbackBaseUrl;
265768
265806
  maxRetries;
265769
265807
  retryBaseDelayMs;
265770
265808
  retryMaxDelayMs;
@@ -265780,6 +265818,8 @@ var AccessTokenGatewayClient = class {
265780
265818
  this.orgMode = options.orgMode ?? false;
265781
265819
  this.fetchImpl = options.fetchImpl ?? fetch;
265782
265820
  this.secretMasker = options.secretMasker ?? createSecretMasker([this.tokenProvider.current()]);
265821
+ const fallbackEnv = typeof process !== "undefined" ? process.env?.POSTMAN_ITEM_CREATE_FALLBACK : void 0;
265822
+ this.fallbackBaseUrl = fallbackEnv === "off" ? void 0 : options.fallbackBaseUrl?.replace(/\/+$/, "");
265783
265823
  this.maxRetries = options.maxRetries ?? 3;
265784
265824
  this.retryBaseDelayMs = options.retryBaseDelayMs ?? 400;
265785
265825
  this.retryMaxDelayMs = options.retryMaxDelayMs ?? 5e3;
@@ -265802,8 +265842,8 @@ var AccessTokenGatewayClient = class {
265802
265842
  }
265803
265843
  return headers;
265804
265844
  }
265805
- async send(request) {
265806
- const url = `${this.bifrostBaseUrl}/ws/proxy`;
265845
+ async send(request, baseUrl) {
265846
+ const url = `${baseUrl ?? this.bifrostBaseUrl}/ws/proxy`;
265807
265847
  const controller = new AbortController();
265808
265848
  const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
265809
265849
  try {
@@ -265833,6 +265873,50 @@ var AccessTokenGatewayClient = class {
265833
265873
  * 200 envelope carrying an inner collection-service error is treated as that
265834
265874
  * inner status. The auth-refresh-once path is independent of the retry budget.
265835
265875
  */
265876
+ /**
265877
+ * One cold, serial attempt against the fallback base URL after the primary
265878
+ * budget is exhausted on a transient failure. Never hedged in parallel with
265879
+ * the primary; only fires when the request would otherwise throw. Callers
265880
+ * with `retry: 'none'` still reconcile first — the fallback attempt here is
265881
+ * the resend, so it is only used for requests whose mutation is known
265882
+ * idempotent or already reconciled by the caller's adopt-on-ambiguous loop.
265883
+ */
265884
+ async tryFallback(request) {
265885
+ if (!this.fallbackBaseUrl) return null;
265886
+ try {
265887
+ return await this.send(request, this.fallbackBaseUrl);
265888
+ } catch {
265889
+ return null;
265890
+ }
265891
+ }
265892
+ /**
265893
+ * Run the fallback attempt and classify its response the same way the
265894
+ * primary path would. Returns the rebuilt success response, or null when the
265895
+ * fallback also failed transiently (caller then throws the original error).
265896
+ * Non-transient fallback failures (4xx, inner errors) surface as their own
265897
+ * HttpError since they are the freshest authoritative answer.
265898
+ */
265899
+ fallbackEligible(request) {
265900
+ if (!this.fallbackBaseUrl) return false;
265901
+ const retryMode = request.retry ?? (request.method === "get" ? "safe" : "none");
265902
+ return retryMode === "safe" || request.fallback === "auto";
265903
+ }
265904
+ async attemptFallback(request) {
265905
+ if (!this.fallbackEligible(request)) return null;
265906
+ const response = await this.tryFallback(request);
265907
+ if (!response) return null;
265908
+ const body2 = await response.text().catch(() => "");
265909
+ if (response.ok) {
265910
+ const innerStatus = detectInnerError(body2);
265911
+ if (innerStatus !== null) {
265912
+ if (isTransientGatewayError(innerStatus, body2)) return null;
265913
+ throw this.toInnerHttpError(request, innerStatus, body2);
265914
+ }
265915
+ return this.rebuildResponse(response, body2);
265916
+ }
265917
+ if (isTransientGatewayError(response.status, body2)) return null;
265918
+ throw this.toHttpError(request, response, body2);
265919
+ }
265836
265920
  async request(request) {
265837
265921
  if (!this.tokenProvider.current() && this.tokenProvider.canRefresh()) {
265838
265922
  await this.tokenProvider.refresh();
@@ -265850,6 +265934,8 @@ var AccessTokenGatewayClient = class {
265850
265934
  await this.sleepImpl(delay);
265851
265935
  continue;
265852
265936
  }
265937
+ const fallbackResponse2 = await this.attemptFallback(request);
265938
+ if (fallbackResponse2) return fallbackResponse2;
265853
265939
  throw error2;
265854
265940
  }
265855
265941
  if (response.ok) {
@@ -265862,6 +265948,8 @@ var AccessTokenGatewayClient = class {
265862
265948
  await this.sleepImpl(delay);
265863
265949
  continue;
265864
265950
  }
265951
+ const fallbackResponse2 = await this.attemptFallback(request);
265952
+ if (fallbackResponse2) return fallbackResponse2;
265865
265953
  throw this.toInnerHttpError(request, innerStatus, okBody);
265866
265954
  }
265867
265955
  return this.rebuildResponse(response, okBody);
@@ -265890,6 +265978,8 @@ var AccessTokenGatewayClient = class {
265890
265978
  await this.sleepImpl(delay);
265891
265979
  continue;
265892
265980
  }
265981
+ const fallbackResponse = await this.attemptFallback(request);
265982
+ if (fallbackResponse) return fallbackResponse;
265893
265983
  throw this.toHttpError(request, response, body2);
265894
265984
  }
265895
265985
  }
@@ -312015,6 +312105,7 @@ function resolveInputs(env = process.env) {
312015
312105
  postmanStack,
312016
312106
  postmanApiBase: endpointProfile.apiBaseUrl,
312017
312107
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
312108
+ postmanFallbackBase: endpointProfile.fallbackBaseUrl,
312018
312109
  postmanGatewayBase: endpointProfile.gatewayBaseUrl,
312019
312110
  postmanCliInstallUrl: endpointProfile.cliInstallUrl,
312020
312111
  postmanIapubBase: endpointProfile.iapubBaseUrl,
@@ -313772,6 +313863,7 @@ async function runAction(actionCore = core_exports, actionExec = exec_exports, a
313772
313863
  gateway: new AccessTokenGatewayClient({
313773
313864
  tokenProvider: probeProvider,
313774
313865
  bifrostBaseUrl: inputs.postmanBifrostBase,
313866
+ fallbackBaseUrl: inputs.postmanFallbackBase,
313775
313867
  teamId: inputs.teamId || "",
313776
313868
  orgMode: false,
313777
313869
  secretMasker: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken])
@@ -313894,6 +313986,7 @@ function createBootstrapDependencies(inputs, factories, orgMode = false) {
313894
313986
  const gatewayClient = inputs.postmanAccessToken || inputs.postmanApiKey ? new AccessTokenGatewayClient({
313895
313987
  tokenProvider,
313896
313988
  bifrostBaseUrl: inputs.postmanBifrostBase,
313989
+ fallbackBaseUrl: inputs.postmanFallbackBase,
313897
313990
  teamId: inputs.teamId || "",
313898
313991
  orgMode,
313899
313992
  secretMasker
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-bootstrap",
3
- "version": "2.9.7",
3
+ "version": "2.9.9",
4
4
  "description": "Bootstrap Postman workspaces, specs, and collections from OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",