@postman-cse/onboarding-bootstrap 0.9.1 → 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/.github/workflows/contract-smoke.yml +155 -0
- package/.omc/sessions/b36ac16d-ce26-4e94-ae7e-dd817f2430a3.json +8 -0
- package/CLAUDE.md +57 -0
- package/README.md +133 -12
- package/action.yml +21 -18
- package/dist/cli.cjs +370 -518
- package/dist/index.cjs +364 -509
- package/package.json +3 -2
- package/src/cli.ts +6 -11
- package/src/contracts.ts +30 -24
- package/src/index.ts +302 -322
- package/src/lib/postman/internal-integration-adapter.ts +116 -30
- package/src/lib/postman/postman-assets-client.ts +27 -4
- package/tests/bootstrap-action.test.ts +647 -77
- package/tests/cli.test.ts +15 -6
- package/tests/contract.test.ts +28 -3
- package/tests/internal-integration-adapter.test.ts +69 -0
- package/tests/postman-assets-client.test.ts +153 -0
|
@@ -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;
|
|
@@ -110,6 +110,21 @@ export class PostmanAssetsClient {
|
|
|
110
110
|
return undefined;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
async getTeams(): Promise<Array<{ id: number; name: string; handle: string; organizationId?: number }>> {
|
|
114
|
+
const data = await this.request('/teams');
|
|
115
|
+
const teams = data?.data ?? [];
|
|
116
|
+
return Array.isArray(teams)
|
|
117
|
+
? teams
|
|
118
|
+
.filter((t: any) => t?.id && t?.name)
|
|
119
|
+
.map((t: any) => ({
|
|
120
|
+
id: Number(t.id),
|
|
121
|
+
name: String(t.name),
|
|
122
|
+
handle: String(t.handle || ''),
|
|
123
|
+
...(t.organizationId != null ? { organizationId: Number(t.organizationId) } : {})
|
|
124
|
+
}))
|
|
125
|
+
: [];
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
private async request(
|
|
114
129
|
path: string,
|
|
115
130
|
init: RequestInit = {}
|
|
@@ -144,13 +159,14 @@ export class PostmanAssetsClient {
|
|
|
144
159
|
}
|
|
145
160
|
}
|
|
146
161
|
|
|
147
|
-
async createWorkspace(name: string, about: string): Promise<{ id: string }> {
|
|
162
|
+
async createWorkspace(name: string, about: string, targetTeamId?: number): Promise<{ id: string }> {
|
|
148
163
|
return retry(async () => {
|
|
149
164
|
const payload = {
|
|
150
165
|
workspace: {
|
|
151
166
|
about,
|
|
152
167
|
name,
|
|
153
|
-
type: 'team'
|
|
168
|
+
type: 'team',
|
|
169
|
+
...(targetTeamId != null && !Number.isNaN(targetTeamId) ? { teamId: targetTeamId } : {})
|
|
154
170
|
}
|
|
155
171
|
};
|
|
156
172
|
let created: FetchResult;
|
|
@@ -162,7 +178,9 @@ export class PostmanAssetsClient {
|
|
|
162
178
|
} catch (err) {
|
|
163
179
|
if (err instanceof Error && err.message.includes('Only personal workspaces')) {
|
|
164
180
|
throw new Error(
|
|
165
|
-
'Workspace creation failed:
|
|
181
|
+
'Workspace creation failed: This may be an Org-mode account that requires a workspace-team-id input. ' +
|
|
182
|
+
'The Postman API does not allow creating team workspaces at the organization level. ' +
|
|
183
|
+
'Use the workspace-team-id input to specify which sub-team should own this workspace.'
|
|
166
184
|
);
|
|
167
185
|
}
|
|
168
186
|
throw err;
|
|
@@ -184,7 +202,12 @@ export class PostmanAssetsClient {
|
|
|
184
202
|
return {
|
|
185
203
|
id: workspaceId
|
|
186
204
|
};
|
|
187
|
-
}, {
|
|
205
|
+
}, {
|
|
206
|
+
maxAttempts: 3,
|
|
207
|
+
delayMs: 2000,
|
|
208
|
+
shouldRetry: (err) =>
|
|
209
|
+
!(err instanceof Error && err.message.includes('workspace-team-id'))
|
|
210
|
+
});
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
async listWorkspaces(): Promise<Array<{ id: string; name: string; type: string }>> {
|