Skip to content

Commit 4a39a5b

Browse files
committed
add documentation generator
1 parent f1ae872 commit 4a39a5b

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

src/core/capitalize.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function capitalize(text: string) {
2+
return text.split(" ").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Handlebars from "handlebars";
2+
import type { OpenAPI } from "~/core/openapi";
3+
import { type DocumentedOperation, resolveDocumentedOperations } from "~/core/resolvers/documented-operation";
4+
import getTemplate from "~/core/template";
5+
import documentationTemplate from "~/templates/documentation.hbs";
6+
7+
type DocumentationTemplate = {
8+
serviceName: string,
9+
packageName: string,
10+
envName: string,
11+
operations: DocumentedOperation[],
12+
};
13+
14+
export default function generateDocumentation(
15+
serviceName: string,
16+
packageName: string,
17+
envName: string,
18+
paths: OpenAPI["paths"]
19+
) {
20+
const template = getTemplate<DocumentationTemplate>(documentationTemplate);
21+
return template({
22+
serviceName,
23+
packageName,
24+
envName,
25+
operations: resolveDocumentedOperations(paths),
26+
});
27+
}
28+
29+
Handlebars.registerHelper("serialize", (input: string) => {
30+
return input
31+
.toLowerCase()
32+
.replace(/[^a-z0-9\s]/g, "")
33+
.trim()
34+
.replace(/\s+/g, "-");
35+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { OpenAPI, Operation } from "~/core/openapi";
2+
import getContentSchema from "./content";
3+
import resolveEndpoints from "./enpoint";
4+
import { resolveResponsesForDocs } from "./response";
5+
import { resolveSchema } from "./schema-definition";
6+
7+
type DocumentedParam = {
8+
name: string,
9+
description: string,
10+
type: string,
11+
optional: boolean,
12+
};
13+
14+
type DocumentedException = {
15+
statusCode: string,
16+
description: string,
17+
};
18+
19+
export type DocumentedOperation = {
20+
name: string,
21+
summary: string,
22+
description: string,
23+
parameters: string,
24+
parametersRaw: DocumentedParam[],
25+
result: string,
26+
exceptions: DocumentedException[],
27+
};
28+
29+
export function resolveDocumentedOperations(paths: OpenAPI["paths"]) {
30+
return resolveEndpoints(paths).map<DocumentedOperation>(({ operation }) => {
31+
const parameters = (operation.parameters ?? []).map<DocumentedParam>(p => ({
32+
name: p.name,
33+
description: p.description,
34+
type: resolveSchema(p.schema),
35+
optional: !p.required,
36+
}));
37+
return {
38+
name: operation.operationId,
39+
summary: operation.summary,
40+
description: operation.description,
41+
parameters: parameters.map(p => p.name).join(", "),
42+
parametersRaw: parameters,
43+
result: resolveOperationResult(operation.responses),
44+
exceptions: resolveResponsesForDocs(operation.responses).filter(r => !r.statusCode.startsWith("2")),
45+
};
46+
});
47+
}
48+
49+
function resolveOperationResult(responses: Operation["responses"]) {
50+
const schemas = Object.values(responses).map(response => {
51+
return response.content ? resolveSchema(getContentSchema(response.content)) : null;
52+
});
53+
return schemas.find(s => typeof s === "string") ?? "unknown";
54+
}

src/core/resolvers/response.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ export function resolveResponses(responses: Operation["responses"]) {
2424
`case ${statusCode}: ${handleResponse(statusCode, response, false)};`
2525
));
2626
}
27+
28+
export function resolveResponsesForDocs(responses: Operation["responses"]) {
29+
return Object.entries(responses).map(([statusCode, response]) => ({
30+
statusCode,
31+
description: response.description,
32+
}));
33+
}

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import "~/core/renderers/parameter";
33
import path from "node:path";
44
import getAppName from "./core/app";
55
import getArgument from "./core/arguments";
6+
import capitalize from "./core/capitalize";
67
import createFile from "./core/file";
78
import generateDeclaration from "./core/renderers/declaration";
9+
import generateDocumentation from "./core/renderers/documentation";
810
import generateInterface from "./core/renderers/interface";
911
import generateSchema from "./core/renderers/schema";
1012
import { resolveSchemas, resolveSchemasFromProps } from "./core/resolvers/imported-schema";
@@ -31,6 +33,7 @@ import generateSwaggerJson from "./core/swagger";
3133

3234
const packageName = getAppName();
3335
const appName = packageName.split("/").pop() ?? "unknown-service";
36+
const serviceName = capitalize(appName.replace(/-/g, " "));
3437
const envName = `${appName.replace(/-/g, "_").toUpperCase()}_BASE_URL`;
3538

3639
const schemas = resolveSchemas(data.paths);
@@ -41,4 +44,7 @@ import generateSwaggerJson from "./core/swagger";
4144

4245
const declaration = generateDeclaration(schemas, data.paths);
4346
await createFile(declaration, "index.d.ts", outputDir);
47+
48+
const doc = generateDocumentation(serviceName, packageName, envName, data.paths);
49+
await createFile(doc, "README.md", process.cwd());
4450
})();

src/templates/documentation.hbs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# {{serviceName}}
2+
3+
Streamline your interactions with {{serviceName}} using this comprehensive service interface. Simplify integration and boost efficiency for seamless development.
4+
5+
## Installation
6+
To use the {{serviceName}}, follow these steps:
7+
8+
1. Install the `{{packageName}}` package using npm:
9+
```bash
10+
npm install {{packageName}}
11+
```
12+
13+
2. Set up the `{{envName}}` environment variable in your project's `.env` file. This variable should contain the base URL for the {{serviceName}}.
14+
15+
```dotenv
16+
{{envName}}=http://localhost:XXXX
17+
```
18+
19+
Now you're ready to use the {{serviceName}} in your project!
20+
21+
## Content
22+
{{#each operations}}
23+
- [{{summary}}](#{{serialize summary}})
24+
{{/each}}
25+
26+
---
27+
28+
{{#each operations}}
29+
## {{summary}}
30+
31+
{{description}}
32+
33+
### Example Usage:
34+
```typescript
35+
import { {{name}} } from "{{../packageName}}";
36+
37+
const result = await {{name}}({{parameters}});
38+
```
39+
40+
{{#if parametersRaw.length}}
41+
### Parameters:
42+
{{#each parametersRaw}}
43+
- **{{name}}** `({{type}})` {{description}} {{#if optional}}`(Optional)`{{/if}}
44+
{{/each}}
45+
{{/if}}
46+
47+
### Returns:
48+
- `{{result}}`
49+
50+
{{#if exceptions.length}}
51+
### Exceptions:
52+
{{#each exceptions}}
53+
- `{{statusCode}}` {{description}}
54+
{{/each}}
55+
{{/if}}
56+
57+
---
58+
59+
{{/each}}
60+
If you have any questions or need further assistance, feel free to reach out.

0 commit comments

Comments
 (0)