#!/usr/bin/env node /** * publish-openapi.js * * Uploads each API's openapi.yaml to the RapidAPI Platform API. * Environment variables required per API (skips silently if not set): * RAPIDAPI_KEY — RapidAPI Platform API bearer token * RAPIDAPI_ZIP_API_ID — API ID for ZIP Enrichment on RapidAPI * RAPIDAPI_ZIP_VERSION_ID — Version ID for ZIP Enrichment * RAPIDAPI_HOLIDAYS_API_ID * RAPIDAPI_HOLIDAYS_VERSION_ID * RAPIDAPI_AQI_API_ID * RAPIDAPI_AQI_VERSION_ID * RAPIDAPI_VIN_API_ID * RAPIDAPI_VIN_VERSION_ID * * Usage: node scripts/publish-openapi.js */ const fs = require('fs'); const path = require('path'); const https = require('https'); const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY || process.env.RAPIDAPI_PLATFORM_KEY; if (!RAPIDAPI_KEY) { console.log('⚠️ RAPIDAPI_KEY / RAPIDAPI_PLATFORM_KEY not set — skipping all spec uploads.'); process.exit(0); } const APIS = [ { name: 'zip-enrichment', specPath: path.join(__dirname, '..', 'apis', 'zip-enrichment', 'openapi.yaml'), apiId: process.env.RAPIDAPI_ZIP_API_ID, versionId: process.env.RAPIDAPI_ZIP_VERSION_ID, }, { name: 'holidays', specPath: path.join(__dirname, '..', 'apis', 'holidays', 'openapi.yaml'), apiId: process.env.RAPIDAPI_HOLIDAYS_API_ID, versionId: process.env.RAPIDAPI_HOLIDAYS_VERSION_ID, }, { name: 'air-quality', specPath: path.join(__dirname, '..', 'apis', 'air-quality', 'openapi.yaml'), apiId: process.env.RAPIDAPI_AQI_API_ID, versionId: process.env.RAPIDAPI_AQI_VERSION_ID, }, { name: 'vin-decoder', specPath: path.join(__dirname, '..', 'apis', 'vin-decoder', 'openapi.yaml'), apiId: process.env.RAPIDAPI_VIN_API_ID, versionId: process.env.RAPIDAPI_VIN_VERSION_ID, }, ]; /** * Build a multipart/form-data body from a file buffer. * Returns { body: Buffer, boundary: string } */ function buildMultipart(fieldName, filename, fileBuffer, contentType = 'application/yaml') { const boundary = '----FormBoundary' + Math.random().toString(36).slice(2); const CRLF = '\r\n'; const parts = [ Buffer.from( `--${boundary}${CRLF}` + `Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"${CRLF}` + `Content-Type: ${contentType}${CRLF}${CRLF}` ), fileBuffer, Buffer.from(`${CRLF}--${boundary}--${CRLF}`), ]; return { body: Buffer.concat(parts), boundary }; } /** * Upload a spec file to RapidAPI Platform API. * Returns a promise that resolves with the response status code. */ function uploadSpec(api) { return new Promise((resolve, reject) => { if (!api.apiId || !api.versionId) { console.log(`⏭️ Skipping ${api.name}: API ID or Version ID not configured.`); return resolve(null); } if (!fs.existsSync(api.specPath)) { console.log(`⏭️ Skipping ${api.name}: spec file not found at ${api.specPath}`); return resolve(null); } const fileBuffer = fs.readFileSync(api.specPath); const { body, boundary } = buildMultipart('spec', 'openapi.yaml', fileBuffer); const options = { hostname: 'platformapi1.p.rapidapi.com', path: `/v1/apis/${api.apiId}/versions/${api.versionId}`, method: 'PUT', headers: { 'Authorization': `Bearer ${RAPIDAPI_KEY}`, 'X-RapidAPI-Key': RAPIDAPI_KEY, 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': body.length, }, }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { console.log(`✓ Published ${api.name} spec to RapidAPI (HTTP ${res.statusCode})`); resolve(res.statusCode); } else { reject(new Error(`Failed to publish ${api.name}: HTTP ${res.statusCode} — ${data}`)); } }); }); req.on('error', reject); req.write(body); req.end(); }); } async function main() { let hasError = false; for (const api of APIS) { try { await uploadSpec(api); } catch (err) { console.error(`✗ ${err.message}`); hasError = true; } } if (hasError) { process.exit(1); } } main();