@postman-cse/onboarding-bootstrap 2.9.4 → 2.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/action.yml +24 -0
- package/dist/action.cjs +1123 -154
- package/dist/cli.cjs +1145 -164
- package/dist/index.cjs +1140 -156
- package/package.json +1 -1
package/dist/action.cjs
CHANGED
|
@@ -99989,11 +99989,11 @@ var require_valid = __commonJS({
|
|
|
99989
99989
|
var require_clean = __commonJS({
|
|
99990
99990
|
"node_modules/postman-collection/node_modules/semver/functions/clean.js"(exports2, module2) {
|
|
99991
99991
|
var parse10 = require_parse6();
|
|
99992
|
-
var
|
|
99992
|
+
var clean2 = (version, options) => {
|
|
99993
99993
|
const s = parse10(version.trim().replace(/^[=v]+/, ""), options);
|
|
99994
99994
|
return s ? s.version : null;
|
|
99995
99995
|
};
|
|
99996
|
-
module2.exports =
|
|
99996
|
+
module2.exports = clean2;
|
|
99997
99997
|
}
|
|
99998
99998
|
});
|
|
99999
99999
|
|
|
@@ -101304,7 +101304,7 @@ var require_semver2 = __commonJS({
|
|
|
101304
101304
|
var identifiers = require_identifiers();
|
|
101305
101305
|
var parse10 = require_parse6();
|
|
101306
101306
|
var valid = require_valid();
|
|
101307
|
-
var
|
|
101307
|
+
var clean2 = require_clean();
|
|
101308
101308
|
var inc = require_inc();
|
|
101309
101309
|
var diff = require_diff();
|
|
101310
101310
|
var major = require_major();
|
|
@@ -101342,7 +101342,7 @@ var require_semver2 = __commonJS({
|
|
|
101342
101342
|
module2.exports = {
|
|
101343
101343
|
parse: parse10,
|
|
101344
101344
|
valid,
|
|
101345
|
-
clean,
|
|
101345
|
+
clean: clean2,
|
|
101346
101346
|
inc,
|
|
101347
101347
|
diff,
|
|
101348
101348
|
major,
|
|
@@ -256197,8 +256197,8 @@ function getIDToken(aud) {
|
|
|
256197
256197
|
}
|
|
256198
256198
|
|
|
256199
256199
|
// src/index.ts
|
|
256200
|
-
var
|
|
256201
|
-
var
|
|
256200
|
+
var import_node_crypto6 = require("node:crypto");
|
|
256201
|
+
var import_node_fs6 = require("node:fs");
|
|
256202
256202
|
var path9 = __toESM(require("node:path"), 1);
|
|
256203
256203
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
256204
256204
|
|
|
@@ -256306,6 +256306,12 @@ var bootstrapActionContract = {
|
|
|
256306
256306
|
default: "",
|
|
256307
256307
|
allowedValues: ["3.0", "3.1"]
|
|
256308
256308
|
},
|
|
256309
|
+
"preserve-oas30-type-null": {
|
|
256310
|
+
description: "Opt-in compatibility mode for legacy OpenAPI 3.0 oneOf schemas that pair a normal schema with type: null. The original source bytes are uploaded unchanged while an internal nullable view is used for validation and generated artifacts.",
|
|
256311
|
+
required: false,
|
|
256312
|
+
default: "false",
|
|
256313
|
+
allowedValues: ["true", "false"]
|
|
256314
|
+
},
|
|
256309
256315
|
"breaking-change-mode": {
|
|
256310
256316
|
description: "OpenAPI breaking-change comparison mode.",
|
|
256311
256317
|
required: false,
|
|
@@ -256362,6 +256368,20 @@ var bootstrapActionContract = {
|
|
|
256362
256368
|
default: "warn",
|
|
256363
256369
|
allowedValues: ["enforce", "warn"]
|
|
256364
256370
|
},
|
|
256371
|
+
"branch-strategy": {
|
|
256372
|
+
description: "Branch-aware sync strategy. legacy (default) keeps branch-blind behavior; publish-gate restricts canonical writes to the canonical branch and runs credential-free static validation on other branches; preview additionally maintains suffixed per-branch preview asset sets.",
|
|
256373
|
+
required: false,
|
|
256374
|
+
default: "legacy",
|
|
256375
|
+
allowedValues: ["legacy", "preview", "publish-gate"]
|
|
256376
|
+
},
|
|
256377
|
+
"canonical-branch": {
|
|
256378
|
+
description: "Explicit canonical branch (the sole writer of canonical assets). Defaults to the provider-resolved default branch; required on providers without a default-branch variable (Bitbucket, Azure DevOps) when branch-strategy is not legacy.",
|
|
256379
|
+
required: false
|
|
256380
|
+
},
|
|
256381
|
+
"channels": {
|
|
256382
|
+
description: 'Comma-separated channel map for long-lived promotion branches, e.g. "develop=DEV, staging=STAGE, release/*=RC". Channel branches maintain prefix-named parallel asset sets and never mutate canonical assets.',
|
|
256383
|
+
required: false
|
|
256384
|
+
},
|
|
256365
256385
|
"integration-backend": {
|
|
256366
256386
|
description: "Integration backend for downstream workspace connectivity.",
|
|
256367
256387
|
required: false,
|
|
@@ -256431,6 +256451,21 @@ var bootstrapActionContract = {
|
|
|
256431
256451
|
},
|
|
256432
256452
|
"breaking-change-summary-json": {
|
|
256433
256453
|
description: "JSON summary of the OpenAPI breaking-change check."
|
|
256454
|
+
},
|
|
256455
|
+
"sync-status": {
|
|
256456
|
+
description: "Branch-aware sync status: synced (canonical/channel/preview write ran), skipped-branch-gate (gated run, credential-free validation only), or empty under branch-strategy legacy."
|
|
256457
|
+
},
|
|
256458
|
+
"branch-decision": {
|
|
256459
|
+
description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
|
|
256460
|
+
},
|
|
256461
|
+
"spec-version-tag": {
|
|
256462
|
+
description: "Native Spec Hub version tag applied on this canonical publish (tag-per-publish), empty when tagging was skipped (no-op sync, non-canonical run, or legacy client)."
|
|
256463
|
+
},
|
|
256464
|
+
"spec-version-url": {
|
|
256465
|
+
description: "Reserved for the repo-sync finalizer; bootstrap does not tag before complete onboarding."
|
|
256466
|
+
},
|
|
256467
|
+
"spec-content-changed": {
|
|
256468
|
+
description: "Whether bootstrap changed canonical spec content; repo-sync uses this to skip native version tags on no-op syncs."
|
|
256434
256469
|
}
|
|
256435
256470
|
},
|
|
256436
256471
|
retainedBehavior: [
|
|
@@ -257557,6 +257592,15 @@ function assertSupportedLocalViewContract(node, options) {
|
|
|
257557
257592
|
}
|
|
257558
257593
|
|
|
257559
257594
|
// src/lib/postman/additional-collections.ts
|
|
257595
|
+
var RESOURCES_STATE_VERSION = 2;
|
|
257596
|
+
var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
|
|
257597
|
+
var StateUnreadableError = class extends Error {
|
|
257598
|
+
code = "CONTRACT_STATE_UNREADABLE";
|
|
257599
|
+
constructor(message) {
|
|
257600
|
+
super(`CONTRACT_STATE_UNREADABLE: ${message}`);
|
|
257601
|
+
this.name = "StateUnreadableError";
|
|
257602
|
+
}
|
|
257603
|
+
};
|
|
257560
257604
|
var ADDITIONAL_COLLECTION_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".yaml", ".yml"]);
|
|
257561
257605
|
var POSTMAN_COLLECTION_V21_SCHEMA_FRAGMENT = "/collection/v2.1.0/collection.json";
|
|
257562
257606
|
var V3_DEFINITION_PATH = import_node_path2.default.join(".resources", "definition.yaml");
|
|
@@ -257612,16 +257656,46 @@ function resourcesStatePath() {
|
|
|
257612
257656
|
return import_node_path2.default.join(workspaceRootForLocalInputs(), RESOURCES_PATH);
|
|
257613
257657
|
}
|
|
257614
257658
|
function readResourcesState() {
|
|
257659
|
+
let raw;
|
|
257615
257660
|
try {
|
|
257616
|
-
|
|
257661
|
+
raw = (0, import_node_fs2.readFileSync)(resourcesStatePath(), "utf8");
|
|
257617
257662
|
} catch {
|
|
257618
257663
|
return null;
|
|
257619
257664
|
}
|
|
257665
|
+
let parsed;
|
|
257666
|
+
try {
|
|
257667
|
+
parsed = (0, import_yaml.parse)(raw);
|
|
257668
|
+
} catch (error2) {
|
|
257669
|
+
throw new StateUnreadableError(
|
|
257670
|
+
`.postman/resources.yaml exists but is not parseable YAML (${error2 instanceof Error ? error2.message : String(error2)}). Fix or delete the file; refusing to treat tracked state as absent.`
|
|
257671
|
+
);
|
|
257672
|
+
}
|
|
257673
|
+
if (parsed === null || parsed === void 0) {
|
|
257674
|
+
return null;
|
|
257675
|
+
}
|
|
257676
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
257677
|
+
throw new StateUnreadableError(
|
|
257678
|
+
".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
|
|
257679
|
+
);
|
|
257680
|
+
}
|
|
257681
|
+
const state = parsed;
|
|
257682
|
+
if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
|
|
257683
|
+
throw new StateUnreadableError(
|
|
257684
|
+
`.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
|
|
257685
|
+
);
|
|
257686
|
+
}
|
|
257687
|
+
if (state.canonical && !state.cloudResources) {
|
|
257688
|
+
state.cloudResources = { ...state.canonical };
|
|
257689
|
+
}
|
|
257690
|
+
return state;
|
|
257620
257691
|
}
|
|
257621
257692
|
function writeResourcesState(state) {
|
|
257622
257693
|
const target = resourcesStatePath();
|
|
257623
257694
|
(0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(target), { recursive: true });
|
|
257624
|
-
|
|
257695
|
+
const canonical = state.cloudResources ?? state.canonical ?? {};
|
|
257696
|
+
const persisted = { ...state, version: RESOURCES_STATE_VERSION, canonical };
|
|
257697
|
+
delete persisted.cloudResources;
|
|
257698
|
+
(0, import_node_fs2.writeFileSync)(target, (0, import_yaml.stringify)(persisted), "utf8");
|
|
257625
257699
|
}
|
|
257626
257700
|
function findCloudResourceId(map, matcher) {
|
|
257627
257701
|
if (!map) {
|
|
@@ -257631,7 +257705,7 @@ function findCloudResourceId(map, matcher) {
|
|
|
257631
257705
|
return match?.[1];
|
|
257632
257706
|
}
|
|
257633
257707
|
function findExistingAdditionalCollectionId(resourcesState, resourcePath) {
|
|
257634
|
-
return resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
|
|
257708
|
+
return resourcesState?.canonical?.additionalCollections?.[resourcePath] ?? resourcesState?.canonical?.collections?.[resourcePath] ?? resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
|
|
257635
257709
|
}
|
|
257636
257710
|
function resolveAdditionalCollectionsDir(directoryInput) {
|
|
257637
257711
|
const workspaceRoot = workspaceRootForLocalInputs();
|
|
@@ -258633,6 +258707,8 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258633
258707
|
* endpoint aborts rather than blocking the run forever. */
|
|
258634
258708
|
static REQUEST_TIMEOUT_MS = 3e4;
|
|
258635
258709
|
static POSTMAN_APP_VERSION_URL = `https://dl.pstmn.io/update/status?currentVersion=${_BifrostInternalIntegrationAdapter.MINIMUM_POSTMAN_APP_VERSION}&platform=osx_arm64`;
|
|
258710
|
+
/** Concurrent dual-trigger previews share one spec; peer sync holds a 423 lock. */
|
|
258711
|
+
static SYNC_LOCKED_MAX_RETRIES = 6;
|
|
258636
258712
|
accessToken;
|
|
258637
258713
|
tokenProvider;
|
|
258638
258714
|
appVersionPromise;
|
|
@@ -258641,6 +258717,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258641
258717
|
gatewayBaseUrl;
|
|
258642
258718
|
orgMode;
|
|
258643
258719
|
secretMasker;
|
|
258720
|
+
sleep;
|
|
258644
258721
|
teamId;
|
|
258645
258722
|
constructor(options) {
|
|
258646
258723
|
this.accessToken = String(options.accessToken || "").trim();
|
|
@@ -258654,6 +258731,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258654
258731
|
).replace(/\/+$/, "");
|
|
258655
258732
|
this.orgMode = options.orgMode ?? false;
|
|
258656
258733
|
this.secretMasker = options.secretMasker ?? createSecretMasker([this.accessToken]);
|
|
258734
|
+
this.sleep = options.sleep ?? ((delayMs) => new Promise((resolve5) => setTimeout(resolve5, delayMs)));
|
|
258657
258735
|
this.teamId = String(options.teamId || "").trim();
|
|
258658
258736
|
}
|
|
258659
258737
|
configureTeamContext(teamId, orgMode) {
|
|
@@ -258860,55 +258938,72 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258860
258938
|
if (collections.length === 0) {
|
|
258861
258939
|
return;
|
|
258862
258940
|
}
|
|
258863
|
-
const
|
|
258864
|
-
|
|
258865
|
-
|
|
258866
|
-
|
|
258867
|
-
|
|
258868
|
-
|
|
258869
|
-
|
|
258870
|
-
|
|
258871
|
-
|
|
258872
|
-
|
|
258873
|
-
|
|
258941
|
+
const body2 = collections.map((collection) => ({
|
|
258942
|
+
collectionId: collection.collectionId,
|
|
258943
|
+
...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
|
|
258944
|
+
}));
|
|
258945
|
+
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
258946
|
+
const response = await this.proxyRequest(
|
|
258947
|
+
"specification",
|
|
258948
|
+
"put",
|
|
258949
|
+
`/specifications/${specificationId}/collections`,
|
|
258950
|
+
body2
|
|
258951
|
+
);
|
|
258952
|
+
if (response.ok) {
|
|
258953
|
+
return;
|
|
258954
|
+
}
|
|
258955
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
258956
|
+
method: "POST",
|
|
258957
|
+
requestHeaders: {
|
|
258958
|
+
"Content-Type": "application/json",
|
|
258959
|
+
"x-access-token": this.currentToken(),
|
|
258960
|
+
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258961
|
+
},
|
|
258962
|
+
secretValues: [this.currentToken()],
|
|
258963
|
+
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258964
|
+
});
|
|
258965
|
+
if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
|
|
258966
|
+
await this.sleep(2e3 * Math.pow(2, lockedAttempt));
|
|
258967
|
+
continue;
|
|
258968
|
+
}
|
|
258969
|
+
const advised = adviseFromHttpError(
|
|
258970
|
+
httpErr,
|
|
258971
|
+
this.adviceContext("collection-to-specification linking")
|
|
258972
|
+
);
|
|
258973
|
+
throw advised ?? httpErr;
|
|
258874
258974
|
}
|
|
258875
|
-
const httpErr = await HttpError.fromResponse(response, {
|
|
258876
|
-
method: "POST",
|
|
258877
|
-
requestHeaders: {
|
|
258878
|
-
"Content-Type": "application/json",
|
|
258879
|
-
"x-access-token": this.currentToken(),
|
|
258880
|
-
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258881
|
-
},
|
|
258882
|
-
secretValues: [this.currentToken()],
|
|
258883
|
-
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258884
|
-
});
|
|
258885
|
-
const advised = adviseFromHttpError(
|
|
258886
|
-
httpErr,
|
|
258887
|
-
this.adviceContext("collection-to-specification linking")
|
|
258888
|
-
);
|
|
258889
|
-
throw advised ?? httpErr;
|
|
258890
258975
|
}
|
|
258891
258976
|
async syncCollection(specificationId, collectionId) {
|
|
258892
|
-
|
|
258893
|
-
|
|
258894
|
-
|
|
258895
|
-
|
|
258896
|
-
|
|
258897
|
-
|
|
258898
|
-
|
|
258977
|
+
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
258978
|
+
const response = await this.proxyRequest(
|
|
258979
|
+
"specification",
|
|
258980
|
+
"post",
|
|
258981
|
+
`/specifications/${specificationId}/collections/${collectionId}/sync`
|
|
258982
|
+
);
|
|
258983
|
+
if (response.ok) {
|
|
258984
|
+
return;
|
|
258985
|
+
}
|
|
258986
|
+
const bodyText = await response.clone().text().catch(() => "");
|
|
258987
|
+
if (response.status === 400 && /already in sync/i.test(bodyText)) {
|
|
258988
|
+
return;
|
|
258989
|
+
}
|
|
258990
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
258991
|
+
method: "POST",
|
|
258992
|
+
requestHeaders: {
|
|
258993
|
+
"Content-Type": "application/json",
|
|
258994
|
+
"x-access-token": this.currentToken(),
|
|
258995
|
+
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258996
|
+
},
|
|
258997
|
+
secretValues: [this.currentToken()],
|
|
258998
|
+
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258999
|
+
});
|
|
259000
|
+
if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
|
|
259001
|
+
await this.sleep(2e3 * Math.pow(2, lockedAttempt));
|
|
259002
|
+
continue;
|
|
259003
|
+
}
|
|
259004
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
|
|
259005
|
+
throw advised ?? httpErr;
|
|
258899
259006
|
}
|
|
258900
|
-
const httpErr = await HttpError.fromResponse(response, {
|
|
258901
|
-
method: "POST",
|
|
258902
|
-
requestHeaders: {
|
|
258903
|
-
"Content-Type": "application/json",
|
|
258904
|
-
"x-access-token": this.currentToken(),
|
|
258905
|
-
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258906
|
-
},
|
|
258907
|
-
secretValues: [this.currentToken()],
|
|
258908
|
-
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258909
|
-
});
|
|
258910
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
|
|
258911
|
-
throw advised ?? httpErr;
|
|
258912
259007
|
}
|
|
258913
259008
|
async getWorkspaceGitRepoUrl(workspaceId) {
|
|
258914
259009
|
const response = await this.proxyRequest(
|
|
@@ -264053,6 +264148,16 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264053
264148
|
if (openapiVersion !== "3.0" && openapiVersion !== "3.1") {
|
|
264054
264149
|
throw new Error(`uploadSpec: unsupported openapiVersion "${openapiVersion}". Expected '3.0' or '3.1'.`);
|
|
264055
264150
|
}
|
|
264151
|
+
const before = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264152
|
+
const existing = adoptExactMatch(
|
|
264153
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264154
|
+
before,
|
|
264155
|
+
(entry) => entry.id
|
|
264156
|
+
);
|
|
264157
|
+
if (existing) {
|
|
264158
|
+
await this.updateSpec(existing.id, specContent, workspaceId);
|
|
264159
|
+
return existing.id;
|
|
264160
|
+
}
|
|
264056
264161
|
const specType = openapiVersion === "3.1" ? "OPENAPI:3.1" : "OPENAPI:3.0";
|
|
264057
264162
|
let created;
|
|
264058
264163
|
try {
|
|
@@ -264077,10 +264182,49 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264077
264182
|
if (!match) throw error2;
|
|
264078
264183
|
created = { data: { id: match.id } };
|
|
264079
264184
|
}
|
|
264080
|
-
|
|
264185
|
+
let specId = String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim();
|
|
264081
264186
|
if (!specId) {
|
|
264082
264187
|
throw new Error("Spec upload did not return an ID");
|
|
264083
264188
|
}
|
|
264189
|
+
let matches = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264190
|
+
if (before.length === 0 && matches.length <= 1) {
|
|
264191
|
+
await this.sleep(1e3);
|
|
264192
|
+
const settled = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264193
|
+
if (settled.length > 0) matches = settled;
|
|
264194
|
+
}
|
|
264195
|
+
if (matches.length > 1 && before.length === 0) {
|
|
264196
|
+
for (const duplicate of matches.slice(1)) {
|
|
264197
|
+
try {
|
|
264198
|
+
await this.deleteSpecification(duplicate.id);
|
|
264199
|
+
} catch (error2) {
|
|
264200
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264201
|
+
}
|
|
264202
|
+
}
|
|
264203
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
264204
|
+
matches = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264205
|
+
if (matches.length <= 1) break;
|
|
264206
|
+
await this.sleep(250 * (attempt + 1));
|
|
264207
|
+
}
|
|
264208
|
+
const converged = adoptExactMatch(
|
|
264209
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264210
|
+
matches,
|
|
264211
|
+
(entry) => entry.id
|
|
264212
|
+
);
|
|
264213
|
+
if (!converged) {
|
|
264214
|
+
throw new Error(`Concurrent specification create for ${projectName} did not converge`);
|
|
264215
|
+
}
|
|
264216
|
+
specId = converged.id;
|
|
264217
|
+
if (specId !== String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim()) {
|
|
264218
|
+
await this.updateSpec(specId, specContent, workspaceId);
|
|
264219
|
+
}
|
|
264220
|
+
} else {
|
|
264221
|
+
const verified = adoptExactMatch(
|
|
264222
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264223
|
+
matches,
|
|
264224
|
+
(entry) => entry.id
|
|
264225
|
+
);
|
|
264226
|
+
if (verified) specId = verified.id;
|
|
264227
|
+
}
|
|
264084
264228
|
await this.gateway.requestJson({
|
|
264085
264229
|
service: "specification",
|
|
264086
264230
|
method: "get",
|
|
@@ -264088,6 +264232,14 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264088
264232
|
});
|
|
264089
264233
|
return specId;
|
|
264090
264234
|
}
|
|
264235
|
+
async deleteSpecification(specId) {
|
|
264236
|
+
await this.gateway.requestJson({
|
|
264237
|
+
service: "specification",
|
|
264238
|
+
method: "delete",
|
|
264239
|
+
path: `/specifications/${specId}`,
|
|
264240
|
+
retry: "none"
|
|
264241
|
+
});
|
|
264242
|
+
}
|
|
264091
264243
|
async findSpecificationsByExactName(workspaceId, name) {
|
|
264092
264244
|
const response = await this.gateway.requestJson({
|
|
264093
264245
|
service: "specification",
|
|
@@ -264151,6 +264303,41 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264151
264303
|
return void 0;
|
|
264152
264304
|
}
|
|
264153
264305
|
}
|
|
264306
|
+
/**
|
|
264307
|
+
* Native Spec Hub version tags (branch-aware sync P3.5). Tags attach to the
|
|
264308
|
+
* LATEST changelog group; the backend 409s when that group is already tagged
|
|
264309
|
+
* (VersionControlService). Callers handle 409 as idempotent-by-group.
|
|
264310
|
+
*/
|
|
264311
|
+
async tagSpecVersion(specId, name) {
|
|
264312
|
+
const trimmed = name.trim().slice(0, 255);
|
|
264313
|
+
const created = await this.gateway.requestJson({
|
|
264314
|
+
service: "specification",
|
|
264315
|
+
method: "post",
|
|
264316
|
+
path: `/specifications/${specId}/tags`,
|
|
264317
|
+
retry: "none",
|
|
264318
|
+
body: { name: trimmed }
|
|
264319
|
+
});
|
|
264320
|
+
const record = asRecord8(created?.data) ?? created ?? {};
|
|
264321
|
+
return {
|
|
264322
|
+
id: String(record.id ?? "").trim(),
|
|
264323
|
+
name: String(record.name ?? trimmed).trim()
|
|
264324
|
+
};
|
|
264325
|
+
}
|
|
264326
|
+
/** List a spec's native version tags (newest first per backend ordering). */
|
|
264327
|
+
async listSpecVersionTags(specId) {
|
|
264328
|
+
const response = await this.gateway.requestJson({
|
|
264329
|
+
service: "specification",
|
|
264330
|
+
method: "get",
|
|
264331
|
+
path: `/specifications/${specId}/tags`,
|
|
264332
|
+
query: { limit: "50" }
|
|
264333
|
+
});
|
|
264334
|
+
const entries = Array.isArray(response?.data) ? response.data : [];
|
|
264335
|
+
return entries.map((value) => asRecord8(value)).filter((value) => value !== null).map((value) => ({
|
|
264336
|
+
id: String(value.id ?? "").trim(),
|
|
264337
|
+
// listTags returns `message`; createTag returns `name`. Accept both.
|
|
264338
|
+
name: String(value.name ?? value.message ?? "").trim()
|
|
264339
|
+
})).filter((value) => value.id || value.name);
|
|
264340
|
+
}
|
|
264154
264341
|
/** Resolve a specification's ROOT file uuid via the files list. */
|
|
264155
264342
|
async resolveRootFileId(specId) {
|
|
264156
264343
|
const files = await this.gateway.requestJson({
|
|
@@ -264170,79 +264357,182 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264170
264357
|
*/
|
|
264171
264358
|
async generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) {
|
|
264172
264359
|
const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
|
|
264173
|
-
const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
|
|
264174
|
-
const body2 = {
|
|
264175
|
-
name: submittedName,
|
|
264176
|
-
options: {
|
|
264177
|
-
requestNameSource,
|
|
264178
|
-
folderStrategy,
|
|
264179
|
-
...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
|
|
264180
|
-
}
|
|
264181
|
-
};
|
|
264182
264360
|
const before = await this.listGeneratedCollectionRefs(specId);
|
|
264183
|
-
|
|
264184
|
-
|
|
264185
|
-
|
|
264186
|
-
|
|
264187
|
-
|
|
264188
|
-
|
|
264189
|
-
|
|
264190
|
-
|
|
264191
|
-
)
|
|
264192
|
-
const
|
|
264193
|
-
|
|
264361
|
+
const existing = adoptExactMatch(
|
|
264362
|
+
`generated-collection:${specId}:${name}`,
|
|
264363
|
+
await this.filterGeneratedCollectionsByExactName(before, name),
|
|
264364
|
+
(entry) => entry.id
|
|
264365
|
+
);
|
|
264366
|
+
if (existing) return this.convergeGeneratedCollections(specId, name, existing.id);
|
|
264367
|
+
let lastError;
|
|
264368
|
+
for (let taskAttempt = 0; taskAttempt < 2; taskAttempt += 1) {
|
|
264369
|
+
const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
|
|
264370
|
+
const body2 = {
|
|
264371
|
+
name: submittedName,
|
|
264372
|
+
options: {
|
|
264373
|
+
requestNameSource,
|
|
264374
|
+
folderStrategy,
|
|
264375
|
+
...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
|
|
264376
|
+
}
|
|
264377
|
+
};
|
|
264378
|
+
let taskId;
|
|
264379
|
+
try {
|
|
264380
|
+
const generation = await this.postGenerationWithLockRetry(specId, body2, name);
|
|
264381
|
+
if (generation.adoptedId) {
|
|
264382
|
+
return this.convergeGeneratedCollections(specId, name, generation.adoptedId);
|
|
264383
|
+
}
|
|
264384
|
+
taskId = generation.taskId ?? "";
|
|
264385
|
+
} catch (error2) {
|
|
264386
|
+
if (!isAmbiguousTransportError(error2)) throw error2;
|
|
264387
|
+
const beforeIds2 = new Set(before.map((entry) => entry.id));
|
|
264388
|
+
const appeared2 = (await this.listGeneratedCollectionRefs(specId)).filter(
|
|
264389
|
+
(entry) => !beforeIds2.has(entry.id)
|
|
264390
|
+
);
|
|
264391
|
+
const exactAppeared = await this.filterGeneratedCollectionsByExactName(
|
|
264392
|
+
appeared2,
|
|
264393
|
+
submittedName
|
|
264394
|
+
);
|
|
264395
|
+
const match = adoptExactMatch(
|
|
264396
|
+
`generated-collection:${specId}:${submittedName}`,
|
|
264397
|
+
exactAppeared,
|
|
264398
|
+
(entry) => entry.id
|
|
264399
|
+
);
|
|
264400
|
+
if (!match) throw error2;
|
|
264401
|
+
await this.renameGeneratedCollection(match.id, name);
|
|
264402
|
+
return this.convergeGeneratedCollections(specId, name, match.id);
|
|
264403
|
+
}
|
|
264404
|
+
let taskFailed = false;
|
|
264405
|
+
if (taskId) {
|
|
264406
|
+
for (let attempt = 0; attempt < this.generationPollAttempts; attempt += 1) {
|
|
264407
|
+
await this.sleep(this.generationPollDelayMs);
|
|
264408
|
+
const task = await this.gateway.requestJson({
|
|
264409
|
+
service: "specification",
|
|
264410
|
+
method: "get",
|
|
264411
|
+
path: "/tasks",
|
|
264412
|
+
query: { entityId: specId, entityType: "specification", type: "collection-generation" }
|
|
264413
|
+
});
|
|
264414
|
+
const status = String(asRecord8(task?.data)?.[taskId] ?? "").toLowerCase();
|
|
264415
|
+
if (status === "failed" || status === "error") {
|
|
264416
|
+
taskFailed = true;
|
|
264417
|
+
lastError = new Error(`Collection generation task failed for ${prefix}`);
|
|
264418
|
+
break;
|
|
264419
|
+
}
|
|
264420
|
+
if (status && status !== "in-progress" && status !== "pending" && status !== "queued") {
|
|
264421
|
+
break;
|
|
264422
|
+
}
|
|
264423
|
+
if (attempt === this.generationPollAttempts - 1) {
|
|
264424
|
+
throw new Error(`Collection generation timed out for ${prefix}`);
|
|
264425
|
+
}
|
|
264426
|
+
}
|
|
264427
|
+
}
|
|
264428
|
+
if (taskFailed) {
|
|
264429
|
+
await this.sleep(1e3 * (taskAttempt + 1));
|
|
264430
|
+
continue;
|
|
264431
|
+
}
|
|
264432
|
+
const beforeIds = new Set(before.map((entry) => entry.id));
|
|
264433
|
+
const after = await this.listGeneratedCollectionRefs(specId);
|
|
264434
|
+
const appeared = after.filter((entry) => !beforeIds.has(entry.id));
|
|
264435
|
+
const candidates = await this.filterGeneratedCollectionsByExactName(
|
|
264436
|
+
appeared,
|
|
264194
264437
|
submittedName
|
|
264195
264438
|
);
|
|
264196
|
-
const
|
|
264439
|
+
const uid = adoptExactMatch(
|
|
264197
264440
|
`generated-collection:${specId}:${submittedName}`,
|
|
264198
|
-
|
|
264441
|
+
candidates,
|
|
264199
264442
|
(entry) => entry.id
|
|
264200
|
-
);
|
|
264201
|
-
if (!
|
|
264202
|
-
|
|
264203
|
-
|
|
264443
|
+
)?.id;
|
|
264444
|
+
if (!uid) {
|
|
264445
|
+
throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
|
|
264446
|
+
}
|
|
264447
|
+
await this.renameGeneratedCollection(uid, name);
|
|
264448
|
+
return this.convergeGeneratedCollections(specId, name, uid);
|
|
264204
264449
|
}
|
|
264205
|
-
|
|
264206
|
-
|
|
264207
|
-
|
|
264208
|
-
|
|
264209
|
-
|
|
264210
|
-
|
|
264211
|
-
|
|
264212
|
-
|
|
264213
|
-
|
|
264214
|
-
|
|
264215
|
-
|
|
264216
|
-
|
|
264217
|
-
|
|
264218
|
-
|
|
264219
|
-
|
|
264220
|
-
|
|
264221
|
-
|
|
264222
|
-
|
|
264450
|
+
throw lastError ?? new Error(`Collection generation task failed for ${prefix}`);
|
|
264451
|
+
}
|
|
264452
|
+
/**
|
|
264453
|
+
* Re-elect the durable generated collection for a final name. Call after
|
|
264454
|
+
* concurrent dual-trigger generates and before description/inject/tag so a
|
|
264455
|
+
* peer orphan-sweep cannot leave this runner holding a deleted id.
|
|
264456
|
+
*/
|
|
264457
|
+
async adoptGeneratedCollection(specId, projectName, prefix, preferredId = "") {
|
|
264458
|
+
const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
|
|
264459
|
+
return this.convergeGeneratedCollections(specId, name, preferredId);
|
|
264460
|
+
}
|
|
264461
|
+
/**
|
|
264462
|
+
* Concurrent dual-trigger previews can each generate+rename the same final
|
|
264463
|
+
* collection identity. Elect the stable lowest-id winner.
|
|
264464
|
+
*
|
|
264465
|
+
* Losers only delete *their own* preferred collection (never a peer's still-
|
|
264466
|
+
* in-use id). Winners wait briefly for peers to self-delete, then clean any
|
|
264467
|
+
* leftover same-identity orphans (temps + extra finals).
|
|
264468
|
+
*/
|
|
264469
|
+
async convergeGeneratedCollections(specId, finalName, preferredId) {
|
|
264470
|
+
const tempPrefix = `${finalName} [bootstrap:`;
|
|
264471
|
+
const hydrate = async () => {
|
|
264472
|
+
const linked = await this.listGeneratedCollectionRefs(specId);
|
|
264473
|
+
return Promise.all(
|
|
264474
|
+
linked.map(async (entry) => {
|
|
264475
|
+
if (entry.name) return { id: entry.id, name: entry.name };
|
|
264476
|
+
try {
|
|
264477
|
+
const collection = await this.gateway.requestJson({
|
|
264478
|
+
service: "collection",
|
|
264479
|
+
method: "get",
|
|
264480
|
+
path: `/v3/collections/${this.bareModelId(entry.id)}`
|
|
264481
|
+
});
|
|
264482
|
+
return {
|
|
264483
|
+
id: entry.id,
|
|
264484
|
+
name: String(asRecord8(collection?.data)?.name ?? "").trim()
|
|
264485
|
+
};
|
|
264486
|
+
} catch (error2) {
|
|
264487
|
+
if (error2 instanceof HttpError && error2.status === 404) {
|
|
264488
|
+
return { id: entry.id, name: "" };
|
|
264489
|
+
}
|
|
264490
|
+
throw error2;
|
|
264491
|
+
}
|
|
264492
|
+
})
|
|
264493
|
+
);
|
|
264494
|
+
};
|
|
264495
|
+
const selectSameIdentity = (entries) => entries.filter(
|
|
264496
|
+
(entry) => entry.name === finalName || entry.name.startsWith(tempPrefix)
|
|
264497
|
+
).sort((a, b) => a.id.localeCompare(b.id));
|
|
264498
|
+
let sameIdentity = selectSameIdentity(await hydrate());
|
|
264499
|
+
if (sameIdentity.length === 0) return preferredId;
|
|
264500
|
+
if (sameIdentity.length === 1) {
|
|
264501
|
+
await this.sleep(1e3);
|
|
264502
|
+
sameIdentity = selectSameIdentity(await hydrate());
|
|
264503
|
+
if (sameIdentity.length === 0) return preferredId;
|
|
264504
|
+
}
|
|
264505
|
+
const winner = sameIdentity[0];
|
|
264506
|
+
if (winner.name !== finalName) {
|
|
264507
|
+
await this.renameGeneratedCollection(winner.id, finalName);
|
|
264508
|
+
}
|
|
264509
|
+
if (preferredId && preferredId !== winner.id) {
|
|
264510
|
+
const own = sameIdentity.find((entry) => entry.id === preferredId);
|
|
264511
|
+
if (own) {
|
|
264512
|
+
try {
|
|
264513
|
+
await this.deleteCollection(preferredId);
|
|
264514
|
+
} catch (error2) {
|
|
264515
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264223
264516
|
}
|
|
264224
264517
|
}
|
|
264518
|
+
return winner.id;
|
|
264225
264519
|
}
|
|
264226
|
-
|
|
264227
|
-
|
|
264228
|
-
|
|
264229
|
-
|
|
264230
|
-
|
|
264231
|
-
|
|
264232
|
-
|
|
264233
|
-
|
|
264234
|
-
|
|
264235
|
-
|
|
264236
|
-
|
|
264237
|
-
)?.id;
|
|
264238
|
-
if (!uid) {
|
|
264239
|
-
throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
|
|
264520
|
+
if (sameIdentity.length > 1) {
|
|
264521
|
+
await this.sleep(1500);
|
|
264522
|
+
sameIdentity = selectSameIdentity(await hydrate());
|
|
264523
|
+
for (const duplicate of sameIdentity) {
|
|
264524
|
+
if (duplicate.id === winner.id) continue;
|
|
264525
|
+
try {
|
|
264526
|
+
await this.deleteCollection(duplicate.id);
|
|
264527
|
+
} catch (error2) {
|
|
264528
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264529
|
+
}
|
|
264530
|
+
}
|
|
264240
264531
|
}
|
|
264241
|
-
|
|
264242
|
-
return uid;
|
|
264532
|
+
return winner.id;
|
|
264243
264533
|
}
|
|
264244
264534
|
/** POST the generation request, retrying a 423-locked spec; returns the task id. */
|
|
264245
|
-
async postGenerationWithLockRetry(specId, body2) {
|
|
264535
|
+
async postGenerationWithLockRetry(specId, body2, finalName) {
|
|
264246
264536
|
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
264247
264537
|
try {
|
|
264248
264538
|
const created = await this.gateway.requestJson({
|
|
@@ -264252,13 +264542,22 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264252
264542
|
retry: "none",
|
|
264253
264543
|
body: body2
|
|
264254
264544
|
});
|
|
264255
|
-
return String(asRecord8(created?.data)?.taskId ?? "").trim();
|
|
264545
|
+
return { taskId: String(asRecord8(created?.data)?.taskId ?? "").trim() };
|
|
264256
264546
|
} catch (error2) {
|
|
264257
264547
|
const locked = error2 instanceof HttpError && error2.status === 423;
|
|
264258
264548
|
if (!locked || lockedAttempt >= _PostmanGatewayAssetsClient.GENERATION_LOCKED_MAX_RETRIES) {
|
|
264259
264549
|
throw error2;
|
|
264260
264550
|
}
|
|
264261
264551
|
await this.sleep(5e3 * Math.pow(2, lockedAttempt));
|
|
264552
|
+
const adopted = adoptExactMatch(
|
|
264553
|
+
`generated-collection:${specId}:${finalName}`,
|
|
264554
|
+
await this.filterGeneratedCollectionsByExactName(
|
|
264555
|
+
await this.listGeneratedCollectionRefs(specId),
|
|
264556
|
+
finalName
|
|
264557
|
+
),
|
|
264558
|
+
(entry) => entry.id
|
|
264559
|
+
);
|
|
264560
|
+
if (adopted) return { adoptedId: adopted.id };
|
|
264262
264561
|
}
|
|
264263
264562
|
}
|
|
264264
264563
|
}
|
|
@@ -264295,14 +264594,24 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264295
264594
|
return hydrated.filter((entry) => entry.name === expectedName);
|
|
264296
264595
|
}
|
|
264297
264596
|
async renameGeneratedCollection(collectionId, name) {
|
|
264298
|
-
|
|
264299
|
-
|
|
264300
|
-
|
|
264301
|
-
|
|
264302
|
-
|
|
264303
|
-
|
|
264304
|
-
|
|
264305
|
-
|
|
264597
|
+
try {
|
|
264598
|
+
await this.gateway.requestJson({
|
|
264599
|
+
service: "collection",
|
|
264600
|
+
method: "patch",
|
|
264601
|
+
path: `/v3/collections/${this.bareModelId(collectionId)}`,
|
|
264602
|
+
// Replacing a generated collection's name with the same value is idempotent.
|
|
264603
|
+
retry: "safe",
|
|
264604
|
+
body: [{ op: "replace", path: "/name", value: name }]
|
|
264605
|
+
});
|
|
264606
|
+
} catch (error2) {
|
|
264607
|
+
if (error2 instanceof HttpError && error2.status === 400 && /must update at least one|REJECTED_PATCH/i.test(
|
|
264608
|
+
`${error2.message}
|
|
264609
|
+
${error2.responseBody ?? ""}`
|
|
264610
|
+
)) {
|
|
264611
|
+
return;
|
|
264612
|
+
}
|
|
264613
|
+
throw error2;
|
|
264614
|
+
}
|
|
264306
264615
|
}
|
|
264307
264616
|
/**
|
|
264308
264617
|
* Create a team-visible workspace through the gateway workspaces service.
|
|
@@ -264577,6 +264886,7 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264577
264886
|
service: "tagging",
|
|
264578
264887
|
method: "put",
|
|
264579
264888
|
path: `/v1/tags/collections/${collectionUid}`,
|
|
264889
|
+
retry: "safe",
|
|
264580
264890
|
body: { tags: normalized.map((slug) => ({ slug })) }
|
|
264581
264891
|
});
|
|
264582
264892
|
}
|
|
@@ -265203,6 +265513,15 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
265203
265513
|
async createCollection(workspaceId, collection, options = {}) {
|
|
265204
265514
|
const v3 = this.normalizeCollectionForWrite(collection);
|
|
265205
265515
|
const desiredName = String(v3.name ?? "Untitled Collection");
|
|
265516
|
+
const existing = adoptExactMatch(
|
|
265517
|
+
`collection:${workspaceId}:${desiredName}`,
|
|
265518
|
+
await this.findCollectionsByExactName(workspaceId, desiredName),
|
|
265519
|
+
(entry) => entry.id
|
|
265520
|
+
);
|
|
265521
|
+
if (existing) {
|
|
265522
|
+
await this.updateCollection(existing.id, collection);
|
|
265523
|
+
return existing.id;
|
|
265524
|
+
}
|
|
265206
265525
|
const submittedName = `${desiredName} [bootstrap:${this.createIdentity()}]`;
|
|
265207
265526
|
const rootBody = { name: submittedName };
|
|
265208
265527
|
let created;
|
|
@@ -265253,6 +265572,10 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
265253
265572
|
}
|
|
265254
265573
|
return rawId;
|
|
265255
265574
|
}
|
|
265575
|
+
/** Patch only the durable collection description without reconciling its item tree. */
|
|
265576
|
+
async updateCollectionDescription(collectionUid, description) {
|
|
265577
|
+
await this.applyCollectionLevelSettings(this.bareModelId(collectionUid), { description });
|
|
265578
|
+
}
|
|
265256
265579
|
/**
|
|
265257
265580
|
* Full-replace reconcile of a curated local v2.1.0 or collection v3 payload: delete every
|
|
265258
265581
|
* root-level item (deleting a folder cascades its children server-side —
|
|
@@ -265477,7 +265800,8 @@ var AccessTokenGatewayClient = class {
|
|
|
265477
265800
|
* `requestJson` can still parse it.
|
|
265478
265801
|
*/
|
|
265479
265802
|
rebuildResponse(response, body2) {
|
|
265480
|
-
|
|
265803
|
+
const nullBody = response.status === 204 || response.status === 205 || response.status === 304;
|
|
265804
|
+
return new Response(nullBody ? null : body2, {
|
|
265481
265805
|
status: response.status,
|
|
265482
265806
|
statusText: response.statusText,
|
|
265483
265807
|
headers: response.headers
|
|
@@ -281929,6 +282253,61 @@ function serializeOpenApiDocument(document2) {
|
|
|
281929
282253
|
return `${JSON.stringify(document2, null, 2)}
|
|
281930
282254
|
`;
|
|
281931
282255
|
}
|
|
282256
|
+
function typeNullPath(pathSegments) {
|
|
282257
|
+
return pathSegments.map(String).join(".");
|
|
282258
|
+
}
|
|
282259
|
+
function isNullOnlySchema(value) {
|
|
282260
|
+
const record = asRecord10(value);
|
|
282261
|
+
if (!record || record.type !== "null") return false;
|
|
282262
|
+
return Object.keys(record).every((key) => key === "type" || key.startsWith("x-"));
|
|
282263
|
+
}
|
|
282264
|
+
function createOas30TypeNullCompatibilityDocument(sourceDocument) {
|
|
282265
|
+
const document2 = structuredClone(sourceDocument);
|
|
282266
|
+
const sourceTypeNullPaths = [];
|
|
282267
|
+
const visit4 = (value, pathSegments) => {
|
|
282268
|
+
if (Array.isArray(value)) {
|
|
282269
|
+
value.forEach((entry, index) => visit4(entry, [...pathSegments, index]));
|
|
282270
|
+
return;
|
|
282271
|
+
}
|
|
282272
|
+
const record = asRecord10(value);
|
|
282273
|
+
if (!record) return;
|
|
282274
|
+
const oneOf = Array.isArray(record.oneOf) ? record.oneOf : void 0;
|
|
282275
|
+
const nullIndexes = oneOf ? oneOf.flatMap((entry, index) => isNullOnlySchema(entry) ? [index] : []) : [];
|
|
282276
|
+
if (nullIndexes.length > 0) {
|
|
282277
|
+
if (oneOf?.length !== 2 || nullIndexes.length !== 1) {
|
|
282278
|
+
throw new Error(
|
|
282279
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must use oneOf with exactly one null-only member and one non-null schema`
|
|
282280
|
+
);
|
|
282281
|
+
}
|
|
282282
|
+
const nullIndex = nullIndexes[0];
|
|
282283
|
+
const nonNullIndex = nullIndex === 0 ? 1 : 0;
|
|
282284
|
+
const nonNullSchema = asRecord10(oneOf[nonNullIndex]);
|
|
282285
|
+
if (!nonNullSchema || nonNullSchema.type === "null") {
|
|
282286
|
+
throw new Error(
|
|
282287
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must pair the null-only member with one non-null schema`
|
|
282288
|
+
);
|
|
282289
|
+
}
|
|
282290
|
+
const siblings = Object.fromEntries(
|
|
282291
|
+
Object.entries(record).filter(([key]) => key !== "oneOf")
|
|
282292
|
+
);
|
|
282293
|
+
for (const key of Object.keys(record)) delete record[key];
|
|
282294
|
+
Object.assign(record, structuredClone(nonNullSchema), siblings, { nullable: true });
|
|
282295
|
+
sourceTypeNullPaths.push(typeNullPath([...pathSegments, "oneOf", nullIndex, "type"]));
|
|
282296
|
+
visit4(record, pathSegments);
|
|
282297
|
+
return;
|
|
282298
|
+
}
|
|
282299
|
+
if (record.type === "null") {
|
|
282300
|
+
throw new Error(
|
|
282301
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath([...pathSegments, "type"])} is not a supported nullable oneOf member`
|
|
282302
|
+
);
|
|
282303
|
+
}
|
|
282304
|
+
for (const [key, child4] of Object.entries(record)) {
|
|
282305
|
+
visit4(child4, [...pathSegments, key]);
|
|
282306
|
+
}
|
|
282307
|
+
};
|
|
282308
|
+
visit4(document2, []);
|
|
282309
|
+
return { document: document2, sourceTypeNullPaths };
|
|
282310
|
+
}
|
|
281932
282311
|
async function bundleSpec(baseUrl, document2, options) {
|
|
281933
282312
|
const budget = options.budget ?? { refs: 0, totalBytes: Buffer.byteLength(JSON.stringify(document2), "utf8") };
|
|
281934
282313
|
const fetchText = options.fetchText ?? safeFetchText;
|
|
@@ -281970,7 +282349,14 @@ function createCachedFetchText(options) {
|
|
|
281970
282349
|
async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
|
|
281971
282350
|
const document2 = parseOpenApiDocument(content);
|
|
281972
282351
|
const version = detectOpenApiVersion2(document2);
|
|
281973
|
-
|
|
282352
|
+
let contractDocument = document2;
|
|
282353
|
+
let sourceTypeNullPaths = [];
|
|
282354
|
+
if (options.preserveOas30TypeNull && version === "3.0") {
|
|
282355
|
+
const compatibility = createOas30TypeNullCompatibilityDocument(document2);
|
|
282356
|
+
contractDocument = compatibility.document;
|
|
282357
|
+
sourceTypeNullPaths = compatibility.sourceTypeNullPaths;
|
|
282358
|
+
}
|
|
282359
|
+
const bundledDocument = await bundleSpec(baseRef, contractDocument, { ...options, budget, fetchText });
|
|
281974
282360
|
const validation = await validate(bundledDocument, {
|
|
281975
282361
|
resolve: { external: false, file: false },
|
|
281976
282362
|
dereference: { circular: "ignore" },
|
|
@@ -281984,6 +282370,7 @@ async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
|
|
|
281984
282370
|
bundledDocument,
|
|
281985
282371
|
contractIndex: buildContractIndex(bundledDocument),
|
|
281986
282372
|
content,
|
|
282373
|
+
sourceTypeNullPaths,
|
|
281987
282374
|
version
|
|
281988
282375
|
};
|
|
281989
282376
|
}
|
|
@@ -282109,6 +282496,309 @@ function looksLikeIntrospection(record) {
|
|
|
282109
282496
|
return Boolean(data && typeof data === "object" && data.__schema);
|
|
282110
282497
|
}
|
|
282111
282498
|
|
|
282499
|
+
// src/lib/repo/branch-decision.ts
|
|
282500
|
+
var import_node_fs5 = require("node:fs");
|
|
282501
|
+
var import_node_crypto4 = require("node:crypto");
|
|
282502
|
+
var ContractError = class extends Error {
|
|
282503
|
+
code;
|
|
282504
|
+
constructor(code, message) {
|
|
282505
|
+
super(`${code}: ${message}`);
|
|
282506
|
+
this.code = code;
|
|
282507
|
+
this.name = "ContractError";
|
|
282508
|
+
}
|
|
282509
|
+
};
|
|
282510
|
+
function clean(value) {
|
|
282511
|
+
const trimmed = (value ?? "").trim();
|
|
282512
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
282513
|
+
}
|
|
282514
|
+
function stripRefPrefix(ref) {
|
|
282515
|
+
const raw = clean(ref);
|
|
282516
|
+
if (!raw) {
|
|
282517
|
+
return { kind: "unknown" };
|
|
282518
|
+
}
|
|
282519
|
+
if (raw.startsWith("refs/heads/")) {
|
|
282520
|
+
return { name: raw.slice("refs/heads/".length), kind: "branch" };
|
|
282521
|
+
}
|
|
282522
|
+
if (raw.startsWith("refs/tags/")) {
|
|
282523
|
+
return { name: raw.slice("refs/tags/".length), kind: "tag" };
|
|
282524
|
+
}
|
|
282525
|
+
if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
|
|
282526
|
+
return { kind: "unknown" };
|
|
282527
|
+
}
|
|
282528
|
+
return { name: raw, kind: "branch" };
|
|
282529
|
+
}
|
|
282530
|
+
function detectProvider(env) {
|
|
282531
|
+
if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
|
|
282532
|
+
if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
|
|
282533
|
+
if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
|
|
282534
|
+
if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
|
|
282535
|
+
return "unknown";
|
|
282536
|
+
}
|
|
282537
|
+
function readGithubEvent(env) {
|
|
282538
|
+
const path10 = clean(env.GITHUB_EVENT_PATH);
|
|
282539
|
+
if (!path10) return void 0;
|
|
282540
|
+
try {
|
|
282541
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path10, "utf8"));
|
|
282542
|
+
} catch {
|
|
282543
|
+
return void 0;
|
|
282544
|
+
}
|
|
282545
|
+
}
|
|
282546
|
+
function resolveBranchIdentity(env = process.env, overrides = {}) {
|
|
282547
|
+
const provider = detectProvider(env);
|
|
282548
|
+
const explicitDefault = clean(overrides.defaultBranch);
|
|
282549
|
+
let headBranch;
|
|
282550
|
+
let rawRef;
|
|
282551
|
+
let refKind;
|
|
282552
|
+
let isPrContext = false;
|
|
282553
|
+
let isForkPr = false;
|
|
282554
|
+
let defaultBranch = explicitDefault;
|
|
282555
|
+
let headSha;
|
|
282556
|
+
switch (provider) {
|
|
282557
|
+
case "github": {
|
|
282558
|
+
const event2 = readGithubEvent(env);
|
|
282559
|
+
headSha = clean(env.GITHUB_SHA);
|
|
282560
|
+
defaultBranch ??= clean(event2?.repository?.default_branch);
|
|
282561
|
+
const headRef = clean(env.GITHUB_HEAD_REF);
|
|
282562
|
+
if (headRef) {
|
|
282563
|
+
isPrContext = true;
|
|
282564
|
+
headBranch = headRef;
|
|
282565
|
+
rawRef = clean(env.GITHUB_REF) ?? headRef;
|
|
282566
|
+
refKind = "branch";
|
|
282567
|
+
const headRepo = event2?.pull_request?.head?.repo?.full_name;
|
|
282568
|
+
const baseRepo = event2?.pull_request?.base?.repo?.full_name ?? event2?.repository?.full_name;
|
|
282569
|
+
isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
|
|
282570
|
+
headSha = clean(event2?.pull_request?.head?.sha) ?? headSha;
|
|
282571
|
+
} else {
|
|
282572
|
+
rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
|
|
282573
|
+
const parsed = stripRefPrefix(rawRef);
|
|
282574
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282575
|
+
refKind = parsed.kind;
|
|
282576
|
+
}
|
|
282577
|
+
break;
|
|
282578
|
+
}
|
|
282579
|
+
case "gitlab": {
|
|
282580
|
+
headSha = clean(env.CI_COMMIT_SHA);
|
|
282581
|
+
defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
|
|
282582
|
+
const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
|
|
282583
|
+
if (mrSource) {
|
|
282584
|
+
isPrContext = true;
|
|
282585
|
+
headBranch = mrSource;
|
|
282586
|
+
rawRef = mrSource;
|
|
282587
|
+
refKind = "branch";
|
|
282588
|
+
const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
|
|
282589
|
+
const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
|
|
282590
|
+
isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
|
|
282591
|
+
} else if (clean(env.CI_COMMIT_TAG)) {
|
|
282592
|
+
rawRef = clean(env.CI_COMMIT_TAG);
|
|
282593
|
+
refKind = "tag";
|
|
282594
|
+
} else {
|
|
282595
|
+
headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
|
|
282596
|
+
rawRef = headBranch;
|
|
282597
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
282598
|
+
}
|
|
282599
|
+
break;
|
|
282600
|
+
}
|
|
282601
|
+
case "bitbucket": {
|
|
282602
|
+
headSha = clean(env.BITBUCKET_COMMIT);
|
|
282603
|
+
if (clean(env.BITBUCKET_TAG)) {
|
|
282604
|
+
rawRef = clean(env.BITBUCKET_TAG);
|
|
282605
|
+
refKind = "tag";
|
|
282606
|
+
} else {
|
|
282607
|
+
headBranch = clean(env.BITBUCKET_BRANCH);
|
|
282608
|
+
rawRef = headBranch;
|
|
282609
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
282610
|
+
isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
|
|
282611
|
+
}
|
|
282612
|
+
break;
|
|
282613
|
+
}
|
|
282614
|
+
case "azure-devops": {
|
|
282615
|
+
headSha = clean(env.BUILD_SOURCEVERSION);
|
|
282616
|
+
const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
|
|
282617
|
+
if (prSource) {
|
|
282618
|
+
isPrContext = true;
|
|
282619
|
+
const parsed = stripRefPrefix(prSource);
|
|
282620
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282621
|
+
rawRef = prSource;
|
|
282622
|
+
refKind = parsed.kind;
|
|
282623
|
+
const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
|
|
282624
|
+
isForkPr = forkFlag?.toLowerCase() === "true";
|
|
282625
|
+
} else {
|
|
282626
|
+
rawRef = clean(env.BUILD_SOURCEBRANCH);
|
|
282627
|
+
const parsed = stripRefPrefix(rawRef);
|
|
282628
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282629
|
+
refKind = parsed.kind;
|
|
282630
|
+
}
|
|
282631
|
+
break;
|
|
282632
|
+
}
|
|
282633
|
+
default: {
|
|
282634
|
+
refKind = "unknown";
|
|
282635
|
+
}
|
|
282636
|
+
}
|
|
282637
|
+
if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
|
|
282638
|
+
refKind = "default-branch";
|
|
282639
|
+
}
|
|
282640
|
+
return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
|
|
282641
|
+
}
|
|
282642
|
+
function parseChannelRules(input) {
|
|
282643
|
+
const raw = clean(input);
|
|
282644
|
+
const rules = [];
|
|
282645
|
+
for (const part of raw ? raw.split(",") : []) {
|
|
282646
|
+
const entry = part.trim();
|
|
282647
|
+
if (!entry) continue;
|
|
282648
|
+
const eq = entry.indexOf("=");
|
|
282649
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
282650
|
+
throw new ContractError(
|
|
282651
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
282652
|
+
`channels entry "${entry}" must be <branch-or-glob>=<CODE>`
|
|
282653
|
+
);
|
|
282654
|
+
}
|
|
282655
|
+
const pattern = entry.slice(0, eq).trim();
|
|
282656
|
+
const code = entry.slice(eq + 1).trim().toUpperCase();
|
|
282657
|
+
if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
|
|
282658
|
+
throw new ContractError(
|
|
282659
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
282660
|
+
`channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
|
|
282661
|
+
);
|
|
282662
|
+
}
|
|
282663
|
+
rules.push({ pattern, code });
|
|
282664
|
+
}
|
|
282665
|
+
if (!rules.some((rule) => rule.pattern === "release/*")) {
|
|
282666
|
+
rules.push({ pattern: "release/*", code: "RC" });
|
|
282667
|
+
}
|
|
282668
|
+
return rules;
|
|
282669
|
+
}
|
|
282670
|
+
function matchChannel(branch, rules) {
|
|
282671
|
+
for (const rule of rules) {
|
|
282672
|
+
if (rule.pattern.endsWith("*")) {
|
|
282673
|
+
const prefix = rule.pattern.slice(0, -1);
|
|
282674
|
+
if (branch.startsWith(prefix)) return rule;
|
|
282675
|
+
} else if (branch === rule.pattern) {
|
|
282676
|
+
return rule;
|
|
282677
|
+
}
|
|
282678
|
+
}
|
|
282679
|
+
return void 0;
|
|
282680
|
+
}
|
|
282681
|
+
function resolveBranchDecision(options) {
|
|
282682
|
+
const { strategy, identity } = options;
|
|
282683
|
+
const channels = options.channels ?? [];
|
|
282684
|
+
if (strategy === "legacy") {
|
|
282685
|
+
return {
|
|
282686
|
+
tier: "legacy",
|
|
282687
|
+
strategy,
|
|
282688
|
+
identity,
|
|
282689
|
+
canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
|
|
282690
|
+
reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
|
|
282691
|
+
};
|
|
282692
|
+
}
|
|
282693
|
+
const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
|
|
282694
|
+
if (!canonicalBranch) {
|
|
282695
|
+
throw new ContractError(
|
|
282696
|
+
"CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
|
|
282697
|
+
`cannot resolve the canonical branch on ${identity.provider} (no explicit canonical-branch input and the provider exposes no default-branch variable). Set the canonical-branch input.`
|
|
282698
|
+
);
|
|
282699
|
+
}
|
|
282700
|
+
if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
|
|
282701
|
+
return {
|
|
282702
|
+
tier: "gated",
|
|
282703
|
+
strategy,
|
|
282704
|
+
identity,
|
|
282705
|
+
canonicalBranch,
|
|
282706
|
+
reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
|
|
282707
|
+
};
|
|
282708
|
+
}
|
|
282709
|
+
if (identity.headBranch === canonicalBranch) {
|
|
282710
|
+
return {
|
|
282711
|
+
tier: "canonical",
|
|
282712
|
+
strategy,
|
|
282713
|
+
identity,
|
|
282714
|
+
canonicalBranch,
|
|
282715
|
+
reason: `head branch equals canonical branch ${canonicalBranch}`
|
|
282716
|
+
};
|
|
282717
|
+
}
|
|
282718
|
+
const channel = matchChannel(identity.headBranch, channels);
|
|
282719
|
+
if (channel) {
|
|
282720
|
+
return {
|
|
282721
|
+
tier: "channel",
|
|
282722
|
+
strategy,
|
|
282723
|
+
identity,
|
|
282724
|
+
canonicalBranch,
|
|
282725
|
+
channel,
|
|
282726
|
+
reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
|
|
282727
|
+
};
|
|
282728
|
+
}
|
|
282729
|
+
if (strategy === "preview") {
|
|
282730
|
+
if (identity.isForkPr) {
|
|
282731
|
+
return {
|
|
282732
|
+
tier: "gated",
|
|
282733
|
+
strategy,
|
|
282734
|
+
identity,
|
|
282735
|
+
canonicalBranch,
|
|
282736
|
+
reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
|
|
282737
|
+
};
|
|
282738
|
+
}
|
|
282739
|
+
return {
|
|
282740
|
+
tier: "preview",
|
|
282741
|
+
strategy,
|
|
282742
|
+
identity,
|
|
282743
|
+
canonicalBranch,
|
|
282744
|
+
reason: `branch ${identity.headBranch} under branch-strategy preview`
|
|
282745
|
+
};
|
|
282746
|
+
}
|
|
282747
|
+
return {
|
|
282748
|
+
tier: "gated",
|
|
282749
|
+
strategy,
|
|
282750
|
+
identity,
|
|
282751
|
+
canonicalBranch,
|
|
282752
|
+
reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
|
|
282753
|
+
};
|
|
282754
|
+
}
|
|
282755
|
+
var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
|
|
282756
|
+
function serializeBranchDecision(decision) {
|
|
282757
|
+
return JSON.stringify(decision);
|
|
282758
|
+
}
|
|
282759
|
+
function parseBranchDecision(raw) {
|
|
282760
|
+
const value = clean(raw);
|
|
282761
|
+
if (!value) return void 0;
|
|
282762
|
+
let parsed;
|
|
282763
|
+
try {
|
|
282764
|
+
parsed = JSON.parse(value);
|
|
282765
|
+
} catch {
|
|
282766
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
|
|
282767
|
+
}
|
|
282768
|
+
const candidate = parsed;
|
|
282769
|
+
const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
|
|
282770
|
+
if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
|
|
282771
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
|
|
282772
|
+
}
|
|
282773
|
+
return candidate;
|
|
282774
|
+
}
|
|
282775
|
+
function resolveEffectiveBranchDecision(options, env = process.env) {
|
|
282776
|
+
const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
|
|
282777
|
+
if (inherited) return inherited;
|
|
282778
|
+
return resolveBranchDecision(options);
|
|
282779
|
+
}
|
|
282780
|
+
var PREVIEW_SLUG_MAX = 30;
|
|
282781
|
+
function buildBranchSlug(rawBranch) {
|
|
282782
|
+
const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
|
|
282783
|
+
const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
|
|
282784
|
+
const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
|
|
282785
|
+
if (!lossy) {
|
|
282786
|
+
return { suffix: truncated, slug: truncated, lossy };
|
|
282787
|
+
}
|
|
282788
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
|
|
282789
|
+
return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
|
|
282790
|
+
}
|
|
282791
|
+
function previewAssetName(baseName, rawBranch) {
|
|
282792
|
+
return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
|
|
282793
|
+
}
|
|
282794
|
+
function channelAssetName(baseName, code) {
|
|
282795
|
+
return `[${code}] ${baseName}`;
|
|
282796
|
+
}
|
|
282797
|
+
var MARKER_KEY = "x-pm-onboarding";
|
|
282798
|
+
function renderAssetMarker(marker) {
|
|
282799
|
+
return `${MARKER_KEY}: ${JSON.stringify(marker)}`;
|
|
282800
|
+
}
|
|
282801
|
+
|
|
282112
282802
|
// node_modules/graphql/jsutils/inspect.mjs
|
|
282113
282803
|
var MAX_ARRAY_LENGTH = 10;
|
|
282114
282804
|
var MAX_RECURSIVE_DEPTH = 2;
|
|
@@ -305440,7 +306130,7 @@ function parseWsdl(content, opts) {
|
|
|
305440
306130
|
}
|
|
305441
306131
|
|
|
305442
306132
|
// src/lib/protocols/soap/builder.ts
|
|
305443
|
-
var
|
|
306133
|
+
var import_node_crypto5 = require("node:crypto");
|
|
305444
306134
|
var COLLECTION_V210_SCHEMA2 = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
|
|
305445
306135
|
var SOAP11_ENVELOPE_NS = "http://schemas.xmlsoap.org/soap/envelope/";
|
|
305446
306136
|
var SOAP12_ENVELOPE_NS = "http://www.w3.org/2003/05/soap-envelope";
|
|
@@ -305448,7 +306138,7 @@ var SOAP11_CONTENT_TYPE = "text/xml; charset=UTF-8";
|
|
|
305448
306138
|
var SOAP12_CONTENT_TYPE = "application/soap+xml; charset=UTF-8";
|
|
305449
306139
|
var WSA_NS = "http://www.w3.org/2005/08/addressing";
|
|
305450
306140
|
function stableId2(seed) {
|
|
305451
|
-
return (0,
|
|
306141
|
+
return (0, import_node_crypto5.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
|
|
305452
306142
|
}
|
|
305453
306143
|
function envelopeNamespace(version) {
|
|
305454
306144
|
return version === "1.2" ? SOAP12_ENVELOPE_NS : SOAP11_ENVELOPE_NS;
|
|
@@ -312211,6 +312901,11 @@ function resolveInputs(env = process.env) {
|
|
|
312211
312901
|
),
|
|
312212
312902
|
protocolEndpointUrl: getInput2("protocol-endpoint-url", env),
|
|
312213
312903
|
openapiVersion: resolveOpenapiVersion(getInput2("openapi-version", env)),
|
|
312904
|
+
preserveOas30TypeNull: parseBooleanInput(
|
|
312905
|
+
"preserve-oas30-type-null",
|
|
312906
|
+
getInput2("preserve-oas30-type-null", env),
|
|
312907
|
+
false
|
|
312908
|
+
),
|
|
312214
312909
|
breakingChangeMode: parseBreakingChangeMode(getInput2("breaking-change-mode", env)),
|
|
312215
312910
|
breakingBaselineSpecPath: getInput2("breaking-baseline-spec-path", env),
|
|
312216
312911
|
breakingRulesPath: getInput2("breaking-rules-path", env) ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
|
|
@@ -312241,8 +312936,71 @@ function resolveInputs(env = process.env) {
|
|
|
312241
312936
|
githubRef: env.GITHUB_REF,
|
|
312242
312937
|
githubSha: env.GITHUB_SHA,
|
|
312243
312938
|
githubToken: getInput2("github-token", env) || env.GITHUB_TOKEN || "",
|
|
312244
|
-
ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || ""
|
|
312939
|
+
ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || "",
|
|
312940
|
+
branchStrategy: parseEnumInput(
|
|
312941
|
+
"branch-strategy",
|
|
312942
|
+
getInput2("branch-strategy", env),
|
|
312943
|
+
"legacy"
|
|
312944
|
+
),
|
|
312945
|
+
canonicalBranch: getInput2("canonical-branch", env),
|
|
312946
|
+
channels: getInput2("channels", env)
|
|
312947
|
+
};
|
|
312948
|
+
}
|
|
312949
|
+
function decideBranchTier(inputs, env = process.env) {
|
|
312950
|
+
return resolveEffectiveBranchDecision(
|
|
312951
|
+
{
|
|
312952
|
+
// Tolerate hand-built inputs objects (tests, embedders) that omit the
|
|
312953
|
+
// field: absent means legacy, exactly like the action default.
|
|
312954
|
+
strategy: inputs.branchStrategy ?? "legacy",
|
|
312955
|
+
identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
|
|
312956
|
+
canonicalBranch: inputs.canonicalBranch,
|
|
312957
|
+
channels: parseChannelRules(inputs.channels)
|
|
312958
|
+
},
|
|
312959
|
+
env
|
|
312960
|
+
);
|
|
312961
|
+
}
|
|
312962
|
+
function embedSpecBranchMarker(content, decision, repo) {
|
|
312963
|
+
if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
|
|
312964
|
+
return content;
|
|
312965
|
+
}
|
|
312966
|
+
const rawBranch = decision.identity.headBranch;
|
|
312967
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
312968
|
+
const marker = {
|
|
312969
|
+
repo,
|
|
312970
|
+
rawBranch,
|
|
312971
|
+
sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
|
|
312972
|
+
role: decision.tier,
|
|
312973
|
+
headSha: decision.identity.headSha,
|
|
312974
|
+
createdAt: now,
|
|
312975
|
+
lastSyncedAt: now
|
|
312245
312976
|
};
|
|
312977
|
+
try {
|
|
312978
|
+
if (content.trim().startsWith("{")) {
|
|
312979
|
+
return `${JSON.stringify({ ...JSON.parse(content), "x-postman-onboarding": marker }, null, 2)}
|
|
312980
|
+
`;
|
|
312981
|
+
}
|
|
312982
|
+
const document2 = (0, import_yaml4.parse)(content);
|
|
312983
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) return content;
|
|
312984
|
+
return (0, import_yaml4.stringify)({ ...document2, "x-postman-onboarding": marker });
|
|
312985
|
+
} catch {
|
|
312986
|
+
return content;
|
|
312987
|
+
}
|
|
312988
|
+
}
|
|
312989
|
+
function renderCollectionBranchMarker(decision, repo, now = /* @__PURE__ */ new Date()) {
|
|
312990
|
+
if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
|
|
312991
|
+
return void 0;
|
|
312992
|
+
}
|
|
312993
|
+
const rawBranch = decision.identity.headBranch;
|
|
312994
|
+
const timestamp = now.toISOString();
|
|
312995
|
+
return renderAssetMarker({
|
|
312996
|
+
repo,
|
|
312997
|
+
rawBranch,
|
|
312998
|
+
sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
|
|
312999
|
+
role: decision.tier,
|
|
313000
|
+
headSha: decision.identity.headSha,
|
|
313001
|
+
createdAt: timestamp,
|
|
313002
|
+
lastSyncedAt: timestamp
|
|
313003
|
+
});
|
|
312246
313004
|
}
|
|
312247
313005
|
function createPlannedOutputs(inputs) {
|
|
312248
313006
|
const workspaceName = inputs.domainCode ? `[${inputs.domainCode}] ${inputs.projectName}` : inputs.projectName;
|
|
@@ -312275,7 +313033,12 @@ function createPlannedOutputs(inputs) {
|
|
|
312275
313033
|
mode: inputs.breakingChangeMode,
|
|
312276
313034
|
status: "skipped",
|
|
312277
313035
|
summaryPath: ""
|
|
312278
|
-
})
|
|
313036
|
+
}),
|
|
313037
|
+
"sync-status": "",
|
|
313038
|
+
"branch-decision": "",
|
|
313039
|
+
"spec-version-tag": "",
|
|
313040
|
+
"spec-version-url": "",
|
|
313041
|
+
"spec-content-changed": ""
|
|
312279
313042
|
};
|
|
312280
313043
|
}
|
|
312281
313044
|
function warnIfDeprecatedAccessToken(actionCore, inputs) {
|
|
@@ -312312,9 +313075,6 @@ function readActionInputs(actionCore) {
|
|
|
312312
313075
|
}
|
|
312313
313076
|
const postmanApiKey = optionalInput(actionCore, "postman-api-key") ?? "";
|
|
312314
313077
|
const postmanAccessToken = optionalInput(actionCore, "postman-access-token");
|
|
312315
|
-
if (!postmanApiKey && !postmanAccessToken) {
|
|
312316
|
-
throw new Error("One of postman-api-key or postman-access-token is required.");
|
|
312317
|
-
}
|
|
312318
313078
|
const githubToken = optionalInput(actionCore, "github-token") || process.env.GITHUB_TOKEN;
|
|
312319
313079
|
const ghFallbackToken = optionalInput(actionCore, "gh-fallback-token") || process.env.GH_FALLBACK_TOKEN;
|
|
312320
313080
|
if (postmanApiKey) actionCore.setSecret(postmanApiKey);
|
|
@@ -312358,6 +313118,7 @@ function readActionInputs(actionCore) {
|
|
|
312358
313118
|
INPUT_NESTED_FOLDER_HIERARCHY: optionalInput(actionCore, "nested-folder-hierarchy") ?? bootstrapActionContract.inputs["nested-folder-hierarchy"].default,
|
|
312359
313119
|
INPUT_REQUEST_NAME_SOURCE: optionalInput(actionCore, "request-name-source") ?? bootstrapActionContract.inputs["request-name-source"].default,
|
|
312360
313120
|
INPUT_OPENAPI_VERSION: optionalInput(actionCore, "openapi-version") ?? "",
|
|
313121
|
+
INPUT_PRESERVE_OAS30_TYPE_NULL: optionalInput(actionCore, "preserve-oas30-type-null") ?? bootstrapActionContract.inputs["preserve-oas30-type-null"].default,
|
|
312361
313122
|
INPUT_BREAKING_CHANGE_MODE: optionalInput(actionCore, "breaking-change-mode") ?? bootstrapActionContract.inputs["breaking-change-mode"].default,
|
|
312362
313123
|
INPUT_BREAKING_BASELINE_SPEC_PATH: optionalInput(actionCore, "breaking-baseline-spec-path"),
|
|
312363
313124
|
INPUT_BREAKING_RULES_PATH: optionalInput(actionCore, "breaking-rules-path") ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
|
|
@@ -312468,6 +313229,27 @@ async function lintSpecViaCli(dependencies, workspaceId, specId) {
|
|
|
312468
313229
|
warnings
|
|
312469
313230
|
};
|
|
312470
313231
|
}
|
|
313232
|
+
function normalizeLintPath(value) {
|
|
313233
|
+
return value.trim().replace(/^\$\.?/, "").replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "");
|
|
313234
|
+
}
|
|
313235
|
+
function applyOas30TypeNullLintCompatibility(summary2, sourceTypeNullPaths) {
|
|
313236
|
+
const acceptedPaths = new Set(sourceTypeNullPaths.map(normalizeLintPath));
|
|
313237
|
+
if (acceptedPaths.size === 0) return summary2;
|
|
313238
|
+
const violations = summary2.violations.map((violation) => {
|
|
313239
|
+
const path10 = normalizeLintPath(violation.path ?? "");
|
|
313240
|
+
const issue2 = violation.issue ?? "";
|
|
313241
|
+
const isTypeEnumFinding = /["']?type["']? property/i.test(issue2) && /allowed values|must be equal to one of/i.test(issue2);
|
|
313242
|
+
if (violation.severity === "ERROR" && acceptedPaths.has(path10) && isTypeEnumFinding) {
|
|
313243
|
+
return { ...violation, severity: "WARNING" };
|
|
313244
|
+
}
|
|
313245
|
+
return violation;
|
|
313246
|
+
});
|
|
313247
|
+
return {
|
|
313248
|
+
errors: violations.filter((entry) => entry.severity === "ERROR").length,
|
|
313249
|
+
violations,
|
|
313250
|
+
warnings: violations.filter((entry) => entry.severity === "WARNING").length
|
|
313251
|
+
};
|
|
313252
|
+
}
|
|
312471
313253
|
function shouldRetrySpecFetch(error2) {
|
|
312472
313254
|
const retryability = classifySafeFetchRetryability(error2);
|
|
312473
313255
|
return retryability === "retryable" || retryability === "unknown";
|
|
@@ -312948,7 +313730,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
|
|
|
312948
313730
|
const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
|
|
312949
313731
|
const serviceConfigPath = path9.join(path9.dirname(path9.resolve(workspaceRoot, inputs.specPath)), "grpc_service_config.json");
|
|
312950
313732
|
try {
|
|
312951
|
-
grpcServiceConfigJson = (0,
|
|
313733
|
+
grpcServiceConfigJson = (0, import_node_fs6.readFileSync)(serviceConfigPath, "utf8");
|
|
312952
313734
|
dependencies.core.info(`Found gRPC service config at ${serviceConfigPath}; validating it against the proto contract`);
|
|
312953
313735
|
} catch {
|
|
312954
313736
|
}
|
|
@@ -312962,7 +313744,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
|
|
|
312962
313744
|
const resolved = path9.resolve(specDir, location2);
|
|
312963
313745
|
if (!resolved.startsWith(specDir + path9.sep)) return void 0;
|
|
312964
313746
|
try {
|
|
312965
|
-
return (0,
|
|
313747
|
+
return (0, import_node_fs6.readFileSync)(resolved, "utf8");
|
|
312966
313748
|
} catch {
|
|
312967
313749
|
return void 0;
|
|
312968
313750
|
}
|
|
@@ -313103,6 +313885,42 @@ async function createExtensibleContractCollection(workspaceId, built, inputs, de
|
|
|
313103
313885
|
}
|
|
313104
313886
|
async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
313105
313887
|
const outputs = createPlannedOutputs(inputs);
|
|
313888
|
+
const branchDecision = decideBranchTier(inputs);
|
|
313889
|
+
const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
|
|
313890
|
+
const canonicalProjectName = inputs.projectName;
|
|
313891
|
+
const workspaceName = createWorkspaceName(inputs);
|
|
313892
|
+
const aboutText = `Auto-provisioned by Postman for ${inputs.projectName}`;
|
|
313893
|
+
if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
|
|
313894
|
+
inputs = {
|
|
313895
|
+
...inputs,
|
|
313896
|
+
projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch)
|
|
313897
|
+
};
|
|
313898
|
+
dependencies.core.info(`branch-aware sync: preview asset set "${inputs.projectName}"`);
|
|
313899
|
+
} else if (branchDecision.tier === "channel" && branchDecision.channel) {
|
|
313900
|
+
inputs = {
|
|
313901
|
+
...inputs,
|
|
313902
|
+
projectName: channelAssetName(inputs.projectName, branchDecision.channel.code)
|
|
313903
|
+
};
|
|
313904
|
+
dependencies.core.info(`branch-aware sync: channel asset set "${inputs.projectName}"`);
|
|
313905
|
+
}
|
|
313906
|
+
const collectionBranchMarker = renderCollectionBranchMarker(branchDecision, inputs.repoUrl);
|
|
313907
|
+
if (branchDecision.tier !== "legacy") {
|
|
313908
|
+
outputs["sync-status"] = "synced";
|
|
313909
|
+
outputs["branch-decision"] = serializeBranchDecision(branchDecision);
|
|
313910
|
+
}
|
|
313911
|
+
if (!isCanonicalWriter) {
|
|
313912
|
+
const explicitCanonicalIds = [
|
|
313913
|
+
["spec-id", inputs.specId],
|
|
313914
|
+
["baseline-collection-id", inputs.baselineCollectionId],
|
|
313915
|
+
["smoke-collection-id", inputs.smokeCollectionId],
|
|
313916
|
+
["contract-collection-id", inputs.contractCollectionId]
|
|
313917
|
+
].filter(([, value]) => Boolean(value));
|
|
313918
|
+
if (explicitCanonicalIds.length > 0) {
|
|
313919
|
+
throw new Error(
|
|
313920
|
+
`CONTRACT_BRANCH_CANONICAL_WRITE: a ${branchDecision.tier} run must not mutate canonical assets, but explicit asset id input(s) were provided: ${explicitCanonicalIds.map(([name]) => name).join(", ")}. Remove them (preview/channel runs discover or create their own suffixed asset sets).`
|
|
313921
|
+
);
|
|
313922
|
+
}
|
|
313923
|
+
}
|
|
313106
313924
|
const requiresReleaseLabel = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
|
|
313107
313925
|
const releaseLabel = requiresReleaseLabel ? deriveReleaseLabel(inputs) : void 0;
|
|
313108
313926
|
if (requiresReleaseLabel && !releaseLabel) {
|
|
@@ -313110,11 +313928,23 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313110
313928
|
"Versioned spec or collection sync requires a release-label or derivable GitHub ref metadata"
|
|
313111
313929
|
);
|
|
313112
313930
|
}
|
|
313113
|
-
const collectionAssetProjectName = inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
|
|
313114
|
-
const
|
|
313115
|
-
const
|
|
313116
|
-
const stateStore =
|
|
313117
|
-
|
|
313931
|
+
const collectionAssetProjectName = branchDecision.tier === "channel" ? canonicalProjectName : inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
|
|
313932
|
+
const rawStateStore = resolveResourcesStateStore(dependencies);
|
|
313933
|
+
const trackedState = rawStateStore.read();
|
|
313934
|
+
const stateStore = isCanonicalWriter ? rawStateStore : {
|
|
313935
|
+
read: rawStateStore.read,
|
|
313936
|
+
write: () => {
|
|
313937
|
+
dependencies.core.info(
|
|
313938
|
+
`branch-aware sync: skipping .postman/resources.yaml write on ${branchDecision.tier} run (canonical-only tracked state)`
|
|
313939
|
+
);
|
|
313940
|
+
}
|
|
313941
|
+
};
|
|
313942
|
+
const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
|
|
313943
|
+
if (!isCanonicalWriter && trackedState?.cloudResources) {
|
|
313944
|
+
dependencies.core.info(
|
|
313945
|
+
"branch-aware sync: canonical asset ids in .postman/resources.yaml are not resolved on a non-canonical run"
|
|
313946
|
+
);
|
|
313947
|
+
}
|
|
313118
313948
|
const writableResourcesState = resourcesState ?? {};
|
|
313119
313949
|
const additionalCollections = loadAdditionalCollectionFiles(
|
|
313120
313950
|
inputs.additionalCollectionsDir,
|
|
@@ -313134,16 +313964,19 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313134
313964
|
}
|
|
313135
313965
|
let previousSpecContent;
|
|
313136
313966
|
let previousSpecRollbackHash;
|
|
313967
|
+
let specContentUnchanged = false;
|
|
313137
313968
|
let detectedOpenapiVersion = "3.0";
|
|
313138
313969
|
let contractIndex;
|
|
313139
313970
|
let sourceSpecContent = "";
|
|
313971
|
+
let sourceTypeNullPaths = [];
|
|
313972
|
+
let preserveSourceSpecBytes = false;
|
|
313140
313973
|
const rawSpecContent = await runGroup(
|
|
313141
313974
|
dependencies.core,
|
|
313142
313975
|
"Read API Spec",
|
|
313143
313976
|
async () => {
|
|
313144
313977
|
if (inputs.specPath) {
|
|
313145
313978
|
const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
|
|
313146
|
-
return (0,
|
|
313979
|
+
return (0, import_node_fs6.readFileSync)(path9.resolve(workspaceRoot, inputs.specPath), "utf8");
|
|
313147
313980
|
}
|
|
313148
313981
|
if (dependencies.specFetcher === fetch) {
|
|
313149
313982
|
return safeFetchText(inputs.specUrl, { depth: 0 });
|
|
@@ -313179,6 +314012,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313179
314012
|
};
|
|
313180
314013
|
const rootKey = inputs.specPath ? void 0 : normalizeRef(inputs.specUrl);
|
|
313181
314014
|
const loaderOptions = {
|
|
314015
|
+
preserveOas30TypeNull: Boolean(inputs.preserveOas30TypeNull),
|
|
313182
314016
|
fetchText: async (url, fetchOptions) => {
|
|
313183
314017
|
if (rootKey && normalizeRef(url) === rootKey) return rawSpecContent;
|
|
313184
314018
|
if (dependencies.specFetcher === fetch) return safeFetchText(url, fetchOptions);
|
|
@@ -313187,6 +314021,8 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313187
314021
|
};
|
|
313188
314022
|
const loaded = inputs.specPath ? await loadOpenApiContractSpecFromPath(inputs.specPath, loaderOptions) : await loadOpenApiContractSpec(inputs.specUrl, loaderOptions);
|
|
313189
314023
|
sourceSpecContent = loaded.content;
|
|
314024
|
+
sourceTypeNullPaths = loaded.sourceTypeNullPaths;
|
|
314025
|
+
preserveSourceSpecBytes = sourceTypeNullPaths.length > 0;
|
|
313190
314026
|
const document2 = normalizeSpecDocument(
|
|
313191
314027
|
loaded.bundledContent,
|
|
313192
314028
|
(msg) => dependencies.core.warning(msg)
|
|
@@ -313209,11 +314045,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313209
314045
|
`Unable to verify existing Spec Hub OpenAPI version for spec-id ${specId}; clear spec-id to create a fresh spec`
|
|
313210
314046
|
);
|
|
313211
314047
|
}
|
|
313212
|
-
previousSpecContent = normalizeSpecDocument(
|
|
314048
|
+
previousSpecContent = preserveSourceSpecBytes ? previousRaw : normalizeSpecDocument(
|
|
313213
314049
|
previousRaw,
|
|
313214
314050
|
(msg) => dependencies.core.warning(`Previous spec normalization: ${msg}`)
|
|
313215
314051
|
);
|
|
313216
|
-
previousSpecRollbackHash = (0,
|
|
314052
|
+
previousSpecRollbackHash = (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex");
|
|
313217
314053
|
const existingSpecType = normalizeSpecTypeFromContent(previousSpecContent);
|
|
313218
314054
|
if (existingSpecType !== incomingSpecType) {
|
|
313219
314055
|
throw new Error(
|
|
@@ -313224,6 +314060,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313224
314060
|
dependencies.core.info(
|
|
313225
314061
|
`Auto-detected OpenAPI version from spec content: ${detectedOpenapiVersion}`
|
|
313226
314062
|
);
|
|
314063
|
+
if (preserveSourceSpecBytes) {
|
|
314064
|
+
dependencies.core.info(
|
|
314065
|
+
`Preserving original OpenAPI source bytes; accepted ${sourceTypeNullPaths.length} legacy type: null member(s) for internal validation`
|
|
314066
|
+
);
|
|
314067
|
+
return loaded.content;
|
|
314068
|
+
}
|
|
313227
314069
|
return document2;
|
|
313228
314070
|
}
|
|
313229
314071
|
);
|
|
@@ -313259,6 +314101,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313259
314101
|
`OpenAPI breaking-change check failed: ${breakingChangeResult.message || "breaking changes detected"}`
|
|
313260
314102
|
);
|
|
313261
314103
|
}
|
|
314104
|
+
const uploadSpecContent = embedSpecBranchMarker(
|
|
314105
|
+
specContent,
|
|
314106
|
+
branchDecision,
|
|
314107
|
+
inputs.repoUrl
|
|
314108
|
+
);
|
|
313262
314109
|
const provisioned = await provisionWorkspace(
|
|
313263
314110
|
inputs,
|
|
313264
314111
|
dependencies,
|
|
@@ -313383,10 +314230,17 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313383
314230
|
specId ? "Update Spec in Spec Hub" : "Upload Spec to Spec Hub",
|
|
313384
314231
|
async () => {
|
|
313385
314232
|
if (specId) {
|
|
313386
|
-
|
|
313387
|
-
|
|
313388
|
-
|
|
313389
|
-
|
|
314233
|
+
if (previousSpecContent !== void 0 && (0, import_node_crypto6.createHash)("sha256").update(uploadSpecContent).digest("hex") === (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex")) {
|
|
314234
|
+
specContentUnchanged = true;
|
|
314235
|
+
dependencies.core.info(
|
|
314236
|
+
`Spec content unchanged (sha256 match); skipping Spec Hub update and version tag for ${specId}.`
|
|
314237
|
+
);
|
|
314238
|
+
} else {
|
|
314239
|
+
dependencies.core.info(
|
|
314240
|
+
`Updating existing spec ${specId} (detected version: ${detectedOpenapiVersion}). Note: the spec type (OPENAPI:3.0 / OPENAPI:3.1) is set at creation and cannot be changed on update. If you changed OpenAPI versions, clear the spec-id input to create a fresh spec.`
|
|
314241
|
+
);
|
|
314242
|
+
await dependencies.postman.updateSpec(specId, uploadSpecContent, workspaceId);
|
|
314243
|
+
}
|
|
313390
314244
|
} else {
|
|
313391
314245
|
specId = await dependencies.postman.uploadSpec(
|
|
313392
314246
|
workspaceId || "",
|
|
@@ -313394,7 +314248,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313394
314248
|
inputs,
|
|
313395
314249
|
inputs.specSyncMode === "version" ? releaseLabel : void 0
|
|
313396
314250
|
),
|
|
313397
|
-
|
|
314251
|
+
uploadSpecContent,
|
|
313398
314252
|
detectedOpenapiVersion
|
|
313399
314253
|
);
|
|
313400
314254
|
}
|
|
@@ -313411,8 +314265,9 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313411
314265
|
}
|
|
313412
314266
|
)
|
|
313413
314267
|
);
|
|
314268
|
+
outputs["spec-content-changed"] = isCanonicalWriter && !specContentUnchanged ? "true" : "false";
|
|
313414
314269
|
if (lintEnabled) {
|
|
313415
|
-
|
|
314270
|
+
let lintSummary = await runRollbackStage(
|
|
313416
314271
|
"Lint Spec via Postman CLI",
|
|
313417
314272
|
async () => runGroup(
|
|
313418
314273
|
dependencies.core,
|
|
@@ -313420,6 +314275,16 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313420
314275
|
async () => lintSpecViaCli(dependencies, workspaceId || "", outputs["spec-id"])
|
|
313421
314276
|
)
|
|
313422
314277
|
);
|
|
314278
|
+
if (preserveSourceSpecBytes) {
|
|
314279
|
+
const originalErrorCount = lintSummary.errors;
|
|
314280
|
+
lintSummary = applyOas30TypeNullLintCompatibility(lintSummary, sourceTypeNullPaths);
|
|
314281
|
+
const acceptedCount = originalErrorCount - lintSummary.errors;
|
|
314282
|
+
if (acceptedCount > 0) {
|
|
314283
|
+
dependencies.core.info(
|
|
314284
|
+
`Accepted ${acceptedCount} Postman CLI type: null finding(s) covered by preserve-oas30-type-null`
|
|
314285
|
+
);
|
|
314286
|
+
}
|
|
314287
|
+
}
|
|
313423
314288
|
outputs["lint-summary-json"] = JSON.stringify({
|
|
313424
314289
|
errors: lintSummary.errors,
|
|
313425
314290
|
total: lintSummary.violations.length,
|
|
@@ -313444,7 +314309,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313444
314309
|
});
|
|
313445
314310
|
dependencies.core.warning("lint skipped: governance errors not enforced (no postman-api-key)");
|
|
313446
314311
|
}
|
|
313447
|
-
|
|
314312
|
+
if (specContentUnchanged) {
|
|
314313
|
+
outputs["baseline-collection-id"] = baselineCollectionId || "";
|
|
314314
|
+
outputs["smoke-collection-id"] = smokeCollectionId || "";
|
|
314315
|
+
outputs["contract-collection-id"] = contractCollectionId || "";
|
|
314316
|
+
dependencies.core.info("Spec content unchanged; skipping collection regeneration and version finalization.");
|
|
314317
|
+
} else await runRollbackStage(
|
|
313448
314318
|
"Generate Collections from Spec",
|
|
313449
314319
|
async () => runGroup(
|
|
313450
314320
|
dependencies.core,
|
|
@@ -313483,10 +314353,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313483
314353
|
return;
|
|
313484
314354
|
}
|
|
313485
314355
|
}
|
|
314356
|
+
const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
|
|
313486
314357
|
outputs[outputKey] = await dependencies.postman.generateCollection(
|
|
313487
314358
|
specId2,
|
|
313488
314359
|
assetProjectName,
|
|
313489
|
-
|
|
314360
|
+
effectivePrefix,
|
|
313490
314361
|
inputs.folderStrategy,
|
|
313491
314362
|
inputs.nestedFolderHierarchy,
|
|
313492
314363
|
inputs.requestNameSource
|
|
@@ -313507,6 +314378,32 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313507
314378
|
await ensureCollection(BASELINE_COLLECTION_PREFIX, baselineCollectionId, "baseline-collection-id");
|
|
313508
314379
|
await ensureCollection(SMOKE_COLLECTION_PREFIX, smokeCollectionId, "smoke-collection-id");
|
|
313509
314380
|
await ensureCollection(CONTRACT_COLLECTION_PREFIX, contractCollectionId, "contract-collection-id");
|
|
314381
|
+
if (dependencies.postman.adoptGeneratedCollection) {
|
|
314382
|
+
const reelect = async (prefix, outputKey) => {
|
|
314383
|
+
const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
|
|
314384
|
+
const preferred = outputs[outputKey];
|
|
314385
|
+
if (!preferred) return;
|
|
314386
|
+
outputs[outputKey] = await dependencies.postman.adoptGeneratedCollection(
|
|
314387
|
+
specId2,
|
|
314388
|
+
assetProjectName,
|
|
314389
|
+
effectivePrefix,
|
|
314390
|
+
preferred
|
|
314391
|
+
);
|
|
314392
|
+
};
|
|
314393
|
+
await reelect(BASELINE_COLLECTION_PREFIX, "baseline-collection-id");
|
|
314394
|
+
await reelect(SMOKE_COLLECTION_PREFIX, "smoke-collection-id");
|
|
314395
|
+
await reelect(CONTRACT_COLLECTION_PREFIX, "contract-collection-id");
|
|
314396
|
+
}
|
|
314397
|
+
if (collectionBranchMarker) {
|
|
314398
|
+
if (!dependencies.postman.updateCollectionDescription) {
|
|
314399
|
+
throw new Error("Branch-scoped collections require updateCollectionDescription support");
|
|
314400
|
+
}
|
|
314401
|
+
await Promise.all([
|
|
314402
|
+
outputs["baseline-collection-id"],
|
|
314403
|
+
outputs["smoke-collection-id"],
|
|
314404
|
+
outputs["contract-collection-id"]
|
|
314405
|
+
].filter(Boolean).map((id) => dependencies.postman.updateCollectionDescription(id, collectionBranchMarker)));
|
|
314406
|
+
}
|
|
313510
314407
|
if (!dependencies.postman.injectContractTests) {
|
|
313511
314408
|
throw new Error(
|
|
313512
314409
|
"Dynamic contract tests require injectContractTests support from the access-token gateway client"
|
|
@@ -313609,6 +314506,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313609
314506
|
workspaceId: workspaceId || ""
|
|
313610
314507
|
});
|
|
313611
314508
|
for (const result of additionalResults) {
|
|
314509
|
+
if (collectionBranchMarker) {
|
|
314510
|
+
if (!dependencies.postman.updateCollectionDescription) {
|
|
314511
|
+
throw new Error("Branch-scoped collections require updateCollectionDescription support");
|
|
314512
|
+
}
|
|
314513
|
+
await dependencies.postman.updateCollectionDescription(result.collectionId, collectionBranchMarker);
|
|
314514
|
+
}
|
|
313612
314515
|
completedExternalSideEffects.push(
|
|
313613
314516
|
`${result.operation}AdditionalCollection(${result.collectionId} from ${result.displayPath})`
|
|
313614
314517
|
);
|
|
@@ -313684,8 +314587,66 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313684
314587
|
}
|
|
313685
314588
|
return outputs;
|
|
313686
314589
|
}
|
|
314590
|
+
async function runGatedValidation(inputs, decision, actionCore) {
|
|
314591
|
+
actionCore.info(`branch-aware sync: gated run (${decision.reason}) \u2014 credential-free static validation, zero workspace writes`);
|
|
314592
|
+
const outputs = createPlannedOutputs(inputs);
|
|
314593
|
+
outputs["sync-status"] = "skipped-branch-gate";
|
|
314594
|
+
outputs["branch-decision"] = serializeBranchDecision(decision);
|
|
314595
|
+
let violations = [];
|
|
314596
|
+
let validated = false;
|
|
314597
|
+
try {
|
|
314598
|
+
let content;
|
|
314599
|
+
if (inputs.specPath) {
|
|
314600
|
+
content = (0, import_node_fs6.readFileSync)(inputs.specPath, "utf8");
|
|
314601
|
+
} else if (inputs.specUrl) {
|
|
314602
|
+
content = await safeFetchText(inputs.specUrl, { depth: 0 });
|
|
314603
|
+
}
|
|
314604
|
+
if (content) {
|
|
314605
|
+
const specType = detectSpecType(content, inputs.specPath);
|
|
314606
|
+
if (specType === "openapi") {
|
|
314607
|
+
const document2 = parseOpenApiDocument(content);
|
|
314608
|
+
const index = buildContractIndex(document2);
|
|
314609
|
+
violations = index.warnings;
|
|
314610
|
+
validated = true;
|
|
314611
|
+
} else {
|
|
314612
|
+
actionCore.info(`branch gate: static lint for spec type ${specType} runs through its protocol builder on publish; parse-only gate applied`);
|
|
314613
|
+
validated = true;
|
|
314614
|
+
}
|
|
314615
|
+
} else {
|
|
314616
|
+
actionCore.info("branch gate: no spec-url/spec-path provided; nothing to validate");
|
|
314617
|
+
}
|
|
314618
|
+
} catch (error2) {
|
|
314619
|
+
throw new Error(
|
|
314620
|
+
`branch gate: static validation failed: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
314621
|
+
{ cause: error2 }
|
|
314622
|
+
);
|
|
314623
|
+
}
|
|
314624
|
+
outputs["lint-summary-json"] = JSON.stringify({
|
|
314625
|
+
status: validated ? "static-only" : "skipped",
|
|
314626
|
+
reason: "branch-gated run: governance lint requires upload and runs on publish/preview syncs",
|
|
314627
|
+
errors: 0,
|
|
314628
|
+
warnings: violations.length,
|
|
314629
|
+
total: violations.length,
|
|
314630
|
+
violations: violations.map((issue2) => ({ issue: issue2, severity: "WARNING" }))
|
|
314631
|
+
});
|
|
314632
|
+
for (const [name, value] of Object.entries(outputs)) {
|
|
314633
|
+
actionCore.setOutput(name, value);
|
|
314634
|
+
}
|
|
314635
|
+
return outputs;
|
|
314636
|
+
}
|
|
313687
314637
|
async function runAction(actionCore = core_exports, actionExec = exec_exports, actionIo = io_exports) {
|
|
313688
314638
|
const inputs = readActionInputs(actionCore);
|
|
314639
|
+
const branchDecision = decideBranchTier(inputs);
|
|
314640
|
+
if (branchDecision.tier === "gated") {
|
|
314641
|
+
return runGatedValidation(inputs, branchDecision, actionCore);
|
|
314642
|
+
}
|
|
314643
|
+
if (!inputs.postmanApiKey && !inputs.postmanAccessToken) {
|
|
314644
|
+
throw new Error("One of postman-api-key or postman-access-token is required for a writing sync.");
|
|
314645
|
+
}
|
|
314646
|
+
if (branchDecision.tier !== "legacy") {
|
|
314647
|
+
actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
|
|
314648
|
+
process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
|
|
314649
|
+
}
|
|
313689
314650
|
await mintAccessTokenIfNeeded2(inputs, {
|
|
313690
314651
|
info: (message) => actionCore.info(message),
|
|
313691
314652
|
warning: (message) => actionCore.warning(message)
|
|
@@ -313762,6 +314723,7 @@ function createRoutingPostmanClient(options) {
|
|
|
313762
314723
|
updateSpec: requireAccessToken("updateSpec"),
|
|
313763
314724
|
getSpecContent: requireAccessToken("getSpecContent"),
|
|
313764
314725
|
generateCollection: requireAccessToken("generateCollection"),
|
|
314726
|
+
adoptGeneratedCollection: requireAccessToken("adoptGeneratedCollection"),
|
|
313765
314727
|
createWorkspace: requireAccessToken("createWorkspace"),
|
|
313766
314728
|
getWorkspaceVisibility: requireAccessToken("getWorkspaceVisibility"),
|
|
313767
314729
|
getWorkspaceGitRepoUrl: requireAccessToken("getWorkspaceGitRepoUrl"),
|
|
@@ -313771,10 +314733,13 @@ function createRoutingPostmanClient(options) {
|
|
|
313771
314733
|
inviteRequesterToWorkspace: requireAccessToken("inviteRequesterToWorkspace"),
|
|
313772
314734
|
injectTests: requireAccessToken("injectTests"),
|
|
313773
314735
|
tagCollection: requireAccessToken("tagCollection"),
|
|
314736
|
+
tagSpecVersion: requireAccessToken("tagSpecVersion"),
|
|
314737
|
+
listSpecVersionTags: requireAccessToken("listSpecVersionTags"),
|
|
313774
314738
|
deleteCollection: requireAccessToken("deleteCollection"),
|
|
313775
314739
|
injectContractTests: requireAccessToken("injectContractTests"),
|
|
313776
314740
|
createCollection: requireAccessToken("createCollection"),
|
|
313777
|
-
updateCollection: requireAccessToken("updateCollection")
|
|
314741
|
+
updateCollection: requireAccessToken("updateCollection"),
|
|
314742
|
+
updateCollectionDescription: requireAccessToken("updateCollectionDescription")
|
|
313778
314743
|
};
|
|
313779
314744
|
}
|
|
313780
314745
|
return {
|
|
@@ -313786,6 +314751,7 @@ function createRoutingPostmanClient(options) {
|
|
|
313786
314751
|
// not a reason to reach for the API key.
|
|
313787
314752
|
uploadSpec: (workspaceId, projectName, specContent, openapiVersion) => gateway.uploadSpec(workspaceId, projectName, specContent, openapiVersion ?? "3.0"),
|
|
313788
314753
|
generateCollection: (specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) => gateway.generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource),
|
|
314754
|
+
adoptGeneratedCollection: (specId, projectName, prefix, preferredId) => gateway.adoptGeneratedCollection(specId, projectName, prefix, preferredId),
|
|
313789
314755
|
updateSpec: (specId, specContent, workspaceId) => gateway.updateSpec(specId, specContent, workspaceId),
|
|
313790
314756
|
getSpecContent: (specId) => gateway.getSpecContent(specId),
|
|
313791
314757
|
createWorkspace: (name, about, targetTeamId) => gateway.createWorkspace(name, about, targetTeamId),
|
|
@@ -313797,6 +314763,8 @@ function createRoutingPostmanClient(options) {
|
|
|
313797
314763
|
// + tagging surfaces (live-proven). PMAK is reserved for token minting, so
|
|
313798
314764
|
// these never fall back to the API key even when one is present.
|
|
313799
314765
|
injectTests: (collectionId, type) => gateway.injectTests(collectionId, type),
|
|
314766
|
+
tagSpecVersion: (specId, name) => gateway.tagSpecVersion(specId, name),
|
|
314767
|
+
listSpecVersionTags: (specId) => gateway.listSpecVersionTags(specId),
|
|
313800
314768
|
tagCollection: (collectionId, tags) => gateway.tagCollection(collectionId, tags),
|
|
313801
314769
|
// Sub-team (squad) enumeration over the gateway `ums` service. Access-token
|
|
313802
314770
|
// only — never PMAK — so org-mode detection no longer needs a PMAK GET /teams.
|
|
@@ -313814,7 +314782,8 @@ function createRoutingPostmanClient(options) {
|
|
|
313814
314782
|
// so no asset op ever reaches for the API key.
|
|
313815
314783
|
injectContractTests: (collectionUid, index) => gateway.injectContractTests(collectionUid, index),
|
|
313816
314784
|
createCollection: (workspaceId, collection, options2) => gateway.createCollection(workspaceId, collection, options2),
|
|
313817
|
-
updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection)
|
|
314785
|
+
updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection),
|
|
314786
|
+
updateCollectionDescription: (collectionUid, description) => gateway.updateCollectionDescription(collectionUid, description)
|
|
313818
314787
|
};
|
|
313819
314788
|
}
|
|
313820
314789
|
function createBootstrapDependencies(inputs, factories, orgMode = false) {
|