exrout 1.0.4 → 1.1.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.
@@ -0,0 +1,166 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import { Router } from "express";
5
+ import { normalizeRouteName, cleanSlashes, shouldExclude, extractHttpMethod, getRouteFromMethodFile, sortRoutes, } from "./utils.js";
6
+ /**
7
+ * Recursively loads route files from a directory structure
8
+ */
9
+ export default async function loadRoutes(app, dir, options = {}) {
10
+ const { prefix = "", middleware = [], log = false, exclude = [] } = options;
11
+ const loadedRoutes = [];
12
+ async function processDirectory(currentDir, baseRoute) {
13
+ let files;
14
+ try {
15
+ files = fs.readdirSync(currentDir);
16
+ }
17
+ catch (error) {
18
+ const err = error;
19
+ if (err.code === "ENOENT") {
20
+ console.warn(`[exrout] Warning: Directory not found: ${currentDir}`);
21
+ return;
22
+ }
23
+ throw new Error(`[exrout] Failed to read directory "${currentDir}": ${err.message}`);
24
+ }
25
+ // Collect routes for sorting
26
+ const routesToProcess = [];
27
+ for (const file of files) {
28
+ const fullPath = path.join(currentDir, file);
29
+ // Check exclusion patterns
30
+ if (shouldExclude(file, exclude)) {
31
+ if (log) {
32
+ console.log(`[exrout] Skipped (excluded): ${file}`);
33
+ }
34
+ continue;
35
+ }
36
+ let stat;
37
+ try {
38
+ stat = fs.statSync(fullPath);
39
+ }
40
+ catch (error) {
41
+ console.warn(`[exrout] Warning: Cannot stat file: ${fullPath}`);
42
+ continue;
43
+ }
44
+ if (stat.isDirectory()) {
45
+ routesToProcess.push({
46
+ fullPath,
47
+ routePath: cleanSlashes(`${baseRoute}/${normalizeRouteName(file)}`),
48
+ isDirectory: true,
49
+ });
50
+ continue;
51
+ }
52
+ // Only process .js and .ts files (not .d.ts)
53
+ if (!file.match(/\.(js|ts)$/) || file.endsWith(".d.ts")) {
54
+ continue;
55
+ }
56
+ const name = file.replace(/\.(js|ts)$/, "");
57
+ const isIndex = name === "index";
58
+ const httpMethod = extractHttpMethod(file);
59
+ let routePath;
60
+ if (httpMethod) {
61
+ // HTTP method file: get.ts -> route handled with specific method
62
+ const routeName = getRouteFromMethodFile(file);
63
+ routePath = cleanSlashes(routeName
64
+ ? `${baseRoute}/${normalizeRouteName(routeName)}`
65
+ : baseRoute);
66
+ }
67
+ else {
68
+ // Regular file: users.ts -> /users
69
+ routePath = cleanSlashes(isIndex ? baseRoute : `${baseRoute}/${normalizeRouteName(name)}`);
70
+ }
71
+ routesToProcess.push({
72
+ fullPath,
73
+ routePath,
74
+ isDirectory: false,
75
+ httpMethod: httpMethod || undefined,
76
+ });
77
+ }
78
+ // Sort routes for consistent order (static before dynamic)
79
+ const sortedRoutePaths = sortRoutes(routesToProcess.filter((r) => !r.isDirectory).map((r) => r.routePath));
80
+ // Process directories first (recursively)
81
+ for (const item of routesToProcess.filter((r) => r.isDirectory)) {
82
+ await processDirectory(item.fullPath, item.routePath);
83
+ }
84
+ // Process files in sorted order
85
+ for (const routePath of sortedRoutePaths) {
86
+ const item = routesToProcess.find((r) => !r.isDirectory && r.routePath === routePath);
87
+ if (!item)
88
+ continue;
89
+ try {
90
+ // Use pathToFileURL for cross-platform compatibility
91
+ const fileUrl = pathToFileURL(item.fullPath).href;
92
+ // Clear module cache for hot reload support
93
+ if (typeof require !== "undefined" && require.cache) {
94
+ delete require.cache[require.resolve(item.fullPath)];
95
+ }
96
+ let mod;
97
+ try {
98
+ mod = await import(fileUrl);
99
+ }
100
+ catch (importError) {
101
+ // Fallback for CommonJS environments
102
+ mod = require(item.fullPath);
103
+ }
104
+ let router = mod.default || mod;
105
+ // Support async router factory functions
106
+ if (typeof router === "function" && !isRouter(router)) {
107
+ try {
108
+ router = await router();
109
+ }
110
+ catch (factoryError) {
111
+ throw new Error(`[exrout] Router factory in "${item.fullPath}" threw an error: ${factoryError.message}`);
112
+ }
113
+ }
114
+ // Validate router
115
+ if (!isRouter(router)) {
116
+ console.warn(`[exrout] Warning: "${item.fullPath}" does not export a valid Router. Skipping.`);
117
+ continue;
118
+ }
119
+ // Register route with appropriate method or use all methods
120
+ if (item.httpMethod && item.httpMethod !== "all") {
121
+ // For method-specific files, wrap in a new router
122
+ const methodRouter = Router();
123
+ methodRouter[item.httpMethod]("/", router);
124
+ app.use(routePath, ...middleware, methodRouter);
125
+ }
126
+ else {
127
+ app.use(routePath, ...middleware, router);
128
+ }
129
+ loadedRoutes.push({
130
+ path: routePath,
131
+ file: item.fullPath,
132
+ method: item.httpMethod,
133
+ });
134
+ if (log) {
135
+ const methodStr = item.httpMethod
136
+ ? ` [${item.httpMethod.toUpperCase()}]`
137
+ : "";
138
+ console.log(`[exrout] ${routePath}${methodStr}`);
139
+ }
140
+ }
141
+ catch (error) {
142
+ const err = error;
143
+ console.error(`[exrout] Error loading route "${item.fullPath}": ${err.message}`);
144
+ // Re-throw in production, continue in development
145
+ if (process.env.NODE_ENV === "production") {
146
+ throw err;
147
+ }
148
+ }
149
+ }
150
+ }
151
+ // Resolve the directory path
152
+ const resolvedDir = path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir);
153
+ await processDirectory(resolvedDir, prefix);
154
+ return loadedRoutes;
155
+ }
156
+ /**
157
+ * Type guard to check if a value is an Express Router
158
+ */
159
+ function isRouter(obj) {
160
+ return (obj &&
161
+ typeof obj === "function" &&
162
+ typeof obj.use === "function" &&
163
+ typeof obj.get === "function" &&
164
+ typeof obj.post === "function");
165
+ }
166
+ //# sourceMappingURL=loadRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadRoutes.js","sourceRoot":"","sources":["../../src/loadRoutes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Utility functions for route path processing
3
+ */
4
+ /**
5
+ * Converts bracket notation in filenames to Express route parameters
6
+ * @example normalizeRouteName("[id]") => ":id"
7
+ * @example normalizeRouteName("[userId]") => ":userId"
8
+ */
9
+ export declare function normalizeRouteName(name: string): string;
10
+ /**
11
+ * Cleans up multiple consecutive slashes and ensures consistent path format
12
+ * @example cleanSlashes("//api///users") => "/api/users"
13
+ */
14
+ export declare function cleanSlashes(routePath: string): string;
15
+ /**
16
+ * Checks if a filename should be excluded based on patterns
17
+ * @param filename The filename to check
18
+ * @param patterns Array of glob-like patterns (supports * and ?)
19
+ * @returns true if the file should be excluded
20
+ */
21
+ export declare function shouldExclude(filename: string, patterns: string[]): boolean;
22
+ /**
23
+ * Extracts HTTP method from filename if it follows the pattern
24
+ * @example extractHttpMethod("get.ts") => "get"
25
+ * @example extractHttpMethod("post.users.ts") => "post"
26
+ * @example extractHttpMethod("users.ts") => null
27
+ */
28
+ export declare function extractHttpMethod(filename: string): string | null;
29
+ /**
30
+ * Gets the route name from a method-prefixed filename
31
+ * @example getRouteFromMethodFile("get.users.ts") => "users"
32
+ * @example getRouteFromMethodFile("post.ts") => ""
33
+ */
34
+ export declare function getRouteFromMethodFile(filename: string): string;
35
+ /**
36
+ * Sorts route paths for consistent registration order
37
+ * Static routes come before dynamic routes (with :params)
38
+ */
39
+ export declare function sortRoutes(routes: string[]): string[];
40
+ /**
41
+ * Validates that a path exists and is accessible
42
+ */
43
+ export declare function validatePath(dirPath: string): void;
44
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAS3E;AAgBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYjE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS/D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAYrD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIlD"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Utility functions for route path processing
3
+ */
4
+ /**
5
+ * Converts bracket notation in filenames to Express route parameters
6
+ * @example normalizeRouteName("[id]") => ":id"
7
+ * @example normalizeRouteName("[userId]") => ":userId"
8
+ */
9
+ export function normalizeRouteName(name) {
10
+ return name.replace(/\[(.+?)\]/g, ":$1");
11
+ }
12
+ /**
13
+ * Cleans up multiple consecutive slashes and ensures consistent path format
14
+ * @example cleanSlashes("//api///users") => "/api/users"
15
+ */
16
+ export function cleanSlashes(routePath) {
17
+ // Replace multiple slashes with single slash
18
+ let cleaned = routePath.replace(/\/+/g, "/");
19
+ // Ensure path starts with /
20
+ if (cleaned && !cleaned.startsWith("/")) {
21
+ cleaned = "/" + cleaned;
22
+ }
23
+ // Remove trailing slash (except for root)
24
+ if (cleaned.length > 1 && cleaned.endsWith("/")) {
25
+ cleaned = cleaned.slice(0, -1);
26
+ }
27
+ return cleaned || "/";
28
+ }
29
+ /**
30
+ * Checks if a filename should be excluded based on patterns
31
+ * @param filename The filename to check
32
+ * @param patterns Array of glob-like patterns (supports * and ?)
33
+ * @returns true if the file should be excluded
34
+ */
35
+ export function shouldExclude(filename, patterns) {
36
+ if (!patterns || patterns.length === 0)
37
+ return false;
38
+ for (const pattern of patterns) {
39
+ if (matchPattern(filename, pattern)) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Simple glob-like pattern matching
47
+ * Supports * (any characters) and ? (single character)
48
+ */
49
+ function matchPattern(str, pattern) {
50
+ // Escape special regex chars except * and ?
51
+ const regexPattern = pattern
52
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
53
+ .replace(/\*/g, ".*")
54
+ .replace(/\?/g, ".");
55
+ return new RegExp(`^${regexPattern}$`, "i").test(str);
56
+ }
57
+ /**
58
+ * Extracts HTTP method from filename if it follows the pattern
59
+ * @example extractHttpMethod("get.ts") => "get"
60
+ * @example extractHttpMethod("post.users.ts") => "post"
61
+ * @example extractHttpMethod("users.ts") => null
62
+ */
63
+ export function extractHttpMethod(filename) {
64
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options", "all"];
65
+ const name = filename.replace(/\.(js|ts)$/, "").toLowerCase();
66
+ // Check if filename starts with HTTP method
67
+ for (const method of methods) {
68
+ if (name === method || name.startsWith(`${method}.`)) {
69
+ return method;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Gets the route name from a method-prefixed filename
76
+ * @example getRouteFromMethodFile("get.users.ts") => "users"
77
+ * @example getRouteFromMethodFile("post.ts") => ""
78
+ */
79
+ export function getRouteFromMethodFile(filename) {
80
+ const name = filename.replace(/\.(js|ts)$/, "");
81
+ const parts = name.split(".");
82
+ if (parts.length > 1) {
83
+ return parts.slice(1).join(".");
84
+ }
85
+ return "";
86
+ }
87
+ /**
88
+ * Sorts route paths for consistent registration order
89
+ * Static routes come before dynamic routes (with :params)
90
+ */
91
+ export function sortRoutes(routes) {
92
+ return [...routes].sort((a, b) => {
93
+ // Static routes first
94
+ const aHasParam = a.includes(":");
95
+ const bHasParam = b.includes(":");
96
+ if (aHasParam && !bHasParam)
97
+ return 1;
98
+ if (!aHasParam && bHasParam)
99
+ return -1;
100
+ // Alphabetical within same type
101
+ return a.localeCompare(b);
102
+ });
103
+ }
104
+ /**
105
+ * Validates that a path exists and is accessible
106
+ */
107
+ export function validatePath(dirPath) {
108
+ if (!dirPath) {
109
+ throw new Error("[exrout] Routes directory path is required");
110
+ }
111
+ }
112
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exrout",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "Automatically load and register Express routes from folder structure",
5
5
  "type": "module",
6
6
 
@@ -10,8 +10,14 @@
10
10
 
11
11
  "exports": {
12
12
  ".": {
13
- "require": "./dist/cjs/index.cjs",
14
- "import": "./dist/esm/index.js"
13
+ "require": {
14
+ "types": "./dist/cjs/index.d.ts",
15
+ "default": "./dist/cjs/index.cjs"
16
+ },
17
+ "import": {
18
+ "types": "./dist/esm/index.d.ts",
19
+ "default": "./dist/esm/index.js"
20
+ }
15
21
  }
16
22
  },
17
23
 
@@ -19,21 +25,56 @@
19
25
  "dist"
20
26
  ],
21
27
  "scripts": {
22
- "build": "tsc && tsc-esm",
23
- "test": "jest"
28
+ "build": "npm run build:cjs && npm run build:esm && npm run fix:extensions",
29
+ "build:cjs": "tsc -p tsconfig.json",
30
+ "build:esm": "tsc -p tsconfig.esm.json",
31
+ "fix:extensions": "node scripts/fix-extensions.js",
32
+ "test": "npx jest --runInBand",
33
+ "prepublishOnly": "npm run build && npm test"
24
34
  },
25
- "dependencies": {
26
- "express": "^4.18.2"
35
+ "dependencies": {},
36
+ "peerDependencies": {
37
+ "express": "^4.18.0 || ^5.0.0",
38
+ "chokidar": "^3.0.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "chokidar": {
42
+ "optional": true
43
+ }
27
44
  },
28
45
  "devDependencies": {
29
46
  "@types/express": "^4.17.17",
30
47
  "@types/jest": "^29.5.3",
48
+ "@types/supertest": "^2.0.12",
49
+ "chokidar": "^3.5.3",
50
+ "express": "^4.18.2",
31
51
  "jest": "^29.6.1",
32
52
  "supertest": "^6.3.3",
33
- "typescript": "^5.2.2",
34
- "tsc-alias": "^1.8.16"
53
+ "ts-jest": "^29.1.0",
54
+ "typescript": "^5.2.2"
35
55
  },
36
- "keywords": ["express", "routes", "auto", "typescript", "commonjs", "esm"],
56
+ "keywords": [
57
+ "express",
58
+ "routes",
59
+ "auto",
60
+ "automatic",
61
+ "loader",
62
+ "file-based",
63
+ "routing",
64
+ "typescript",
65
+ "commonjs",
66
+ "esm"
67
+ ],
37
68
  "license": "MIT",
38
- "author": "Marouane Akrich"
69
+ "author": "Marouane Akrich",
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "https://github.com/marouaneakrich/exrout"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/marouaneakrich/exrout/issues"
76
+ },
77
+ "engines": {
78
+ "node": ">=14.0.0"
79
+ }
39
80
  }