Search code examples
swaggerfastifyfastify-swagger

Generating the openapi.yml file after passing the tests


After finishing all tests (in tap) I would like to run a script that generates openapi.yml.

In the .taprc config, I added the after property:

after: utils/generateOpenApi.ts

and my script (utils/generateOpenApi.ts) looks like this:

const helper = require('fastify-cli/helper.js')
import fs from 'fs'
import path from 'path'

const generateOpenApi = async() => {
   const argv = [path.join(__dirname, '..', 'src', 'app.ts')]
   const app = await helper.build(argv, {})
  
   console.log('app.swagger', app.swagger)
   const yaml = app.swagger({ yaml: true });
   fs.writeFileSync("swagger.yaml", yaml);
}

generateOpenApi()

My main file (app.ts):

import { FastifyPluginAsync } from "fastify";
import { buildJsonSchemas, register } from "fastify-zod";
import { join } from "path";
import AutoLoad, { AutoloadPluginOptions } from "@fastify/autoload";
import { type FastifyZod } from "fastify-zod";

import { models } from "./routes/users/users.schemas";

export type AppOptions = {
   // Place your custom options for app below here.
} & Partial<AutoloadPluginOptions>;

// Pass --options via CLI arguments in command to enable these options.
const options: AppOptions = {};

declare module "fastify" {
   interface FastifyInstance {
     readonly zod: FastifyZod<typeof models>;
   }
}

const app: FastifyPluginAsync<AppOptions> = async (
   fastify,
   opts
): Promise<void> => {
   await register(fastify, {
     jsonSchemas: buildJsonSchemas(models, { errorMessages: true }),
     swaggerOptions: {
       swagger: {
         info: {
           title: "Fastify API",
           description:
             "Building a blazing fast REST API with Node.js, MongoDB, Fastify and Swagger",
           version: "0.1.0",
         },
         host: "localhost",
         schemes: ["http"],
         consumes: ["application/json"],
       },
     },
     swaggerUiOptions: {
       routePrefix: "/docs",
       uiConfig: {
         docExpansion: "full",
         deepLinking: false,
       },
       uiHooks: {
         onRequest: function (request, reply, next) {
           next();
         },
         preHandler: function (request, reply, next) {
           next();
         },
       },
       staticCSP: true,
       transformStaticCSP: (header) => header,
       transformSpecification: (swaggerObject, request, reply) => {
         return swaggerObject;
       },
       transformSpecificationClone: true,
     },
   });

   void fastify.register(AutoLoad, {
     dir: join(__dirname, "plugins"),
     options: opts,
   });

   void fastify.register(AutoLoad, {
     dir: join(__dirname, "routes"),
     options: opts,
     ignorePattern: /.*(schemas)\.ts/
   });
};

export default app;
export { app, option

Codesandbox


Solution

  • You need to wrap the app.ts with the fastify-plugin.

    The structure of your application is like this (on the left):

    app structure

    The helper.build(argv, {}) function returns the fastify-cli root instance that does not have the swagger-ui plugin attached.

    So, if you break the encapsulation by using the fastify-plugin, the app.ts lives in the same parent's context and you can access the swagger-ui plugin from the reference returned by helper.build(argv, {}).

    Further reading: