ts-file-router 1.0.5 → 2.0.2

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
@@ -7,12 +7,12 @@ Automatically scans folders and outputs a clean, structured `routes.ts` tree rea
7
7
 
8
8
  ## âœĻ Features
9
9
 
10
- - 🔍 Recursive folder scanning
11
- - 📄 Auto-generated `routes.ts` (TypeScript code, no `resolveJsonModule` required)
12
- - ⚛ïļ Works perfectly with `React.lazy()` and dynamic imports
13
- - 📘 Full TypeScript `.d.ts` definitions included
14
- - ðŸ§Đ Custom route file name (default: `page.ts`)
15
- - ðŸŠķ Zero runtime dependencies
10
+ - 🔍 Recursive folder scanning
11
+ - 📄 Auto-generated `routes.ts` (TypeScript code, no `resolveJsonModule` required)
12
+ - ⚛ïļ Works perfectly with `React.lazy()` and dynamic imports
13
+ - 📘 Full TypeScript `.d.ts` definitions included
14
+ - ðŸ§Đ Custom route file name (default: `page.ts`)
15
+ - ðŸŠķ Zero runtime dependencies
16
16
  - ðŸŽŊ Clean, formatted output with readable keys
17
17
 
18
18
  ---
@@ -23,3 +23,72 @@ Automatically scans folders and outputs a clean, structured `routes.ts` tree rea
23
23
  npm install ts-file-router
24
24
  # or
25
25
  yarn add ts-file-router
