@scalar/postman-to-openapi 0.6.3 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @scalar/postman-to-openapi
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#8904](https://github.com/scalar/scalar/pull/8904): feat: don't keep all headers
8
+
9
+ ### Patch Changes
10
+
11
+ - [#8901](https://github.com/scalar/scalar/pull/8901): fix fixture generation formatting by writing output textures with 2-space indented JSON so regeneration does not produce noisy diffs when data is unchanged
12
+ - [#8901](https://github.com/scalar/scalar/pull/8901): Move Postman conversion fixtures into the repository and update tests to read local fixture files instead of downloading from cloud storage.
13
+ - [#8899](https://github.com/scalar/scalar/pull/8899): resolve Postman `{{variables}}` in server URLs by substituting collection variable values and emitting OpenAPI server variables when values are unresolved or recursive
14
+ - [#8900](https://github.com/scalar/scalar/pull/8900): fix(postman-to-openapi): preserve tag context when folder description is an empty string
15
+ - [#8900](https://github.com/scalar/scalar/pull/8900): feat(postman-to-openapi): default to leaf-based tag naming with chain fallback option
16
+ - [#8895](https://github.com/scalar/scalar/pull/8895): fix: omit response content for no-body status codes by default
17
+
3
18
  ## 0.6.3
4
19
 
5
20
  ### Patch Changes
package/dist/convert.d.ts CHANGED
@@ -5,6 +5,7 @@ import type { PostmanCollection } from './types.js';
5
5
  * Example: `[0, 2, 1]` → `collection.item[0].item[2].item[1]`.
6
6
  */
7
7
  export type PostmanRequestIndexPath = readonly number[];
8
+ export type TagNamingStrategy = 'leaf' | 'chain';
8
9
  export type ConvertOptions = {
9
10
  /**
10
11
  * Whether to merge operations with the same path and method.
@@ -21,12 +22,25 @@ export type ConvertOptions = {
21
22
  * When omitted, the whole collection is converted (existing behavior).
22
23
  */
23
24
  requestIndexPaths?: readonly PostmanRequestIndexPath[];
25
+ /**
26
+ * Strategy for generating OpenAPI tag names from nested Postman folders.
27
+ * - `leaf` (default): use the folder name only; duplicate leaves fallback to `parent / leaf`.
28
+ * - `chain`: keep the legacy full folder chain joined by ` > `.
29
+ */
30
+ tagNamingStrategy?: TagNamingStrategy;
24
31
  /**
25
32
  * Existing OpenAPI document to merge into. The input is is updated with Postman paths,
26
33
  * tags (union by name), security schemes, and servers.
27
34
  * Root `info` and existing paths are preserved unless Postman adds or merges operations.
28
35
  */
29
36
  document?: OpenAPIV3_1.Document;
37
+ /**
38
+ * Header keys (case-insensitive) to preserve as `parameters[in=header]` even
39
+ * if they match the built-in block-list of transport, content-negotiation, or
40
+ * auth headers (Accept, Content-Type, Authorization, Host, ...). Rarely needed —
41
+ * useful when an API intentionally documents a non-standard use of these names.
42
+ */
43
+ keepHeaders?: readonly string[];
30
44
  };
31
45
  /**
32
46
  * Converts a Postman Collection to an OpenAPI 3.1.0 document.
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAkBxD,OAAO,KAAK,EAAgC,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAE9E;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,MAAM,EAAE,CAAA;AA8iBvD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAA;IACtD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAA;CAChC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,iBAAiB,EAAE,iBAAiB,GAAG,MAAM,EAC7C,OAAO,GAAE,cAA0C,GAClD,WAAW,CAAC,QAAQ,CA4MtB"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAoBxD,OAAO,KAAK,EAAgC,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAE9E;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,MAAM,EAAE,CAAA;AA6DvD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAA;AA6rBhD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAA;IACtD;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAA;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,iBAAiB,EAAE,iBAAiB,GAAG,MAAM,EAC7C,OAAO,GAAE,cAA0C,GAClD,WAAW,CAAC,QAAQ,CAkOtB"}
package/dist/convert.js CHANGED
@@ -4,10 +4,10 @@ import { processExternalDocs } from './helpers/external-docs.js';
4
4
  import { processLicense } from './helpers/license.js';
5
5
  import { processLogo } from './helpers/logo.js';
6
6
  import { DEFAULT_EXAMPLE_NAME, OPERATION_KEYS, mergePathItem } from './helpers/merge-path-item.js';
7
- import { POSTMAN_EXAMPLE_NAME_EXTENSION, POSTMAN_POST_RESPONSE_SCRIPTS_EXTENSION, POSTMAN_PRE_REQUEST_SCRIPTS_EXTENSION, processItem, } from './helpers/path-items.js';
7
+ import { POSTMAN_EXAMPLE_NAME_EXTENSION, POSTMAN_FOLDER_SEGMENTS_EXTENSION, POSTMAN_POST_RESPONSE_SCRIPTS_EXTENSION, POSTMAN_PRE_REQUEST_SCRIPTS_EXTENSION, processItem, } from './helpers/path-items.js';
8
8
  import { pruneDocument } from './helpers/prune-document.js';
9
9
  import { analyzeServerDistribution } from './helpers/servers.js';
10
- import { getPathStructuralSignature, normalizePath } from './helpers/urls.js';
10
+ import { createCollectionVariableLookup, getPathStructuralSignature, normalizePath } from './helpers/urls.js';
11
11
  const normalizeDescription = (description) => {
12
12
  if (typeof description === 'string') {
13
13
  return description;
@@ -53,35 +53,173 @@ const validateCollectionShape = (collection) => {
53
53
  }
54
54
  return candidate;
55
55
  };
56
- /**
57
- * Extracts tags from Postman collection folders.
58
- * We keep folder nesting using " > " so tag names stay readable while preserving hierarchy.
59
- * Requests do not produce tags; only folders are reflected as tags.
60
- */
61
56
  const isItemGroup = (item) => 'item' in item && Array.isArray(item.item);
62
- const extractTags = (items) => {
63
- const collectTags = (item, parentPath = '') => {
64
- if (!isItemGroup(item)) {
65
- return [];
57
+ const TAG_CHAIN_SEPARATOR = ' > ';
58
+ const TAG_DUPLICATE_SEPARATOR = ' / ';
59
+ const getTagContextKey = (segments) => JSON.stringify(segments);
60
+ const getChainTagName = (segments) => segments.join(TAG_CHAIN_SEPARATOR);
61
+ const isPathParameterSegment = (segment) => (segment.startsWith('{') && segment.endsWith('}')) || segment.startsWith(':');
62
+ const normalizeLeafTagSegment = (segment) => {
63
+ const trimmed = segment.trim();
64
+ if (!trimmed) {
65
+ return trimmed;
66
+ }
67
+ // Postman folders are sometimes literal URL templates (`/languages/{languageCode}`).
68
+ // Keep tag names short by turning those into a readable leaf identifier.
69
+ const looksLikePathTemplate = trimmed.startsWith('/') || (trimmed.includes('/') && !trimmed.includes(' '));
70
+ if (!looksLikePathTemplate) {
71
+ return trimmed;
72
+ }
73
+ const segments = trimmed
74
+ .split('/')
75
+ .map((part) => part.trim())
76
+ .filter(Boolean);
77
+ if (segments.length === 0) {
78
+ return trimmed;
79
+ }
80
+ const preferredSegment = [...segments].reverse().find((part) => !isPathParameterSegment(part));
81
+ const fallbackSegment = preferredSegment ?? segments[segments.length - 1] ?? trimmed;
82
+ if (fallbackSegment.startsWith('{') && fallbackSegment.endsWith('}')) {
83
+ return fallbackSegment.slice(1, -1);
84
+ }
85
+ if (fallbackSegment.startsWith(':')) {
86
+ return fallbackSegment.slice(1);
87
+ }
88
+ return fallbackSegment;
89
+ };
90
+ const getLeafTagName = (segments) => normalizeLeafTagSegment(segments[segments.length - 1] ?? '');
91
+ const getLeafDuplicateFallback = (segments, leafTagName) => {
92
+ const parentSegment = segments[segments.length - 2];
93
+ if (!parentSegment) {
94
+ return getChainTagName(segments);
95
+ }
96
+ const normalizedParent = normalizeLeafTagSegment(parentSegment);
97
+ if (!normalizedParent) {
98
+ return getChainTagName(segments);
99
+ }
100
+ return `${normalizedParent}${TAG_DUPLICATE_SEPARATOR}${leafTagName}`;
101
+ };
102
+ const buildLeafTagContextDescription = (segments) => {
103
+ if (segments.length <= 1) {
104
+ return undefined;
105
+ }
106
+ return `Part of ${segments.slice(0, -1).join(' -> ')}`;
107
+ };
108
+ const mergeTagDescriptions = (description, contextDescription) => {
109
+ if (description && contextDescription) {
110
+ return `${description}\n\n${contextDescription}`;
111
+ }
112
+ return description || contextDescription;
113
+ };
114
+ const dedupeTagContexts = (contexts) => {
115
+ const contextMap = new Map();
116
+ contexts.forEach((context) => {
117
+ const key = getTagContextKey(context.segments);
118
+ const existing = contextMap.get(key);
119
+ if (!existing) {
120
+ contextMap.set(key, context);
121
+ return;
122
+ }
123
+ if (!existing.description && context.description) {
124
+ contextMap.set(key, context);
125
+ }
126
+ });
127
+ return [...contextMap.values()];
128
+ };
129
+ const resolveTagNameMap = (contexts, strategy) => {
130
+ const nameMap = new Map();
131
+ contexts.forEach((context) => {
132
+ const key = getTagContextKey(context.segments);
133
+ const initialName = strategy === 'chain' ? getChainTagName(context.segments) : getLeafTagName(context.segments);
134
+ nameMap.set(key, initialName || getChainTagName(context.segments));
135
+ });
136
+ if (strategy === 'chain') {
137
+ return nameMap;
138
+ }
139
+ const initialCounts = new Map();
140
+ nameMap.forEach((name) => {
141
+ initialCounts.set(name, (initialCounts.get(name) ?? 0) + 1);
142
+ });
143
+ contexts.forEach((context) => {
144
+ const key = getTagContextKey(context.segments);
145
+ const currentName = nameMap.get(key);
146
+ if (!currentName) {
147
+ return;
148
+ }
149
+ if ((initialCounts.get(currentName) ?? 0) > 1) {
150
+ nameMap.set(key, getLeafDuplicateFallback(context.segments, currentName));
151
+ }
152
+ });
153
+ const fallbackCounts = new Map();
154
+ nameMap.forEach((name) => {
155
+ fallbackCounts.set(name, (fallbackCounts.get(name) ?? 0) + 1);
156
+ });
157
+ contexts.forEach((context) => {
158
+ const key = getTagContextKey(context.segments);
159
+ const currentName = nameMap.get(key);
160
+ if (!currentName) {
161
+ return;
66
162
  }
67
- const nextPath = item.name ? (parentPath ? `${parentPath} > ${item.name}` : item.name) : parentPath;
68
- const description = normalizeDescription(item.description);
69
- const currentTag = item.name?.length
70
- ? [
71
- {
72
- name: nextPath,
73
- ...(description && { description }),
74
- },
75
- ]
76
- : [];
77
- return [...currentTag, ...item.item.flatMap((subItem) => collectTags(subItem, nextPath))];
163
+ if ((fallbackCounts.get(currentName) ?? 0) > 1) {
164
+ nameMap.set(key, getChainTagName(context.segments));
165
+ }
166
+ });
167
+ return nameMap;
168
+ };
169
+ const buildTagMetadata = (contexts, strategy) => {
170
+ const uniqueContexts = dedupeTagContexts(contexts);
171
+ const resolvedNameMap = resolveTagNameMap(uniqueContexts, strategy);
172
+ const tags = [];
173
+ const seenNames = new Set();
174
+ uniqueContexts.forEach((context) => {
175
+ const key = getTagContextKey(context.segments);
176
+ const name = resolvedNameMap.get(key);
177
+ if (!name || seenNames.has(name)) {
178
+ return;
179
+ }
180
+ seenNames.add(name);
181
+ const contextDescription = strategy === 'leaf' ? buildLeafTagContextDescription(context.segments) : undefined;
182
+ const description = mergeTagDescriptions(context.description, contextDescription);
183
+ tags.push({
184
+ name,
185
+ ...(description && { description }),
186
+ });
187
+ });
188
+ const resolveTagName = (segments) => {
189
+ if (segments.length === 0) {
190
+ return undefined;
191
+ }
192
+ const fromMap = resolvedNameMap.get(getTagContextKey(segments));
193
+ if (fromMap) {
194
+ return fromMap;
195
+ }
196
+ if (strategy === 'chain') {
197
+ return getChainTagName(segments);
198
+ }
199
+ return getLeafTagName(segments) || getChainTagName(segments);
78
200
  };
79
- return items.flatMap((item) => collectTags(item));
201
+ return { tags, resolveTagName };
80
202
  };
203
+ const collectTagContexts = (items, parentSegments = []) => items.flatMap((item) => {
204
+ if (!isItemGroup(item)) {
205
+ return [];
206
+ }
207
+ const nextSegments = item.name ? [...parentSegments, item.name] : parentSegments;
208
+ const currentContext = item.name?.length
209
+ ? [
210
+ {
211
+ segments: nextSegments,
212
+ description: normalizeDescription(item.description),
213
+ },
214
+ ]
215
+ : [];
216
+ return [...currentContext, ...collectTagContexts(item.item, nextSegments)];
217
+ });
218
+ const extractTags = (items, strategy) => buildTagMetadata(collectTagContexts(items), strategy);
81
219
  /**
82
- * Folder tags for ancestors of each selected path only (same shape as {@link extractTags}).
220
+ * Folder tags for ancestors of each selected path only (same shape as full extraction).
83
221
  */
84
- const extractTagsForSelectedPaths = (items, paths) => {
222
+ const extractTagContextsForSelectedPaths = (items, paths) => {
85
223
  const seen = new Set();
86
224
  const result = [];
87
225
  for (const path of paths) {
@@ -89,7 +227,7 @@ const extractTagsForSelectedPaths = (items, paths) => {
89
227
  continue;
90
228
  }
91
229
  let list = items;
92
- let parentPath = '';
230
+ const segments = [];
93
231
  for (let i = 0; i < path.length - 1; i++) {
94
232
  const idx = path[i];
95
233
  if (idx === undefined || idx < 0 || idx >= list.length) {
@@ -99,21 +237,23 @@ const extractTagsForSelectedPaths = (items, paths) => {
99
237
  if (node === undefined || !isItemGroup(node)) {
100
238
  break;
101
239
  }
102
- const nextPath = node.name ? (parentPath ? `${parentPath} > ${node.name}` : node.name) : parentPath;
103
- const description = normalizeDescription(node.description);
104
- if (node.name?.length && !seen.has(nextPath)) {
105
- seen.add(nextPath);
106
- result.push({
107
- name: nextPath,
108
- ...(description && { description }),
109
- });
240
+ if (node.name?.length) {
241
+ segments.push(node.name);
242
+ const key = getTagContextKey(segments);
243
+ if (!seen.has(key)) {
244
+ seen.add(key);
245
+ result.push({
246
+ segments: [...segments],
247
+ description: normalizeDescription(node.description),
248
+ });
249
+ }
110
250
  }
111
- parentPath = nextPath;
112
251
  list = node.item;
113
252
  }
114
253
  }
115
254
  return result;
116
255
  };
256
+ const extractTagsForSelectedPaths = (items, paths, strategy) => buildTagMetadata(extractTagContextsForSelectedPaths(items, paths), strategy);
117
257
  const getNodeAtPath = (items, path) => {
118
258
  if (path.length === 0) {
119
259
  return undefined;
@@ -178,11 +318,13 @@ const mergeSecuritySchemes = (openapi, securitySchemes) => {
178
318
  };
179
319
  };
180
320
  const mergeServerLists = (existing, incoming) => {
181
- const seen = new Set((existing ?? []).map((s) => s.url));
321
+ const createServerKey = (server) => JSON.stringify(server);
322
+ const seen = new Set((existing ?? []).map(createServerKey));
182
323
  const out = [...(existing ?? [])];
183
324
  for (const server of incoming) {
184
- if (!seen.has(server.url)) {
185
- seen.add(server.url);
325
+ const serverKey = createServerKey(server);
326
+ if (!seen.has(serverKey)) {
327
+ seen.add(serverKey);
186
328
  out.push(server);
187
329
  }
188
330
  }
@@ -241,6 +383,7 @@ const cleanupOperations = (paths) => {
241
383
  delete operation[POSTMAN_EXAMPLE_NAME_EXTENSION];
242
384
  delete operation[POSTMAN_PRE_REQUEST_SCRIPTS_EXTENSION];
243
385
  delete operation[POSTMAN_POST_RESPONSE_SCRIPTS_EXTENSION];
386
+ delete operation[POSTMAN_FOLDER_SEGMENTS_EXTENSION];
244
387
  });
245
388
  });
246
389
  };
@@ -351,20 +494,24 @@ const findFolderTemplateHint = (pathItemGroup, signature) => {
351
494
  if (!operation) {
352
495
  continue;
353
496
  }
497
+ // Use the raw Postman folder chain if available; otherwise fall back to
498
+ // splitting tag strings (supports the legacy chain tag naming strategy).
499
+ const rawSegments = operation[POSTMAN_FOLDER_SEGMENTS_EXTENSION];
500
+ const folderNameCandidates = rawSegments ? [...rawSegments] : [];
354
501
  for (const tag of operation.tags ?? []) {
355
- const folderNames = tag.split(' > ').map((segment) => segment.trim());
356
- for (const folderName of folderNames) {
357
- if (!folderName.startsWith('/')) {
358
- continue;
359
- }
360
- const normalizedFolderName = normalizePath(folderName);
361
- if (getPathStructuralSignature(normalizedFolderName) !== signature) {
362
- continue;
363
- }
364
- const folderParameterNames = getOrderedPathParameterNames(normalizedFolderName);
365
- if (folderParameterNames.length === parameterNames.length) {
366
- return folderParameterNames;
367
- }
502
+ folderNameCandidates.push(...tag.split(' > ').map((segment) => segment.trim()));
503
+ }
504
+ for (const folderName of folderNameCandidates) {
505
+ if (!folderName.startsWith('/')) {
506
+ continue;
507
+ }
508
+ const normalizedFolderName = normalizePath(folderName);
509
+ if (getPathStructuralSignature(normalizedFolderName) !== signature) {
510
+ continue;
511
+ }
512
+ const folderParameterNames = getOrderedPathParameterNames(normalizedFolderName);
513
+ if (folderParameterNames.length === parameterNames.length) {
514
+ return folderParameterNames;
368
515
  }
369
516
  }
370
517
  }
@@ -432,7 +579,7 @@ const unifyEquivalentPathParameters = (paths) => {
432
579
  * and items to create a corresponding OpenAPI structure.
433
580
  */
434
581
  export function convert(postmanCollection, options = { mergeOperation: false }) {
435
- const { requestIndexPaths, mergeOperation = false, document: baseDocument } = options;
582
+ const { requestIndexPaths, mergeOperation = false, tagNamingStrategy = 'leaf', document: baseDocument, keepHeaders, } = options;
436
583
  const isMergingIntoBase = baseDocument !== undefined;
437
584
  const collection = validateCollectionShape(parseCollectionInput(postmanCollection));
438
585
  // Extract title from collection info, fallback to 'API' if not provided
@@ -480,11 +627,12 @@ export function convert(postmanCollection, options = { mergeOperation: false })
480
627
  }
481
628
  // Process each item in the collection and merge into OpenAPI spec
482
629
  const allServerUsage = [];
630
+ const collectionVariableLookup = createCollectionVariableLookup(collection.variable);
483
631
  if (collection.item) {
484
632
  const usePathFilter = requestIndexPaths !== undefined;
485
633
  if (usePathFilter) {
486
634
  const uniquePaths = dedupeIndexPaths(requestIndexPaths);
487
- const tags = extractTagsForSelectedPaths(collection.item, uniquePaths);
635
+ const { tags, resolveTagName } = extractTagsForSelectedPaths(collection.item, uniquePaths, tagNamingStrategy);
488
636
  assignTagsFromPostman(openapi, tags, isMergingIntoBase);
489
637
  for (const path of uniquePaths) {
490
638
  const node = getNodeAtPath(collection.item, path);
@@ -492,7 +640,7 @@ export function convert(postmanCollection, options = { mergeOperation: false })
492
640
  continue;
493
641
  }
494
642
  const parentTags = collectParentTagSegments(collection.item, path);
495
- const { paths: itemPaths, components: itemComponents, serverUsage, } = processItem(node, DEFAULT_EXAMPLE_NAME, parentTags, '', mergeOperation);
643
+ const { paths: itemPaths, components: itemComponents, serverUsage, } = processItem(node, DEFAULT_EXAMPLE_NAME, parentTags, '', mergeOperation, resolveTagName, collectionVariableLookup, keepHeaders);
496
644
  allServerUsage.push(...serverUsage);
497
645
  for (const [pathKey, pathItem] of Object.entries(itemPaths)) {
498
646
  const normalizedPathKey = normalizePath(pathKey);
@@ -507,10 +655,10 @@ export function convert(postmanCollection, options = { mergeOperation: false })
507
655
  }
508
656
  }
509
657
  else {
510
- const tags = extractTags(collection.item);
658
+ const { tags, resolveTagName } = extractTags(collection.item, tagNamingStrategy);
511
659
  assignTagsFromPostman(openapi, tags, isMergingIntoBase);
512
660
  collection.item.forEach((item) => {
513
- const { paths: itemPaths, components: itemComponents, serverUsage, } = processItem(item, DEFAULT_EXAMPLE_NAME, [], '', mergeOperation);
661
+ const { paths: itemPaths, components: itemComponents, serverUsage, } = processItem(item, DEFAULT_EXAMPLE_NAME, [], '', mergeOperation, resolveTagName, collectionVariableLookup, keepHeaders);
514
662
  allServerUsage.push(...serverUsage);
515
663
  openapi.paths = openapi.paths || {};
516
664
  for (const [pathKey, pathItem] of Object.entries(itemPaths)) {
@@ -0,0 +1,21 @@
1
+ import type { HeaderList } from '../types.js';
2
+ type RequestHeaders = HeaderList | string | null | undefined;
3
+ /**
4
+ * Looks up a header value case-insensitively. Returns the value of the first
5
+ * non-disabled match, or undefined. Silently ignores string-typed headers
6
+ * (Postman occasionally stores them that way).
7
+ */
8
+ export declare function readHeader(headers: RequestHeaders, name: string): string | undefined;
9
+ /**
10
+ * Normalises a raw Content-Type header value (`application/json; charset=utf-8`)
11
+ * to just the media type (`application/json`). Returns undefined for empty input.
12
+ */
13
+ export declare function parseMediaType(value: string | undefined): string | undefined;
14
+ /**
15
+ * Picks a single media type from an Accept header value, preferring
16
+ * `application/json` if present, else the first non-wildcard type. Returns
17
+ * undefined for `*​/*` alone, empty input, or only wildcard types.
18
+ */
19
+ export declare function pickAcceptMediaType(value: string | undefined): string | undefined;
20
+ export {};
21
+ //# sourceMappingURL=header-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-utils.d.ts","sourceRoot":"","sources":["../../src/helpers/header-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,SAAS,CAAA;AAEjD,KAAK,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;AAE5D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAM5E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAYjF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Looks up a header value case-insensitively. Returns the value of the first
3
+ * non-disabled match, or undefined. Silently ignores string-typed headers
4
+ * (Postman occasionally stores them that way).
5
+ */
6
+ export function readHeader(headers, name) {
7
+ if (!headers || typeof headers === 'string' || !Array.isArray(headers)) {
8
+ return undefined;
9
+ }
10
+ const target = name.toLowerCase();
11
+ const match = headers.find((h) => h.key?.toLowerCase() === target && !h.disabled);
12
+ return match?.value;
13
+ }
14
+ /**
15
+ * Normalises a raw Content-Type header value (`application/json; charset=utf-8`)
16
+ * to just the media type (`application/json`). Returns undefined for empty input.
17
+ */
18
+ export function parseMediaType(value) {
19
+ if (!value) {
20
+ return undefined;
21
+ }
22
+ const trimmed = value.split(';')[0]?.trim().toLowerCase();
23
+ return trimmed || undefined;
24
+ }
25
+ /**
26
+ * Picks a single media type from an Accept header value, preferring
27
+ * `application/json` if present, else the first non-wildcard type. Returns
28
+ * undefined for `*​/*` alone, empty input, or only wildcard types.
29
+ */
30
+ export function pickAcceptMediaType(value) {
31
+ if (!value) {
32
+ return undefined;
33
+ }
34
+ const types = value
35
+ .split(',')
36
+ .map((t) => parseMediaType(t))
37
+ .filter((t) => !!t && t !== '*/*');
38
+ if (types.length === 0) {
39
+ return undefined;
40
+ }
41
+ return types.find((t) => t === 'application/json') ?? types[0];
42
+ }
@@ -3,8 +3,11 @@ import type { Request } from '../types.js';
3
3
  /**
4
4
  * Extracts parameters from a Postman request and converts them to OpenAPI parameter objects.
5
5
  * Processes query, path, and header parameters from the request URL and headers.
6
+ *
7
+ * Headers on the built-in block-list (content negotiation, transport, auth) are
8
+ * dropped unless the caller passes them via `keepHeaders` (case-insensitive).
6
9
  */
7
- export declare function extractParameters(request: Request, exampleName: string): OpenAPIV3_1.ParameterObject[];
10
+ export declare function extractParameters(request: Request, exampleName: string, keepHeaders?: readonly string[]): OpenAPIV3_1.ParameterObject[];
8
11
  /**
9
12
  * Creates an OpenAPI parameter object from a Postman parameter.
10
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../src/helpers/parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,SAAS,CAAA;AAI9C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,CAAC,eAAe,EAAE,CA2DtG;AAoBD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACpC,WAAW,EAAE,MAAM,GAClB,WAAW,CAAC,eAAe,CAwD7B"}
1
+ {"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../src/helpers/parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,SAAS,CAAA;AAuC9C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAC9B,WAAW,CAAC,eAAe,EAAE,CA8D/B;AAoBD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACpC,WAAW,EAAE,MAAM,GAClB,WAAW,CAAC,eAAe,CAwD7B"}
@@ -1,9 +1,45 @@
1
1
  import { inferSchemaType } from './schemas.js';
2
+ /**
3
+ * Header keys (lower-case) that never become `parameters[in=header]` on an
4
+ * OpenAPI operation. They either describe the transport (Host, Connection),
5
+ * overlap with `requestBody.content` / `responses.content` (Accept,
6
+ * Content-Type), or belong in `components.securitySchemes` (Authorization,
7
+ * Cookie). Callers can opt a specific name back in via `ConvertOptions.keepHeaders`.
8
+ */
9
+ const BLOCKED_HEADERS = new Set([
10
+ // Content negotiation
11
+ 'accept',
12
+ 'accept-encoding',
13
+ 'accept-language',
14
+ 'content-type',
15
+ // Transport / hop-by-hop
16
+ 'connection',
17
+ 'content-length',
18
+ 'host',
19
+ 'transfer-encoding',
20
+ // Auth — belong in components.securitySchemes
21
+ 'authorization',
22
+ 'cookie',
23
+ 'proxy-authorization',
24
+ ]);
25
+ function isBlockedHeader(name, keepHeaders) {
26
+ if (!name) {
27
+ return false;
28
+ }
29
+ const lower = name.toLowerCase();
30
+ if (keepHeaders?.some((kept) => kept.toLowerCase() === lower)) {
31
+ return false;
32
+ }
33
+ return BLOCKED_HEADERS.has(lower);
34
+ }
2
35
  /**
3
36
  * Extracts parameters from a Postman request and converts them to OpenAPI parameter objects.
4
37
  * Processes query, path, and header parameters from the request URL and headers.
38
+ *
39
+ * Headers on the built-in block-list (content negotiation, transport, auth) are
40
+ * dropped unless the caller passes them via `keepHeaders` (case-insensitive).
5
41
  */
6
- export function extractParameters(request, exampleName) {
42
+ export function extractParameters(request, exampleName, keepHeaders) {
7
43
  const parameters = [];
8
44
  const parameterMap = new Map();
9
45
  if (typeof request === 'string' || !request.url) {
@@ -48,6 +84,9 @@ export function extractParameters(request, exampleName) {
48
84
  // Process header parameters
49
85
  if (request.header && Array.isArray(request.header)) {
50
86
  request.header.forEach((header) => {
87
+ if (isBlockedHeader(header.key, keepHeaders)) {
88
+ return;
89
+ }
51
90
  const paramObj = createParameterObject(header, 'header', exampleName);
52
91
  if (paramObj.name) {
53
92
  parameterMap.set(paramObj.name, paramObj);
@@ -4,21 +4,24 @@ type HttpMethods = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'pat
4
4
  export declare const POSTMAN_EXAMPLE_NAME_EXTENSION = "x-postman-example-name";
5
5
  export declare const POSTMAN_PRE_REQUEST_SCRIPTS_EXTENSION = "x-postman-pre-request-scripts";
6
6
  export declare const POSTMAN_POST_RESPONSE_SCRIPTS_EXTENSION = "x-postman-post-response-scripts";
7
+ export declare const POSTMAN_FOLDER_SEGMENTS_EXTENSION = "x-postman-folder-segments";
7
8
  /**
8
9
  * Information about server usage for an operation.
9
10
  */
10
11
  export type ServerUsage = {
11
12
  serverUrl: string;
13
+ server?: OpenAPIV3_1.ServerObject;
12
14
  path: string;
13
15
  method: HttpMethods;
14
16
  };
17
+ type CollectionVariableLookup = ReadonlyMap<string, string>;
15
18
  /**
16
19
  * Processes a Postman collection item or item group and returns
17
20
  * the corresponding OpenAPI paths and components.
18
21
  * Handles nested item groups, extracts request details, and generates corresponding
19
22
  * OpenAPI path items and operations.
20
23
  */
21
- export declare function processItem(item: Item | ItemGroup, exampleName?: string, parentTags?: string[], parentPath?: string, preserveCollapsedVariants?: boolean): {
24
+ export declare function processItem(item: Item | ItemGroup, exampleName?: string, parentTags?: string[], parentPath?: string, preserveCollapsedVariants?: boolean, resolveTagName?: (segments: string[]) => string | undefined, collectionVariableLookup?: CollectionVariableLookup, keepHeaders?: readonly string[]): {
22
25
  paths: OpenAPIV3_1.PathsObject;
23
26
  components: OpenAPIV3_1.ComponentsObject;
24
27
  serverUsage: ServerUsage[];
@@ -1 +1 @@
1
- {"version":3,"file":"path-items.d.ts","sourceRoot":"","sources":["../../src/helpers/path-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAW9C,KAAK,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAE7F,eAAO,MAAM,8BAA8B,2BAA2B,CAAA;AACtE,eAAO,MAAM,qCAAqC,kCAAkC,CAAA;AACpF,eAAO,MAAM,uCAAuC,oCAAoC,CAAA;AAExF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AAoBD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,GAAG,SAAS,EACtB,WAAW,GAAE,MAAkB,EAC/B,UAAU,GAAE,MAAM,EAAO,EACzB,UAAU,GAAE,MAAW,EACvB,yBAAyB,GAAE,OAAe,GACzC;IACD,KAAK,EAAE,WAAW,CAAC,WAAW,CAAA;IAC9B,UAAU,EAAE,WAAW,CAAC,gBAAgB,CAAA;IACxC,WAAW,EAAE,WAAW,EAAE,CAAA;CAC3B,CAoMA;AAED,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkCpD"}
1
+ {"version":3,"file":"path-items.d.ts","sourceRoot":"","sources":["../../src/helpers/path-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAY9C,KAAK,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAE7F,eAAO,MAAM,8BAA8B,2BAA2B,CAAA;AACtE,eAAO,MAAM,qCAAqC,kCAAkC,CAAA;AACpF,eAAO,MAAM,uCAAuC,oCAAoC,CAAA;AAIxF,eAAO,MAAM,iCAAiC,8BAA8B,CAAA;AAE5E;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC,YAAY,CAAA;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AAED,KAAK,wBAAwB,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAoB3D;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,GAAG,SAAS,EACtB,WAAW,GAAE,MAAkB,EAC/B,UAAU,GAAE,MAAM,EAAO,EACzB,UAAU,GAAE,MAAW,EACvB,yBAAyB,GAAE,OAAe,EAC1C,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,SAAS,EAC3D,wBAAwB,GAAE,wBAAoC,EAC9D,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAC9B;IACD,KAAK,EAAE,WAAW,CAAC,WAAW,CAAA;IAC9B,UAAU,EAAE,WAAW,CAAC,gBAAgB,CAAA;IACxC,WAAW,EAAE,WAAW,EAAE,CAAA;CAC3B,CAoNA;AAED,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkCpD"}
@@ -1,14 +1,19 @@
1
1
  import { processAuth } from './auth.js';
2
+ import { parseMediaType, pickAcceptMediaType, readHeader } from './header-utils.js';
2
3
  import { parseMdTable } from './markdown.js';
3
4
  import { extractParameters } from './parameters.js';
4
5
  import { processPostResponseScripts } from './post-response-scripts.js';
5
6
  import { processPreRequestScripts } from './pre-request-scripts.js';
6
7
  import { extractRequestBody } from './request-body.js';
7
8
  import { DEFAULT_RESPONSE_DESCRIPTIONS, extractResponses } from './responses.js';
8
- import { extractPathFromUrl, extractPathParameterNames, extractServerFromUrl, normalizePath } from './urls.js';
9
+ import { extractPathFromUrl, extractPathParameterNames, extractServerObjectFromUrl, normalizePath } from './urls.js';
9
10
  export const POSTMAN_EXAMPLE_NAME_EXTENSION = 'x-postman-example-name';
10
11
  export const POSTMAN_PRE_REQUEST_SCRIPTS_EXTENSION = 'x-postman-pre-request-scripts';
11
12
  export const POSTMAN_POST_RESPONSE_SCRIPTS_EXTENSION = 'x-postman-post-response-scripts';
13
+ // Raw Postman folder-name chain for the operation. Used internally to preserve
14
+ // template hints (for example `/applications/{id}`) even when tag names are
15
+ // simplified. Stripped before emitting the final OpenAPI document.
16
+ export const POSTMAN_FOLDER_SEGMENTS_EXTENSION = 'x-postman-folder-segments';
12
17
  function ensureRequestBodyContent(requestBody) {
13
18
  const content = requestBody.content ?? {};
14
19
  if (Object.keys(content).length === 0) {
@@ -30,14 +35,14 @@ function ensureRequestBodyContent(requestBody) {
30
35
  * Handles nested item groups, extracts request details, and generates corresponding
31
36
  * OpenAPI path items and operations.
32
37
  */
33
- export function processItem(item, exampleName = 'default', parentTags = [], parentPath = '', preserveCollapsedVariants = false) {
38
+ export function processItem(item, exampleName = 'default', parentTags = [], parentPath = '', preserveCollapsedVariants = false, resolveTagName, collectionVariableLookup = new Map(), keepHeaders) {
34
39
  const paths = {};
35
40
  const components = {};
36
41
  const serverUsage = [];
37
42
  if ('item' in item && Array.isArray(item.item)) {
38
43
  const newParentTags = item.name ? [...parentTags, item.name] : parentTags;
39
44
  item.item.forEach((childItem) => {
40
- const childResult = processItem(childItem, exampleName, newParentTags, `${parentPath}/${item.name || ''}`, preserveCollapsedVariants);
45
+ const childResult = processItem(childItem, exampleName, newParentTags, `${parentPath}/${item.name || ''}`, preserveCollapsedVariants, resolveTagName, collectionVariableLookup, keepHeaders);
41
46
  // Merge child paths and components
42
47
  for (const [pathKey, pathItem] of Object.entries(childResult.paths)) {
43
48
  if (!paths[pathKey]) {
@@ -74,10 +79,11 @@ export function processItem(item, exampleName = 'default', parentTags = [], pare
74
79
  // Normalize path parameters from ':param' to '{param}'
75
80
  const normalizedPath = normalizePath(path);
76
81
  // Extract server URL from request URL
77
- const serverUrl = extractServerFromUrl(requestUrl);
78
- if (serverUrl) {
82
+ const server = extractServerObjectFromUrl(requestUrl, collectionVariableLookup);
83
+ if (server?.url) {
79
84
  serverUsage.push({
80
- serverUrl,
85
+ serverUrl: server.url,
86
+ server,
81
87
  path: normalizedPath,
82
88
  method,
83
89
  });
@@ -91,13 +97,23 @@ export function processItem(item, exampleName = 'default', parentTags = [], pare
91
97
  : typeof request.description === 'string'
92
98
  ? request.description
93
99
  : (request.description?.content ?? '');
100
+ const tagName = parentTags.length > 0 ? (resolveTagName ? resolveTagName(parentTags) : parentTags.join(' > ')) : undefined;
101
+ // Derive media-type signals from request headers before they are filtered out
102
+ // of `parameters[in=header]`. Content-Type drives the request body media type;
103
+ // Accept drives responses when no saved-response Content-Type is available.
104
+ const requestHeaders = typeof request === 'string' ? undefined : request.header;
105
+ const contentType = parseMediaType(readHeader(requestHeaders, 'Content-Type'));
106
+ const acceptMediaType = pickAcceptMediaType(readHeader(requestHeaders, 'Accept'));
94
107
  const operationObject = {
95
- tags: parentTags.length > 0 ? [parentTags.join(' > ')] : undefined,
108
+ tags: tagName ? [tagName] : undefined,
96
109
  summary,
97
110
  description,
98
- responses: extractResponses(response || [], item),
111
+ responses: extractResponses(response || [], item, acceptMediaType),
99
112
  parameters: [],
100
113
  };
114
+ if (parentTags.length > 0) {
115
+ operationObject[POSTMAN_FOLDER_SEGMENTS_EXTENSION] = [...parentTags];
116
+ }
101
117
  if (preserveCollapsedVariants) {
102
118
  operationObject[POSTMAN_EXAMPLE_NAME_EXTENSION] = sourceRequestName;
103
119
  }
@@ -127,7 +143,7 @@ export function processItem(item, exampleName = 'default', parentTags = [], pare
127
143
  }
128
144
  // Extract parameters from the request (query, path, header)
129
145
  // This should always happen, regardless of whether a description exists
130
- const extractedParameters = extractParameters(request, operationExampleName);
146
+ const extractedParameters = extractParameters(request, operationExampleName, keepHeaders);
131
147
  // Merge parameters, giving priority to those from the Markdown table if description exists
132
148
  const mergedParameters = new Map();
133
149
  // Add extracted parameters, filtering out path parameters not in the path
@@ -174,7 +190,7 @@ export function processItem(item, exampleName = 'default', parentTags = [], pare
174
190
  }
175
191
  // Allow request bodies for all methods (including GET) if body is present
176
192
  if (typeof request !== 'string' && request.body) {
177
- const requestBody = extractRequestBody(request.body, operationExampleName);
193
+ const requestBody = extractRequestBody(request.body, operationExampleName, contentType);
178
194
  ensureRequestBodyContent(requestBody);
179
195
  // Only add requestBody if it has content
180
196
  if (requestBody.content && Object.keys(requestBody.content).length > 0) {
@@ -3,6 +3,10 @@ import type { RequestBody } from '../types.js';
3
3
  /**
4
4
  * Extracts and converts the request body from a Postman request to an OpenAPI RequestBodyObject.
5
5
  * Handles raw JSON, form-data, and URL-encoded body types, creating appropriate schemas and content types.
6
+ *
7
+ * When `contentType` is provided (already normalised via `parseMediaType`), it
8
+ * wins over the Postman `options.raw.language` hint for `raw` bodies.
9
+ * `formdata` and `urlencoded` modes keep their natural media types.
6
10
  */
7
- export declare function extractRequestBody(body: RequestBody, exampleName: string): OpenAPIV3_1.RequestBodyObject;
11
+ export declare function extractRequestBody(body: RequestBody, exampleName: string, contentType?: string): OpenAPIV3_1.RequestBodyObject;
8
12
  //# sourceMappingURL=request-body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../../src/helpers/request-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAiB,WAAW,EAAuB,MAAM,SAAS,CAAA;AAK9E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,CAAC,iBAAiB,CAqBxG"}
1
+ {"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../../src/helpers/request-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAiB,WAAW,EAAuB,MAAM,SAAS,CAAA;AAK9E;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,WAAW,CAAC,iBAAiB,CAqB/B"}
@@ -3,13 +3,17 @@ import { createParameterObject } from './parameters.js';
3
3
  /**
4
4
  * Extracts and converts the request body from a Postman request to an OpenAPI RequestBodyObject.
5
5
  * Handles raw JSON, form-data, and URL-encoded body types, creating appropriate schemas and content types.
6
+ *
7
+ * When `contentType` is provided (already normalised via `parseMediaType`), it
8
+ * wins over the Postman `options.raw.language` hint for `raw` bodies.
9
+ * `formdata` and `urlencoded` modes keep their natural media types.
6
10
  */
7
- export function extractRequestBody(body, exampleName) {
11
+ export function extractRequestBody(body, exampleName, contentType) {
8
12
  const requestBody = {
9
13
  content: {},
10
14
  };
11
15
  if (body.mode === 'raw') {
12
- handleRawBody(body, requestBody, exampleName);
16
+ handleRawBody(body, requestBody, exampleName, contentType);
13
17
  return requestBody;
14
18
  }
15
19
  if (body.mode === 'formdata' && body.formdata) {
@@ -22,11 +26,11 @@ export function extractRequestBody(body, exampleName) {
22
26
  }
23
27
  return requestBody;
24
28
  }
25
- function handleRawBody(body, requestBody, exampleName) {
29
+ function handleRawBody(body, requestBody, exampleName, contentType) {
26
30
  const rawBody = body.raw || '';
27
31
  const isJsonLanguage = body.options?.raw?.language === 'json';
28
- // If we have valid JSON, use it
29
- if (isJsonLanguage) {
32
+ const mediaType = contentType ?? (isJsonLanguage ? 'application/json' : 'text/plain');
33
+ if (mediaType === 'application/json') {
30
34
  requestBody.content = {
31
35
  'application/json': {
32
36
  schema: {
@@ -41,12 +45,27 @@ function handleRawBody(body, requestBody, exampleName) {
41
45
  };
42
46
  return;
43
47
  }
44
- // Fallback to text/plain
48
+ if (mediaType === 'text/plain') {
49
+ requestBody.content = {
50
+ 'text/plain': {
51
+ schema: {
52
+ type: 'string',
53
+ examples: rawBody ? [rawBody] : undefined,
54
+ },
55
+ },
56
+ };
57
+ return;
58
+ }
59
+ // Any other caller-declared media type (application/xml, text/csv, application/octet-stream, ...)
45
60
  requestBody.content = {
46
- 'text/plain': {
61
+ [mediaType]: {
47
62
  schema: {
48
63
  type: 'string',
49
- examples: rawBody ? [rawBody] : undefined,
64
+ },
65
+ examples: {
66
+ [exampleName]: {
67
+ value: rawBody,
68
+ },
50
69
  },
51
70
  },
52
71
  };
@@ -5,6 +5,12 @@ export declare const DEFAULT_RESPONSE_DESCRIPTIONS: Record<string, string>;
5
5
  * Extracts and converts Postman response objects to OpenAPI response objects.
6
6
  * Processes response status codes, descriptions, headers, and body content,
7
7
  * inferring schemas from example responses when possible.
8
+ *
9
+ * Media-type selection precedence for each response:
10
+ * 1. The saved response's own `Content-Type` header.
11
+ * 2. The request's `Accept` header (passed as `acceptMediaType`, already
12
+ * narrowed via `pickAcceptMediaType`).
13
+ * 3. `application/json` fallback.
8
14
  */
9
- export declare function extractResponses(responses: Response[], item?: Item): OpenAPIV3_1.ResponsesObject | undefined;
15
+ export declare function extractResponses(responses: Response[], item?: Item, acceptMediaType?: string): OpenAPIV3_1.ResponsesObject | undefined;
10
16
  //# sourceMappingURL=responses.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/helpers/responses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAc,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAKzD,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAchE,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,WAAW,CAAC,eAAe,GAAG,SAAS,CAwC5G"}
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/helpers/responses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAc,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAMzD,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAchE,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,QAAQ,EAAE,EACrB,IAAI,CAAC,EAAE,IAAI,EACX,eAAe,CAAC,EAAE,MAAM,GACvB,WAAW,CAAC,eAAe,GAAG,SAAS,CA+DzC"}
@@ -1,3 +1,4 @@
1
+ import { parseMediaType, readHeader } from './header-utils.js';
1
2
  import { inferSchemaFromExample } from './schemas.js';
2
3
  import { extractStatusCodesFromTests } from './status-codes.js';
3
4
  export const DEFAULT_RESPONSE_DESCRIPTIONS = {
@@ -19,36 +20,58 @@ export const DEFAULT_RESPONSE_DESCRIPTIONS = {
19
20
  * Extracts and converts Postman response objects to OpenAPI response objects.
20
21
  * Processes response status codes, descriptions, headers, and body content,
21
22
  * inferring schemas from example responses when possible.
23
+ *
24
+ * Media-type selection precedence for each response:
25
+ * 1. The saved response's own `Content-Type` header.
26
+ * 2. The request's `Accept` header (passed as `acceptMediaType`, already
27
+ * narrowed via `pickAcceptMediaType`).
28
+ * 3. `application/json` fallback.
22
29
  */
23
- export function extractResponses(responses, item) {
30
+ export function extractResponses(responses, item, acceptMediaType) {
24
31
  // Extract status codes from tests
25
32
  const statusCodes = item ? extractStatusCodesFromTests(item) : [];
26
33
  // Create a map of status codes to descriptions from responses
27
34
  const responseMap = responses.reduce((acc, response) => {
28
35
  const statusCode = response.code?.toString() || 'default';
29
- acc[statusCode] = {
30
- description: getResponseDescription(response, statusCode),
31
- headers: extractHeaders(response.header),
32
- content: {
33
- 'application/json': {
36
+ const hasNoContentStatusCode = hasNoResponseBodyStatusCode(statusCode);
37
+ const hasExplicitBodyExample = response.body !== undefined && response.body !== null && response.body !== '';
38
+ if (hasNoContentStatusCode && hasExplicitBodyExample) {
39
+ console.warn(`[postman-to-openapi] Response ${statusCode} usually has no body, but Postman includes a body example. Keeping OpenAPI content.`);
40
+ }
41
+ const savedContentType = parseMediaType(readHeader(response.header, 'Content-Type'));
42
+ const mediaType = savedContentType ?? acceptMediaType ?? 'application/json';
43
+ const content = hasNoContentStatusCode && !hasExplicitBodyExample
44
+ ? undefined
45
+ : {
46
+ [mediaType]: {
34
47
  schema: inferSchemaFromExample(response.body || ''),
35
48
  examples: {
36
49
  default: tryParseJson(response.body || ''),
37
50
  },
38
51
  },
39
- },
52
+ };
53
+ acc[statusCode] = {
54
+ description: getResponseDescription(response, statusCode),
55
+ headers: extractHeaders(response.header),
56
+ ...(content ? { content } : {}),
40
57
  };
41
58
  return acc;
42
59
  }, {});
43
60
  // Add status codes from tests if not already present
61
+ const fallbackMediaType = acceptMediaType ?? 'application/json';
44
62
  statusCodes.forEach((code) => {
45
63
  const codeStr = code.toString();
46
64
  if (!responseMap[codeStr]) {
65
+ const hasNoContentStatusCode = hasNoResponseBodyStatusCode(codeStr);
47
66
  responseMap[codeStr] = {
48
67
  description: getDefaultResponseDescription(codeStr),
49
- content: {
50
- 'application/json': {},
51
- },
68
+ ...(!hasNoContentStatusCode
69
+ ? {
70
+ content: {
71
+ [fallbackMediaType]: {},
72
+ },
73
+ }
74
+ : {}),
52
75
  };
53
76
  }
54
77
  });
@@ -100,6 +123,13 @@ function isThreeDigitStatusCode(value) {
100
123
  }
101
124
  return true;
102
125
  }
126
+ const hasNoResponseBodyStatusCode = (statusCode) => {
127
+ const numericCode = Number(statusCode);
128
+ if (!Number.isInteger(numericCode)) {
129
+ return false;
130
+ }
131
+ return (numericCode >= 100 && numericCode <= 199) || numericCode === 204 || numericCode === 205 || numericCode === 304;
132
+ };
103
133
  function extractHeaders(headers) {
104
134
  if (!headers || typeof headers === 'string') {
105
135
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../src/helpers/servers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;GAEG;AACH,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,WAAW,CAAC,YAAY,EAAE,CAAA;IACpC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE,CAAC,CAAA;IAClD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;CACjE,CAAA;AAsBD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,WAAW,EAAE,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,eAAe,CAuElH"}
1
+ {"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../src/helpers/servers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;GAEG;AACH,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,WAAW,CAAC,YAAY,EAAE,CAAA;IACpC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE,CAAC,CAAA;IAClD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;CACjE,CAAA;AAsBD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,WAAW,EAAE,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,eAAe,CA0ElH"}
@@ -33,18 +33,22 @@ export function analyzeServerDistribution(serverUsage, allUniquePaths) {
33
33
  if (serverUsage.length === 0) {
34
34
  return placement;
35
35
  }
36
- // Build a map: serverUrl -> Set<operationKey>
36
+ // Build a map: serverKey -> { server, operations }
37
37
  // Using string keys instead of objects because JavaScript Sets compare by reference
38
38
  const serverMap = new Map();
39
39
  for (const usage of serverUsage) {
40
- if (!serverMap.has(usage.serverUrl)) {
41
- serverMap.set(usage.serverUrl, new Set());
40
+ const serverObject = usage.server ?? { url: usage.serverUrl };
41
+ const serverKey = JSON.stringify(serverObject);
42
+ if (!serverMap.has(serverKey)) {
43
+ serverMap.set(serverKey, {
44
+ server: serverObject,
45
+ operations: new Set(),
46
+ });
42
47
  }
43
- serverMap.get(usage.serverUrl).add(createOperationKey(usage.path, usage.method));
48
+ serverMap.get(serverKey).operations.add(createOperationKey(usage.path, usage.method));
44
49
  }
45
50
  // For each server, determine its placement
46
- for (const [serverUrl, operationKeys] of serverMap.entries()) {
47
- const serverObject = { url: serverUrl };
51
+ for (const { server: serverObject, operations: operationKeys } of serverMap.values()) {
48
52
  // Parse operation keys back to path/method pairs
49
53
  const operations = Array.from(operationKeys).map(parseOperationKey);
50
54
  // Count unique paths this server appears in
@@ -1,3 +1,4 @@
1
+ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
1
2
  /**
2
3
  * Extracts the domain (including protocol and port if present) from a given URL.
3
4
  */
@@ -11,6 +12,15 @@ export declare function extractPathFromUrl(url: string | undefined): string;
11
12
  * e.g., '/users/:id' becomes '/users/{id}'
12
13
  */
13
14
  export declare const normalizePath: (path: string) => string;
15
+ type CollectionVariableLookup = ReadonlyMap<string, string>;
16
+ /**
17
+ * Extracts Postman collection variables into a lookup table.
18
+ */
19
+ export declare function createCollectionVariableLookup(variables: ReadonlyArray<{
20
+ key?: string;
21
+ value?: string | number | boolean;
22
+ disabled?: boolean;
23
+ }> | undefined): ReadonlyMap<string, string>;
14
24
  /**
15
25
  * Generates a structural path signature by replacing parameter segments with `{*}`.
16
26
  * Paths with the same signature are equivalent except for parameter names.
@@ -27,4 +37,9 @@ export declare function extractPathParameterNames(path: string): string[];
27
37
  * Returns undefined if no valid server URL can be extracted.
28
38
  */
29
39
  export declare function extractServerFromUrl(url: string | undefined): string | undefined;
40
+ /**
41
+ * Extracts the server object from a request URL and resolves Postman templates.
42
+ */
43
+ export declare function extractServerObjectFromUrl(url: string | undefined, collectionVariableLookup?: CollectionVariableLookup): OpenAPIV3_1.ServerObject | undefined;
44
+ export {};
30
45
  //# sourceMappingURL=urls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/helpers/urls.ts"],"names":[],"mappings":"AAgBA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYlE;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAyC,CAAA;AAEtF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GAAI,MAAM,MAAM,KAAG,MAWzD,CAAA;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAahE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAyBhF"}
1
+ {"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/helpers/urls.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAoBxD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYlE;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAyC,CAAA;AAEtF,KAAK,wBAAwB,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AA8B3D;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,aAAa,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,SAAS,GAC5G,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAU7B;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GAAI,MAAM,MAAM,KAAG,MAWzD,CAAA;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAahE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,wBAAwB,GAAE,wBAAoC,GAC7D,WAAW,CAAC,YAAY,GAAG,SAAS,CAsEtC"}
@@ -1,4 +1,7 @@
1
1
  import { REGEX } from '@scalar/helpers/regex/regex-helpers';
2
+ const POSTMAN_TEMPLATE_REGEX = /\{\{([^{}]{0,1000})\}\}/g;
3
+ const DEFAULT_SERVER_VARIABLE_VALUE = 'example.com';
4
+ const SERVER_VARIABLE_DESCRIPTION = 'Declared in Postman collection variables.';
2
5
  /**
3
6
  * Parses a URL string into its component parts.
4
7
  */
@@ -27,7 +30,7 @@ export function extractPathFromUrl(url) {
27
30
  // Remove scheme, domain, query parameters, and hash fragments
28
31
  const path = url.replace(/^(?:https?:\/\/)?[^/]+(\/|$)/, '/').split(/[?#]/)[0] ?? '';
29
32
  // Replace Postman variables and ensure single leading slash
30
- const finalPath = ('/' + path.replace(/\{\{([^{}]{0,1000})\}\}/g, '{$1}').replace(/^\/+/, '')).replace(/\/\/+/g, '/');
33
+ const finalPath = ('/' + path.replace(POSTMAN_TEMPLATE_REGEX, '{$1}').replace(/^\/+/, '')).replace(/\/\/+/g, '/');
31
34
  return finalPath;
32
35
  }
33
36
  /**
@@ -35,6 +38,41 @@ export function extractPathFromUrl(url) {
35
38
  * e.g., '/users/:id' becomes '/users/{id}'
36
39
  */
37
40
  export const normalizePath = (path) => path.replace(/:(\w+)/g, '{$1}');
41
+ const isCompleteUrl = (value) => /^(https?:\/\/)/i.test(value);
42
+ const hasPostmanTemplateSyntax = (value) => /\{\{([^{}]{0,1000})\}\}/.test(value);
43
+ const createServerVariableDefinition = () => ({
44
+ default: DEFAULT_SERVER_VARIABLE_VALUE,
45
+ description: SERVER_VARIABLE_DESCRIPTION,
46
+ });
47
+ const extractFullHostTemplateVariableName = (rawServerUrl) => {
48
+ const protocolSeparatorIndex = rawServerUrl.indexOf('://');
49
+ if (protocolSeparatorIndex === -1) {
50
+ return undefined;
51
+ }
52
+ const hostCandidate = rawServerUrl.slice(protocolSeparatorIndex + 3);
53
+ if (!hostCandidate.startsWith('{{') || !hostCandidate.endsWith('}}')) {
54
+ return undefined;
55
+ }
56
+ const variableName = hostCandidate.slice(2, -2).trim();
57
+ if (!variableName || variableName.includes('{') || variableName.includes('}')) {
58
+ return undefined;
59
+ }
60
+ return variableName;
61
+ };
62
+ /**
63
+ * Extracts Postman collection variables into a lookup table.
64
+ */
65
+ export function createCollectionVariableLookup(variables) {
66
+ const variableLookup = new Map();
67
+ for (const variable of variables ?? []) {
68
+ const key = variable.key?.trim();
69
+ if (!key || variable.disabled || variable.value === undefined) {
70
+ continue;
71
+ }
72
+ variableLookup.set(key, String(variable.value));
73
+ }
74
+ return variableLookup;
75
+ }
38
76
  /**
39
77
  * Generates a structural path signature by replacing parameter segments with `{*}`.
40
78
  * Paths with the same signature are equivalent except for parameter names.
@@ -70,6 +108,12 @@ export function extractPathParameterNames(path) {
70
108
  * Returns undefined if no valid server URL can be extracted.
71
109
  */
72
110
  export function extractServerFromUrl(url) {
111
+ return extractServerObjectFromUrl(url)?.url;
112
+ }
113
+ /**
114
+ * Extracts the server object from a request URL and resolves Postman templates.
115
+ */
116
+ export function extractServerObjectFromUrl(url, collectionVariableLookup = new Map()) {
73
117
  if (!url) {
74
118
  return undefined;
75
119
  }
@@ -84,8 +128,49 @@ export function extractServerFromUrl(url) {
84
128
  }
85
129
  const hostPart = urlMatch[1];
86
130
  // Preserve the original protocol if present, otherwise default to https
87
- const serverUrl = protocol ? `${protocol}${hostPart}`.replace(/\/$/, '') : `https://${hostPart}`.replace(/\/$/, '');
88
- return serverUrl;
131
+ const rawServerUrl = protocol
132
+ ? `${protocol}${hostPart}`.replace(/\/$/, '')
133
+ : `https://${hostPart}`.replace(/\/$/, '');
134
+ const templateMatches = Array.from(rawServerUrl.matchAll(POSTMAN_TEMPLATE_REGEX));
135
+ if (templateMatches.length === 0) {
136
+ return { url: rawServerUrl };
137
+ }
138
+ const unresolvedVariables = new Set();
139
+ const fullHostTemplateVariableName = extractFullHostTemplateVariableName(rawServerUrl);
140
+ if (fullHostTemplateVariableName) {
141
+ const variableName = fullHostTemplateVariableName;
142
+ const variableValue = collectionVariableLookup.get(variableName);
143
+ if (!variableValue || hasPostmanTemplateSyntax(variableValue)) {
144
+ unresolvedVariables.add(variableName);
145
+ }
146
+ else if (isCompleteUrl(variableValue)) {
147
+ return { url: variableValue.replace(/\/$/, '') };
148
+ }
149
+ }
150
+ const resolvedUrl = rawServerUrl.replace(POSTMAN_TEMPLATE_REGEX, (_, rawName) => {
151
+ const variableName = rawName.trim();
152
+ const variableValue = collectionVariableLookup.get(variableName);
153
+ if (!variableValue || hasPostmanTemplateSyntax(variableValue)) {
154
+ unresolvedVariables.add(variableName);
155
+ return `{${variableName}}`;
156
+ }
157
+ if (isCompleteUrl(variableValue)) {
158
+ unresolvedVariables.add(variableName);
159
+ return `{${variableName}}`;
160
+ }
161
+ return variableValue;
162
+ });
163
+ if (unresolvedVariables.size > 0) {
164
+ const variables = {};
165
+ for (const variableName of unresolvedVariables) {
166
+ variables[variableName] = createServerVariableDefinition();
167
+ }
168
+ return {
169
+ url: resolvedUrl,
170
+ variables,
171
+ };
172
+ }
173
+ return { url: resolvedUrl.replace(/\/$/, '') };
89
174
  }
90
175
  catch (error) {
91
176
  console.error(`Error extracting server from URL "${url}":`, error);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type ConvertOptions, type PostmanRequestIndexPath, convert } from './convert.js';
1
+ export { type ConvertOptions, convert, type PostmanRequestIndexPath, type TagNamingStrategy } from './convert.js';
2
2
  export { isPostmanCollection } from './is-postman-collection.js';
3
3
  export { extractPathFromUrl, normalizePath } from './helpers/urls.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,uBAAuB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC9G,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "export",
20
20
  "scalar"
21
21
  ],
22
- "version": "0.6.3",
22
+ "version": "0.7.0",
23
23
  "engines": {
24
24
  "node": ">=22"
25
25
  },
@@ -39,17 +39,16 @@
39
39
  ],
40
40
  "dependencies": {
41
41
  "@scalar/helpers": "0.5.1",
42
- "@scalar/openapi-types": "0.7.0"
42
+ "@scalar/openapi-types": "0.8.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.1.0",
46
46
  "vite": "8.0.0",
47
- "@scalar/openapi-parser": "0.25.10"
47
+ "@scalar/openapi-parser": "0.25.11"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
51
51
  "evaluate": "tsx ./scripts/evaluate/run.ts",
52
- "generate:textures": "tsx ./scripts/generate-textures.ts",
53
52
  "test": "vitest --run",
54
53
  "types:check": "tsc --noEmit"
55
54
  }