Files
sofarr/scripts/simple-raml-converter.js
T
gronod 37bed1cd4e
Docs Check / Markdown lint (push) Successful in 1m6s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m20s
Build and Push Docker Image / build (push) Successful in 1m35s
CI / Swagger Validation & Coverage (push) Failing after 2m0s
CI / Security audit (push) Successful in 2m6s
Docs Check / Mermaid diagram parse check (push) Successful in 2m20s
CI / Tests & coverage (push) Failing after 2m30s
feat: add automated RAML 1.0 package generation to CI/CD pipeline
- Add RAML generation scripts (generate-openapi, downgrade-openapi, simple-raml-converter, package-raml)
- Add /api/swagger.json endpoint to server/app.js
- Add minimal .spectral.yml ruleset for OpenAPI linting
- Add npm scripts for OpenAPI/RAML generation and packaging
- Extend CI swagger job with RAML generation steps
- Upload raml-package artifact with 14-day retention
- Update CHANGELOG.md for v1.7.1
2026-05-21 14:26:21 +01:00

184 lines
5.8 KiB
JavaScript

// Copyright (c) 2026 Gordon Bolton. MIT License.
/**
* Simple OpenAPI 3.0 to RAML 1.0 converter.
* This is a basic converter that handles the essential parts of the sofarr API.
* For a production system, you'd want a more sophisticated converter.
*/
const fs = require('fs');
const path = require('path');
const INPUT_FILE = path.join(process.cwd(), 'dist/openapi-30.json');
const OUTPUT_FILE = path.join(process.cwd(), 'dist/api.raml');
function convertToRaml(spec) {
const lines = [];
// RAML header
lines.push('#%RAML 1.0');
lines.push('');
// Title and version
lines.push(`title: ${spec.info.title}`);
if (spec.info.version) {
lines.push(`version: ${spec.info.version}`);
}
if (spec.info.description) {
lines.push(`description: |`);
spec.info.description.split('\n').forEach(line => {
lines.push(` ${line}`);
});
}
lines.push('');
// Base URI
if (spec.servers && spec.servers.length > 0) {
lines.push(`baseUri: ${spec.servers[0].url}`);
lines.push('');
}
// Security Schemes
if (spec.components && spec.components.securitySchemes) {
lines.push('securitySchemes:');
for (const [name, scheme] of Object.entries(spec.components.securitySchemes)) {
lines.push(` ${name}:`);
if (scheme.type === 'apiKey') {
lines.push(` type: Api Key`);
lines.push(` describedBy:`);
lines.push(` headers:`);
lines.push(` Authorization:`);
lines.push(` description: API key for authentication`);
lines.push(` type: string`);
} else if (scheme.type === 'http' && scheme.scheme === 'bearer') {
lines.push(` type: OAuth 2.0`);
lines.push(` settings:`);
lines.push(` authorizationUri: ${scheme.bearerFormat || 'Bearer'}`);
}
}
lines.push('');
}
// Types (schemas)
if (spec.components && spec.components.schemas) {
lines.push('types:');
for (const [name, schema] of Object.entries(spec.components.schemas)) {
lines.push(` ${name}:`);
if (schema.type === 'object') {
lines.push(` type: object`);
if (schema.properties) {
lines.push(` properties:`);
for (const [propName, prop] of Object.entries(schema.properties)) {
lines.push(` ${propName}:`);
lines.push(` type: ${mapJsonTypeToRaml(prop.type || 'string')}`);
if (prop.description) {
lines.push(` description: ${prop.description}`);
}
}
}
} else {
lines.push(` type: ${mapJsonTypeToRaml(schema.type || 'string')}`);
}
}
lines.push('');
}
// Paths
if (spec.paths) {
for (const [path, pathItem] of Object.entries(spec.paths)) {
lines.push(`/${path.replace(/^\//, '')}:`);
// Methods
for (const [method, operation] of Object.entries(pathItem)) {
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
lines.push(` ${method}:`);
if (operation.summary) {
lines.push(` displayName: ${operation.summary}`);
}
if (operation.description) {
lines.push(` description: |`);
operation.description.split('\n').forEach(line => {
lines.push(` ${line}`);
});
}
// Query parameters
if (operation.parameters) {
const queryParams = operation.parameters.filter(p => p.in === 'query');
if (queryParams.length > 0) {
lines.push(` queryParameters:`);
queryParams.forEach(param => {
lines.push(` ${param.name}:`);
lines.push(` type: ${mapJsonTypeToRaml(param.schema?.type || 'string')}`);
lines.push(` required: ${param.required || false}`);
if (param.description) {
lines.push(` description: ${param.description}`);
}
});
}
}
// Responses
if (operation.responses) {
lines.push(` responses:`);
for (const [code, response] of Object.entries(operation.responses)) {
lines.push(` ${code}:`);
if (response.description) {
lines.push(` description: ${response.description}`);
}
if (response.content && response.content['application/json']) {
const schema = response.content['application/json'].schema;
if (schema && schema.$ref) {
const refName = schema.$ref.replace('#/components/schemas/', '');
lines.push(` body:`);
lines.push(` application/json:`);
lines.push(` type: ${refName}`);
}
}
}
}
}
}
lines.push('');
}
}
return lines.join('\n');
}
function mapJsonTypeToRaml(jsonType) {
const typeMap = {
'string': 'string',
'integer': 'integer',
'number': 'number',
'boolean': 'boolean',
'array': 'array',
'object': 'object'
};
return typeMap[jsonType] || 'string';
}
async function main() {
if (!fs.existsSync(INPUT_FILE)) {
throw new Error(`Input file not found: ${INPUT_FILE}`);
}
console.log(`Reading OpenAPI 3.0 spec from ${INPUT_FILE}`);
const spec = JSON.parse(fs.readFileSync(INPUT_FILE, 'utf-8'));
console.log('Converting to RAML 1.0...');
const ramlContent = convertToRaml(spec);
fs.writeFileSync(OUTPUT_FILE, ramlContent);
console.log(`✓ RAML spec written to ${OUTPUT_FILE}`);
console.log('RAML conversion complete');
}
main()
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error('Failed to convert to RAML:', error);
process.exit(1);
});