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.
- package/README.md +271 -64
- package/dist/cjs/index.cjs +155 -11
- package/dist/cjs/index.d.ts +47 -2
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/loadRoutes.cjs +158 -22
- package/dist/cjs/loadRoutes.d.ts +16 -1
- package/dist/cjs/loadRoutes.d.ts.map +1 -0
- package/dist/cjs/loadRoutes.js.map +1 -0
- package/dist/cjs/utils.cjs +113 -2
- package/dist/cjs/utils.d.ts +43 -1
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/index.d.ts +53 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +117 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/loadRoutes.d.ts +17 -0
- package/dist/esm/loadRoutes.d.ts.map +1 -0
- package/dist/esm/loadRoutes.js +166 -0
- package/dist/esm/loadRoutes.js.map +1 -0
- package/dist/esm/utils.d.ts +44 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +112 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +52 -11
|
@@ -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.
|
|
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":
|
|
14
|
-
|
|
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": "
|
|
23
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
34
|
-
"
|
|
53
|
+
"ts-jest": "^29.1.0",
|
|
54
|
+
"typescript": "^5.2.2"
|
|
35
55
|
},
|
|
36
|
-
"keywords": [
|
|
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
|
}
|