Skip to content

Commit 41daafd

Browse files
authored
fix: recreate Postman collections on publish instead of PUT update
2 parents 6de1314 + 06f1f7f commit 41daafd

2 files changed

Lines changed: 84 additions & 50 deletions

File tree

scripts/postman-publish-lib.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ export function writeMapping(mapping: PublishMapping): void {
7171
export function listCollectionFiles(): string[] {
7272
return fs
7373
.readdirSync(postmanDir)
74-
.filter((filename) => filename.endsWith('.json') && !filename.startsWith('.'))
74+
.filter(
75+
(filename) => filename.endsWith('.json') && !filename.startsWith('.'),
76+
)
7577
.sort();
7678
}
7779

@@ -81,6 +83,20 @@ export function loadLocalCollection(filename: string): PostmanCollection {
8183
) as PostmanCollection;
8284
}
8385

86+
export async function deleteCollection(
87+
apiKey: string,
88+
uid: string,
89+
): Promise<void> {
90+
const response = await postmanFetch(apiKey, `/collections/${uid}`, {
91+
method: 'DELETE',
92+
});
93+
const body = await readJson(response);
94+
if (response.status === 404) {
95+
return;
96+
}
97+
assertOk(response, body, `DELETE /collections/${uid}`);
98+
}
99+
84100
export function collectionKeyFromFilename(filename: string): string {
85101
return path.basename(filename, '.json');
86102
}
@@ -103,11 +119,17 @@ export async function readJson(response: Response): Promise<unknown> {
103119
try {
104120
return text ? JSON.parse(text) : {};
105121
} catch {
106-
throw new Error(`Invalid JSON from Postman API (${response.status}): ${text.slice(0, 500)}`);
122+
throw new Error(
123+
`Invalid JSON from Postman API (${response.status}): ${text.slice(0, 500)}`,
124+
);
107125
}
108126
}
109127

