ts-file-router 5.0.0 → 6.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/README.md CHANGED
@@ -39,7 +39,6 @@ import { generateRoutes } from 'ts-file-router';
39
39
  generateRoutes({
40
40
  baseFolder: 'src/screens',
41
41
  outputFile: 'src/screens/routes.ts',
42
- routeFileName: 'page.tsx',
43
42
  });
44
43
  ```
45
44
 
@@ -100,7 +99,7 @@ or setup generateRoutesPlugin in vite.config.ts
100
99
  export const routes = {
101
100
  page: {
102
101
  path: '/',
103
- import: () => import('./page'),
102
+ import: import('./page'),
104
103
  },
105
104
  } as const;
106
105
  ```
@@ -1,3 +1,3 @@
1
1
  import type { TGenerateRoutesConfig } from './types.js';
2
- export declare const generateRoutes: ({ baseFolder, outputFile, routeFileName, options, }: TGenerateRoutesConfig) => void;
2
+ export declare const generateRoutes: ({ baseFolder, outputFile, options, }: TGenerateRoutesConfig) => void;
3
3
  //# sourceMappingURL=generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgBrE,eAAO,MAAM,cAAc,GAAI,qDAK5B,qBAAqB,SA2IvB,CAAC"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKrE,eAAO,MAAM,cAAc,GAAI,sCAI5B,qBAAqB,SA6IvB,CAAC"}
package/dist/generator.js CHANGED
@@ -1,12 +1,7 @@
1
- import { serialize } from './serialize.js';
1
+ import { FileHelper, SerializeHelper } from './helpers/index.js';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
- const getIgnoredOutputFile = (file, output) => file.includes(output);
5
- const getIgnoredFiles = (file, output) => file.includes('index') ||
6
- file.startsWith('_') ||
7
- getIgnoredOutputFile(file, output);
8
- const cleanPaths = (path) => path.replaceAll(/\\/gi, '/').replaceAll(/.(tsx|ts|jsx|js)/gi, '');
9
- export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.tsx', options = { exitCodeOnResolution: true }, }) => {
4
+ export const generateRoutes = ({ baseFolder, outputFile, options = { exitCodeOnResolution: true }, }) => {
10
5
  // Get the pages dir to resolve routes
11
6
  const basePath = path.resolve(process.cwd(), baseFolder);
12
7
  // Output file for routes
@@ -14,12 +9,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
14
9
  const mapRoutes = async (dir) => {
15
10
  const routes = {};
16
11
  const directory = await fs.readdir(dir);
17
- if (!directory.includes(routeFileName)) {
18
- throw new Error(`Invalid pages structure: The folder "${dir}" must contain a ${routeFileName} file.`);
19
- }
20
12
  for (const file of directory) {
13
+ if (!directory.length || directory.length === 0) {
14
+ throw new Error(`Invalid pages structure: The folder "${dir}" must contain at least one valid file.`);
15
+ }
21
16
  // ignore index files, underscore marked, or route file generated
22
- if (getIgnoredFiles(file, outputFile)) {
17
+ if (FileHelper.getIgnoredFiles(file, outputFile)) {
23
18
  continue;
24
19
  }
25
20
  const fullPath = path.join(dir, file);
@@ -27,7 +22,6 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
27
22
  const dirInfo = await fs.stat(fullPath);
28
23
  // Path to browser sync if necessary
29
24
  const relativePath = '/' + path.relative(basePath, dir);
30
- // Normalize import path to esm pattern
31
25
  const importPath = './' + path.relative(basePath, fullPath);
32
26
  // Remove extension from file to naming the route
33
27
  const key = path.basename(file, path.extname(file));
@@ -39,10 +33,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
39
33
  continue;
40
34
  }
41
35
  // Mount the route object with path like "/folder" and import
42
- // import will be like "(./baseFolder/file or ./baseFolder/folders).extension"
36
+ // import will be like "import((./baseFolder/file or ./baseFolder/folders).extension)"
43
37
  routes[key] = {
44
- path: cleanPaths(relativePath),
45
- import: cleanPaths(importPath),
38
+ // Normalize path
39
+ path: FileHelper.cleanPaths(relativePath),
40
+ // Normalize import path to esm pattern
41
+ import: FileHelper.cleanPaths(importPath),
46
42
  };
47
43
  }
48
44
  return routes;
@@ -52,7 +48,7 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
52
48
  // Create routes
53
49
  const routes = await mapRoutes(basePath);
54
50
  // Create ts file
55
- await serialize(routes, output);
51
+ await SerializeHelper.serializeOutputFile(routes, output);
56
52
  // Promise writeFile was successfully resolved
57
53
  console.log('🚀 Routes generated successfully!\n');
58
54
  if (options.exitCodeOnResolution) {
@@ -85,7 +81,7 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
85
81
  }, debounce);
86
82
  };
