@jsarc/initiator 0.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 +440 -0
- package/config.ts +153 -0
- package/index.ts +13 -0
- package/intl.ts +353 -0
- package/js/config.js +110 -0
- package/js/index.js +10 -0
- package/js/intl.js +274 -0
- package/js/provider.js +323 -0
- package/js/routing.js +473 -0
- package/package.json +20 -0
- package/provider.ts +423 -0
- package/tsconfig.json +27 -0
package/js/intl.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import * as ts from 'typescript';
|
|
5
|
+
import { Pajo, } from '@jsarc/pajo';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
export class TranslationGenerator {
|
|
9
|
+
config;
|
|
10
|
+
keys = [];
|
|
11
|
+
modules = new Set();
|
|
12
|
+
importedModules = new Set();
|
|
13
|
+
constructor(config = {}, dirname = __dirname) {
|
|
14
|
+
this.config = {
|
|
15
|
+
srcDir: config.srcDir || Pajo.join(dirname, 'src') || '',
|
|
16
|
+
supportedLocales: config.supportedLocales || ['en', 'fr'],
|
|
17
|
+
outputFile: config.outputFile || Pajo.join(dirname, 'src\\auto-intl.ts') || '',
|
|
18
|
+
modulesDir: config.modulesDir || Pajo.join(dirname, 'src\\modules') || '',
|
|
19
|
+
localesDir: config.localesDir || Pajo.join(dirname, 'src\\locales') || ''
|
|
20
|
+
};
|
|
21
|
+
console.log(`--> this.config:: `, this.config);
|
|
22
|
+
}
|
|
23
|
+
async generate() {
|
|
24
|
+
console.log('🔍 Scanning source files for translation keys...');
|
|
25
|
+
const srcFiles = this.findSourceFiles(this.config.srcDir);
|
|
26
|
+
for (const file of srcFiles) {
|
|
27
|
+
await this.extractKeysFromFile(file);
|
|
28
|
+
}
|
|
29
|
+
await this.discoverModules();
|
|
30
|
+
await this.generateAutoIntlFile();
|
|
31
|
+
console.log(`✅ Generated ${this.config.outputFile} with ${this.keys.length} translation keys`);
|
|
32
|
+
console.log(`📦 Found modules: ${Array.from(this.modules).join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
findSourceFiles(dir) {
|
|
35
|
+
const files = [];
|
|
36
|
+
try {
|
|
37
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const fullPath = path.join(dir, item.name);
|
|
40
|
+
if (item.name === 'node_modules' ||
|
|
41
|
+
item.name === 'dist' ||
|
|
42
|
+
item.name === 'build' ||
|
|
43
|
+
fullPath === this.config.outputFile) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (item.isDirectory()) {
|
|
47
|
+
files.push(...this.findSourceFiles(fullPath));
|
|
48
|
+
}
|
|
49
|
+
else if (item.isFile()) {
|
|
50
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
51
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
52
|
+
files.push(fullPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(`Error scanning directory ${dir}:`, error);
|
|
59
|
+
}
|
|
60
|
+
return files;
|
|
61
|
+
}
|
|
62
|
+
async extractKeysFromFile(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
65
|
+
const sourceFile = ts.createSourceFile(path.basename(filePath), content, ts.ScriptTarget.Latest, true);
|
|
66
|
+
this.visitNode(sourceFile, filePath);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error(`Error processing file ${filePath}:`, error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
visitNode(node, filePath) {
|
|
73
|
+
if (ts.isCallExpression(node)) {
|
|
74
|
+
const expression = node.expression;
|
|
75
|
+
if (ts.isIdentifier(expression) && expression.text === 't') {
|
|
76
|
+
this.extractTFunctionCall(node, filePath);
|
|
77
|
+
}
|
|
78
|
+
if (ts.isIdentifier(expression) && expression.text === 'useTranslation') {
|
|
79
|
+
this.extractUseTranslationHook(node, filePath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
ts.forEachChild(node, (child) => this.visitNode(child, filePath));
|
|
83
|
+
}
|
|
84
|
+
extractTFunctionCall(node, filePath) {
|
|
85
|
+
try {
|
|
86
|
+
const args = node.arguments;
|
|
87
|
+
if (args.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
const keyArg = args[0];
|
|
90
|
+
let key = '';
|
|
91
|
+
if (ts.isStringLiteral(keyArg)) {
|
|
92
|
+
key = keyArg.text;
|
|
93
|
+
}
|
|
94
|
+
else if (ts.isTemplateLiteral(keyArg)) {
|
|
95
|
+
const text = keyArg.getText();
|
|
96
|
+
const templateParts = text.match(/`([^`]*)`/);
|
|
97
|
+
if (templateParts && templateParts[1]) {
|
|
98
|
+
key = templateParts[1].split('${')[0].trim();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (ts.isNoSubstitutionTemplateLiteral(keyArg)) {
|
|
102
|
+
key = keyArg.text;
|
|
103
|
+
}
|
|
104
|
+
if (!key)
|
|
105
|
+
return;
|
|
106
|
+
const translationKey = { key };
|
|
107
|
+
// Extract module name from options (third argument)
|
|
108
|
+
if (args.length >= 3 && ts.isObjectLiteralExpression(args[2])) {
|
|
109
|
+
args[2].properties.forEach(property => {
|
|
110
|
+
if (ts.isPropertyAssignment(property)) {
|
|
111
|
+
const propertyName = property.name.getText();
|
|
112
|
+
if (propertyName === 'moduleName' && ts.isStringLiteral(property.initializer)) {
|
|
113
|
+
translationKey.module = property.initializer.text;
|
|
114
|
+
}
|
|
115
|
+
else if (propertyName === 'defaultValue' && ts.isStringLiteral(property.initializer)) {
|
|
116
|
+
translationKey.defaultValue = property.initializer.text;
|
|
117
|
+
}
|
|
118
|
+
else if (propertyName === 'context' && ts.isStringLiteral(property.initializer)) {
|
|
119
|
+
translationKey.context = property.initializer.text;
|
|
120
|
+
}
|
|
121
|
+
else if (propertyName === 'count') {
|
|
122
|
+
translationKey.count = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Try to infer module from file path
|
|
128
|
+
if (!translationKey.module) {
|
|
129
|
+
const moduleMatch = filePath.match(/modules[\\\/]([^\\\/]+)/);
|
|
130
|
+
if (moduleMatch) {
|
|
131
|
+
translationKey.module = moduleMatch[1];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Add to keys if not already present
|
|
135
|
+
const existingKey = this.keys.find(k => k.key === translationKey.key &&
|
|
136
|
+
k.module === translationKey.module);
|
|
137
|
+
if (!existingKey) {
|
|
138
|
+
this.keys.push(translationKey);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error(`Error extracting t() call in ${filePath}:`, error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
extractUseTranslationHook(node, filePath) {
|
|
146
|
+
try {
|
|
147
|
+
const args = node.arguments;
|
|
148
|
+
if (args.length > 0 && ts.isStringLiteral(args[0])) {
|
|
149
|
+
const moduleName = args[0].text;
|
|
150
|
+
// Add to imported modules
|
|
151
|
+
this.importedModules.add(moduleName);
|
|
152
|
+
// Also add to modules set
|
|
153
|
+
this.modules.add(moduleName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`Error extracting useTranslation hook in ${filePath}:`, error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async discoverModules() {
|
|
161
|
+
try {
|
|
162
|
+
// Check if modules directory exists
|
|
163
|
+
if (!fs.existsSync(this.config.modulesDir)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// List all modules
|
|
167
|
+
const moduleDirs = fs.readdirSync(this.config.modulesDir, { withFileTypes: true })
|
|
168
|
+
.filter(dirent => dirent.isDirectory())
|
|
169
|
+
.map(dirent => dirent.name);
|
|
170
|
+
// Check each module for locales directory
|
|
171
|
+
for (const moduleName of moduleDirs) {
|
|
172
|
+
const localesPath = path.join(this.config.modulesDir, moduleName, 'locales');
|
|
173
|
+
if (fs.existsSync(localesPath)) {
|
|
174
|
+
// Check if any locale file exists
|
|
175
|
+
const localeFiles = fs.readdirSync(localesPath)
|
|
176
|
+
.filter(file => file.endsWith('.json'))
|
|
177
|
+
.map(file => file.replace('.json', ''));
|
|
178
|
+
// Only add module if it has locale files
|
|
179
|
+
if (localeFiles.length > 0) {
|
|
180
|
+
this.modules.add(moduleName);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('Error discovering modules:', error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async generateAutoIntlFile() {
|
|
190
|
+
const outputDir = path.dirname(this.config.outputFile);
|
|
191
|
+
// Create output directory if it doesn't exist
|
|
192
|
+
if (!fs.existsSync(outputDir)) {
|
|
193
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
const content = this.generateFileContent();
|
|
196
|
+
fs.writeFileSync(this.config.outputFile, content, 'utf-8');
|
|
197
|
+
}
|
|
198
|
+
generateFileContent() {
|
|
199
|
+
const { supportedLocales } = this.config;
|
|
200
|
+
// Sort modules alphabetically
|
|
201
|
+
const sortedModules = Array.from(this.modules).sort();
|
|
202
|
+
// Check if base locales directory exists
|
|
203
|
+
const baseLocalesExist = fs.existsSync(this.config.localesDir);
|
|
204
|
+
// Generate base locales imports
|
|
205
|
+
const baseImports = supportedLocales.map(locale => {
|
|
206
|
+
const importPath = `./locales/${locale}.json`;
|
|
207
|
+
if (baseLocalesExist) {
|
|
208
|
+
return ` '${locale}': (() => import('${importPath}').then(module => module.default || module))`;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
return ` '${locale}': (() => Promise.resolve({}))`;
|
|
212
|
+
}
|
|
213
|
+
}).join(',\n');
|
|
214
|
+
// Generate modules imports
|
|
215
|
+
const modulesImports = sortedModules.map(moduleName => {
|
|
216
|
+
const localesPath = path.join(this.config.modulesDir, moduleName, 'locales');
|
|
217
|
+
const moduleLocalesExist = fs.existsSync(localesPath);
|
|
218
|
+
const moduleLocales = supportedLocales.map(locale => {
|
|
219
|
+
const importPath = `./modules/${moduleName}/locales/${locale}.json`;
|
|
220
|
+
if (moduleLocalesExist) {
|
|
221
|
+
return ` '${locale}': (() => import('${importPath}').then(module => module.default || module))`;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
return ` '${locale}': (() => Promise.resolve({}))`;
|
|
225
|
+
}
|
|
226
|
+
}).join(',\n');
|
|
227
|
+
return ` '${moduleName}': {\n${moduleLocales}\n }`;
|
|
228
|
+
}).join(',\n');
|
|
229
|
+
// Generate the TypeScript content
|
|
230
|
+
return `export const translations = {
|
|
231
|
+
'base': {
|
|
232
|
+
${baseImports}
|
|
233
|
+
},
|
|
234
|
+
'modules': {
|
|
235
|
+
${modulesImports.length > 0 ? modulesImports : ' // No modules with translations found'}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default translations;`;
|
|
240
|
+
}
|
|
241
|
+
generateTypeDefinitions() {
|
|
242
|
+
if (this.keys.length === 0) {
|
|
243
|
+
return 'string';
|
|
244
|
+
}
|
|
245
|
+
// Group keys by module
|
|
246
|
+
const keysByModule = {};
|
|
247
|
+
this.keys.forEach(key => {
|
|
248
|
+
const module = key.module || 'core';
|
|
249
|
+
if (!keysByModule[module]) {
|
|
250
|
+
keysByModule[module] = [];
|
|
251
|
+
}
|
|
252
|
+
keysByModule[module].push(key.key);
|
|
253
|
+
});
|
|
254
|
+
// Generate union types for each module
|
|
255
|
+
const moduleTypes = Object.entries(keysByModule).map(([module, keys]) => {
|
|
256
|
+
const keyUnion = keys
|
|
257
|
+
.filter((value, index, self) => self.indexOf(value) === index) // Remove duplicates
|
|
258
|
+
.map(k => `"${k}"`)
|
|
259
|
+
.join(' | ');
|
|
260
|
+
return module === 'core' ? keyUnion : `${module}:${keyUnion}`;
|
|
261
|
+
});
|
|
262
|
+
return moduleTypes.join(' | ');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function TranslationInitiator(dirname = __dirname) {
|
|
266
|
+
const config = {};
|
|
267
|
+
const generator = new TranslationGenerator(config, dirname);
|
|
268
|
+
generator.generate().then(() => {
|
|
269
|
+
console.log('🎉 Translation generation completed successfully!');
|
|
270
|
+
}).catch((err) => {
|
|
271
|
+
console.error('❌ Failed to generate translations: ', err);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
export default TranslationInitiator;
|
package/js/provider.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { Pajo } from '@jsarc/pajo';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
export class ProviderGenerator {
|
|
8
|
+
config;
|
|
9
|
+
providers = [];
|
|
10
|
+
modules = new Set();
|
|
11
|
+
constructor(config = {}, dirname = __dirname) {
|
|
12
|
+
this.config = {
|
|
13
|
+
srcDir: config.srcDir || Pajo.join(dirname, 'src') || '',
|
|
14
|
+
modulesDir: config.modulesDir || Pajo.join(dirname, 'src\\modules') || '',
|
|
15
|
+
providersDir: config.providersDir || Pajo.join(dirname, 'src\\providers') || '',
|
|
16
|
+
outputFile: config.outputFile || Pajo.join(dirname, 'src\\auto-provider.tsx') || '',
|
|
17
|
+
globalProvidersDir: config.globalProvidersDir || Pajo.join(dirname, 'src\\providers') || ''
|
|
18
|
+
};
|
|
19
|
+
console.log(`--> ProviderGenerator config:: `, this.config);
|
|
20
|
+
}
|
|
21
|
+
async generate() {
|
|
22
|
+
console.log('🔍 Scanning for provider files...');
|
|
23
|
+
await this.findProviderFiles();
|
|
24
|
+
await this.generateAutoProviderFile();
|
|
25
|
+
console.log(`✅ Generated ${this.config.outputFile} with ${this.providers.length} providers`);
|
|
26
|
+
console.log(`📦 Found modules with providers: ${Array.from(this.modules).join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
async findProviderFiles() {
|
|
29
|
+
this.providers = [];
|
|
30
|
+
await this.scanDirectoryForProviders(this.config.globalProvidersDir, undefined, true);
|
|
31
|
+
if (fs.existsSync(this.config.modulesDir)) {
|
|
32
|
+
const moduleDirs = fs.readdirSync(this.config.modulesDir, { withFileTypes: true })
|
|
33
|
+
.filter(dirent => dirent.isDirectory())
|
|
34
|
+
.map(dirent => dirent.name);
|
|
35
|
+
for (const moduleName of moduleDirs) {
|
|
36
|
+
const moduleProvidersDir = path.join(this.config.modulesDir, moduleName, 'providers');
|
|
37
|
+
if (fs.existsSync(moduleProvidersDir)) {
|
|
38
|
+
this.modules.add(moduleName);
|
|
39
|
+
await this.scanDirectoryForProviders(moduleProvidersDir, moduleName, false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async scanDirectoryForProviders(dir, moduleName, isGlobal = false) {
|
|
45
|
+
try {
|
|
46
|
+
if (!fs.existsSync(dir)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
const fullPath = path.join(dir, item.name);
|
|
52
|
+
if (item.name === 'node_modules' ||
|
|
53
|
+
item.name === 'dist' ||
|
|
54
|
+
item.name === 'build' ||
|
|
55
|
+
item.name.startsWith('.') ||
|
|
56
|
+
item.name === 'index.ts' ||
|
|
57
|
+
item.name === 'index.tsx' ||
|
|
58
|
+
item.name === 'auto-provider.tsx') {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (item.isDirectory()) {
|
|
62
|
+
await this.scanDirectoryForProviders(fullPath, moduleName, isGlobal);
|
|
63
|
+
}
|
|
64
|
+
else if (item.isFile()) {
|
|
65
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
66
|
+
if (['.tsx', '.jsx', '.ts', '.js'].includes(ext)) {
|
|
67
|
+
const fileName = path.basename(item.name, ext);
|
|
68
|
+
if (fileName.includes('Provider') || this.isLikelyProviderFile(fullPath)) {
|
|
69
|
+
await this.processProviderFile(fullPath, moduleName, isGlobal);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(`Error scanning directory ${dir}:`, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async processProviderFile(filePath, moduleName, isGlobal = false) {
|
|
80
|
+
try {
|
|
81
|
+
const componentName = this.extractComponentName(filePath, moduleName, isGlobal);
|
|
82
|
+
const providerType = this.determineProviderType(filePath, componentName);
|
|
83
|
+
const priority = this.determineProviderPriority(providerType, componentName, isGlobal);
|
|
84
|
+
const providerInfo = {
|
|
85
|
+
filePath,
|
|
86
|
+
componentName,
|
|
87
|
+
moduleName,
|
|
88
|
+
priority,
|
|
89
|
+
isGlobal,
|
|
90
|
+
providerType
|
|
91
|
+
};
|
|
92
|
+
this.providers.push(providerInfo);
|
|
93
|
+
console.log(`📦 Found provider: ${componentName} (${providerType}, priority: ${priority})`);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error(`Error processing provider file ${filePath}:`, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
extractComponentName(filePath, moduleName, isGlobal) {
|
|
100
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
101
|
+
let componentName = fileName
|
|
102
|
+
.replace(/\.provider$/i, '')
|
|
103
|
+
.replace(/Provider$/i, '')
|
|
104
|
+
.replace(/\.context$/i, '');
|
|
105
|
+
if (!componentName.match(/^[A-Z]/)) {
|
|
106
|
+
componentName = this.toPascalCase(componentName);
|
|
107
|
+
}
|
|
108
|
+
if (!componentName.endsWith('Provider')) {
|
|
109
|
+
componentName += 'Provider';
|
|
110
|
+
}
|
|
111
|
+
if (moduleName && isGlobal === false) {
|
|
112
|
+
const modulePrefix = this.toPascalCase(moduleName);
|
|
113
|
+
if (!componentName.startsWith(modulePrefix)) {
|
|
114
|
+
componentName = modulePrefix + componentName;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return componentName;
|
|
118
|
+
}
|
|
119
|
+
toPascalCase(str) {
|
|
120
|
+
if (!str)
|
|
121
|
+
return '';
|
|
122
|
+
return str
|
|
123
|
+
.split(/[-_]/)
|
|
124
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
125
|
+
.join('');
|
|
126
|
+
}
|
|
127
|
+
isLikelyProviderFile(filePath) {
|
|
128
|
+
try {
|
|
129
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
130
|
+
const providerPatterns = [
|
|
131
|
+
/createContext/i,
|
|
132
|
+
/Context\.Provider/i,
|
|
133
|
+
/Provider.*children/i,
|
|
134
|
+
/<Provider/i,
|
|
135
|
+
/export.*Provider/i
|
|
136
|
+
];
|
|
137
|
+
return providerPatterns.some(pattern => pattern.test(content));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
determineProviderType(filePath, componentName) {
|
|
144
|
+
try {
|
|
145
|
+
const content = fs.readFileSync(filePath, 'utf-8').toLowerCase();
|
|
146
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
147
|
+
if (content.includes('redux') || content.includes('store') || fileName.includes('redux') || fileName.includes('store')) {
|
|
148
|
+
return 'redux';
|
|
149
|
+
}
|
|
150
|
+
if (content.includes('react-router') || content.includes('browserrouter') || fileName.includes('router')) {
|
|
151
|
+
return 'router';
|
|
152
|
+
}
|
|
153
|
+
if (content.includes('theme') || content.includes('themeprovider') || fileName.includes('theme')) {
|
|
154
|
+
return 'theme';
|
|
155
|
+
}
|
|
156
|
+
if (content.includes('createcontext') || content.includes('context.provider') || fileName.includes('context')) {
|
|
157
|
+
return 'context';
|
|
158
|
+
}
|
|
159
|
+
return 'other';
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return 'other';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
determineProviderPriority(type, componentName, isGlobal) {
|
|
166
|
+
const basePriority = isGlobal ? 100 : 200;
|
|
167
|
+
const typePriority = {
|
|
168
|
+
'router': 10,
|
|
169
|
+
'redux': 20,
|
|
170
|
+
'context': 30,
|
|
171
|
+
'theme': 40,
|
|
172
|
+
'other': 50
|
|
173
|
+
};
|
|
174
|
+
if (componentName.includes('Router') || componentName.includes('BrowserRouter')) {
|
|
175
|
+
return basePriority + 5;
|
|
176
|
+
}
|
|
177
|
+
if (componentName.includes('Error') || componentName.includes('Boundary')) {
|
|
178
|
+
return basePriority + 100;
|
|
179
|
+
}
|
|
180
|
+
if (componentName.includes('Toast') || componentName.includes('Notification')) {
|
|
181
|
+
return basePriority + 90;
|
|
182
|
+
}
|
|
183
|
+
return basePriority + (typePriority[type] || 50);
|
|
184
|
+
}
|
|
185
|
+
async generateAutoProviderFile() {
|
|
186
|
+
const outputDir = path.dirname(this.config.outputFile);
|
|
187
|
+
if (!fs.existsSync(outputDir)) {
|
|
188
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
const content = this.generateFileContent();
|
|
191
|
+
fs.writeFileSync(this.config.outputFile, content, 'utf-8');
|
|
192
|
+
}
|
|
193
|
+
getRelativeImportPath(targetPath) {
|
|
194
|
+
const outputDir = path.dirname(this.config.outputFile);
|
|
195
|
+
const relative = path.relative(outputDir, targetPath);
|
|
196
|
+
let importPath = relative.replace(/\\/g, '/');
|
|
197
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
|
|
198
|
+
importPath = './' + importPath;
|
|
199
|
+
}
|
|
200
|
+
importPath = importPath.replace(/\.(tsx|jsx|ts|js)$/, '');
|
|
201
|
+
return importPath;
|
|
202
|
+
}
|
|
203
|
+
generateFileContent() {
|
|
204
|
+
const sortedProviders = [...this.providers].sort((a, b) => a.priority - b.priority);
|
|
205
|
+
const providersByModule = {};
|
|
206
|
+
const globalProviders = [];
|
|
207
|
+
sortedProviders.forEach(provider => {
|
|
208
|
+
if (provider.isGlobal || !provider.moduleName) {
|
|
209
|
+
globalProviders.push(provider);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
if (!providersByModule[provider.moduleName]) {
|
|
213
|
+
providersByModule[provider.moduleName] = [];
|
|
214
|
+
}
|
|
215
|
+
providersByModule[provider.moduleName].push(provider);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const imports = [
|
|
219
|
+
`import React, { ReactNode, lazy } from 'react';`,
|
|
220
|
+
`import { Suspense } from 'react';`,
|
|
221
|
+
``,
|
|
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
|
+
``
|
|
234
|
+
];
|
|
235
|
+
const allProviders = [...globalProviders, ...Object.values(providersByModule).flat()];
|
|
236
|
+
allProviders.forEach(provider => {
|
|
237
|
+
const importPath = this.getRelativeImportPath(provider.filePath);
|
|
238
|
+
const actualExportName = this.tryGetExportName(provider.filePath, provider.componentName);
|
|
239
|
+
imports.push(`const ${provider.componentName} = lazy(() => import('${importPath}').then(module => ({ default: module.${actualExportName} || module.default || module })));`);
|
|
240
|
+
});
|
|
241
|
+
imports.push(``);
|
|
242
|
+
const moduleComponents = [];
|
|
243
|
+
Object.entries(providersByModule).forEach(([moduleName, moduleProviders]) => {
|
|
244
|
+
const pascalModuleName = this.toPascalCase(moduleName);
|
|
245
|
+
const componentName = `${pascalModuleName}Providers`;
|
|
246
|
+
const sortedModuleProviders = moduleProviders.sort((a, b) => a.priority - b.priority);
|
|
247
|
+
let nestedJSX = '{children}';
|
|
248
|
+
sortedModuleProviders.forEach(provider => {
|
|
249
|
+
nestedJSX = `<${provider.componentName}>\n ${nestedJSX}\n </${provider.componentName}>`;
|
|
250
|
+
});
|
|
251
|
+
moduleComponents.push(`const ${componentName} = ({ children }: { children: ReactNode }) => (
|
|
252
|
+
${nestedJSX}
|
|
253
|
+
);`);
|
|
254
|
+
moduleComponents.push(``);
|
|
255
|
+
});
|
|
256
|
+
const sortedGlobalProviders = globalProviders.sort((a, b) => a.priority - b.priority);
|
|
257
|
+
let globalNestedJSX = '{children}';
|
|
258
|
+
sortedGlobalProviders.forEach(provider => {
|
|
259
|
+
globalNestedJSX = `<${provider.componentName}>\n ${globalNestedJSX}\n </${provider.componentName}>`;
|
|
260
|
+
});
|
|
261
|
+
moduleComponents.push(`const GlobalProviders = ({ children }: { children: ReactNode }) => (
|
|
262
|
+
${globalNestedJSX}
|
|
263
|
+
);`);
|
|
264
|
+
moduleComponents.push(``);
|
|
265
|
+
const allModuleNames = Object.keys(providersByModule);
|
|
266
|
+
let mainNestedJSX = '{children}';
|
|
267
|
+
allModuleNames.sort().forEach(moduleName => {
|
|
268
|
+
const pascalModuleName = this.toPascalCase(moduleName);
|
|
269
|
+
const componentName = `${pascalModuleName}Providers`;
|
|
270
|
+
mainNestedJSX = `<${componentName}>\n ${mainNestedJSX}\n </${componentName}>`;
|
|
271
|
+
});
|
|
272
|
+
mainNestedJSX = `<GlobalProviders>\n ${mainNestedJSX}\n</GlobalProviders>`;
|
|
273
|
+
moduleComponents.push(`export const AppProvider = ({ children }: { children: ReactNode }) => (
|
|
274
|
+
<Suspense fallback={<LoadingFallback>{children}</LoadingFallback>}>
|
|
275
|
+
${mainNestedJSX}
|
|
276
|
+
</Suspense>
|
|
277
|
+
);`);
|
|
278
|
+
moduleComponents.push(``);
|
|
279
|
+
moduleComponents.push(`export default AppProvider;`);
|
|
280
|
+
return [
|
|
281
|
+
...imports,
|
|
282
|
+
...moduleComponents
|
|
283
|
+
].join('\n');
|
|
284
|
+
}
|
|
285
|
+
tryGetExportName(filePath, defaultName) {
|
|
286
|
+
try {
|
|
287
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
288
|
+
const exportRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
|
|
289
|
+
const exports = [];
|
|
290
|
+
let match;
|
|
291
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
292
|
+
exports.push(match[1]);
|
|
293
|
+
}
|
|
294
|
+
const defaultExportRegex = /export\s+default\s+(\w+)/;
|
|
295
|
+
const defaultMatch = defaultExportRegex.exec(content);
|
|
296
|
+
if (exports.includes(defaultName)) {
|
|
297
|
+
return defaultName;
|
|
298
|
+
}
|
|
299
|
+
else if (defaultMatch) {
|
|
300
|
+
return defaultMatch[1];
|
|
301
|
+
}
|
|
302
|
+
else if (exports.length > 0) {
|
|
303
|
+
return exports[0];
|
|
304
|
+
}
|
|
305
|
+
return 'default';
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return 'default';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function ProviderInitiator(dirname = __dirname) {
|
|
313
|
+
const config = {};
|
|
314
|
+
const generator = new ProviderGenerator(config, dirname);
|
|
315
|
+
try {
|
|
316
|
+
await generator.generate();
|
|
317
|
+
console.log('🎉 Provider generation completed successfully!');
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
console.error('❌ Failed to generate providers: ', err);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export default ProviderInitiator;
|