next-openapi-gen 0.8.9 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/dist/commands/init.js +69 -16
- package/dist/components/rapidoc.js +1 -0
- package/dist/components/redoc.js +1 -0
- package/dist/components/scalar.js +1 -0
- package/dist/components/stoplight.js +1 -0
- package/dist/components/swagger.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/route-processor.js +27 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,15 +14,12 @@ Automatically generate OpenAPI 3.0 documentation from Next.js projects, with sup
|
|
|
14
14
|
|
|
15
15
|
## Supported interfaces
|
|
16
16
|
|
|
17
|
-
- Scalar
|
|
17
|
+
- Scalar 💡(default)
|
|
18
18
|
- Swagger
|
|
19
19
|
- Redoc
|
|
20
20
|
- Stoplight Elements
|
|
21
21
|
- RapiDoc
|
|
22
22
|
|
|
23
|
-
> [!TIP]
|
|
24
|
-
> You can use the `--ui none` option during initialization to skip UI setup if you only care about generating the OpenAPI documentation.
|
|
25
|
-
|
|
26
23
|
## Installation
|
|
27
24
|
|
|
28
25
|
```bash
|
|
@@ -33,14 +30,27 @@ npm install next-openapi-gen --save-dev
|
|
|
33
30
|
|
|
34
31
|
```bash
|
|
35
32
|
# Initialize OpenAPI configuration
|
|
36
|
-
npx next-openapi-gen init
|
|
33
|
+
npx next-openapi-gen init
|
|
37
34
|
|
|
38
35
|
# Generate OpenAPI documentation
|
|
39
36
|
npx next-openapi-gen generate
|
|
40
37
|
```
|
|
41
38
|
|
|
42
39
|
> [!TIP]
|
|
43
|
-
>
|
|
40
|
+
> Scalar UI and Zod are set by default
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Init Command Options
|
|
44
|
+
|
|
45
|
+
| Option | Choices | Default | Description |
|
|
46
|
+
|--------|---------|---------|-------------|
|
|
47
|
+
| `--ui` | `scalar`, `swagger`, `redoc`, `stoplight`, `rapidoc`, `none` | `scalar` | UI framework for API docs |
|
|
48
|
+
| `--schema` | `zod`, `typescript` | `zod` | Schema validation tool |
|
|
49
|
+
| `--docs-url` | any string | `api-docs` | URL path for documentation page |
|
|
50
|
+
| `--output` | any path | `next.openapi.json` | Output file for OpenAPI template |
|
|
51
|
+
|
|
52
|
+
> [!TIP]
|
|
53
|
+
> Use `--ui none` to skip UI setup and only generate the OpenAPI specification file.
|
|
44
54
|
|
|
45
55
|
## Configuration
|
|
46
56
|
|
package/dist/commands/init.js
CHANGED
|
@@ -5,13 +5,23 @@ import ora from "ora";
|
|
|
5
5
|
import { exec } from "child_process";
|
|
6
6
|
import util from "util";
|
|
7
7
|
import openapiTemplate from "../openapi-template.js";
|
|
8
|
-
import { scalarDeps, ScalarUI } from "../components/scalar.js";
|
|
9
|
-
import { swaggerDeps, SwaggerUI } from "../components/swagger.js";
|
|
10
|
-
import { redocDeps, RedocUI } from "../components/redoc.js";
|
|
11
|
-
import { stoplightDeps, StoplightUI } from "../components/stoplight.js";
|
|
12
|
-
import { rapidocDeps, RapidocUI } from "../components/rapidoc.js";
|
|
8
|
+
import { scalarDeps, scalarDevDeps, ScalarUI } from "../components/scalar.js";
|
|
9
|
+
import { swaggerDeps, swaggerDevDeps, SwaggerUI } from "../components/swagger.js";
|
|
10
|
+
import { redocDeps, redocDevDeps, RedocUI } from "../components/redoc.js";
|
|
11
|
+
import { stoplightDeps, stoplightDevDeps, StoplightUI } from "../components/stoplight.js";
|
|
12
|
+
import { rapidocDeps, rapidocDevDeps, RapidocUI } from "../components/rapidoc.js";
|
|
13
13
|
const execPromise = util.promisify(exec);
|
|
14
14
|
const spinner = ora("Initializing project with OpenAPI template...\n");
|
|
15
|
+
async function hasDependency(packageName) {
|
|
16
|
+
try {
|
|
17
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
18
|
+
const packageJson = await fse.readJson(packageJsonPath);
|
|
19
|
+
return !!(packageJson.dependencies?.[packageName] || packageJson.devDependencies?.[packageName]);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
const getPackageManager = async () => {
|
|
16
26
|
let currentDir = process.cwd();
|
|
17
27
|
while (true) {
|
|
@@ -84,6 +94,25 @@ function getDocsPageDependencies(ui) {
|
|
|
84
94
|
}
|
|
85
95
|
return deps.join(" ");
|
|
86
96
|
}
|
|
97
|
+
function getDocsPageDevDependencies(ui) {
|
|
98
|
+
let devDeps = [];
|
|
99
|
+
if (ui === "scalar") {
|
|
100
|
+
devDeps = scalarDevDeps;
|
|
101
|
+
}
|
|
102
|
+
else if (ui === "swagger") {
|
|
103
|
+
devDeps = swaggerDevDeps;
|
|
104
|
+
}
|
|
105
|
+
else if (ui === "redoc") {
|
|
106
|
+
devDeps = redocDevDeps;
|
|
107
|
+
}
|
|
108
|
+
else if (ui === "stoplight") {
|
|
109
|
+
devDeps = stoplightDevDeps;
|
|
110
|
+
}
|
|
111
|
+
else if (ui === "rapidoc") {
|
|
112
|
+
devDeps = rapidocDevDeps;
|
|
113
|
+
}
|
|
114
|
+
return devDeps.join(" ");
|
|
115
|
+
}
|
|
87
116
|
async function createDocsPage(ui, outputFile) {
|
|
88
117
|
if (ui === "none") {
|
|
89
118
|
return;
|
|
@@ -100,17 +129,41 @@ async function createDocsPage(ui, outputFile) {
|
|
|
100
129
|
await fs.promises.writeFile(componentPath, docsPage.trim());
|
|
101
130
|
spinner.succeed(`Created ${paths.join("/")}/page.tsx for ${ui}.`);
|
|
102
131
|
}
|
|
103
|
-
async function installDependencies(ui) {
|
|
104
|
-
if (ui === "none") {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
132
|
+
async function installDependencies(ui, schema) {
|
|
107
133
|
const packageManager = await getPackageManager();
|
|
108
134
|
const installCmd = `${packageManager} ${packageManager === "npm" ? "install" : "add"}`;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
// Install UI dependencies
|
|
136
|
+
if (ui !== "none") {
|
|
137
|
+
const deps = getDocsPageDependencies(ui);
|
|
138
|
+
const devDeps = getDocsPageDevDependencies(ui);
|
|
139
|
+
const flags = getDocsPageInstallFlags(ui, packageManager);
|
|
140
|
+
if (deps) {
|
|
141
|
+
spinner.succeed(`Installing ${deps} dependencies...`);
|
|
142
|
+
await execPromise(`${installCmd} ${deps} ${flags}`);
|
|
143
|
+
spinner.succeed(`Successfully installed ${deps}.`);
|
|
144
|
+
}
|
|
145
|
+
if (devDeps) {
|
|
146
|
+
const devFlag = packageManager === "npm" ? "--save-dev" : "-D";
|
|
147
|
+
spinner.succeed(`Installing ${devDeps} dev dependencies...`);
|
|
148
|
+
await execPromise(`${installCmd} ${devFlag} ${devDeps} ${flags}`);
|
|
149
|
+
spinner.succeed(`Successfully installed ${devDeps}.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Install schema dependencies
|
|
153
|
+
const schemaTypes = Array.isArray(schema) ? schema : [schema];
|
|
154
|
+
for (const schemaType of schemaTypes) {
|
|
155
|
+
if (schemaType === "zod" && !(await hasDependency("zod"))) {
|
|
156
|
+
spinner.succeed(`Installing zod...`);
|
|
157
|
+
await execPromise(`${installCmd} zod`);
|
|
158
|
+
spinner.succeed(`Successfully installed zod.`);
|
|
159
|
+
}
|
|
160
|
+
else if (schemaType === "typescript" && !(await hasDependency("typescript"))) {
|
|
161
|
+
const devFlag = packageManager === "npm" ? "--save-dev" : "-D";
|
|
162
|
+
spinner.succeed(`Installing typescript...`);
|
|
163
|
+
await execPromise(`${installCmd} ${devFlag} typescript`);
|
|
164
|
+
spinner.succeed(`Successfully installed typescript.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
114
167
|
}
|
|
115
168
|
function extendOpenApiTemplate(spec, options) {
|
|
116
169
|
spec.ui = options.ui ?? spec.ui;
|
|
@@ -124,7 +177,7 @@ function getOutputPath(output) {
|
|
|
124
177
|
return path.join(process.cwd(), "next.openapi.json");
|
|
125
178
|
}
|
|
126
179
|
export async function init(options) {
|
|
127
|
-
const { ui, output } = options;
|
|
180
|
+
const { ui, output, schema } = options;
|
|
128
181
|
spinner.start();
|
|
129
182
|
try {
|
|
130
183
|
const outputPath = getOutputPath(output);
|
|
@@ -133,7 +186,7 @@ export async function init(options) {
|
|
|
133
186
|
await fse.writeJson(outputPath, template, { spaces: 2 });
|
|
134
187
|
spinner.succeed(`Created OpenAPI template in ${outputPath}`);
|
|
135
188
|
createDocsPage(ui, template.outputFile);
|
|
136
|
-
installDependencies(ui);
|
|
189
|
+
installDependencies(ui, schema);
|
|
137
190
|
}
|
|
138
191
|
catch (error) {
|
|
139
192
|
spinner.fail(`Failed to initialize project: ${error.message}`);
|
package/dist/components/redoc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const swaggerDeps = ["swagger-ui", "swagger-ui-react"];
|
|
2
|
+
export const swaggerDevDeps = ["@types/swagger-ui-react"];
|
|
2
3
|
export function SwaggerUI(outputFile) {
|
|
3
4
|
return `
|
|
4
5
|
import "swagger-ui-react/swagger-ui.css";
|
|
@@ -6,7 +7,6 @@ import "swagger-ui-react/swagger-ui.css";
|
|
|
6
7
|
import dynamic from "next/dynamic";
|
|
7
8
|
|
|
8
9
|
const SwaggerUI = dynamic(() => import("swagger-ui-react"), {
|
|
9
|
-
ssr: false,
|
|
10
10
|
loading: () => <p>Loading Component...</p>,
|
|
11
11
|
});
|
|
12
12
|
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ program
|
|
|
11
11
|
.command("init")
|
|
12
12
|
.addOption(new Option("-i, --ui <type>", "Specify the UI type, e.g., scalar. Use \"none\" for no UI")
|
|
13
13
|
.choices(["scalar", "swagger", "redoc", "stoplight", "rapidoc", "none"])
|
|
14
|
-
.default("
|
|
14
|
+
.default("scalar"))
|
|
15
15
|
.option("-u, --docs-url <url>", "Specify the docs URL", "api-docs")
|
|
16
16
|
.addOption(new Option("-s, --schema <schemaType>", "Specify the schema tool")
|
|
17
17
|
.choices(["zod", "typescript"])
|
|
@@ -336,29 +336,34 @@ export class RouteProcessor {
|
|
|
336
336
|
getRoutePath(filePath) {
|
|
337
337
|
// Normalize path separators first
|
|
338
338
|
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// Convert Next.js dynamic route syntax to OpenAPI parameter syntax
|
|
349
|
-
relativePath = relativePath.replace(/\/\[([^\]]+)\]/g, "/{$1}");
|
|
350
|
-
// Handle catch-all routes ([...param])
|
|
351
|
-
relativePath = relativePath.replace(/\/\[\.\.\.(.*)\]/g, "/{$1}");
|
|
352
|
-
return relativePath;
|
|
339
|
+
// Normalize apiDir to ensure consistent format
|
|
340
|
+
const normalizedApiDir = this.config.apiDir
|
|
341
|
+
.replaceAll("\\", "/")
|
|
342
|
+
.replace(/^\.\//, "")
|
|
343
|
+
.replace(/\/$/, "");
|
|
344
|
+
// Find the apiDir position in the normalized path
|
|
345
|
+
const apiDirIndex = normalizedPath.indexOf(normalizedApiDir);
|
|
346
|
+
if (apiDirIndex === -1) {
|
|
347
|
+
throw new Error(`Could not find apiDir "${this.config.apiDir}" in file path "${filePath}"`);
|
|
353
348
|
}
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
349
|
+
// Extract the path after apiDir
|
|
350
|
+
let relativePath = normalizedPath.substring(apiDirIndex + normalizedApiDir.length);
|
|
351
|
+
// Remove the /route.ts or /route.tsx suffix
|
|
352
|
+
relativePath = relativePath.replace(/\/route\.tsx?$/, "");
|
|
353
|
+
// Ensure the path starts with /
|
|
354
|
+
if (!relativePath.startsWith("/")) {
|
|
355
|
+
relativePath = "/" + relativePath;
|
|
356
|
+
}
|
|
357
|
+
// Remove trailing slash
|
|
358
|
+
relativePath = relativePath.replace(/\/$/, "");
|
|
359
|
+
// Remove Next.js route groups (folders in parentheses like (authenticated), (marketing))
|
|
360
|
+
relativePath = relativePath.replace(/\/\([^)]+\)/g, "");
|
|
361
|
+
// Handle catch-all routes ([...param]) before converting dynamic routes
|
|
362
|
+
// This must come first because [...param] would also match the [param] pattern
|
|
363
|
+
relativePath = relativePath.replace(/\/\[\.\.\.(.*?)\]/g, "/{$1}");
|
|
364
|
+
// Convert Next.js dynamic route syntax to OpenAPI parameter syntax
|
|
365
|
+
relativePath = relativePath.replace(/\/\[([^\]]+)\]/g, "/{$1}");
|
|
366
|
+
return relativePath || "/";
|
|
362
367
|
}
|
|
363
368
|
getSortedPaths(paths) {
|
|
364
369
|
function comparePaths(a, b) {
|
package/package.json
CHANGED