Files
sofarr/scripts/package-raml.js
gronod 1a4ff73067
Build and Push Docker Image / build (push) Successful in 1m27s
CI / Security audit (push) Successful in 1m43s
CI / Swagger Validation & Coverage (push) Failing after 1m56s
CI / Tests & coverage (push) Failing after 1m56s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 53s
feat(ci): add RAML 1.0 package generation pipeline
- Add generate:openapi, generate:raml, package:raml scripts to package.json
- Add archiver dependency for creating tar.gz archives
- Create scripts/generate-openapi.js to fetch merged OpenAPI spec from running server
- Create scripts/package-raml.js to build versioned RAML tar.gz archive
- Create .spectral.yml with minimal OpenAPI linting rules
- Add /api/swagger.json endpoint to server/app.js for serving merged spec
- Extend swagger job in ci.yml with RAML generation steps
- Upload raml-package artifact to CI with 14-day retention
2026-05-21 14:04:26 +01:00

185 lines
5.2 KiB
JavaScript

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