110-
export function assertOk(response: Response, body: unknown, context: string): void {
128+
export function assertOk(
129+
response: Response,
130+
body: unknown,
131+
context: string,
132+
): void {
111133
if (response.ok) {
112134
return;
113135
}
@@ -120,7 +142,9 @@ export function workspaceOverviewUrl(teamDomain: string, slug: string): string {
120142
return `https://www.postman.com/${teamDomain}/${slug}/overview`;
121143
}
122144

123-
export function countRequests(items: PostmanCollectionItem[] | undefined): number {
145+
export function countRequests(
146+
items: PostmanCollectionItem[] | undefined,
147+
): number {
124148
if (!items) {
125149
return 0;
126150
}
@@ -136,7 +160,9 @@ export function countRequests(items: PostmanCollectionItem[] | undefined): numbe
136160
return count;
137161
}
138162

139-
export function collectRequestPaths(items: PostmanCollectionItem[] | undefined): string[] {
163+
export function collectRequestPaths(
164+
items: PostmanCollectionItem[] | undefined,
165+
): string[] {
140166
if (!items) {
141167
return [];
142168
}
@@ -185,7 +211,10 @@ export function fingerprintCollection(collection: PostmanCollection): string {
185211
},
186212
paths: collectRequestPaths(collection.item),
187213
};
188-
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
214+
return crypto
215+
.createHash('sha256')
216+
.update(JSON.stringify(payload))
217+
.digest('hex');
189218
}
190219

191220
export function collectBaseUrls(collection: PostmanCollection): string[] {

scripts/publish-to-postman.ts

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
WORKSPACE_SLUG,
99
assertOk,
1010
collectionKeyFromFilename,
11+
deleteCollection,
1112
listCollectionFiles,
1213
loadLocalCollection,
1314
postmanDir,
@@ -59,7 +60,10 @@ async function getTeamDomain(apiKey: string): Promise<string> {
5960
return teamDomain;
6061
}
6162

62-
async function createWorkspace(apiKey: string, teamDomain: string): Promise<WorkspaceResponse['workspace']> {
63+
async function createWorkspace(
64+
apiKey: string,
65+
teamDomain: string,
66+
): Promise<WorkspaceResponse['workspace']> {
6367
const createPublic = async () => {
6468
const response = await postmanFetch(apiKey, '/workspaces', {
6569
method: 'POST',
@@ -103,7 +107,8 @@ async function createWorkspace(apiKey: string, teamDomain: string): Promise<Work
103107
}
104108

105109
const workspace = body.workspace;
106-
const workspaceType = (workspace.type as 'public' | 'team' | 'personal') ?? 'team';
110+
const workspaceType =
111+
(workspace.type as 'public' | 'team' | 'personal') ?? 'team';
107112
return {
108113
...workspace,
109114
slug: workspace.slug ?? WORKSPACE_SLUG,
@@ -117,7 +122,10 @@ async function ensureWorkspace(
117122
): Promise<NonNullable<ReturnType<typeof readMapping>['workspace']>> {
118123
const mapping = readMapping();
119124
if (mapping.workspace?.id) {
120-
const response = await postmanFetch(apiKey, `/workspaces/${mapping.workspace.id}`);
125+
const response = await postmanFetch(
126+
apiKey,
127+
`/workspaces/${mapping.workspace.id}`,
128+
);
121129
const body = (await readJson(response)) as WorkspaceResponse;
122130
if (response.ok) {
123131
return mapping.workspace;
@@ -134,7 +142,10 @@ async function ensureWorkspace(
134142
slug: workspace.slug ?? WORKSPACE_SLUG,
135143
teamDomain,
136144
type: (workspace.type as 'public' | 'team' | 'personal') ?? 'team',
137-
overviewUrl: workspaceOverviewUrl(teamDomain, workspace.slug ?? WORKSPACE_SLUG),
145+
overviewUrl: workspaceOverviewUrl(
146+
teamDomain,
147+
workspace.slug ?? WORKSPACE_SLUG,
148+
),
138149
};
139150
}
140151

@@ -143,32 +154,19 @@ async function createCollection(
143154
workspaceId: string,
144155
collection: unknown,
145156
): Promise<CollectionResponse['collection']> {
146-
const response = await postmanFetch(apiKey, `/collections?workspace=${workspaceId}`, {
147-
method: 'POST',
148-
body: JSON.stringify({ collection }),
149-
});
157+
const response = await postmanFetch(
158+
apiKey,
159+
`/collections?workspace=${workspaceId}`,
160+
{
161+
method: 'POST',
162+
body: JSON.stringify({ collection }),
163+
},
164+
);
150165
const body = (await readJson(response)) as CollectionResponse;
151166
assertOk(response, body, 'POST /collections');
152167
return body.collection;
153168
}
154169

155-
async function updateCollection(
156-
apiKey: string,
157-
uid: string,
158-
collection: unknown,
159-
): Promise<CollectionResponse['collection']> {
160-
const response = await postmanFetch(apiKey, `/collections/${uid}`, {
161-
method: 'PUT',
162-
body: JSON.stringify({ collection }),
163-
});
164-
const body = (await readJson(response)) as CollectionResponse;
165-
if (response.status === 404) {
166-
throw new Error('COLLECTION_NOT_FOUND');
167-
}
168-
assertOk(response, body, `PUT /collections/${uid}`);
169-
return body.collection;
170-
}
171-
172170
async function publishCollectionFile(
173171
apiKey: string,
174172
workspaceId: string,
@@ -187,26 +185,24 @@ async function publishCollectionFile(
187185
const localCollection = loadLocalCollection(filename);
188186
const existing = mapping.collections[key];
189187

190-
console.log(`Publishing ${filename} (${(fileSize / (1024 * 1024)).toFixed(2)} MB)...`);
188+
console.log(
189+
`Publishing ${filename} (${(fileSize / (1024 * 1024)).toFixed(2)} MB)...`,
190+
);
191191

192-
let published: CollectionResponse['collection'];
193192
if (existing?.uid) {
194-
try {
195-
published = await updateCollection(apiKey, existing.uid, localCollection);
196-
console.log(`Updated collection ${published.name} (${published.uid}).`);
197-
} catch (error) {
198-
if (!(error instanceof Error) || error.message !== 'COLLECTION_NOT_FOUND') {
199-
throw error;
200-
}
201-
console.warn(`Mapped UID ${existing.uid} missing in Postman; creating a new collection.`);
202-
published = await createCollection(apiKey, workspaceId, localCollection);
203-
console.log(`Created replacement collection ${published.name} (${published.uid}).`);
204-
}
205-
} else {
206-
published = await createCollection(apiKey, workspaceId, localCollection);
207-
console.log(`Created collection ${published.name} (${published.uid}).`);
193+
console.log(
194+
`Deleting previous collection ${existing.name} (${existing.uid})...`,
195+
);
196+
await deleteCollection(apiKey, existing.uid);
208197
}
209198

199+
const published = await createCollection(
200+
apiKey,
201+
workspaceId,
202+
localCollection,
203+
);
204+
console.log(`Created collection ${published.name} (${published.uid}).`);
205+
210206
mapping.collections[key] = {
211207
uid: published.uid,
212208
name: published.name,
@@ -221,17 +217,26 @@ async function main(): Promise<void> {
221217

222218
const files = listCollectionFiles();
223219
if (files.length === 0) {
224-
throw new Error('No Postman collections found in postman/. Run npm run build first.');
220+
throw new Error(
221+
'No Postman collections found in postman/. Run npm run build first.',
222+
);
225223
}
226224

227225
for (const filename of files) {
228-
await publishCollectionFile(apiKey, mapping.workspace!.id, filename, mapping);
226+
await publishCollectionFile(
227+
apiKey,
228+
mapping.workspace!.id,
229+
filename,
230+
mapping,
231+
);
229232
}
230233

231234
writeMapping(mapping);
232235

233236
console.log('\nPublish complete.');
234-
console.log(`Workspace: ${mapping.workspace.name} (${mapping.workspace.type})`);
237+
console.log(
238+
`Workspace: ${mapping.workspace.name} (${mapping.workspace.type})`,
239+
);
235240
console.log(`Overview: ${mapping.workspace.overviewUrl}`);
236241
console.log(`Collections: ${Object.keys(mapping.collections).length}`);
237242
console.log(`Mapping: postman/.postman-publish.json`);

0 commit comments

Comments
 (0)