@postman-cse/onboarding-bootstrap 0.10.0 → 0.11.0
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 +4 -0
- package/dist/cli.cjs +140 -30
- package/dist/index.cjs +139 -30
- package/package.json +1 -1
- package/src/cli.ts +1 -0
- package/src/contracts.ts +6 -0
- package/src/index.ts +51 -1
- package/src/lib/postman/internal-integration-adapter.ts +116 -30
- package/tests/bootstrap-action.test.ts +86 -2
- package/tests/cli.test.ts +3 -0
- package/tests/contract.test.ts +8 -0
- package/tests/internal-integration-adapter.test.ts +69 -0
package/README.md
CHANGED
|
@@ -202,6 +202,7 @@ steps:
|
|
|
202
202
|
| `baseline-collection-id` | | Reuse an existing baseline collection. |
|
|
203
203
|
| `smoke-collection-id` | | Reuse an existing smoke collection. |
|
|
204
204
|
| `contract-collection-id` | | Reuse an existing contract collection. |
|
|
205
|
+
| `sync-examples` | `true` | Whether linked spec/collection relations should enable example syncing during cloud linkage. |
|
|
205
206
|
| `collection-sync-mode` | `refresh` | Collection lifecycle policy. `reuse` keeps existing collections, `refresh` regenerates the current collection set from the latest spec, and `version` creates or reuses release-scoped collections. |
|
|
206
207
|
| `spec-sync-mode` | `update` | Spec lifecycle policy. `update` keeps one canonical spec current in Spec Hub, while `version` creates or reuses a release-scoped spec asset. |
|
|
207
208
|
| `release-label` | | Optional release label used for versioned specs and collections. When omitted for versioned sync, the action derives one from GitHub tag or branch metadata. |
|
|
@@ -247,6 +248,14 @@ Current Postman asset state lives in `.postman/resources.yaml`.
|
|
|
247
248
|
- `update` and `reuse` modes resolve current-state mappings from the checked-out ref.
|
|
248
249
|
- `version` mode reuses only the checked-out ref's mappings; release history lives in git history and tags, not in a separate manifest file or repository variables.
|
|
249
250
|
|
|
251
|
+
### Cloud spec-to-collection syncing
|
|
252
|
+
|
|
253
|
+
After collections exist, bootstrap links them to the cloud specification and triggers a spec-side collection sync when `postman-access-token` is available.
|
|
254
|
+
|
|
255
|
+
- `sync-examples: true` (default) enables example syncing in that relation setup.
|
|
256
|
+
- `sync-examples: false` keeps the relation but disables example syncing.
|
|
257
|
+
- If `postman-access-token` is missing, bootstrap warns and skips the cloud link/sync step.
|
|
258
|
+
|
|
250
259
|
### Contract smoke monitoring
|
|
251
260
|
|
|
252
261
|
This repo includes `.github/workflows/contract-smoke.yml`, a scheduled live contract check for the upstream Postman APIs used by bootstrap.
|
package/action.yml
CHANGED
|
@@ -17,6 +17,10 @@ inputs:
|
|
|
17
17
|
contract-collection-id:
|
|
18
18
|
description: Existing contract collection ID
|
|
19
19
|
required: false
|
|
20
|
+
sync-examples:
|
|
21
|
+
description: Whether linked spec/collection relations should enable example syncing
|
|
22
|
+
required: false
|
|
23
|
+
default: 'true'
|
|
20
24
|
collection-sync-mode:
|
|
21
25
|
description: Collection lifecycle policy (reuse, refresh, or version)
|
|
22
26
|
required: false
|
package/dist/cli.cjs
CHANGED
|
@@ -28728,6 +28728,12 @@ var openAlphaActionContract = {
|
|
|
28728
28728
|
description: "Existing contract collection ID.",
|
|
28729
28729
|
required: false
|
|
28730
28730
|
},
|
|
28731
|
+
"sync-examples": {
|
|
28732
|
+
description: "Whether linked spec/collection relations should enable example syncing.",
|
|
28733
|
+
required: false,
|
|
28734
|
+
default: "true",
|
|
28735
|
+
allowedValues: ["true", "false"]
|
|
28736
|
+
},
|
|
28731
28737
|
"collection-sync-mode": {
|
|
28732
28738
|
description: "Collection lifecycle policy: reuse existing collections, refresh them from the latest spec, or version them by release label.",
|
|
28733
28739
|
required: false,
|
|
@@ -29659,6 +29665,26 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29659
29665
|
options.workerBaseUrl || "https://catalog-admin.postman-account2009.workers.dev"
|
|
29660
29666
|
).replace(/\/+$/, "");
|
|
29661
29667
|
}
|
|
29668
|
+
async proxyRequest(service, method, requestPath, body) {
|
|
29669
|
+
const url = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
|
|
29670
|
+
const headers = {
|
|
29671
|
+
"Content-Type": "application/json",
|
|
29672
|
+
"x-access-token": this.accessToken
|
|
29673
|
+
};
|
|
29674
|
+
if (this.teamId) {
|
|
29675
|
+
headers["x-entity-team-id"] = this.teamId;
|
|
29676
|
+
}
|
|
29677
|
+
return this.fetchImpl(url, {
|
|
29678
|
+
method: "POST",
|
|
29679
|
+
headers,
|
|
29680
|
+
body: JSON.stringify({
|
|
29681
|
+
service,
|
|
29682
|
+
method,
|
|
29683
|
+
path: requestPath,
|
|
29684
|
+
...body !== void 0 ? { body } : {}
|
|
29685
|
+
})
|
|
29686
|
+
});
|
|
29687
|
+
}
|
|
29662
29688
|
async assignWorkspaceToGovernanceGroup(workspaceId, domain, mappingJson) {
|
|
29663
29689
|
let mapping;
|
|
29664
29690
|
try {
|
|
@@ -29752,14 +29778,6 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29752
29778
|
}
|
|
29753
29779
|
}
|
|
29754
29780
|
async connectWorkspaceToRepository(workspaceId, repoUrl) {
|
|
29755
|
-
const url = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
|
|
29756
|
-
const headers = {
|
|
29757
|
-
"Content-Type": "application/json",
|
|
29758
|
-
"x-access-token": this.accessToken
|
|
29759
|
-
};
|
|
29760
|
-
if (this.teamId) {
|
|
29761
|
-
headers["x-entity-team-id"] = this.teamId;
|
|
29762
|
-
}
|
|
29763
29781
|
const payload = {
|
|
29764
29782
|
service: "workspaces",
|
|
29765
29783
|
method: "POST",
|
|
@@ -29770,11 +29788,12 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29770
29788
|
versionControl: true
|
|
29771
29789
|
}
|
|
29772
29790
|
};
|
|
29773
|
-
const response = await this.
|
|
29774
|
-
|
|
29775
|
-
|
|
29776
|
-
|
|
29777
|
-
|
|
29791
|
+
const response = await this.proxyRequest(
|
|
29792
|
+
payload.service,
|
|
29793
|
+
payload.method,
|
|
29794
|
+
payload.path,
|
|
29795
|
+
payload.body
|
|
29796
|
+
);
|
|
29778
29797
|
if (response.ok) return;
|
|
29779
29798
|
if (response.status === 400) {
|
|
29780
29799
|
const body = await response.text();
|
|
@@ -29791,29 +29810,68 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29791
29810
|
}
|
|
29792
29811
|
throw await HttpError.fromResponse(response, {
|
|
29793
29812
|
method: "POST",
|
|
29794
|
-
requestHeaders:
|
|
29813
|
+
requestHeaders: {
|
|
29814
|
+
"Content-Type": "application/json",
|
|
29815
|
+
"x-access-token": this.accessToken,
|
|
29816
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29817
|
+
},
|
|
29795
29818
|
secretValues: [this.accessToken],
|
|
29796
|
-
url
|
|
29819
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29797
29820
|
});
|
|
29798
29821
|
}
|
|
29799
|
-
async
|
|
29800
|
-
|
|
29801
|
-
|
|
29802
|
-
"Content-Type": "application/json",
|
|
29803
|
-
"x-access-token": this.accessToken
|
|
29804
|
-
};
|
|
29805
|
-
if (this.teamId) {
|
|
29806
|
-
headers["x-entity-team-id"] = this.teamId;
|
|
29822
|
+
async linkCollectionsToSpecification(specificationId, collections) {
|
|
29823
|
+
if (collections.length === 0) {
|
|
29824
|
+
return;
|
|
29807
29825
|
}
|
|
29808
|
-
const response = await this.
|
|
29826
|
+
const response = await this.proxyRequest(
|
|
29827
|
+
"specification",
|
|
29828
|
+
"put",
|
|
29829
|
+
`/specifications/${specificationId}/collections`,
|
|
29830
|
+
collections.map((collection) => ({
|
|
29831
|
+
collectionId: collection.collectionId,
|
|
29832
|
+
...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
|
|
29833
|
+
}))
|
|
29834
|
+
);
|
|
29835
|
+
if (response.ok) {
|
|
29836
|
+
return;
|
|
29837
|
+
}
|
|
29838
|
+
throw await HttpError.fromResponse(response, {
|
|
29809
29839
|
method: "POST",
|
|
29810
|
-
|
|
29811
|
-
|
|
29812
|
-
|
|
29813
|
-
|
|
29814
|
-
|
|
29815
|
-
|
|
29840
|
+
requestHeaders: {
|
|
29841
|
+
"Content-Type": "application/json",
|
|
29842
|
+
"x-access-token": this.accessToken,
|
|
29843
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29844
|
+
},
|
|
29845
|
+
secretValues: [this.accessToken],
|
|
29846
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29847
|
+
});
|
|
29848
|
+
}
|
|
29849
|
+
async syncCollection(specificationId, collectionId) {
|
|
29850
|
+
const response = await this.proxyRequest(
|
|
29851
|
+
"specification",
|
|
29852
|
+
"post",
|
|
29853
|
+
`/specifications/${specificationId}/collections/${collectionId}/sync`
|
|
29854
|
+
);
|
|
29855
|
+
if (response.ok) {
|
|
29856
|
+
return;
|
|
29857
|
+
}
|
|
29858
|
+
throw await HttpError.fromResponse(response, {
|
|
29859
|
+
method: "POST",
|
|
29860
|
+
requestHeaders: {
|
|
29861
|
+
"Content-Type": "application/json",
|
|
29862
|
+
"x-access-token": this.accessToken,
|
|
29863
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29864
|
+
},
|
|
29865
|
+
secretValues: [this.accessToken],
|
|
29866
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29816
29867
|
});
|
|
29868
|
+
}
|
|
29869
|
+
async getWorkspaceGitRepoUrl(workspaceId) {
|
|
29870
|
+
const response = await this.proxyRequest(
|
|
29871
|
+
"workspaces",
|
|
29872
|
+
"GET",
|
|
29873
|
+
`/workspaces/${workspaceId}/filesystem`
|
|
29874
|
+
);
|
|
29817
29875
|
if (response.status === 404) return null;
|
|
29818
29876
|
if (!response.ok) return null;
|
|
29819
29877
|
const body = await response.text();
|
|
@@ -29996,6 +30054,13 @@ function requireInput(actionCore, name) {
|
|
|
29996
30054
|
function optionalInput(actionCore, name) {
|
|
29997
30055
|
return normalizeInputValue(actionCore.getInput(name));
|
|
29998
30056
|
}
|
|
30057
|
+
function parseBooleanInput(value, defaultValue) {
|
|
30058
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
30059
|
+
if (!normalized) return defaultValue;
|
|
30060
|
+
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
30061
|
+
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
30062
|
+
return defaultValue;
|
|
30063
|
+
}
|
|
29999
30064
|
function parseCollectionSyncMode(value) {
|
|
30000
30065
|
if (value === "reuse" || value === "version") {
|
|
30001
30066
|
return value;
|
|
@@ -30040,6 +30105,7 @@ function resolveInputs(env = process.env) {
|
|
|
30040
30105
|
baselineCollectionId: getInput("baseline-collection-id", env),
|
|
30041
30106
|
smokeCollectionId: getInput("smoke-collection-id", env),
|
|
30042
30107
|
contractCollectionId: getInput("contract-collection-id", env),
|
|
30108
|
+
syncExamples: parseBooleanInput(getInput("sync-examples", env), true),
|
|
30043
30109
|
collectionSyncMode: parseCollectionSyncMode(getInput("collection-sync-mode", env)),
|
|
30044
30110
|
specSyncMode: parseSpecSyncMode(getInput("spec-sync-mode", env)),
|
|
30045
30111
|
releaseLabel: getInput("release-label", env),
|
|
@@ -30099,6 +30165,7 @@ function readActionInputs(actionCore) {
|
|
|
30099
30165
|
INPUT_BASELINE_COLLECTION_ID: optionalInput(actionCore, "baseline-collection-id"),
|
|
30100
30166
|
INPUT_SMOKE_COLLECTION_ID: optionalInput(actionCore, "smoke-collection-id"),
|
|
30101
30167
|
INPUT_CONTRACT_COLLECTION_ID: optionalInput(actionCore, "contract-collection-id"),
|
|
30168
|
+
INPUT_SYNC_EXAMPLES: optionalInput(actionCore, "sync-examples") ?? openAlphaActionContract.inputs["sync-examples"].default,
|
|
30102
30169
|
INPUT_COLLECTION_SYNC_MODE: optionalInput(actionCore, "collection-sync-mode") ?? openAlphaActionContract.inputs["collection-sync-mode"].default,
|
|
30103
30170
|
INPUT_SPEC_SYNC_MODE: optionalInput(actionCore, "spec-sync-mode") ?? openAlphaActionContract.inputs["spec-sync-mode"].default,
|
|
30104
30171
|
INPUT_RELEASE_LABEL: optionalInput(actionCore, "release-label"),
|
|
@@ -30671,6 +30738,48 @@ For CLI usage, pass --workspace-team-id <id> or export POSTMAN_WORKSPACE_TEAM_ID
|
|
|
30671
30738
|
]);
|
|
30672
30739
|
}
|
|
30673
30740
|
);
|
|
30741
|
+
const linkedCollectionIds = [
|
|
30742
|
+
outputs["baseline-collection-id"],
|
|
30743
|
+
outputs["smoke-collection-id"],
|
|
30744
|
+
outputs["contract-collection-id"]
|
|
30745
|
+
].filter(Boolean);
|
|
30746
|
+
if (linkedCollectionIds.length > 0) {
|
|
30747
|
+
if (dependencies.internalIntegration) {
|
|
30748
|
+
await runGroup(
|
|
30749
|
+
dependencies.core,
|
|
30750
|
+
"Link Collections to Specification",
|
|
30751
|
+
async () => {
|
|
30752
|
+
await dependencies.internalIntegration?.linkCollectionsToSpecification(
|
|
30753
|
+
outputs["spec-id"],
|
|
30754
|
+
linkedCollectionIds.map((collectionId) => ({
|
|
30755
|
+
collectionId,
|
|
30756
|
+
syncOptions: {
|
|
30757
|
+
syncExamples: inputs.syncExamples
|
|
30758
|
+
}
|
|
30759
|
+
}))
|
|
30760
|
+
);
|
|
30761
|
+
}
|
|
30762
|
+
);
|
|
30763
|
+
await runGroup(
|
|
30764
|
+
dependencies.core,
|
|
30765
|
+
"Sync Linked Collections",
|
|
30766
|
+
async () => {
|
|
30767
|
+
await Promise.all(
|
|
30768
|
+
linkedCollectionIds.map(
|
|
30769
|
+
(collectionId) => dependencies.internalIntegration.syncCollection(
|
|
30770
|
+
outputs["spec-id"],
|
|
30771
|
+
collectionId
|
|
30772
|
+
)
|
|
30773
|
+
)
|
|
30774
|
+
);
|
|
30775
|
+
}
|
|
30776
|
+
);
|
|
30777
|
+
} else {
|
|
30778
|
+
dependencies.core.warning(
|
|
30779
|
+
"Skipping cloud spec-to-collection linking and sync because postman-access-token is not configured"
|
|
30780
|
+
);
|
|
30781
|
+
}
|
|
30782
|
+
}
|
|
30674
30783
|
for (const [name, value] of Object.entries(outputs)) {
|
|
30675
30784
|
dependencies.core.setOutput(name, value);
|
|
30676
30785
|
}
|
|
@@ -30858,6 +30967,7 @@ function parseCliArgs(argv, env = process.env) {
|
|
|
30858
30967
|
"baseline-collection-id",
|
|
30859
30968
|
"smoke-collection-id",
|
|
30860
30969
|
"contract-collection-id",
|
|
30970
|
+
"sync-examples",
|
|
30861
30971
|
"collection-sync-mode",
|
|
30862
30972
|
"spec-sync-mode",
|
|
30863
30973
|
"release-label",
|
package/dist/index.cjs
CHANGED
|
@@ -28724,6 +28724,12 @@ var openAlphaActionContract = {
|
|
|
28724
28724
|
description: "Existing contract collection ID.",
|
|
28725
28725
|
required: false
|
|
28726
28726
|
},
|
|
28727
|
+
"sync-examples": {
|
|
28728
|
+
description: "Whether linked spec/collection relations should enable example syncing.",
|
|
28729
|
+
required: false,
|
|
28730
|
+
default: "true",
|
|
28731
|
+
allowedValues: ["true", "false"]
|
|
28732
|
+
},
|
|
28727
28733
|
"collection-sync-mode": {
|
|
28728
28734
|
description: "Collection lifecycle policy: reuse existing collections, refresh them from the latest spec, or version them by release label.",
|
|
28729
28735
|
required: false,
|
|
@@ -29655,6 +29661,26 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29655
29661
|
options.workerBaseUrl || "https://catalog-admin.postman-account2009.workers.dev"
|
|
29656
29662
|
).replace(/\/+$/, "");
|
|
29657
29663
|
}
|
|
29664
|
+
async proxyRequest(service, method, requestPath, body) {
|
|
29665
|
+
const url = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
|
|
29666
|
+
const headers = {
|
|
29667
|
+
"Content-Type": "application/json",
|
|
29668
|
+
"x-access-token": this.accessToken
|
|
29669
|
+
};
|
|
29670
|
+
if (this.teamId) {
|
|
29671
|
+
headers["x-entity-team-id"] = this.teamId;
|
|
29672
|
+
}
|
|
29673
|
+
return this.fetchImpl(url, {
|
|
29674
|
+
method: "POST",
|
|
29675
|
+
headers,
|
|
29676
|
+
body: JSON.stringify({
|
|
29677
|
+
service,
|
|
29678
|
+
method,
|
|
29679
|
+
path: requestPath,
|
|
29680
|
+
...body !== void 0 ? { body } : {}
|
|
29681
|
+
})
|
|
29682
|
+
});
|
|
29683
|
+
}
|
|
29658
29684
|
async assignWorkspaceToGovernanceGroup(workspaceId, domain, mappingJson) {
|
|
29659
29685
|
let mapping;
|
|
29660
29686
|
try {
|
|
@@ -29748,14 +29774,6 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29748
29774
|
}
|
|
29749
29775
|
}
|
|
29750
29776
|
async connectWorkspaceToRepository(workspaceId, repoUrl) {
|
|
29751
|
-
const url = "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy";
|
|
29752
|
-
const headers = {
|
|
29753
|
-
"Content-Type": "application/json",
|
|
29754
|
-
"x-access-token": this.accessToken
|
|
29755
|
-
};
|
|
29756
|
-
if (this.teamId) {
|
|
29757
|
-
headers["x-entity-team-id"] = this.teamId;
|
|
29758
|
-
}
|
|
29759
29777
|
const payload = {
|
|
29760
29778
|
service: "workspaces",
|
|
29761
29779
|
method: "POST",
|
|
@@ -29766,11 +29784,12 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29766
29784
|
versionControl: true
|
|
29767
29785
|
}
|
|
29768
29786
|
};
|
|
29769
|
-
const response = await this.
|
|
29770
|
-
|
|
29771
|
-
|
|
29772
|
-
|
|
29773
|
-
|
|
29787
|
+
const response = await this.proxyRequest(
|
|
29788
|
+
payload.service,
|
|
29789
|
+
payload.method,
|
|
29790
|
+
payload.path,
|
|
29791
|
+
payload.body
|
|
29792
|
+
);
|
|
29774
29793
|
if (response.ok) return;
|
|
29775
29794
|
if (response.status === 400) {
|
|
29776
29795
|
const body = await response.text();
|
|
@@ -29787,29 +29806,68 @@ var BifrostInternalIntegrationAdapter = class {
|
|
|
29787
29806
|
}
|
|
29788
29807
|
throw await HttpError.fromResponse(response, {
|
|
29789
29808
|
method: "POST",
|
|
29790
|
-
requestHeaders:
|
|
29809
|
+
requestHeaders: {
|
|
29810
|
+
"Content-Type": "application/json",
|
|
29811
|
+
"x-access-token": this.accessToken,
|
|
29812
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29813
|
+
},
|
|
29791
29814
|
secretValues: [this.accessToken],
|
|
29792
|
-
url
|
|
29815
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29793
29816
|
});
|
|
29794
29817
|
}
|
|
29795
|
-
async
|
|
29796
|
-
|
|
29797
|
-
|
|
29798
|
-
"Content-Type": "application/json",
|
|
29799
|
-
"x-access-token": this.accessToken
|
|
29800
|
-
};
|
|
29801
|
-
if (this.teamId) {
|
|
29802
|
-
headers["x-entity-team-id"] = this.teamId;
|
|
29818
|
+
async linkCollectionsToSpecification(specificationId, collections) {
|
|
29819
|
+
if (collections.length === 0) {
|
|
29820
|
+
return;
|
|
29803
29821
|
}
|
|
29804
|
-
const response = await this.
|
|
29822
|
+
const response = await this.proxyRequest(
|
|
29823
|
+
"specification",
|
|
29824
|
+
"put",
|
|
29825
|
+
`/specifications/${specificationId}/collections`,
|
|
29826
|
+
collections.map((collection) => ({
|
|
29827
|
+
collectionId: collection.collectionId,
|
|
29828
|
+
...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
|
|
29829
|
+
}))
|
|
29830
|
+
);
|
|
29831
|
+
if (response.ok) {
|
|
29832
|
+
return;
|
|
29833
|
+
}
|
|
29834
|
+
throw await HttpError.fromResponse(response, {
|
|
29805
29835
|
method: "POST",
|
|
29806
|
-
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
29836
|
+
requestHeaders: {
|
|
29837
|
+
"Content-Type": "application/json",
|
|
29838
|
+
"x-access-token": this.accessToken,
|
|
29839
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29840
|
+
},
|
|
29841
|
+
secretValues: [this.accessToken],
|
|
29842
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29843
|
+
});
|
|
29844
|
+
}
|
|
29845
|
+
async syncCollection(specificationId, collectionId) {
|
|
29846
|
+
const response = await this.proxyRequest(
|
|
29847
|
+
"specification",
|
|
29848
|
+
"post",
|
|
29849
|
+
`/specifications/${specificationId}/collections/${collectionId}/sync`
|
|
29850
|
+
);
|
|
29851
|
+
if (response.ok) {
|
|
29852
|
+
return;
|
|
29853
|
+
}
|
|
29854
|
+
throw await HttpError.fromResponse(response, {
|
|
29855
|
+
method: "POST",
|
|
29856
|
+
requestHeaders: {
|
|
29857
|
+
"Content-Type": "application/json",
|
|
29858
|
+
"x-access-token": this.accessToken,
|
|
29859
|
+
...this.teamId ? { "x-entity-team-id": this.teamId } : {}
|
|
29860
|
+
},
|
|
29861
|
+
secretValues: [this.accessToken],
|
|
29862
|
+
url: "https://bifrost-premium-https-v4.gw.postman.com/ws/proxy"
|
|
29812
29863
|
});
|
|
29864
|
+
}
|
|
29865
|
+
async getWorkspaceGitRepoUrl(workspaceId) {
|
|
29866
|
+
const response = await this.proxyRequest(
|
|
29867
|
+
"workspaces",
|
|
29868
|
+
"GET",
|
|
29869
|
+
`/workspaces/${workspaceId}/filesystem`
|
|
29870
|
+
);
|
|
29813
29871
|
if (response.status === 404) return null;
|
|
29814
29872
|
if (!response.ok) return null;
|
|
29815
29873
|
const body = await response.text();
|
|
@@ -29992,6 +30050,13 @@ function requireInput(actionCore, name) {
|
|
|
29992
30050
|
function optionalInput(actionCore, name) {
|
|
29993
30051
|
return normalizeInputValue(actionCore.getInput(name));
|
|
29994
30052
|
}
|
|
30053
|
+
function parseBooleanInput(value, defaultValue) {
|
|
30054
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
30055
|
+
if (!normalized) return defaultValue;
|
|
30056
|
+
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
30057
|
+
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
30058
|
+
return defaultValue;
|
|
30059
|
+
}
|
|
29995
30060
|
function parseCollectionSyncMode(value) {
|
|
29996
30061
|
if (value === "reuse" || value === "version") {
|
|
29997
30062
|
return value;
|
|
@@ -30036,6 +30101,7 @@ function resolveInputs(env = process.env) {
|
|
|
30036
30101
|
baselineCollectionId: getInput("baseline-collection-id", env),
|
|
30037
30102
|
smokeCollectionId: getInput("smoke-collection-id", env),
|
|
30038
30103
|
contractCollectionId: getInput("contract-collection-id", env),
|
|
30104
|
+
syncExamples: parseBooleanInput(getInput("sync-examples", env), true),
|
|
30039
30105
|
collectionSyncMode: parseCollectionSyncMode(getInput("collection-sync-mode", env)),
|
|
30040
30106
|
specSyncMode: parseSpecSyncMode(getInput("spec-sync-mode", env)),
|
|
30041
30107
|
releaseLabel: getInput("release-label", env),
|
|
@@ -30095,6 +30161,7 @@ function readActionInputs(actionCore) {
|
|
|
30095
30161
|
INPUT_BASELINE_COLLECTION_ID: optionalInput(actionCore, "baseline-collection-id"),
|
|
30096
30162
|
INPUT_SMOKE_COLLECTION_ID: optionalInput(actionCore, "smoke-collection-id"),
|
|
30097
30163
|
INPUT_CONTRACT_COLLECTION_ID: optionalInput(actionCore, "contract-collection-id"),
|
|
30164
|
+
INPUT_SYNC_EXAMPLES: optionalInput(actionCore, "sync-examples") ?? openAlphaActionContract.inputs["sync-examples"].default,
|
|
30098
30165
|
INPUT_COLLECTION_SYNC_MODE: optionalInput(actionCore, "collection-sync-mode") ?? openAlphaActionContract.inputs["collection-sync-mode"].default,
|
|
30099
30166
|
INPUT_SPEC_SYNC_MODE: optionalInput(actionCore, "spec-sync-mode") ?? openAlphaActionContract.inputs["spec-sync-mode"].default,
|
|
30100
30167
|
INPUT_RELEASE_LABEL: optionalInput(actionCore, "release-label"),
|
|
@@ -30667,6 +30734,48 @@ For CLI usage, pass --workspace-team-id <id> or export POSTMAN_WORKSPACE_TEAM_ID
|
|
|
30667
30734
|
]);
|
|
30668
30735
|
}
|
|
30669
30736
|
);
|
|
30737
|
+
const linkedCollectionIds = [
|
|
30738
|
+
outputs["baseline-collection-id"],
|
|
30739
|
+
outputs["smoke-collection-id"],
|
|
30740
|
+
outputs["contract-collection-id"]
|
|
30741
|
+
].filter(Boolean);
|
|
30742
|
+
if (linkedCollectionIds.length > 0) {
|
|
30743
|
+
if (dependencies.internalIntegration) {
|
|
30744
|
+
await runGroup(
|
|
30745
|
+
dependencies.core,
|
|
30746
|
+
"Link Collections to Specification",
|
|
30747
|
+
async () => {
|
|
30748
|
+
await dependencies.internalIntegration?.linkCollectionsToSpecification(
|
|
30749
|
+
outputs["spec-id"],
|
|
30750
|
+
linkedCollectionIds.map((collectionId) => ({
|
|
30751
|
+
collectionId,
|
|
30752
|
+
syncOptions: {
|
|
30753
|
+
syncExamples: inputs.syncExamples
|
|
30754
|
+
}
|
|
30755
|
+
}))
|
|
30756
|
+
);
|
|
30757
|
+
}
|
|
30758
|
+
);
|
|
30759
|
+
await runGroup(
|
|
30760
|
+
dependencies.core,
|
|
30761
|
+
"Sync Linked Collections",
|
|
30762
|
+
async () => {
|
|
30763
|
+
await Promise.all(
|
|
30764
|
+
linkedCollectionIds.map(
|
|
30765
|
+
(collectionId) => dependencies.internalIntegration.syncCollection(
|
|
30766
|
+
outputs["spec-id"],
|
|
30767
|
+
collectionId
|
|
30768
|
+
)
|
|
30769
|
+
)
|
|
30770
|
+
);
|
|
30771
|
+
}
|
|
30772
|
+
);
|
|
30773
|
+
} else {
|
|
30774
|
+
dependencies.core.warning(
|
|
30775
|
+
"Skipping cloud spec-to-collection linking and sync because postman-access-token is not configured"
|
|
30776
|
+
);
|
|
30777
|
+
}
|
|
30778
|
+
}
|
|
30670
30779
|
for (const [name, value] of Object.entries(outputs)) {
|
|
30671
30780
|
dependencies.core.setOutput(name, value);
|
|
30672
30781
|
}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -191,6 +191,7 @@ export function parseCliArgs(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
191
191
|
'baseline-collection-id',
|
|
192
192
|
'smoke-collection-id',
|
|
193
193
|
'contract-collection-id',
|
|
194
|
+
'sync-examples',
|
|
194
195
|
'collection-sync-mode',
|
|
195
196
|
'spec-sync-mode',
|
|
196
197
|
'release-label',
|
package/src/contracts.ts
CHANGED
|
@@ -43,6 +43,12 @@ export const openAlphaActionContract: BetaActionContract = {
|
|
|
43
43
|
description: 'Existing contract collection ID.',
|
|
44
44
|
required: false
|
|
45
45
|
},
|
|
46
|
+
'sync-examples': {
|
|
47
|
+
description: 'Whether linked spec/collection relations should enable example syncing.',
|
|
48
|
+
required: false,
|
|
49
|
+
default: 'true',
|
|
50
|
+
allowedValues: ['true', 'false']
|
|
51
|
+
},
|
|
46
52
|
'collection-sync-mode': {
|
|
47
53
|
description:
|
|
48
54
|
'Collection lifecycle policy: reuse existing collections, refresh them from the latest spec, or version them by release label.',
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface ResolvedInputs {
|
|
|
19
19
|
baselineCollectionId?: string;
|
|
20
20
|
smokeCollectionId?: string;
|
|
21
21
|
contractCollectionId?: string;
|
|
22
|
+
syncExamples: boolean;
|
|
22
23
|
collectionSyncMode: 'reuse' | 'refresh' | 'version';
|
|
23
24
|
specSyncMode: 'update' | 'version';
|
|
24
25
|
releaseLabel?: string;
|
|
@@ -101,7 +102,7 @@ export interface BootstrapExecutionDependencies {
|
|
|
101
102
|
io: IOLike;
|
|
102
103
|
internalIntegration?: Pick<
|
|
103
104
|
InternalIntegrationAdapter,
|
|
104
|
-
'assignWorkspaceToGovernanceGroup'
|
|
105
|
+
'assignWorkspaceToGovernanceGroup' | 'linkCollectionsToSpecification' | 'syncCollection'
|
|
105
106
|
>;
|
|
106
107
|
postman: Pick<
|
|
107
108
|
PostmanAssetsClient,
|
|
@@ -250,6 +251,7 @@ export function resolveInputs(
|
|
|
250
251
|
baselineCollectionId: getInput('baseline-collection-id', env),
|
|
251
252
|
smokeCollectionId: getInput('smoke-collection-id', env),
|
|
252
253
|
contractCollectionId: getInput('contract-collection-id', env),
|
|
254
|
+
syncExamples: parseBooleanInput(getInput('sync-examples', env), true),
|
|
253
255
|
collectionSyncMode: parseCollectionSyncMode(getInput('collection-sync-mode', env)),
|
|
254
256
|
specSyncMode: parseSpecSyncMode(getInput('spec-sync-mode', env)),
|
|
255
257
|
releaseLabel: getInput('release-label', env),
|
|
@@ -322,6 +324,9 @@ export function readActionInputs(
|
|
|
322
324
|
INPUT_BASELINE_COLLECTION_ID: optionalInput(actionCore, 'baseline-collection-id'),
|
|
323
325
|
INPUT_SMOKE_COLLECTION_ID: optionalInput(actionCore, 'smoke-collection-id'),
|
|
324
326
|
INPUT_CONTRACT_COLLECTION_ID: optionalInput(actionCore, 'contract-collection-id'),
|
|
327
|
+
INPUT_SYNC_EXAMPLES:
|
|
328
|
+
optionalInput(actionCore, 'sync-examples') ??
|
|
329
|
+
openAlphaActionContract.inputs['sync-examples'].default,
|
|
325
330
|
INPUT_COLLECTION_SYNC_MODE:
|
|
326
331
|
optionalInput(actionCore, 'collection-sync-mode') ??
|
|
327
332
|
openAlphaActionContract.inputs['collection-sync-mode'].default,
|
|
@@ -1025,6 +1030,51 @@ export async function runBootstrap(
|
|
|
1025
1030
|
}
|
|
1026
1031
|
);
|
|
1027
1032
|
|
|
1033
|
+
const linkedCollectionIds = [
|
|
1034
|
+
outputs['baseline-collection-id'],
|
|
1035
|
+
outputs['smoke-collection-id'],
|
|
1036
|
+
outputs['contract-collection-id']
|
|
1037
|
+
].filter(Boolean);
|
|
1038
|
+
|
|
1039
|
+
if (linkedCollectionIds.length > 0) {
|
|
1040
|
+
if (dependencies.internalIntegration) {
|
|
1041
|
+
await runGroup(
|
|
1042
|
+
dependencies.core,
|
|
1043
|
+
'Link Collections to Specification',
|
|
1044
|
+
async () => {
|
|
1045
|
+
await dependencies.internalIntegration?.linkCollectionsToSpecification(
|
|
1046
|
+
outputs['spec-id'],
|
|
1047
|
+
linkedCollectionIds.map((collectionId) => ({
|
|
1048
|
+
collectionId,
|
|
1049
|
+
syncOptions: {
|
|
1050
|
+
syncExamples: inputs.syncExamples
|
|
1051
|
+
}
|
|
1052
|
+
}))
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
await runGroup(
|
|
1058
|
+
dependencies.core,
|
|
1059
|
+
'Sync Linked Collections',
|
|
1060
|
+
async () => {
|
|
1061
|
+
await Promise.all(
|
|
1062
|
+
linkedCollectionIds.map((collectionId) =>
|
|
1063
|
+
dependencies.internalIntegration!.syncCollection(
|
|
1064
|
+
outputs['spec-id'],
|
|
1065
|
+
collectionId
|
|
1066
|
+
)
|
|
1067
|
+
)
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
} else {
|
|
1072
|
+
dependencies.core.warning(
|
|
1073
|
+
'Skipping cloud spec-to-collection linking and sync because postman-access-token is not configured'
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1028
1078
|
for (const [name, value] of Object.entries(outputs)) {
|
|
1029
1079
|
dependencies.core.setOutput(name, value);
|
|
1030
1080
|
}
|
|
@@ -9,6 +9,13 @@ export interface GovernanceAssociation {
|
|
|
9
9
|
systemEnvId: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface SpecificationCollectionLink {
|
|
13
|
+
collectionId: string;
|
|
14
|
+
syncOptions?: {
|
|
15
|
+
syncExamples: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export interface InternalIntegrationAdapterOptions {
|
|
13
20
|
accessToken: string;
|
|
14
21
|
backend: string;
|
|
@@ -32,6 +39,14 @@ export interface InternalIntegrationAdapter {
|
|
|
32
39
|
workspaceId: string,
|
|
33
40
|
repoUrl: string
|
|
34
41
|
): Promise<void>;
|
|
42
|
+
linkCollectionsToSpecification(
|
|
43
|
+
specificationId: string,
|
|
44
|
+
collections: SpecificationCollectionLink[]
|
|
45
|
+
): Promise<void>;
|
|
46
|
+
syncCollection(
|
|
47
|
+
specificationId: string,
|
|
48
|
+
collectionId: string
|
|
49
|
+
): Promise<void>;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
class BifrostInternalIntegrationAdapter implements InternalIntegrationAdapter {
|
|
@@ -53,6 +68,33 @@ class BifrostInternalIntegrationAdapter implements InternalIntegrationAdapter {
|
|
|
53
68
|
).replace(/\/+$/, '');
|
|
54
69
|
}
|
|
55
70
|
|
|
71
|
+
private async proxyRequest(
|
|
72
|
+
service: string,
|
|
73
|
+
method: string,
|
|
74
|
+
requestPath: string,
|
|
75
|
+
body?: unknown
|
|
76
|
+
): Promise<Response> {
|
|
77
|
+
const url = 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy';
|
|
78
|
+
const headers: Record<string, string> = {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'x-access-token': this.accessToken
|
|
81
|
+
};
|
|
82
|
+
if (this.teamId) {
|
|
83
|
+
headers['x-entity-team-id'] = this.teamId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.fetchImpl(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
service,
|
|
91
|
+
method,
|
|
92
|
+
path: requestPath,
|
|
93
|
+
...(body !== undefined ? { body } : {})
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
56
98
|
async assignWorkspaceToGovernanceGroup(
|
|
57
99
|
workspaceId: string,
|
|
58
100
|
domain: string,
|
|
@@ -168,14 +210,6 @@ class BifrostInternalIntegrationAdapter implements InternalIntegrationAdapter {
|
|
|
168
210
|
workspaceId: string,
|
|
169
211
|
repoUrl: string
|
|
170
212
|
): Promise<void> {
|
|
171
|
-
const url = 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy';
|
|
172
|
-
const headers: Record<string, string> = {
|
|
173
|
-
'Content-Type': 'application/json',
|
|
174
|
-
'x-access-token': this.accessToken
|
|
175
|
-
};
|
|
176
|
-
if (this.teamId) {
|
|
177
|
-
headers['x-entity-team-id'] = this.teamId;
|
|
178
|
-
}
|
|
179
213
|
const payload = {
|
|
180
214
|
service: 'workspaces',
|
|
181
215
|
method: 'POST',
|
|
@@ -187,11 +221,12 @@ class BifrostInternalIntegrationAdapter implements InternalIntegrationAdapter {
|
|
|
187
221
|
}
|
|
188
222
|
};
|
|
189
223
|
|
|
190
|
-
const response = await this.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
224
|
+
const response = await this.proxyRequest(
|
|
225
|
+
payload.service,
|
|
226
|
+
payload.method,
|
|
227
|
+
payload.path,
|
|
228
|
+
payload.body
|
|
229
|
+
);
|
|
195
230
|
|
|
196
231
|
if (response.ok) return;
|
|
197
232
|
|
|
@@ -215,31 +250,82 @@ class BifrostInternalIntegrationAdapter implements InternalIntegrationAdapter {
|
|
|
215
250
|
|
|
216
251
|
throw await HttpError.fromResponse(response, {
|
|
217
252
|
method: 'POST',
|
|
218
|
-
requestHeaders:
|
|
253
|
+
requestHeaders: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
'x-access-token': this.accessToken,
|
|
256
|
+
...(this.teamId ? { 'x-entity-team-id': this.teamId } : {})
|
|
257
|
+
},
|
|
219
258
|
secretValues: [this.accessToken],
|
|
220
|
-
url
|
|
259
|
+
url: 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy'
|
|
221
260
|
});
|
|
222
261
|
}
|
|
223
262
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (this.teamId) {
|
|
231
|
-
headers['x-entity-team-id'] = this.teamId;
|
|
263
|
+
async linkCollectionsToSpecification(
|
|
264
|
+
specificationId: string,
|
|
265
|
+
collections: SpecificationCollectionLink[]
|
|
266
|
+
): Promise<void> {
|
|
267
|
+
if (collections.length === 0) {
|
|
268
|
+
return;
|
|
232
269
|
}
|
|
233
270
|
|
|
234
|
-
const response = await this.
|
|
271
|
+
const response = await this.proxyRequest(
|
|
272
|
+
'specification',
|
|
273
|
+
'put',
|
|
274
|
+
`/specifications/${specificationId}/collections`,
|
|
275
|
+
collections.map((collection) => ({
|
|
276
|
+
collectionId: collection.collectionId,
|
|
277
|
+
...(collection.syncOptions ? { syncOptions: collection.syncOptions } : {})
|
|
278
|
+
}))
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (response.ok) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
throw await HttpError.fromResponse(response, {
|
|
235
286
|
method: 'POST',
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
287
|
+
requestHeaders: {
|
|
288
|
+
'Content-Type': 'application/json',
|
|
289
|
+
'x-access-token': this.accessToken,
|
|
290
|
+
...(this.teamId ? { 'x-entity-team-id': this.teamId } : {})
|
|
291
|
+
},
|
|
292
|
+
secretValues: [this.accessToken],
|
|
293
|
+
url: 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy'
|
|
242
294
|
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async syncCollection(
|
|
298
|
+
specificationId: string,
|
|
299
|
+
collectionId: string
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
const response = await this.proxyRequest(
|
|
302
|
+
'specification',
|
|
303
|
+
'post',
|
|
304
|
+
`/specifications/${specificationId}/collections/${collectionId}/sync`
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (response.ok) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
throw await HttpError.fromResponse(response, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
requestHeaders: {
|
|
314
|
+
'Content-Type': 'application/json',
|
|
315
|
+
'x-access-token': this.accessToken,
|
|
316
|
+
...(this.teamId ? { 'x-entity-team-id': this.teamId } : {})
|
|
317
|
+
},
|
|
318
|
+
secretValues: [this.accessToken],
|
|
319
|
+
url: 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy'
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private async getWorkspaceGitRepoUrl(workspaceId: string): Promise<string | null> {
|
|
324
|
+
const response = await this.proxyRequest(
|
|
325
|
+
'workspaces',
|
|
326
|
+
'GET',
|
|
327
|
+
`/workspaces/${workspaceId}/filesystem`
|
|
328
|
+
);
|
|
243
329
|
|
|
244
330
|
if (response.status === 404) return null;
|
|
245
331
|
if (!response.ok) return null;
|
|
@@ -60,6 +60,7 @@ function createCoreStub(values: Record<string, string> = {}) {
|
|
|
60
60
|
function createInputs(overrides: Partial<ResolvedInputs> = {}): ResolvedInputs {
|
|
61
61
|
return {
|
|
62
62
|
projectName: 'core-payments',
|
|
63
|
+
syncExamples: true,
|
|
63
64
|
collectionSyncMode: 'refresh',
|
|
64
65
|
specSyncMode: 'update',
|
|
65
66
|
releaseLabel: undefined,
|
|
@@ -148,7 +149,9 @@ describe('bootstrap action', () => {
|
|
|
148
149
|
getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
|
|
149
150
|
};
|
|
150
151
|
const internalIntegration = {
|
|
151
|
-
assignWorkspaceToGovernanceGroup: vi.fn().mockResolvedValue(undefined)
|
|
152
|
+
assignWorkspaceToGovernanceGroup: vi.fn().mockResolvedValue(undefined),
|
|
153
|
+
linkCollectionsToSpecification: vi.fn().mockResolvedValue(undefined),
|
|
154
|
+
syncCollection: vi.fn().mockResolvedValue(undefined)
|
|
152
155
|
};
|
|
153
156
|
const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
|
154
157
|
new Response('openapi: 3.1.0', {
|
|
@@ -177,6 +180,29 @@ describe('bootstrap action', () => {
|
|
|
177
180
|
);
|
|
178
181
|
expect(postman.addAdminsToWorkspace).toHaveBeenCalledWith('ws-123', '101,102');
|
|
179
182
|
expect(order).toEqual(['[Baseline]', '[Smoke]', '[Contract]']);
|
|
183
|
+
expect(internalIntegration.linkCollectionsToSpecification).toHaveBeenCalledWith(
|
|
184
|
+
'spec-123',
|
|
185
|
+
[
|
|
186
|
+
{ collectionId: 'col-baseline', syncOptions: { syncExamples: true } },
|
|
187
|
+
{ collectionId: 'col-smoke', syncOptions: { syncExamples: true } },
|
|
188
|
+
{ collectionId: 'col-contract', syncOptions: { syncExamples: true } }
|
|
189
|
+
]
|
|
190
|
+
);
|
|
191
|
+
expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
|
|
192
|
+
1,
|
|
193
|
+
'spec-123',
|
|
194
|
+
'col-baseline'
|
|
195
|
+
);
|
|
196
|
+
expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
|
|
197
|
+
2,
|
|
198
|
+
'spec-123',
|
|
199
|
+
'col-smoke'
|
|
200
|
+
);
|
|
201
|
+
expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
|
|
202
|
+
3,
|
|
203
|
+
'spec-123',
|
|
204
|
+
'col-contract'
|
|
205
|
+
);
|
|
180
206
|
expect(result).toMatchObject({
|
|
181
207
|
'workspace-id': 'ws-123',
|
|
182
208
|
'workspace-name': '[AF] core-payments',
|
|
@@ -730,7 +756,7 @@ describe('bootstrap action', () => {
|
|
|
730
756
|
});
|
|
731
757
|
|
|
732
758
|
it('skips governance assignment when postman-access-token is absent', async () => {
|
|
733
|
-
const { core } = createCoreStub();
|
|
759
|
+
const { core, warnings } = createCoreStub();
|
|
734
760
|
const execStub = createExecStub();
|
|
735
761
|
const ioStub = createIoStub();
|
|
736
762
|
const postman = {
|
|
@@ -764,6 +790,64 @@ describe('bootstrap action', () => {
|
|
|
764
790
|
|
|
765
791
|
expect(result['workspace-id']).toBe('ws-123');
|
|
766
792
|
expect(postman.createWorkspace).toHaveBeenCalled();
|
|
793
|
+
expect(
|
|
794
|
+
warnings.some((warning) =>
|
|
795
|
+
warning.includes('Skipping cloud spec-to-collection linking and sync because postman-access-token is not configured')
|
|
796
|
+
)
|
|
797
|
+
).toBe(true);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it('passes syncExamples=false when configured', async () => {
|
|
801
|
+
const { core } = createCoreStub();
|
|
802
|
+
const execStub = createExecStub();
|
|
803
|
+
const ioStub = createIoStub();
|
|
804
|
+
const postman = {
|
|
805
|
+
addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
|
|
806
|
+
createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
|
|
807
|
+
findWorkspacesByName: vi.fn().mockResolvedValue([]),
|
|
808
|
+
generateCollection: vi
|
|
809
|
+
.fn()
|
|
810
|
+
.mockResolvedValueOnce('col-baseline')
|
|
811
|
+
.mockResolvedValueOnce('col-smoke')
|
|
812
|
+
.mockResolvedValueOnce('col-contract'),
|
|
813
|
+
getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
|
|
814
|
+
getTeams: vi.fn().mockResolvedValue([]),
|
|
815
|
+
getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
|
|
816
|
+
injectTests: vi.fn().mockResolvedValue(undefined),
|
|
817
|
+
inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
|
|
818
|
+
tagCollection: vi.fn().mockResolvedValue(undefined),
|
|
819
|
+
uploadSpec: vi.fn().mockResolvedValue('spec-123'),
|
|
820
|
+
updateSpec: vi.fn().mockResolvedValue(undefined),
|
|
821
|
+
getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
|
|
822
|
+
};
|
|
823
|
+
const internalIntegration = {
|
|
824
|
+
assignWorkspaceToGovernanceGroup: vi.fn().mockResolvedValue(undefined),
|
|
825
|
+
linkCollectionsToSpecification: vi.fn().mockResolvedValue(undefined),
|
|
826
|
+
syncCollection: vi.fn().mockResolvedValue(undefined)
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
await runBootstrap(
|
|
830
|
+
createInputs({ syncExamples: false }),
|
|
831
|
+
{
|
|
832
|
+
core,
|
|
833
|
+
exec: execStub,
|
|
834
|
+
io: ioStub,
|
|
835
|
+
internalIntegration,
|
|
836
|
+
postman,
|
|
837
|
+
specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
|
|
838
|
+
new Response('openapi: 3.1.0', { status: 200 })
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
expect(internalIntegration.linkCollectionsToSpecification).toHaveBeenCalledWith(
|
|
844
|
+
'spec-123',
|
|
845
|
+
[
|
|
846
|
+
{ collectionId: 'col-baseline', syncOptions: { syncExamples: false } },
|
|
847
|
+
{ collectionId: 'col-smoke', syncOptions: { syncExamples: false } },
|
|
848
|
+
{ collectionId: 'col-contract', syncOptions: { syncExamples: false } }
|
|
849
|
+
]
|
|
850
|
+
);
|
|
767
851
|
});
|
|
768
852
|
|
|
769
853
|
it('runs without any GitHub dependency', async () => {
|
package/tests/cli.test.ts
CHANGED
|
@@ -14,6 +14,8 @@ describe('parseCliArgs', () => {
|
|
|
14
14
|
'pmak-test',
|
|
15
15
|
'--workspace-admin-user-ids',
|
|
16
16
|
'101,102',
|
|
17
|
+
'--sync-examples',
|
|
18
|
+
'false',
|
|
17
19
|
'--collection-sync-mode',
|
|
18
20
|
'version',
|
|
19
21
|
'--spec-sync-mode',
|
|
@@ -36,6 +38,7 @@ describe('parseCliArgs', () => {
|
|
|
36
38
|
expect(config.inputEnv.INPUT_SPEC_URL).toBe('https://example.test/openapi.yaml');
|
|
37
39
|
expect(config.inputEnv.INPUT_POSTMAN_API_KEY).toBe('pmak-test');
|
|
38
40
|
expect(config.inputEnv.INPUT_WORKSPACE_ADMIN_USER_IDS).toBe('101,102');
|
|
41
|
+
expect(config.inputEnv.INPUT_SYNC_EXAMPLES).toBe('false');
|
|
39
42
|
expect(config.inputEnv.INPUT_COLLECTION_SYNC_MODE).toBe('version');
|
|
40
43
|
expect(config.inputEnv.INPUT_SPEC_SYNC_MODE).toBe('version');
|
|
41
44
|
expect(config.inputEnv.INPUT_RELEASE_LABEL).toBe('v1.2.3');
|
package/tests/contract.test.ts
CHANGED
|
@@ -42,6 +42,14 @@ describe('open-alpha action contract', () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('defaults lifecycle controls in contract, manifest, and runtime', () => {
|
|
45
|
+
expect(openAlphaActionContract.inputs['sync-examples'].default).toBe('true');
|
|
46
|
+
expect(openAlphaActionContract.inputs['sync-examples'].allowedValues).toEqual([
|
|
47
|
+
'true',
|
|
48
|
+
'false'
|
|
49
|
+
]);
|
|
50
|
+
expect(actionManifest.inputs['sync-examples'].default).toBe('true');
|
|
51
|
+
expect(resolveInputs({}).syncExamples).toBe(true);
|
|
52
|
+
|
|
45
53
|
expect(openAlphaActionContract.inputs['collection-sync-mode'].default).toBe('refresh');
|
|
46
54
|
expect(openAlphaActionContract.inputs['collection-sync-mode'].allowedValues).toEqual([
|
|
47
55
|
'reuse',
|
|
@@ -184,6 +184,75 @@ describe('internal integration adapter', () => {
|
|
|
184
184
|
);
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
it('routes specification collection linking through the Bifrost proxy with sync options', async () => {
|
|
188
|
+
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
|
|
189
|
+
jsonResponse({ data: { updated: 1 } })
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const adapter = createInternalIntegrationAdapter({
|
|
193
|
+
backend: 'bifrost',
|
|
194
|
+
accessToken: 'token-123',
|
|
195
|
+
teamId: '11430732',
|
|
196
|
+
fetchImpl
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await adapter.linkCollectionsToSpecification('spec-123', [
|
|
200
|
+
{
|
|
201
|
+
collectionId: 'col-1',
|
|
202
|
+
syncOptions: { syncExamples: true }
|
|
203
|
+
}
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
expect(fetchImpl).toHaveBeenCalledWith(
|
|
207
|
+
'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy',
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: expect.objectContaining({
|
|
211
|
+
'x-access-token': 'token-123',
|
|
212
|
+
'x-entity-team-id': '11430732'
|
|
213
|
+
}),
|
|
214
|
+
body: JSON.stringify({
|
|
215
|
+
service: 'specification',
|
|
216
|
+
method: 'put',
|
|
217
|
+
path: '/specifications/spec-123/collections',
|
|
218
|
+
body: [
|
|
219
|
+
{
|
|
220
|
+
collectionId: 'col-1',
|
|
221
|
+
syncOptions: { syncExamples: true }
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('routes specification collection sync through the Bifrost proxy', async () => {
|
|
230
|
+
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
|
|
231
|
+
jsonResponse({ data: { taskId: 'task-1' } })
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const adapter = createInternalIntegrationAdapter({
|
|
235
|
+
backend: 'bifrost',
|
|
236
|
+
accessToken: 'token-123',
|
|
237
|
+
teamId: '11430732',
|
|
238
|
+
fetchImpl
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await adapter.syncCollection('spec-123', 'col-1');
|
|
242
|
+
|
|
243
|
+
expect(fetchImpl).toHaveBeenCalledWith(
|
|
244
|
+
'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy',
|
|
245
|
+
expect.objectContaining({
|
|
246
|
+
method: 'POST',
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
service: 'specification',
|
|
249
|
+
method: 'post',
|
|
250
|
+
path: '/specifications/spec-123/collections/col-1/sync'
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
187
256
|
it('treats projectAlreadyConnected as idempotent when the same repo is linked', async () => {
|
|
188
257
|
const fetchImpl = vi.fn<typeof fetch>()
|
|
189
258
|
// First call: connectWorkspaceToRepository POST returns 400 projectAlreadyConnected
|