@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 +104 -11
- package/dist/cli.cjs +103 -11
- package/dist/index.cjs +104 -11
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262656
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260974
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262681
|
-
|
|
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
|
-
|
|
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
|