@llmnative/react 0.1.1 → 1.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.
Files changed (181) hide show
  1. package/README.md +387 -364
  2. package/bin/cli.js +117 -98
  3. package/dist/index.css +1 -1
  4. package/dist/index.js +21 -823
  5. package/dist/index.mjs +14246 -9296
  6. package/dist/style.css +1 -0
  7. package/dist/types/src/App.d.ts +72 -14
  8. package/dist/types/src/Config.d.ts +28 -5
  9. package/dist/types/src/Global.d.ts +2 -2
  10. package/dist/types/src/I18n.d.ts +185 -0
  11. package/dist/types/src/Theme.d.ts +153 -81
  12. package/dist/types/src/auth.d.ts +13 -1
  13. package/dist/types/src/components/Component.d.ts +13 -27
  14. package/dist/types/src/components/ErrorBoundary.d.ts +30 -0
  15. package/dist/types/src/components/blocks/Brand.d.ts +4 -4
  16. package/dist/types/src/components/blocks/Breadcrumbs.d.ts +50 -4
  17. package/dist/types/src/components/blocks/Carousel.d.ts +5 -5
  18. package/dist/types/src/components/blocks/Dropdown.d.ts +26 -20
  19. package/dist/types/src/components/blocks/ListCard.d.ts +31 -0
  20. package/dist/types/src/components/blocks/Menu.d.ts +13 -16
  21. package/dist/types/src/components/blocks/Notifications.d.ts +8 -3
  22. package/dist/types/src/components/blocks/ProviderSwitcher.d.ts +63 -0
  23. package/dist/types/src/components/blocks/Search.d.ts +2 -2
  24. package/dist/types/src/components/blocks/SideNav.d.ts +45 -0
  25. package/dist/types/src/components/blocks/ThemeSwitcher.d.ts +24 -0
  26. package/dist/types/src/components/blocks/Toolbar.d.ts +19 -0
  27. package/dist/types/src/components/index.d.ts +27 -6
  28. package/dist/types/src/components/types.d.ts +10 -3
  29. package/dist/types/src/components/ui/Alert.d.ts +16 -4
  30. package/dist/types/src/components/ui/Badge.d.ts +6 -5
  31. package/dist/types/src/components/ui/Buttons.d.ts +13 -12
  32. package/dist/types/src/components/ui/Card.d.ts +8 -7
  33. package/dist/types/src/components/ui/Code.d.ts +1 -1
  34. package/dist/types/src/components/ui/Gallery.d.ts +13 -14
  35. package/dist/types/src/components/ui/GridSystem.d.ts +1 -1
  36. package/dist/types/src/components/ui/Icon.d.ts +3 -1
  37. package/dist/types/src/components/ui/Image.d.ts +1 -1
  38. package/dist/types/src/components/ui/ImageAvatar.d.ts +1 -1
  39. package/dist/types/src/components/ui/Loader.d.ts +1 -1
  40. package/dist/types/src/components/ui/LocaleSwitcher.d.ts +14 -0
  41. package/dist/types/src/components/ui/Modal.d.ts +42 -15
  42. package/dist/types/src/components/ui/Pagination.d.ts +14 -3
  43. package/dist/types/src/components/ui/Percentage.d.ts +5 -5
  44. package/dist/types/src/components/ui/Repeat.d.ts +15 -6
  45. package/dist/types/src/components/ui/Tab.d.ts +29 -2
  46. package/dist/types/src/components/ui/TabDynamic.d.ts +4 -3
  47. package/dist/types/src/components/ui/Table.d.ts +15 -14
  48. package/dist/types/src/components/ui/fields/CodeEditor.d.ts +43 -0
  49. package/dist/types/src/components/ui/fields/ContextMenu.d.ts +38 -0
  50. package/dist/types/src/components/ui/fields/Crop.d.ts +4 -2
  51. package/dist/types/src/components/ui/fields/ImageField.d.ts +31 -0
  52. package/dist/types/src/components/ui/fields/Input.d.ts +43 -14
  53. package/dist/types/src/components/ui/fields/Prompt.d.ts +2 -31
  54. package/dist/types/src/components/ui/fields/RichText.d.ts +86 -0
  55. package/dist/types/src/components/ui/fields/Select.d.ts +13 -10
  56. package/dist/types/src/components/ui/fields/Upload.d.ts +37 -2
  57. package/dist/types/src/components/ui/fields/UploadCSV.d.ts +2 -0
  58. package/dist/types/src/components/ui/useRecordSelection.d.ts +1 -2
  59. package/dist/types/src/components/widgets/Form.d.ts +80 -43
  60. package/dist/types/src/components/widgets/ImageEditor.d.ts +4 -5
  61. package/dist/types/src/components/widgets/MarkdownReader.d.ts +4 -4
  62. package/dist/types/src/components/widgets/Prompt.d.ts +71 -23
  63. package/dist/types/src/components/widgets/TabDynamic.d.ts +4 -3
  64. package/dist/types/src/components/widgets/form-controller.d.ts +42 -0
  65. package/dist/types/src/components/widgets/grid-core/GridCore.d.ts +1 -1
  66. package/dist/types/src/components/widgets/grid-core/GridDB.d.ts +1 -1
  67. package/dist/types/src/components/widgets/grid-core/GridGalleryView.d.ts +1 -1
  68. package/dist/types/src/components/widgets/grid-core/GridTableView.d.ts +1 -1
  69. package/dist/types/src/components/widgets/grid-core/types.d.ts +77 -27
  70. package/dist/types/src/components/widgets/grid-core/useGridActions.d.ts +4 -8
  71. package/dist/types/src/components/widgets/grid-core/useGridColumns.d.ts +2 -2
  72. package/dist/types/src/components/widgets/grid-core/useGridDBRecords.d.ts +1 -2
  73. package/dist/types/src/components/widgets/grid-core/useGridPreparedRecords.d.ts +2 -2
  74. package/dist/types/src/components/widgets/grid-core/useGridSelection.d.ts +4 -5
  75. package/dist/types/src/components/widgets/grid-core/utils.d.ts +11 -3
  76. package/dist/types/src/conf/Prompt.d.ts +1 -1
  77. package/dist/types/src/conf/i18n/ar.d.ts +2 -0
  78. package/dist/types/src/conf/i18n/de.d.ts +2 -0
  79. package/dist/types/src/conf/i18n/en.d.ts +2 -0
  80. package/dist/types/src/conf/i18n/index.d.ts +6 -0
  81. package/dist/types/src/conf/i18n/it.d.ts +2 -0
  82. package/dist/types/src/conf/i18n/ru.d.ts +2 -0
  83. package/dist/types/src/conf/i18n/zh.d.ts +2 -0
  84. package/dist/types/src/constant.d.ts +1 -1
  85. package/dist/types/src/index.d.ts +38 -13
  86. package/dist/types/src/libs/converter.d.ts +4 -4
  87. package/dist/types/src/libs/editorHeight.d.ts +12 -0
  88. package/dist/types/src/libs/fetch.d.ts +4 -4
  89. package/dist/types/src/libs/imageBuilder.d.ts +63 -14
  90. package/dist/types/src/libs/imageVariants.d.ts +35 -0
  91. package/dist/types/src/libs/index.d.ts +1 -4
  92. package/dist/types/src/libs/promptUtils.d.ts +24 -0
  93. package/dist/types/src/libs/sanitizer.d.ts +2 -2
  94. package/dist/types/src/libs/utils.d.ts +9 -10
  95. package/dist/types/src/pages/index.d.ts +0 -2
  96. package/dist/types/src/providers/ProviderConfiguration.d.ts +6 -0
  97. package/dist/types/src/providers/ProviderRegistryContext.d.ts +51 -0
  98. package/dist/types/src/providers/ProviderSession.d.ts +68 -0
  99. package/dist/types/src/providers/ai/AIProvider.d.ts +57 -0
  100. package/dist/types/src/providers/ai/AIProviderContext.d.ts +13 -0
  101. package/dist/types/src/providers/ai/anthropic.d.ts +2 -0
  102. package/dist/types/src/providers/ai/deepseek.d.ts +1 -0
  103. package/dist/types/src/providers/ai/gemini.d.ts +2 -0
  104. package/dist/types/src/providers/ai/glm.d.ts +1 -0
  105. package/dist/types/src/providers/ai/index.d.ts +9 -134
  106. package/dist/types/src/providers/ai/mistral.d.ts +1 -0
  107. package/dist/types/src/providers/ai/openai.d.ts +1 -0
  108. package/dist/types/src/providers/ai/openaiCompatible.d.ts +17 -0
  109. package/dist/types/src/providers/ai/opencode.d.ts +2 -0
  110. package/dist/types/src/providers/ai/openrouter.d.ts +1 -0
  111. package/dist/types/src/providers/ai/shared.d.ts +46 -0
  112. package/dist/types/src/providers/api/ApiProvider.d.ts +43 -0
  113. package/dist/types/src/providers/api/direct.d.ts +14 -0
  114. package/dist/types/src/providers/api/firebase.d.ts +14 -0
  115. package/dist/types/src/providers/api/mock.d.ts +7 -0
  116. package/dist/types/src/providers/api/supabase.d.ts +14 -0
  117. package/dist/types/src/providers/auth/AuthProvider.d.ts +2 -2
  118. package/dist/types/src/providers/auth/firebase/FirebaseAuthProvider.d.ts +26 -0
  119. package/dist/types/src/providers/auth/google/GoogleAuth.d.ts +2 -2
  120. package/dist/types/src/providers/auth/supabase/SupabaseAuthProvider.d.ts +48 -0
  121. package/dist/types/src/providers/credentials/CredentialsProvider.d.ts +3 -0
  122. package/dist/types/src/providers/credentials/CredentialsProviderContext.d.ts +12 -0
  123. package/dist/types/src/providers/credentials/google/GoogleServiceAccountProvider.d.ts +8 -0
  124. package/dist/types/src/providers/data/DataProvider.d.ts +27 -1
  125. package/dist/types/src/providers/data/firebase.d.ts +5 -4
  126. package/dist/types/src/providers/data/firestore.d.ts +38 -0
  127. package/dist/types/src/providers/data/mock.d.ts +8 -1
  128. package/dist/types/src/providers/data/supabase.d.ts +36 -10
  129. package/dist/types/src/providers/firebase-init.d.ts +15 -4
  130. package/dist/types/src/providers/icon/PhosphorIconProvider.d.ts +1 -1
  131. package/dist/types/src/providers/manifest.d.ts +20 -14
  132. package/dist/types/src/providers/proxy/index.d.ts +7 -0
  133. package/dist/types/src/providers/proxy/vite.d.ts +3 -0
  134. package/dist/types/src/providers/scrape/index.d.ts +2 -1
  135. package/dist/types/src/providers/seo/google/keyword.d.ts +6 -1
  136. package/dist/types/src/providers/storage/StorageProvider.d.ts +155 -4
  137. package/dist/types/src/providers/storage/dropbox.d.ts +3 -3
  138. package/dist/types/src/providers/storage/firebase.d.ts +38 -7
  139. package/dist/types/src/providers/storage/supabase.d.ts +47 -7
  140. package/dist/types/src/providers/supabase-init.d.ts +35 -0
  141. package/dist/types/src/types/FormFields.d.ts +4 -2
  142. package/dist/types/src/types/FormSchema.d.ts +29 -0
  143. package/dist/types/themes/tokyo-night-light.d.ts +3 -0
  144. package/dist/types/themes/tokyo-night.d.ts +3 -0
  145. package/dist/types/themes/vscode-dark-plus.d.ts +3 -0
  146. package/dist/types/themes/vscode-light-plus.d.ts +3 -0
  147. package/dist/vite.js +1 -0
  148. package/dist/vite.mjs +54 -0
  149. package/package.json +40 -9
  150. package/scripts/cli/proxy-templates/cloudflare.ts +34 -0
  151. package/scripts/cli/proxy-templates/express.ts +77 -0
  152. package/scripts/cli/proxy-templates/nextjs-app.ts +42 -0
  153. package/scripts/cli/proxy-templates/nextjs-pages.ts +41 -0
  154. package/scripts/cli/proxy-templates/scaffold.json +33 -0
  155. package/scripts/cli/setup-devtools.js +155 -85
  156. package/scripts/cli/setup-project.js +618 -484
  157. package/themes/cyber.ts +469 -405
  158. package/themes/default.ts +469 -405
  159. package/themes/flat.ts +469 -405
  160. package/themes/tokyo-night-light.ts +38 -0
  161. package/themes/tokyo-night.ts +38 -0
  162. package/themes/vscode-dark-plus.ts +29 -0
  163. package/themes/vscode-light-plus.ts +29 -0
  164. package/dist/index.js.map +0 -1
  165. package/dist/index.mjs.map +0 -1
  166. package/dist/types/src/components/FormEnhancer.d.ts +0 -26
  167. package/dist/types/src/components/Template.d.ts +0 -18
  168. package/dist/types/src/components/ui/fields/AssistantAI.d.ts +0 -23
  169. package/dist/types/src/components/ui/fields/Command.d.ts +0 -15
  170. package/dist/types/src/components/ui/fields/ImageUrl.d.ts +0 -7
  171. package/dist/types/src/libs/cache.d.ts +0 -9
  172. package/dist/types/src/libs/database.d.ts +0 -3
  173. package/dist/types/src/libs/log.d.ts +0 -2
  174. package/dist/types/src/libs/seo.d.ts +0 -8
  175. package/dist/types/src/libs/storage.d.ts +0 -2
  176. package/dist/types/src/pages/Blog.d.ts +0 -3
  177. package/dist/types/src/pages/BlogPost.d.ts +0 -16
  178. package/dist/types/src/pages/Helper.d.ts +0 -8
  179. package/dist/types/src/providers/auth/google/apis/auth.d.ts +0 -4
  180. package/dist/types/src/types/Block.d.ts +0 -4
  181. package/dist/types/src/types/Section.d.ts +0 -4
