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
109 lines
2.9 KiB
JavaScript
109 lines
2.9 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* Generates the merged OpenAPI spec by bootstrapping the Express app
|
|
* and fetching the spec from /api/swagger.json.
|
|
*
|
|
* This ensures the generated spec matches exactly what users see in production.
|
|
*/
|
|
|
|
const { createApp } = require('../server/app.js');
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const PORT = 34567; // Use a different port to avoid conflicts
|
|
const OUTPUT_DIR = path.join(process.cwd(), 'dist');
|
|
const OUTPUT_FILE = path.join(OUTPUT_DIR, 'openapi-merged.json');
|
|
|
|
async function generateOpenApiSpec() {
|
|
// Ensure output directory exists
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
|
|
console.log('Bootstrapping Express app in test mode...');
|
|
const app = createApp({ skipRateLimits: true });
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const server = http.createServer(app);
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`Server listening on port ${PORT}`);
|
|
|
|
// Fetch the merged spec
|
|
const options = {
|
|
hostname: 'localhost',
|
|
port: PORT,
|
|
path: '/api/swagger.json',
|
|
method: 'GET'
|
|
};
|
|
|
|
const req = http.request(options, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const spec = JSON.parse(data);
|
|
|
|
// Validate it's a proper OpenAPI spec
|
|
if (!spec.openapi || !spec.info) {
|
|
throw new Error('Invalid OpenAPI spec: missing openapi or info field');
|
|
}
|
|
|
|
// Write to file
|
|
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(spec, null, 2));
|
|
console.log(`✓ OpenAPI spec written to ${OUTPUT_FILE}`);
|
|
console.log(` Version: ${spec.openapi}`);
|
|
console.log(` Title: ${spec.info.title}`);
|
|
|
|
server.close(() => {
|
|
resolve();
|
|
});
|
|
} catch (error) {
|
|
console.error('Error processing OpenAPI spec:', error.message);
|
|
server.close(() => {
|
|
reject(error);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
console.error('Error fetching spec:', error.message);
|
|
server.close(() => {
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
req.end();
|
|
});
|
|
|
|
server.on('error', (error) => {
|
|
if (error.code === 'EADDRINUSE') {
|
|
reject(new Error(`Port ${PORT} is already in use`));
|
|
} else {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
generateOpenApiSpec()
|
|
.then(() => {
|
|
console.log('OpenAPI spec generation complete');
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed to generate OpenAPI spec:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { generateOpenApiSpec };
|