Skip to content

Commit a390719

Browse files
committed
add include/exclude options to generateOpenApiSpec
1 parent c71ec84 commit a390719

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

src/core/dir.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from "@jest/globals";
2+
import { filterDirectoryItems } from "./dir";
3+
4+
describe("filterDirectoryItems", () => {
5+
const rootPath = "/projects/app/src/app";
6+
it("should filter out items correctly", () => {
7+
const items = [
8+
"/projects/app/src/app/users/route.ts",
9+
];
10+
const result = filterDirectoryItems(rootPath, items, [], []);
11+
expect(result).toStrictEqual([
12+
"/projects/app/src/app/users/route.ts",
13+
]);
14+
});
15+
it("should handle wildcards correctly with include list", () => {
16+
const items = [
17+
"/projects/app/src/app/a/b/c/d/route.ts",
18+
"/projects/app/src/app/a/b/b/d/route.ts",
19+
];
20+
const include = [
21+
"a/*/c/d/route.ts",
22+
];
23+
const result = filterDirectoryItems(rootPath, items, include, []);
24+
expect(result).toStrictEqual([
25+
"/projects/app/src/app/a/b/c/d/route.ts",
26+
]);
27+
});
28+
it("should handle wildcards correctly with exclude list", () => {
29+
const items = [
30+
"/projects/app/src/app/users/route.ts",
31+
"/projects/app/src/app/a/b/c/d/route.ts",
32+
"/projects/app/src/app/messages/route.ts",
33+
];
34+
const include = [
35+
"**/route.ts",
36+
];
37+
const exclude = [
38+
"messages/route.ts",
39+
"a/**/d/route.ts",
40+
];
41+
const result = filterDirectoryItems(rootPath, items, include, exclude);
42+
expect(result).toStrictEqual([
43+
"/projects/app/src/app/users/route.ts",
44+
]);
45+
});
46+
});

src/core/dir.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { constants } from "fs";
22
import fs from "fs/promises";
33
import path from "node:path";
4+
import { Minimatch } from "minimatch";
45

56
export async function directoryExists(dirPath: string) {
67
try {
@@ -26,3 +27,15 @@ export async function getDirectoryItems(dirPath: string, targetFileName: string)
2627
}
2728
return collection;
2829
}
30+
31+
export function filterDirectoryItems(rootPath: string, items: string[], include: string[], exclude: string[]) {
32+
const includedPatterns = include.map(pattern => new Minimatch(pattern));
33+
const excludedPatterns = exclude.map(pattern => new Minimatch(pattern));
34+
35+
return items.filter(item => {
36+
const relativePath = path.relative(rootPath, item);
37+
const isIncluded = includedPatterns.some(pattern => pattern.match(relativePath));
38+
const isExcluded = excludedPatterns.some(pattern => pattern.match(relativePath));
39+
return (isIncluded || !include.length) && !isExcluded;
40+
});
41+
}

src/core/options.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, it } from "@jest/globals";
2+
import { verifyOptions } from "./options";
3+
4+
describe("verifyOptions", () => {
5+
it("should filter out invalid route handler paths", () => {
6+
const { include, exclude } = verifyOptions([
7+
"another_file.ts",
8+
"folder_name/route.ts",
9+
"**/route.ts",
10+
], [
11+
"another_file.ts",
12+
"folder_name/route.ts",
13+
"**/route.ts",
14+
]);
15+
expect(include).toStrictEqual([
16+
"folder_name/route.ts",
17+
"**/route.ts",
18+
]);
19+
expect(exclude).toStrictEqual([
20+
"folder_name/route.ts",
21+
"**/route.ts",
22+
]);
23+
});
24+
});

src/core/options.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable no-console */
2+
3+
export function verifyOptions(include: string[] = [], exclude: string[] = []) {
4+
if (process.env.NODE_ENV === "development") {
5+
for (const item of include) {
6+
if (!item.endsWith("/route.ts")) {
7+
console.log(`${item} is not a valid route handler path`);
8+
}
9+
}
10+
for (const item of exclude) {
11+
if (!item.endsWith("/route.ts")) {
12+
console.log(`${item} is not a valid route handler path`);
13+
}
14+
}
15+
}
16+
return {
17+
include: include.filter(item => item.endsWith("/route.ts")),
18+
exclude: exclude.filter(item => item.endsWith("/route.ts")),
19+
};
20+
}

src/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import getPackageMetadata from "@omer-x/package-metadata";
2-
import { getDirectoryItems } from "./core/dir";
2+
import { filterDirectoryItems, getDirectoryItems } from "./core/dir";
33
import { findAppFolderPath, getRouteExports } from "./core/next";
4+
import { verifyOptions } from "./core/options";
45
import { type RouteRecord, bundlePaths, createRouteRecord } from "./core/route";
56
import { bundleSchemas } from "./core/schema";
67
import type { OpenApiDocument } from "@omer-x/openapi-types";
78
import type { ZodType } from "zod";
89

9-
export default async function generateOpenApiSpec(schemas: Record<string, ZodType>) {
10+
type GeneratorOptions = {
11+
include?: string[],
12+
exclude?: string[],
13+
};
14+
15+
export default async function generateOpenApiSpec(schemas: Record<string, ZodType>, options?: GeneratorOptions) {
16+
const verifiedOptions = verifyOptions(options?.include, options?.exclude);
1017
const appFolderPath = await findAppFolderPath();
1118
if (!appFolderPath) throw new Error("This is not a Next.js application!");
1219
const routes = await getDirectoryItems(appFolderPath, "route.ts");
20+
const verifiedRoutes = filterDirectoryItems(appFolderPath, routes, verifiedOptions.include, verifiedOptions.exclude);
1321
const validRoutes: RouteRecord[] = [];
14-
for (const route of routes) {
22+
for (const route of verifiedRoutes) {
1523
const exportedRouteHandlers = await getRouteExports(route, schemas);
1624
for (const [method, routeHandler] of Object.entries(exportedRouteHandlers)) {
1725
if (!routeHandler || !routeHandler.apiData) continue;

0 commit comments

Comments
 (0)