@visulima/api-platform 1.0.2 → 1.0.3
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/CHANGELOG.md +16 -0
- package/bin/index.js +46 -0
- package/dist/{chunk-XXZ56SKG.mjs → chunk-2M45MXC2.mjs} +1 -2
- package/dist/chunk-2M45MXC2.mjs.map +1 -0
- package/dist/chunk-3Y3YBDRS.mjs +284 -0
- package/dist/chunk-3Y3YBDRS.mjs.map +1 -0
- package/dist/chunk-NY75KGAH.js +284 -0
- package/dist/chunk-NY75KGAH.js.map +1 -0
- package/dist/{chunk-AJKZCWFG.js → chunk-S46GRBOS.js} +1 -2
- package/dist/chunk-S46GRBOS.js.map +1 -0
- package/dist/index-server.d.ts +2 -0
- package/dist/index-server.js +9 -10
- package/dist/index-server.js.map +1 -1
- package/dist/index-server.mjs +7 -8
- package/dist/index-server.mjs.map +1 -1
- package/dist/next/cli/index.js +8 -8
- package/dist/next/cli/index.js.map +1 -1
- package/dist/next/cli/index.mjs +8 -8
- package/dist/next/cli/index.mjs.map +1 -1
- package/dist/next/index-browser.js +2 -2
- package/dist/next/index-browser.mjs +1 -1
- package/dist/next/index-server.d.ts +9 -5
- package/dist/next/index-server.js +4 -4
- package/dist/next/index-server.mjs +2 -2
- package/next/cli/package.json +12 -3
- package/next/package.json +9 -0
- package/package.json +20 -20
- package/recipes/api/swagger.ts +12 -0
- package/recipes/pages/redoc-ui.tsx +5 -0
- package/recipes/pages/swagger-ui.tsx +5 -0
- package/dist/chunk-2LATTLUM.mjs +0 -166
- package/dist/chunk-2LATTLUM.mjs.map +0 -1
- package/dist/chunk-AJKZCWFG.js.map +0 -1
- package/dist/chunk-S7GUPAL4.js +0 -166
- package/dist/chunk-S7GUPAL4.js.map +0 -1
- package/dist/chunk-XXZ56SKG.mjs.map +0 -1
- package/src/connect/create-node-router.ts +0 -44
- package/src/connect/handler.ts +0 -46
- package/src/connect/middleware/cors-middleware.ts +0 -10
- package/src/connect/middleware/http-header-normalizer.ts +0 -93
- package/src/connect/middleware/rate-limiter-middleware.ts +0 -43
- package/src/connect/middleware/serializers-middleware.ts +0 -121
- package/src/connect/serializers/types.d.ts +0 -1
- package/src/connect/serializers/xml.ts +0 -13
- package/src/connect/serializers/yaml.ts +0 -7
- package/src/error-handler/jsonapi-error-handler.ts +0 -46
- package/src/error-handler/problem-error-handler.ts +0 -44
- package/src/error-handler/types.d.ts +0 -14
- package/src/error-handler/utils.ts +0 -39
- package/src/index-browser.tsx +0 -1
- package/src/index-server.ts +0 -75
- package/src/next/cli/index.ts +0 -2
- package/src/next/cli/list/api-route-file-parser.ts +0 -74
- package/src/next/cli/list/collect-api-route-files.ts +0 -42
- package/src/next/cli/list/list-command.ts +0 -105
- package/src/next/cli/list/routes-render.ts +0 -62
- package/src/next/cli/list/types.d.ts +0 -1
- package/src/next/index-browser.tsx +0 -3
- package/src/next/index-server.ts +0 -6
- package/src/next/routes/api/swagger.ts +0 -23
- package/src/next/routes/pages/swagger/get-static-properties-swagger.ts +0 -32
- package/src/next/routes/pages/swagger/redoc.tsx +0 -35
- package/src/next/routes/pages/swagger/swagger.tsx +0 -44
- package/src/next/webpack/with-open-api.ts +0 -63
- package/src/swagger/extend-swagger-spec.ts +0 -167
- package/src/swagger/swagger-handler.ts +0 -83
- package/src/utils.ts +0 -37
- package/src/zod/date-in-schema.ts +0 -57
- package/src/zod/date-out-schema.ts +0 -41
- package/src/zod/index.ts +0 -9
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import { collect } from "@visulima/readdir";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
|
|
6
|
-
const isDirectory = (path: string): boolean => {
|
|
7
|
-
try {
|
|
8
|
-
return fs.statSync(path).isDirectory();
|
|
9
|
-
} catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const collectApiRouteFiles = async (path: string = "", verbose: boolean = false): Promise<string[]> => {
|
|
15
|
-
let apiFolderPath = join(path, "pages/api");
|
|
16
|
-
|
|
17
|
-
// src/pages will be ignored if pages is present in the root directory
|
|
18
|
-
if (!isDirectory(apiFolderPath)) {
|
|
19
|
-
apiFolderPath = join(path, "src/pages/api");
|
|
20
|
-
|
|
21
|
-
if (!isDirectory(apiFolderPath)) {
|
|
22
|
-
return [];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return collect(apiFolderPath, {
|
|
27
|
-
extensions: [".js", ".cjs", ".mjs", ".ts"],
|
|
28
|
-
includeDirs: false,
|
|
29
|
-
minimatchOptions: {
|
|
30
|
-
match: {
|
|
31
|
-
debug: verbose,
|
|
32
|
-
matchBase: true,
|
|
33
|
-
},
|
|
34
|
-
skip: {
|
|
35
|
-
debug: verbose,
|
|
36
|
-
matchBase: true,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default collectApiRouteFiles;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import colors from "chalk";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
|
-
|
|
5
|
-
import apiRouteFileParser from "./api-route-file-parser";
|
|
6
|
-
import collectApiRouteFiles from "./collect-api-route-files";
|
|
7
|
-
import routesRender from "./routes-render";
|
|
8
|
-
import type { Route } from "./types";
|
|
9
|
-
|
|
10
|
-
type RenderOptions = {
|
|
11
|
-
includePaths: string[];
|
|
12
|
-
excludePaths: string[];
|
|
13
|
-
methods: string[];
|
|
14
|
-
group: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const groupBy = (list: Route[], keyGetter: (item: Route) => keyof Route): Map<string, Route[]> => {
|
|
18
|
-
const map = new Map();
|
|
19
|
-
|
|
20
|
-
list.forEach((item) => {
|
|
21
|
-
const key = keyGetter(item);
|
|
22
|
-
const collection = map.get(key);
|
|
23
|
-
|
|
24
|
-
if (!collection) {
|
|
25
|
-
map.set(key, [item]);
|
|
26
|
-
} else {
|
|
27
|
-
collection.push(item);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
return map;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const listCommand = async (
|
|
35
|
-
path: string = "",
|
|
36
|
-
options: Partial<
|
|
37
|
-
{
|
|
38
|
-
verbose: boolean;
|
|
39
|
-
} & RenderOptions
|
|
40
|
-
> = {},
|
|
41
|
-
// eslint-disable-next-line radar/cognitive-complexity
|
|
42
|
-
) => {
|
|
43
|
-
const routePath = join(process.cwd(), path);
|
|
44
|
-
|
|
45
|
-
const apiRouteFiles = await collectApiRouteFiles(routePath, options.verbose || false);
|
|
46
|
-
|
|
47
|
-
if (apiRouteFiles.length === 0) {
|
|
48
|
-
// eslint-disable-next-line no-console
|
|
49
|
-
console.error(`No API routes found, in "${routePath}".`);
|
|
50
|
-
// eslint-disable-next-line unicorn/no-process-exit
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let parsedApiRoutes = apiRouteFiles.flatMap((apiRouteFile) => apiRouteFileParser(apiRouteFile, routePath, options.verbose || false));
|
|
55
|
-
|
|
56
|
-
if (options.includePaths) {
|
|
57
|
-
parsedApiRoutes = options.includePaths.flatMap((ipath) => parsedApiRoutes.filter((route) => route.path.startsWith(ipath)));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (options.excludePaths) {
|
|
61
|
-
parsedApiRoutes = options.excludePaths.flatMap((epath) => parsedApiRoutes.filter((route) => !route.path.startsWith(epath)));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (typeof options.group !== "undefined") {
|
|
65
|
-
const groupedMap = groupBy(parsedApiRoutes, (route) => {
|
|
66
|
-
if (options.group === "path") {
|
|
67
|
-
return route.path.replace("/pages", "").split("/")[1];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return route.tags[0] || "unsorted";
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
let counter = 0;
|
|
74
|
-
|
|
75
|
-
groupedMap.forEach((routes, key) => {
|
|
76
|
-
if (counter > 0) {
|
|
77
|
-
// eslint-disable-next-line no-console
|
|
78
|
-
console.log();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const dotsCount = (process.stdout.columns - 16 - key.length) / 2;
|
|
82
|
-
const dots = dotsCount > 0 ? Array.from({ length: dotsCount }).fill(" ").join("") : "";
|
|
83
|
-
// eslint-disable-next-line no-console
|
|
84
|
-
console.log(dots + colors.bold.underline(key));
|
|
85
|
-
|
|
86
|
-
routesRender(routes, options).forEach((renderedRoute) => {
|
|
87
|
-
// eslint-disable-next-line no-console
|
|
88
|
-
console.log(renderedRoute);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
counter += 1;
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
routesRender([], options).forEach((renderedRoute) => {
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
|
-
console.log(renderedRoute);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
// });
|
|
100
|
-
|
|
101
|
-
// eslint-disable-next-line no-console
|
|
102
|
-
console.log(`\n Listed ${colors.greenBright(String(apiRouteFiles.length))} HTTP ${apiRouteFiles.length === 1 ? "route" : "routes"}.\n`);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export default listCommand;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import colors from "chalk";
|
|
3
|
-
|
|
4
|
-
import type { Route } from "./types";
|
|
5
|
-
|
|
6
|
-
const renderRoute = (method: string, routePath: string): string => {
|
|
7
|
-
const colorMap = {
|
|
8
|
-
GET: colors.blue,
|
|
9
|
-
POST: colors.yellow,
|
|
10
|
-
PATCH: colors.yellow,
|
|
11
|
-
PUT: colors.yellow,
|
|
12
|
-
DELETE: colors.redBright,
|
|
13
|
-
OPTIONS: colors.hex("#6C7280"),
|
|
14
|
-
ANY: colors.redBright,
|
|
15
|
-
HEAD: colors.hex("#6C7280"),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
let methodText: string;
|
|
19
|
-
|
|
20
|
-
if (method === "GET|HEAD") {
|
|
21
|
-
methodText = `${colors.blue("GET")}${colors.grey("|HEAD")}`;
|
|
22
|
-
} else {
|
|
23
|
-
const coloredMethod = colorMap[method as keyof typeof colorMap](method);
|
|
24
|
-
|
|
25
|
-
methodText = method === "GET" ? `${coloredMethod}${colors.grey("|HEAD")}` : coloredMethod;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const spacesCount = method === "GET" ? 6 : 14 - method.length;
|
|
29
|
-
const spaces = Array.from({ length: spacesCount }).fill(" ").join("");
|
|
30
|
-
|
|
31
|
-
const dotsCount = process.stdout.columns - 16 - routePath.length - 4;
|
|
32
|
-
const dots = dotsCount > 0 ? Array.from({ length: dotsCount }).fill(".").join("") : "";
|
|
33
|
-
|
|
34
|
-
const routeText = routePath
|
|
35
|
-
.split("/")
|
|
36
|
-
.map((segment) => {
|
|
37
|
-
const isDynamicSegment = ["[", ":"].includes(segment[0] || "");
|
|
38
|
-
|
|
39
|
-
return isDynamicSegment ? colors.yellowBright(segment) : segment;
|
|
40
|
-
})
|
|
41
|
-
.join("/");
|
|
42
|
-
|
|
43
|
-
return ` ${methodText}${spaces}${routeText}${colors.grey(dots)}`;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const routesRender = (routesMap: Route[], options: { methods?: string[]; } = {}) => routesMap
|
|
47
|
-
.map((route) => {
|
|
48
|
-
if (options.methods && options.methods.includes(route.method)) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (route.method === "GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS") {
|
|
53
|
-
// eslint-disable-next-line no-param-reassign
|
|
54
|
-
route.method = "ANY";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// eslint-disable-next-line consistent-return
|
|
58
|
-
return renderRoute(route.method, route.path.replace("/pages", ""));
|
|
59
|
-
})
|
|
60
|
-
.filter(Boolean);
|
|
61
|
-
|
|
62
|
-
export default routesRender;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Route = { path: string; method: string; tags: any; file: string };
|
package/src/next/index-server.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { createNodeRouter } from "@visulima/connect";
|
|
2
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies
|
|
3
|
-
import type { ModelsToOpenApiParameters } from "@visulima/crud";
|
|
4
|
-
import type { NextApiRequest, NextApiResponse } from "next";
|
|
5
|
-
|
|
6
|
-
import swaggerHandler from "../../../swagger/swagger-handler";
|
|
7
|
-
|
|
8
|
-
// eslint-disable-next-line max-len
|
|
9
|
-
const swaggerApiRoute = (
|
|
10
|
-
options: Partial<{
|
|
11
|
-
allowedMediaTypes: { [key: string]: boolean };
|
|
12
|
-
swaggerFilePath: string;
|
|
13
|
-
crud: Exclude<ModelsToOpenApiParameters, "swagger.allowedMediaTypes">;
|
|
14
|
-
}> = {},
|
|
15
|
-
) => {
|
|
16
|
-
const handler = swaggerHandler(options);
|
|
17
|
-
|
|
18
|
-
const router = createNodeRouter<NextApiRequest, NextApiResponse>().get(handler);
|
|
19
|
-
|
|
20
|
-
return router.handler();
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default swaggerApiRoute;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
2
|
-
import debug from "debug";
|
|
3
|
-
import type { GetStaticProps } from "next";
|
|
4
|
-
import type { OAS3Definition } from "swagger-jsdoc";
|
|
5
|
-
|
|
6
|
-
// eslint-disable-next-line testing-library/no-debugging-utils
|
|
7
|
-
const swaggerDebug = debug("visulima:api-platform:swagger:get-static-properties-swagger");
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
10
|
-
const getStaticProps: (
|
|
11
|
-
swaggerUrl: string,
|
|
12
|
-
) => GetStaticProps = (swaggerUrl) => async (): Promise<{
|
|
13
|
-
props: {
|
|
14
|
-
swaggerUrl: string;
|
|
15
|
-
swaggerData: OAS3Definition;
|
|
16
|
-
};
|
|
17
|
-
}> => {
|
|
18
|
-
// eslint-disable-next-line compat/compat
|
|
19
|
-
const response = await fetch(swaggerUrl);
|
|
20
|
-
const swaggerData = await response.json();
|
|
21
|
-
|
|
22
|
-
swaggerDebug(swaggerData);
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
props: {
|
|
26
|
-
swaggerUrl,
|
|
27
|
-
swaggerData: JSON.parse(JSON.stringify(swaggerData)),
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export default getStaticProps;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import type { InferGetStaticPropsType, NextPage } from "next";
|
|
3
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
4
|
-
import Head from "next/head";
|
|
5
|
-
import React from "react";
|
|
6
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
|
-
import type { RedocStandaloneProps } from "redoc";
|
|
8
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
-
import { RedocStandalone } from "redoc";
|
|
10
|
-
|
|
11
|
-
import getStaticProps from "./get-static-properties-swagger";
|
|
12
|
-
|
|
13
|
-
// eslint-disable-next-line max-len
|
|
14
|
-
const RedocApiDocument: (
|
|
15
|
-
name: string,
|
|
16
|
-
swagger?: Exclude<RedocStandaloneProps, "spec">,
|
|
17
|
-
// eslint-disable-next-line max-len,unicorn/no-useless-undefined
|
|
18
|
-
) => NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (name, swagger = {}) => ({ swaggerData }: InferGetStaticPropsType<typeof getStaticProps>) => (
|
|
19
|
-
<>
|
|
20
|
-
<Head>
|
|
21
|
-
<title>{name}</title>
|
|
22
|
-
<style>
|
|
23
|
-
{`
|
|
24
|
-
body {
|
|
25
|
-
background: #fafafa !important;
|
|
26
|
-
}
|
|
27
|
-
`}
|
|
28
|
-
</style>
|
|
29
|
-
</Head>
|
|
30
|
-
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
31
|
-
<RedocStandalone {...swagger} spec={swaggerData} />
|
|
32
|
-
</>
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
export default RedocApiDocument;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { InferGetStaticPropsType, NextPage } from "next";
|
|
2
|
-
import dynamic from "next/dynamic";
|
|
3
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
4
|
-
import Head from "next/head";
|
|
5
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
6
|
-
import React from "react";
|
|
7
|
-
import type { SwaggerUIProps } from "swagger-ui-react";
|
|
8
|
-
|
|
9
|
-
import getStaticProps from "./get-static-properties-swagger";
|
|
10
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
|
-
const SwaggerUI = dynamic<{
|
|
12
|
-
spec: any;
|
|
13
|
-
// @ts-ignore
|
|
14
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
15
|
-
}>(import("swagger-ui-react"), { ssr: false });
|
|
16
|
-
dynamic<{
|
|
17
|
-
spec: any;
|
|
18
|
-
// @ts-ignore
|
|
19
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
20
|
-
}>(import("swagger-ui-react/swagger-ui.css"), { ssr: false });
|
|
21
|
-
|
|
22
|
-
// eslint-disable-next-line max-len
|
|
23
|
-
const SwaggerApiDocument: (
|
|
24
|
-
name: string,
|
|
25
|
-
swagger?: Exclude<SwaggerUIProps, "spec">,
|
|
26
|
-
// eslint-disable-next-line max-len,unicorn/no-useless-undefined
|
|
27
|
-
) => NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (name, swagger = {}) => ({ swaggerData }: InferGetStaticPropsType<typeof getStaticProps>) => (
|
|
28
|
-
<>
|
|
29
|
-
<Head>
|
|
30
|
-
<title>{name}</title>
|
|
31
|
-
<style>
|
|
32
|
-
{`
|
|
33
|
-
body {
|
|
34
|
-
background: #fafafa !important;
|
|
35
|
-
}
|
|
36
|
-
`}
|
|
37
|
-
</style>
|
|
38
|
-
</Head>
|
|
39
|
-
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
40
|
-
<SwaggerUI {...swagger} spec={swaggerData} />
|
|
41
|
-
</>
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
export default SwaggerApiDocument;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { BaseDefinition } from "@visulima/jsdoc-open-api";
|
|
2
|
-
import { SwaggerCompilerPlugin } from "@visulima/jsdoc-open-api";
|
|
3
|
-
import type { NextConfig } from "next";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import type { Configuration } from "webpack";
|
|
7
|
-
|
|
8
|
-
const withOpenApi = ({
|
|
9
|
-
definition, sources, verbose, output = "swagger/swagger.json",
|
|
10
|
-
// eslint-disable-next-line max-len
|
|
11
|
-
}: { definition: Exclude<BaseDefinition, "openapi"> & { openapi?: string }; sources: string[]; verbose?: boolean; output: string }) => (nextConfig: NextConfig) => {
|
|
12
|
-
return {
|
|
13
|
-
...nextConfig,
|
|
14
|
-
webpack: (config: Configuration, options: any) => {
|
|
15
|
-
if (!options.isServer) {
|
|
16
|
-
return config;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (output.startsWith("/")) {
|
|
20
|
-
// eslint-disable-next-line no-param-reassign
|
|
21
|
-
output = output.slice(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!output.endsWith(".json")) {
|
|
25
|
-
throw new Error("The output path must end with .json");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// eslint-disable-next-line no-param-reassign
|
|
29
|
-
config = {
|
|
30
|
-
...config,
|
|
31
|
-
plugins: [
|
|
32
|
-
// @ts-ignore
|
|
33
|
-
...config.plugins,
|
|
34
|
-
new SwaggerCompilerPlugin(
|
|
35
|
-
`${options.dir}/${output}`,
|
|
36
|
-
sources.map((source) => {
|
|
37
|
-
const combinedPath = path.join(options.dir, source.replace("./", ""));
|
|
38
|
-
|
|
39
|
-
// Check if the path is a directory
|
|
40
|
-
fs.lstatSync(combinedPath).isDirectory();
|
|
41
|
-
|
|
42
|
-
return combinedPath;
|
|
43
|
-
}),
|
|
44
|
-
{
|
|
45
|
-
// @ts-ignore
|
|
46
|
-
openapi: "3.0.0",
|
|
47
|
-
...definition,
|
|
48
|
-
},
|
|
49
|
-
{ verbose },
|
|
50
|
-
),
|
|
51
|
-
],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
if (typeof nextConfig.webpack === "function") {
|
|
55
|
-
return nextConfig.webpack(config, options);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return config;
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export default withOpenApi;
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { header as headerCase } from "case";
|
|
2
|
-
import type { OpenAPIV3 } from "openapi-types";
|
|
3
|
-
import type { OAS3Definition, Operation, Responses } from "swagger-jsdoc";
|
|
4
|
-
|
|
5
|
-
const extendComponentSchemas = (spec: Partial<OAS3Definition>, schemaName: string, schema: OpenAPIV3.SchemaObject) => {
|
|
6
|
-
if (typeof spec.components !== "object") {
|
|
7
|
-
// eslint-disable-next-line no-param-reassign
|
|
8
|
-
spec.components = {};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (typeof spec.components.schemas !== "object") {
|
|
12
|
-
// eslint-disable-next-line no-param-reassign
|
|
13
|
-
spec.components.schemas = {};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (typeof spec.components.schemas[schemaName] === "undefined") {
|
|
17
|
-
// eslint-disable-next-line no-param-reassign
|
|
18
|
-
spec.components.schemas[schemaName] = schema;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const extendComponentExamples = (spec: Partial<OAS3Definition>, exampleName: string, example: OpenAPIV3.SchemaObject) => {
|
|
23
|
-
if (typeof spec.components !== "object") {
|
|
24
|
-
// eslint-disable-next-line no-param-reassign
|
|
25
|
-
spec.components = {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (typeof spec.components.examples !== "object") {
|
|
29
|
-
// eslint-disable-next-line no-param-reassign
|
|
30
|
-
spec.components.examples = {};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (typeof spec.components.examples[exampleName] === "undefined") {
|
|
34
|
-
// eslint-disable-next-line no-param-reassign
|
|
35
|
-
spec.components.examples[exampleName] = example;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
function extendSwaggerWithMediaTypeSchema(
|
|
40
|
-
responseSpec: OpenAPIV3.ResponseObject,
|
|
41
|
-
allowedMediaTypes: { [p: string]: boolean } | undefined,
|
|
42
|
-
pathKey: string,
|
|
43
|
-
spec: Partial<OAS3Definition>,
|
|
44
|
-
methodSpec: Operation,
|
|
45
|
-
status: string,
|
|
46
|
-
) {
|
|
47
|
-
let examples:
|
|
48
|
-
| {
|
|
49
|
-
[media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;
|
|
50
|
-
}
|
|
51
|
-
| undefined;
|
|
52
|
-
|
|
53
|
-
Object.entries(responseSpec.content as object).forEach(([mediaName, contentSpec]) => {
|
|
54
|
-
if (typeof contentSpec.schema === "object") {
|
|
55
|
-
const { schema } = contentSpec;
|
|
56
|
-
|
|
57
|
-
if (mediaName === "application/json" && typeof contentSpec.examples !== "undefined") {
|
|
58
|
-
examples = contentSpec.examples;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (typeof (schema as OpenAPIV3.ReferenceObject).$ref !== "undefined") {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const schemaIsArray = (schema as OpenAPIV3.SchemaObject).type === "array";
|
|
66
|
-
|
|
67
|
-
Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
|
|
68
|
-
if (!allowed) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// eslint-disable-next-line max-len
|
|
73
|
-
const schemaName = `${headerCase(pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
|
|
74
|
-
|
|
75
|
-
extendComponentSchemas(spec as OAS3Definition, schemaName, schema as OpenAPIV3.SchemaObject);
|
|
76
|
-
|
|
77
|
-
if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.schema === "undefined") {
|
|
78
|
-
// eslint-disable-next-line no-param-reassign
|
|
79
|
-
(methodSpec.responses as Responses)[status].content[mediaType] = { schema: {} };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// eslint-disable-next-line no-param-reassign
|
|
83
|
-
(methodSpec.responses as Responses)[status].content[mediaType].schema = schemaIsArray
|
|
84
|
-
? {
|
|
85
|
-
type: "array",
|
|
86
|
-
items: {
|
|
87
|
-
$ref: `#/components/schemas/${schemaName}`,
|
|
88
|
-
},
|
|
89
|
-
}
|
|
90
|
-
: {
|
|
91
|
-
$ref: `#/components/schemas/${schemaName}`,
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
if (typeof methodSpec.produces === "undefined") {
|
|
95
|
-
// eslint-disable-next-line no-param-reassign
|
|
96
|
-
methodSpec.produces = [];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
methodSpec.produces.push(mediaType);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return examples;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function extendSwaggerWithMediaTypeExamples(
|
|
108
|
-
responseSpec: OpenAPIV3.ResponseObject,
|
|
109
|
-
allowedMediaTypes: { [p: string]: boolean } | undefined,
|
|
110
|
-
pathKey: string,
|
|
111
|
-
spec: Partial<OAS3Definition>,
|
|
112
|
-
examples: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject } | undefined,
|
|
113
|
-
methodSpec: Operation,
|
|
114
|
-
status: string,
|
|
115
|
-
) {
|
|
116
|
-
Object.keys(responseSpec.content as object).forEach((mediaName) => {
|
|
117
|
-
if (mediaName === "application/json") {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
|
|
122
|
-
if (!allowed) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// eslint-disable-next-line max-len
|
|
127
|
-
const examplesName = `${headerCase(pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
|
|
128
|
-
|
|
129
|
-
extendComponentExamples(spec as OAS3Definition, examplesName, examples as OpenAPIV3.SchemaObject);
|
|
130
|
-
|
|
131
|
-
if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.examples === "undefined") {
|
|
132
|
-
// eslint-disable-next-line no-param-reassign
|
|
133
|
-
(methodSpec.responses as Responses)[status].content[mediaType] = { examples: {} };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// eslint-disable-next-line no-param-reassign
|
|
137
|
-
(methodSpec.responses as Responses)[status].content[mediaType].examples = examples;
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// eslint-disable-next-line radar/cognitive-complexity
|
|
143
|
-
export default function extendSwaggerSpec(spec: Partial<OAS3Definition>, allowedMediaTypes?: { [key: string]: boolean }): Partial<OAS3Definition> {
|
|
144
|
-
if (typeof spec === "object" && typeof spec.paths === "object") {
|
|
145
|
-
Object.entries(spec.paths).forEach(([pathKey, pathSpec]) => {
|
|
146
|
-
Object.values(pathSpec).forEach((methodSpec) => {
|
|
147
|
-
if (typeof methodSpec.responses === "object") {
|
|
148
|
-
Object.entries<OpenAPIV3.ResponseObject>(methodSpec.responses).forEach(([status, responseSpec]) => {
|
|
149
|
-
if (typeof responseSpec.content === "object") {
|
|
150
|
-
let examples:
|
|
151
|
-
| {
|
|
152
|
-
[media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;
|
|
153
|
-
}
|
|
154
|
-
| undefined = extendSwaggerWithMediaTypeSchema(responseSpec, allowedMediaTypes, pathKey, spec, methodSpec, status);
|
|
155
|
-
|
|
156
|
-
if (typeof examples !== "undefined") {
|
|
157
|
-
extendSwaggerWithMediaTypeExamples(responseSpec, allowedMediaTypes, pathKey, spec, examples, methodSpec, status);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return spec;
|
|
167
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies
|
|
2
|
-
import type { ModelsToOpenApiParameters, SwaggerModelsConfig } from "@visulima/crud";
|
|
3
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies
|
|
4
|
-
import { modelsToOpenApi } from "@visulima/crud";
|
|
5
|
-
import debug from "debug";
|
|
6
|
-
import merge from "lodash.merge";
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
-
import path from "node:path";
|
|
10
|
-
import type { OAS3Definition, Tag } from "swagger-jsdoc";
|
|
11
|
-
|
|
12
|
-
import yamlTransformer from "../connect/serializers/yaml";
|
|
13
|
-
import extendSwaggerSpec from "./extend-swagger-spec";
|
|
14
|
-
|
|
15
|
-
// eslint-disable-next-line testing-library/no-debugging-utils
|
|
16
|
-
const swaggerCrudDebug = debug("visulima:api-platform:swagger:crud:get-static-properties-swagger");
|
|
17
|
-
|
|
18
|
-
const swaggerHandler = (
|
|
19
|
-
options: Partial<{
|
|
20
|
-
allowedMediaTypes: { [key: string]: boolean };
|
|
21
|
-
swaggerFilePath: string;
|
|
22
|
-
crud: Exclude<ModelsToOpenApiParameters, "swagger"> & {
|
|
23
|
-
swagger?: {
|
|
24
|
-
models?: SwaggerModelsConfig<string>;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
}> = {},
|
|
28
|
-
) => {
|
|
29
|
-
const {
|
|
30
|
-
allowedMediaTypes = {
|
|
31
|
-
"application/json": true,
|
|
32
|
-
},
|
|
33
|
-
swaggerFilePath,
|
|
34
|
-
crud,
|
|
35
|
-
} = options;
|
|
36
|
-
|
|
37
|
-
return async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response) => {
|
|
38
|
-
const swaggerPath = path.join(process.cwd(), swaggerFilePath || "swagger/swagger.json");
|
|
39
|
-
|
|
40
|
-
if (!existsSync(swaggerPath)) {
|
|
41
|
-
throw new Error(`Swagger file not found at ${swaggerPath}. Did you change the output path in "withOpenApi" inside the next.config.js file?`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const fileContents = readFileSync(swaggerPath, "utf8");
|
|
45
|
-
|
|
46
|
-
let spec = extendSwaggerSpec(JSON.parse(fileContents) as OAS3Definition, allowedMediaTypes) as OAS3Definition;
|
|
47
|
-
let crudSwagger: Partial<OAS3Definition> = {};
|
|
48
|
-
|
|
49
|
-
if (typeof crud !== "undefined") {
|
|
50
|
-
try {
|
|
51
|
-
const modelsOpenApi = await modelsToOpenApi(crud);
|
|
52
|
-
|
|
53
|
-
crudSwagger = {
|
|
54
|
-
components: { schemas: modelsOpenApi.schemas, examples: modelsOpenApi.examples },
|
|
55
|
-
tags: modelsOpenApi.tags as Tag[],
|
|
56
|
-
paths: modelsOpenApi.paths,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
crudSwagger = extendSwaggerSpec(crudSwagger, allowedMediaTypes);
|
|
60
|
-
|
|
61
|
-
swaggerCrudDebug(JSON.stringify(crudSwagger, null, 2));
|
|
62
|
-
|
|
63
|
-
spec = merge(spec, crudSwagger);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
// eslint-disable-next-line no-console
|
|
66
|
-
console.log(error);
|
|
67
|
-
throw new Error("Please install @visulima/crud to use the crud swagger generator.");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (typeof request.headers.accept === "string" && /yaml|yml/.test(request.headers.accept)) {
|
|
72
|
-
response.statusCode = 200;
|
|
73
|
-
response.setHeader("Content-Type", request.headers.accept);
|
|
74
|
-
response.end(yamlTransformer(spec));
|
|
75
|
-
} else {
|
|
76
|
-
response.statusCode = 200;
|
|
77
|
-
response.setHeader("Content-Type", "application/json");
|
|
78
|
-
response.end(JSON.stringify(spec, null, 2));
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export default swaggerHandler;
|