OpenAPI: CLI support configuration files

This commit is contained in:
Fuma Nama
2024-01-14 19:17:53 +08:00
parent c0195449dd
commit 45a52ae5eb
15 changed files with 161 additions and 48 deletions
+29
View File
@@ -0,0 +1,29 @@
---
'@fuma-docs/openapi': minor
---
**Support generating docs for OpenAPI schema**
In `openapi.config.js`:
```js
/**
* @type {import("@fuma-docs/openapi").Config}
*/
module.exports = {
input: ['./petstore.yaml'],
output: './content/docs',
per: 'tag',
render: (title, description) => {
return {
frontmatter: [
'---',
`title: ${title}`,
`description: ${description}`,
'toc: false',
'---',
].join('\n'),
};
},
};
```
-1
View File
@@ -14,7 +14,6 @@ node_modules
# outputs
.turbo
.next
out
dist
.contentlayer
.eslintcache
-2
View File
@@ -5,8 +5,6 @@
"---Guide---",
"index",
"...",
"---API---",
"...api",
"---Components---",
"...components",
"---Blocks---",
-24
View File
@@ -1,24 +0,0 @@
import { writeFileSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { generateTags } from '@fuma-docs/openapi';
const folder = resolve('./content/docs/ui/api');
generateTags('./petstore.yaml', {
render(title, description) {
return {
frontmatter: [
'---',
`title: ${title}`,
`description: ${description}`,
'toc: false',
'---',
].join('\n'),
};
},
}).then((tags) => {
tags.forEach(({ tag, content }) => {
writeFileSync(join(folder, `${tag.toLowerCase()}.mdx`), content);
console.log(`Generated page for ${tag}`);
});
});
+19
View File
@@ -0,0 +1,19 @@
/**
* @type {import("@fuma-docs/openapi").Config}
*/
module.exports = {
input: ['./petstore.yaml'],
output: './content/docs/headless',
per: 'tag',
render: (title, description) => {
return {
frontmatter: [
'---',
`title: ${title}`,
`description: ${description}`,
'toc: false',
'---',
].join('\n'),
};
},
};
+1 -1
View File
@@ -5,7 +5,7 @@
"scripts": {
"build": "pnpm build:openapi && next build && pnpm build:index",
"build:index": "node ./update-index.mjs",
"build:openapi": "node ./generate-docs.mjs",
"build:openapi": "fuma-docs-openapi",
"build:shaders": "glslx shaders/rain.glsl --output=shaders/rain.min.js --format=js --renaming=internal-only",
"dev": "next dev",
"lint": "next lint",
+99 -8
View File
@@ -1,11 +1,102 @@
#!/usr/bin/env node
import { writeFileSync } from 'node:fs';
import { generate } from './generate';
import { writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join, parse, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import type { GenerateOptions } from './generate';
import { generate, generateTags } from './generate';
const path = process.argv[2];
const output = process.argv[3];
async function main(): Promise<void> {
const configName = process.argv[2];
const {
input,
output,
name: nameFn,
per = 'file',
render,
} = await readConfig(configName);
const outputDir = resolve(output);
void generate(path).then((res) => {
writeFileSync(output, res);
console.log(`Generated: ${res.length} characters`);
});
const options = {
render,
};
await Promise.all(
input.map(async (file) => {
const path = resolve(file);
let filename = parse(path).name;
filename = nameFn?.('file', filename) ?? filename;
if (per === 'file') {
const outPath = join(outputDir, `${filename}.mdx`);
const result = await generate(path, options);
write(outPath, result);
console.log(`Generated: ${outPath}`);
} else {
const results = await generateTags(path, options);
results.forEach((result) => {
let tagName = result.tag;
tagName =
nameFn?.('tag', tagName) ??
tagName.toLowerCase().replace(/\s+/g, '-');
const outPath = join(outputDir, `${filename}/${tagName}.mdx`);
write(outPath, result.content);
console.log(`Generated: ${outPath}`);
});
}
}),
);
}
function write(path: string, content: string): void {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, content);
}
export interface Config {
/**
* Schema files
*/
input: string[];
/**
* Output directory
*/
output: string;
/**
* tag: Generate a page for each tag
*
* file: Generate a page for each schema
*
* @defaultValue tag
*/
per?: 'tag' | 'file';
/**
* Specify name for output file
*/
name?: (type: 'file' | 'tag', name: string) => string;
/**
* Modify output file
*/
render?: NonNullable<GenerateOptions['render']>;
}
async function readConfig(name = 'openapi.config.js'): Promise<Config> {
const path = resolve(process.cwd(), name);
const result = (await import(pathToFileURL(path).toString())) as {
default: Config;
};
if (typeof result.default !== 'object')
throw new Error('Invalid configuration');
return result.default;
}
void main();
+1
View File
@@ -1,2 +1,3 @@
export * from './generate';
export * from './types';
export type { Config } from './bin';
@@ -8,12 +8,13 @@ describe('Generate documents', () => {
fileURLToPath(new URL('./fixtures/petstore.yaml', import.meta.url)),
);
for (const tag of tags) {
// eslint-disable-next-line no-await-in-loop -- Ignore
await expect(tag.content).toMatchFileSnapshot(
`./test/petstore-out/${tag.tag}.mdx`,
);
}
await Promise.all(
tags.map(async (tag) =>
expect(tag.content).toMatchFileSnapshot(
`./out/petstore/${tag.tag}.mdx`,
),
),
);
});
test('Pet Store', async () => {
@@ -21,11 +22,10 @@ describe('Generate documents', () => {
fileURLToPath(new URL('./fixtures/museum.yaml', import.meta.url)),
);
for (const tag of tags) {
// eslint-disable-next-line no-await-in-loop -- Ignore
await expect(tag.content).toMatchFileSnapshot(
`./test/museum-out/${tag.tag}.mdx`,
);
}
await Promise.all(
tags.map(async (tag) =>
expect(tag.content).toMatchFileSnapshot(`./out/museum/${tag.tag}.mdx`),
),
);
});
});