Skip to content

Instantly share code, notes, and snippets.

@gabigabogabu
Created June 12, 2025 16:35
Show Gist options
  • Save gabigabogabu/0f7fa9ee04b63dcc48cd9dd74681370e to your computer and use it in GitHub Desktop.
Save gabigabogabu/0f7fa9ee04b63dcc48cd9dd74681370e to your computer and use it in GitHub Desktop.
postman 2 curl
#!/usr/bin/env node
import { readFile, writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
/**
* @typedef {Object} PostmanUrl
* @property {string} [raw]
* @property {string} [protocol]
* @property {string[]} [host]
* @property {string} [port]
* @property {string[]} [path]
* @property {Array<{key: string, value: string, disabled?: boolean}>} [query]
*/
/**
* @typedef {Object} PostmanHeader
* @property {string} key
* @property {string} value
* @property {string} [type]
* @property {boolean} [disabled]
*/
/**
* @typedef {Object} PostmanBody
* @property {string} [mode]
* @property {string} [raw]
* @property {Object} [options]
* @property {Object} [options.raw]
* @property {string} [options.raw.language]
*/
/**
* @typedef {Object} PostmanAuth
* @property {string} type
* @property {Array<{key: string, value: string, type: string}>} [basic]
* @property {Array<{key: string, value: string, type: string}>} [bearer]
* @property {Array<{key: string, value: string, type: string}>} [apikey]
*/
/**
* @typedef {Object} PostmanRequest
* @property {string} method
* @property {PostmanHeader[]} [header]
* @property {PostmanBody} [body]
* @property {PostmanUrl} [url]
* @property {PostmanAuth} [auth]
*/
/**
* @typedef {Object} PostmanItem
* @property {string} name
* @property {PostmanRequest} [request]
* @property {PostmanItem[]} [item]
* @property {any[]} [response]
*/
/**
* @typedef {Object} PostmanCollection
* @property {Object} info
* @property {string} info.name
* @property {string} info._postman_id
* @property {string} info.schema
* @property {PostmanItem[]} item
*/
/**
* Load a Postman collection from file
* @param {string} collectionPath
* @returns {Promise<PostmanCollection>}
*/
async function loadCollection(collectionPath) {
try {
const fileContent = await readFile(collectionPath, 'utf-8');
const collection = JSON.parse(fileContent);
return collection;
} catch (error) {
throw new Error(`Failed to load collection: ${error}`);
}
}
/**
* Build URL from Postman URL object
* @param {PostmanUrl | undefined} url
* @returns {string}
*/
function buildUrl(url) {
if (!url) return 'http://localhost';
if (url.raw) return url.raw;
let fullUrl = '';
if (url.protocol)
fullUrl += `${url.protocol}://`;
else
fullUrl += 'http://';
if (url.host && url.host.length > 0)
fullUrl += url.host.join('.');
else
fullUrl += 'localhost';
if (url.port) fullUrl += `:${url.port}`;
if (url.path && url.path.length > 0)
fullUrl += `/${url.path.join('/')}`;
if (url.query && url.query.length > 0) {
const enabledQueries = url.query.filter(q => !q.disabled);
if (enabledQueries.length > 0) {
const queryString = enabledQueries
.map(q => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value)}`)
.join('&');
fullUrl += `?${queryString}`;
}
}
return fullUrl;
}
/**
* Build headers array for curl command
* @param {PostmanHeader[]} [headers]
* @returns {string[]}
*/
function buildHeaders(headers) {
if (!headers) return [];
return headers
.filter(header => !header.disabled)
.map(header => `-H "${header.key}: ${header.value.replace(/"/g, '\\"')}"`)
.filter(header => header.length > 0);
}
/**
* Build body string for curl command
* @param {PostmanBody} [body]
* @returns {string}
*/
function buildBody(body) {
if (!body || !body.raw) return '';
const escapedBody = body.raw
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
return `-d "${escapedBody}"`;
}
/**
* Build authentication parts for curl command
* @param {PostmanAuth} [auth]
* @returns {string[]}
*/
function buildAuth(auth) {
if (!auth) return [];
const authParts = [];
switch (auth.type) {
case 'basic':
if (auth.basic) {
const username = auth.basic.find(item => item.key === 'username')?.value || '';
const password = auth.basic.find(item => item.key === 'password')?.value || '';
if (username || password) {
authParts.push(`-u "${username}:${password}"`);
}
}
break;
case 'bearer':
if (auth.bearer) {
const token = auth.bearer.find(item => item.key === 'token')?.value;
if (token) {
authParts.push(`-H "Authorization: Bearer ${token}"`);
}
}
break;
case 'apikey':
if (auth.apikey) {
const key = auth.apikey.find(item => item.key === 'key')?.value;
const value = auth.apikey.find(item => item.key === 'value')?.value;
const addTo = auth.apikey.find(item => item.key === 'in')?.value || 'header';
if (key && value) {
if (addTo === 'header') {
authParts.push(`-H "${key}: ${value}"`);
}
// Note: Query parameter API keys would need URL modification
}
}
break;
case 'noauth':
default:
// No authentication needed
break;
}
return authParts;
}
/**
* Convert a Postman request to curl command
* @param {PostmanRequest} request
* @param {string} name
* @returns {string}
*/
function requestToCurl(request, name) {
const url = buildUrl(request.url);
const method = request.method.toUpperCase();
const headers = buildHeaders(request.header);
const auth = buildAuth(request.auth);
const body = buildBody(request.body);
let curlCommand = `# ${name}\ncurl`;
if (method !== 'GET')
curlCommand += ` -X ${method}`;
if (auth.length > 0)
curlCommand += ` \\\n ${auth.join(' \\\n ')}`;
if (headers.length > 0)
curlCommand += ` \\\n ${headers.join(' \\\n ')}`;
if (body && ['POST', 'PUT', 'PATCH'].includes(method))
curlCommand += ` \\\n ${body}`;
curlCommand += ` \\\n "${url}"`;
return curlCommand;
}
/**
* Extract all requests from collection items recursively with folder structure
* @param {PostmanItem[]} items
* @param {string} [parentPath]
* @returns {Array<{name: string, request: PostmanRequest, path: string}>}
*/
function extractRequests(items, parentPath = '') {
return items.reduce((acc, item) => {
const currentPath = parentPath ? `${parentPath}/${item.name}` : item.name;
if (item.request) acc.push({ name: item.name, request: item.request, path: currentPath });
if (item.item) acc.push(...extractRequests(item.item, currentPath));
return acc;
}, []);
}
/**
* Sanitize filename by removing invalid characters
* @param {string} filename
* @returns {string}
*/
function sanitizeFilename(filename) {
return filename
.replace(/[<>:"/\\|?*]/g, '_')
.replace(/\s+/g, '_')
.replace(/_{2,}/g, '_')
.replace(/^_|_$/g, '');
}
/**
* Create directory structure and write individual request files
* @param {PostmanCollection} collection
* @param {string} outputDir
* @returns {Promise<void>}
*/
async function saveRequestsToFiles(collection, outputDir) {
const requests = extractRequests(collection.item);
if (requests.length === 0) {
console.log('No requests found in the collection');
return;
}
// Create the main output directory
await mkdir(outputDir, { recursive: true });
// Create a summary file
const summaryContent = `# ${collection.info.name}
# Total requests: ${requests.length}
# Generated on: ${new Date().toISOString()}
`;
await writeFile(join(outputDir, 'README.md'), summaryContent, 'utf-8');
let savedCount = 0;
for (const { name, request, path } of requests) {
try {
const pathParts = path.split('/');
const filename = sanitizeFilename(pathParts.pop()) + '.sh';
const dirPath = pathParts.length > 0 ? join(outputDir, ...pathParts.map(sanitizeFilename)) : outputDir;
if (pathParts.length > 0)
await mkdir(dirPath, { recursive: true });
const curlCommand = requestToCurl(request, name);
const fileContent = `#!/bin/bash
# ${name}
# Generated on: ${new Date().toISOString()}
${curlCommand}
`;
const filePath = join(dirPath, filename);
await writeFile(filePath, fileContent, 'utf-8');
savedCount++;
} catch (error) {
console.error(`Error saving request "${name}": ${error.message}`);
}
}
console.log(`Saved ${savedCount} curl commands to ${outputDir}/`);
}
/**
* Main execution function
* @returns {Promise<void>}
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(`
🔄 Postman to Curl Converter
Usage:
node pm2curl.js <collection-file> [output-directory]
Examples:
node pm2curl.js collection.json
node pm2curl.js collection.json ./curl-commands
node pm2curl.js example.postman_collection.json
Options:
collection-file Path to the Postman collection JSON file
output-directory Output directory for curl files (defaults to collection-name-curl)
`);
process.exit(1);
}
const collectionFile = args[0];
const outputDir = args[1] || collectionFile.replace(/\.json$/, '').replace(/.*\//, '') + '-curl';
try {
const collection = await loadCollection(collectionFile);
await saveRequestsToFiles(collection, outputDir);
} catch (error) {
console.error(`Error: ${error instanceof Error ? error.message : error}`);
process.exit(1);
}
}
// Run the main function if this script is executed directly
if (process.argv[1] === new URL(import.meta.url).pathname) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment