// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Creates a versioned tar.gz archive containing the RAML spec, * original OpenAPI spec, version metadata, and README. */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const archiver = require('archiver'); const DIST_DIR = path.join(process.cwd(), 'dist'); const RAML_FILE = path.join(DIST_DIR, 'api.raml'); const OPENAPI_FILE = path.join(DIST_DIR, 'openapi-merged.json'); function getVersion() { try { // Try to get the exact tag if we're on one const tag = execSync('git describe --tags --exact-match 2>/dev/null', { encoding: 'utf-8' }).trim(); if (tag) return tag; } catch (e) { // Not on a tag, fall back to SHA } try { // Get short commit SHA const sha = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim(); return sha; } catch (e) { // Not in a git repo, use timestamp return `dev-${Date.now()}`; } } function getCommitSha() { try { return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim(); } catch (e) { return 'unknown'; } } function createVersionJson(version, commitSha) { return { version, commit: commitSha, generatedAt: new Date().toISOString(), tool: 'oas3-to-raml', openapiVersion: '3.1.0', ramlVersion: '1.0' }; } function createReadme(version, commitSha) { return `# sofarr RAML 1.0 Specification ## Origin This RAML specification was automatically generated from the sofarr OpenAPI 3.1.0 specification. - **Version:** ${version} - **Commit:** ${commitSha} - **Generated At:** ${new Date().toISOString()} - **Conversion Tool:** oas3-to-raml (npx) ## Contents - \`api.raml\` - The RAML 1.0 specification - \`openapi-merged.json\` - Original merged OpenAPI 3.1.0 spec (for reference) - \`version.json\` - Metadata about this generation ## Known Limitations This RAML spec was converted from OpenAPI 3.1.0. Some OpenAPI 3.1 features may not translate perfectly to RAML 1.0: - Cookie-based authentication (CookieAuth) may require manual mapping to RAML security schemes - Advanced schema features (e.g., certain keywords, complex polymorphism) may be approximated or dropped - Webhook-specific features may not be fully represented For the most accurate API documentation, refer to the live Swagger UI at \`/api/swagger\` or the original OpenAPI spec included in this archive. ## Verification Steps 1. Validate the RAML spec: \`\`\`bash npx raml-1-parser validate api.raml \`\`\` 2. Compare endpoints with the live Swagger UI at \`/api/swagger\` 3. Test in a RAML-aware tool (e.g., API Workbench, MuleSoft Anypoint) ## Quick Start To use this RAML spec: 1. Extract the archive 2. Open \`api.raml\` in your preferred RAML tool 3. For development, import it into API Workbench or similar tools ## Source This artifact was generated from the sofarr project: https://git.i3omb.com/Gandalf/sofarr Generated from CI run on commit ${commitSha}. `; } async function packageRaml() { const version = getVersion(); const commitSha = getCommitSha(); const archiveName = `raml-${version}`; const archivePath = path.join(DIST_DIR, `${archiveName}.tar.gz`); const stagingDir = path.join(DIST_DIR, archiveName); console.log(`Packaging RAML for version: ${version}`); console.log(`Commit: ${commitSha}`); // Check that required files exist if (!fs.existsSync(RAML_FILE)) { throw new Error(`RAML file not found: ${RAML_FILE}`); } if (!fs.existsSync(OPENAPI_FILE)) { throw new Error(`OpenAPI file not found: ${OPENAPI_FILE}`); } // Create staging directory if (fs.existsSync(stagingDir)) { fs.rmSync(stagingDir, { recursive: true, force: true }); } fs.mkdirSync(stagingDir, { recursive: true }); // Copy files to staging directory fs.copyFileSync(RAML_FILE, path.join(stagingDir, 'api.raml')); fs.copyFileSync(OPENAPI_FILE, path.join(stagingDir, 'openapi-merged.json')); // Create version.json const versionJson = createVersionJson(version, commitSha); fs.writeFileSync(path.join(stagingDir, 'version.json'), JSON.stringify(versionJson, null, 2)); // Create README.md const readme = createReadme(version, commitSha); fs.writeFileSync(path.join(stagingDir, 'README.md'), readme); // Create tar.gz archive console.log(`Creating archive: ${archivePath}`); const output = fs.createWriteStream(archivePath); const archive = archiver('tar', { gzip: true }); return new Promise((resolve, reject) => { output.on('close', () => { console.log(`✓ Archive created: ${archivePath}`); console.log(` Size: ${archive.pointer()} bytes`); resolve(); }); archive.on('error', (err) => { reject(err); }); archive.pipe(output); archive.directory(stagingDir, false); archive.finalize(); }).then(() => { // Clean up staging directory fs.rmSync(stagingDir, { recursive: true, force: true }); }); } // Run if executed directly if (require.main === module) { packageRaml() .then(() => { console.log('RAML packaging complete'); process.exit(0); }) .catch((error) => { console.error('Failed to package RAML:', error); process.exit(1); }); } module.exports = { packageRaml };