diff --git a/README.md b/README.md index 0f51490f..ce7955bc 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Fastify command line interface, available commands are: * eject turns your application into a standalone executable with a server.(js|ts) file being added * generate generate a new project * generate-plugin generate a new plugin project + * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger * readme generate a README.md for the plugin * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. * version the current fastify-cli version @@ -316,6 +317,12 @@ Finally, there will be a new `README.md` file, which provides internal informati * Encapsulation semantics * Compatible Fastify version +### generate-swagger + +if your project uses `@fastify/swagger`, `fastify-cli` can generate and write out the resulting Swagger/OpenAPI schema for you. + +`fastify generate-swagger app.js` + ### linting `fastify-cli` is unopinionated on the choice of linter. We recommend you to add a linter, like so: @@ -370,8 +377,8 @@ test('test my application', async t => { }) ``` -Log output is consumed by tap. If log messages should be logged to the console -the logger needs to be configured to output to stderr instead of stdout. +Log output is consumed by tap. If log messages should be logged to the console +the logger needs to be configured to output to stderr instead of stdout. ```js const logger = { diff --git a/cli.js b/cli.js index a01d24b4..70d7d40e 100755 --- a/cli.js +++ b/cli.js @@ -13,12 +13,14 @@ const start = require('./start') const eject = require('./eject') const generate = require('./generate') const generatePlugin = require('./generate-plugin') +const generateSwagger = require('./generate-swagger') const generateReadme = require('./generate-readme') const printRoutes = require('./print-routes') commist.register('start', start.cli) commist.register('eject', eject.cli) commist.register('generate', generate.cli) commist.register('generate-plugin', generatePlugin.cli) +commist.register('generate-swagger', generateSwagger.cli) commist.register('readme', generateReadme.cli) commist.register('help', help.toStdout) commist.register('version', function () { diff --git a/generate-swagger.js b/generate-swagger.js new file mode 100644 index 00000000..93cffab1 --- /dev/null +++ b/generate-swagger.js @@ -0,0 +1,92 @@ +#! /usr/bin/env node + +'use strict' + +const parseArgs = require('./args') +const log = require('./log') +const { + exit, + requireFastifyForModule, + requireServerPluginFromPath, + showHelpForCommand +} = require('./util') +const fp = require('fastify-plugin') + +let Fastify = null + +function loadModules (opts) { + try { + Fastify = requireFastifyForModule(opts._[0]).module + } catch (e) { + module.exports.stop(e) + } +} + +async function generateSwagger (args) { + const opts = parseArgs(args) + if (opts.help) { + return showHelpForCommand('generate-swagger') + } + + if (opts._.length !== 1) { + console.error('Missing the required file parameter\n') + return showHelpForCommand('generate-swagger') + } + + // we start crashing on unhandledRejection + require('make-promises-safe') + + loadModules(opts) + + const fastify = await runFastify(opts) + try { + if (fastify.swagger == null) { + log('error', '@fastify/swagger plugin not installed') + process.exit(1) + } + + return JSON.stringify(fastify.swagger(), undefined, 2) + } finally { + fastify.close() + } +} + +async function runFastify (opts) { + require('dotenv').config() + + let file = null + + try { + file = await requireServerPluginFromPath(opts._[0]) + } catch (e) { + return module.exports.stop(e) + } + + const fastify = Fastify(opts.options) + + const pluginOptions = {} + if (opts.prefix) { + pluginOptions.prefix = opts.prefix + } + + await fastify.register(fp(file), pluginOptions) + await fastify.ready() + + return fastify +} + +function stop (message) { + exit(message) +} + +function cli (args) { + return generateSwagger(args).then(swagger => { + process.stdout.write(swagger + '\n') + }) +} + +module.exports = { cli, stop, generateSwagger } + +if (require.main === module) { + cli(process.argv.slice(2)) +} diff --git a/help/generate-swagger.txt b/help/generate-swagger.txt new file mode 100644 index 00000000..1958a5cc --- /dev/null +++ b/help/generate-swagger.txt @@ -0,0 +1,3 @@ +Usage: fastify generate-plugin [opts] [--] [] + +Generate Swagger/OpenAPI schema for a project using @fastify/cli. diff --git a/help/help.txt b/help/help.txt index 6fc6a2d3..a86aeb44 100644 --- a/help/help.txt +++ b/help/help.txt @@ -5,6 +5,7 @@ Fastify command line interface available commands are: * eject-ts turns your application into a standalone executable with a server.ts file being added. * generate generate a new project * generate-plugin generate a new plugin project + * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger * readme generate a README.md for the plugin * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. * version the current fastify-cli version diff --git a/package.json b/package.json index 534b42f3..84d3de95 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "commist": "^3.0.0", "dotenv": "^16.0.0", "fastify": "^4.0.0", + "fastify-plugin": "^4.0.0", "generify": "^4.0.0", "help-me": "^4.0.1", "is-docker": "^2.0.0", @@ -68,7 +69,6 @@ "@types/tap": "^15.0.5", "concurrently": "^7.0.0", "del-cli": "^3.0.1", - "fastify-plugin": "^4.0.0", "fastify-tsconfig": "^1.0.1", "minimatch": "^5.1.0", "proxyquire": "^2.1.3", diff --git a/test/generate-swagger.test.js b/test/generate-swagger.test.js new file mode 100644 index 00000000..b97566b7 --- /dev/null +++ b/test/generate-swagger.test.js @@ -0,0 +1,18 @@ +const path = require('path') +const t = require('tap') +const { test } = t +const { generateSwagger } = require('../generate-swagger') + +const swaggerplugindir = path.join(__dirname, 'swaggerplugindir') +const swaggerplugin = path.join(swaggerplugindir, 'plugin.js') + +test('should generate swagger', async (t) => { + t.plan(1) + + try { + const swagger = JSON.parse(await generateSwagger([swaggerplugin])) + t.equal(swagger.openapi, '3.0.3') + } catch (err) { + t.error(err) + } +}) diff --git a/test/swaggerplugindir/package.json b/test/swaggerplugindir/package.json new file mode 100644 index 00000000..ec4153a2 --- /dev/null +++ b/test/swaggerplugindir/package.json @@ -0,0 +1,14 @@ +{ + "name": "swaggerplugindir", + "version": "1.0.0", + "description": "test for generator-swagger", + "main": "plugin.js", + "scripts": { + "test": "" + }, + "dependencies": { + "fastify-plugin": "^1.4.0" + }, + "author": "", + "license": "ISC" +} diff --git a/test/swaggerplugindir/plugin.js b/test/swaggerplugindir/plugin.js new file mode 100644 index 00000000..80ef2594 --- /dev/null +++ b/test/swaggerplugindir/plugin.js @@ -0,0 +1,39 @@ +'use strict' + +const fp = require('fastify-plugin') + +module.exports = fp(function (fastify, opts, next) { + fastify.decorate('swagger', function () { + return { + openapi: '3.0.3', + info: { + version: '8.1.0', + title: '@fastify/swagger' + }, + components: { + schemas: {} + }, + paths: { + '/': { + get: { + responses: { + 200: { + description: 'Default Response' + } + } + } + }, + '/example/': { + get: { + responses: { + 200: { + description: 'Default Response' + } + } + } + } + } + } + }) + next() +})