@@ -1,484 +1,618 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const readline = require('readline');
4
-
5
- const root = process.cwd();
6
- const args = process.argv.slice(2);
7
- const shouldReset = args.includes('--reset');
8
-
9
- function getArg(name, fallback = '') {
10
- const prefix = `--${name}=`;
11
- const arg = args.find((item) => item.startsWith(prefix));
12
- return arg ? arg.slice(prefix.length) : fallback;
13
- }
14
-
15
- function ensureDir(dirPath) {
16
- if (!fs.existsSync(dirPath)) {
17
- fs.mkdirSync(dirPath, { recursive: true });
18
- console.log(`Created directory: ${dirPath}`);
19
- }
20
- }
21
-
22
- function ensureFile(filePath, content) {
23
- ensureDir(path.dirname(filePath));
24
- if (!fs.existsSync(filePath)) {
25
- fs.writeFileSync(filePath, content.trimStart());
26
- console.log(`Created file: ${filePath}`);
27
- }
28
- }
29
-
30
- function resetProjectDirectory() {
31
- console.log('Resetting scaffold files...');
32
- [
33
- 'src',
34
- 'public',
35
- 'dist',
36
- '.env',
37
- '.firebaserc',
38
- 'database.rules.json',
39
- 'firebase.json',
40
- 'storage.rules',
41
- 'index.html',
42
- 'vite.config.ts',
43
- 'tsconfig.json',
44
- 'tsconfig.node.json',
45
- 'postcss.config.js',
46
- ].forEach((entry) => {
47
- const entryPath = path.join(root, entry);
48
- if (fs.existsSync(entryPath)) {
49
- fs.rmSync(entryPath, { recursive: true, force: true });
50
- console.log(`Removed: ${entryPath}`);
51
- }
52
- });
53
- }
54
-
55
- async function askInteractive(callback) {
56
- const rl = readline.createInterface({
57
- input: process.stdin,
58
- output: process.stdout,
59
- });
60
-
61
- const question = (text) => new Promise((resolve) => rl.question(text, resolve));
62
- const defaultName = path.basename(root);
63
-
64
- try {
65
- const projectnameInput = await question(`What is your project name? (default: ${defaultName}) `);
66
- const projectname = projectnameInput.trim() || defaultName;
67
-
68
- const providerInput = await question('Which data provider? (firebase, supabase, mock, custom; default: mock) ');
69
- const provider = ['firebase', 'supabase', 'mock', 'custom'].includes(providerInput.trim())
70
- ? providerInput.trim()
71
- : 'mock';
72
-
73
- const iconProviderInput = await question('Which icon provider? (lucide, phosphor; default: lucide) ');
74
- const iconProvider = ['lucide', 'phosphor'].includes(iconProviderInput.trim())
75
- ? iconProviderInput.trim()
76
- : 'lucide';
77
-
78
- const themeInput = await question('Which theme? (default, flat, cyber; default: default) ');
79
- const theme = ['default', 'flat', 'cyber'].includes(themeInput.trim())
80
- ? themeInput.trim()
81
- : 'default';
82
-
83
- const templateInput = await question('Which app template? (blank, crm, admin, inventory, project; default: blank) ');
84
- const template = ['blank', 'crm', 'admin', 'inventory', 'project'].includes(templateInput.trim())
85
- ? templateInput.trim()
86
- : 'blank';
87
-
88
- let hosting = 'n';
89
- const firebase = {};
90
- const supabase = {};
91
-
92
- if (provider === 'firebase') {
93
- const hostingInput = await question(`Firebase Hosting site? Leave blank to use "${defaultName}", type 'n' to disable: `);
94
- hosting = hostingInput.trim();
95
-
96
- firebase.apikey = (await question('Firebase API key: ')).trim();
97
- firebase.authDomain = (await question('Firebase Auth Domain: ')).trim();
98
- firebase.dbUrl = (await question('Firebase Database URL: ')).trim();
99
- firebase.projId = (await question('Firebase Project ID: ')).trim();
100
- firebase.storageBucket = (await question('Firebase Storage Bucket: ')).trim();
101
- firebase.messSenderId = (await question('Firebase Messaging Sender ID: ')).trim();
102
- firebase.appId = (await question('Firebase App ID: ')).trim();
103
- firebase.measurementId = (await question('Firebase Measurement ID: ')).trim();
104
- firebase.googleClientId = (await question('Google Client ID: ')).trim();
105
- }
106
-
107
- if (provider === 'supabase') {
108
- supabase.url = (await question('Supabase URL: ')).trim();
109
- supabase.anonKey = (await question('Supabase anon key: ')).trim();
110
- }
111
-
112
- rl.close();
113
- callback({ projectname, provider, iconProvider, theme, template, hosting, firebase, supabase });
114
- } catch (error) {
115
- rl.close();
116
- console.error('Error during interactive prompt:', error);
117
- }
118
- }
119
-
120
- function normalizeFirebaseProjectId(projectName) {
121
- return String(projectName ?? '')
122
- .trim()
123
- .replace(/^[A-Z]/, (m) => m.toLowerCase())
124
- .replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
125
- .replace(/\s+/g, '-')
126
- .replace(/[^a-z0-9-]/g, '-')
127
- .replace(/-+/g, '-')
128
- .replace(/^-+|-+$/g, '');
129
- }
130
-
131
- function createPackageJson(params) {
132
- const packagePath = path.resolve(__dirname, '../../package.json');
133
- const version = JSON.parse(fs.readFileSync(packagePath, 'utf8')).version;
134
-
135
- ensureFile(path.join(root, 'package.json'), JSON.stringify({
136
- name: normalizeFirebaseProjectId(params.projectname) || 'ash-app',
137
- version: '0.1.0',
138
- private: true,
139
- type: 'module',
140
- scripts: {
141
- dev: 'vite',
142
- build: 'vite build',
143
- preview: 'vite preview',
144
- typecheck: 'tsc --noEmit',
145
- },
146
- dependencies: {
147
- '@phosphor-icons/react': '^2.0.0',
148
- '@vitejs/plugin-react': '^4.3.4',
149
- firebase: '^10.14.0',
150
- 'lucide-react': '^0.400.0',
151
- react: '^18.2.0',
152
- 'react-dom': '^18.2.0',
153
- '@llmnative/react': `^${version}`,
154
- 'react-router-dom': '^6.22.0',
155
- },
156
- devDependencies: {
157
- '@tailwindcss/postcss': '^4.2.4',
158
- '@types/react': '^18.2.0',
159
- '@types/react-dom': '^18.2.0',
160
- autoprefixer: '^10.5.0',
161
- postcss: '^8.5.14',
162
- typescript: '^5.6.2',
163
- vite: '^5.4.21',
164
- },
165
- }, null, 2));
166
- }
167
-
168
- function createDevTools() {
169
- require('./setup-devtools').setupDevTools();
170
- }
171
-
172
- function createIndexHtml(params) {
173
- ensureFile(path.join(root, 'index.html'), `
174
- <!doctype html>
175
- <html lang="en">
176
- <head>
177
- <meta charset="UTF-8" />
178
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
179
- <title>${params.projectname}</title>
180
- </head>
181
- <body>
182
- <div id="root"></div>
183
- <script type="module" src="/src/index.tsx"></script>
184
- </body>
185
- </html>
186
- `);
187
- }
188
-
189
- function createEnvFile(params) {
190
- const env = [
191
- `VITE_PROVIDER=${params.provider}`,
192
- `VITE_ICON_PROVIDER=${params.iconProvider}`,
193
- `VITE_THEME=${params.theme}`,
194
- `VITE_FIREBASE_APIKEY=${params.firebase.apikey ?? ''}`,
195
- `VITE_FIREBASE_AUTH_DOMAIN=${params.firebase.authDomain ?? ''}`,
196
- `VITE_FIREBASE_DATABASE_URL=${params.firebase.dbUrl ?? ''}`,
197
- `VITE_FIREBASE_PROJECT_ID=${params.firebase.projId ?? ''}`,
198
- `VITE_FIREBASE_STORAGE_BUCKET=${params.firebase.storageBucket ?? ''}`,
199
- `VITE_FIREBASE_MESSAGING_SENDER_ID=${params.firebase.messSenderId ?? ''}`,
200
- `VITE_FIREBASE_APP_ID=${params.firebase.appId ?? ''}`,
201
- `VITE_FIREBASE_MEASUREMENT_ID=${params.firebase.measurementId ?? ''}`,
202
- `VITE_GOOGLE_CLIENT_ID=${params.firebase.googleClientId ?? ''}`,
203
- 'VITE_GOOGLE_SCOPE=',
204
- `VITE_SUPABASE_URL=${params.supabase.url ?? ''}`,
205
- `VITE_SUPABASE_ANON_KEY=${params.supabase.anonKey ?? ''}`,
206
- ].join('\n');
207
-
208
- ensureFile(path.join(root, '.env'), `${env}\n`);
209
- }
210
-
211
- function createFirebaseConfig(params) {
212
- if (params.provider !== 'firebase') return;
213
-
214
- ensureFile(path.join(root, '.firebaserc'), JSON.stringify({
215
- projects: { default: params.projectname },
216
- }, null, 2));
217
-
218
- const includeHosting = params.hosting.toLowerCase() !== 'n';
219
- const hostingSite = params.hosting === '' ? params.projectname : params.hosting;
220
-
221
- ensureFile(path.join(root, 'firebase.json'), JSON.stringify({
222
- database: { rules: 'database.rules.json' },
223
- ...(includeHosting && {
224
- hosting: {
225
- site: normalizeFirebaseProjectId(hostingSite),
226
- public: 'dist',
227
- ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
228
- predeploy: ['npm run build'],
229
- rewrites: [{ source: '**', destination: '/index.html' }],
230
- },
231
- }),
232
- }, null, 2));
233
-
234
- ensureFile(path.join(root, 'database.rules.json'), JSON.stringify({
235
- rules: {
236
- '.read': 'auth != null',
237
- '.write': 'auth != null',
238
- },
239
- }, null, 2));
240
-
241
- ensureFile(path.join(root, 'storage.rules'), `
242
- rules_version = '2';
243
-
244
- service firebase.storage {
245
- match /b/{bucket}/o {
246
- match /{allPaths=**} {
247
- allow read, write: if request.auth != null;
248
- }
249
- }
250
- }
251
- `);
252
- }
253
-
254
- function copyDir(src, dest) {
255
- if (!fs.existsSync(src)) return;
256
- fs.readdirSync(src, { withFileTypes: true }).forEach((entry) => {
257
- const srcPath = path.join(src, entry.name);
258
- const destPath = path.join(dest, entry.name);
259
- if (entry.isDirectory()) {
260
- copyDir(srcPath, destPath);
261
- } else {
262
- ensureDir(path.dirname(destPath));
263
- if (!fs.existsSync(destPath)) {
264
- fs.copyFileSync(srcPath, destPath);
265
- console.log(`Created file: ${destPath}`);
266
- }
267
- }
268
- });
269
- }
270
-
271
- function substituteProjectName(filePath, projectname) {
272
- const content = fs.readFileSync(filePath, 'utf8');
273
- const updated = content.replace(/\[projectname\]/g, projectname);
274
- if (updated !== content) fs.writeFileSync(filePath, updated);
275
- }
276
-
277
- function copyTemplateFiles(params) {
278
- const sharedSrc = path.resolve(__dirname, '../../templates/_shared');
279
- const templateSrc = path.resolve(__dirname, `../../templates/${params.template}`);
280
-
281
- // Copy shared layouts and sections
282
- copyDir(path.join(sharedSrc, 'layouts'), path.join(root, 'src/layouts'));
283
- copyDir(path.join(sharedSrc, 'sections'), path.join(root, 'src/sections'));
284
-
285
- // Copy template-specific files (pages, data, conf/menu.ts)
286
- // layouts and sections from template override shared ones if present
287
- copyDir(templateSrc, path.join(root, 'src'));
288
-
289
- // Substitute [projectname] in copied files
290
- ['src/layouts', 'src/sections', 'src/pages'].forEach((dir) => {
291
- const absDir = path.join(root, dir);
292
- if (!fs.existsSync(absDir)) return;
293
- walkFiles(absDir, (file) => substituteProjectName(file, params.projectname));
294
- });
295
- }
296
-
297
- function walkFiles(dir, fn) {
298
- fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
299
- const full = path.join(dir, entry.name);
300
- if (entry.isDirectory()) walkFiles(full, fn);
301
- else fn(full);
302
- });
303
- }
304
-
305
- function createSourceFiles(params) {
306
- copyTemplateFiles(params);
307
-
308
- ensureFile(path.join(root, 'src/vite-env.d.ts'), `
309
- /// <reference types="vite/client" />
310
- `);
311
-
312
- ensureFile(path.join(root, 'src/styles/globals.css'), `
313
- @import "tailwindcss";
314
- @import "@llmnative/react/dist/index.css";
315
-
316
- @theme inline {
317
- --color-background: hsl(var(--rf-background));
318
- --color-foreground: hsl(var(--rf-foreground));
319
- --color-card: hsl(var(--rf-card));
320
- --color-card-foreground: hsl(var(--rf-card-foreground));
321
- --color-popover: hsl(var(--rf-popover));
322
- --color-popover-foreground: hsl(var(--rf-popover-foreground));
323
- --color-primary: hsl(var(--rf-primary));
324
- --color-primary-foreground: hsl(var(--rf-primary-foreground));
325
- --color-secondary: hsl(var(--rf-secondary));
326
- --color-secondary-foreground: hsl(var(--rf-secondary-foreground));
327
- --color-muted: hsl(var(--rf-muted));
328
- --color-muted-foreground: hsl(var(--rf-muted-foreground));
329
- --color-accent: hsl(var(--rf-accent));
330
- --color-accent-foreground: hsl(var(--rf-accent-foreground));
331
- --color-destructive: hsl(var(--rf-destructive));
332
- --color-destructive-foreground: hsl(var(--rf-destructive-foreground));
333
- --color-success: hsl(var(--rf-success));
334
- --color-success-foreground: hsl(var(--rf-success-foreground));
335
- --color-warning: hsl(var(--rf-warning));
336
- --color-warning-foreground: hsl(var(--rf-warning-foreground));
337
- --color-info: hsl(var(--rf-info));
338
- --color-info-foreground: hsl(var(--rf-info-foreground));
339
- --color-border: hsl(var(--rf-border));
340
- --color-input: hsl(var(--rf-input));
341
- --color-ring: hsl(var(--rf-ring));
342
- --radius: 0.5rem;
343
- }
344
-
345
- html, body, #root {
346
- min-height: 100%;
347
- }
348
-
349
- body {
350
- margin: 0;
351
- font-family: var(--font-sans);
352
- background-color: hsl(var(--rf-background));
353
- color: hsl(var(--rf-foreground));
354
- }
355
- `);
356
-
357
- ensureFile(path.join(root, 'src/conf/app.ts'), `
358
- const env = import.meta.env;
359
-
360
- export const appConfig = {
361
- provider: env.VITE_PROVIDER ?? '${params.provider}',
362
- iconProvider: env.VITE_ICON_PROVIDER ?? '${params.iconProvider}',
363
- theme: env.VITE_THEME ?? '${params.theme}',
364
- };
365
- `);
366
-
367
- ensureFile(path.join(root, 'src/index.tsx'), `
368
- import React from 'react';
369
- import { createRoot } from 'react-dom/client';
370
- import { App } from '@llmnative/react';
371
- import './styles/globals.css';
372
-
373
- import { appConfig } from './conf/app';
374
- import { menu } from './conf/menu';
375
- import { mockData } from './data/mockData';
376
-
377
- const env = import.meta.env;
378
- const selectedProvider = appConfig.provider;
379
- const dataDriver =
380
- selectedProvider === 'firebase' ? 'dbRealtime'
381
- : selectedProvider === 'supabase' ? 'supabaseDb'
382
- : 'mock';
383
- const storageDriver =
384
- selectedProvider === 'firebase' ? 'firestorage'
385
- : selectedProvider === 'supabase' ? 'supabaseStorage'
386
- : undefined;
387
-
388
- createRoot(document.getElementById('root')!).render(
389
- <React.StrictMode>
390
- <App
391
- importPage={(pageSource) => import(/* @vite-ignore */ pageSource)}
392
- menuConfig={menu}
393
- providers={{
394
- mock: {
395
- data: mockData,
396
- },
397
- firebase: {
398
- apiKey: env.VITE_FIREBASE_APIKEY ?? '',
399
- authDomain: env.VITE_FIREBASE_AUTH_DOMAIN ?? '',
400
- databaseURL: env.VITE_FIREBASE_DATABASE_URL ?? '',
401
- projectId: env.VITE_FIREBASE_PROJECT_ID ?? '',
402
- storageBucket: env.VITE_FIREBASE_STORAGE_BUCKET ?? '',
403
- messagingSenderId: env.VITE_FIREBASE_MESSAGING_SENDER_ID ?? '',
404
- appId: env.VITE_FIREBASE_APP_ID ?? '',
405
- measurementId: env.VITE_FIREBASE_MEASUREMENT_ID ?? '',
406
- },
407
- supabase: {
408
- url: env.VITE_SUPABASE_URL ?? '',
409
- anonKey: env.VITE_SUPABASE_ANON_KEY ?? '',
410
- },
411
- google: {
412
- clientId: env.VITE_GOOGLE_CLIENT_ID ?? '',
413
- scope: env.VITE_GOOGLE_SCOPE ?? '',
414
- },
415
- services: {
416
- data: dataDriver,
417
- storage: storageDriver,
418
- auth: 'googleAuth',
419
- },
420
- }}
421
- iconProvider={appConfig.iconProvider}
422
- themeProvider={appConfig.theme}
423
- />
424
- </React.StrictMode>
425
- );
426
- `);
427
- }
428
-
429
- function scaffoldProject() {
430
- const nonInteractive = args.includes('--yes') || args.some((arg) => arg.startsWith('--provider='));
431
- if (nonInteractive) {
432
- const defaultName = path.basename(root);
433
- const params = {
434
- projectname: getArg('name', defaultName),
435
- provider: getArg('provider', 'mock'),
436
- iconProvider: getArg('icon-provider', 'lucide'),
437
- theme: getArg('theme', getArg('theme-provider', 'default')),
438
- template: getArg('template', 'blank'),
439
- hosting: getArg('hosting', 'n'),
440
- firebase: {
441
- apikey: getArg('firebase-api-key'),
442
- authDomain: getArg('firebase-auth-domain'),
443
- dbUrl: getArg('firebase-database-url'),
444
- projId: getArg('firebase-project-id'),
445
- storageBucket: getArg('firebase-storage-bucket'),
446
- messSenderId: getArg('firebase-messaging-sender-id'),
447
- appId: getArg('firebase-app-id'),
448
- measurementId: getArg('firebase-measurement-id'),
449
- googleClientId:getArg('google-client-id'),
450
- },
451
- supabase: {
452
- url: getArg('supabase-url'),
453
- anonKey: getArg('supabase-anon-key'),
454
- },
455
- };
456
-
457
- if (shouldReset) resetProjectDirectory();
458
-
459
- console.log(`\nCreating project: ${params.projectname} | theme: ${params.theme} | template: ${params.template}\n`);
460
- createPackageJson(params);
461
- createDevTools();
462
- createIndexHtml(params);
463
- createEnvFile(params);
464
- createFirebaseConfig(params);
465
- createSourceFiles(params);
466
- console.log(`\nDone. Run: npm install && npm run dev\n`);
467
- return;
468
- }
469
-
470
- askInteractive((params) => {
471
- if (shouldReset) resetProjectDirectory();
472
-
473
- console.log(`\nCreating project: ${params.projectname} | theme: ${params.theme} | template: ${params.template}\n`);
474
- createPackageJson(params);
475
- createDevTools();
476
- createIndexHtml(params);
477
- createEnvFile(params);
478
- createFirebaseConfig(params);
479
- createSourceFiles(params);
480
- console.log(`\nDone. Run: npm install && npm run dev\n`);
481
- });
482
- }
483
-
484
- module.exports = { scaffoldProject };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+
5
+ const root = process.cwd();
6
+ const args = process.argv.slice(2);
7
+ const shouldReset = args.includes('--reset');
8
+
9
+ function getArg(name, fallback = '') {
10
+ const prefix = `--${name}=`;
11
+ const arg = args.find((item) => item.startsWith(prefix));
12
+ return arg ? arg.slice(prefix.length) : fallback;
13
+ }
14
+
15
+ function ensureDir(dirPath) {
16
+ if (!fs.existsSync(dirPath)) {
17
+ fs.mkdirSync(dirPath, { recursive: true });
18
+ console.log(`Created directory: ${dirPath}`);
19
+ }
20
+ }
21
+
22
+ function ensureFile(filePath, content) {
23
+ ensureDir(path.dirname(filePath));
24
+ if (!fs.existsSync(filePath)) {
25
+ fs.writeFileSync(filePath, content.trimStart());
26
+ console.log(`Created file: ${filePath}`);
27
+ }
28
+ }
29
+
30
+ function resetProjectDirectory() {
31
+ console.log('Resetting scaffold files...');
32
+ [
33
+ 'src',
34
+ 'public',
35
+ 'dist',
36
+ '.env',
37
+ '.env.example',
38
+ '.firebaserc',
39
+ 'database.rules.json',
40
+ 'firebase.json',
41
+ 'storage.rules',
42
+ 'index.html',
43
+ 'vite.config.ts',
44
+ 'tsconfig.json',
45
+ 'tsconfig.node.json',
46
+ 'postcss.config.js',
47
+ ].forEach((entry) => {
48
+ const entryPath = path.join(root, entry);
49
+ if (fs.existsSync(entryPath)) {
50
+ fs.rmSync(entryPath, { recursive: true, force: true });
51
+ console.log(`Removed: ${entryPath}`);
52
+ }
53
+ });
54
+ }
55
+
56
+ async function askInteractive(callback) {
57
+ const rl = readline.createInterface({
58
+ input: process.stdin,
59
+ output: process.stdout,
60
+ });
61
+
62
+ const question = (text) => new Promise((resolve) => rl.question(text, resolve));
63
+ const defaultName = path.basename(root);
64
+
65
+ try {
66
+ const projectnameInput = await question(`What is your project name? (default: ${defaultName}) `);
67
+ const projectname = projectnameInput.trim() || defaultName;
68
+
69
+ const providerInput = await question('Which data provider? (firebase, supabase, mock, custom; default: mock) ');
70
+ const provider = ['firebase', 'supabase', 'mock', 'custom'].includes(providerInput.trim())
71
+ ? providerInput.trim()
72
+ : 'mock';
73
+
74
+ const aiProviderInput = await question('Which AI provider? (none, openai, openrouter, opencode, gemini, anthropic, deepseek, mistral; default: none) ');
75
+ const aiProvider = ['none', 'openai', 'openrouter', 'opencode', 'gemini', 'anthropic', 'deepseek', 'mistral'].includes(aiProviderInput.trim())
76
+ ? aiProviderInput.trim()
77
+ : 'none';
78
+
79
+ const proxyProviderInput = await question('Which proxy provider? (none, vite, express, nextjs-app, nextjs-pages, cloudflare; default: none) ');
80
+ const proxyProvider = ['none', 'vite', 'express', 'nextjs-app', 'nextjs-pages', 'cloudflare'].includes(proxyProviderInput.trim())
81
+ ? proxyProviderInput.trim()
82
+ : 'none';
83
+
84
+ const iconProviderInput = await question('Which icon provider? (lucide, phosphor; default: lucide) ');
85
+ const iconProvider = ['lucide', 'phosphor'].includes(iconProviderInput.trim())
86
+ ? iconProviderInput.trim()
87
+ : 'lucide';
88
+
89
+ const themeInput = await question('Which theme? (default, flat, cyber; default: default) ');
90
+ const theme = ['default', 'flat', 'cyber'].includes(themeInput.trim())
91
+ ? themeInput.trim()
92
+ : 'default';
93
+
94
+ const localeInput = await question('Default locale? (en, it, fr, de, es; default: en) ');
95
+ const locale = localeInput.trim() || 'en';
96
+
97
+ const templateInput = await question('Which app template? (blank, crm, admin, inventory, project; default: blank) ');
98
+ const template = ['blank', 'crm', 'admin', 'inventory', 'project'].includes(templateInput.trim())
99
+ ? templateInput.trim()
100
+ : 'blank';
101
+
102
+ let hosting = 'n';
103
+ const firebase = {};
104
+ const supabase = {};
105
+ const ai = {};
106
+
107
+ if (provider === 'firebase') {
108
+ const hostingInput = await question(`Firebase Hosting site? Leave blank to use "${defaultName}", type 'n' to disable: `);
109
+ hosting = hostingInput.trim();
110
+
111
+ firebase.apikey = (await question('Firebase API key: ')).trim();
112
+ firebase.authDomain = (await question('Firebase Auth Domain: ')).trim();
113
+ firebase.dbUrl = (await question('Firebase Database URL: ')).trim();
114
+ firebase.projId = (await question('Firebase Project ID: ')).trim();
115
+ firebase.storageBucket = (await question('Firebase Storage Bucket: ')).trim();
116
+ firebase.messSenderId = (await question('Firebase Messaging Sender ID: ')).trim();
117
+ firebase.appId = (await question('Firebase App ID: ')).trim();
118
+ firebase.measurementId = (await question('Firebase Measurement ID: ')).trim();
119
+ firebase.googleClientId = (await question('Google Client ID: ')).trim();
120
+ }
121
+
122
+ if (provider === 'supabase') {
123
+ supabase.url = (await question('Supabase URL: ')).trim();
124
+ supabase.anonKey = (await question('Supabase anon key: ')).trim();
125
+ }
126
+
127
+ if (aiProvider === 'openai') {
128
+ ai.openaiApiKey = (await question('OpenAI API key: ')).trim();
129
+ }
130
+ if (aiProvider === 'openrouter') {
131
+ ai.openRouterApiKey = (await question('OpenRouter API key: ')).trim();
132
+ }
133
+ if (aiProvider === 'opencode') {
134
+ ai.openCodeApiKey = (await question('OpenCode API key: ')).trim();
135
+ }
136
+ if (aiProvider === 'gemini') {
137
+ ai.geminiApiKey = (await question('Gemini API key: ')).trim();
138
+ }
139
+ if (aiProvider === 'anthropic') {
140
+ ai.anthropicApiKey = (await question('Anthropic API key: ')).trim();
141
+ }
142
+ if (aiProvider === 'deepseek') {
143
+ ai.deepSeekApiKey = (await question('DeepSeek API key: ')).trim();
144
+ }
145
+ if (aiProvider === 'mistral') {
146
+ ai.mistralApiKey = (await question('Mistral API key: ')).trim();
147
+ }
148
+
149
+ rl.close();
150
+ callback({ projectname, provider, aiProvider, proxyProvider, iconProvider, theme, locale, template, hosting, firebase, supabase, ai });
151
+ } catch (error) {
152
+ rl.close();
153
+ console.error('Error during interactive prompt:', error);
154
+ }
155
+ }
156
+
157
+ function normalizeFirebaseProjectId(projectName) {
158
+ return String(projectName ?? '')
159
+ .trim()
160
+ .replace(/^[A-Z]/, (m) => m.toLowerCase())
161
+ .replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
162
+ .replace(/\s+/g, '-')
163
+ .replace(/[^a-z0-9-]/g, '-')
164
+ .replace(/-+/g, '-')
165
+ .replace(/^-+|-+$/g, '');
166
+ }
167
+
168
+ function createPackageJson(params) {
169
+ const packagePath = path.resolve(__dirname, '../../package.json');
170
+ const version = JSON.parse(fs.readFileSync(packagePath, 'utf8')).version;
171
+
172
+ ensureFile(path.join(root, 'package.json'), JSON.stringify({
173
+ name: normalizeFirebaseProjectId(params.projectname) || 'llmnative-app',
174
+ version: '0.1.0',
175
+ private: true,
176
+ type: 'module',
177
+ scripts: {
178
+ dev: 'vite',
179
+ build: 'vite build',
180
+ preview: 'vite preview',
181
+ typecheck: 'tsc --noEmit',
182
+ },
183
+ dependencies: {
184
+ '@phosphor-icons/react': '^2.0.0',
185
+ '@vitejs/plugin-react': '^4.3.4',
186
+ firebase: '^10.14.0',
187
+ 'lucide-react': '^0.400.0',
188
+ react: '^18.2.0',
189
+ 'react-dom': '^18.2.0',
190
+ '@llmnative/react': `^${version}`,
191
+ 'react-router-dom': '^6.22.0',
192
+ },
193
+ devDependencies: {
194
+ '@tailwindcss/postcss': '^4.2.4',
195
+ '@types/react': '^18.2.0',
196
+ '@types/react-dom': '^18.2.0',
197
+ autoprefixer: '^10.5.0',
198
+ postcss: '^8.5.14',
199
+ typescript: '^5.6.2',
200
+ vite: '^5.4.21',
201
+ },
202
+ }, null, 2));
203
+ }
204
+
205
+ function createIndexHtml(params) {
206
+ ensureFile(path.join(root, 'index.html'), `
207
+ <!doctype html>
208
+ <html lang="en">
209
+ <head>
210
+ <meta charset="UTF-8" />
211
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
212
+ <title>${params.projectname}</title>
213
+ </head>
214
+ <body>
215
+ <div id="root"></div>
216
+ <script type="module" src="/src/index.tsx"></script>
217
+ </body>
218
+ </html>
219
+ `);
220
+ }
221
+
222
+ function createEnvFile(params) {
223
+ const proxyEnabled = params.proxyProvider && params.proxyProvider !== 'none' ? 'true' : 'false';
224
+ const env = [
225
+ '# --- Core ---',
226
+ `VITE_PROVIDER=${params.provider}`,
227
+ `VITE_AI_PROVIDER=${params.aiProvider ?? 'none'}`,
228
+ `VITE_ICON_PROVIDER=${params.iconProvider}`,
229
+ `VITE_THEME=${params.theme}`,
230
+ `VITE_LOCALE=${params.locale ?? 'en'}`,
231
+ '',
232
+ '# --- Proxy (required for AI calls from the browser) ---',
233
+ `VITE_PROXY_PROVIDER=${params.proxyProvider ?? 'none'}`,
234
+ `VITE_PROXY_ENABLED=${proxyEnabled}`,
235
+ '',
236
+ '# --- Firebase ---',
237
+ `VITE_FIREBASE_APIKEY=${params.firebase.apikey ?? ''}`,
238
+ `VITE_FIREBASE_AUTH_DOMAIN=${params.firebase.authDomain ?? ''}`,
239
+ `VITE_FIREBASE_DATABASE_URL=${params.firebase.dbUrl ?? ''}`,
240
+ `VITE_FIREBASE_PROJECT_ID=${params.firebase.projId ?? ''}`,
241
+ `VITE_FIREBASE_STORAGE_BUCKET=${params.firebase.storageBucket ?? ''}`,
242
+ `VITE_FIREBASE_MESSAGING_SENDER_ID=${params.firebase.messSenderId ?? ''}`,
243
+ `VITE_FIREBASE_APP_ID=${params.firebase.appId ?? ''}`,
244
+ `VITE_FIREBASE_MEASUREMENT_ID=${params.firebase.measurementId ?? ''}`,
245
+ '',
246
+ '# --- Google OAuth ---',
247
+ `VITE_GOOGLE_CLIENT_ID=${params.firebase.googleClientId ?? ''}`,
248
+ 'VITE_GOOGLE_SCOPE=',
249
+ '',
250
+ '# --- Supabase ---',
251
+ `VITE_SUPABASE_URL=${params.supabase.url ?? ''}`,
252
+ `VITE_SUPABASE_ANON_KEY=${params.supabase.anonKey ?? ''}`,
253
+ '',
254
+ '# --- Dropbox ---',
255
+ 'VITE_DROPBOX_CLIENT_ID=',
256
+ 'VITE_DROPBOX_ROOT_PATH=',
257
+ '',
258
+ '# --- AI providers ---',
259
+ `VITE_OPENAI_API_KEY=${params.ai.openaiApiKey ?? ''}`,
260
+ `VITE_OPENROUTER_API_KEY=${params.ai.openRouterApiKey ?? ''}`,
261
+ `VITE_OPENCODE_API_KEY=${params.ai.openCodeApiKey ?? ''}`,
262
+ `VITE_GEMINI_API_KEY=${params.ai.geminiApiKey ?? ''}`,
263
+ `VITE_ANTHROPIC_API_KEY=${params.ai.anthropicApiKey ?? ''}`,
264
+ `VITE_DEEPSEEK_API_KEY=${params.ai.deepSeekApiKey ?? ''}`,
265
+ `VITE_MISTRAL_API_KEY=${params.ai.mistralApiKey ?? ''}`,
266
+ '',
267
+ '# --- OpenAI-compatible (leave BASE_URL empty to disable) ---',
268
+ 'VITE_OPENAI_COMPATIBLE_BASE_URL=',
269
+ 'VITE_OPENAI_COMPATIBLE_API_KEY=',
270
+ ].join('\n');
271
+
272
+ ensureFile(path.join(root, '.env'), `${env}\n`);
273
+
274
+ const example = env
275
+ .split('\n')
276
+ .map(line => (line && !line.startsWith('#') && line.includes('=') ? line.split('=')[0] + '=' : line))
277
+ .join('\n');
278
+ ensureFile(path.join(root, '.env.example'), `${example}\n`);
279
+ }
280
+
281
+ function createFirebaseConfig(params) {
282
+ if (params.provider !== 'firebase') return;
283
+
284
+ ensureFile(path.join(root, '.firebaserc'), JSON.stringify({
285
+ projects: { default: params.projectname },
286
+ }, null, 2));
287
+
288
+ const includeHosting = params.hosting.toLowerCase() !== 'n';
289
+ const hostingSite = params.hosting === '' ? params.projectname : params.hosting;
290
+
291
+ ensureFile(path.join(root, 'firebase.json'), JSON.stringify({
292
+ database: { rules: 'database.rules.json' },
293
+ ...(includeHosting && {
294
+ hosting: {
295
+ site: normalizeFirebaseProjectId(hostingSite),
296
+ public: 'dist',
297
+ ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
298
+ predeploy: ['npm run build'],
299
+ rewrites: [{ source: '**', destination: '/index.html' }],
300
+ },
301
+ }),
302
+ }, null, 2));
303
+
304
+ ensureFile(path.join(root, 'database.rules.json'), JSON.stringify({
305
+ rules: {
306
+ '.read': 'auth != null',
307
+ '.write': 'auth != null',
308
+ },
309
+ }, null, 2));
310
+
311
+ ensureFile(path.join(root, 'storage.rules'), `
312
+ rules_version = '2';
313
+
314
+ service firebase.storage {
315
+ match /b/{bucket}/o {
316
+ match /{allPaths=**} {
317
+ allow read, write: if request.auth != null;
318
+ }
319
+ }
320
+ }
321
+ `);
322
+ }
323
+
324
+ function copyDir(src, dest) {
325
+ if (!fs.existsSync(src)) return;
326
+ fs.readdirSync(src, { withFileTypes: true }).forEach((entry) => {
327
+ const srcPath = path.join(src, entry.name);
328
+ const destPath = path.join(dest, entry.name);
329
+ if (entry.isDirectory()) {
330
+ copyDir(srcPath, destPath);
331
+ } else {
332
+ ensureDir(path.dirname(destPath));
333
+ if (!fs.existsSync(destPath)) {
334
+ fs.copyFileSync(srcPath, destPath);
335
+ console.log(`Created file: ${destPath}`);
336
+ }
337
+ }
338
+ });
339
+ }
340
+
341
+ function substituteProjectName(filePath, projectname) {
342
+ const content = fs.readFileSync(filePath, 'utf8');
343
+ const updated = content.replace(/\[projectname\]/g, projectname);
344
+ if (updated !== content) fs.writeFileSync(filePath, updated);
345
+ }
346
+
347
+ function copyTemplateFiles(params) {
348
+ const sharedSrc = path.resolve(__dirname, '../../templates/_shared');
349
+ const templateSrc = path.resolve(__dirname, `../../templates/${params.template}`);
350
+
351
+ // Copy shared layouts and sections
352
+ copyDir(path.join(sharedSrc, 'layouts'), path.join(root, 'src/layouts'));
353
+ copyDir(path.join(sharedSrc, 'sections'), path.join(root, 'src/sections'));
354
+
355
+ // Copy template-specific files (pages, data, conf/menu.ts)
356
+ // layouts and sections from template override shared ones if present
357
+ copyDir(templateSrc, path.join(root, 'src'));
358
+
359
+ // Substitute [projectname] in copied files
360
+ ['src/layouts', 'src/sections', 'src/pages'].forEach((dir) => {
361
+ const absDir = path.join(root, dir);
362
+ if (!fs.existsSync(absDir)) return;
363
+ walkFiles(absDir, (file) => substituteProjectName(file, params.projectname));
364
+ });
365
+ }
366
+
367
+ function walkFiles(dir, fn) {
368
+ fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
369
+ const full = path.join(dir, entry.name);
370
+ if (entry.isDirectory()) walkFiles(full, fn);
371
+ else fn(full);
372
+ });
373
+ }
374
+
375
+ function createSourceFiles(params) {
376
+ copyTemplateFiles(params);
377
+
378
+ ensureFile(path.join(root, 'src/vite-env.d.ts'), `
379
+ /// <reference types="vite/client" />
380
+ `);
381
+
382
+ ensureFile(path.join(root, 'src/styles/globals.css'), `
383
+ @import "tailwindcss";
384
+ @import "@llmnative/react/dist/index.css";
385
+
386
+ @theme inline {
387
+ --color-background: hsl(var(--rf-background));
388
+ --color-foreground: hsl(var(--rf-foreground));
389
+ --color-card: hsl(var(--rf-card));
390
+ --color-card-foreground: hsl(var(--rf-card-foreground));
391
+ --color-popover: hsl(var(--rf-popover));
392
+ --color-popover-foreground: hsl(var(--rf-popover-foreground));
393
+ --color-primary: hsl(var(--rf-primary));
394
+ --color-primary-foreground: hsl(var(--rf-primary-foreground));
395
+ --color-secondary: hsl(var(--rf-secondary));
396
+ --color-secondary-foreground: hsl(var(--rf-secondary-foreground));
397
+ --color-muted: hsl(var(--rf-muted));
398
+ --color-muted-foreground: hsl(var(--rf-muted-foreground));
399
+ --color-accent: hsl(var(--rf-accent));
400
+ --color-accent-foreground: hsl(var(--rf-accent-foreground));
401
+ --color-destructive: hsl(var(--rf-destructive));
402
+ --color-destructive-foreground: hsl(var(--rf-destructive-foreground));
403
+ --color-success: hsl(var(--rf-success));
404
+ --color-success-foreground: hsl(var(--rf-success-foreground));
405
+ --color-warning: hsl(var(--rf-warning));
406
+ --color-warning-foreground: hsl(var(--rf-warning-foreground));
407
+ --color-info: hsl(var(--rf-info));
408
+ --color-info-foreground: hsl(var(--rf-info-foreground));
409
+ --color-border: hsl(var(--rf-border));
410
+ --color-input: hsl(var(--rf-input));
411
+ --color-ring: hsl(var(--rf-ring));
412
+ --radius: 0.5rem;
413
+ }
414
+
415
+ html, body, #root {
416
+ min-height: 100%;
417
+ }
418
+
419
+ body {
420
+ margin: 0;
421
+ font-family: var(--font-sans);
422
+ background-color: hsl(var(--rf-background));
423
+ color: hsl(var(--rf-foreground));
424
+ }
425
+ `);
426
+
427
+ ensureFile(path.join(root, 'src/conf/app.ts'), `
428
+ import type { AIConfig, AppProvidersConfig } from '@llmnative/react';
429
+ import { mockData } from '../data/mockData';
430
+
431
+ const env = import.meta.env;
432
+ const selectedProvider = env.VITE_PROVIDER ?? '${params.provider}';
433
+ const selectedAIProvider = env.VITE_AI_PROVIDER ?? '${params.aiProvider ?? 'none'}';
434
+
435
+ const dataDriver =
436
+ selectedProvider === 'firebase' ? 'dbRealtime'
437
+ : selectedProvider === 'supabase' ? 'supabaseDb'
438
+ : 'mock';
439
+
440
+ const storageDriver =
441
+ selectedProvider === 'firebase' ? 'firestorage'
442
+ : selectedProvider === 'supabase' ? 'supabaseStorage'
443
+ : undefined;
444
+
445
+ const aiDriver = selectedAIProvider !== 'none'
446
+ ? selectedAIProvider
447
+ : undefined;
448
+
449
+ export const appConfig = {
450
+ provider: selectedProvider,
451
+ aiProvider: selectedAIProvider,
452
+ iconProvider: env.VITE_ICON_PROVIDER ?? '${params.iconProvider}',
453
+ theme: env.VITE_THEME ?? '${params.theme}',
454
+ locale: env.VITE_LOCALE ?? '${params.locale}',
455
+ };
456
+
457
+ export const aiConfig: AIConfig = {
458
+ openaiApiKey: env.VITE_OPENAI_API_KEY ?? '',
459
+ openRouterApiKey: env.VITE_OPENROUTER_API_KEY ?? '',
460
+ openCodeApiKey: env.VITE_OPENCODE_API_KEY ?? '',
461
+ geminiApiKey: env.VITE_GEMINI_API_KEY ?? '',
462
+ anthropicApiKey: env.VITE_ANTHROPIC_API_KEY ?? '',
463
+ deepSeekApiKey: env.VITE_DEEPSEEK_API_KEY ?? '',
464
+ mistralApiKey: env.VITE_MISTRAL_API_KEY ?? '',
465
+ ...(env.VITE_OPENAI_COMPATIBLE_BASE_URL ? {
466
+ openAICompatible: {
467
+ apiKey: env.VITE_OPENAI_COMPATIBLE_API_KEY ?? '',
468
+ baseUrl: env.VITE_OPENAI_COMPATIBLE_BASE_URL,
469
+ },
470
+ } : {}),
471
+ };
472
+
473
+ export const providers: AppProvidersConfig = {
474
+ proxy: {
475
+ enabled: env.VITE_PROXY_ENABLED === 'true',
476
+ },
477
+ mock: {
478
+ data: mockData,
479
+ },
480
+ firebase: {
481
+ apiKey: env.VITE_FIREBASE_APIKEY ?? '',
482
+ authDomain: env.VITE_FIREBASE_AUTH_DOMAIN ?? '',
483
+ databaseURL: env.VITE_FIREBASE_DATABASE_URL ?? '',
484
+ projectId: env.VITE_FIREBASE_PROJECT_ID ?? '',
485
+ storageBucket: env.VITE_FIREBASE_STORAGE_BUCKET ?? '',
486
+ messagingSenderId: env.VITE_FIREBASE_MESSAGING_SENDER_ID ?? '',
487
+ appId: env.VITE_FIREBASE_APP_ID ?? '',
488
+ measurementId: env.VITE_FIREBASE_MEASUREMENT_ID ?? '',
489
+ },
490
+ supabase: {
491
+ url: env.VITE_SUPABASE_URL ?? '',
492
+ anonKey: env.VITE_SUPABASE_ANON_KEY ?? '',
493
+ },
494
+ google: {
495
+ clientId: env.VITE_GOOGLE_CLIENT_ID ?? '',
496
+ scope: env.VITE_GOOGLE_SCOPE ?? '',
497
+ },
498
+ dropbox: {
499
+ clientId: env.VITE_DROPBOX_CLIENT_ID ?? '',
500
+ rootPath: env.VITE_DROPBOX_ROOT_PATH ?? '',
501
+ },
502
+ services: {
503
+ data: dataDriver,
504
+ ...(storageDriver ? { storage: storageDriver } : {}),
505
+ auth: 'googleAuth',
506
+ ...(aiDriver ? { ai: aiDriver } : {}),
507
+ },
508
+ };
509
+ `);
510
+
511
+ ensureFile(path.join(root, 'src/index.tsx'), `
512
+ import React from 'react';
513
+ import { createRoot } from 'react-dom/client';
514
+ import { App } from '@llmnative/react';
515
+ import './styles/globals.css';
516
+
517
+ import { aiConfig, appConfig, providers } from './conf/app';
518
+ import { menu } from './conf/menu';
519
+
520
+ const env = import.meta.env;
521
+
522
+ createRoot(document.getElementById('root')!).render(
523
+ <React.StrictMode>
524
+ <App
525
+ importPage={(pageSource) => import(/* @vite-ignore */ pageSource)}
526
+ menuConfig={menu}
527
+ providers={providers}
528
+ aiConfig={aiConfig}
529
+ iconProvider={appConfig.iconProvider}
530
+ themeProvider={{
531
+ theme: appConfig.theme,
532
+ // themeOverride: {
533
+ // Modal: { size: 'xl' },
534
+ // ActionButton: { className: 'btn-primary font-semibold' },
535
+ // },
536
+ }}
537
+ i18n={{
538
+ locale: appConfig.locale,
539
+ // translations: {
540
+ // it: { common: { save: 'Salva', cancel: 'Annulla' }, form: { buttonSave: 'Salva' } },
541
+ // },
542
+ }}
543
+ />
544
+ </React.StrictMode>
545
+ );
546
+ `);
547
+ }
548
+
549
+ function scaffoldProject() {
550
+ const nonInteractive = args.includes('--yes')
551
+ || args.some((arg) => arg.startsWith('--provider='))
552
+ || args.some((arg) => arg.startsWith('--ai-provider='));
553
+ if (nonInteractive) {
554
+ const defaultName = path.basename(root);
555
+ const params = {
556
+ projectname: getArg('name', defaultName),
557
+ provider: getArg('provider', 'mock'),
558
+ aiProvider: getArg('ai-provider', 'none'),
559
+ proxyProvider: getArg('proxy-provider', 'none'),
560
+ iconProvider: getArg('icon-provider', 'lucide'),
561
+ theme: getArg('theme', getArg('theme-provider', 'default')),
562
+ locale: getArg('locale', 'en'),
563
+ template: getArg('template', 'blank'),
564
+ hosting: getArg('hosting', 'n'),
565
+ firebase: {
566
+ apikey: getArg('firebase-api-key'),
567
+ authDomain: getArg('firebase-auth-domain'),
568
+ dbUrl: getArg('firebase-database-url'),
569
+ projId: getArg('firebase-project-id'),
570
+ storageBucket: getArg('firebase-storage-bucket'),
571
+ messSenderId: getArg('firebase-messaging-sender-id'),
572
+ appId: getArg('firebase-app-id'),
573
+ measurementId: getArg('firebase-measurement-id'),
574
+ googleClientId:getArg('google-client-id'),
575
+ },
576
+ supabase: {
577
+ url: getArg('supabase-url'),
578
+ anonKey: getArg('supabase-anon-key'),
579
+ },
580
+ ai: {
581
+ openaiApiKey: getArg('openai-api-key'),
582
+ openRouterApiKey: getArg('openrouter-api-key'),
583
+ openCodeApiKey: getArg('opencode-api-key'),
584
+ geminiApiKey: getArg('gemini-api-key'),
585
+ anthropicApiKey: getArg('anthropic-api-key'),
586
+ deepSeekApiKey: getArg('deepseek-api-key'),
587
+ mistralApiKey: getArg('mistral-api-key'),
588
+ },
589
+ };
590
+
591
+ if (shouldReset) resetProjectDirectory();
592
+
593
+ console.log(`\nCreating project: ${params.projectname} | theme: ${params.theme} | template: ${params.template}\n`);
594
+ createPackageJson(params);
595
+ require('./setup-devtools').setupDevTools({ proxyProvider: params.proxyProvider });
596
+ createIndexHtml(params);
597
+ createEnvFile(params);
598
+ createFirebaseConfig(params);
599
+ createSourceFiles(params);
600
+ console.log(`\nDone. Run: npm install && npm run dev\n`);
601
+ return;
602
+ }
603
+
604
+ askInteractive((params) => {
605
+ if (shouldReset) resetProjectDirectory();
606
+
607
+ console.log(`\nCreating project: ${params.projectname} | theme: ${params.theme} | template: ${params.template}\n`);
608
+ createPackageJson(params);
609
+ require('./setup-devtools').setupDevTools({ proxyProvider: params.proxyProvider });
610
+ createIndexHtml(params);
611
+ createEnvFile(params);
612
+ createFirebaseConfig(params);
613
+ createSourceFiles(params);
614
+ console.log(`\nDone. Run: npm install && npm run dev\n`);
615
+ });
616
+ }
617
+
618
+ module.exports = { scaffoldProject };