nuxt-openapi-docs-module
Version:
A module for Nuxt.js to generate pages from OpenAPI specifications
230 lines (225 loc) • 9.05 kB
JavaScript
import { defineNuxtModule, createResolver, addComponentsDir, addTemplate, extendPages, addLayout, addPlugin } from '@nuxt/kit';
import { resolve, extname, basename, join } from 'path';
import { readFileSync, promises, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import _ from 'lodash';
import { kebabCase } from 'scule';
import OpenApiProcessor from '../dist/runtime/Parser/OpenApiProcessor.js';
// -- Unbuild CommonJS Shims --
import __cjs_url__ from 'url';
import __cjs_path__ from 'path';
import __cjs_mod__ from 'module';
const __filename = __cjs_url__.fileURLToPath(import.meta.url);
const __dirname = __cjs_path__.dirname(__filename);
const require = __cjs_mod__.createRequire(import.meta.url);
function filesCleanup(files) {
const result = {};
for (const i in files) {
const extension = extname(i);
const fileName = basename(i, extension);
result[kebabCase(fileName)] = files[i];
}
return result;
}
async function makeTemplate(templateName, fileName, options, resolver) {
const srcContents = await promises.readFile(resolver.resolve(`./runtime/${templateName}`), "utf-8");
const template = _.template(srcContents, {})({ options });
if (!existsSync(join(__dirname, ".cache"))) {
mkdirSync(join(__dirname, ".cache"));
}
const path = join(__dirname, ".cache", `${fileName}.vue`);
writeFileSync(path, template);
return path;
}
const module = defineNuxtModule({
meta: {
name: "nuxt-openapi-docs-module",
configKey: "openApiDocs",
compatibility: {
nuxt: "^3.0.0"
}
},
defaults: {
folder: "./docs/openapi",
name: "OpenApiDocs",
logo: '<svg width="140" height="30" viewBox="0 0 380 91" class="css-1j8o68f"><g id="SvgjsG1669" featurekey="PG4fjM-0" transform="matrix(0.3580488055912308,0,0,0.3580488055912308,0,0.00012395068781816175)" fill="rgb(62 142 238 / 89%)"><path xmlns="http://www.w3.org/2000/svg" d="M126.795,249.872l-5.068-6.189l26.459-21.67l3.933-0.654c2.383,0.891,4.517,0.809,6.008-0.227 c1.556-1.08,2.412-3.164,2.412-5.869v-145c0-6.626-5.137-14.141-11.217-16.408l-0.146-0.059L39.823,8.012 c-8.66-0.255-17.459,3.097-23.702,9.031C10.884,22.02,8,28.378,8,34.947v4H0v-4c0-8.783,3.768-17.201,10.609-23.703 C18.576,3.673,29.911-0.513,40.907,0.05l1.342,0.305l109.947,46.033c9.17,3.467,16.342,13.936,16.342,23.875v145 c0,5.326-2.132,9.859-5.85,12.441c-3.078,2.135-7.019,2.771-11.074,1.84L126.795,249.872z"></path><path xmlns="http://www.w3.org/2000/svg" d="M1.038,38.491v145c0,8.08,6.053,16.912,13.493,19.688l105.042,46.072c1.457,0.543,2.91,0.824,4.293,0.824 c5.277,0,8.823-4.104,8.823-10.209v-145c0-8.08-6.053-16.912-13.492-19.688l-9.267-4.064c-1.756,1.523-4.893,5.444-4.893,14.093v56 l-18-25.25l-13.25,11.25c0,0-0.169-34.875-0.044-50.25c-0.037-10.146,1.992-16.831,5.298-19.391L14.155,29.104 c-1.457-0.543-2.91-0.823-4.293-0.823C4.584,28.281,1.038,32.384,1.038,38.491z"></path><path xmlns="http://www.w3.org/2000/svg" d="M4.475,29.498C2.76,30.81,1.495,32.787,0.91,35.272C1.715,33.491,3.003,31.483,4.475,29.498z"></path></g><text x="60%" y="60%" dominant-baseline="middle" text-anchor="middle" fill="black" font-size="56" font-weight="600">:name</text></svg>',
footer: null,
path: "docs",
debug: false,
list: false,
devtools: true,
localize: true,
doc: {},
locales: [],
files: () => {
return {};
}
},
async setup(options, nuxt) {
if (!options.locales || !options.locales.length) {
options.locales = ["en", "fr", "de", "ru", "ch", "es", "hi", "ar", "zh", "pt", "bn", "it", "ja", "jv", "ko", "pa", "ta", "te", "tr"];
}
nuxt.options.dev === false;
const resolver = createResolver(import.meta.url);
await addComponentsDir({
path: resolver.resolve("./runtime/components"),
pathPrefix: false,
// prefix: 'OA',
extensions: ["vue"]
});
const filesClean = filesCleanup(options.files());
const files = options.files();
const workDir = resolve(nuxt.options.rootDir, options.folder);
const docs = [];
for (const filePath in files) {
const parser = new OpenApiProcessor(workDir);
parser.load(filePath);
docs.push({
filename: parser.getFilename(),
name: options.name.toString(),
filePath,
doc: parser.getDoc(),
path: options.path,
pathsByTags: parser.getPaths(),
locales: parser.getLocales(),
localesReload: parser.getLocalesReload(),
servers: parser.getServers()
});
}
addTemplate({
filename: "openapi/config.mjs",
write: true,
getContents: () => "export default " + JSON.stringify({
filesClean,
path: options.path ?? "docs",
doc_path: options.path ?? "docs",
base_url: nuxt.options.app.baseURL ?? "/",
logo: options.logo,
footer: options.footer
})
});
for (const item of docs) {
addTemplate({
filename: `openapi/docs.${item.filename}.config.mjs`,
write: true,
getContents: () => "export default " + JSON.stringify({
path: item.path,
locales: item.locales,
localesReload: item.localesReload,
servers: item.servers,
paths_by_tags: item.pathsByTags,
name: item.name
})
});
addTemplate({
filename: `openapi/docs.${item.filename}.mjs`,
write: true,
getContents: () => "export default " + JSON.stringify(item.doc)
});
}
if (options.list) {
const dst = await makeTemplate("templates/OpenApiTemplateDocsList.vue", "DocsList", {
files: filesClean
}, resolver);
nuxt.hook("prepare:types", ({ references }) => {
references.push({ path: dst });
});
extendPages((pages) => {
pages.push({
name: `openapi-docs-list`,
path: `/${options.path}`,
file: dst
});
});
}
const hasModule = (moduleName) => (nuxt.options?.modules ?? []).some(
(module) => Array.isArray(module) && module.length > 0 && module[0] === moduleName
);
const hasI18nModule = hasModule("@nuxtjs/i18n");
const hasI18nMicroModule = hasModule("nuxt-i18n-micro");
for (const item of docs) {
addLayout({
getContents({ options: options2 }) {
const contents = readFileSync(resolver.resolve(`./runtime/layout/OpenApiLayoutNuxt3.vue`), "utf-8");
return _.template(contents)({ options: options2 });
},
filename: `openapi/apidocs.layout.${item.filename}.vue`,
write: true,
options: {
path: options.path ?? "docs",
filename: item.filename,
locales: item.locales,
localesReload: item.localesReload
}
}, "open-api-layout-" + item.filename);
const dst = await makeTemplate("templates/OpenApiTemplateNuxt3.vue", `${item.filename}`, {
path: options.path ?? "docs",
filename: item.filename,
locales: item.locales,
localesReload: item.localesReload,
i18n: hasI18nModule,
i18nMicro: hasI18nMicroModule,
i18n_text: resolver.resolve("./runtime/lang/en.json")
}, resolver);
nuxt.hook("prepare:types", ({ references }) => {
references.push({ path: dst });
});
extendPages((pages) => {
pages.push({
name: `openapi-${options.path}-${item.filename}`,
path: `/${options.path}/${item.filename}/:type`,
file: dst,
meta: {
layout: `open-api-layout-${item.filename}`
}
});
pages.push({
name: `openapi-${options.path}-${item.filename}/type-mathod`,
path: `/${options.path}/${item.filename}/:type/:mathod`,
file: dst,
meta: {
layout: `open-api-layout-${item.filename}`
}
});
});
}
addPlugin({
src: resolver.resolve("./runtime/plugins/plugin3"),
order: 0
});
if (options.localize) {
if (hasI18nModule) {
nuxt.hook("i18n:registerModule", (register) => {
register({
langDir: resolver.resolve("./runtime/lang"),
locales: options.locales?.map((code) => ({
code,
iso: code,
file: `${code}.json`
}))
});
});
}
if (hasI18nMicroModule) {
addPlugin({
src: resolver.resolve("./runtime/plugins/localeLoader.ts")
});
}
}
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
nitroConfig.publicAssets.push({ dir: resolver.resolve("./runtime/public"), maxAge: 31536e3 });
});
nuxt.hook("devtools:customTabs", (iframeTabs) => {
iframeTabs.push({
name: "nuxt-openapi-docs-module",
title: "OpenApi",
icon: "carbon:doc",
view: {
type: "iframe",
src: "https://on-org.github.io/nuxt-openapi-docs-module/"
}
});
});
}
});
export { module as default };