@efebia/fastify-auto-import 1.0.3 → 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 CHANGED
@@ -69,8 +69,41 @@ A `fastify-plugin` compatible plugin. Register it with `fastify.register()`.
69
69
  | `directory` | `string` | `'src/plugins'` | Directory to scan for route subdirectories, relative to `startingDirectory`. |
70
70
  | `routeFile` | `string` | `'routes'` | File name (without extension) to import from each subdirectory. |
71
71
  | `excludedDirectories` | `string[]` | `[]` | Subdirectory names to skip. |
72
+ | `recursive` | `boolean` | `false` | Scan nested subdirectories for route files, not just direct children. |
72
73
  | `log` | `pino.BaseLogger` | Fastify logger | Logger instance for import errors. |
73
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
+
74
107
  ## License
75
108
 
76
109
  MIT
@@ -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<never[]>;
12
+ }) => Promise<any[]>;
@@ -41,7 +41,6 @@ const promises_1 = require("node:fs/promises");
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
42
  const node_url_1 = __importStar(require("node:url"));
43
43
  const pkg_up_1 = __importDefault(require("pkg-up"));
44
- const serial_js_1 = require("./serial.js");
45
44
  const getPackageType = async (directory) => {
46
45
  const nearestPackage = await (0, pkg_up_1.default)({ cwd: directory });
47
46
  if (nearestPackage) {
@@ -52,13 +51,57 @@ const getPackageType = async (directory) => {
52
51
  return JSON.parse(file).type;
53
52
  }
54
53
  };
54
+ const collectRecursivePlugins = async (dir, opts) => {
55
+ const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
56
+ const dirEntries = entries.filter((entry) => entry.isDirectory() && !opts.excludedDirectories.includes(entry.name));
57
+ const perDirResults = await Promise.all(dirEntries.map(async (entry) => {
58
+ var _a;
59
+ const subDir = node_path_1.default.join(dir, entry.name);
60
+ const subEntries = await (0, promises_1.readdir)(subDir);
61
+ const routeFileName = opts.routeFile.replace('.js', '');
62
+ const hasRouteFile = subEntries.some((f) => f === `${routeFileName}.js` || f === `${routeFileName}.ts`);
63
+ const own = [];
64
+ if (hasRouteFile) {
65
+ const osPath = node_path_1.default.join(subDir, opts.packageType === 'module' ? `${routeFileName}.js` : routeFileName);
66
+ const href = node_url_1.default.pathToFileURL(osPath).href;
67
+ try {
68
+ if (opts.packageType === 'module') {
69
+ const imported = await Promise.resolve(`${href}`).then(s => __importStar(require(s)));
70
+ if (imported.default)
71
+ own.push(imported.default);
72
+ }
73
+ else {
74
+ const imported = require(osPath);
75
+ if (imported)
76
+ own.push(imported);
77
+ }
78
+ }
79
+ catch (e) {
80
+ (_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
81
+ throw e;
82
+ }
83
+ }
84
+ const nested = await collectRecursivePlugins(subDir, opts);
85
+ return [...own, ...nested];
86
+ }));
87
+ return perDirResults.flat();
88
+ };
55
89
  const globFiles = async (opts) => {
56
90
  const packageType = await getPackageType(opts.directory);
57
91
  const startingDirectory = packageType === 'module' ? node_path_1.default.dirname((0, node_url_1.fileURLToPath)(opts.startingDirectory)) : opts.startingDirectory;
58
92
  const pluginsDirectory = node_path_1.default.join(startingDirectory, opts.directory);
93
+ if (opts.recursive) {
94
+ const plugins = await collectRecursivePlugins(pluginsDirectory, {
95
+ routeFile: opts.routeFile,
96
+ packageType,
97
+ excludedDirectories: opts.excludedDirectories,
98
+ log: opts.log,
99
+ });
100
+ return plugins.filter(Boolean);
101
+ }
59
102
  const routesDirectories = await (0, promises_1.readdir)(pluginsDirectory);
60
103
  const filteredDirectories = routesDirectories.filter(dir => !opts.excludedDirectories.includes(dir));
61
- const importedFiles = await (0, serial_js_1.serial)(filteredDirectories.map((dir) => async () => {
104
+ const importedFiles = await Promise.all(filteredDirectories.map(async (dir) => {
62
105
  var _a;
63
106
  const osPath = node_path_1.default.join(pluginsDirectory, dir, packageType === 'module' ? `${opts.routeFile.replace(".js", "")}.js` : opts.routeFile.replace(".js", ""));
64
107
  const href = node_url_1.default.pathToFileURL(osPath).href;
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
  }));
@@ -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<never[]>;
12
+ }) => Promise<any[]>;
@@ -2,7 +2,6 @@ import { readFile, readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import url, { fileURLToPath } from "node:url";
4
4
  import pkgUp from "pkg-up";
5
- import { serial } from "./serial.js";
6
5
  const getPackageType = async (directory) => {
7
6
  const nearestPackage = await pkgUp({ cwd: directory });
8
7
  if (nearestPackage) {
@@ -13,13 +12,57 @@ const getPackageType = async (directory) => {
13
12
  return JSON.parse(file).type;
14
13
  }
15
14
  };
15
+ const collectRecursivePlugins = async (dir, opts) => {
16
+ const entries = await readdir(dir, { withFileTypes: true });
17
+ const dirEntries = entries.filter((entry) => entry.isDirectory() && !opts.excludedDirectories.includes(entry.name));
18
+ const perDirResults = await Promise.all(dirEntries.map(async (entry) => {
19
+ var _a;
20
+ const subDir = path.join(dir, entry.name);
21
+ const subEntries = await readdir(subDir);
22
+ const routeFileName = opts.routeFile.replace('.js', '');
23
+ const hasRouteFile = subEntries.some((f) => f === `${routeFileName}.js` || f === `${routeFileName}.ts`);
24
+ const own = [];
25
+ if (hasRouteFile) {
26
+ const osPath = path.join(subDir, opts.packageType === 'module' ? `${routeFileName}.js` : routeFileName);
27
+ const href = url.pathToFileURL(osPath).href;
28
+ try {
29
+ if (opts.packageType === 'module') {
30
+ const imported = await import(href);
31
+ if (imported.default)
32
+ own.push(imported.default);
33
+ }
34
+ else {
35
+ const imported = require(osPath);
36
+ if (imported)
37
+ own.push(imported);
38
+ }
39
+ }
40
+ catch (e) {
41
+ (_a = opts.log) === null || _a === void 0 ? void 0 : _a.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
42
+ throw e;
43
+ }
44
+ }
45
+ const nested = await collectRecursivePlugins(subDir, opts);
46
+ return [...own, ...nested];
47
+ }));
48
+ return perDirResults.flat();
49
+ };
16
50
  export const globFiles = async (opts) => {
17
51
  const packageType = await getPackageType(opts.directory);
18
52
  const startingDirectory = packageType === 'module' ? path.dirname(fileURLToPath(opts.startingDirectory)) : opts.startingDirectory;
19
53
  const pluginsDirectory = path.join(startingDirectory, opts.directory);
54
+ if (opts.recursive) {
55
+ const plugins = await collectRecursivePlugins(pluginsDirectory, {
56
+ routeFile: opts.routeFile,
57
+ packageType,
58
+ excludedDirectories: opts.excludedDirectories,
59
+ log: opts.log,
60
+ });
61
+ return plugins.filter(Boolean);
62
+ }
20
63
  const routesDirectories = await readdir(pluginsDirectory);
21
64
  const filteredDirectories = routesDirectories.filter(dir => !opts.excludedDirectories.includes(dir));
22
- const importedFiles = await serial(filteredDirectories.map((dir) => async () => {
65
+ const importedFiles = await Promise.all(filteredDirectories.map(async (dir) => {
23
66
  var _a;
24
67
  const osPath = path.join(pluginsDirectory, dir, packageType === 'module' ? `${opts.routeFile.replace(".js", "")}.js` : opts.routeFile.replace(".js", ""));
25
68
  const href = url.pathToFileURL(osPath).href;
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",
3
+ "version": "1.1.1",
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
- "jest": "^29.3.1",
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
- "test": "yarn node $(yarn bin jest)",
26
- "build:esm": "tsc --module nodenext --moduleResolution nodenext --outDir lib/esm",
27
- "build:cjs": "tsc --module commonjs --moduleResolution node --outDir lib/cjs",
28
- "build": "yarn build:esm && yarn build:cjs"
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
  ".": {