wrekenfile-converter
Version:
Convert OpenAPI and Postman specs to Wrekenfile format with mini-chunking for vector DB storage
605 lines (604 loc) • 23.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateWrekenfile = generateWrekenfile;
exports.extractStructs = extractStructs;
exports.extractOperations = extractOperations;
exports.mapType = mapType;
exports.parseJsonExample = parseJsonExample;
exports.extractFieldsFromObject = extractFieldsFromObject;
exports.loadEnvironmentFile = loadEnvironmentFile;
exports.extractCollectionVariables = extractCollectionVariables;
exports.resolveVariables = resolveVariables;
const fs = __importStar(require("fs"));
const js_yaml_1 = require("js-yaml");
function mapType(value) {
if (typeof value === 'string') {
// Check for common patterns
if (/^\d{4}-\d{2}-\d{2}/.test(value))
return 'DATE';
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value))
return 'UUID';
if (/^\d+$/.test(value))
return 'INT';
if (/^\d+\.\d+$/.test(value))
return 'FLOAT';
if (value === 'true' || value === 'false')
return 'BOOL';
return 'STRING';
}
if (typeof value === 'number') {
return Number.isInteger(value) ? 'INT' : 'FLOAT';
}
if (typeof value === 'boolean')
return 'BOOL';
if (Array.isArray(value))
return 'ANY'; // Arrays will be handled specially
if (value === null || value === undefined)
return 'ANY';
return 'ANY';
}
function generateDesc(item, method, path) {
if (item.description) {
// Remove HTML tags and clean up description
return item.description.replace(/<[^>]*>/g, '').replace(/\n+/g, ' ').trim();
}
return `${method.toUpperCase()} ${path}`;
}
function generateStructName(itemName, method, path, suffix) {
const cleanName = itemName.replace(/[^a-zA-Z0-9]/g, '');
const cleanPath = path.replace(/[\/{}]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
return `${method.toLowerCase()}-${cleanPath}-${suffix}`;
}
function parseJsonExample(jsonStr) {
try {
return JSON.parse(jsonStr);
}
catch (_a) {
return null;
}
}
function extractFieldsFromObject(obj, depth = 0, prefix = '') {
if (depth > 3)
return [];
if (!obj)
return [];
// If the root object is an array, extract fields from the first object
if (Array.isArray(obj) && obj.length > 0 && typeof obj[0] === 'object' && obj[0] !== null) {
return extractFieldsFromObject(obj[0], depth + 1, prefix);
}
if (Array.isArray(obj))
return [];
if (typeof obj !== 'object')
return [];
const fields = [];
const keyCount = {};
for (const [key, value] of Object.entries(obj)) {
let type = 'ANY';
let required = 'OPTIONAL';
// Handle duplicate keys
let fieldName = key;
if (keyCount[key] === undefined) {
keyCount[key] = 1;
}
else {
keyCount[key] += 1;
fieldName = `${key} ${keyCount[key]}`;
}
if (Array.isArray(value)) {
if (value.length > 0) {
const firstItem = value[0];
if (typeof firstItem === 'object' && firstItem !== null) {
type = `[]STRUCT(${prefix}${fieldName}Item)`;
}
else {
type = `[]${mapType(firstItem)}`;
}
}
else {
type = '[]ANY';
}
}
else if (typeof value === 'object' && value !== null) {
type = `STRUCT(${prefix}${fieldName})`;
}
else {
type = mapType(value);
}
fields.push({
name: fieldName,
type,
required,
});
}
return fields;
}
function loadEnvironmentFile(envPath) {
try {
const envData = fs.readFileSync(envPath, 'utf8');
const env = JSON.parse(envData);
const variables = {};
if (env.values) {
for (const variable of env.values) {
if (variable.key && variable.value) {
variables[variable.key] = variable.value;
}
}
}
return variables;
}
catch (error) {
console.warn(`⚠️ Warning: Could not load environment file ${envPath}:`, error);
return {};
}
}
function extractCollectionVariables(collection) {
const variables = {};
// Extract collection-level variables
if (collection.variable) {
for (const variable of collection.variable) {
if (variable.key && variable.value) {
variables[variable.key] = variable.value;
}
}
}
return variables;
}
function resolveVariables(value, variables) {
if (typeof value !== 'string')
return value;
// Replace {{variable}} patterns with actual values
return value.replace(/\{\{([^}]+)\}\}/g, (match, varName) => {
return variables[varName] || match;
});
}
function extractStructs(collection, variables) {
const structs = {};
const structNameCount = {};
function getUniqueStructName(name) {
if (structs[name] === undefined) {
structNameCount[name] = 1;
return name;
}
else {
structNameCount[name] = (structNameCount[name] || 1) + 1;
return `${name} ${structNameCount[name]}`;
}
}
function processItem(item) {
var _a;
if (item.request) {
const method = item.request.method || 'GET';
const url = item.request.url;
const path = extractPathFromUrl(url, variables);
const itemName = item.name || 'unknown';
// Extract request body structs
if (((_a = item.request.body) === null || _a === void 0 ? void 0 : _a.mode) === 'raw' && item.request.body.raw) {
const bodyData = parseJsonExample(item.request.body.raw);
if (bodyData) {
let requestStructName = generateStructName(itemName, method, path, 'Request');
requestStructName = getUniqueStructName(requestStructName);
const fields = extractFieldsFromObject(bodyData, 0, requestStructName);
structs[requestStructName] = fields.length > 0 ? fields : [];
extractNestedStructs(bodyData, structs, requestStructName);
}
}
// Extract response structs from examples
if (item.response) {
for (const response of item.response) {
let responseStructName = generateStructName(itemName, method, path, `Response${response.code || '200'}`);
responseStructName = getUniqueStructName(responseStructName);
if (response.body) {
const responseData = parseJsonExample(response.body);
if (responseData) {
const fields = extractFieldsFromObject(responseData, 0, responseStructName);
structs[responseStructName] = fields.length > 0 ? fields : [];
extractNestedStructs(responseData, structs, responseStructName);
}
else {
structs[responseStructName] = [];
}
}
else {
structs[responseStructName] = [];
}
}
}
}
if (item.item) {
for (const subItem of item.item) {
processItem(subItem);
}
}
}
for (const item of collection.item) {
processItem(item);
}
return structs;
}
function extractNestedStructs(obj, structs, prefix = '') {
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
return;
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value) && value.length > 0) {
const firstItem = value[0];
if (typeof firstItem === 'object' && firstItem !== null) {
const structName = `${prefix}${key}Item`;
const fields = extractFieldsFromObject(firstItem, 0, structName);
// Always add the struct, even if empty
structs[structName] = fields.length > 0 ? fields : [];
extractNestedStructs(firstItem, structs, `${prefix}${key}Item`);
}
}
else if (typeof value === 'object' && value !== null) {
const structName = `${prefix}${key}`;
const fields = extractFieldsFromObject(value, 0, structName);
// Always add the struct, even if empty
structs[structName] = fields.length > 0 ? fields : [];
extractNestedStructs(value, structs, `${prefix}${key}`);
}
}
}
function extractPathFromUrl(url, variables) {
if (url === null || url === void 0 ? void 0 : url.raw) {
// Remove base URL and protocol
let path = url.raw;
path = resolveVariables(path, variables); // Resolve variables first
path = path.replace(/^https?:\/\/[^\/]+/, ''); // Remove protocol and host
path = path.replace(/\{\{.*?\}\}/g, ''); // Remove any remaining Postman variables
path = path.replace(/\/+/g, '/'); // Normalize slashes
path = path.replace(/^\/|\/$/g, ''); // Remove leading/trailing slashes
return path;
}
if (url === null || url === void 0 ? void 0 : url.path) {
const resolvedPath = url.path.map((segment) => resolveVariables(segment, variables));
return resolvedPath.join('/');
}
return '';
}
function getContentTypeAndBodyType(request) {
const headers = request.header || [];
const contentTypeHeader = headers.find((h) => { var _a; return ((_a = h.key) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'content-type'; });
let contentType = 'application/json';
if (contentTypeHeader) {
contentType = contentTypeHeader.value || 'application/json';
}
let bodyType = 'RAW';
if (contentType === 'multipart/form-data') {
bodyType = 'FORM';
}
else if (contentType === 'application/x-www-form-urlencoded') {
bodyType = 'FORM';
}
else if (contentType === 'application/json') {
bodyType = 'JSON';
}
return { contentType, bodyType };
}
function getHeadersForOperation(request, variables) {
const { contentType } = getContentTypeAndBodyType(request);
const headers = [];
// Add Content-Type header
headers.push({ 'Content-Type': contentType });
// Add authentication headers
const authHeaders = request.header || [];
for (const header of authHeaders) {
if (header.key && header.value) {
const key = header.key.toLowerCase();
if (key === 'x-api-key' || key === 'authorization' || key === 'x-signature') {
let value = resolveVariables(header.value, variables);
if (key === 'x-api-key')
value = 'api_key';
else if (key === 'authorization')
value = 'bearer_token';
else if (key === 'x-signature')
value = 'signature';
headers.push({ [header.key]: value });
}
}
}
return headers;
}
function extractParameters(request, variables) {
const inputParams = [];
// Extract URL parameters
const url = request.url;
if (url === null || url === void 0 ? void 0 : url.variable) {
for (const variable of url.variable) {
inputParams.push({
name: variable.key,
type: 'STRING',
required: variable.disabled ? 'FALSE' : 'TRUE',
location: 'PATH',
});
}
}
// Extract query parameters
if (url === null || url === void 0 ? void 0 : url.query) {
for (const query of url.query) {
inputParams.push({
name: query.key,
type: 'STRING',
required: query.disabled ? 'FALSE' : 'TRUE',
location: 'QUERY',
});
}
}
return inputParams;
}
function extractRequestBody(request, itemName, method, path) {
var _a;
const inputParams = [];
if (((_a = request.body) === null || _a === void 0 ? void 0 : _a.mode) === 'raw' && request.body.raw) {
const bodyData = parseJsonExample(request.body.raw);
if (bodyData) {
const requestStructName = generateStructName(itemName, method, path, 'Request');
inputParams.push({
name: 'body',
type: `STRUCT(${requestStructName})`,
required: 'TRUE',
});
}
}
return inputParams;
}
function extractResponses(item, itemName, method, path) {
var _a;
const returns = [];
const seenCodes = new Set();
if (item.response) {
for (const response of item.response) {
const code = ((_a = response.code) === null || _a === void 0 ? void 0 : _a.toString()) || '200';
// Avoid duplicate response codes
if (seenCodes.has(code))
continue;
seenCodes.add(code);
let returnType = 'ANY';
if (response.body) {
const responseData = parseJsonExample(response.body);
if (responseData) {
const responseStructName = generateStructName(itemName, method, path, `Response${code}`);
returnType = `STRUCT(${responseStructName})`;
}
}
returns.push({
RETURNTYPE: returnType,
RETURNNAME: 'response',
CODE: code,
});
}
}
else {
// Default response if no examples provided
returns.push({
RETURNTYPE: 'ANY',
RETURNNAME: 'response',
CODE: '200',
});
}
return returns;
}
function extractOperations(collection, variables) {
const operations = [];
const operationNameCount = {};
function getUniqueOperationName(name) {
if (operationNameCount[name] === undefined) {
operationNameCount[name] = 1;
return name;
}
else {
operationNameCount[name] += 1;
return `${name} ${operationNameCount[name]}`;
}
}
function processItem(item) {
if (item.request) {
const method = item.request.method || 'GET';
const url = item.request.url;
const path = extractPathFromUrl(url, variables);
const itemName = item.name || 'unknown';
// Generate operation ID
let operationId = itemName.toLowerCase().replace(/[^a-z0-9]/g, '-');
operationId = getUniqueOperationName(operationId);
const { contentType, bodyType } = getContentTypeAndBodyType(item.request);
const headers = getHeadersForOperation(item.request, variables);
const inputs = extractParameters(item.request, variables);
const bodyInputs = extractRequestBody(item.request, itemName, method, path);
const returns = extractResponses(item, itemName, method, path);
const allInputs = [...inputs];
if (bodyInputs.length > 0) {
allInputs.push(...bodyInputs);
}
operations.push({
name: operationId,
SUMMARY: itemName || '',
DESCRIPTION: item.description ? item.description.replace(/<[^>]*>/g, '').replace(/\n+/g, ' ').trim() : '',
DESC: generateDesc(item, method, path),
ENDPOINT: `"/${path}"`,
VISIBILITY: 'PUBLIC',
HTTP: {
METHOD: method.toUpperCase(),
HEADERS: headers,
BODYTYPE: bodyType,
},
INPUTS: allInputs,
RETURNS: returns,
});
}
if (item.item) {
for (const subItem of item.item) {
processItem(subItem);
}
}
}
for (const item of collection.item) {
processItem(item);
}
return operations;
}
function cleanYaml(yamlString) {
// Remove tabs, non-breaking spaces, and non-printable chars except standard whitespace and newlines
return yamlString
.replace(/\t/g, ' ')
.replace(/[\u00A0]/g, ' ')
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
.replace(/[ \t]+$/gm, '')
.replace(/\r\n/g, '\n');
}
function checkYamlForHiddenChars(yamlString) {
const lines = yamlString.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/\t/.test(line)) {
throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`);
}
if (/\u00A0/.test(line)) {
throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`);
}
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) {
throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`);
}
}
}
function validateYaml(yamlString) {
try {
(0, js_yaml_1.load)(yamlString);
}
catch (e) {
throw new Error('Generated YAML is invalid: ' + e.message);
}
}
function generateWrekenfile(collection, variables) {
var _a;
if (!collection || typeof collection !== 'object') {
throw new Error("Argument 'collection' is required and must be an object");
}
if (!variables || typeof variables !== 'object') {
throw new Error("Argument 'variables' is required and must be an object");
}
const structs = extractStructs(collection, variables);
const operations = extractOperations(collection, variables);
let wrekenfile = `VERSION: '1.2'\n`;
wrekenfile += `INIT:\n`;
wrekenfile += ` DEFAULTS:\n`;
if (Object.keys(variables).length === 0) {
wrekenfile += ` - w_base_url: https://api.default.com\n`;
}
else {
for (const [key, value] of Object.entries(variables)) {
const sensitiveKeys = ['api_key', 'api-key', 'x-api-key', 'signature', 'x-signature', 'authorization', 'token', 'password', 'secret'];
const isSensitive = sensitiveKeys.some(sensitiveKey => key.toLowerCase().includes(sensitiveKey));
if (isSensitive) {
wrekenfile += ` - ${key}: "{{${key}}}"\n`;
}
else {
wrekenfile += ` - ${key}: "${value}"\n`;
}
}
}
wrekenfile += `INTERFACES:\n`;
for (const operation of operations) {
wrekenfile += ` ${operation.name}:\n`;
// Quote SUMMARY and DESCRIPTION if they contain special characters
const summary = operation.SUMMARY.includes(':') || operation.SUMMARY.includes('"') ? `"${operation.SUMMARY.replace(/"/g, '\\"')}"` : operation.SUMMARY;
const description = operation.DESCRIPTION.includes(':') || operation.DESCRIPTION.includes('"') ? `"${operation.DESCRIPTION.replace(/"/g, '\\"')}"` : operation.DESCRIPTION;
const desc = operation.DESC.includes(':') || operation.DESC.includes('"') ? `"${operation.DESC.replace(/"/g, '\\"')}"` : operation.DESC;
wrekenfile += ` SUMMARY: ${summary}\n`;
wrekenfile += ` DESCRIPTION: ${description}\n`;
wrekenfile += ` DESC: ${desc}\n`;
wrekenfile += ` ENDPOINT: ${operation.ENDPOINT}\n`;
wrekenfile += ` VISIBILITY: ${operation.VISIBILITY}\n`;
wrekenfile += ` HTTP:\n`;
wrekenfile += ` METHOD: ${operation.HTTP.METHOD}\n`;
wrekenfile += ` HEADERS:\n`;
for (const header of operation.HTTP.HEADERS) {
for (const [key, value] of Object.entries(header)) {
wrekenfile += ` - ${key}: ${value}\n`;
}
}
wrekenfile += ` BODYTYPE: ${operation.HTTP.BODYTYPE}\n`;
// INPUTS
if (operation.INPUTS.length === 0) {
wrekenfile += ` INPUTS: []\n`;
}
else {
wrekenfile += ` INPUTS:\n`;
for (const input of operation.INPUTS) {
wrekenfile += ` - name: ${input.name}\n`;
wrekenfile += ` type: ${input.type}\n`;
wrekenfile += ` required: '${input.required}'\n`;
if (input.location) {
wrekenfile += ` location: ${input.location}\n`;
}
}
}
// RETURNS
wrekenfile += ` RETURNS:\n`;
for (const ret of operation.RETURNS) {
wrekenfile += ` - RETURNTYPE: ${ret.RETURNTYPE}\n`;
wrekenfile += ` RETURNNAME: ${ret.RETURNNAME}\n`;
wrekenfile += ` CODE: '${ret.CODE}'\n`;
}
}
wrekenfile += `STRUCTS:\n`;
for (const [structName, fields] of Object.entries(structs)) {
if (fields.length === 0) {
wrekenfile += ` ${structName}: []\n`;
}
else {
wrekenfile += ` ${structName}:\n`;
for (const field of fields) {
wrekenfile += ' - name: ' + field.name + '\n';
// Quote type if it contains non-alphanumeric characters (except underscore)
let typeValue = field.type;
if (/[^a-zA-Z0-9_]/.test(typeValue)) {
typeValue = '"' + typeValue.replace(/"/g, '\"') + '"';
}
wrekenfile += ' type: ' + typeValue + '\n';
wrekenfile += " required: '" + field.required + "'\n";
}
}
}
// Debug print: show first 20 lines of STRUCTS block
const structsBlock = (_a = wrekenfile.split('STRUCTS:')[1]) === null || _a === void 0 ? void 0 : _a.split('\n').slice(0, 20).join('\n');
if (structsBlock) {
console.log('--- STRUCTS block preview ---');
console.log(structsBlock);
}
wrekenfile = cleanYaml(wrekenfile);
checkYamlForHiddenChars(wrekenfile);
validateYaml(wrekenfile);
return wrekenfile;
}