diff --git a/tools/spectral/ipa/scripts/filter-ipa-violations.js b/tools/spectral/ipa/scripts/filter-ipa-violations.js index 28ab003cb2..b31a7e010b 100644 --- a/tools/spectral/ipa/scripts/filter-ipa-violations.js +++ b/tools/spectral/ipa/scripts/filter-ipa-violations.js @@ -1,27 +1,47 @@ import fs from 'node:fs/promises'; import { execSync } from 'child_process'; import path from 'path'; +import http from 'http'; +import https from 'https'; async function filterIpaViolations() { try { // Check if rule ID is provided const ruleId = process.argv[2]; if (!ruleId) { - console.error('Usage: node filter-ipa-violations.js '); + console.error('Usage: node filter-ipa-violations.js [remote-openapi-url]'); console.error('Example: node filter-ipa-violations.js xgen-IPA-102-collection-identifier-camelCase'); + console.error( + 'Example with remote file: node filter-ipa-violations.js xgen-IPA-102-collection-identifier-camelCase https://raw.githubusercontent.com/mongodb/openapi/refs/heads/dev/openapi/.raw/v2.yaml' + ); process.exit(1); } + // Check if a remote OpenAPI file URL is provided + const remoteUrl = process.argv[3]; const outputFile = path.join(process.cwd(), `${ruleId}-violations.md`); console.log(`Filtering violations for rule ID: ${ruleId}`); console.log('Running IPA validation...'); + // If remote URL provided, download it to a temp file + let openapiFilePath = './openapi/.raw/v2.yaml'; // Default local file + let tempFile = null; + + if (remoteUrl) { + console.log(`Using remote OpenAPI file: ${remoteUrl}`); + tempFile = path.join(process.cwd(), 'temp_openapi_file.yaml'); + await downloadFile(remoteUrl, tempFile); + openapiFilePath = tempFile; + } else { + console.log('Using local OpenAPI file'); + } + let validationOutput; try { // Run IPA validation and get output as JSON execSync( - 'spectral lint --format=json -o results.json ./openapi/.raw/v2.yaml --ruleset=./tools/spectral/ipa/ipa-spectral.yaml', + `spectral lint --format=json -o results.json ${openapiFilePath} --ruleset=./tools/spectral/ipa/ipa-spectral.yaml`, { encoding: 'utf-8', timeout: 4000, @@ -30,6 +50,15 @@ async function filterIpaViolations() { ); } catch (error) { console.error('Error (expected):', error.message); + } finally { + // Clean up temp file if it exists + if (tempFile) { + try { + await fs.unlink(tempFile); + } catch (err) { + console.error('Error removing temporary file:', err.message); + } + } } // Read the JSON output @@ -59,12 +88,11 @@ async function filterIpaViolations() { // Generate markdown content let markdownContent = `# ${ruleId} Violations Checklist\n\n`; markdownContent += `Generated on: ${new Date().toLocaleString()}\n\n`; - Object.keys(groupedBySource).forEach((source) => { const violations = groupedBySource[source]; violations.forEach((violation) => { - markdownContent += `## ${violation.source}\n\n`; + markdownContent += `## ${violation.message}\n\n`; markdownContent += `Path: \`${violation.path.join('/')}\`\n\n`; markdownContent += `- [ ] Fixed\n\n`; }); @@ -82,4 +110,34 @@ async function filterIpaViolations() { } } +// Function to download a file from a URL +function downloadFile(url, outputPath) { + return new Promise((resolve, reject) => { + const client = url.startsWith('https') ? https : http; + + console.log(`Downloading OpenAPI file from ${url}...`); + + const request = client.get(url, (response) => { + if (response.statusCode < 200 || response.statusCode >= 300) { + return reject(new Error(`Failed to download file, status code: ${response.statusCode}`)); + } + + const file = fs.open(outputPath, 'w').then((fileHandle) => fileHandle.createWriteStream()); + file + .then((fileStream) => { + response.pipe(fileStream); + response.on('end', () => { + console.log('Download complete'); + resolve(); + }); + }) + .catch(reject); + }); + + request.on('error', (err) => { + reject(err); + }); + }); +} + filterIpaViolations();