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
- 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
185 lines
5.2 KiB
JavaScript
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 };
|