UNPKG

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
"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; }