@efebia/fastify-auto-import 1.0.1 → 1.1.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/README.md +109 -0
- package/lib/cjs/globber.d.ts +2 -1
- package/lib/cjs/globber.js +48 -1
- package/lib/cjs/index.js +2 -2
- package/lib/esm/globber.d.ts +2 -1
- package/lib/esm/globber.js +48 -1
- package/lib/esm/index.js +2 -2
- package/package.json +7 -8
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @efebia/fastify-auto-import
|
|
2
|
+
|
|
3
|
+
Fastify plugin that automatically discovers and registers route plugins from a directory.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @efebia/fastify-auto-import
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import Fastify from 'fastify';
|
|
15
|
+
import autoImport from '@efebia/fastify-auto-import';
|
|
16
|
+
|
|
17
|
+
const fastify = Fastify();
|
|
18
|
+
|
|
19
|
+
await fastify.register(autoImport, {
|
|
20
|
+
// Required: the starting directory (use import.meta.url in ESM)
|
|
21
|
+
startingDirectory: import.meta.url,
|
|
22
|
+
// Directory to scan, relative to startingDirectory (default: 'src/plugins')
|
|
23
|
+
directory: 'src/plugins',
|
|
24
|
+
// File name to import from each subdirectory (default: 'routes')
|
|
25
|
+
routeFile: 'routes',
|
|
26
|
+
// Subdirectories to skip (default: [])
|
|
27
|
+
excludedDirectories: ['_shared'],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Given the following structure:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
src/plugins/
|
|
35
|
+
users/
|
|
36
|
+
routes.ts
|
|
37
|
+
orders/
|
|
38
|
+
routes.ts
|
|
39
|
+
_shared/
|
|
40
|
+
utils.ts ← skipped
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The plugin will register `users/routes.ts` and `orders/routes.ts` as Fastify plugins.
|
|
44
|
+
|
|
45
|
+
Each route file must export a default Fastify plugin:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// src/plugins/users/routes.ts
|
|
49
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
50
|
+
|
|
51
|
+
const plugin: FastifyPluginAsync = async (fastify) => {
|
|
52
|
+
fastify.get('/users', async () => ({ users: [] }));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default plugin;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### `autoImport` (default export)
|
|
61
|
+
|
|
62
|
+
A `fastify-plugin` compatible plugin. Register it with `fastify.register()`.
|
|
63
|
+
|
|
64
|
+
**Options** (`Globber`):
|
|
65
|
+
|
|
66
|
+
| Option | Type | Default | Description |
|
|
67
|
+
|--------|------|---------|-------------|
|
|
68
|
+
| `startingDirectory` | `string` | — | **Required.** Base directory. Pass `import.meta.url` (ESM) or `__dirname` (CJS). |
|
|
69
|
+
| `directory` | `string` | `'src/plugins'` | Directory to scan for route subdirectories, relative to `startingDirectory`. |
|
|
70
|
+
| `routeFile` | `string` | `'routes'` | File name (without extension) to import from each subdirectory. |
|
|
71
|
+
| `excludedDirectories` | `string[]` | `[]` | Subdirectory names to skip. |
|
|
72
|
+
| `recursive` | `boolean` | `false` | Scan nested subdirectories for route files, not just direct children. |
|
|
73
|
+
| `log` | `pino.BaseLogger` | Fastify logger | Logger instance for import errors. |
|
|
74
|
+
|
|
75
|
+
## Recursive mode
|
|
76
|
+
|
|
77
|
+
By default the plugin only looks at **direct subdirectories** of `directory`. Set `recursive: true` to walk the entire tree and register every subdirectory that contains a route file, at any depth.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await fastify.register(autoImport, {
|
|
81
|
+
startingDirectory: import.meta.url,
|
|
82
|
+
directory: 'src/plugins',
|
|
83
|
+
recursive: true,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Given this structure:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
src/plugins/
|
|
91
|
+
users/
|
|
92
|
+
routes.ts ← registered
|
|
93
|
+
profile/
|
|
94
|
+
routes.ts ← registered (nested)
|
|
95
|
+
orders/
|
|
96
|
+
routes.ts ← registered
|
|
97
|
+
items/
|
|
98
|
+
routes.ts ← registered (nested)
|
|
99
|
+
_shared/
|
|
100
|
+
utils.ts ← skipped (no routes file)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Without `recursive`, only `users/routes.ts` and `orders/routes.ts` would be registered. With `recursive: true`, `users/profile/routes.ts` and `orders/items/routes.ts` are picked up as well.
|
|
104
|
+
|
|
105
|
+
`excludedDirectories` still applies at every depth — a matching directory name is skipped along with all its descendants.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
package/lib/cjs/globber.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export type Globber = {
|
|
|
5
5
|
startingDirectory: string;
|
|
6
6
|
log?: pino.BaseLogger;
|
|
7
7
|
excludedDirectories?: string[];
|
|
8
|
+
recursive?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare const globFiles: (opts: Required<Globber> & {
|
|
10
11
|
log?: pino.BaseLogger;
|
|
11
|
-
}) => Promise<
|
|
12
|
+
}) => Promise<any[]>;
|
package/lib/cjs/globber.js
CHANGED
|
@@ -52,10 +52,57 @@ const getPackageType = async (directory) => {
|
|
|
52
52
|
return JSON.parse(file).type;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
+
const collectRecursivePlugins = async (dir, opts) => {
|
|
56
|
+
var _a;
|
|
57
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
58
|
+
const results = [];
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!entry.isDirectory())
|
|
61
|
+
continue;
|
|
62
|
+
if (opts.excludedDirectories.includes(entry.name))
|
|
63
|
+
continue;
|
|
64
|
+
const subDir = node_path_1.default.join(dir, entry.name);
|
|
65
|
+
const subEntries = await (0, promises_1.readdir)(subDir);
|
|
66
|
+
const routeFileName = opts.routeFile.replace('.js', '');
|
|
67
|
+
const hasRouteFile = subEntries.some((f) => f === `${routeFileName}.js` || f === `${routeFileName}.ts`);
|
|
68
|
+
if (hasRouteFile) {
|
|
69
|
+
const osPath = node_path_1.default.join(subDir, opts.packageType === 'module' ? `${routeFileName}.js` : routeFileName);
|
|
70
|
+
const href = node_url_1.default.pathToFileURL(osPath).href;
|
|
71
|
+
try {
|
|
72
|
+
if (opts.packageType === 'module') {
|
|
73
|
+
const imported = await Promise.resolve(`${href}`).then(s => __importStar(require(s)));
|
|
74
|
+
if (imported.default)
|
|
75
|
+
results.push(imported.default);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const imported = require(osPath);
|
|
79
|
+
if (imported)
|
|
80
|
+
results.push(imported);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
(_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
|
|
85
|
+
throw e;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const nested = await collectRecursivePlugins(subDir, opts);
|
|
89
|
+
results.push(...nested);
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
};
|
|
55
93
|
const globFiles = async (opts) => {
|
|
56
94
|
const packageType = await getPackageType(opts.directory);
|
|
57
95
|
const startingDirectory = packageType === 'module' ? node_path_1.default.dirname((0, node_url_1.fileURLToPath)(opts.startingDirectory)) : opts.startingDirectory;
|
|
58
96
|
const pluginsDirectory = node_path_1.default.join(startingDirectory, opts.directory);
|
|
97
|
+
if (opts.recursive) {
|
|
98
|
+
const plugins = await collectRecursivePlugins(pluginsDirectory, {
|
|
99
|
+
routeFile: opts.routeFile,
|
|
100
|
+
packageType,
|
|
101
|
+
excludedDirectories: opts.excludedDirectories,
|
|
102
|
+
log: opts.log,
|
|
103
|
+
});
|
|
104
|
+
return plugins.filter(Boolean);
|
|
105
|
+
}
|
|
59
106
|
const routesDirectories = await (0, promises_1.readdir)(pluginsDirectory);
|
|
60
107
|
const filteredDirectories = routesDirectories.filter(dir => !opts.excludedDirectories.includes(dir));
|
|
61
108
|
const importedFiles = await (0, serial_js_1.serial)(filteredDirectories.map((dir) => async () => {
|
|
@@ -72,7 +119,7 @@ const globFiles = async (opts) => {
|
|
|
72
119
|
catch (e) {
|
|
73
120
|
console.log(e);
|
|
74
121
|
(_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, "@efebia/fastify-auto-import error on importing plugin");
|
|
75
|
-
|
|
122
|
+
throw e;
|
|
76
123
|
}
|
|
77
124
|
}));
|
|
78
125
|
return importedFiles.filter((e) => e);
|
package/lib/cjs/index.js
CHANGED
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
7
7
|
const globber_js_1 = require("./globber.js");
|
|
8
8
|
const plugin = (0, fastify_plugin_1.default)(async (fastify, opts) => {
|
|
9
|
-
var _a, _b, _c;
|
|
10
|
-
const globbedFiles = await (0, globber_js_1.globFiles)(Object.assign(Object.assign({}, opts), { routeFile: (_a = opts.routeFile) !== null && _a !== void 0 ? _a : 'routes', directory: (_b = opts.directory) !== null && _b !== void 0 ? _b : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [] }));
|
|
9
|
+
var _a, _b, _c, _d;
|
|
10
|
+
const globbedFiles = await (0, globber_js_1.globFiles)(Object.assign(Object.assign({}, opts), { routeFile: (_a = opts.routeFile) !== null && _a !== void 0 ? _a : 'routes', directory: (_b = opts.directory) !== null && _b !== void 0 ? _b : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [], recursive: (_d = opts.recursive) !== null && _d !== void 0 ? _d : false }));
|
|
11
11
|
await Promise.all(globbedFiles.map(async (globbedFile) => {
|
|
12
12
|
await fastify.register(globbedFile);
|
|
13
13
|
}));
|
package/lib/esm/globber.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export type Globber = {
|
|
|
5
5
|
startingDirectory: string;
|
|
6
6
|
log?: pino.BaseLogger;
|
|
7
7
|
excludedDirectories?: string[];
|
|
8
|
+
recursive?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare const globFiles: (opts: Required<Globber> & {
|
|
10
11
|
log?: pino.BaseLogger;
|
|
11
|
-
}) => Promise<
|
|
12
|
+
}) => Promise<any[]>;
|
package/lib/esm/globber.js
CHANGED
|
@@ -13,10 +13,57 @@ const getPackageType = async (directory) => {
|
|
|
13
13
|
return JSON.parse(file).type;
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
+
const collectRecursivePlugins = async (dir, opts) => {
|
|
17
|
+
var _a;
|
|
18
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (!entry.isDirectory())
|
|
22
|
+
continue;
|
|
23
|
+
if (opts.excludedDirectories.includes(entry.name))
|
|
24
|
+
continue;
|
|
25
|
+
const subDir = path.join(dir, entry.name);
|
|
26
|
+
const subEntries = await readdir(subDir);
|
|
27
|
+
const routeFileName = opts.routeFile.replace('.js', '');
|
|
28
|
+
const hasRouteFile = subEntries.some((f) => f === `${routeFileName}.js` || f === `${routeFileName}.ts`);
|
|
29
|
+
if (hasRouteFile) {
|
|
30
|
+
const osPath = path.join(subDir, opts.packageType === 'module' ? `${routeFileName}.js` : routeFileName);
|
|
31
|
+
const href = url.pathToFileURL(osPath).href;
|
|
32
|
+
try {
|
|
33
|
+
if (opts.packageType === 'module') {
|
|
34
|
+
const imported = await import(href);
|
|
35
|
+
if (imported.default)
|
|
36
|
+
results.push(imported.default);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const imported = require(osPath);
|
|
40
|
+
if (imported)
|
|
41
|
+
results.push(imported);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
(_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const nested = await collectRecursivePlugins(subDir, opts);
|
|
50
|
+
results.push(...nested);
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
};
|
|
16
54
|
export const globFiles = async (opts) => {
|
|
17
55
|
const packageType = await getPackageType(opts.directory);
|
|
18
56
|
const startingDirectory = packageType === 'module' ? path.dirname(fileURLToPath(opts.startingDirectory)) : opts.startingDirectory;
|
|
19
57
|
const pluginsDirectory = path.join(startingDirectory, opts.directory);
|
|
58
|
+
if (opts.recursive) {
|
|
59
|
+
const plugins = await collectRecursivePlugins(pluginsDirectory, {
|
|
60
|
+
routeFile: opts.routeFile,
|
|
61
|
+
packageType,
|
|
62
|
+
excludedDirectories: opts.excludedDirectories,
|
|
63
|
+
log: opts.log,
|
|
64
|
+
});
|
|
65
|
+
return plugins.filter(Boolean);
|
|
66
|
+
}
|
|
20
67
|
const routesDirectories = await readdir(pluginsDirectory);
|
|
21
68
|
const filteredDirectories = routesDirectories.filter(dir => !opts.excludedDirectories.includes(dir));
|
|
22
69
|
const importedFiles = await serial(filteredDirectories.map((dir) => async () => {
|
|
@@ -33,7 +80,7 @@ export const globFiles = async (opts) => {
|
|
|
33
80
|
catch (e) {
|
|
34
81
|
console.log(e);
|
|
35
82
|
(_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, "@efebia/fastify-auto-import error on importing plugin");
|
|
36
|
-
|
|
83
|
+
throw e;
|
|
37
84
|
}
|
|
38
85
|
}));
|
|
39
86
|
return importedFiles.filter((e) => e);
|
package/lib/esm/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
2
|
import { globFiles } from './globber.js';
|
|
3
3
|
const plugin = fp(async (fastify, opts) => {
|
|
4
|
-
var _a, _b, _c;
|
|
5
|
-
const globbedFiles = await globFiles(Object.assign(Object.assign({}, opts), { routeFile: (_a = opts.routeFile) !== null && _a !== void 0 ? _a : 'routes', directory: (_b = opts.directory) !== null && _b !== void 0 ? _b : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [] }));
|
|
4
|
+
var _a, _b, _c, _d;
|
|
5
|
+
const globbedFiles = await globFiles(Object.assign(Object.assign({}, opts), { routeFile: (_a = opts.routeFile) !== null && _a !== void 0 ? _a : 'routes', directory: (_b = opts.directory) !== null && _b !== void 0 ? _b : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [], recursive: (_d = opts.recursive) !== null && _d !== void 0 ? _d : false }));
|
|
6
6
|
await Promise.all(globbedFiles.map(async (globbedFile) => {
|
|
7
7
|
await fastify.register(globbedFile);
|
|
8
8
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efebia/fastify-auto-import",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -12,20 +12,19 @@
|
|
|
12
12
|
"pkg-up": "^3.1.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@types/jest": "^29.2.5",
|
|
16
15
|
"@types/node": "^18.11.18",
|
|
17
|
-
"
|
|
18
|
-
"ts-jest": "^29.0.3",
|
|
16
|
+
"tsx": "^4.20.3",
|
|
19
17
|
"typescript": "~5.8.3"
|
|
20
18
|
},
|
|
21
19
|
"files": [
|
|
22
20
|
"/lib"
|
|
23
21
|
],
|
|
24
22
|
"scripts": {
|
|
25
|
-
"
|
|
26
|
-
"build:
|
|
27
|
-
"build
|
|
28
|
-
"
|
|
23
|
+
"build:esm": "tsc -p ./tsconfig.build.json --module nodenext --moduleResolution nodenext --outDir lib/esm",
|
|
24
|
+
"build:cjs": "tsc -p ./tsconfig.build.json --module commonjs --moduleResolution node --outDir lib/cjs",
|
|
25
|
+
"build": "yarn build:esm && yarn build:cjs",
|
|
26
|
+
"test": "node --import tsx --test ./**/*.test.ts",
|
|
27
|
+
"test:only": "node --import tsx --test-only ./**/*.test.ts"
|
|
29
28
|
},
|
|
30
29
|
"exports": {
|
|
31
30
|
".": {
|