@jsarc/initiator 0.0.0 → 0.0.1-beta.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 +99 -15
- package/js/provider.js +4 -20
- package/package.json +1 -1
- package/provider.ts +4 -20
- package/routing.ts +621 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|
|
|
8
|
-
**@jsarc/initiator** est un plugin d'initialisation intelligent pour les applications React avec TypeScript/Javascript. Il génère automatiquement les fichiers de configuration, de routage
|
|
8
|
+
**@jsarc/initiator** est un plugin d'initialisation intelligent pour les applications React avec TypeScript/Javascript. Il génère automatiquement les fichiers de configuration, de routage, d'internationalisation et de providers basés sur la structure de votre projet.
|
|
9
9
|
|
|
10
10
|
## ✨ Fonctionnalités Principales
|
|
11
11
|
|
|
@@ -13,19 +13,22 @@
|
|
|
13
13
|
- **Génération automatique des routes** à partir de la structure du système de fichiers
|
|
14
14
|
- **Configuration modulaire** avec détection automatique des modules
|
|
15
15
|
- **Internationalisation automatisée** avec extraction des clés de traduction
|
|
16
|
+
- **Providers React organisés** par module et priorité
|
|
16
17
|
- **Fichiers de configuration** générés dynamiquement
|
|
17
18
|
|
|
18
19
|
### ⚙️ Initialisation Intelligente
|
|
19
|
-
- **Détection automatique** des fichiers de pages et
|
|
20
|
+
- **Détection automatique** des fichiers de pages, modules et providers
|
|
20
21
|
- **Génération de fichiers TypeScript** typesafe
|
|
21
22
|
- **Support des layouts hiérarchiques** avec héritage automatique
|
|
23
|
+
- **Organisation automatique des providers** (Context, Redux, Router, etc.)
|
|
22
24
|
- **Configuration minimale** requise
|
|
23
25
|
|
|
24
26
|
### 📁 Structure de Projet
|
|
25
27
|
- **Organisation modulaire** naturelle
|
|
26
28
|
- **Support des pages spéciales** (layout, error, 404)
|
|
27
29
|
- **Routes dynamiques** avec paramètres
|
|
28
|
-
- **Modules indépendants** avec leur propre configuration
|
|
30
|
+
- **Modules indépendants** avec leur propre configuration et providers
|
|
31
|
+
- **Providers globaux et par module** hiérarchisés
|
|
29
32
|
|
|
30
33
|
## 📦 Installation
|
|
31
34
|
|
|
@@ -78,10 +81,16 @@ src/
|
|
|
78
81
|
├── auto-config.ts # Configuration générée
|
|
79
82
|
├── auto-intl.ts # Internationalisation générée
|
|
80
83
|
├── auto-routes.tsx # Routes générées
|
|
84
|
+
├── auto-provider.tsx # Providers organisés
|
|
81
85
|
├── config.json # Configuration racine
|
|
82
86
|
├── locales/
|
|
83
87
|
│ ├── en.json # Traductions anglais
|
|
84
88
|
│ └── fr.json # Traductions français
|
|
89
|
+
├── providers/ # Providers globaux
|
|
90
|
+
│ ├── RouterProvider.tsx
|
|
91
|
+
│ ├── ReduxProvider.tsx
|
|
92
|
+
│ ├── ThemeProvider.tsx
|
|
93
|
+
│ └── AuthProvider.tsx
|
|
85
94
|
├── pages/
|
|
86
95
|
│ ├── _layout.tsx # Layout racine
|
|
87
96
|
│ ├── _error.tsx # Page d'erreur
|
|
@@ -93,6 +102,9 @@ src/
|
|
|
93
102
|
├── locales/
|
|
94
103
|
│ ├── en.json # Traductions module
|
|
95
104
|
│ └── fr.json # Traductions module
|
|
105
|
+
├── providers/ # Providers du module
|
|
106
|
+
│ ├── ReduxProvider.tsx
|
|
107
|
+
│ └── AuthProvider.tsx
|
|
96
108
|
└── pages/
|
|
97
109
|
└── index.tsx # Page du module
|
|
98
110
|
```
|
|
@@ -119,6 +131,14 @@ Génération centralisée de configuration :
|
|
|
119
131
|
- **Configuration des modules** depuis `modules/*/config.json`
|
|
120
132
|
- **Fichier TypeScript** avec imports dynamiques
|
|
121
133
|
|
|
134
|
+
### 4. Génération de Providers
|
|
135
|
+
Organisation automatique des providers React :
|
|
136
|
+
- **Détection automatique** des fichiers provider (`Provider.tsx`)
|
|
137
|
+
- **Hiérarchisation intelligente** par priorité
|
|
138
|
+
- **Organisation par module** avec encapsulation automatique
|
|
139
|
+
- **Chargement lazy** avec Suspense intégré
|
|
140
|
+
- **Typage TypeScript** complet
|
|
141
|
+
|
|
122
142
|
## 📚 API du Plugin
|
|
123
143
|
|
|
124
144
|
### Fonction principale
|
|
@@ -137,13 +157,15 @@ init('/chemin/vers/mon/projet');
|
|
|
137
157
|
import {
|
|
138
158
|
TranslationGenerator,
|
|
139
159
|
RouteGenerator,
|
|
140
|
-
ConfigGenerator
|
|
160
|
+
ConfigGenerator,
|
|
161
|
+
ProviderGenerator
|
|
141
162
|
} from '@jsarc/initiator';
|
|
142
163
|
|
|
143
164
|
// Utilisation avancée
|
|
144
165
|
const translationGen = new TranslationGenerator(config);
|
|
145
166
|
const routeGen = new RouteGenerator(config);
|
|
146
167
|
const configGen = new ConfigGenerator(config);
|
|
168
|
+
const providerGen = new ProviderGenerator(config);
|
|
147
169
|
```
|
|
148
170
|
|
|
149
171
|
### Interfaces TypeScript
|
|
@@ -152,8 +174,10 @@ import type {
|
|
|
152
174
|
TranslationConfig,
|
|
153
175
|
RouteConfig,
|
|
154
176
|
ConfigGeneratorOptions,
|
|
177
|
+
ProviderConfig,
|
|
155
178
|
TranslationKey,
|
|
156
|
-
RouteFile
|
|
179
|
+
RouteFile,
|
|
180
|
+
ProviderInfo
|
|
157
181
|
} from '@jsarc/initiator';
|
|
158
182
|
```
|
|
159
183
|
|
|
@@ -173,19 +197,17 @@ console.log('✅ Initialisation terminée !');
|
|
|
173
197
|
### Exemple 2 : Personnalisation avancée
|
|
174
198
|
```typescript
|
|
175
199
|
// scripts/custom-init.ts
|
|
176
|
-
import {
|
|
200
|
+
import { ProviderGenerator } from '@jsarc/initiator';
|
|
177
201
|
|
|
178
202
|
const customConfig = {
|
|
179
203
|
srcDir: './src',
|
|
180
204
|
modulesDir: './src/modules',
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
errorFileName: 'error-page',
|
|
185
|
-
notFoundFileName: 'not-found-page'
|
|
205
|
+
providersDir: './src/providers',
|
|
206
|
+
globalProvidersDir: './src/global-providers', // Dossier personnalisé
|
|
207
|
+
outputFile: './src/generated/provider.tsx'
|
|
186
208
|
};
|
|
187
209
|
|
|
188
|
-
const generator = new
|
|
210
|
+
const generator = new ProviderGenerator(customConfig);
|
|
189
211
|
await generator.generate();
|
|
190
212
|
```
|
|
191
213
|
|
|
@@ -198,7 +220,8 @@ await generator.generate();
|
|
|
198
220
|
"generate": "node scripts/generate-all.js",
|
|
199
221
|
"generate:routes": "node scripts/generate-routes.js",
|
|
200
222
|
"generate:intl": "node scripts/generate-intl.js",
|
|
201
|
-
"generate:config": "node scripts/generate-config.js"
|
|
223
|
+
"generate:config": "node scripts/generate-config.js",
|
|
224
|
+
"generate:providers": "node scripts/generate-providers.js"
|
|
202
225
|
}
|
|
203
226
|
}
|
|
204
227
|
```
|
|
@@ -229,6 +252,17 @@ const routeConfig = {
|
|
|
229
252
|
};
|
|
230
253
|
```
|
|
231
254
|
|
|
255
|
+
### Configuration des providers
|
|
256
|
+
```typescript
|
|
257
|
+
const providerConfig = {
|
|
258
|
+
srcDir: './src',
|
|
259
|
+
modulesDir: './src/modules',
|
|
260
|
+
providersDir: './src/providers',
|
|
261
|
+
outputFile: './src/auto-provider.tsx',
|
|
262
|
+
globalProvidersDir: './src/providers' // Dossier des providers globaux
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
232
266
|
### Fichier config.json racine
|
|
233
267
|
```json
|
|
234
268
|
{
|
|
@@ -273,6 +307,16 @@ const routeConfig = {
|
|
|
273
307
|
| `[...slug].tsx` | Route catch-all | `/*` |
|
|
274
308
|
| `index.tsx` | Page d'index | `/` ou `/dossier/` |
|
|
275
309
|
|
|
310
|
+
### Providers détectés automatiquement
|
|
311
|
+
| Pattern | Type détecté | Priorité |
|
|
312
|
+
|---------|-------------|----------|
|
|
313
|
+
| `*Router*.tsx` | Provider Router | Haute |
|
|
314
|
+
| `*Redux*.tsx` | Provider Redux | Haute |
|
|
315
|
+
| `*Theme*.tsx` | Provider Theme | Moyenne |
|
|
316
|
+
| `*Context*.tsx` | Provider Context | Moyenne |
|
|
317
|
+
| `*Provider.tsx` | Provider générique | Basse |
|
|
318
|
+
| `*Error*.tsx` | Error Boundary | Très basse |
|
|
319
|
+
|
|
276
320
|
### Structure de module
|
|
277
321
|
```
|
|
278
322
|
modules/
|
|
@@ -281,6 +325,10 @@ modules/
|
|
|
281
325
|
├── locales/ # Traductions du module
|
|
282
326
|
│ ├── en.json
|
|
283
327
|
│ └── fr.json
|
|
328
|
+
├── providers/ # Providers du module
|
|
329
|
+
│ ├── ReduxProvider.tsx
|
|
330
|
+
│ ├── AuthProvider.tsx
|
|
331
|
+
│ └── ThemeProvider.tsx
|
|
284
332
|
├── pages/ # Pages du module
|
|
285
333
|
│ ├── _layout.tsx # Layout du module
|
|
286
334
|
│ ├── _error.tsx # Erreur du module
|
|
@@ -307,18 +355,29 @@ npx @jsarc/initiator
|
|
|
307
355
|
### 2. Ajout d'un nouveau module
|
|
308
356
|
```bash
|
|
309
357
|
# Créer la structure du module
|
|
310
|
-
mkdir -p src/modules/admin/{locales,pages,components}
|
|
358
|
+
mkdir -p src/modules/admin/{locales,providers,pages,components}
|
|
311
359
|
|
|
312
360
|
# Ajouter les fichiers de base
|
|
313
361
|
touch src/modules/admin/config.json
|
|
314
362
|
touch src/modules/admin/pages/index.tsx
|
|
315
363
|
touch src/modules/admin/locales/fr.json
|
|
364
|
+
touch src/modules/admin/providers/ReduxProvider.tsx
|
|
316
365
|
|
|
317
366
|
# Régénérer les fichiers
|
|
318
367
|
npx @jsarc/initiator
|
|
319
368
|
```
|
|
320
369
|
|
|
321
|
-
### 3.
|
|
370
|
+
### 3. Ajout d'un provider global
|
|
371
|
+
```bash
|
|
372
|
+
# Créer un provider global
|
|
373
|
+
mkdir -p src/providers
|
|
374
|
+
touch src/providers/ThemeProvider.tsx
|
|
375
|
+
|
|
376
|
+
# Régénérer le fichier auto-provider.tsx
|
|
377
|
+
npx @jsarc/initiator
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 4. Développement avec hot-reload
|
|
322
381
|
```bash
|
|
323
382
|
# Démarrer le serveur de développement
|
|
324
383
|
npm run dev
|
|
@@ -345,6 +404,24 @@ export default defineConfig({
|
|
|
345
404
|
});
|
|
346
405
|
```
|
|
347
406
|
|
|
407
|
+
### Utilisation du AppProvider généré
|
|
408
|
+
```tsx
|
|
409
|
+
// main.tsx
|
|
410
|
+
import React from 'react';
|
|
411
|
+
import ReactDOM from 'react-dom/client';
|
|
412
|
+
import App from './App';
|
|
413
|
+
import { AppProvider } from './auto-provider';
|
|
414
|
+
import './index.css';
|
|
415
|
+
|
|
416
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
417
|
+
<React.StrictMode>
|
|
418
|
+
<AppProvider>
|
|
419
|
+
<App />
|
|
420
|
+
</AppProvider>
|
|
421
|
+
</React.StrictMode>
|
|
422
|
+
);
|
|
423
|
+
```
|
|
424
|
+
|
|
348
425
|
### Avec Next.js (Adaptation)
|
|
349
426
|
```javascript
|
|
350
427
|
// next.config.js
|
|
@@ -403,6 +480,13 @@ module.exports = {
|
|
|
403
480
|
npx @jsarc/initiator
|
|
404
481
|
```
|
|
405
482
|
|
|
483
|
+
4. **Providers non détectés**
|
|
484
|
+
```bash
|
|
485
|
+
# Vérifier que le fichier contient "Provider" dans le nom
|
|
486
|
+
# Ou utilise createContext / Context.Provider
|
|
487
|
+
mv src/my-context.tsx src/MyContextProvider.tsx
|
|
488
|
+
```
|
|
489
|
+
|
|
406
490
|
### Logs de débogage
|
|
407
491
|
```bash
|
|
408
492
|
# Activer les logs détaillés
|
package/js/provider.js
CHANGED
|
@@ -216,20 +216,8 @@ export class ProviderGenerator {
|
|
|
216
216
|
}
|
|
217
217
|
});
|
|
218
218
|
const imports = [
|
|
219
|
-
`import React, {
|
|
220
|
-
`import { Suspense } from 'react';`,
|
|
219
|
+
`import React, { lazy } from 'react';`,
|
|
221
220
|
``,
|
|
222
|
-
`// Loading fallback component`,
|
|
223
|
-
`const LoadingFallback = ({ children }: { children: ReactNode }) => (`,
|
|
224
|
-
` <div style={{`,
|
|
225
|
-
` display: 'flex',`,
|
|
226
|
-
` justifyContent: 'center',`,
|
|
227
|
-
` alignItems: 'center',`,
|
|
228
|
-
` minHeight: '100vh'`,
|
|
229
|
-
` }}>`,
|
|
230
|
-
` <div>Loading...</div>`,
|
|
231
|
-
` </div>`,
|
|
232
|
-
`);`,
|
|
233
221
|
``
|
|
234
222
|
];
|
|
235
223
|
const allProviders = [...globalProviders, ...Object.values(providersByModule).flat()];
|
|
@@ -248,7 +236,7 @@ export class ProviderGenerator {
|
|
|
248
236
|
sortedModuleProviders.forEach(provider => {
|
|
249
237
|
nestedJSX = `<${provider.componentName}>\n ${nestedJSX}\n </${provider.componentName}>`;
|
|
250
238
|
});
|
|
251
|
-
moduleComponents.push(`const ${componentName} = ({ children }
|
|
239
|
+
moduleComponents.push(`const ${componentName} = ({ children }) => (
|
|
252
240
|
${nestedJSX}
|
|
253
241
|
);`);
|
|
254
242
|
moduleComponents.push(``);
|
|
@@ -258,7 +246,7 @@ export class ProviderGenerator {
|
|
|
258
246
|
sortedGlobalProviders.forEach(provider => {
|
|
259
247
|
globalNestedJSX = `<${provider.componentName}>\n ${globalNestedJSX}\n </${provider.componentName}>`;
|
|
260
248
|
});
|
|
261
|
-
moduleComponents.push(`const GlobalProviders = ({ children }
|
|
249
|
+
moduleComponents.push(`const GlobalProviders = ({ children }) => (
|
|
262
250
|
${globalNestedJSX}
|
|
263
251
|
);`);
|
|
264
252
|
moduleComponents.push(``);
|
|
@@ -270,11 +258,7 @@ export class ProviderGenerator {
|
|
|
270
258
|
mainNestedJSX = `<${componentName}>\n ${mainNestedJSX}\n </${componentName}>`;
|
|
271
259
|
});
|
|
272
260
|
mainNestedJSX = `<GlobalProviders>\n ${mainNestedJSX}\n</GlobalProviders>`;
|
|
273
|
-
moduleComponents.push(`export const AppProvider = ({ children }
|
|
274
|
-
<Suspense fallback={<LoadingFallback>{children}</LoadingFallback>}>
|
|
275
|
-
${mainNestedJSX}
|
|
276
|
-
</Suspense>
|
|
277
|
-
);`);
|
|
261
|
+
moduleComponents.push(`export const AppProvider = ({ children }) => (<>${mainNestedJSX}</>);`);
|
|
278
262
|
moduleComponents.push(``);
|
|
279
263
|
moduleComponents.push(`export default AppProvider;`);
|
|
280
264
|
return [
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.0.0",
|
|
6
|
+
"version": "0.0.1-beta.0.2",
|
|
7
7
|
"description": "INITIATOR est un plugin d'initialisation intelligent pour les applications React avec TypeScript/Javascript. Il génère automatiquement les fichiers de configuration, de routage et d'internationalisation basés sur la structure de votre projet.",
|
|
8
8
|
"main": "index.ts",
|
|
9
9
|
"keywords": [],
|
package/provider.ts
CHANGED
|
@@ -289,20 +289,8 @@ export class ProviderGenerator {
|
|
|
289
289
|
});
|
|
290
290
|
|
|
291
291
|
const imports: string[] = [
|
|
292
|
-
`import React, {
|
|
293
|
-
`import { Suspense } from 'react';`,
|
|
292
|
+
`import React, { lazy } from 'react';`,
|
|
294
293
|
``,
|
|
295
|
-
`// Loading fallback component`,
|
|
296
|
-
`const LoadingFallback = ({ children }: { children: ReactNode }) => (`,
|
|
297
|
-
` <div style={{`,
|
|
298
|
-
` display: 'flex',`,
|
|
299
|
-
` justifyContent: 'center',`,
|
|
300
|
-
` alignItems: 'center',`,
|
|
301
|
-
` minHeight: '100vh'`,
|
|
302
|
-
` }}>`,
|
|
303
|
-
` <div>Loading...</div>`,
|
|
304
|
-
` </div>`,
|
|
305
|
-
`);`,
|
|
306
294
|
``
|
|
307
295
|
];
|
|
308
296
|
|
|
@@ -330,7 +318,7 @@ export class ProviderGenerator {
|
|
|
330
318
|
nestedJSX = `<${provider.componentName}>\n ${nestedJSX}\n </${provider.componentName}>`;
|
|
331
319
|
});
|
|
332
320
|
|
|
333
|
-
moduleComponents.push(`const ${componentName} = ({ children }
|
|
321
|
+
moduleComponents.push(`const ${componentName} = ({ children }) => (
|
|
334
322
|
${nestedJSX}
|
|
335
323
|
);`);
|
|
336
324
|
|
|
@@ -343,7 +331,7 @@ export class ProviderGenerator {
|
|
|
343
331
|
globalNestedJSX = `<${provider.componentName}>\n ${globalNestedJSX}\n </${provider.componentName}>`;
|
|
344
332
|
});
|
|
345
333
|
|
|
346
|
-
moduleComponents.push(`const GlobalProviders = ({ children }
|
|
334
|
+
moduleComponents.push(`const GlobalProviders = ({ children }) => (
|
|
347
335
|
${globalNestedJSX}
|
|
348
336
|
);`);
|
|
349
337
|
moduleComponents.push(``);
|
|
@@ -360,11 +348,7 @@ export class ProviderGenerator {
|
|
|
360
348
|
|
|
361
349
|
mainNestedJSX = `<GlobalProviders>\n ${mainNestedJSX}\n</GlobalProviders>`;
|
|
362
350
|
|
|
363
|
-
moduleComponents.push(`export const AppProvider = ({ children }
|
|
364
|
-
<Suspense fallback={<LoadingFallback>{children}</LoadingFallback>}>
|
|
365
|
-
${mainNestedJSX}
|
|
366
|
-
</Suspense>
|
|
367
|
-
);`);
|
|
351
|
+
moduleComponents.push(`export const AppProvider = ({ children }) => (<>${mainNestedJSX}</>);`);
|
|
368
352
|
|
|
369
353
|
moduleComponents.push(``);
|
|
370
354
|
moduleComponents.push(`export default AppProvider;`);
|
package/routing.ts
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { Pajo } from '@jsarc/pajo';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export interface RouteConfig {
|
|
10
|
+
srcDir: string;
|
|
11
|
+
modulesDir: string;
|
|
12
|
+
pagesDir: string;
|
|
13
|
+
outputFile: string;
|
|
14
|
+
layoutFileName: string;
|
|
15
|
+
errorFileName: string;
|
|
16
|
+
notFoundFileName: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RouteFile {
|
|
20
|
+
filePath: string;
|
|
21
|
+
relativePath: string;
|
|
22
|
+
routePath: string;
|
|
23
|
+
componentName: string;
|
|
24
|
+
layoutComponent?: string;
|
|
25
|
+
errorComponent?: string;
|
|
26
|
+
notFoundComponent?: string;
|
|
27
|
+
moduleName?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RouteGenerator {
|
|
31
|
+
private config: RouteConfig;
|
|
32
|
+
private routeFiles: RouteFile[] = [];
|
|
33
|
+
private modules: Set<string> = new Set();
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
config: Partial<RouteConfig> = {},
|
|
37
|
+
dirname: string | undefined = __dirname,
|
|
38
|
+
) {
|
|
39
|
+
this.config = {
|
|
40
|
+
srcDir: config.srcDir || Pajo.join(dirname, 'src') || '',
|
|
41
|
+
modulesDir: config.modulesDir || Pajo.join(dirname, 'src\\modules') || '',
|
|
42
|
+
pagesDir: config.pagesDir || Pajo.join(dirname, 'src\\pages') || '',
|
|
43
|
+
outputFile: config.outputFile || Pajo.join(dirname, 'src\\auto-routes.tsx') || '',
|
|
44
|
+
layoutFileName: config.layoutFileName || '_layout',
|
|
45
|
+
errorFileName: config.errorFileName || '_error',
|
|
46
|
+
notFoundFileName: config.notFoundFileName || '_404'
|
|
47
|
+
};
|
|
48
|
+
console.log(`--> RouteGenerator config:: `, this.config);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public async generate(): Promise<void> {
|
|
52
|
+
console.log('🔍 Scanning for route files...');
|
|
53
|
+
|
|
54
|
+
await this.findRouteFiles();
|
|
55
|
+
|
|
56
|
+
await this.generateAutoRoutesFile();
|
|
57
|
+
|
|
58
|
+
console.log(`✅ Generated ${this.config.outputFile} with ${this.routeFiles.length} routes`);
|
|
59
|
+
console.log(`📦 Found modules with routes: ${Array.from(this.modules).join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async findRouteFiles(): Promise<void> {
|
|
63
|
+
this.routeFiles = [];
|
|
64
|
+
|
|
65
|
+
await this.scanDirectoryForRoutes(this.config.pagesDir);
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(this.config.modulesDir)) {
|
|
68
|
+
const moduleDirs = fs.readdirSync(this.config.modulesDir, { withFileTypes: true })
|
|
69
|
+
.filter(dirent => dirent.isDirectory())
|
|
70
|
+
.map(dirent => dirent.name);
|
|
71
|
+
|
|
72
|
+
for (const moduleName of moduleDirs) {
|
|
73
|
+
const modulePagesDir = path.join(this.config.modulesDir, moduleName, 'pages');
|
|
74
|
+
if (fs.existsSync(modulePagesDir)) {
|
|
75
|
+
this.modules.add(moduleName);
|
|
76
|
+
await this.scanDirectoryForRoutes(modulePagesDir, moduleName);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async scanDirectoryForRoutes(dir: string, moduleName?: string): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
const fullPath = path.join(dir, item.name);
|
|
88
|
+
|
|
89
|
+
if (item.name === 'node_modules' ||
|
|
90
|
+
item.name === 'dist' ||
|
|
91
|
+
item.name === 'build' ||
|
|
92
|
+
item.name.startsWith('.')) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (item.isDirectory()) {
|
|
97
|
+
|
|
98
|
+
await this.scanDirectoryForRoutes(fullPath, moduleName);
|
|
99
|
+
} else if (item.isFile()) {
|
|
100
|
+
|
|
101
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
102
|
+
if (['.tsx', '.jsx'].includes(ext)) {
|
|
103
|
+
const fileName = path.basename(item.name, ext);
|
|
104
|
+
|
|
105
|
+
if (fileName === this.config.layoutFileName ||
|
|
106
|
+
fileName === this.config.errorFileName ||
|
|
107
|
+
fileName === this.config.notFoundFileName) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await this.processRouteFile(fullPath, moduleName);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(`Error scanning directory ${dir}:`, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private async processRouteFile(filePath: string, moduleName?: string): Promise<void> {
|
|
121
|
+
try {
|
|
122
|
+
|
|
123
|
+
let routePath = this.convertFilePathToRoutePath(filePath, moduleName);
|
|
124
|
+
|
|
125
|
+
const componentName = this.extractComponentName(filePath, moduleName);
|
|
126
|
+
|
|
127
|
+
const layoutComponent = this.findLayoutComponent(filePath, moduleName);
|
|
128
|
+
const errorComponent = this.findErrorComponent(filePath, moduleName);
|
|
129
|
+
const notFoundComponent = this.findNotFoundComponent(filePath, moduleName);
|
|
130
|
+
|
|
131
|
+
const routeFile: RouteFile = {
|
|
132
|
+
filePath,
|
|
133
|
+
relativePath: path.relative(this.config.srcDir, filePath),
|
|
134
|
+
routePath,
|
|
135
|
+
componentName,
|
|
136
|
+
layoutComponent,
|
|
137
|
+
errorComponent,
|
|
138
|
+
notFoundComponent,
|
|
139
|
+
moduleName
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
this.routeFiles.push(routeFile);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`Error processing route file ${filePath}:`, error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private convertFilePathToRoutePath(filePath: string, moduleName?: string): string {
|
|
149
|
+
let routePath = filePath;
|
|
150
|
+
|
|
151
|
+
routePath = routePath.replace(this.config.srcDir, '');
|
|
152
|
+
|
|
153
|
+
if (moduleName) {
|
|
154
|
+
|
|
155
|
+
const modulePagesPrefix = `modules${path.sep}${moduleName}${path.sep}pages`;
|
|
156
|
+
if (routePath.includes(modulePagesPrefix)) {
|
|
157
|
+
routePath = routePath.substring(routePath.indexOf(modulePagesPrefix) + modulePagesPrefix.length);
|
|
158
|
+
|
|
159
|
+
routePath = `/${moduleName}${routePath}`;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
|
|
163
|
+
const pagesPrefix = 'pages';
|
|
164
|
+
if (routePath.includes(pagesPrefix)) {
|
|
165
|
+
routePath = routePath.substring(routePath.indexOf(pagesPrefix) + pagesPrefix.length);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
routePath = routePath.replace(/\.(tsx|jsx)$/, '');
|
|
170
|
+
|
|
171
|
+
routePath = routePath.replace(/\\/g, '/');
|
|
172
|
+
|
|
173
|
+
if (routePath.endsWith('/index')) {
|
|
174
|
+
routePath = routePath.replace(/\/index$/, '');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
routePath = routePath.replace(/\[([^\]]+)\]/g, '{$1}');
|
|
178
|
+
|
|
179
|
+
if (!routePath.startsWith('/')) {
|
|
180
|
+
routePath = '/' + routePath;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (routePath === '/' || routePath === '//') {
|
|
184
|
+
return '/';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return routePath;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private extractComponentName(filePath: string, moduleName?: string): string {
|
|
191
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
192
|
+
const dirName = path.dirname(filePath);
|
|
193
|
+
|
|
194
|
+
const parts = dirName.split(path.sep);
|
|
195
|
+
|
|
196
|
+
let startIndex = 0;
|
|
197
|
+
if (moduleName) {
|
|
198
|
+
|
|
199
|
+
const modulePagesPath = path.sep + 'modules' + path.sep + moduleName + path.sep + 'pages';
|
|
200
|
+
startIndex = parts.findIndex((part, index, arr) => {
|
|
201
|
+
const pathSoFar = arr.slice(0, index + 1).join(path.sep);
|
|
202
|
+
return pathSoFar.endsWith(modulePagesPath);
|
|
203
|
+
}) + 1;
|
|
204
|
+
} else {
|
|
205
|
+
|
|
206
|
+
const pagesPath = path.sep + 'pages';
|
|
207
|
+
startIndex = parts.findIndex((part, index, arr) => {
|
|
208
|
+
const pathSoFar = arr.slice(0, index + 1).join(path.sep);
|
|
209
|
+
return pathSoFar.endsWith(pagesPath);
|
|
210
|
+
}) + 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (startIndex < 0) startIndex = 0;
|
|
214
|
+
|
|
215
|
+
const relevantParts = parts.slice(startIndex);
|
|
216
|
+
|
|
217
|
+
let componentName = moduleName ? this.toPascalCase(moduleName) : '';
|
|
218
|
+
|
|
219
|
+
relevantParts.forEach(part => {
|
|
220
|
+
if (part && part !== 'pages') {
|
|
221
|
+
componentName += this.toPascalCase(part);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
let fileNamePart = fileName;
|
|
226
|
+
if (fileName.startsWith('{') && fileName.endsWith('}')) {
|
|
227
|
+
|
|
228
|
+
const paramName = fileName.substring(1, fileName.length - 1);
|
|
229
|
+
fileNamePart = this.toPascalCase(paramName) + 'Params';
|
|
230
|
+
} else {
|
|
231
|
+
fileNamePart = this.toPascalCase(fileName);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!fileNamePart.endsWith('Page')) {
|
|
235
|
+
fileNamePart += 'Page';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const reservedNames = ['Layout', 'ErrorBoundary', 'NotFound'];
|
|
239
|
+
if (reservedNames.includes(fileNamePart)) {
|
|
240
|
+
fileNamePart += 'Component';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return componentName + fileNamePart;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private toPascalCase(str: string): string {
|
|
247
|
+
if (!str) return '';
|
|
248
|
+
return str
|
|
249
|
+
.split(/[-_]/)
|
|
250
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
251
|
+
.join('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private findLayoutComponent(filePath: string, moduleName?: string): string | undefined {
|
|
255
|
+
const dir = path.dirname(filePath);
|
|
256
|
+
let currentDir = dir;
|
|
257
|
+
|
|
258
|
+
while (currentDir !== this.config.srcDir && currentDir !== path.dirname(this.config.srcDir)) {
|
|
259
|
+
const layoutPath = path.join(currentDir, `${this.config.layoutFileName}.tsx`);
|
|
260
|
+
|
|
261
|
+
if (fs.existsSync(layoutPath)) {
|
|
262
|
+
return this.generateComponentName(layoutPath, moduleName, 'Layout');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (moduleName) {
|
|
266
|
+
const modulePath = path.join(this.config.modulesDir, moduleName);
|
|
267
|
+
if (currentDir === modulePath) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
currentDir = path.dirname(currentDir);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const globalLayoutPath = path.join(this.config.pagesDir, `${this.config.layoutFileName}.tsx`);
|
|
276
|
+
if (fs.existsSync(globalLayoutPath)) {
|
|
277
|
+
return 'Layout';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private findErrorComponent(filePath: string, moduleName?: string): string | undefined {
|
|
284
|
+
const dir = path.dirname(filePath);
|
|
285
|
+
let currentDir = dir;
|
|
286
|
+
|
|
287
|
+
while (currentDir !== this.config.srcDir && currentDir !== path.dirname(this.config.srcDir)) {
|
|
288
|
+
const errorPath = path.join(currentDir, `${this.config.errorFileName}.tsx`);
|
|
289
|
+
|
|
290
|
+
if (fs.existsSync(errorPath)) {
|
|
291
|
+
return this.generateComponentName(errorPath, moduleName, 'ErrorBoundary');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (moduleName) {
|
|
295
|
+
const modulePath = path.join(this.config.modulesDir, moduleName);
|
|
296
|
+
if (currentDir === modulePath) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
currentDir = path.dirname(currentDir);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const globalErrorPath = path.join(this.config.pagesDir, `${this.config.errorFileName}.tsx`);
|
|
305
|
+
if (fs.existsSync(globalErrorPath)) {
|
|
306
|
+
return 'ErrorBoundary';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private findNotFoundComponent(filePath: string, moduleName?: string): string | undefined {
|
|
313
|
+
if (!moduleName) return undefined;
|
|
314
|
+
|
|
315
|
+
const modulePagesDir = path.join(this.config.modulesDir, moduleName, 'pages');
|
|
316
|
+
const notFoundPath = path.join(modulePagesDir, `${this.config.notFoundFileName}.tsx`);
|
|
317
|
+
|
|
318
|
+
if (fs.existsSync(notFoundPath)) {
|
|
319
|
+
return this.generateComponentName(notFoundPath, moduleName, 'NotFound');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private generateComponentName(filePath: string, moduleName: string | undefined, suffix: string): string {
|
|
326
|
+
if (!moduleName) {
|
|
327
|
+
return suffix;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
331
|
+
const dirName = path.dirname(filePath);
|
|
332
|
+
|
|
333
|
+
const modulePagesPath = path.join(this.config.modulesDir, moduleName, 'pages');
|
|
334
|
+
const relativePath = path.relative(modulePagesPath, dirName);
|
|
335
|
+
|
|
336
|
+
let componentName = this.toPascalCase(moduleName);
|
|
337
|
+
|
|
338
|
+
if (relativePath && relativePath !== '.') {
|
|
339
|
+
const dirParts = relativePath.split(path.sep).filter(part => part && part !== '.');
|
|
340
|
+
dirParts.forEach(part => {
|
|
341
|
+
componentName += this.toPascalCase(part);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (fileName !== this.config.layoutFileName &&
|
|
346
|
+
fileName !== this.config.errorFileName &&
|
|
347
|
+
fileName !== this.config.notFoundFileName) {
|
|
348
|
+
componentName += this.toPascalCase(fileName);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return componentName + suffix;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private async generateAutoRoutesFile(): Promise<void> {
|
|
355
|
+
const outputDir = path.dirname(this.config.outputFile);
|
|
356
|
+
|
|
357
|
+
if (!fs.existsSync(outputDir)) {
|
|
358
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const content = this.generateFileContent();
|
|
362
|
+
fs.writeFileSync(this.config.outputFile, content, 'utf-8');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private getRelativeImportPath(targetPath: string): string {
|
|
366
|
+
const outputDir = path.dirname(this.config.outputFile);
|
|
367
|
+
const relative = path.relative(outputDir, targetPath);
|
|
368
|
+
|
|
369
|
+
let importPath = relative.replace(/\\/g, '/');
|
|
370
|
+
|
|
371
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
|
|
372
|
+
importPath = './' + importPath;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
importPath = importPath.replace(/\.(tsx|jsx)$/, '');
|
|
376
|
+
|
|
377
|
+
return importPath;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private generateFileContent(): string {
|
|
381
|
+
|
|
382
|
+
const components = new Set<string>();
|
|
383
|
+
const imports: string[] = [
|
|
384
|
+
`import { lazy } from 'react';`,
|
|
385
|
+
`import type { RouteObject } from 'react-router-dom';`,
|
|
386
|
+
``
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const globalLayoutPath = path.join(this.config.pagesDir, `${this.config.layoutFileName}.tsx`);
|
|
390
|
+
const globalErrorPath = path.join(this.config.pagesDir, `${this.config.errorFileName}.tsx`);
|
|
391
|
+
const global404Path = path.join(this.config.pagesDir, `${this.config.notFoundFileName}.tsx`);
|
|
392
|
+
|
|
393
|
+
if (fs.existsSync(globalLayoutPath)) {
|
|
394
|
+
const importPath = this.getRelativeImportPath(globalLayoutPath);
|
|
395
|
+
imports.push(`const Layout = lazy(() => import('${importPath}'));`);
|
|
396
|
+
components.add('Layout');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (fs.existsSync(globalErrorPath)) {
|
|
400
|
+
const importPath = this.getRelativeImportPath(globalErrorPath);
|
|
401
|
+
imports.push(`const ErrorBoundary = lazy(() => import('${importPath}'));`);
|
|
402
|
+
components.add('ErrorBoundary');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (fs.existsSync(global404Path)) {
|
|
406
|
+
const importPath = this.getRelativeImportPath(global404Path);
|
|
407
|
+
imports.push(`const NotFound = lazy(() => import('${importPath}'));`);
|
|
408
|
+
components.add('NotFound');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
imports.push(``);
|
|
412
|
+
|
|
413
|
+
const specialComponents = new Map<string, string>();
|
|
414
|
+
|
|
415
|
+
const findSpecialFiles = (baseDir: string, isModule: boolean = false, moduleName?: string) => {
|
|
416
|
+
if (!fs.existsSync(baseDir)) return;
|
|
417
|
+
|
|
418
|
+
const walkDir = (dir: string) => {
|
|
419
|
+
try {
|
|
420
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
421
|
+
|
|
422
|
+
for (const item of items) {
|
|
423
|
+
const fullPath = path.join(dir, item.name);
|
|
424
|
+
|
|
425
|
+
if (item.isDirectory()) {
|
|
426
|
+
walkDir(fullPath);
|
|
427
|
+
} else if (item.isFile()) {
|
|
428
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
429
|
+
if (['.tsx', '.jsx'].includes(ext)) {
|
|
430
|
+
const fileName = path.basename(item.name, ext);
|
|
431
|
+
|
|
432
|
+
if (fileName === this.config.layoutFileName ||
|
|
433
|
+
fileName === this.config.errorFileName ||
|
|
434
|
+
fileName === this.config.notFoundFileName) {
|
|
435
|
+
|
|
436
|
+
const componentName = this.generateComponentName(fullPath, moduleName,
|
|
437
|
+
fileName === this.config.layoutFileName ? 'Layout' :
|
|
438
|
+
fileName === this.config.errorFileName ? 'ErrorBoundary' : 'NotFound');
|
|
439
|
+
|
|
440
|
+
if (!components.has(componentName)) {
|
|
441
|
+
specialComponents.set(fullPath, componentName);
|
|
442
|
+
components.add(componentName);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error(`Error walking directory ${dir}:`, error);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
walkDir(baseDir);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
findSpecialFiles(this.config.pagesDir, false);
|
|
457
|
+
|
|
458
|
+
this.modules.forEach(moduleName => {
|
|
459
|
+
const modulePagesDir = path.join(this.config.modulesDir, moduleName, 'pages');
|
|
460
|
+
if (fs.existsSync(modulePagesDir)) {
|
|
461
|
+
findSpecialFiles(modulePagesDir, true, moduleName);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
specialComponents.forEach((componentName, filePath) => {
|
|
466
|
+
const importPath = this.getRelativeImportPath(filePath);
|
|
467
|
+
imports.push(`const ${componentName} = lazy(() => import('${importPath}'));`);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
if (specialComponents.size > 0) {
|
|
471
|
+
imports.push(``);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.routeFiles.forEach(route => {
|
|
475
|
+
|
|
476
|
+
const importPath = this.getRelativeImportPath(route.filePath);
|
|
477
|
+
imports.push(`const ${route.componentName} = lazy(() => import('${importPath}'));`);
|
|
478
|
+
components.add(route.componentName);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
imports.push(``);
|
|
482
|
+
|
|
483
|
+
const routes: string[] = ['export const routes: RouteObject[] = ['];
|
|
484
|
+
|
|
485
|
+
const sortedRoutes = this.routeFiles.sort((a, b) => {
|
|
486
|
+
|
|
487
|
+
if (a.routePath === '/') return -1;
|
|
488
|
+
if (b.routePath === '/') return 1;
|
|
489
|
+
|
|
490
|
+
if (!a.moduleName && b.moduleName) return -1;
|
|
491
|
+
if (a.moduleName && !b.moduleName) return 1;
|
|
492
|
+
|
|
493
|
+
if (a.moduleName === b.moduleName) {
|
|
494
|
+
return a.routePath.localeCompare(b.routePath);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return 0;
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
let lastModuleName: string | undefined = undefined;
|
|
501
|
+
|
|
502
|
+
sortedRoutes.forEach((route, index) => {
|
|
503
|
+
|
|
504
|
+
if (route.moduleName !== lastModuleName) {
|
|
505
|
+
if (lastModuleName !== undefined) {
|
|
506
|
+
routes.push(``);
|
|
507
|
+
}
|
|
508
|
+
if (route.moduleName) {
|
|
509
|
+
routes.push(` // ${route.moduleName} module routes`);
|
|
510
|
+
}
|
|
511
|
+
lastModuleName = route.moduleName;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const routeLines: string[] = [' {'];
|
|
515
|
+
routeLines.push(` path: '${route.routePath.replace(/\{([^}]+)\}/g, ':$1')}',`);
|
|
516
|
+
|
|
517
|
+
let layoutComponent = route.layoutComponent;
|
|
518
|
+
if (!layoutComponent && route.moduleName) {
|
|
519
|
+
|
|
520
|
+
const moduleLayoutPath = path.join(this.config.modulesDir, route.moduleName, 'pages', `${this.config.layoutFileName}.tsx`);
|
|
521
|
+
if (fs.existsSync(moduleLayoutPath)) {
|
|
522
|
+
layoutComponent = this.generateComponentName(moduleLayoutPath, route.moduleName, 'Layout');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (layoutComponent) {
|
|
527
|
+
routeLines.push(` element: <${layoutComponent}><${route.componentName} /></${layoutComponent}>,`);
|
|
528
|
+
} else {
|
|
529
|
+
routeLines.push(` element: <${route.componentName} />,`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
let errorComponent = route.errorComponent;
|
|
533
|
+
if (!errorComponent && route.moduleName) {
|
|
534
|
+
|
|
535
|
+
const moduleErrorPath = path.join(this.config.modulesDir, route.moduleName, 'pages', `${this.config.errorFileName}.tsx`);
|
|
536
|
+
if (fs.existsSync(moduleErrorPath)) {
|
|
537
|
+
errorComponent = this.generateComponentName(moduleErrorPath, route.moduleName, 'ErrorBoundary');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (errorComponent) {
|
|
542
|
+
routeLines.push(` errorElement: <${errorComponent} />`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (routeLines[routeLines.length - 1].endsWith(',')) {
|
|
546
|
+
routeLines[routeLines.length - 1] = routeLines[routeLines.length - 1].slice(0, -1);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
routeLines.push(' }');
|
|
550
|
+
|
|
551
|
+
if (index < sortedRoutes.length - 1) {
|
|
552
|
+
routeLines[routeLines.length - 1] += ',';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
routes.push(routeLines.join('\n'));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const modulesWith404 = new Set<string>();
|
|
559
|
+
specialComponents.forEach((componentName, filePath) => {
|
|
560
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
561
|
+
if (fileName === this.config.notFoundFileName) {
|
|
562
|
+
|
|
563
|
+
const pathParts = filePath.split(path.sep);
|
|
564
|
+
const modulesIndex = pathParts.indexOf('modules');
|
|
565
|
+
if (modulesIndex !== -1 && modulesIndex + 1 < pathParts.length) {
|
|
566
|
+
const moduleName = pathParts[modulesIndex + 1];
|
|
567
|
+
if (!modulesWith404.has(moduleName)) {
|
|
568
|
+
modulesWith404.add(moduleName);
|
|
569
|
+
|
|
570
|
+
if (routes[routes.length - 1].endsWith(',')) {
|
|
571
|
+
routes[routes.length - 1] = routes[routes.length - 1].slice(0, -1);
|
|
572
|
+
}
|
|
573
|
+
routes.push(`,`);
|
|
574
|
+
routes.push(` {`);
|
|
575
|
+
routes.push(` path: '/${moduleName}/*',`.replace(/\{([^}]+)\}/g, ':$1'));
|
|
576
|
+
routes.push(` element: <${componentName} />`);
|
|
577
|
+
routes.push(` }`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (components.has('NotFound')) {
|
|
584
|
+
if (routes[routes.length - 1].endsWith(',')) {
|
|
585
|
+
routes[routes.length - 1] = routes[routes.length - 1].slice(0, -1);
|
|
586
|
+
}
|
|
587
|
+
routes.push(`,`);
|
|
588
|
+
routes.push(` {`);
|
|
589
|
+
routes.push(` path: '*',`.replace(/\{([^}]+)\}/g, ':$1'));
|
|
590
|
+
routes.push(` element: <NotFound />`);
|
|
591
|
+
routes.push(` }`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
routes.push('];');
|
|
595
|
+
routes.push('');
|
|
596
|
+
routes.push('export default routes;');
|
|
597
|
+
|
|
598
|
+
return [
|
|
599
|
+
...imports,
|
|
600
|
+
'',
|
|
601
|
+
...routes
|
|
602
|
+
].join('\n');
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function RouteInitiator(
|
|
607
|
+
dirname: string | undefined = __dirname
|
|
608
|
+
) {
|
|
609
|
+
const config: Partial<RouteConfig> = {};
|
|
610
|
+
|
|
611
|
+
const generator = new RouteGenerator(config, dirname);
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
await generator.generate();
|
|
615
|
+
console.log('🎉 Route generation completed successfully!');
|
|
616
|
+
} catch (err) {
|
|
617
|
+
console.error('❌ Failed to generate routes: ', err);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export default RouteInitiator;
|