dirent-router 1.0.0
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/CLAUDE.md +33 -0
- package/README.md +102 -0
- package/bin/bin.d.ts +2 -0
- package/bin/bin.d.ts.map +1 -0
- package/bin/bin.js +24 -0
- package/bin/bin.js.map +1 -0
- package/bin/common/index.d.ts +2 -0
- package/bin/common/index.d.ts.map +1 -0
- package/bin/common/index.js +18 -0
- package/bin/common/index.js.map +1 -0
- package/bin/common/types.d.ts +4 -0
- package/bin/common/types.d.ts.map +1 -0
- package/bin/common/types.js +3 -0
- package/bin/common/types.js.map +1 -0
- package/bin/endpoint.d.ts +6 -0
- package/bin/endpoint.d.ts.map +1 -0
- package/bin/endpoint.js +42 -0
- package/bin/endpoint.js.map +1 -0
- package/bin/router.d.ts +21 -0
- package/bin/router.d.ts.map +1 -0
- package/bin/router.js +90 -0
- package/bin/router.js.map +1 -0
- package/bin/util.d.ts +3 -0
- package/bin/util.d.ts.map +1 -0
- package/bin/util.js +24 -0
- package/bin/util.js.map +1 -0
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +18 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/types.d.ts +4 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/types.js +3 -0
- package/dist/common/types.js.map +1 -0
- package/dist/endpoint.d.ts +6 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +42 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +90 -0
- package/dist/router.js.map +1 -0
- package/dist/util.d.ts +3 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +24 -0
- package/dist/util.js.map +1 -0
- package/package.json +29 -0
- package/sample-routes/companies/[id]/index.ts +3 -0
- package/sample-routes/companies/index.ts +7 -0
- package/sample-routes/companies/search.ts +3 -0
- package/sample-routes/index.ts +3 -0
- package/src/bin.ts +27 -0
- package/src/common/index.ts +1 -0
- package/src/common/types.ts +3 -0
- package/src/endpoint.test.ts +60 -0
- package/src/endpoint.ts +46 -0
- package/src/index.ts +2 -0
- package/src/router.ts +139 -0
- package/src/util.ts +30 -0
- package/tsconfig.bin.json +24 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +24 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
dirent-router is a TypeScript library and CLI that generates type-safe route definitions from a file-system directory structure (Next.js-style routing). It scans a directory for TypeScript files, extracts exported functions as HTTP method handlers, and produces a flat array of route definitions.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
- **Build**: `pnpm build` (runs both `pnpm build:lib` and `pnpm build:bin`)
|
|
12
|
+
- **Type check**: `pnpm check`
|
|
13
|
+
- **Test**: `pnpm test` (vitest)
|
|
14
|
+
- **Run single test**: `pnpm vitest run src/endpoint.test.ts`
|
|
15
|
+
- **Format**: `pnpm prettier --write .`
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
The pipeline flows: **scan directory → build file tree → parse endpoints → import modules → flatten routes**.
|
|
20
|
+
|
|
21
|
+
- `src/router.ts` — Core logic. `findRouteStructure()` recursively walks the routes directory building a `RouteStructure` tree. `findRouteDefs()` converts file paths to endpoint strings (stripping `.ts`/`index.ts`). `flattenRoutes()` dynamically imports each module, extracts exported functions as HTTP methods, and deduplicates by endpoint. Public API: `getRoutes(config)`.
|
|
22
|
+
- `src/endpoint.ts` — Parses endpoint path strings into `EndpointPart[]` segments typed as `path` or `variable` (bracket syntax like `[id]`).
|
|
23
|
+
- `src/bin.ts` — CLI entry point. Takes a directory argument, calls `getRoutes()`, outputs JSON.
|
|
24
|
+
- `src/index.ts` — Library entry re-exporting `endpoint.ts` and `router.ts`.
|
|
25
|
+
|
|
26
|
+
Route files export named functions corresponding to HTTP methods (e.g., `export const get = ...`, `export const post = ...`). Directory segments wrapped in brackets (e.g., `[id]/`) become route variables.
|
|
27
|
+
|
|
28
|
+
## TypeScript Configuration
|
|
29
|
+
|
|
30
|
+
- Module system: `nodenext` with `esnext` target
|
|
31
|
+
- Strict mode with additional strictness: `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`
|
|
32
|
+
- Library builds to `dist/`, CLI builds to `bin/`
|
|
33
|
+
- Package manager: pnpm
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# dirent-router
|
|
2
|
+
Generate routes from a directory
|
|
3
|
+
|
|
4
|
+
## Install
|
|
5
|
+
```
|
|
6
|
+
npm install dirent-router
|
|
7
|
+
# or
|
|
8
|
+
pnpm install dirent-router
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Route Directory Structure
|
|
12
|
+
|
|
13
|
+
Each `.ts` file in your routes directory becomes an endpoint. Exported function names correspond to HTTP methods (`get`, `post`, `put`, `delete`, etc.).
|
|
14
|
+
|
|
15
|
+
- `index.ts` files map to the parent directory path
|
|
16
|
+
- Other `.ts` files map to a path segment matching their filename
|
|
17
|
+
- Directory names wrapped in brackets (e.g., `[id]`) become route variables
|
|
18
|
+
|
|
19
|
+
**Example:**
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
routes/
|
|
23
|
+
├── index.ts → /
|
|
24
|
+
├── companies/
|
|
25
|
+
│ ├── index.ts → /companies
|
|
26
|
+
│ ├── search.ts → /companies/search
|
|
27
|
+
│ └── [id]/
|
|
28
|
+
│ └── index.ts → /companies/[id]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// routes/index.ts
|
|
33
|
+
export const get = () => {
|
|
34
|
+
return 'foo';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// routes/companies/index.ts
|
|
38
|
+
export const get = () => {
|
|
39
|
+
return [{ id: 'a' }, { id: 'b' }];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const post = () => {
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// routes/companies/search.ts
|
|
47
|
+
export const get = (searchQuery: string) => {
|
|
48
|
+
return { searchQuery };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// routes/companies/[id]/index.ts
|
|
52
|
+
export const get = (id: string) => {
|
|
53
|
+
return { id };
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Integration
|
|
60
|
+
```typescript
|
|
61
|
+
import fs from "fs";
|
|
62
|
+
import { getRoutes } from "dirent-router";
|
|
63
|
+
|
|
64
|
+
const routes = await getRoutes({
|
|
65
|
+
routesDir: "./path/to/your/routes",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(
|
|
69
|
+
JSON.stringify(
|
|
70
|
+
routes,
|
|
71
|
+
(_, v) => (typeof v === "function" ? `${v.name}(...)` : v),
|
|
72
|
+
2,
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Output example
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
"filepath": "sample-routes/companies/[id]",
|
|
80
|
+
"relativepath": "companies/[id]",
|
|
81
|
+
"endpoint": "/companies/[id]",
|
|
82
|
+
"args": [
|
|
83
|
+
{
|
|
84
|
+
"type": "path",
|
|
85
|
+
"value": "companies"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "variable",
|
|
89
|
+
"value": "id"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"methods": {
|
|
93
|
+
get: "[Function]"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### CLI
|
|
100
|
+
```
|
|
101
|
+
dirent-router ./path/to/your/routes
|
|
102
|
+
```
|
package/bin/bin.d.ts
ADDED
package/bin/bin.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/bin/bin.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const router_1 = require("./router");
|
|
8
|
+
const main = async () => {
|
|
9
|
+
const routesDir = process.argv[2];
|
|
10
|
+
if (!routesDir) {
|
|
11
|
+
console.error(`Please provide directory where routes are stored`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!fs_1.default.existsSync(routesDir)) {
|
|
15
|
+
console.error(`${routesDir}: No such file or directory`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const routes = await (0, router_1.getRoutes)({
|
|
19
|
+
routesDir,
|
|
20
|
+
});
|
|
21
|
+
console.log(JSON.stringify(routes, (_, v) => (typeof v === "function" ? `${v.name}(...)` : v), 2));
|
|
22
|
+
};
|
|
23
|
+
main().catch((e) => console.error(e));
|
|
24
|
+
//# sourceMappingURL=bin.js.map
|
package/bin/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";;;;;AAAA,4CAAoB;AACpB,qCAAqC;AAErC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;IACtB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAClE,OAAO;IACT,CAAC;IACD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,6BAA6B,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAS,EAAC;QAC7B,SAAS;KACV,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,MAAM,EACN,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAC1D,CAAC,CACF,CACF,CAAC;AACJ,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,YAAY,EAwCxD,CAAC"}
|
package/bin/endpoint.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEndpoint = void 0;
|
|
4
|
+
const parseEndpoint = (buff) => {
|
|
5
|
+
const slices = [];
|
|
6
|
+
const len = buff.length;
|
|
7
|
+
let i = 0;
|
|
8
|
+
let c = buff[i++];
|
|
9
|
+
const advance = () => {
|
|
10
|
+
c = buff[i++];
|
|
11
|
+
return c;
|
|
12
|
+
};
|
|
13
|
+
while (i <= len) {
|
|
14
|
+
if (c !== "[" && c !== '/' && i <= len) {
|
|
15
|
+
let param = "";
|
|
16
|
+
while (c !== "/" && c !== "[" && i <= len) {
|
|
17
|
+
param += c;
|
|
18
|
+
c = advance();
|
|
19
|
+
}
|
|
20
|
+
slices.push({
|
|
21
|
+
type: 'path',
|
|
22
|
+
value: param,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (c === "[") {
|
|
26
|
+
let param = "";
|
|
27
|
+
c = advance();
|
|
28
|
+
while (c !== "]" && i < len) {
|
|
29
|
+
param += c;
|
|
30
|
+
c = advance();
|
|
31
|
+
}
|
|
32
|
+
slices.push({
|
|
33
|
+
type: 'variable',
|
|
34
|
+
value: param,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
c = advance();
|
|
38
|
+
}
|
|
39
|
+
return slices;
|
|
40
|
+
};
|
|
41
|
+
exports.parseEndpoint = parseEndpoint;
|
|
42
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":";;;AAKO,MAAM,aAAa,GAAG,CAAC,IAAY,EAAkB,EAAE;IAC5D,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,GAAW,CAAC,CAAC;IAClB,IAAI,CAAC,GAAW,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACvC,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC1C,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,CAAC,GAAG,OAAO,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;gBAC5B,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QAED,CAAC,GAAG,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAxCW,QAAA,aAAa,iBAwCxB"}
|
package/bin/router.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LooseFunction } from "./common";
|
|
2
|
+
import { EndpointPart } from "./endpoint";
|
|
3
|
+
export interface RouterConfig {
|
|
4
|
+
routesDir: string;
|
|
5
|
+
}
|
|
6
|
+
export type RouteStructure = {
|
|
7
|
+
filepath: string;
|
|
8
|
+
children: RouteStructure[];
|
|
9
|
+
};
|
|
10
|
+
export type RouteMethods = Record<string, LooseFunction>;
|
|
11
|
+
export type RouteDefinition = {
|
|
12
|
+
filepath: string;
|
|
13
|
+
relativepath: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
args: EndpointPart[];
|
|
16
|
+
methods: RouteMethods;
|
|
17
|
+
children: RouteDefinition[];
|
|
18
|
+
};
|
|
19
|
+
export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
|
|
20
|
+
export declare const getRoutes: (cfg: RouterConfig) => Promise<FlatRouteDefinition[]>;
|
|
21
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAiB,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAsGpE,eAAO,MAAM,SAAS,GACpB,KAAK,YAAY,KAChB,OAAO,CAAC,mBAAmB,EAAE,CAK/B,CAAC"}
|
package/bin/router.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getRoutes = void 0;
|
|
7
|
+
const util_1 = require("./util");
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const endpoint_1 = require("./endpoint");
|
|
11
|
+
// ---- implementation ----
|
|
12
|
+
const getEntries = async (path) => {
|
|
13
|
+
const entries = (await promises_1.default.readdir(path, {
|
|
14
|
+
recursive: false,
|
|
15
|
+
withFileTypes: true,
|
|
16
|
+
})).filter((entry) => {
|
|
17
|
+
return ((entry.isDirectory() || entry.name.endsWith(".ts")) &&
|
|
18
|
+
!entry.name.includes("#") &&
|
|
19
|
+
!entry.name.includes("~"));
|
|
20
|
+
});
|
|
21
|
+
return entries;
|
|
22
|
+
};
|
|
23
|
+
const findRouteStructure = async (path) => {
|
|
24
|
+
const entries = await getEntries(path);
|
|
25
|
+
const children = (await Promise.all(entries.map(async (entry) => {
|
|
26
|
+
const abspath = path_1.default.join(entry.parentPath, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
return await findRouteStructure(abspath);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
filepath: abspath,
|
|
32
|
+
children: [],
|
|
33
|
+
};
|
|
34
|
+
}))).flat();
|
|
35
|
+
return {
|
|
36
|
+
filepath: path,
|
|
37
|
+
children: children,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const findRouteDefs = async (rootPath, structure) => {
|
|
41
|
+
const relativepath = path_1.default.relative(rootPath, structure.filepath);
|
|
42
|
+
const endpoint = "/" +
|
|
43
|
+
[relativepath]
|
|
44
|
+
.map((x) => x.replace("index.ts", "").replace(".ts", ""))
|
|
45
|
+
.map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0];
|
|
46
|
+
return {
|
|
47
|
+
filepath: structure.filepath,
|
|
48
|
+
relativepath: relativepath,
|
|
49
|
+
endpoint: endpoint,
|
|
50
|
+
args: (0, endpoint_1.parseEndpoint)(endpoint),
|
|
51
|
+
methods: {},
|
|
52
|
+
children: await Promise.all(structure.children.map(async (child) => {
|
|
53
|
+
return findRouteDefs(rootPath, child);
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const flattenRoutes = async (defs) => {
|
|
58
|
+
const items = [];
|
|
59
|
+
const q = [defs];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
while (q.length > 0) {
|
|
62
|
+
const next = q.pop();
|
|
63
|
+
if (seen.has(next.endpoint))
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(next.endpoint);
|
|
66
|
+
const methods = {};
|
|
67
|
+
try {
|
|
68
|
+
const module = await import(next.filepath);
|
|
69
|
+
for (const [k, v] of Object.entries(module)) {
|
|
70
|
+
if (typeof v === "function") {
|
|
71
|
+
methods[k] = v;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.error(e);
|
|
77
|
+
}
|
|
78
|
+
items.push((0, util_1.omit)({ ...next, methods }, "children"));
|
|
79
|
+
q.push(...next.children);
|
|
80
|
+
}
|
|
81
|
+
return (0, util_1.uniqueBy)(items, (x) => x.endpoint);
|
|
82
|
+
};
|
|
83
|
+
const getRoutes = async (cfg) => {
|
|
84
|
+
const rootPath = path_1.default.resolve(cfg.routesDir);
|
|
85
|
+
const structure = await findRouteStructure(rootPath);
|
|
86
|
+
const defs = await findRouteDefs(rootPath, structure);
|
|
87
|
+
return await flattenRoutes(defs);
|
|
88
|
+
};
|
|
89
|
+
exports.getRoutes = getRoutes;
|
|
90
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":";;;;;;AAAA,iCAAwC;AAExC,2DAA6B;AAC7B,gDAA2B;AAE3B,yCAAyD;AA0BzD,2BAA2B;AAE3B,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAE,EAAE;IACxC,MAAM,OAAO,GAAqB,CAChC,MAAM,kBAAE,CAAC,OAAO,CAAC,IAAI,EAAE;QACrB,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,IAAI;KACpB,CAAC,CACH,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,OAAO,CACL,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACnD,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YACzB,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAC1B,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,KAAK,EAAE,IAAY,EAA2B,EAAE;IACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,CACf,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAA2B,EAAE;QACnD,MAAM,OAAO,GAAG,cAAO,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC,CAAC,CACH,CACF,CAAC,IAAI,EAAE,CAAC;IAET,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,QAAQ;KACnB,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,KAAK,EACzB,QAAgB,EAChB,SAAyB,EACC,EAAE;IAC5B,MAAM,YAAY,GAAG,cAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,QAAQ,GACZ,GAAG;QACH,CAAC,YAAY,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;aACxD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;IAE5D,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,YAAY,EAAE,YAAY;QAC1B,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,IAAA,wBAAa,EAAC,QAAQ,CAAC;QAC7B,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,MAAM,OAAO,CAAC,GAAG,CACzB,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACrC,OAAO,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC,CACH;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,KAAK,EACzB,IAAqB,EACW,EAAE;IAClC,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,MAAM,CAAC,GAA2B,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;QACtB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,SAAS;QACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAExB,MAAM,OAAO,GAAiB,EAAE,CAAC;QAEjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE3C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,IAAI,OAAO,CAAC,KAAK,UAAU,EAAE,CAAC;oBAC3B,OAAmC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,IAAA,WAAI,EAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,IAAA,eAAQ,EAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEK,MAAM,SAAS,GAAG,KAAK,EAC5B,GAAiB,EACe,EAAE;IAClC,MAAM,QAAQ,GAAG,cAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACtD,OAAO,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC,CAAC;AAPW,QAAA,SAAS,aAOpB"}
|
package/bin/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,GACf,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,EACtC,IAAI,SAAS,MAAM,CAAC,EAEpB,KAAK,CAAC,EACN,GAAG,MAAM,IAAI,EAAE,KACd,IAAI,CAAC,CAAC,EAAE,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,EAAE,EACR,KAAK,GAAG,KACP,CAAC,EAUH,CAAC"}
|
package/bin/util.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uniqueBy = exports.omit = void 0;
|
|
4
|
+
const omit = (obj, ...keys) => {
|
|
5
|
+
const result = { ...obj };
|
|
6
|
+
for (const k of keys) {
|
|
7
|
+
delete result[k];
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
exports.omit = omit;
|
|
12
|
+
const uniqueBy = (arr, key) => {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
return arr.filter((x) => {
|
|
15
|
+
const k = key(x);
|
|
16
|
+
if (seen.has(k)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
seen.add(k);
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
exports.uniqueBy = uniqueBy;
|
|
24
|
+
//# sourceMappingURL=util.js.map
|
package/bin/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAIlB,GAAM,EACN,GAAG,IAAY,EACA,EAAE;IACjB,MAAM,MAAM,GAAkB,EAAE,GAAG,GAAG,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,CAAmC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAdW,QAAA,IAAI,QAcf;AAEK,MAAM,QAAQ,GAAG,CACtB,GAAQ,EACR,GAAQ,EACH,EAAE;IACP,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAbW,QAAA,QAAQ,YAanB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,YAAY,EAwCxD,CAAC"}
|
package/dist/endpoint.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEndpoint = void 0;
|
|
4
|
+
const parseEndpoint = (buff) => {
|
|
5
|
+
const slices = [];
|
|
6
|
+
const len = buff.length;
|
|
7
|
+
let i = 0;
|
|
8
|
+
let c = buff[i++];
|
|
9
|
+
const advance = () => {
|
|
10
|
+
c = buff[i++];
|
|
11
|
+
return c;
|
|
12
|
+
};
|
|
13
|
+
while (i <= len) {
|
|
14
|
+
if (c !== "[" && c !== '/' && i <= len) {
|
|
15
|
+
let param = "";
|
|
16
|
+
while (c !== "/" && c !== "[" && i <= len) {
|
|
17
|
+
param += c;
|
|
18
|
+
c = advance();
|
|
19
|
+
}
|
|
20
|
+
slices.push({
|
|
21
|
+
type: 'path',
|
|
22
|
+
value: param,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (c === "[") {
|
|
26
|
+
let param = "";
|
|
27
|
+
c = advance();
|
|
28
|
+
while (c !== "]" && i < len) {
|
|
29
|
+
param += c;
|
|
30
|
+
c = advance();
|
|
31
|
+
}
|
|
32
|
+
slices.push({
|
|
33
|
+
type: 'variable',
|
|
34
|
+
value: param,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
c = advance();
|
|
38
|
+
}
|
|
39
|
+
return slices;
|
|
40
|
+
};
|
|
41
|
+
exports.parseEndpoint = parseEndpoint;
|
|
42
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":";;;AAKO,MAAM,aAAa,GAAG,CAAC,IAAY,EAAkB,EAAE;IAC5D,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,GAAW,CAAC,CAAC;IAClB,IAAI,CAAC,GAAW,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACvC,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC1C,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,CAAC,GAAG,OAAO,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;gBAC5B,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QAED,CAAC,GAAG,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAxCW,QAAA,aAAa,iBAwCxB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./endpoint"), exports);
|
|
18
|
+
__exportStar(require("./router"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,2CAAyB"}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LooseFunction } from "./common";
|
|
2
|
+
import { EndpointPart } from "./endpoint";
|
|
3
|
+
export interface RouterConfig {
|
|
4
|
+
routesDir: string;
|
|
5
|
+
}
|
|
6
|
+
export type RouteStructure = {
|
|
7
|
+
filepath: string;
|
|
8
|
+
children: RouteStructure[];
|
|
9
|
+
};
|
|
10
|
+
export type RouteMethods = Record<string, LooseFunction>;
|
|
11
|
+
export type RouteDefinition = {
|
|
12
|
+
filepath: string;
|
|
13
|
+
relativepath: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
args: EndpointPart[];
|
|
16
|
+
methods: RouteMethods;
|
|
17
|
+
children: RouteDefinition[];
|
|
18
|
+
};
|
|
19
|
+
export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
|
|
20
|
+
export declare const getRoutes: (cfg: RouterConfig) => Promise<FlatRouteDefinition[]>;
|
|
21
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAiB,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAsGpE,eAAO,MAAM,SAAS,GACpB,KAAK,YAAY,KAChB,OAAO,CAAC,mBAAmB,EAAE,CAK/B,CAAC"}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getRoutes = void 0;
|
|
7
|
+
const util_1 = require("./util");
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const endpoint_1 = require("./endpoint");
|
|
11
|
+
// ---- implementation ----
|
|
12
|
+
const getEntries = async (path) => {
|
|
13
|
+
const entries = (await promises_1.default.readdir(path, {
|
|
14
|
+
recursive: false,
|
|
15
|
+
withFileTypes: true,
|
|
16
|
+
})).filter((entry) => {
|
|
17
|
+
return ((entry.isDirectory() || entry.name.endsWith(".ts")) &&
|
|
18
|
+
!entry.name.includes("#") &&
|
|
19
|
+
!entry.name.includes("~"));
|
|
20
|
+
});
|
|
21
|
+
return entries;
|
|
22
|
+
};
|
|
23
|
+
const findRouteStructure = async (path) => {
|
|
24
|
+
const entries = await getEntries(path);
|
|
25
|
+
const children = (await Promise.all(entries.map(async (entry) => {
|
|
26
|
+
const abspath = path_1.default.join(entry.parentPath, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
return await findRouteStructure(abspath);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
filepath: abspath,
|
|
32
|
+
children: [],
|
|
33
|
+
};
|
|
34
|
+
}))).flat();
|
|
35
|
+
return {
|
|
36
|
+
filepath: path,
|
|
37
|
+
children: children,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const findRouteDefs = async (rootPath, structure) => {
|
|
41
|
+
const relativepath = path_1.default.relative(rootPath, structure.filepath);
|
|
42
|
+
const endpoint = "/" +
|
|
43
|
+
[relativepath]
|
|
44
|
+
.map((x) => x.replace("index.ts", "").replace(".ts", ""))
|
|
45
|
+
.map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0];
|
|
46
|
+
return {
|
|
47
|
+
filepath: structure.filepath,
|
|
48
|
+
relativepath: relativepath,
|
|
49
|
+
endpoint: endpoint,
|
|
50
|
+
args: (0, endpoint_1.parseEndpoint)(endpoint),
|
|
51
|
+
methods: {},
|
|
52
|
+
children: await Promise.all(structure.children.map(async (child) => {
|
|
53
|
+
return findRouteDefs(rootPath, child);
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const flattenRoutes = async (defs) => {
|
|
58
|
+
const items = [];
|
|
59
|
+
const q = [defs];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
while (q.length > 0) {
|
|
62
|
+
const next = q.pop();
|
|
63
|
+
if (seen.has(next.endpoint))
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(next.endpoint);
|
|
66
|
+
const methods = {};
|
|
67
|
+
try {
|
|
68
|
+
const module = await import(next.filepath);
|
|
69
|
+
for (const [k, v] of Object.entries(module)) {
|
|
70
|
+
if (typeof v === "function") {
|
|
71
|
+
methods[k] = v;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.error(e);
|
|
77
|
+
}
|
|
78
|
+
items.push((0, util_1.omit)({ ...next, methods }, "children"));
|
|
79
|
+
q.push(...next.children);
|
|
80
|
+
}
|
|
81
|
+
return (0, util_1.uniqueBy)(items, (x) => x.endpoint);
|
|
82
|
+
};
|
|
83
|
+
const getRoutes = async (cfg) => {
|
|
84
|
+
const rootPath = path_1.default.resolve(cfg.routesDir);
|
|
85
|
+
const structure = await findRouteStructure(rootPath);
|
|
86
|
+
const defs = await findRouteDefs(rootPath, structure);
|
|
87
|
+
return await flattenRoutes(defs);
|
|
88
|
+
};
|
|
89
|
+
exports.getRoutes = getRoutes;
|
|
90
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,GACf,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,EACtC,IAAI,SAAS,MAAM,CAAC,EAEpB,KAAK,CAAC,EACN,GAAG,MAAM,IAAI,EAAE,KACd,IAAI,CAAC,CAAC,EAAE,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,EAAE,EACR,KAAK,GAAG,KACP,CAAC,EAUH,CAAC"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uniqueBy = exports.omit = void 0;
|
|
4
|
+
const omit = (obj, ...keys) => {
|
|
5
|
+
const result = { ...obj };
|
|
6
|
+
for (const k of keys) {
|
|
7
|
+
delete result[k];
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
exports.omit = omit;
|
|
12
|
+
const uniqueBy = (arr, key) => {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
return arr.filter((x) => {
|
|
15
|
+
const k = key(x);
|
|
16
|
+
if (seen.has(k)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
seen.add(k);
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
exports.uniqueBy = uniqueBy;
|
|
24
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAIlB,GAAM,EACN,GAAG,IAAY,EACA,EAAE;IACjB,MAAM,MAAM,GAAkB,EAAE,GAAG,GAAG,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,CAAmC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAdW,QAAA,IAAI,QAcf;AAEK,MAAM,QAAQ,GAAG,CACtB,GAAQ,EACR,GAAQ,EACH,EAAE;IACP,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAbW,QAAA,QAAQ,YAanB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dirent-router",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"typings": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"dirent-router": "./bin/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"build:lib": "tsc -p ./tsconfig.lib.json",
|
|
14
|
+
"build:bin": "tsc -p ./tsconfig.bin.json",
|
|
15
|
+
"build": "pnpm build:lib && pnpm build:bin",
|
|
16
|
+
"check": "tsc -p ./tsconfig.json --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"packageManager": "pnpm@10.28.2",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^25.2.3",
|
|
24
|
+
"prettier": "^3.8.1",
|
|
25
|
+
"tsx": "^4.21.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.0.18"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getRoutes } from "./router";
|
|
3
|
+
|
|
4
|
+
const main = async () => {
|
|
5
|
+
const routesDir = process.argv[2];
|
|
6
|
+
if (!routesDir) {
|
|
7
|
+
console.error(`Please provide directory where routes are stored`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!fs.existsSync(routesDir)) {
|
|
11
|
+
console.error(`${routesDir}: No such file or directory`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const routes = await getRoutes({
|
|
15
|
+
routesDir,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
console.log(
|
|
19
|
+
JSON.stringify(
|
|
20
|
+
routes,
|
|
21
|
+
(_, v) => (typeof v === "function" ? `${v.name}(...)` : v),
|
|
22
|
+
2,
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
main().catch((e) => console.error(e));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, assert } from "vitest";
|
|
2
|
+
import { EndpointPart, parseEndpoint } from "./endpoint";
|
|
3
|
+
|
|
4
|
+
type ParseExpect = {
|
|
5
|
+
input: string;
|
|
6
|
+
output: EndpointPart[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const EXPECTED_RESULTS: Array<ParseExpect> = [
|
|
10
|
+
{
|
|
11
|
+
input: "/index.ts",
|
|
12
|
+
output: [
|
|
13
|
+
{
|
|
14
|
+
type: "path",
|
|
15
|
+
value: "index.ts",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
input: "/user/[id]/index.ts",
|
|
21
|
+
output: [
|
|
22
|
+
{ type: "path", value: "user" },
|
|
23
|
+
{ type: "variable", value: "id" },
|
|
24
|
+
{ type: "path", value: "index.ts" },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
input: "/user/foobar.ts",
|
|
29
|
+
output: [
|
|
30
|
+
{ type: "path", value: "user" },
|
|
31
|
+
{ type: "path", value: "foobar.ts" },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
input: "/user/[id]/entity/[id]/index.ts",
|
|
36
|
+
output: [
|
|
37
|
+
{ type: "path", value: "user" },
|
|
38
|
+
{ type: "variable", value: "id" },
|
|
39
|
+
{ type: "path", value: "entity" },
|
|
40
|
+
{ type: "variable", value: "id" },
|
|
41
|
+
{ type: "path", value: "index.ts" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
describe("Parse route path", (it) => {
|
|
47
|
+
it("should create correct slices", () => {
|
|
48
|
+
for (const expected of EXPECTED_RESULTS) {
|
|
49
|
+
const output = parseEndpoint(expected.input);
|
|
50
|
+
assert.strictEqual(output.length, expected.output.length);
|
|
51
|
+
for (let i = 0; i < output.length; i++) {
|
|
52
|
+
const a = output[i]!;
|
|
53
|
+
const b = expected.output[i]!;
|
|
54
|
+
|
|
55
|
+
assert.strictEqual(a.value, b.value);
|
|
56
|
+
assert.strictEqual(a.type, b.type);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/endpoint.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type EndpointPart = {
|
|
2
|
+
type: 'path' | 'variable';
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const parseEndpoint = (buff: string): EndpointPart[] => {
|
|
7
|
+
const slices: EndpointPart[] = [];
|
|
8
|
+
const len = buff.length;
|
|
9
|
+
let i: number = 0;
|
|
10
|
+
let c: string = buff[i++]!;
|
|
11
|
+
|
|
12
|
+
const advance = () => {
|
|
13
|
+
c = buff[i++]!;
|
|
14
|
+
return c;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
while (i <= len) {
|
|
18
|
+
if (c !== "[" && c !== '/' && i <= len) {
|
|
19
|
+
let param: string = "";
|
|
20
|
+
while (c !== "/" && c !== "[" && i <= len) {
|
|
21
|
+
param += c;
|
|
22
|
+
c = advance();
|
|
23
|
+
}
|
|
24
|
+
slices.push({
|
|
25
|
+
type: 'path',
|
|
26
|
+
value: param,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (c === "[") {
|
|
30
|
+
let param: string = "";
|
|
31
|
+
c = advance();
|
|
32
|
+
while (c !== "]" && i < len) {
|
|
33
|
+
param += c;
|
|
34
|
+
c = advance();
|
|
35
|
+
}
|
|
36
|
+
slices.push({
|
|
37
|
+
type: 'variable',
|
|
38
|
+
value: param,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
c = advance();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return slices;
|
|
46
|
+
};
|
package/src/index.ts
ADDED
package/src/router.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { omit, uniqueBy } from "./util";
|
|
2
|
+
import { Dirent } from "fs";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import pathlib from "path";
|
|
5
|
+
import { LooseFunction } from "./common";
|
|
6
|
+
import { EndpointPart, parseEndpoint } from "./endpoint";
|
|
7
|
+
|
|
8
|
+
// ---- types ----
|
|
9
|
+
|
|
10
|
+
export interface RouterConfig {
|
|
11
|
+
routesDir: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type RouteStructure = {
|
|
15
|
+
filepath: string;
|
|
16
|
+
children: RouteStructure[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RouteMethods = Record<string, LooseFunction>;
|
|
20
|
+
|
|
21
|
+
export type RouteDefinition = {
|
|
22
|
+
filepath: string;
|
|
23
|
+
relativepath: string;
|
|
24
|
+
endpoint: string;
|
|
25
|
+
args: EndpointPart[];
|
|
26
|
+
methods: RouteMethods;
|
|
27
|
+
children: RouteDefinition[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
|
|
31
|
+
|
|
32
|
+
// ---- implementation ----
|
|
33
|
+
|
|
34
|
+
const getEntries = async (path: string) => {
|
|
35
|
+
const entries: Dirent<string>[] = (
|
|
36
|
+
await fs.readdir(path, {
|
|
37
|
+
recursive: false,
|
|
38
|
+
withFileTypes: true,
|
|
39
|
+
})
|
|
40
|
+
).filter((entry) => {
|
|
41
|
+
return (
|
|
42
|
+
(entry.isDirectory() || entry.name.endsWith(".ts")) &&
|
|
43
|
+
!entry.name.includes("#") &&
|
|
44
|
+
!entry.name.includes("~")
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
return entries;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const findRouteStructure = async (path: string): Promise<RouteStructure> => {
|
|
51
|
+
const entries = await getEntries(path);
|
|
52
|
+
const children = (
|
|
53
|
+
await Promise.all(
|
|
54
|
+
entries.map(async (entry): Promise<RouteStructure> => {
|
|
55
|
+
const abspath = pathlib.join(entry.parentPath, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
return await findRouteStructure(abspath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
filepath: abspath,
|
|
62
|
+
children: [],
|
|
63
|
+
};
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
).flat();
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
filepath: path,
|
|
70
|
+
children: children,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const findRouteDefs = async (
|
|
75
|
+
rootPath: string,
|
|
76
|
+
structure: RouteStructure,
|
|
77
|
+
): Promise<RouteDefinition> => {
|
|
78
|
+
const relativepath = pathlib.relative(rootPath, structure.filepath);
|
|
79
|
+
const endpoint =
|
|
80
|
+
"/" +
|
|
81
|
+
[relativepath]
|
|
82
|
+
.map((x) => x.replace("index.ts", "").replace(".ts", ""))
|
|
83
|
+
.map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0]!;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
filepath: structure.filepath,
|
|
87
|
+
relativepath: relativepath,
|
|
88
|
+
endpoint: endpoint,
|
|
89
|
+
args: parseEndpoint(endpoint),
|
|
90
|
+
methods: {},
|
|
91
|
+
children: await Promise.all(
|
|
92
|
+
structure.children.map(async (child) => {
|
|
93
|
+
return findRouteDefs(rootPath, child);
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const flattenRoutes = async (
|
|
100
|
+
defs: RouteDefinition,
|
|
101
|
+
): Promise<FlatRouteDefinition[]> => {
|
|
102
|
+
const items: FlatRouteDefinition[] = [];
|
|
103
|
+
const q: Array<RouteDefinition> = [defs];
|
|
104
|
+
const seen = new Set<string>();
|
|
105
|
+
|
|
106
|
+
while (q.length > 0) {
|
|
107
|
+
const next = q.pop()!;
|
|
108
|
+
if (seen.has(next.endpoint)) continue;
|
|
109
|
+
seen.add(next.endpoint);
|
|
110
|
+
|
|
111
|
+
const methods: RouteMethods = {};
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const module = await import(next.filepath);
|
|
115
|
+
|
|
116
|
+
for (const [k, v] of Object.entries(module)) {
|
|
117
|
+
if (typeof v === "function") {
|
|
118
|
+
(methods as Record<string, unknown>)[k] = v;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(e);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
items.push(omit({ ...next, methods }, "children"));
|
|
126
|
+
q.push(...next.children);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return uniqueBy(items, (x) => x.endpoint);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const getRoutes = async (
|
|
133
|
+
cfg: RouterConfig,
|
|
134
|
+
): Promise<FlatRouteDefinition[]> => {
|
|
135
|
+
const rootPath = pathlib.resolve(cfg.routesDir);
|
|
136
|
+
const structure = await findRouteStructure(rootPath);
|
|
137
|
+
const defs = await findRouteDefs(rootPath, structure);
|
|
138
|
+
return await flattenRoutes(defs);
|
|
139
|
+
};
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const omit = <
|
|
2
|
+
T extends Record<PropertyKey, unknown>,
|
|
3
|
+
Keys extends keyof T,
|
|
4
|
+
>(
|
|
5
|
+
obj: T,
|
|
6
|
+
...keys: Keys[]
|
|
7
|
+
): Omit<T, Keys> => {
|
|
8
|
+
const result: Omit<T, Keys> = { ...obj };
|
|
9
|
+
|
|
10
|
+
for (const k of keys) {
|
|
11
|
+
delete result[k as unknown as keyof typeof result];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const uniqueBy = <T, Key extends (item: T) => unknown>(
|
|
18
|
+
arr: T[],
|
|
19
|
+
key: Key,
|
|
20
|
+
): T[] => {
|
|
21
|
+
const seen = new Set<unknown>();
|
|
22
|
+
return arr.filter((x) => {
|
|
23
|
+
const k = key(x);
|
|
24
|
+
if (seen.has(k)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
seen.add(k);
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./bin",
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"target": "esnext",
|
|
7
|
+
"lib": ["esnext"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noUncheckedSideEffectImports": true,
|
|
18
|
+
"moduleDetection": "force",
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"skipLibCheck": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["./src/bin.ts"],
|
|
23
|
+
"exclude": ["./src/**/*.test.ts"]
|
|
24
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"emit": false,
|
|
6
|
+
"module": "nodenext",
|
|
7
|
+
"target": "esnext",
|
|
8
|
+
"lib": ["esnext"],
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true,
|
|
14
|
+
"exactOptionalPropertyTypes": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true,
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"skipLibCheck": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["./src/**/*.ts"]
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"target": "esnext",
|
|
7
|
+
"lib": ["esnext"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noUncheckedSideEffectImports": true,
|
|
18
|
+
"moduleDetection": "force",
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"skipLibCheck": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["./src/**/*.ts"],
|
|
23
|
+
"exclude": ["./src/**/*.test.ts", "./src/bin.ts"]
|
|
24
|
+
}
|