@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/index.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,
|
|
@@ -253780,19 +253780,25 @@ var require_cjs4 = __commonJS({
|
|
|
253780
253780
|
// src/index.ts
|
|
253781
253781
|
var index_exports = {};
|
|
253782
253782
|
__export(index_exports, {
|
|
253783
|
+
applyOas30TypeNullLintCompatibility: () => applyOas30TypeNullLintCompatibility,
|
|
253784
|
+
buildSpecVersionUrl: () => buildSpecVersionUrl,
|
|
253783
253785
|
createBootstrapDependencies: () => createBootstrapDependencies,
|
|
253784
253786
|
createExtensibleContractCollection: () => createExtensibleContractCollection,
|
|
253785
253787
|
createPlannedOutputs: () => createPlannedOutputs,
|
|
253786
253788
|
createRoutingPostmanClient: () => createRoutingPostmanClient,
|
|
253789
|
+
decideBranchTier: () => decideBranchTier,
|
|
253790
|
+
embedSpecBranchMarker: () => embedSpecBranchMarker,
|
|
253787
253791
|
ensurePostmanCli: () => ensurePostmanCli,
|
|
253788
253792
|
getInput: () => getInput2,
|
|
253789
253793
|
lintSpecViaCli: () => lintSpecViaCli,
|
|
253790
253794
|
mintAccessTokenIfNeeded: () => mintAccessTokenIfNeeded2,
|
|
253791
253795
|
normalizeSpecDocument: () => normalizeSpecDocument,
|
|
253792
253796
|
readActionInputs: () => readActionInputs,
|
|
253797
|
+
renderCollectionBranchMarker: () => renderCollectionBranchMarker,
|
|
253793
253798
|
resolveInputs: () => resolveInputs,
|
|
253794
253799
|
runAction: () => runAction,
|
|
253795
|
-
runBootstrap: () => runBootstrap
|
|
253800
|
+
runBootstrap: () => runBootstrap,
|
|
253801
|
+
runGatedValidation: () => runGatedValidation
|
|
253796
253802
|
});
|
|
253797
253803
|
module.exports = __toCommonJS(index_exports);
|
|
253798
253804
|
|
|
@@ -256216,8 +256222,8 @@ function getIDToken(aud) {
|
|
|
256216
256222
|
}
|
|
256217
256223
|
|
|
256218
256224
|
// src/index.ts
|
|
256219
|
-
var
|
|
256220
|
-
var
|
|
256225
|
+
var import_node_crypto6 = require("node:crypto");
|
|
256226
|
+
var import_node_fs6 = require("node:fs");
|
|
256221
256227
|
var path9 = __toESM(require("node:path"), 1);
|
|
256222
256228
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
256223
256229
|
|
|
@@ -256325,6 +256331,12 @@ var bootstrapActionContract = {
|
|
|
256325
256331
|
default: "",
|
|
256326
256332
|
allowedValues: ["3.0", "3.1"]
|
|
256327
256333
|
},
|
|
256334
|
+
"preserve-oas30-type-null": {
|
|
256335
|
+
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.",
|
|
256336
|
+
required: false,
|
|
256337
|
+
default: "false",
|
|
256338
|
+
allowedValues: ["true", "false"]
|
|
256339
|
+
},
|
|
256328
256340
|
"breaking-change-mode": {
|
|
256329
256341
|
description: "OpenAPI breaking-change comparison mode.",
|
|
256330
256342
|
required: false,
|
|
@@ -256381,6 +256393,20 @@ var bootstrapActionContract = {
|
|
|
256381
256393
|
default: "warn",
|
|
256382
256394
|
allowedValues: ["enforce", "warn"]
|
|
256383
256395
|
},
|
|
256396
|
+
"branch-strategy": {
|
|
256397
|
+
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.",
|
|
256398
|
+
required: false,
|
|
256399
|
+
default: "legacy",
|
|
256400
|
+
allowedValues: ["legacy", "preview", "publish-gate"]
|
|
256401
|
+
},
|
|
256402
|
+
"canonical-branch": {
|
|
256403
|
+
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.",
|
|
256404
|
+
required: false
|
|
256405
|
+
},
|
|
256406
|
+
"channels": {
|
|
256407
|
+
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.',
|
|
256408
|
+
required: false
|
|
256409
|
+
},
|
|
256384
256410
|
"integration-backend": {
|
|
256385
256411
|
description: "Integration backend for downstream workspace connectivity.",
|
|
256386
256412
|
required: false,
|
|
@@ -256450,6 +256476,21 @@ var bootstrapActionContract = {
|
|
|
256450
256476
|
},
|
|
256451
256477
|
"breaking-change-summary-json": {
|
|
256452
256478
|
description: "JSON summary of the OpenAPI breaking-change check."
|
|
256479
|
+
},
|
|
256480
|
+
"sync-status": {
|
|
256481
|
+
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."
|
|
256482
|
+
},
|
|
256483
|
+
"branch-decision": {
|
|
256484
|
+
description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
|
|
256485
|
+
},
|
|
256486
|
+
"spec-version-tag": {
|
|
256487
|
+
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)."
|
|
256488
|
+
},
|
|
256489
|
+
"spec-version-url": {
|
|
256490
|
+
description: "Reserved for the repo-sync finalizer; bootstrap does not tag before complete onboarding."
|
|
256491
|
+
},
|
|
256492
|
+
"spec-content-changed": {
|
|
256493
|
+
description: "Whether bootstrap changed canonical spec content; repo-sync uses this to skip native version tags on no-op syncs."
|
|
256453
256494
|
}
|
|
256454
256495
|
},
|
|
256455
256496
|
retainedBehavior: [
|
|
@@ -257576,6 +257617,15 @@ function assertSupportedLocalViewContract(node, options) {
|
|
|
257576
257617
|
}
|
|
257577
257618
|
|
|
257578
257619
|
// src/lib/postman/additional-collections.ts
|
|
257620
|
+
var RESOURCES_STATE_VERSION = 2;
|
|
257621
|
+
var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
|
|
257622
|
+
var StateUnreadableError = class extends Error {
|
|
257623
|
+
code = "CONTRACT_STATE_UNREADABLE";
|
|
257624
|
+
constructor(message) {
|
|
257625
|
+
super(`CONTRACT_STATE_UNREADABLE: ${message}`);
|
|
257626
|
+
this.name = "StateUnreadableError";
|
|
257627
|
+
}
|
|
257628
|
+
};
|
|
257579
257629
|
var ADDITIONAL_COLLECTION_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".yaml", ".yml"]);
|
|
257580
257630
|
var POSTMAN_COLLECTION_V21_SCHEMA_FRAGMENT = "/collection/v2.1.0/collection.json";
|
|
257581
257631
|
var V3_DEFINITION_PATH = import_node_path2.default.join(".resources", "definition.yaml");
|
|
@@ -257631,16 +257681,46 @@ function resourcesStatePath() {
|
|
|
257631
257681
|
return import_node_path2.default.join(workspaceRootForLocalInputs(), RESOURCES_PATH);
|
|
257632
257682
|
}
|
|
257633
257683
|
function readResourcesState() {
|
|
257684
|
+
let raw;
|
|
257634
257685
|
try {
|
|
257635
|
-
|
|
257686
|
+
raw = (0, import_node_fs2.readFileSync)(resourcesStatePath(), "utf8");
|
|
257636
257687
|
} catch {
|
|
257637
257688
|
return null;
|
|
257638
257689
|
}
|
|
257690
|
+
let parsed;
|
|
257691
|
+
try {
|
|
257692
|
+
parsed = (0, import_yaml.parse)(raw);
|
|
257693
|
+
} catch (error2) {
|
|
257694
|
+
throw new StateUnreadableError(
|
|
257695
|
+
`.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.`
|
|
257696
|
+
);
|
|
257697
|
+
}
|
|
257698
|
+
if (parsed === null || parsed === void 0) {
|
|
257699
|
+
return null;
|
|
257700
|
+
}
|
|
257701
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
257702
|
+
throw new StateUnreadableError(
|
|
257703
|
+
".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
|
|
257704
|
+
);
|
|
257705
|
+
}
|
|
257706
|
+
const state = parsed;
|
|
257707
|
+
if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
|
|
257708
|
+
throw new StateUnreadableError(
|
|
257709
|
+
`.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
|
|
257710
|
+
);
|
|
257711
|
+
}
|
|
257712
|
+
if (state.canonical && !state.cloudResources) {
|
|
257713
|
+
state.cloudResources = { ...state.canonical };
|
|
257714
|
+
}
|
|
257715
|
+
return state;
|
|
257639
257716
|
}
|
|
257640
257717
|
function writeResourcesState(state) {
|
|
257641
257718
|
const target = resourcesStatePath();
|
|
257642
257719
|
(0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(target), { recursive: true });
|
|
257643
|
-
|
|
257720
|
+
const canonical = state.cloudResources ?? state.canonical ?? {};
|
|
257721
|
+
const persisted = { ...state, version: RESOURCES_STATE_VERSION, canonical };
|
|
257722
|
+
delete persisted.cloudResources;
|
|
257723
|
+
(0, import_node_fs2.writeFileSync)(target, (0, import_yaml.stringify)(persisted), "utf8");
|
|
257644
257724
|
}
|
|
257645
257725
|
function findCloudResourceId(map, matcher) {
|
|
257646
257726
|
if (!map) {
|
|
@@ -257650,7 +257730,7 @@ function findCloudResourceId(map, matcher) {
|
|
|
257650
257730
|
return match?.[1];
|
|
257651
257731
|
}
|
|
257652
257732
|
function findExistingAdditionalCollectionId(resourcesState, resourcePath) {
|
|
257653
|
-
return resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
|
|
257733
|
+
return resourcesState?.canonical?.additionalCollections?.[resourcePath] ?? resourcesState?.canonical?.collections?.[resourcePath] ?? resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
|
|
257654
257734
|
}
|
|
257655
257735
|
function resolveAdditionalCollectionsDir(directoryInput) {
|
|
257656
257736
|
const workspaceRoot = workspaceRootForLocalInputs();
|
|
@@ -258652,6 +258732,8 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258652
258732
|
* endpoint aborts rather than blocking the run forever. */
|
|
258653
258733
|
static REQUEST_TIMEOUT_MS = 3e4;
|
|
258654
258734
|
static POSTMAN_APP_VERSION_URL = `https://dl.pstmn.io/update/status?currentVersion=${_BifrostInternalIntegrationAdapter.MINIMUM_POSTMAN_APP_VERSION}&platform=osx_arm64`;
|
|
258735
|
+
/** Concurrent dual-trigger previews share one spec; peer sync holds a 423 lock. */
|
|
258736
|
+
static SYNC_LOCKED_MAX_RETRIES = 6;
|
|
258655
258737
|
accessToken;
|
|
258656
258738
|
tokenProvider;
|
|
258657
258739
|
appVersionPromise;
|
|
@@ -258660,6 +258742,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258660
258742
|
gatewayBaseUrl;
|
|
258661
258743
|
orgMode;
|
|
258662
258744
|
secretMasker;
|
|
258745
|
+
sleep;
|
|
258663
258746
|
teamId;
|
|
258664
258747
|
constructor(options) {
|
|
258665
258748
|
this.accessToken = String(options.accessToken || "").trim();
|
|
@@ -258673,6 +258756,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258673
258756
|
).replace(/\/+$/, "");
|
|
258674
258757
|
this.orgMode = options.orgMode ?? false;
|
|
258675
258758
|
this.secretMasker = options.secretMasker ?? createSecretMasker([this.accessToken]);
|
|
258759
|
+
this.sleep = options.sleep ?? ((delayMs) => new Promise((resolve5) => setTimeout(resolve5, delayMs)));
|
|
258676
258760
|
this.teamId = String(options.teamId || "").trim();
|
|
258677
258761
|
}
|
|
258678
258762
|
configureTeamContext(teamId, orgMode) {
|
|
@@ -258879,55 +258963,72 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
|
|
|
258879
258963
|
if (collections.length === 0) {
|
|
258880
258964
|
return;
|
|
258881
258965
|
}
|
|
258882
|
-
const
|
|
258883
|
-
|
|
258884
|
-
|
|
258885
|
-
|
|
258886
|
-
|
|
258887
|
-
|
|
258888
|
-
|
|
258889
|
-
|
|
258890
|
-
|
|
258891
|
-
|
|
258892
|
-
|
|
258966
|
+
const body2 = collections.map((collection) => ({
|
|
258967
|
+
collectionId: collection.collectionId,
|
|
258968
|
+
...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
|
|
258969
|
+
}));
|
|
258970
|
+
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
258971
|
+
const response = await this.proxyRequest(
|
|
258972
|
+
"specification",
|
|
258973
|
+
"put",
|
|
258974
|
+
`/specifications/${specificationId}/collections`,
|
|
258975
|
+
body2
|
|
258976
|
+
);
|
|
258977
|
+
if (response.ok) {
|
|
258978
|
+
return;
|
|
258979
|
+
}
|
|
258980
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
258981
|
+
method: "POST",
|
|
258982
|
+
requestHeaders: {
|
|
258983
|
+
"Content-Type": "application/json",
|
|
258984
|
+
"x-access-token": this.currentToken(),
|
|
258985
|
+
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258986
|
+
},
|
|
258987
|
+
secretValues: [this.currentToken()],
|
|
258988
|
+
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258989
|
+
});
|
|
258990
|
+
if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
|
|
258991
|
+
await this.sleep(2e3 * Math.pow(2, lockedAttempt));
|
|
258992
|
+
continue;
|
|
258993
|
+
}
|
|
258994
|
+
const advised = adviseFromHttpError(
|
|
258995
|
+
httpErr,
|
|
258996
|
+
this.adviceContext("collection-to-specification linking")
|
|
258997
|
+
);
|
|
258998
|
+
throw advised ?? httpErr;
|
|
258893
258999
|
}
|
|
258894
|
-
const httpErr = await HttpError.fromResponse(response, {
|
|
258895
|
-
method: "POST",
|
|
258896
|
-
requestHeaders: {
|
|
258897
|
-
"Content-Type": "application/json",
|
|
258898
|
-
"x-access-token": this.currentToken(),
|
|
258899
|
-
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258900
|
-
},
|
|
258901
|
-
secretValues: [this.currentToken()],
|
|
258902
|
-
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258903
|
-
});
|
|
258904
|
-
const advised = adviseFromHttpError(
|
|
258905
|
-
httpErr,
|
|
258906
|
-
this.adviceContext("collection-to-specification linking")
|
|
258907
|
-
);
|
|
258908
|
-
throw advised ?? httpErr;
|
|
258909
259000
|
}
|
|
258910
259001
|
async syncCollection(specificationId, collectionId) {
|
|
258911
|
-
|
|
258912
|
-
|
|
258913
|
-
|
|
258914
|
-
|
|
258915
|
-
|
|
258916
|
-
|
|
258917
|
-
|
|
259002
|
+
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
259003
|
+
const response = await this.proxyRequest(
|
|
259004
|
+
"specification",
|
|
259005
|
+
"post",
|
|
259006
|
+
`/specifications/${specificationId}/collections/${collectionId}/sync`
|
|
259007
|
+
);
|
|
259008
|
+
if (response.ok) {
|
|
259009
|
+
return;
|
|
259010
|
+
}
|
|
259011
|
+
const bodyText = await response.clone().text().catch(() => "");
|
|
259012
|
+
if (response.status === 400 && /already in sync/i.test(bodyText)) {
|
|
259013
|
+
return;
|
|
259014
|
+
}
|
|
259015
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
259016
|
+
method: "POST",
|
|
259017
|
+
requestHeaders: {
|
|
259018
|
+
"Content-Type": "application/json",
|
|
259019
|
+
"x-access-token": this.currentToken(),
|
|
259020
|
+
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
259021
|
+
},
|
|
259022
|
+
secretValues: [this.currentToken()],
|
|
259023
|
+
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
259024
|
+
});
|
|
259025
|
+
if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
|
|
259026
|
+
await this.sleep(2e3 * Math.pow(2, lockedAttempt));
|
|
259027
|
+
continue;
|
|
259028
|
+
}
|
|
259029
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
|
|
259030
|
+
throw advised ?? httpErr;
|
|
258918
259031
|
}
|
|
258919
|
-
const httpErr = await HttpError.fromResponse(response, {
|
|
258920
|
-
method: "POST",
|
|
258921
|
-
requestHeaders: {
|
|
258922
|
-
"Content-Type": "application/json",
|
|
258923
|
-
"x-access-token": this.currentToken(),
|
|
258924
|
-
...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
|
|
258925
|
-
},
|
|
258926
|
-
secretValues: [this.currentToken()],
|
|
258927
|
-
url: `${this.bifrostBaseUrl}/ws/proxy`
|
|
258928
|
-
});
|
|
258929
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
|
|
258930
|
-
throw advised ?? httpErr;
|
|
258931
259032
|
}
|
|
258932
259033
|
async getWorkspaceGitRepoUrl(workspaceId) {
|
|
258933
259034
|
const response = await this.proxyRequest(
|
|
@@ -264072,6 +264173,16 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264072
264173
|
if (openapiVersion !== "3.0" && openapiVersion !== "3.1") {
|
|
264073
264174
|
throw new Error(`uploadSpec: unsupported openapiVersion "${openapiVersion}". Expected '3.0' or '3.1'.`);
|
|
264074
264175
|
}
|
|
264176
|
+
const before = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264177
|
+
const existing = adoptExactMatch(
|
|
264178
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264179
|
+
before,
|
|
264180
|
+
(entry) => entry.id
|
|
264181
|
+
);
|
|
264182
|
+
if (existing) {
|
|
264183
|
+
await this.updateSpec(existing.id, specContent, workspaceId);
|
|
264184
|
+
return existing.id;
|
|
264185
|
+
}
|
|
264075
264186
|
const specType = openapiVersion === "3.1" ? "OPENAPI:3.1" : "OPENAPI:3.0";
|
|
264076
264187
|
let created;
|
|
264077
264188
|
try {
|
|
@@ -264096,10 +264207,49 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264096
264207
|
if (!match) throw error2;
|
|
264097
264208
|
created = { data: { id: match.id } };
|
|
264098
264209
|
}
|
|
264099
|
-
|
|
264210
|
+
let specId = String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim();
|
|
264100
264211
|
if (!specId) {
|
|
264101
264212
|
throw new Error("Spec upload did not return an ID");
|
|
264102
264213
|
}
|
|
264214
|
+
let matches = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264215
|
+
if (before.length === 0 && matches.length <= 1) {
|
|
264216
|
+
await this.sleep(1e3);
|
|
264217
|
+
const settled = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264218
|
+
if (settled.length > 0) matches = settled;
|
|
264219
|
+
}
|
|
264220
|
+
if (matches.length > 1 && before.length === 0) {
|
|
264221
|
+
for (const duplicate of matches.slice(1)) {
|
|
264222
|
+
try {
|
|
264223
|
+
await this.deleteSpecification(duplicate.id);
|
|
264224
|
+
} catch (error2) {
|
|
264225
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264226
|
+
}
|
|
264227
|
+
}
|
|
264228
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
264229
|
+
matches = await this.findSpecificationsByExactName(workspaceId, projectName);
|
|
264230
|
+
if (matches.length <= 1) break;
|
|
264231
|
+
await this.sleep(250 * (attempt + 1));
|
|
264232
|
+
}
|
|
264233
|
+
const converged = adoptExactMatch(
|
|
264234
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264235
|
+
matches,
|
|
264236
|
+
(entry) => entry.id
|
|
264237
|
+
);
|
|
264238
|
+
if (!converged) {
|
|
264239
|
+
throw new Error(`Concurrent specification create for ${projectName} did not converge`);
|
|
264240
|
+
}
|
|
264241
|
+
specId = converged.id;
|
|
264242
|
+
if (specId !== String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim()) {
|
|
264243
|
+
await this.updateSpec(specId, specContent, workspaceId);
|
|
264244
|
+
}
|
|
264245
|
+
} else {
|
|
264246
|
+
const verified = adoptExactMatch(
|
|
264247
|
+
`specification:${workspaceId}:${projectName}`,
|
|
264248
|
+
matches,
|
|
264249
|
+
(entry) => entry.id
|
|
264250
|
+
);
|
|
264251
|
+
if (verified) specId = verified.id;
|
|
264252
|
+
}
|
|
264103
264253
|
await this.gateway.requestJson({
|
|
264104
264254
|
service: "specification",
|
|
264105
264255
|
method: "get",
|
|
@@ -264107,6 +264257,14 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264107
264257
|
});
|
|
264108
264258
|
return specId;
|
|
264109
264259
|
}
|
|
264260
|
+
async deleteSpecification(specId) {
|
|
264261
|
+
await this.gateway.requestJson({
|
|
264262
|
+
service: "specification",
|
|
264263
|
+
method: "delete",
|
|
264264
|
+
path: `/specifications/${specId}`,
|
|
264265
|
+
retry: "none"
|
|
264266
|
+
});
|
|
264267
|
+
}
|
|
264110
264268
|
async findSpecificationsByExactName(workspaceId, name) {
|
|
264111
264269
|
const response = await this.gateway.requestJson({
|
|
264112
264270
|
service: "specification",
|
|
@@ -264170,6 +264328,41 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264170
264328
|
return void 0;
|
|
264171
264329
|
}
|
|
264172
264330
|
}
|
|
264331
|
+
/**
|
|
264332
|
+
* Native Spec Hub version tags (branch-aware sync P3.5). Tags attach to the
|
|
264333
|
+
* LATEST changelog group; the backend 409s when that group is already tagged
|
|
264334
|
+
* (VersionControlService). Callers handle 409 as idempotent-by-group.
|
|
264335
|
+
*/
|
|
264336
|
+
async tagSpecVersion(specId, name) {
|
|
264337
|
+
const trimmed = name.trim().slice(0, 255);
|
|
264338
|
+
const created = await this.gateway.requestJson({
|
|
264339
|
+
service: "specification",
|
|
264340
|
+
method: "post",
|
|
264341
|
+
path: `/specifications/${specId}/tags`,
|
|
264342
|
+
retry: "none",
|
|
264343
|
+
body: { name: trimmed }
|
|
264344
|
+
});
|
|
264345
|
+
const record = asRecord8(created?.data) ?? created ?? {};
|
|
264346
|
+
return {
|
|
264347
|
+
id: String(record.id ?? "").trim(),
|
|
264348
|
+
name: String(record.name ?? trimmed).trim()
|
|
264349
|
+
};
|
|
264350
|
+
}
|
|
264351
|
+
/** List a spec's native version tags (newest first per backend ordering). */
|
|
264352
|
+
async listSpecVersionTags(specId) {
|
|
264353
|
+
const response = await this.gateway.requestJson({
|
|
264354
|
+
service: "specification",
|
|
264355
|
+
method: "get",
|
|
264356
|
+
path: `/specifications/${specId}/tags`,
|
|
264357
|
+
query: { limit: "50" }
|
|
264358
|
+
});
|
|
264359
|
+
const entries = Array.isArray(response?.data) ? response.data : [];
|
|
264360
|
+
return entries.map((value) => asRecord8(value)).filter((value) => value !== null).map((value) => ({
|
|
264361
|
+
id: String(value.id ?? "").trim(),
|
|
264362
|
+
// listTags returns `message`; createTag returns `name`. Accept both.
|
|
264363
|
+
name: String(value.name ?? value.message ?? "").trim()
|
|
264364
|
+
})).filter((value) => value.id || value.name);
|
|
264365
|
+
}
|
|
264173
264366
|
/** Resolve a specification's ROOT file uuid via the files list. */
|
|
264174
264367
|
async resolveRootFileId(specId) {
|
|
264175
264368
|
const files = await this.gateway.requestJson({
|
|
@@ -264189,79 +264382,182 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264189
264382
|
*/
|
|
264190
264383
|
async generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) {
|
|
264191
264384
|
const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
|
|
264192
|
-
const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
|
|
264193
|
-
const body2 = {
|
|
264194
|
-
name: submittedName,
|
|
264195
|
-
options: {
|
|
264196
|
-
requestNameSource,
|
|
264197
|
-
folderStrategy,
|
|
264198
|
-
...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
|
|
264199
|
-
}
|
|
264200
|
-
};
|
|
264201
264385
|
const before = await this.listGeneratedCollectionRefs(specId);
|
|
264202
|
-
|
|
264203
|
-
|
|
264204
|
-
|
|
264205
|
-
|
|
264206
|
-
|
|
264207
|
-
|
|
264208
|
-
|
|
264209
|
-
|
|
264210
|
-
)
|
|
264211
|
-
const
|
|
264212
|
-
|
|
264386
|
+
const existing = adoptExactMatch(
|
|
264387
|
+
`generated-collection:${specId}:${name}`,
|
|
264388
|
+
await this.filterGeneratedCollectionsByExactName(before, name),
|
|
264389
|
+
(entry) => entry.id
|
|
264390
|
+
);
|
|
264391
|
+
if (existing) return this.convergeGeneratedCollections(specId, name, existing.id);
|
|
264392
|
+
let lastError;
|
|
264393
|
+
for (let taskAttempt = 0; taskAttempt < 2; taskAttempt += 1) {
|
|
264394
|
+
const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
|
|
264395
|
+
const body2 = {
|
|
264396
|
+
name: submittedName,
|
|
264397
|
+
options: {
|
|
264398
|
+
requestNameSource,
|
|
264399
|
+
folderStrategy,
|
|
264400
|
+
...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
|
|
264401
|
+
}
|
|
264402
|
+
};
|
|
264403
|
+
let taskId;
|
|
264404
|
+
try {
|
|
264405
|
+
const generation = await this.postGenerationWithLockRetry(specId, body2, name);
|
|
264406
|
+
if (generation.adoptedId) {
|
|
264407
|
+
return this.convergeGeneratedCollections(specId, name, generation.adoptedId);
|
|
264408
|
+
}
|
|
264409
|
+
taskId = generation.taskId ?? "";
|
|
264410
|
+
} catch (error2) {
|
|
264411
|
+
if (!isAmbiguousTransportError(error2)) throw error2;
|
|
264412
|
+
const beforeIds2 = new Set(before.map((entry) => entry.id));
|
|
264413
|
+
const appeared2 = (await this.listGeneratedCollectionRefs(specId)).filter(
|
|
264414
|
+
(entry) => !beforeIds2.has(entry.id)
|
|
264415
|
+
);
|
|
264416
|
+
const exactAppeared = await this.filterGeneratedCollectionsByExactName(
|
|
264417
|
+
appeared2,
|
|
264418
|
+
submittedName
|
|
264419
|
+
);
|
|
264420
|
+
const match = adoptExactMatch(
|
|
264421
|
+
`generated-collection:${specId}:${submittedName}`,
|
|
264422
|
+
exactAppeared,
|
|
264423
|
+
(entry) => entry.id
|
|
264424
|
+
);
|
|
264425
|
+
if (!match) throw error2;
|
|
264426
|
+
await this.renameGeneratedCollection(match.id, name);
|
|
264427
|
+
return this.convergeGeneratedCollections(specId, name, match.id);
|
|
264428
|
+
}
|
|
264429
|
+
let taskFailed = false;
|
|
264430
|
+
if (taskId) {
|
|
264431
|
+
for (let attempt = 0; attempt < this.generationPollAttempts; attempt += 1) {
|
|
264432
|
+
await this.sleep(this.generationPollDelayMs);
|
|
264433
|
+
const task = await this.gateway.requestJson({
|
|
264434
|
+
service: "specification",
|
|
264435
|
+
method: "get",
|
|
264436
|
+
path: "/tasks",
|
|
264437
|
+
query: { entityId: specId, entityType: "specification", type: "collection-generation" }
|
|
264438
|
+
});
|
|
264439
|
+
const status = String(asRecord8(task?.data)?.[taskId] ?? "").toLowerCase();
|
|
264440
|
+
if (status === "failed" || status === "error") {
|
|
264441
|
+
taskFailed = true;
|
|
264442
|
+
lastError = new Error(`Collection generation task failed for ${prefix}`);
|
|
264443
|
+
break;
|
|
264444
|
+
}
|
|
264445
|
+
if (status && status !== "in-progress" && status !== "pending" && status !== "queued") {
|
|
264446
|
+
break;
|
|
264447
|
+
}
|
|
264448
|
+
if (attempt === this.generationPollAttempts - 1) {
|
|
264449
|
+
throw new Error(`Collection generation timed out for ${prefix}`);
|
|
264450
|
+
}
|
|
264451
|
+
}
|
|
264452
|
+
}
|
|
264453
|
+
if (taskFailed) {
|
|
264454
|
+
await this.sleep(1e3 * (taskAttempt + 1));
|
|
264455
|
+
continue;
|
|
264456
|
+
}
|
|
264457
|
+
const beforeIds = new Set(before.map((entry) => entry.id));
|
|
264458
|
+
const after = await this.listGeneratedCollectionRefs(specId);
|
|
264459
|
+
const appeared = after.filter((entry) => !beforeIds.has(entry.id));
|
|
264460
|
+
const candidates = await this.filterGeneratedCollectionsByExactName(
|
|
264461
|
+
appeared,
|
|
264213
264462
|
submittedName
|
|
264214
264463
|
);
|
|
264215
|
-
const
|
|
264464
|
+
const uid = adoptExactMatch(
|
|
264216
264465
|
`generated-collection:${specId}:${submittedName}`,
|
|
264217
|
-
|
|
264466
|
+
candidates,
|
|
264218
264467
|
(entry) => entry.id
|
|
264219
|
-
);
|
|
264220
|
-
if (!
|
|
264221
|
-
|
|
264222
|
-
|
|
264468
|
+
)?.id;
|
|
264469
|
+
if (!uid) {
|
|
264470
|
+
throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
|
|
264471
|
+
}
|
|
264472
|
+
await this.renameGeneratedCollection(uid, name);
|
|
264473
|
+
return this.convergeGeneratedCollections(specId, name, uid);
|
|
264223
264474
|
}
|
|
264224
|
-
|
|
264225
|
-
|
|
264226
|
-
|
|
264227
|
-
|
|
264228
|
-
|
|
264229
|
-
|
|
264230
|
-
|
|
264231
|
-
|
|
264232
|
-
|
|
264233
|
-
|
|
264234
|
-
|
|
264235
|
-
|
|
264236
|
-
|
|
264237
|
-
|
|
264238
|
-
|
|
264239
|
-
|
|
264240
|
-
|
|
264241
|
-
|
|
264475
|
+
throw lastError ?? new Error(`Collection generation task failed for ${prefix}`);
|
|
264476
|
+
}
|
|
264477
|
+
/**
|
|
264478
|
+
* Re-elect the durable generated collection for a final name. Call after
|
|
264479
|
+
* concurrent dual-trigger generates and before description/inject/tag so a
|
|
264480
|
+
* peer orphan-sweep cannot leave this runner holding a deleted id.
|
|
264481
|
+
*/
|
|
264482
|
+
async adoptGeneratedCollection(specId, projectName, prefix, preferredId = "") {
|
|
264483
|
+
const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
|
|
264484
|
+
return this.convergeGeneratedCollections(specId, name, preferredId);
|
|
264485
|
+
}
|
|
264486
|
+
/**
|
|
264487
|
+
* Concurrent dual-trigger previews can each generate+rename the same final
|
|
264488
|
+
* collection identity. Elect the stable lowest-id winner.
|
|
264489
|
+
*
|
|
264490
|
+
* Losers only delete *their own* preferred collection (never a peer's still-
|
|
264491
|
+
* in-use id). Winners wait briefly for peers to self-delete, then clean any
|
|
264492
|
+
* leftover same-identity orphans (temps + extra finals).
|
|
264493
|
+
*/
|
|
264494
|
+
async convergeGeneratedCollections(specId, finalName, preferredId) {
|
|
264495
|
+
const tempPrefix = `${finalName} [bootstrap:`;
|
|
264496
|
+
const hydrate = async () => {
|
|
264497
|
+
const linked = await this.listGeneratedCollectionRefs(specId);
|
|
264498
|
+
return Promise.all(
|
|
264499
|
+
linked.map(async (entry) => {
|
|
264500
|
+
if (entry.name) return { id: entry.id, name: entry.name };
|
|
264501
|
+
try {
|
|
264502
|
+
const collection = await this.gateway.requestJson({
|
|
264503
|
+
service: "collection",
|
|
264504
|
+
method: "get",
|
|
264505
|
+
path: `/v3/collections/${this.bareModelId(entry.id)}`
|
|
264506
|
+
});
|
|
264507
|
+
return {
|
|
264508
|
+
id: entry.id,
|
|
264509
|
+
name: String(asRecord8(collection?.data)?.name ?? "").trim()
|
|
264510
|
+
};
|
|
264511
|
+
} catch (error2) {
|
|
264512
|
+
if (error2 instanceof HttpError && error2.status === 404) {
|
|
264513
|
+
return { id: entry.id, name: "" };
|
|
264514
|
+
}
|
|
264515
|
+
throw error2;
|
|
264516
|
+
}
|
|
264517
|
+
})
|
|
264518
|
+
);
|
|
264519
|
+
};
|
|
264520
|
+
const selectSameIdentity = (entries) => entries.filter(
|
|
264521
|
+
(entry) => entry.name === finalName || entry.name.startsWith(tempPrefix)
|
|
264522
|
+
).sort((a, b) => a.id.localeCompare(b.id));
|
|
264523
|
+
let sameIdentity = selectSameIdentity(await hydrate());
|
|
264524
|
+
if (sameIdentity.length === 0) return preferredId;
|
|
264525
|
+
if (sameIdentity.length === 1) {
|
|
264526
|
+
await this.sleep(1e3);
|
|
264527
|
+
sameIdentity = selectSameIdentity(await hydrate());
|
|
264528
|
+
if (sameIdentity.length === 0) return preferredId;
|
|
264529
|
+
}
|
|
264530
|
+
const winner = sameIdentity[0];
|
|
264531
|
+
if (winner.name !== finalName) {
|
|
264532
|
+
await this.renameGeneratedCollection(winner.id, finalName);
|
|
264533
|
+
}
|
|
264534
|
+
if (preferredId && preferredId !== winner.id) {
|
|
264535
|
+
const own = sameIdentity.find((entry) => entry.id === preferredId);
|
|
264536
|
+
if (own) {
|
|
264537
|
+
try {
|
|
264538
|
+
await this.deleteCollection(preferredId);
|
|
264539
|
+
} catch (error2) {
|
|
264540
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264242
264541
|
}
|
|
264243
264542
|
}
|
|
264543
|
+
return winner.id;
|
|
264244
264544
|
}
|
|
264245
|
-
|
|
264246
|
-
|
|
264247
|
-
|
|
264248
|
-
|
|
264249
|
-
|
|
264250
|
-
|
|
264251
|
-
|
|
264252
|
-
|
|
264253
|
-
|
|
264254
|
-
|
|
264255
|
-
|
|
264256
|
-
)?.id;
|
|
264257
|
-
if (!uid) {
|
|
264258
|
-
throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
|
|
264545
|
+
if (sameIdentity.length > 1) {
|
|
264546
|
+
await this.sleep(1500);
|
|
264547
|
+
sameIdentity = selectSameIdentity(await hydrate());
|
|
264548
|
+
for (const duplicate of sameIdentity) {
|
|
264549
|
+
if (duplicate.id === winner.id) continue;
|
|
264550
|
+
try {
|
|
264551
|
+
await this.deleteCollection(duplicate.id);
|
|
264552
|
+
} catch (error2) {
|
|
264553
|
+
if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
|
|
264554
|
+
}
|
|
264555
|
+
}
|
|
264259
264556
|
}
|
|
264260
|
-
|
|
264261
|
-
return uid;
|
|
264557
|
+
return winner.id;
|
|
264262
264558
|
}
|
|
264263
264559
|
/** POST the generation request, retrying a 423-locked spec; returns the task id. */
|
|
264264
|
-
async postGenerationWithLockRetry(specId, body2) {
|
|
264560
|
+
async postGenerationWithLockRetry(specId, body2, finalName) {
|
|
264265
264561
|
for (let lockedAttempt = 0; ; lockedAttempt += 1) {
|
|
264266
264562
|
try {
|
|
264267
264563
|
const created = await this.gateway.requestJson({
|
|
@@ -264271,13 +264567,22 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264271
264567
|
retry: "none",
|
|
264272
264568
|
body: body2
|
|
264273
264569
|
});
|
|
264274
|
-
return String(asRecord8(created?.data)?.taskId ?? "").trim();
|
|
264570
|
+
return { taskId: String(asRecord8(created?.data)?.taskId ?? "").trim() };
|
|
264275
264571
|
} catch (error2) {
|
|
264276
264572
|
const locked = error2 instanceof HttpError && error2.status === 423;
|
|
264277
264573
|
if (!locked || lockedAttempt >= _PostmanGatewayAssetsClient.GENERATION_LOCKED_MAX_RETRIES) {
|
|
264278
264574
|
throw error2;
|
|
264279
264575
|
}
|
|
264280
264576
|
await this.sleep(5e3 * Math.pow(2, lockedAttempt));
|
|
264577
|
+
const adopted = adoptExactMatch(
|
|
264578
|
+
`generated-collection:${specId}:${finalName}`,
|
|
264579
|
+
await this.filterGeneratedCollectionsByExactName(
|
|
264580
|
+
await this.listGeneratedCollectionRefs(specId),
|
|
264581
|
+
finalName
|
|
264582
|
+
),
|
|
264583
|
+
(entry) => entry.id
|
|
264584
|
+
);
|
|
264585
|
+
if (adopted) return { adoptedId: adopted.id };
|
|
264281
264586
|
}
|
|
264282
264587
|
}
|
|
264283
264588
|
}
|
|
@@ -264314,14 +264619,24 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264314
264619
|
return hydrated.filter((entry) => entry.name === expectedName);
|
|
264315
264620
|
}
|
|
264316
264621
|
async renameGeneratedCollection(collectionId, name) {
|
|
264317
|
-
|
|
264318
|
-
|
|
264319
|
-
|
|
264320
|
-
|
|
264321
|
-
|
|
264322
|
-
|
|
264323
|
-
|
|
264324
|
-
|
|
264622
|
+
try {
|
|
264623
|
+
await this.gateway.requestJson({
|
|
264624
|
+
service: "collection",
|
|
264625
|
+
method: "patch",
|
|
264626
|
+
path: `/v3/collections/${this.bareModelId(collectionId)}`,
|
|
264627
|
+
// Replacing a generated collection's name with the same value is idempotent.
|
|
264628
|
+
retry: "safe",
|
|
264629
|
+
body: [{ op: "replace", path: "/name", value: name }]
|
|
264630
|
+
});
|
|
264631
|
+
} catch (error2) {
|
|
264632
|
+
if (error2 instanceof HttpError && error2.status === 400 && /must update at least one|REJECTED_PATCH/i.test(
|
|
264633
|
+
`${error2.message}
|
|
264634
|
+
${error2.responseBody ?? ""}`
|
|
264635
|
+
)) {
|
|
264636
|
+
return;
|
|
264637
|
+
}
|
|
264638
|
+
throw error2;
|
|
264639
|
+
}
|
|
264325
264640
|
}
|
|
264326
264641
|
/**
|
|
264327
264642
|
* Create a team-visible workspace through the gateway workspaces service.
|
|
@@ -264596,6 +264911,7 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
264596
264911
|
service: "tagging",
|
|
264597
264912
|
method: "put",
|
|
264598
264913
|
path: `/v1/tags/collections/${collectionUid}`,
|
|
264914
|
+
retry: "safe",
|
|
264599
264915
|
body: { tags: normalized.map((slug) => ({ slug })) }
|
|
264600
264916
|
});
|
|
264601
264917
|
}
|
|
@@ -265222,6 +265538,15 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
265222
265538
|
async createCollection(workspaceId, collection, options = {}) {
|
|
265223
265539
|
const v3 = this.normalizeCollectionForWrite(collection);
|
|
265224
265540
|
const desiredName = String(v3.name ?? "Untitled Collection");
|
|
265541
|
+
const existing = adoptExactMatch(
|
|
265542
|
+
`collection:${workspaceId}:${desiredName}`,
|
|
265543
|
+
await this.findCollectionsByExactName(workspaceId, desiredName),
|
|
265544
|
+
(entry) => entry.id
|
|
265545
|
+
);
|
|
265546
|
+
if (existing) {
|
|
265547
|
+
await this.updateCollection(existing.id, collection);
|
|
265548
|
+
return existing.id;
|
|
265549
|
+
}
|
|
265225
265550
|
const submittedName = `${desiredName} [bootstrap:${this.createIdentity()}]`;
|
|
265226
265551
|
const rootBody = { name: submittedName };
|
|
265227
265552
|
let created;
|
|
@@ -265272,6 +265597,10 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
|
|
|
265272
265597
|
}
|
|
265273
265598
|
return rawId;
|
|
265274
265599
|
}
|
|
265600
|
+
/** Patch only the durable collection description without reconciling its item tree. */
|
|
265601
|
+
async updateCollectionDescription(collectionUid, description) {
|
|
265602
|
+
await this.applyCollectionLevelSettings(this.bareModelId(collectionUid), { description });
|
|
265603
|
+
}
|
|
265275
265604
|
/**
|
|
265276
265605
|
* Full-replace reconcile of a curated local v2.1.0 or collection v3 payload: delete every
|
|
265277
265606
|
* root-level item (deleting a folder cascades its children server-side —
|
|
@@ -265496,7 +265825,8 @@ var AccessTokenGatewayClient = class {
|
|
|
265496
265825
|
* `requestJson` can still parse it.
|
|
265497
265826
|
*/
|
|
265498
265827
|
rebuildResponse(response, body2) {
|
|
265499
|
-
|
|
265828
|
+
const nullBody = response.status === 204 || response.status === 205 || response.status === 304;
|
|
265829
|
+
return new Response(nullBody ? null : body2, {
|
|
265500
265830
|
status: response.status,
|
|
265501
265831
|
statusText: response.statusText,
|
|
265502
265832
|
headers: response.headers
|
|
@@ -281948,6 +282278,61 @@ function serializeOpenApiDocument(document2) {
|
|
|
281948
282278
|
return `${JSON.stringify(document2, null, 2)}
|
|
281949
282279
|
`;
|
|
281950
282280
|
}
|
|
282281
|
+
function typeNullPath(pathSegments) {
|
|
282282
|
+
return pathSegments.map(String).join(".");
|
|
282283
|
+
}
|
|
282284
|
+
function isNullOnlySchema(value) {
|
|
282285
|
+
const record = asRecord10(value);
|
|
282286
|
+
if (!record || record.type !== "null") return false;
|
|
282287
|
+
return Object.keys(record).every((key) => key === "type" || key.startsWith("x-"));
|
|
282288
|
+
}
|
|
282289
|
+
function createOas30TypeNullCompatibilityDocument(sourceDocument) {
|
|
282290
|
+
const document2 = structuredClone(sourceDocument);
|
|
282291
|
+
const sourceTypeNullPaths = [];
|
|
282292
|
+
const visit4 = (value, pathSegments) => {
|
|
282293
|
+
if (Array.isArray(value)) {
|
|
282294
|
+
value.forEach((entry, index) => visit4(entry, [...pathSegments, index]));
|
|
282295
|
+
return;
|
|
282296
|
+
}
|
|
282297
|
+
const record = asRecord10(value);
|
|
282298
|
+
if (!record) return;
|
|
282299
|
+
const oneOf = Array.isArray(record.oneOf) ? record.oneOf : void 0;
|
|
282300
|
+
const nullIndexes = oneOf ? oneOf.flatMap((entry, index) => isNullOnlySchema(entry) ? [index] : []) : [];
|
|
282301
|
+
if (nullIndexes.length > 0) {
|
|
282302
|
+
if (oneOf?.length !== 2 || nullIndexes.length !== 1) {
|
|
282303
|
+
throw new Error(
|
|
282304
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must use oneOf with exactly one null-only member and one non-null schema`
|
|
282305
|
+
);
|
|
282306
|
+
}
|
|
282307
|
+
const nullIndex = nullIndexes[0];
|
|
282308
|
+
const nonNullIndex = nullIndex === 0 ? 1 : 0;
|
|
282309
|
+
const nonNullSchema = asRecord10(oneOf[nonNullIndex]);
|
|
282310
|
+
if (!nonNullSchema || nonNullSchema.type === "null") {
|
|
282311
|
+
throw new Error(
|
|
282312
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must pair the null-only member with one non-null schema`
|
|
282313
|
+
);
|
|
282314
|
+
}
|
|
282315
|
+
const siblings = Object.fromEntries(
|
|
282316
|
+
Object.entries(record).filter(([key]) => key !== "oneOf")
|
|
282317
|
+
);
|
|
282318
|
+
for (const key of Object.keys(record)) delete record[key];
|
|
282319
|
+
Object.assign(record, structuredClone(nonNullSchema), siblings, { nullable: true });
|
|
282320
|
+
sourceTypeNullPaths.push(typeNullPath([...pathSegments, "oneOf", nullIndex, "type"]));
|
|
282321
|
+
visit4(record, pathSegments);
|
|
282322
|
+
return;
|
|
282323
|
+
}
|
|
282324
|
+
if (record.type === "null") {
|
|
282325
|
+
throw new Error(
|
|
282326
|
+
`CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath([...pathSegments, "type"])} is not a supported nullable oneOf member`
|
|
282327
|
+
);
|
|
282328
|
+
}
|
|
282329
|
+
for (const [key, child4] of Object.entries(record)) {
|
|
282330
|
+
visit4(child4, [...pathSegments, key]);
|
|
282331
|
+
}
|
|
282332
|
+
};
|
|
282333
|
+
visit4(document2, []);
|
|
282334
|
+
return { document: document2, sourceTypeNullPaths };
|
|
282335
|
+
}
|
|
281951
282336
|
async function bundleSpec(baseUrl, document2, options) {
|
|
281952
282337
|
const budget = options.budget ?? { refs: 0, totalBytes: Buffer.byteLength(JSON.stringify(document2), "utf8") };
|
|
281953
282338
|
const fetchText = options.fetchText ?? safeFetchText;
|
|
@@ -281989,7 +282374,14 @@ function createCachedFetchText(options) {
|
|
|
281989
282374
|
async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
|
|
281990
282375
|
const document2 = parseOpenApiDocument(content);
|
|
281991
282376
|
const version = detectOpenApiVersion2(document2);
|
|
281992
|
-
|
|
282377
|
+
let contractDocument = document2;
|
|
282378
|
+
let sourceTypeNullPaths = [];
|
|
282379
|
+
if (options.preserveOas30TypeNull && version === "3.0") {
|
|
282380
|
+
const compatibility = createOas30TypeNullCompatibilityDocument(document2);
|
|
282381
|
+
contractDocument = compatibility.document;
|
|
282382
|
+
sourceTypeNullPaths = compatibility.sourceTypeNullPaths;
|
|
282383
|
+
}
|
|
282384
|
+
const bundledDocument = await bundleSpec(baseRef, contractDocument, { ...options, budget, fetchText });
|
|
281993
282385
|
const validation = await validate(bundledDocument, {
|
|
281994
282386
|
resolve: { external: false, file: false },
|
|
281995
282387
|
dereference: { circular: "ignore" },
|
|
@@ -282003,6 +282395,7 @@ async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
|
|
|
282003
282395
|
bundledDocument,
|
|
282004
282396
|
contractIndex: buildContractIndex(bundledDocument),
|
|
282005
282397
|
content,
|
|
282398
|
+
sourceTypeNullPaths,
|
|
282006
282399
|
version
|
|
282007
282400
|
};
|
|
282008
282401
|
}
|
|
@@ -282128,6 +282521,309 @@ function looksLikeIntrospection(record) {
|
|
|
282128
282521
|
return Boolean(data && typeof data === "object" && data.__schema);
|
|
282129
282522
|
}
|
|
282130
282523
|
|
|
282524
|
+
// src/lib/repo/branch-decision.ts
|
|
282525
|
+
var import_node_fs5 = require("node:fs");
|
|
282526
|
+
var import_node_crypto4 = require("node:crypto");
|
|
282527
|
+
var ContractError = class extends Error {
|
|
282528
|
+
code;
|
|
282529
|
+
constructor(code, message) {
|
|
282530
|
+
super(`${code}: ${message}`);
|
|
282531
|
+
this.code = code;
|
|
282532
|
+
this.name = "ContractError";
|
|
282533
|
+
}
|
|
282534
|
+
};
|
|
282535
|
+
function clean(value) {
|
|
282536
|
+
const trimmed = (value ?? "").trim();
|
|
282537
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
282538
|
+
}
|
|
282539
|
+
function stripRefPrefix(ref) {
|
|
282540
|
+
const raw = clean(ref);
|
|
282541
|
+
if (!raw) {
|
|
282542
|
+
return { kind: "unknown" };
|
|
282543
|
+
}
|
|
282544
|
+
if (raw.startsWith("refs/heads/")) {
|
|
282545
|
+
return { name: raw.slice("refs/heads/".length), kind: "branch" };
|
|
282546
|
+
}
|
|
282547
|
+
if (raw.startsWith("refs/tags/")) {
|
|
282548
|
+
return { name: raw.slice("refs/tags/".length), kind: "tag" };
|
|
282549
|
+
}
|
|
282550
|
+
if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
|
|
282551
|
+
return { kind: "unknown" };
|
|
282552
|
+
}
|
|
282553
|
+
return { name: raw, kind: "branch" };
|
|
282554
|
+
}
|
|
282555
|
+
function detectProvider(env) {
|
|
282556
|
+
if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
|
|
282557
|
+
if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
|
|
282558
|
+
if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
|
|
282559
|
+
if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
|
|
282560
|
+
return "unknown";
|
|
282561
|
+
}
|
|
282562
|
+
function readGithubEvent(env) {
|
|
282563
|
+
const path10 = clean(env.GITHUB_EVENT_PATH);
|
|
282564
|
+
if (!path10) return void 0;
|
|
282565
|
+
try {
|
|
282566
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path10, "utf8"));
|
|
282567
|
+
} catch {
|
|
282568
|
+
return void 0;
|
|
282569
|
+
}
|
|
282570
|
+
}
|
|
282571
|
+
function resolveBranchIdentity(env = process.env, overrides = {}) {
|
|
282572
|
+
const provider = detectProvider(env);
|
|
282573
|
+
const explicitDefault = clean(overrides.defaultBranch);
|
|
282574
|
+
let headBranch;
|
|
282575
|
+
let rawRef;
|
|
282576
|
+
let refKind;
|
|
282577
|
+
let isPrContext = false;
|
|
282578
|
+
let isForkPr = false;
|
|
282579
|
+
let defaultBranch = explicitDefault;
|
|
282580
|
+
let headSha;
|
|
282581
|
+
switch (provider) {
|
|
282582
|
+
case "github": {
|
|
282583
|
+
const event2 = readGithubEvent(env);
|
|
282584
|
+
headSha = clean(env.GITHUB_SHA);
|
|
282585
|
+
defaultBranch ??= clean(event2?.repository?.default_branch);
|
|
282586
|
+
const headRef = clean(env.GITHUB_HEAD_REF);
|
|
282587
|
+
if (headRef) {
|
|
282588
|
+
isPrContext = true;
|
|
282589
|
+
headBranch = headRef;
|
|
282590
|
+
rawRef = clean(env.GITHUB_REF) ?? headRef;
|
|
282591
|
+
refKind = "branch";
|
|
282592
|
+
const headRepo = event2?.pull_request?.head?.repo?.full_name;
|
|
282593
|
+
const baseRepo = event2?.pull_request?.base?.repo?.full_name ?? event2?.repository?.full_name;
|
|
282594
|
+
isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
|
|
282595
|
+
headSha = clean(event2?.pull_request?.head?.sha) ?? headSha;
|
|
282596
|
+
} else {
|
|
282597
|
+
rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
|
|
282598
|
+
const parsed = stripRefPrefix(rawRef);
|
|
282599
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282600
|
+
refKind = parsed.kind;
|
|
282601
|
+
}
|
|
282602
|
+
break;
|
|
282603
|
+
}
|
|
282604
|
+
case "gitlab": {
|
|
282605
|
+
headSha = clean(env.CI_COMMIT_SHA);
|
|
282606
|
+
defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
|
|
282607
|
+
const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
|
|
282608
|
+
if (mrSource) {
|
|
282609
|
+
isPrContext = true;
|
|
282610
|
+
headBranch = mrSource;
|
|
282611
|
+
rawRef = mrSource;
|
|
282612
|
+
refKind = "branch";
|
|
282613
|
+
const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
|
|
282614
|
+
const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
|
|
282615
|
+
isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
|
|
282616
|
+
} else if (clean(env.CI_COMMIT_TAG)) {
|
|
282617
|
+
rawRef = clean(env.CI_COMMIT_TAG);
|
|
282618
|
+
refKind = "tag";
|
|
282619
|
+
} else {
|
|
282620
|
+
headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
|
|
282621
|
+
rawRef = headBranch;
|
|
282622
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
282623
|
+
}
|
|
282624
|
+
break;
|
|
282625
|
+
}
|
|
282626
|
+
case "bitbucket": {
|
|
282627
|
+
headSha = clean(env.BITBUCKET_COMMIT);
|
|
282628
|
+
if (clean(env.BITBUCKET_TAG)) {
|
|
282629
|
+
rawRef = clean(env.BITBUCKET_TAG);
|
|
282630
|
+
refKind = "tag";
|
|
282631
|
+
} else {
|
|
282632
|
+
headBranch = clean(env.BITBUCKET_BRANCH);
|
|
282633
|
+
rawRef = headBranch;
|
|
282634
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
282635
|
+
isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
|
|
282636
|
+
}
|
|
282637
|
+
break;
|
|
282638
|
+
}
|
|
282639
|
+
case "azure-devops": {
|
|
282640
|
+
headSha = clean(env.BUILD_SOURCEVERSION);
|
|
282641
|
+
const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
|
|
282642
|
+
if (prSource) {
|
|
282643
|
+
isPrContext = true;
|
|
282644
|
+
const parsed = stripRefPrefix(prSource);
|
|
282645
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282646
|
+
rawRef = prSource;
|
|
282647
|
+
refKind = parsed.kind;
|
|
282648
|
+
const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
|
|
282649
|
+
isForkPr = forkFlag?.toLowerCase() === "true";
|
|
282650
|
+
} else {
|
|
282651
|
+
rawRef = clean(env.BUILD_SOURCEBRANCH);
|
|
282652
|
+
const parsed = stripRefPrefix(rawRef);
|
|
282653
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
282654
|
+
refKind = parsed.kind;
|
|
282655
|
+
}
|
|
282656
|
+
break;
|
|
282657
|
+
}
|
|
282658
|
+
default: {
|
|
282659
|
+
refKind = "unknown";
|
|
282660
|
+
}
|
|
282661
|
+
}
|
|
282662
|
+
if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
|
|
282663
|
+
refKind = "default-branch";
|
|
282664
|
+
}
|
|
282665
|
+
return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
|
|
282666
|
+
}
|
|
282667
|
+
function parseChannelRules(input) {
|
|
282668
|
+
const raw = clean(input);
|
|
282669
|
+
const rules = [];
|
|
282670
|
+
for (const part of raw ? raw.split(",") : []) {
|
|
282671
|
+
const entry = part.trim();
|
|
282672
|
+
if (!entry) continue;
|
|
282673
|
+
const eq = entry.indexOf("=");
|
|
282674
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
282675
|
+
throw new ContractError(
|
|
282676
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
282677
|
+
`channels entry "${entry}" must be <branch-or-glob>=<CODE>`
|
|
282678
|
+
);
|
|
282679
|
+
}
|
|
282680
|
+
const pattern = entry.slice(0, eq).trim();
|
|
282681
|
+
const code = entry.slice(eq + 1).trim().toUpperCase();
|
|
282682
|
+
if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
|
|
282683
|
+
throw new ContractError(
|
|
282684
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
282685
|
+
`channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
|
|
282686
|
+
);
|
|
282687
|
+
}
|
|
282688
|
+
rules.push({ pattern, code });
|
|
282689
|
+
}
|
|
282690
|
+
if (!rules.some((rule) => rule.pattern === "release/*")) {
|
|
282691
|
+
rules.push({ pattern: "release/*", code: "RC" });
|
|
282692
|
+
}
|
|
282693
|
+
return rules;
|
|
282694
|
+
}
|
|
282695
|
+
function matchChannel(branch, rules) {
|
|
282696
|
+
for (const rule of rules) {
|
|
282697
|
+
if (rule.pattern.endsWith("*")) {
|
|
282698
|
+
const prefix = rule.pattern.slice(0, -1);
|
|
282699
|
+
if (branch.startsWith(prefix)) return rule;
|
|
282700
|
+
} else if (branch === rule.pattern) {
|
|
282701
|
+
return rule;
|
|
282702
|
+
}
|
|
282703
|
+
}
|
|
282704
|
+
return void 0;
|
|
282705
|
+
}
|
|
282706
|
+
function resolveBranchDecision(options) {
|
|
282707
|
+
const { strategy, identity } = options;
|
|
282708
|
+
const channels = options.channels ?? [];
|
|
282709
|
+
if (strategy === "legacy") {
|
|
282710
|
+
return {
|
|
282711
|
+
tier: "legacy",
|
|
282712
|
+
strategy,
|
|
282713
|
+
identity,
|
|
282714
|
+
canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
|
|
282715
|
+
reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
|
|
282716
|
+
};
|
|
282717
|
+
}
|
|
282718
|
+
const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
|
|
282719
|
+
if (!canonicalBranch) {
|
|
282720
|
+
throw new ContractError(
|
|
282721
|
+
"CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
|
|
282722
|
+
`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.`
|
|
282723
|
+
);
|
|
282724
|
+
}
|
|
282725
|
+
if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
|
|
282726
|
+
return {
|
|
282727
|
+
tier: "gated",
|
|
282728
|
+
strategy,
|
|
282729
|
+
identity,
|
|
282730
|
+
canonicalBranch,
|
|
282731
|
+
reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
|
|
282732
|
+
};
|
|
282733
|
+
}
|
|
282734
|
+
if (identity.headBranch === canonicalBranch) {
|
|
282735
|
+
return {
|
|
282736
|
+
tier: "canonical",
|
|
282737
|
+
strategy,
|
|
282738
|
+
identity,
|
|
282739
|
+
canonicalBranch,
|
|
282740
|
+
reason: `head branch equals canonical branch ${canonicalBranch}`
|
|
282741
|
+
};
|
|
282742
|
+
}
|
|
282743
|
+
const channel = matchChannel(identity.headBranch, channels);
|
|
282744
|
+
if (channel) {
|
|
282745
|
+
return {
|
|
282746
|
+
tier: "channel",
|
|
282747
|
+
strategy,
|
|
282748
|
+
identity,
|
|
282749
|
+
canonicalBranch,
|
|
282750
|
+
channel,
|
|
282751
|
+
reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
|
|
282752
|
+
};
|
|
282753
|
+
}
|
|
282754
|
+
if (strategy === "preview") {
|
|
282755
|
+
if (identity.isForkPr) {
|
|
282756
|
+
return {
|
|
282757
|
+
tier: "gated",
|
|
282758
|
+
strategy,
|
|
282759
|
+
identity,
|
|
282760
|
+
canonicalBranch,
|
|
282761
|
+
reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
|
|
282762
|
+
};
|
|
282763
|
+
}
|
|
282764
|
+
return {
|
|
282765
|
+
tier: "preview",
|
|
282766
|
+
strategy,
|
|
282767
|
+
identity,
|
|
282768
|
+
canonicalBranch,
|
|
282769
|
+
reason: `branch ${identity.headBranch} under branch-strategy preview`
|
|
282770
|
+
};
|
|
282771
|
+
}
|
|
282772
|
+
return {
|
|
282773
|
+
tier: "gated",
|
|
282774
|
+
strategy,
|
|
282775
|
+
identity,
|
|
282776
|
+
canonicalBranch,
|
|
282777
|
+
reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
|
|
282778
|
+
};
|
|
282779
|
+
}
|
|
282780
|
+
var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
|
|
282781
|
+
function serializeBranchDecision(decision) {
|
|
282782
|
+
return JSON.stringify(decision);
|
|
282783
|
+
}
|
|
282784
|
+
function parseBranchDecision(raw) {
|
|
282785
|
+
const value = clean(raw);
|
|
282786
|
+
if (!value) return void 0;
|
|
282787
|
+
let parsed;
|
|
282788
|
+
try {
|
|
282789
|
+
parsed = JSON.parse(value);
|
|
282790
|
+
} catch {
|
|
282791
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
|
|
282792
|
+
}
|
|
282793
|
+
const candidate = parsed;
|
|
282794
|
+
const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
|
|
282795
|
+
if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
|
|
282796
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
|
|
282797
|
+
}
|
|
282798
|
+
return candidate;
|
|
282799
|
+
}
|
|
282800
|
+
function resolveEffectiveBranchDecision(options, env = process.env) {
|
|
282801
|
+
const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
|
|
282802
|
+
if (inherited) return inherited;
|
|
282803
|
+
return resolveBranchDecision(options);
|
|
282804
|
+
}
|
|
282805
|
+
var PREVIEW_SLUG_MAX = 30;
|
|
282806
|
+
function buildBranchSlug(rawBranch) {
|
|
282807
|
+
const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
|
|
282808
|
+
const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
|
|
282809
|
+
const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
|
|
282810
|
+
if (!lossy) {
|
|
282811
|
+
return { suffix: truncated, slug: truncated, lossy };
|
|
282812
|
+
}
|
|
282813
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
|
|
282814
|
+
return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
|
|
282815
|
+
}
|
|
282816
|
+
function previewAssetName(baseName, rawBranch) {
|
|
282817
|
+
return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
|
|
282818
|
+
}
|
|
282819
|
+
function channelAssetName(baseName, code) {
|
|
282820
|
+
return `[${code}] ${baseName}`;
|
|
282821
|
+
}
|
|
282822
|
+
var MARKER_KEY = "x-pm-onboarding";
|
|
282823
|
+
function renderAssetMarker(marker) {
|
|
282824
|
+
return `${MARKER_KEY}: ${JSON.stringify(marker)}`;
|
|
282825
|
+
}
|
|
282826
|
+
|
|
282131
282827
|
// node_modules/graphql/jsutils/inspect.mjs
|
|
282132
282828
|
var MAX_ARRAY_LENGTH = 10;
|
|
282133
282829
|
var MAX_RECURSIVE_DEPTH = 2;
|
|
@@ -305459,7 +306155,7 @@ function parseWsdl(content, opts) {
|
|
|
305459
306155
|
}
|
|
305460
306156
|
|
|
305461
306157
|
// src/lib/protocols/soap/builder.ts
|
|
305462
|
-
var
|
|
306158
|
+
var import_node_crypto5 = require("node:crypto");
|
|
305463
306159
|
var COLLECTION_V210_SCHEMA2 = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
|
|
305464
306160
|
var SOAP11_ENVELOPE_NS = "http://schemas.xmlsoap.org/soap/envelope/";
|
|
305465
306161
|
var SOAP12_ENVELOPE_NS = "http://www.w3.org/2003/05/soap-envelope";
|
|
@@ -305467,7 +306163,7 @@ var SOAP11_CONTENT_TYPE = "text/xml; charset=UTF-8";
|
|
|
305467
306163
|
var SOAP12_CONTENT_TYPE = "application/soap+xml; charset=UTF-8";
|
|
305468
306164
|
var WSA_NS = "http://www.w3.org/2005/08/addressing";
|
|
305469
306165
|
function stableId2(seed) {
|
|
305470
|
-
return (0,
|
|
306166
|
+
return (0, import_node_crypto5.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
|
|
305471
306167
|
}
|
|
305472
306168
|
function envelopeNamespace(version) {
|
|
305473
306169
|
return version === "1.2" ? SOAP12_ENVELOPE_NS : SOAP11_ENVELOPE_NS;
|
|
@@ -312230,6 +312926,11 @@ function resolveInputs(env = process.env) {
|
|
|
312230
312926
|
),
|
|
312231
312927
|
protocolEndpointUrl: getInput2("protocol-endpoint-url", env),
|
|
312232
312928
|
openapiVersion: resolveOpenapiVersion(getInput2("openapi-version", env)),
|
|
312929
|
+
preserveOas30TypeNull: parseBooleanInput(
|
|
312930
|
+
"preserve-oas30-type-null",
|
|
312931
|
+
getInput2("preserve-oas30-type-null", env),
|
|
312932
|
+
false
|
|
312933
|
+
),
|
|
312233
312934
|
breakingChangeMode: parseBreakingChangeMode(getInput2("breaking-change-mode", env)),
|
|
312234
312935
|
breakingBaselineSpecPath: getInput2("breaking-baseline-spec-path", env),
|
|
312235
312936
|
breakingRulesPath: getInput2("breaking-rules-path", env) ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
|
|
@@ -312260,8 +312961,74 @@ function resolveInputs(env = process.env) {
|
|
|
312260
312961
|
githubRef: env.GITHUB_REF,
|
|
312261
312962
|
githubSha: env.GITHUB_SHA,
|
|
312262
312963
|
githubToken: getInput2("github-token", env) || env.GITHUB_TOKEN || "",
|
|
312263
|
-
ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || ""
|
|
312964
|
+
ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || "",
|
|
312965
|
+
branchStrategy: parseEnumInput(
|
|
312966
|
+
"branch-strategy",
|
|
312967
|
+
getInput2("branch-strategy", env),
|
|
312968
|
+
"legacy"
|
|
312969
|
+
),
|
|
312970
|
+
canonicalBranch: getInput2("canonical-branch", env),
|
|
312971
|
+
channels: getInput2("channels", env)
|
|
312972
|
+
};
|
|
312973
|
+
}
|
|
312974
|
+
function decideBranchTier(inputs, env = process.env) {
|
|
312975
|
+
return resolveEffectiveBranchDecision(
|
|
312976
|
+
{
|
|
312977
|
+
// Tolerate hand-built inputs objects (tests, embedders) that omit the
|
|
312978
|
+
// field: absent means legacy, exactly like the action default.
|
|
312979
|
+
strategy: inputs.branchStrategy ?? "legacy",
|
|
312980
|
+
identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
|
|
312981
|
+
canonicalBranch: inputs.canonicalBranch,
|
|
312982
|
+
channels: parseChannelRules(inputs.channels)
|
|
312983
|
+
},
|
|
312984
|
+
env
|
|
312985
|
+
);
|
|
312986
|
+
}
|
|
312987
|
+
function buildSpecVersionUrl(workspaceId, specId, tagId, versionLabel) {
|
|
312988
|
+
return "https://go.postman.co/workspace/" + workspaceId + "/specification/" + specId + "?tagId=" + encodeURIComponent(tagId) + "&versionLabel=" + encodeURIComponent(versionLabel);
|
|
312989
|
+
}
|
|
312990
|
+
function embedSpecBranchMarker(content, decision, repo) {
|
|
312991
|
+
if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
|
|
312992
|
+
return content;
|
|
312993
|
+
}
|
|
312994
|
+
const rawBranch = decision.identity.headBranch;
|
|
312995
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
312996
|
+
const marker = {
|
|
312997
|
+
repo,
|
|
312998
|
+
rawBranch,
|
|
312999
|
+
sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
|
|
313000
|
+
role: decision.tier,
|
|
313001
|
+
headSha: decision.identity.headSha,
|
|
313002
|
+
createdAt: now,
|
|
313003
|
+
lastSyncedAt: now
|
|
312264
313004
|
};
|
|
313005
|
+
try {
|
|
313006
|
+
if (content.trim().startsWith("{")) {
|
|
313007
|
+
return `${JSON.stringify({ ...JSON.parse(content), "x-postman-onboarding": marker }, null, 2)}
|
|
313008
|
+
`;
|
|
313009
|
+
}
|
|
313010
|
+
const document2 = (0, import_yaml4.parse)(content);
|
|
313011
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) return content;
|
|
313012
|
+
return (0, import_yaml4.stringify)({ ...document2, "x-postman-onboarding": marker });
|
|
313013
|
+
} catch {
|
|
313014
|
+
return content;
|
|
313015
|
+
}
|
|
313016
|
+
}
|
|
313017
|
+
function renderCollectionBranchMarker(decision, repo, now = /* @__PURE__ */ new Date()) {
|
|
313018
|
+
if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
|
|
313019
|
+
return void 0;
|
|
313020
|
+
}
|
|
313021
|
+
const rawBranch = decision.identity.headBranch;
|
|
313022
|
+
const timestamp = now.toISOString();
|
|
313023
|
+
return renderAssetMarker({
|
|
313024
|
+
repo,
|
|
313025
|
+
rawBranch,
|
|
313026
|
+
sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
|
|
313027
|
+
role: decision.tier,
|
|
313028
|
+
headSha: decision.identity.headSha,
|
|
313029
|
+
createdAt: timestamp,
|
|
313030
|
+
lastSyncedAt: timestamp
|
|
313031
|
+
});
|
|
312265
313032
|
}
|
|
312266
313033
|
function createPlannedOutputs(inputs) {
|
|
312267
313034
|
const workspaceName = inputs.domainCode ? `[${inputs.domainCode}] ${inputs.projectName}` : inputs.projectName;
|
|
@@ -312294,7 +313061,12 @@ function createPlannedOutputs(inputs) {
|
|
|
312294
313061
|
mode: inputs.breakingChangeMode,
|
|
312295
313062
|
status: "skipped",
|
|
312296
313063
|
summaryPath: ""
|
|
312297
|
-
})
|
|
313064
|
+
}),
|
|
313065
|
+
"sync-status": "",
|
|
313066
|
+
"branch-decision": "",
|
|
313067
|
+
"spec-version-tag": "",
|
|
313068
|
+
"spec-version-url": "",
|
|
313069
|
+
"spec-content-changed": ""
|
|
312298
313070
|
};
|
|
312299
313071
|
}
|
|
312300
313072
|
function warnIfDeprecatedAccessToken(actionCore, inputs) {
|
|
@@ -312331,9 +313103,6 @@ function readActionInputs(actionCore) {
|
|
|
312331
313103
|
}
|
|
312332
313104
|
const postmanApiKey = optionalInput(actionCore, "postman-api-key") ?? "";
|
|
312333
313105
|
const postmanAccessToken = optionalInput(actionCore, "postman-access-token");
|
|
312334
|
-
if (!postmanApiKey && !postmanAccessToken) {
|
|
312335
|
-
throw new Error("One of postman-api-key or postman-access-token is required.");
|
|
312336
|
-
}
|
|
312337
313106
|
const githubToken = optionalInput(actionCore, "github-token") || process.env.GITHUB_TOKEN;
|
|
312338
313107
|
const ghFallbackToken = optionalInput(actionCore, "gh-fallback-token") || process.env.GH_FALLBACK_TOKEN;
|
|
312339
313108
|
if (postmanApiKey) actionCore.setSecret(postmanApiKey);
|
|
@@ -312377,6 +313146,7 @@ function readActionInputs(actionCore) {
|
|
|
312377
313146
|
INPUT_NESTED_FOLDER_HIERARCHY: optionalInput(actionCore, "nested-folder-hierarchy") ?? bootstrapActionContract.inputs["nested-folder-hierarchy"].default,
|
|
312378
313147
|
INPUT_REQUEST_NAME_SOURCE: optionalInput(actionCore, "request-name-source") ?? bootstrapActionContract.inputs["request-name-source"].default,
|
|
312379
313148
|
INPUT_OPENAPI_VERSION: optionalInput(actionCore, "openapi-version") ?? "",
|
|
313149
|
+
INPUT_PRESERVE_OAS30_TYPE_NULL: optionalInput(actionCore, "preserve-oas30-type-null") ?? bootstrapActionContract.inputs["preserve-oas30-type-null"].default,
|
|
312380
313150
|
INPUT_BREAKING_CHANGE_MODE: optionalInput(actionCore, "breaking-change-mode") ?? bootstrapActionContract.inputs["breaking-change-mode"].default,
|
|
312381
313151
|
INPUT_BREAKING_BASELINE_SPEC_PATH: optionalInput(actionCore, "breaking-baseline-spec-path"),
|
|
312382
313152
|
INPUT_BREAKING_RULES_PATH: optionalInput(actionCore, "breaking-rules-path") ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
|
|
@@ -312487,6 +313257,27 @@ async function lintSpecViaCli(dependencies, workspaceId, specId) {
|
|
|
312487
313257
|
warnings
|
|
312488
313258
|
};
|
|
312489
313259
|
}
|
|
313260
|
+
function normalizeLintPath(value) {
|
|
313261
|
+
return value.trim().replace(/^\$\.?/, "").replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "");
|
|
313262
|
+
}
|
|
313263
|
+
function applyOas30TypeNullLintCompatibility(summary2, sourceTypeNullPaths) {
|
|
313264
|
+
const acceptedPaths = new Set(sourceTypeNullPaths.map(normalizeLintPath));
|
|
313265
|
+
if (acceptedPaths.size === 0) return summary2;
|
|
313266
|
+
const violations = summary2.violations.map((violation) => {
|
|
313267
|
+
const path10 = normalizeLintPath(violation.path ?? "");
|
|
313268
|
+
const issue2 = violation.issue ?? "";
|
|
313269
|
+
const isTypeEnumFinding = /["']?type["']? property/i.test(issue2) && /allowed values|must be equal to one of/i.test(issue2);
|
|
313270
|
+
if (violation.severity === "ERROR" && acceptedPaths.has(path10) && isTypeEnumFinding) {
|
|
313271
|
+
return { ...violation, severity: "WARNING" };
|
|
313272
|
+
}
|
|
313273
|
+
return violation;
|
|
313274
|
+
});
|
|
313275
|
+
return {
|
|
313276
|
+
errors: violations.filter((entry) => entry.severity === "ERROR").length,
|
|
313277
|
+
violations,
|
|
313278
|
+
warnings: violations.filter((entry) => entry.severity === "WARNING").length
|
|
313279
|
+
};
|
|
313280
|
+
}
|
|
312490
313281
|
function shouldRetrySpecFetch(error2) {
|
|
312491
313282
|
const retryability = classifySafeFetchRetryability(error2);
|
|
312492
313283
|
return retryability === "retryable" || retryability === "unknown";
|
|
@@ -312967,7 +313758,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
|
|
|
312967
313758
|
const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
|
|
312968
313759
|
const serviceConfigPath = path9.join(path9.dirname(path9.resolve(workspaceRoot, inputs.specPath)), "grpc_service_config.json");
|
|
312969
313760
|
try {
|
|
312970
|
-
grpcServiceConfigJson = (0,
|
|
313761
|
+
grpcServiceConfigJson = (0, import_node_fs6.readFileSync)(serviceConfigPath, "utf8");
|
|
312971
313762
|
dependencies.core.info(`Found gRPC service config at ${serviceConfigPath}; validating it against the proto contract`);
|
|
312972
313763
|
} catch {
|
|
312973
313764
|
}
|
|
@@ -312981,7 +313772,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
|
|
|
312981
313772
|
const resolved = path9.resolve(specDir, location2);
|
|
312982
313773
|
if (!resolved.startsWith(specDir + path9.sep)) return void 0;
|
|
312983
313774
|
try {
|
|
312984
|
-
return (0,
|
|
313775
|
+
return (0, import_node_fs6.readFileSync)(resolved, "utf8");
|
|
312985
313776
|
} catch {
|
|
312986
313777
|
return void 0;
|
|
312987
313778
|
}
|
|
@@ -313122,6 +313913,42 @@ async function createExtensibleContractCollection(workspaceId, built, inputs, de
|
|
|
313122
313913
|
}
|
|
313123
313914
|
async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
313124
313915
|
const outputs = createPlannedOutputs(inputs);
|
|
313916
|
+
const branchDecision = decideBranchTier(inputs);
|
|
313917
|
+
const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
|
|
313918
|
+
const canonicalProjectName = inputs.projectName;
|
|
313919
|
+
const workspaceName = createWorkspaceName(inputs);
|
|
313920
|
+
const aboutText = `Auto-provisioned by Postman for ${inputs.projectName}`;
|
|
313921
|
+
if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
|
|
313922
|
+
inputs = {
|
|
313923
|
+
...inputs,
|
|
313924
|
+
projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch)
|
|
313925
|
+
};
|
|
313926
|
+
dependencies.core.info(`branch-aware sync: preview asset set "${inputs.projectName}"`);
|
|
313927
|
+
} else if (branchDecision.tier === "channel" && branchDecision.channel) {
|
|
313928
|
+
inputs = {
|
|
313929
|
+
...inputs,
|
|
313930
|
+
projectName: channelAssetName(inputs.projectName, branchDecision.channel.code)
|
|
313931
|
+
};
|
|
313932
|
+
dependencies.core.info(`branch-aware sync: channel asset set "${inputs.projectName}"`);
|
|
313933
|
+
}
|
|
313934
|
+
const collectionBranchMarker = renderCollectionBranchMarker(branchDecision, inputs.repoUrl);
|
|
313935
|
+
if (branchDecision.tier !== "legacy") {
|
|
313936
|
+
outputs["sync-status"] = "synced";
|
|
313937
|
+
outputs["branch-decision"] = serializeBranchDecision(branchDecision);
|
|
313938
|
+
}
|
|
313939
|
+
if (!isCanonicalWriter) {
|
|
313940
|
+
const explicitCanonicalIds = [
|
|
313941
|
+
["spec-id", inputs.specId],
|
|
313942
|
+
["baseline-collection-id", inputs.baselineCollectionId],
|
|
313943
|
+
["smoke-collection-id", inputs.smokeCollectionId],
|
|
313944
|
+
["contract-collection-id", inputs.contractCollectionId]
|
|
313945
|
+
].filter(([, value]) => Boolean(value));
|
|
313946
|
+
if (explicitCanonicalIds.length > 0) {
|
|
313947
|
+
throw new Error(
|
|
313948
|
+
`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).`
|
|
313949
|
+
);
|
|
313950
|
+
}
|
|
313951
|
+
}
|
|
313125
313952
|
const requiresReleaseLabel = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
|
|
313126
313953
|
const releaseLabel = requiresReleaseLabel ? deriveReleaseLabel(inputs) : void 0;
|
|
313127
313954
|
if (requiresReleaseLabel && !releaseLabel) {
|
|
@@ -313129,11 +313956,23 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313129
313956
|
"Versioned spec or collection sync requires a release-label or derivable GitHub ref metadata"
|
|
313130
313957
|
);
|
|
313131
313958
|
}
|
|
313132
|
-
const collectionAssetProjectName = inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
|
|
313133
|
-
const
|
|
313134
|
-
const
|
|
313135
|
-
const stateStore =
|
|
313136
|
-
|
|
313959
|
+
const collectionAssetProjectName = branchDecision.tier === "channel" ? canonicalProjectName : inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
|
|
313960
|
+
const rawStateStore = resolveResourcesStateStore(dependencies);
|
|
313961
|
+
const trackedState = rawStateStore.read();
|
|
313962
|
+
const stateStore = isCanonicalWriter ? rawStateStore : {
|
|
313963
|
+
read: rawStateStore.read,
|
|
313964
|
+
write: () => {
|
|
313965
|
+
dependencies.core.info(
|
|
313966
|
+
`branch-aware sync: skipping .postman/resources.yaml write on ${branchDecision.tier} run (canonical-only tracked state)`
|
|
313967
|
+
);
|
|
313968
|
+
}
|
|
313969
|
+
};
|
|
313970
|
+
const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
|
|
313971
|
+
if (!isCanonicalWriter && trackedState?.cloudResources) {
|
|
313972
|
+
dependencies.core.info(
|
|
313973
|
+
"branch-aware sync: canonical asset ids in .postman/resources.yaml are not resolved on a non-canonical run"
|
|
313974
|
+
);
|
|
313975
|
+
}
|
|
313137
313976
|
const writableResourcesState = resourcesState ?? {};
|
|
313138
313977
|
const additionalCollections = loadAdditionalCollectionFiles(
|
|
313139
313978
|
inputs.additionalCollectionsDir,
|
|
@@ -313153,16 +313992,19 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313153
313992
|
}
|
|
313154
313993
|
let previousSpecContent;
|
|
313155
313994
|
let previousSpecRollbackHash;
|
|
313995
|
+
let specContentUnchanged = false;
|
|
313156
313996
|
let detectedOpenapiVersion = "3.0";
|
|
313157
313997
|
let contractIndex;
|
|
313158
313998
|
let sourceSpecContent = "";
|
|
313999
|
+
let sourceTypeNullPaths = [];
|
|
314000
|
+
let preserveSourceSpecBytes = false;
|
|
313159
314001
|
const rawSpecContent = await runGroup(
|
|
313160
314002
|
dependencies.core,
|
|
313161
314003
|
"Read API Spec",
|
|
313162
314004
|
async () => {
|
|
313163
314005
|
if (inputs.specPath) {
|
|
313164
314006
|
const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
|
|
313165
|
-
return (0,
|
|
314007
|
+
return (0, import_node_fs6.readFileSync)(path9.resolve(workspaceRoot, inputs.specPath), "utf8");
|
|
313166
314008
|
}
|
|
313167
314009
|
if (dependencies.specFetcher === fetch) {
|
|
313168
314010
|
return safeFetchText(inputs.specUrl, { depth: 0 });
|
|
@@ -313198,6 +314040,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313198
314040
|
};
|
|
313199
314041
|
const rootKey = inputs.specPath ? void 0 : normalizeRef(inputs.specUrl);
|
|
313200
314042
|
const loaderOptions = {
|
|
314043
|
+
preserveOas30TypeNull: Boolean(inputs.preserveOas30TypeNull),
|
|
313201
314044
|
fetchText: async (url, fetchOptions) => {
|
|
313202
314045
|
if (rootKey && normalizeRef(url) === rootKey) return rawSpecContent;
|
|
313203
314046
|
if (dependencies.specFetcher === fetch) return safeFetchText(url, fetchOptions);
|
|
@@ -313206,6 +314049,8 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313206
314049
|
};
|
|
313207
314050
|
const loaded = inputs.specPath ? await loadOpenApiContractSpecFromPath(inputs.specPath, loaderOptions) : await loadOpenApiContractSpec(inputs.specUrl, loaderOptions);
|
|
313208
314051
|
sourceSpecContent = loaded.content;
|
|
314052
|
+
sourceTypeNullPaths = loaded.sourceTypeNullPaths;
|
|
314053
|
+
preserveSourceSpecBytes = sourceTypeNullPaths.length > 0;
|
|
313209
314054
|
const document2 = normalizeSpecDocument(
|
|
313210
314055
|
loaded.bundledContent,
|
|
313211
314056
|
(msg) => dependencies.core.warning(msg)
|
|
@@ -313228,11 +314073,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313228
314073
|
`Unable to verify existing Spec Hub OpenAPI version for spec-id ${specId}; clear spec-id to create a fresh spec`
|
|
313229
314074
|
);
|
|
313230
314075
|
}
|
|
313231
|
-
previousSpecContent = normalizeSpecDocument(
|
|
314076
|
+
previousSpecContent = preserveSourceSpecBytes ? previousRaw : normalizeSpecDocument(
|
|
313232
314077
|
previousRaw,
|
|
313233
314078
|
(msg) => dependencies.core.warning(`Previous spec normalization: ${msg}`)
|
|
313234
314079
|
);
|
|
313235
|
-
previousSpecRollbackHash = (0,
|
|
314080
|
+
previousSpecRollbackHash = (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex");
|
|
313236
314081
|
const existingSpecType = normalizeSpecTypeFromContent(previousSpecContent);
|
|
313237
314082
|
if (existingSpecType !== incomingSpecType) {
|
|
313238
314083
|
throw new Error(
|
|
@@ -313243,6 +314088,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313243
314088
|
dependencies.core.info(
|
|
313244
314089
|
`Auto-detected OpenAPI version from spec content: ${detectedOpenapiVersion}`
|
|
313245
314090
|
);
|
|
314091
|
+
if (preserveSourceSpecBytes) {
|
|
314092
|
+
dependencies.core.info(
|
|
314093
|
+
`Preserving original OpenAPI source bytes; accepted ${sourceTypeNullPaths.length} legacy type: null member(s) for internal validation`
|
|
314094
|
+
);
|
|
314095
|
+
return loaded.content;
|
|
314096
|
+
}
|
|
313246
314097
|
return document2;
|
|
313247
314098
|
}
|
|
313248
314099
|
);
|
|
@@ -313278,6 +314129,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313278
314129
|
`OpenAPI breaking-change check failed: ${breakingChangeResult.message || "breaking changes detected"}`
|
|
313279
314130
|
);
|
|
313280
314131
|
}
|
|
314132
|
+
const uploadSpecContent = embedSpecBranchMarker(
|
|
314133
|
+
specContent,
|
|
314134
|
+
branchDecision,
|
|
314135
|
+
inputs.repoUrl
|
|
314136
|
+
);
|
|
313281
314137
|
const provisioned = await provisionWorkspace(
|
|
313282
314138
|
inputs,
|
|
313283
314139
|
dependencies,
|
|
@@ -313402,10 +314258,17 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313402
314258
|
specId ? "Update Spec in Spec Hub" : "Upload Spec to Spec Hub",
|
|
313403
314259
|
async () => {
|
|
313404
314260
|
if (specId) {
|
|
313405
|
-
|
|
313406
|
-
|
|
313407
|
-
|
|
313408
|
-
|
|
314261
|
+
if (previousSpecContent !== void 0 && (0, import_node_crypto6.createHash)("sha256").update(uploadSpecContent).digest("hex") === (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex")) {
|
|
314262
|
+
specContentUnchanged = true;
|
|
314263
|
+
dependencies.core.info(
|
|
314264
|
+
`Spec content unchanged (sha256 match); skipping Spec Hub update and version tag for ${specId}.`
|
|
314265
|
+
);
|
|
314266
|
+
} else {
|
|
314267
|
+
dependencies.core.info(
|
|
314268
|
+
`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.`
|
|
314269
|
+
);
|
|
314270
|
+
await dependencies.postman.updateSpec(specId, uploadSpecContent, workspaceId);
|
|
314271
|
+
}
|
|
313409
314272
|
} else {
|
|
313410
314273
|
specId = await dependencies.postman.uploadSpec(
|
|
313411
314274
|
workspaceId || "",
|
|
@@ -313413,7 +314276,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313413
314276
|
inputs,
|
|
313414
314277
|
inputs.specSyncMode === "version" ? releaseLabel : void 0
|
|
313415
314278
|
),
|
|
313416
|
-
|
|
314279
|
+
uploadSpecContent,
|
|
313417
314280
|
detectedOpenapiVersion
|
|
313418
314281
|
);
|
|
313419
314282
|
}
|
|
@@ -313430,8 +314293,9 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313430
314293
|
}
|
|
313431
314294
|
)
|
|
313432
314295
|
);
|
|
314296
|
+
outputs["spec-content-changed"] = isCanonicalWriter && !specContentUnchanged ? "true" : "false";
|
|
313433
314297
|
if (lintEnabled) {
|
|
313434
|
-
|
|
314298
|
+
let lintSummary = await runRollbackStage(
|
|
313435
314299
|
"Lint Spec via Postman CLI",
|
|
313436
314300
|
async () => runGroup(
|
|
313437
314301
|
dependencies.core,
|
|
@@ -313439,6 +314303,16 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313439
314303
|
async () => lintSpecViaCli(dependencies, workspaceId || "", outputs["spec-id"])
|
|
313440
314304
|
)
|
|
313441
314305
|
);
|
|
314306
|
+
if (preserveSourceSpecBytes) {
|
|
314307
|
+
const originalErrorCount = lintSummary.errors;
|
|
314308
|
+
lintSummary = applyOas30TypeNullLintCompatibility(lintSummary, sourceTypeNullPaths);
|
|
314309
|
+
const acceptedCount = originalErrorCount - lintSummary.errors;
|
|
314310
|
+
if (acceptedCount > 0) {
|
|
314311
|
+
dependencies.core.info(
|
|
314312
|
+
`Accepted ${acceptedCount} Postman CLI type: null finding(s) covered by preserve-oas30-type-null`
|
|
314313
|
+
);
|
|
314314
|
+
}
|
|
314315
|
+
}
|
|
313442
314316
|
outputs["lint-summary-json"] = JSON.stringify({
|
|
313443
314317
|
errors: lintSummary.errors,
|
|
313444
314318
|
total: lintSummary.violations.length,
|
|
@@ -313463,7 +314337,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313463
314337
|
});
|
|
313464
314338
|
dependencies.core.warning("lint skipped: governance errors not enforced (no postman-api-key)");
|
|
313465
314339
|
}
|
|
313466
|
-
|
|
314340
|
+
if (specContentUnchanged) {
|
|
314341
|
+
outputs["baseline-collection-id"] = baselineCollectionId || "";
|
|
314342
|
+
outputs["smoke-collection-id"] = smokeCollectionId || "";
|
|
314343
|
+
outputs["contract-collection-id"] = contractCollectionId || "";
|
|
314344
|
+
dependencies.core.info("Spec content unchanged; skipping collection regeneration and version finalization.");
|
|
314345
|
+
} else await runRollbackStage(
|
|
313467
314346
|
"Generate Collections from Spec",
|
|
313468
314347
|
async () => runGroup(
|
|
313469
314348
|
dependencies.core,
|
|
@@ -313502,10 +314381,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313502
314381
|
return;
|
|
313503
314382
|
}
|
|
313504
314383
|
}
|
|
314384
|
+
const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
|
|
313505
314385
|
outputs[outputKey] = await dependencies.postman.generateCollection(
|
|
313506
314386
|
specId2,
|
|
313507
314387
|
assetProjectName,
|
|
313508
|
-
|
|
314388
|
+
effectivePrefix,
|
|
313509
314389
|
inputs.folderStrategy,
|
|
313510
314390
|
inputs.nestedFolderHierarchy,
|
|
313511
314391
|
inputs.requestNameSource
|
|
@@ -313526,6 +314406,32 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313526
314406
|
await ensureCollection(BASELINE_COLLECTION_PREFIX, baselineCollectionId, "baseline-collection-id");
|
|
313527
314407
|
await ensureCollection(SMOKE_COLLECTION_PREFIX, smokeCollectionId, "smoke-collection-id");
|
|
313528
314408
|
await ensureCollection(CONTRACT_COLLECTION_PREFIX, contractCollectionId, "contract-collection-id");
|
|
314409
|
+
if (dependencies.postman.adoptGeneratedCollection) {
|
|
314410
|
+
const reelect = async (prefix, outputKey) => {
|
|
314411
|
+
const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
|
|
314412
|
+
const preferred = outputs[outputKey];
|
|
314413
|
+
if (!preferred) return;
|
|
314414
|
+
outputs[outputKey] = await dependencies.postman.adoptGeneratedCollection(
|
|
314415
|
+
specId2,
|
|
314416
|
+
assetProjectName,
|
|
314417
|
+
effectivePrefix,
|
|
314418
|
+
preferred
|
|
314419
|
+
);
|
|
314420
|
+
};
|
|
314421
|
+
await reelect(BASELINE_COLLECTION_PREFIX, "baseline-collection-id");
|
|
314422
|
+
await reelect(SMOKE_COLLECTION_PREFIX, "smoke-collection-id");
|
|
314423
|
+
await reelect(CONTRACT_COLLECTION_PREFIX, "contract-collection-id");
|
|
314424
|
+
}
|
|
314425
|
+
if (collectionBranchMarker) {
|
|
314426
|
+
if (!dependencies.postman.updateCollectionDescription) {
|
|
314427
|
+
throw new Error("Branch-scoped collections require updateCollectionDescription support");
|
|
314428
|
+
}
|
|
314429
|
+
await Promise.all([
|
|
314430
|
+
outputs["baseline-collection-id"],
|
|
314431
|
+
outputs["smoke-collection-id"],
|
|
314432
|
+
outputs["contract-collection-id"]
|
|
314433
|
+
].filter(Boolean).map((id) => dependencies.postman.updateCollectionDescription(id, collectionBranchMarker)));
|
|
314434
|
+
}
|
|
313529
314435
|
if (!dependencies.postman.injectContractTests) {
|
|
313530
314436
|
throw new Error(
|
|
313531
314437
|
"Dynamic contract tests require injectContractTests support from the access-token gateway client"
|
|
@@ -313628,6 +314534,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313628
314534
|
workspaceId: workspaceId || ""
|
|
313629
314535
|
});
|
|
313630
314536
|
for (const result of additionalResults) {
|
|
314537
|
+
if (collectionBranchMarker) {
|
|
314538
|
+
if (!dependencies.postman.updateCollectionDescription) {
|
|
314539
|
+
throw new Error("Branch-scoped collections require updateCollectionDescription support");
|
|
314540
|
+
}
|
|
314541
|
+
await dependencies.postman.updateCollectionDescription(result.collectionId, collectionBranchMarker);
|
|
314542
|
+
}
|
|
313631
314543
|
completedExternalSideEffects.push(
|
|
313632
314544
|
`${result.operation}AdditionalCollection(${result.collectionId} from ${result.displayPath})`
|
|
313633
314545
|
);
|
|
@@ -313703,8 +314615,66 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
|
|
|
313703
314615
|
}
|
|
313704
314616
|
return outputs;
|
|
313705
314617
|
}
|
|
314618
|
+
async function runGatedValidation(inputs, decision, actionCore) {
|
|
314619
|
+
actionCore.info(`branch-aware sync: gated run (${decision.reason}) \u2014 credential-free static validation, zero workspace writes`);
|
|
314620
|
+
const outputs = createPlannedOutputs(inputs);
|
|
314621
|
+
outputs["sync-status"] = "skipped-branch-gate";
|
|
314622
|
+
outputs["branch-decision"] = serializeBranchDecision(decision);
|
|
314623
|
+
let violations = [];
|
|
314624
|
+
let validated = false;
|
|
314625
|
+
try {
|
|
314626
|
+
let content;
|
|
314627
|
+
if (inputs.specPath) {
|
|
314628
|
+
content = (0, import_node_fs6.readFileSync)(inputs.specPath, "utf8");
|
|
314629
|
+
} else if (inputs.specUrl) {
|
|
314630
|
+
content = await safeFetchText(inputs.specUrl, { depth: 0 });
|
|
314631
|
+
}
|
|
314632
|
+
if (content) {
|
|
314633
|
+
const specType = detectSpecType(content, inputs.specPath);
|
|
314634
|
+
if (specType === "openapi") {
|
|
314635
|
+
const document2 = parseOpenApiDocument(content);
|
|
314636
|
+
const index = buildContractIndex(document2);
|
|
314637
|
+
violations = index.warnings;
|
|
314638
|
+
validated = true;
|
|
314639
|
+
} else {
|
|
314640
|
+
actionCore.info(`branch gate: static lint for spec type ${specType} runs through its protocol builder on publish; parse-only gate applied`);
|
|
314641
|
+
validated = true;
|
|
314642
|
+
}
|
|
314643
|
+
} else {
|
|
314644
|
+
actionCore.info("branch gate: no spec-url/spec-path provided; nothing to validate");
|
|
314645
|
+
}
|
|
314646
|
+
} catch (error2) {
|
|
314647
|
+
throw new Error(
|
|
314648
|
+
`branch gate: static validation failed: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
314649
|
+
{ cause: error2 }
|
|
314650
|
+
);
|
|
314651
|
+
}
|
|
314652
|
+
outputs["lint-summary-json"] = JSON.stringify({
|
|
314653
|
+
status: validated ? "static-only" : "skipped",
|
|
314654
|
+
reason: "branch-gated run: governance lint requires upload and runs on publish/preview syncs",
|
|
314655
|
+
errors: 0,
|
|
314656
|
+
warnings: violations.length,
|
|
314657
|
+
total: violations.length,
|
|
314658
|
+
violations: violations.map((issue2) => ({ issue: issue2, severity: "WARNING" }))
|
|
314659
|
+
});
|
|
314660
|
+
for (const [name, value] of Object.entries(outputs)) {
|
|
314661
|
+
actionCore.setOutput(name, value);
|
|
314662
|
+
}
|
|
314663
|
+
return outputs;
|
|
314664
|
+
}
|
|
313706
314665
|
async function runAction(actionCore = core_exports, actionExec = exec_exports, actionIo = io_exports) {
|
|
313707
314666
|
const inputs = readActionInputs(actionCore);
|
|
314667
|
+
const branchDecision = decideBranchTier(inputs);
|
|
314668
|
+
if (branchDecision.tier === "gated") {
|
|
314669
|
+
return runGatedValidation(inputs, branchDecision, actionCore);
|
|
314670
|
+
}
|
|
314671
|
+
if (!inputs.postmanApiKey && !inputs.postmanAccessToken) {
|
|
314672
|
+
throw new Error("One of postman-api-key or postman-access-token is required for a writing sync.");
|
|
314673
|
+
}
|
|
314674
|
+
if (branchDecision.tier !== "legacy") {
|
|
314675
|
+
actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
|
|
314676
|
+
process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
|
|
314677
|
+
}
|
|
313708
314678
|
await mintAccessTokenIfNeeded2(inputs, {
|
|
313709
314679
|
info: (message) => actionCore.info(message),
|
|
313710
314680
|
warning: (message) => actionCore.warning(message)
|
|
@@ -313781,6 +314751,7 @@ function createRoutingPostmanClient(options) {
|
|
|
313781
314751
|
updateSpec: requireAccessToken("updateSpec"),
|
|
313782
314752
|
getSpecContent: requireAccessToken("getSpecContent"),
|
|
313783
314753
|
generateCollection: requireAccessToken("generateCollection"),
|
|
314754
|
+
adoptGeneratedCollection: requireAccessToken("adoptGeneratedCollection"),
|
|
313784
314755
|
createWorkspace: requireAccessToken("createWorkspace"),
|
|
313785
314756
|
getWorkspaceVisibility: requireAccessToken("getWorkspaceVisibility"),
|
|
313786
314757
|
getWorkspaceGitRepoUrl: requireAccessToken("getWorkspaceGitRepoUrl"),
|
|
@@ -313790,10 +314761,13 @@ function createRoutingPostmanClient(options) {
|
|
|
313790
314761
|
inviteRequesterToWorkspace: requireAccessToken("inviteRequesterToWorkspace"),
|
|
313791
314762
|
injectTests: requireAccessToken("injectTests"),
|
|
313792
314763
|
tagCollection: requireAccessToken("tagCollection"),
|
|
314764
|
+
tagSpecVersion: requireAccessToken("tagSpecVersion"),
|
|
314765
|
+
listSpecVersionTags: requireAccessToken("listSpecVersionTags"),
|
|
313793
314766
|
deleteCollection: requireAccessToken("deleteCollection"),
|
|
313794
314767
|
injectContractTests: requireAccessToken("injectContractTests"),
|
|
313795
314768
|
createCollection: requireAccessToken("createCollection"),
|
|
313796
|
-
updateCollection: requireAccessToken("updateCollection")
|
|
314769
|
+
updateCollection: requireAccessToken("updateCollection"),
|
|
314770
|
+
updateCollectionDescription: requireAccessToken("updateCollectionDescription")
|
|
313797
314771
|
};
|
|
313798
314772
|
}
|
|
313799
314773
|
return {
|
|
@@ -313805,6 +314779,7 @@ function createRoutingPostmanClient(options) {
|
|
|
313805
314779
|
// not a reason to reach for the API key.
|
|
313806
314780
|
uploadSpec: (workspaceId, projectName, specContent, openapiVersion) => gateway.uploadSpec(workspaceId, projectName, specContent, openapiVersion ?? "3.0"),
|
|
313807
314781
|
generateCollection: (specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) => gateway.generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource),
|
|
314782
|
+
adoptGeneratedCollection: (specId, projectName, prefix, preferredId) => gateway.adoptGeneratedCollection(specId, projectName, prefix, preferredId),
|
|
313808
314783
|
updateSpec: (specId, specContent, workspaceId) => gateway.updateSpec(specId, specContent, workspaceId),
|
|
313809
314784
|
getSpecContent: (specId) => gateway.getSpecContent(specId),
|
|
313810
314785
|
createWorkspace: (name, about, targetTeamId) => gateway.createWorkspace(name, about, targetTeamId),
|
|
@@ -313816,6 +314791,8 @@ function createRoutingPostmanClient(options) {
|
|
|
313816
314791
|
// + tagging surfaces (live-proven). PMAK is reserved for token minting, so
|
|
313817
314792
|
// these never fall back to the API key even when one is present.
|
|
313818
314793
|
injectTests: (collectionId, type) => gateway.injectTests(collectionId, type),
|
|
314794
|
+
tagSpecVersion: (specId, name) => gateway.tagSpecVersion(specId, name),
|
|
314795
|
+
listSpecVersionTags: (specId) => gateway.listSpecVersionTags(specId),
|
|
313819
314796
|
tagCollection: (collectionId, tags) => gateway.tagCollection(collectionId, tags),
|
|
313820
314797
|
// Sub-team (squad) enumeration over the gateway `ums` service. Access-token
|
|
313821
314798
|
// only — never PMAK — so org-mode detection no longer needs a PMAK GET /teams.
|
|
@@ -313833,7 +314810,8 @@ function createRoutingPostmanClient(options) {
|
|
|
313833
314810
|
// so no asset op ever reaches for the API key.
|
|
313834
314811
|
injectContractTests: (collectionUid, index) => gateway.injectContractTests(collectionUid, index),
|
|
313835
314812
|
createCollection: (workspaceId, collection, options2) => gateway.createCollection(workspaceId, collection, options2),
|
|
313836
|
-
updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection)
|
|
314813
|
+
updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection),
|
|
314814
|
+
updateCollectionDescription: (collectionUid, description) => gateway.updateCollectionDescription(collectionUid, description)
|
|
313837
314815
|
};
|
|
313838
314816
|
}
|
|
313839
314817
|
function createBootstrapDependencies(inputs, factories, orgMode = false) {
|
|
@@ -313907,19 +314885,25 @@ function createBootstrapDependencies(inputs, factories, orgMode = false) {
|
|
|
313907
314885
|
}
|
|
313908
314886
|
// Annotate the CommonJS export names for ESM import in node:
|
|
313909
314887
|
0 && (module.exports = {
|
|
314888
|
+
applyOas30TypeNullLintCompatibility,
|
|
314889
|
+
buildSpecVersionUrl,
|
|
313910
314890
|
createBootstrapDependencies,
|
|
313911
314891
|
createExtensibleContractCollection,
|
|
313912
314892
|
createPlannedOutputs,
|
|
313913
314893
|
createRoutingPostmanClient,
|
|
314894
|
+
decideBranchTier,
|
|
314895
|
+
embedSpecBranchMarker,
|
|
313914
314896
|
ensurePostmanCli,
|
|
313915
314897
|
getInput,
|
|
313916
314898
|
lintSpecViaCli,
|
|
313917
314899
|
mintAccessTokenIfNeeded,
|
|
313918
314900
|
normalizeSpecDocument,
|
|
313919
314901
|
readActionInputs,
|
|
314902
|
+
renderCollectionBranchMarker,
|
|
313920
314903
|
resolveInputs,
|
|
313921
314904
|
runAction,
|
|
313922
|
-
runBootstrap
|
|
314905
|
+
runBootstrap,
|
|
314906
|
+
runGatedValidation
|
|
313923
314907
|
});
|
|
313924
314908
|
/*! Bundled license information:
|
|
313925
314909
|
|