87
83
  watcher.on('all', (ev, file) => {
88
- const ignoredOuput = getIgnoredOutputFile(file, outputFile);
84
+ const ignoredOuput = FileHelper.getIgnoredOutputFile(file, outputFile);
89
85
  // If output file as deleted regenerate it
90
86
  if (ignoredOuput && ev === 'unlink') {
91
87
  console.log(`🚨 ${outputFile} was deleted, regenerating...`);
@@ -94,11 +90,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
94
90
  }
95
91
  const watchOnEvents = [
96
92
  'add',
93
+ 'addDir',
97
94
  'change',
98
95
  'unlink',
99
96
  'unlinkDir',
100
97
  ];
101
- const ignoredGeneralFiles = getIgnoredFiles(file, outputFile);
98
+ const ignoredGeneralFiles = FileHelper.getIgnoredFiles(file, outputFile);
102
99
  // If added, or change (renamed) or deleted, update routes
103
100
  if (watchOnEvents.includes(ev) && !ignoredGeneralFiles) {
104
101
  console.log(`🔎 Watching files from path: "${file}" for changes...`);
@@ -0,0 +1,6 @@
1
+ export declare const FileHelper: {
2
+ readonly cleanPaths: (path: string) => string;
3
+ readonly getIgnoredFiles: (file: string, output: string) => boolean;
4
+ readonly getIgnoredOutputFile: (file: string, output: string) => boolean;
5
+ };
6
+ //# sourceMappingURL=fileHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/fileHelper.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,UAAU;gCAHG,MAAM;qCALD,MAAM,UAAU,MAAM;0CAHjB,MAAM,UAAU,MAAM;CAehD,CAAC"}
@@ -0,0 +1,10 @@
1
+ const getIgnoredOutputFile = (file, output) => file.includes(output);
2
+ const getIgnoredFiles = (file, output) => file.includes('index') ||
3
+ file.startsWith('_') ||
4
+ getIgnoredOutputFile(file, output);
5
+ const cleanPaths = (path) => path.replaceAll(/\\/gi, '/').replaceAll(/.(tsx|ts|jsx|js)/gi, '');
6
+ export const FileHelper = {
7
+ cleanPaths,
8
+ getIgnoredFiles,
9
+ getIgnoredOutputFile,
10
+ };
@@ -0,0 +1,3 @@
1
+ export * from './serializeHelper.js';
2
+ export * from './fileHelper.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './serializeHelper.js';
2
+ export * from './fileHelper.js';
@@ -0,0 +1,7 @@
1
+ import { TRouteLeaf, TRoutesTree } from '../types.js';
2
+ export declare const SerializeHelper: {
3
+ readonly isRouteLeaf: (value: unknown) => value is TRouteLeaf;
4
+ readonly serializeOutputFile: (routes: TRoutesTree, outputPath: string) => Promise<void>;
5
+ readonly formatAndWriteOutputFile: (filePath: string, code: string) => Promise<void>;
6
+ };
7
+ //# sourceMappingURL=serializeHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializeHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/serializeHelper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqJtD,eAAO,MAAM,eAAe;kCAnHA,OAAO,KAAG,KAAK,IAAI,UAAU;2CAoGd,WAAW,cAAc,MAAM;kDAlBxB,MAAM,QAAQ,MAAM;CAqC5D,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { Biome, Distribution } from '@biomejs/js-api';
2
+ import ts from 'typescript';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ const biomeInstance = {
6
+ biome: null,
7
+ project: null,
8
+ };
9
+ const getBiomeSingleton = async () => {
10
+ if (!biomeInstance.biome) {
11
+ biomeInstance.biome = await Biome.create({
12
+ distribution: Distribution.NODE,
13
+ });
14
+ }
15
+ if (!biomeInstance.project) {
16
+ biomeInstance.project = biomeInstance.biome.openProject();
17
+ biomeInstance.biome.applyConfiguration(biomeInstance.project.projectKey, {
18
+ formatter: { enabled: true, indentStyle: 'space', lineWidth: 100 },
19
+ javascript: { formatter: { quoteStyle: 'single' } },
20
+ });
21
+ }
22
+ return {
23
+ projectKey: biomeInstance.project.projectKey,
24
+ formatContent: biomeInstance.biome.formatContent,
25
+ };
26
+ };
27
+ // Type guard
28
+ const isRouteLeaf = (value) => {
29
+ return (typeof value === 'object' &&
30
+ value !== null &&
31
+ 'path' in value &&
32
+ 'import' in value);
33
+ };
34
+ const createRouteObject = (obj) => {
35
+ const properties = Object.entries(obj).map(([key, value]) => {
36
+ if (isRouteLeaf(value)) {
37
+ return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(key), ts.factory.createObjectLiteralExpression([
38
+ ts.factory.createPropertyAssignment('path', ts.factory.createStringLiteral(value.path)),
39
+ ts.factory.createPropertyAssignment('import', ts.factory.createCallExpression(ts.factory.createIdentifier('import'), undefined, [ts.factory.createStringLiteral(value.import)])),
40
+ ], true));
41
+ }
42
+ // Recursive object
43
+ return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(key), createRouteObject(value));
44
+ });
45
+ return ts.factory.createObjectLiteralExpression(properties, true);
46
+ };
47
+ const createRoutesFile = (obj) => {
48
+ const routesObject = createRouteObject(obj);
49
+ // Create AST from routes variable
50
+ const exportStatement = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
51
+ ts.factory.createVariableDeclaration('routes', undefined, undefined, ts.factory.createAsExpression(routesObject, ts.factory.createTypeReferenceNode('const'))),
52
+ ], ts.NodeFlags.Const));
53
+ // Add the comment
54
+ ts.addSyntheticLeadingComment(exportStatement, ts.SyntaxKind.SingleLineCommentTrivia, ' GENERATED WITH TS-FILE-ROUTER DO NOT EDIT', true);
55
+ // Create the source file
56
+ return ts.factory.createSourceFile([exportStatement], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
57
+ };
58
+ const formatAndWriteOutputFile = async (filePath, code) => {
59
+ const biomeInstance = await getBiomeSingleton();
60
+ const formatted = biomeInstance.formatContent(biomeInstance.projectKey, code, {
61
+ filePath: path.basename(filePath),
62
+ });
63
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
64
+ fs.writeFileSync(filePath, formatted.content, 'utf8');
65
+ };
66
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
67
+ const serializeOutputFile = async (routes, outputPath) => {
68
+ const code = createRoutesFile(routes);
69
+ try {
70
+ await formatAndWriteOutputFile(path.resolve(outputPath), printer.printFile(code));
71
+ console.log('✨ File was parsed succesfully');
72
+ }
73
+ catch (err) {
74
+ console.error('❌ Error parsing file:\n', err);
75
+ }
76
+ };
77
+ export const SerializeHelper = {
78
+ isRouteLeaf,
79
+ serializeOutputFile,
80
+ formatAndWriteOutputFile,
81
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-file-router",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "router based on project files using typescript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,3 +0,0 @@
1
- import type { TRoutesTree } from './types.js';
2
- export declare const serialize: (routes: TRoutesTree, outputPath: string) => Promise<void>;
3
- //# sourceMappingURL=serialize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,YAAY,CAAC;AAmD1D,eAAO,MAAM,SAAS,GAAU,QAAQ,WAAW,EAAE,YAAY,MAAM,kBActE,CAAC"}
package/dist/serialize.js DELETED
@@ -1,51 +0,0 @@
1
- import { Biome, Distribution } from '@biomejs/js-api';
2
- import * as fs from 'node:fs';
3
- import * as path from 'node:path';
4
- const isRouteLeaf = (value) => {
5
- return (typeof value === 'object' &&
6
- value !== null &&
7
- 'path' in value &&
8
- 'import' in value);
9
- };
10
- const stringifyRoutes = (obj) => {
11
- const entries = Object.entries(obj).map(([key, value]) => {
12
- if (isRouteLeaf(value)) {
13
- return `'${key}': {
14
- path: '${value.path}',
15
- import: import('${value.import}')
16
- }`;
17
- }
18
- return `'${key}': ${stringifyRoutes(value)}`;
19
- });
20
- return `{ ${entries.join(',\n')} }`;
21
- };
22
- let biome = null;
23
- const format = async (filePath, content) => {
24
- if (biome === null) {
25
- biome = await Biome.create({ distribution: Distribution.NODE });
26
- }
27
- const project = biome.openProject();
28
- biome.applyConfiguration(project.projectKey, {
29
- formatter: { enabled: true, indentStyle: 'space', lineWidth: 100 },
30
- javascript: { formatter: { quoteStyle: 'single' } },
31
- });
32
- const formatted = biome.formatContent(project.projectKey, content, {
33
- filePath: path.basename(filePath),
34
- });
35
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
36
- fs.writeFileSync(filePath, formatted.content, 'utf8');
37
- };
38
- export const serialize = async (routes, outputPath) => {
39
- const rawCode = `
40
- // GENERATED WITH TS-FILE-ROUTER DO NOT EDIT
41
-
42
- export const routes = ${stringifyRoutes(routes)} as const;
43
- \n`;
44
- try {
45
- await format(path.resolve(outputPath), rawCode);
46
- console.log('✨ File was parsed succesfully');
47
- }
48
- catch (err) {
49
- console.error('❌ Error parsing file:\n', err);
50
- }
51
- };