@arcpkg/initiator 0.0.1-beta.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/provider.js ADDED
@@ -0,0 +1,303 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { Pajo } from '@arcpkg/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, { lazy } from 'react';`,
220
+ ``,
221
+ ``
222
+ ];
223
+ const allProviders = [...globalProviders, ...Object.values(providersByModule).flat()];
224
+ allProviders.forEach(provider => {
225
+ const importPath = this.getRelativeImportPath(provider.filePath);
226
+ const actualExportName = this.tryGetExportName(provider.filePath, provider.componentName);
227
+ imports.push(`const ${provider.componentName} = lazy(() => import('${importPath}').then(module => ({ default: module.${actualExportName} || module.default || module })));`);
228
+ });
229
+ imports.push(``);
230
+ const moduleComponents = [];
231
+ Object.entries(providersByModule).forEach(([moduleName, moduleProviders]) => {
232
+ const pascalModuleName = this.toPascalCase(moduleName);
233
+ const componentName = `${pascalModuleName}Providers`;
234
+ const sortedModuleProviders = moduleProviders.sort((a, b) => a.priority - b.priority);
235
+ let nestedJSX = '{children}';
236
+ sortedModuleProviders.forEach(provider => {
237
+ nestedJSX = `<${provider.componentName}>\n ${nestedJSX}\n </${provider.componentName}>`;
238
+ });
239
+ moduleComponents.push(`const ${componentName} = ({ children }: { children: any }) => (<>${nestedJSX}</>);`);
240
+ moduleComponents.push(``);
241
+ });
242
+ const sortedGlobalProviders = globalProviders.sort((a, b) => a.priority - b.priority);
243
+ let globalNestedJSX = '{children}';
244
+ sortedGlobalProviders.forEach(provider => {
245
+ globalNestedJSX = `<${provider.componentName}>\n ${globalNestedJSX}\n </${provider.componentName}>`;
246
+ });
247
+ moduleComponents.push(`const GlobalProviders = ({ children }: { children: any }) => (<>${globalNestedJSX}</>);`);
248
+ moduleComponents.push(``);
249
+ const allModuleNames = Object.keys(providersByModule);
250
+ let mainNestedJSX = '{children}';
251
+ allModuleNames.sort().forEach(moduleName => {
252
+ const pascalModuleName = this.toPascalCase(moduleName);
253
+ const componentName = `${pascalModuleName}Providers`;
254
+ mainNestedJSX = `<${componentName}>\n ${mainNestedJSX}\n </${componentName}>`;
255
+ });
256
+ mainNestedJSX = `<GlobalProviders>\n ${mainNestedJSX}\n</GlobalProviders>`;
257
+ moduleComponents.push(`export const AppProvider = ({ children }: { children: any }) => (<>${mainNestedJSX}</>);`);
258
+ moduleComponents.push(``);
259
+ moduleComponents.push(`export default AppProvider;`);
260
+ return [
261
+ ...imports,
262
+ ...moduleComponents
263
+ ].join('\n');
264
+ }
265
+ tryGetExportName(filePath, defaultName) {
266
+ try {
267
+ const content = fs.readFileSync(filePath, 'utf-8');
268
+ const exportRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
269
+ const exports = [];
270
+ let match;
271
+ while ((match = exportRegex.exec(content)) !== null) {
272
+ exports.push(match[1]);
273
+ }
274
+ const defaultExportRegex = /export\s+default\s+(\w+)/;
275
+ const defaultMatch = defaultExportRegex.exec(content);
276
+ if (exports.includes(defaultName)) {
277
+ return defaultName;
278
+ }
279
+ else if (defaultMatch) {
280
+ return defaultMatch[1];
281
+ }
282
+ else if (exports.length > 0) {
283
+ return exports[0];
284
+ }
285
+ return 'default';
286
+ }
287
+ catch (error) {
288
+ return 'default';
289
+ }
290
+ }
291
+ }
292
+ async function ProviderInitiator(dirname = __dirname) {
293
+ const config = {};
294
+ const generator = new ProviderGenerator(config, dirname);
295
+ try {
296
+ await generator.generate();
297
+ console.log('🎉 Provider generation completed successfully!');
298
+ }
299
+ catch (err) {
300
+ console.error('❌ Failed to generate providers: ', err);
301
+ }
302
+ }
303
+ export default ProviderInitiator;