26
+
27
+ ```
28
+
29
+ ---
30
+
31
+ ## ðŸŽŊ Usage
32
+
33
+ Create a script to generate your routes. Example:
34
+
35
+ ```js
36
+ // scripts/generate-routes.mjs
37
+ import { generateRoutes } from 'ts-file-router';
38
+
39
+ generateRoutes({
40
+ baseFolder: 'src/screens',
41
+ outputFile: 'src/screens/routes.ts',
42
+ routeFileName: 'page.tsx',
43
+ });
44
+ ```
45
+
46
+ ## ðŸŽŊ Usage With Vite
47
+
48
+ Create a script to generate your routes. Example:
49
+
50
+ ```js
51
+ // scripts/vite.config.ts
52
+ import { generateRoutesPlugin } from 'ts-file-router';
53
+
54
+ // https://vite.dev/config/
55
+ export default defineConfig({
56
+ plugins: [
57
+ generateRoutesPlugin({
58
+ baseFolder: 'src/screens',
59
+ outputFile: 'screens.ts',
60
+ exitCodeOnResolution: false,
61
+ }),
62
+ ],
63
+ });
64
+ ```
65
+
66
+ ## ðŸŽŊ Usage With Watcher (Chokidar peerDependencie)
67
+
68
+ Create a script to generate your routes. Example:
69
+
70
+ ```js
71
+ // scripts/generate-routes.mjs
72
+ import { generateRoutesWithWatcher } from 'ts-file-router';
73
+
74
+ generateRoutesWithWatcher({
75
+ input: {
76
+ baseFolder: 'src/screens',
77
+ outputFile: 'screens.ts',
78
+ exitCodeOnResolution: false,
79
+ },
80
+ options: { debounce: 500 },
81
+ });
82
+ ```
83
+
84
+ ## What this does
85
+
86
+ - baseFolder: Root directory where your screens/pages live
87
+
88
+ - routeFileName: File that represents a route (e.g. page.tsx)
89
+
90
+ - outputFile: Generated routes file (fully typed)
91
+
92
+ Run the script with:
93
+
94
+ node scripts/generate-routes.mjs
@@ -0,0 +1,3 @@
1
+ import { TGenerateRoutesConfig } from './types.js';
2
+ export declare const generateRoutes: ({ baseFolder, outputFile, routeFileName, exitCodeOnResolution, }: TGenerateRoutesConfig) => void;
3
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGhE,eAAO,MAAM,cAAc,GAAI,kEAK5B,qBAAqB,SAqFvB,CAAC"}
@@ -0,0 +1,69 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { serialize } from './serialize.js';
4
+ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.tsx', exitCodeOnResolution = true, }) => {
5
+ // Get the pages dir to resolve routes
6
+ const basePath = path.resolve(process.cwd(), baseFolder);
7
+ // Output file for routes
8
+ const output = path.resolve(basePath, outputFile);
9
+ const mapRoutes = async (dir) => {
10
+ const routes = {};
11
+ const directory = await fs.readdir(dir);
12
+ if (!directory.includes(routeFileName)) {
13
+ throw new Error(`Invalid pages structure: The folder "${dir}" must contain a ${routeFileName} file.`);
14
+ }
15
+ for (const file of directory) {
16
+ // ignore index files, underscore marked, or route file generated
17
+ if (file.includes('index') ||
18
+ file.startsWith('_') ||
19
+ file.includes(outputFile)) {
20
+ continue;
21
+ }
22
+ const fullPath = path.join(dir, file);
23
+ // Get directory info to control file or folder
24
+ const dirInfo = await fs.stat(fullPath);
25
+ // Path to browser sync if necessary
26
+ const relativePath = '/' + path.relative(basePath, dir).replaceAll(/\\/g, '/');
27
+ // Normalize import path to esm pattern
28
+ const importPath = './' + path.relative(basePath, fullPath).replaceAll(/\\/g, '/');
29
+ // Remove extension from file to naming the route
30
+ const key = path.basename(file, path.extname(file));
31
+ if (dirInfo.isDirectory()) {
32
+ // Dev friendly when enter in this conditional file is a directory(folder)
33
+ const directory = file;
34
+ // Recursively for sub directories
35
+ routes[directory] = await mapRoutes(fullPath);
36
+ continue;
37
+ }
38
+ // Mount the route object with path like "/folder" and import
39
+ // import will be like "(./baseFolder/file or ./baseFolder/folders).extension"
40
+ routes[key] = {
41
+ path: relativePath,
42
+ import: importPath,
43
+ };
44
+ }
45
+ return routes;
46
+ };
47
+ const createRoutes = async () => {
48
+ try {
49
+ // Create routes
50
+ const routes = await mapRoutes(basePath);
51
+ // Create ts file
52
+ serialize(routes, output);
53
+ // Promise writeFile was successfully resolved
54
+ console.log('🚀 Routes generated successfully!\n');
55
+ if (exitCodeOnResolution) {
56
+ // Code 0 to finish the process as success
57
+ process.exit(0);
58
+ }
59
+ }
60
+ catch (err) {
61
+ console.error('❌ Error generating routes:\n', err);
62
+ if (exitCodeOnResolution) {
63
+ // Code 1 to finish the process as error
64
+ process.exit(1);
65
+ }
66
+ }
67
+ };
68
+ createRoutes();
69
+ };
@@ -0,0 +1,3 @@
1
+ import { TWatcherConfig } from './types.js';
2
+ export declare const generateRoutesWithWatcher: ({ input, options, }: TWatcherConfig) => Promise<() => Promise<void>>;
3
+ //# sourceMappingURL=generatorWatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generatorWatcher.d.ts","sourceRoot":"","sources":["../src/generatorWatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,yBAAyB,GAAU,qBAG7C,cAAc,iCA0BhB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { generateRoutes } from './generator.js';
2
+ export const generateRoutesWithWatcher = async ({ input, options = { debounce: 500 }, }) => {
3
+ const { watch } = await import('chokidar');
4
+ let timeoutId;
5
+ const runFromWatcher = () => {
6
+ if (timeoutId) {
7
+ clearTimeout(timeoutId);
8
+ }
9
+ timeoutId = setTimeout(() => {
10
+ generateRoutes(input);
11
+ }, options.debounce);
12
+ };
13
+ const watcher = watch(input.baseFolder, {
14
+ ignoreInitial: true,
15
+ ignored: `${input.baseFolder}/${input.outputFile}`,
16
+ persistent: true,
17
+ });
18
+ console.log(`👀 Watching folder: ${input.baseFolder} for changes...`);
19
+ watcher.on('add', runFromWatcher);
20
+ return () => watcher.close();
21
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { start } from './file-router';
2
- export * from './types';
1
+ export { generateRoutes } from './generator.js';
2
+ export { generateRoutesWithWatcher } from './generatorWatcher.js';
3
+ export { generateRoutesPlugin } from './plugin.js';
4
+ export { TGenerateRoutesConfig } from './types.js';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,20 +1,3 @@
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
- exports.start = void 0;
18
- var file_router_1 = require("./file-router");
19
- Object.defineProperty(exports, "start", { enumerable: true, get: function () { return file_router_1.start; } });
20
- __exportStar(require("./types"), exports);
1
+ export { generateRoutes } from './generator.js';
2
+ export { generateRoutesWithWatcher } from './generatorWatcher.js';
3
+ export { generateRoutesPlugin } from './plugin.js';
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ import { TGenerateRoutesConfig } from './types.js';
3
+ export declare const generateRoutesPlugin: ({ baseFolder, outputFile, exitCodeOnResolution, }: TGenerateRoutesConfig) => Plugin;
4
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGnD,eAAO,MAAM,oBAAoB,GAAI,mDAIlC,qBAAqB,KAAG,MAW1B,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,13 @@
1
+ import { generateRoutesWithWatcher } from './generatorWatcher.js';
2
+ export const generateRoutesPlugin = ({ baseFolder, outputFile, exitCodeOnResolution = false, }) => {
3
+ return {
4
+ name: 'file-router-plugin',
5
+ apply: 'serve',
6
+ buildStart() {
7
+ generateRoutesWithWatcher({
8
+ input: { baseFolder, outputFile, exitCodeOnResolution },
9
+ options: { debounce: 500 },
10
+ });
11
+ },
12
+ };
13
+ };
@@ -0,0 +1,3 @@
1
+ import type { TRoutesTree } from './types.js';
2
+ export declare const serialize: (routes: TRoutesTree, outputPath: string) => void;
3
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAuB9C,eAAO,MAAM,SAAS,GAAI,QAAQ,WAAW,EAAE,YAAY,MAAM,SAMhE,CAAC"}
@@ -0,0 +1,21 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const stringifyTS = (obj, indent = 0) => {
4
+ const spaces = ' '.repeat(indent);
5
+ const innerSpaces = ' '.repeat(indent + 2);
6
+ if (typeof obj !== 'object' || obj === null) {
7
+ return JSON.stringify(obj);
8
+ }
9
+ const entries = Object.entries(obj).map(([key, value]) => {
10
+ const validKey = /^[a-z_$][a-z0-9_$]*$/i.test(key)
11
+ ? key
12
+ : JSON.stringify(key);
13
+ return `${innerSpaces}${validKey}: ${stringifyTS(value, indent + 2)}`;
14
+ });
15
+ return `{\n${entries.join(',\n')}\n${spaces}}`;
16
+ };
17
+ export const serialize = (routes, outputPath) => {
18
+ const content = `export const routes = ${stringifyTS(routes)} as const;\n`;
19
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
20
+ fs.writeFileSync(outputPath, content, 'utf8');
21
+ };
package/dist/types.d.ts CHANGED
@@ -1,7 +1,15 @@
1
- export type TStartConfigs = {
1
+ type TWatcherOptions = {
2
+ debounce?: number;
3
+ };
4
+ export type TWatcherConfig = {
5
+ input: TGenerateRoutesConfig;
6
+ options?: TWatcherOptions;
7
+ };
8
+ export type TGenerateRoutesConfig = {
2
9
  baseFolder: string;
3
10
  outputFile: string;
4
11
  routeFileName?: string;
12
+ exitCodeOnResolution?: boolean;
5
13
  };
6
14
  export type TRoute = {
7
15
  path: string;
@@ -10,4 +18,5 @@ export type TRoute = {
10
18
  export type TRoutesTree = {
11
19
  [key: string]: TRoute | TRoutesTree;
12
20
  };
21
+ export {};
13
22
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;CACrC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,qBAAqB,CAAC;IAC7B,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;CACrC,CAAC"}
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
package/package.json CHANGED
@@ -1,20 +1,30 @@
1
- {
2
- "name": "ts-file-router",
3
- "version": "1.0.5",
4
- "description": "router based on project files using typescript",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist"
9
- ],
10
- "scripts": {
11
- "build": "tsc"
12
- },
13
- "author": "MatheusF10",
14
- "license": "ISC",
15
- "devDependencies": {
16
- "@types/node": "^24.10.1",
17
- "ts-node": "^10.9.2",
18
- "typescript": "^5.9.3"
19
- }
20
- }
1
+ {
2
+ "name": "ts-file-router",
3
+ "version": "2.0.2",
4
+ "description": "router based on project files using typescript",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "start": "tsx ./src/generator.ts",
13
+ "prebuild": "rimraf dist",
14
+ "build": "tsc"
15
+ },
16
+ "author": "MatheusF10",
17
+ "license": "ISC",
18
+ "devDependencies": {
19
+ "@types/node": "^24.10.1",
20
+ "chokidar": "^5.0.0",
21
+ "rimraf": "^6.1.2",
22
+ "tsx": "^4.21.0",
23
+ "typescript": "^5.9.3",
24
+ "vite": "^7.3.0"
25
+ },
26
+ "peerDependencies": {
27
+ "chokidar": ">=4.0.0",
28
+ "vite": ">=3.0.0"
29
+ }
30
+ }
@@ -1,3 +0,0 @@
1
- import { TStartConfigs } from './types';
2
- export declare const start: ({ baseFolder, outputFile, routeFileName, }: TStartConfigs) => void;
3
- //# sourceMappingURL=file-router.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../src/file-router.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAe,MAAM,SAAS,CAAC;AA+BrD,eAAO,MAAM,KAAK,GAAI,4CAInB,aAAa,SAsEf,CAAC"}
@@ -1,84 +0,0 @@
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.start = void 0;
7
- const path_1 = __importDefault(require("path"));
8
- const promises_1 = __importDefault(require("fs/promises"));
9
- // Serialize routes objects to TS file output
10
- const serializeRoutes = (obj, ident = 2) => {
11
- const pad = ' '.repeat(ident);
12
- let str = '{\n';
13
- for (const key in obj) {
14
- const value = obj[key];
15
- // Manipulate JS keys
16
- const keyStr = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
17
- if (typeof value === 'object' &&
18
- value !== null &&
19
- !('path' in value && 'import' in value)) {
20
- // Create sub folders
21
- str += `${pad}${keyStr}: ${serializeRoutes(value, ident + 2)},\n`;
22
- }
23
- else {
24
- // Routes
25
- str += `${pad}${keyStr}: { path: "${value.path}", import: "${value.import}" },\n`;
26
- }
27
- }
28
- str += ' '.repeat(ident - 2) + '}';
29
- return str;
30
- };
31
- const start = ({ baseFolder, outputFile, routeFileName = 'page.tsx', }) => {
32
- // Get the pages dir to resolve routes
33
- const basePath = path_1.default.resolve(process.cwd(), baseFolder);
34
- // Output file for routes
35
- const output = path_1.default.resolve(process.cwd(), outputFile);
36
- const generateRouter = async () => {
37
- const mapRoutes = async (dir) => {
38
- const routes = {};
39
- const directory = await promises_1.default.readdir(dir);
40
- if (!directory.includes(routeFileName)) {
41
- throw new Error(`Invalid pages structure: The folder "${dir}" must contain a ${routeFileName} file.`);
42
- }
43
- for (const file of directory) {
44
- const fullPath = path_1.default.join(dir, file);
45
- // Get directory info to control file or folder
46
- const dirInfo = await promises_1.default.stat(fullPath);
47
- // Normalize import path to esm pattern
48
- const importPath = './' + path_1.default.relative(basePath, fullPath).replaceAll(/\\/g, '/');
49
- const relativePath = './' + path_1.default.relative(basePath, dir).replaceAll(/\\/g, '/');
50
- // Remove extension from file to naming the route
51
- const key = path_1.default.basename(file, path_1.default.extname(file));
52
- if (dirInfo.isDirectory()) {
53
- // Dev friendly when enter in this conditional file is a directory(folder)
54
- const directory = file;
55
- // Recursion for sub directories
56
- routes[directory] = await mapRoutes(fullPath);
57
- // Skip this directory iteration
58
- continue;
59
- }
60
- routes[key] = {
61
- path: relativePath,
62
- import: importPath,
63
- };
64
- }
65
- return routes;
66
- };
67
- try {
68
- // Create routes
69
- const routes = await mapRoutes(basePath);
70
- // Cria arquivo TS exportando JSON
71
- const tsContent = `// AUTO-GENERATED ROUTES\n\nconst routes = ${serializeRoutes(routes)};\n\nexport default routes;\n`;
72
- // Write .ts file
73
- await promises_1.default.writeFile(output, tsContent, 'utf-8');
74
- console.log('🚀 Routes generated successfully!\n', output);
75
- process.exit(0);
76
- }
77
- catch (err) {
78
- console.error('❌ Error generating routes:', err);
79
- process.exit(1);
80
- }
81
- };
82
- generateRouter();
83
- };
84
- exports.start = start;