@digilogiclabs/create-saas-app 2.12.0 → 2.13.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 (159) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/index.js +1870 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/templates/mobile/base/template/App.tsx +7 -4
  6. package/dist/templates/mobile/base/template/app/checkout.tsx +5 -2
  7. package/dist/templates/mobile/base/template/package.json +2 -2
  8. package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/_layout.tsx +6 -2
  9. package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/billing.tsx +7 -3
  10. package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/index.tsx +5 -2
  11. package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/profile.tsx +7 -2
  12. package/dist/templates/mobile/ui-auth-payments/template/app/_layout.tsx +2 -4
  13. package/dist/templates/mobile/ui-auth-payments/template/app/auth/login.tsx +6 -3
  14. package/dist/templates/mobile/ui-auth-payments/template/app/auth/signup.tsx +6 -3
  15. package/dist/templates/mobile/ui-auth-payments/template/package.json +2 -2
  16. package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/_layout.tsx +6 -2
  17. package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/billing.tsx +7 -3
  18. package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/index.tsx +5 -2
  19. package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/profile.tsx +7 -2
  20. package/dist/templates/mobile/ui-auth-payments-ai/template/app/_layout.tsx +2 -4
  21. package/dist/templates/mobile/ui-auth-payments-ai/template/app/auth/login.tsx +6 -3
  22. package/dist/templates/mobile/ui-auth-payments-ai/template/app/auth/signup.tsx +6 -3
  23. package/dist/templates/mobile/ui-auth-payments-ai/template/package.json +2 -2
  24. package/dist/templates/shared/config/web/next.config.mjs +0 -1
  25. package/dist/templates/web/ai-platform/template/package.json +3 -4
  26. package/dist/templates/web/ai-platform/template/src/app/chat/page.tsx +5 -2
  27. package/dist/templates/web/ai-platform/template/src/app/playground/page.tsx +5 -2
  28. package/dist/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +2 -5
  29. package/dist/templates/web/base/template/package.json +3 -3
  30. package/dist/templates/web/iot-dashboard/template/package.json +3 -4
  31. package/dist/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +2 -5
  32. package/dist/templates/web/marketplace/template/package.json +3 -4
  33. package/dist/templates/web/marketplace/template/src/components/providers/app-providers.tsx +2 -5
  34. package/dist/templates/web/micro-saas/template/package.json +3 -4
  35. package/dist/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +2 -5
  36. package/dist/templates/web/ui-auth/template/package.json +3 -3
  37. package/dist/templates/web/ui-auth-ai/template/package.json +3 -3
  38. package/dist/templates/web/ui-auth-payments/template/package.json +3 -3
  39. package/dist/templates/web/ui-auth-payments-ai/template/package.json +3 -3
  40. package/dist/templates/web/ui-auth-payments-audio/template/package.json +3 -3
  41. package/dist/templates/web/ui-auth-payments-video/template/package.json +3 -3
  42. package/dist/templates/web/ui-only/template/package.json +2 -2
  43. package/dist/templates/web/ui-package-test/template/package.json +3 -3
  44. package/package.json +1 -1
  45. package/src/templates/mobile/base/template/App.tsx +7 -4
  46. package/src/templates/mobile/base/template/app/checkout.tsx +5 -2
  47. package/src/templates/mobile/base/template/package.json +2 -2
  48. package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/_layout.tsx +6 -2
  49. package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/billing.tsx +7 -3
  50. package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/index.tsx +5 -2
  51. package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/profile.tsx +7 -2
  52. package/src/templates/mobile/ui-auth-payments/template/app/_layout.tsx +2 -4
  53. package/src/templates/mobile/ui-auth-payments/template/app/auth/login.tsx +6 -3
  54. package/src/templates/mobile/ui-auth-payments/template/app/auth/signup.tsx +6 -3
  55. package/src/templates/mobile/ui-auth-payments/template/package.json +2 -2
  56. package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/_layout.tsx +6 -2
  57. package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/billing.tsx +7 -3
  58. package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/index.tsx +5 -2
  59. package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/profile.tsx +7 -2
  60. package/src/templates/mobile/ui-auth-payments-ai/template/app/_layout.tsx +2 -4
  61. package/src/templates/mobile/ui-auth-payments-ai/template/app/auth/login.tsx +6 -3
  62. package/src/templates/mobile/ui-auth-payments-ai/template/app/auth/signup.tsx +6 -3
  63. package/src/templates/mobile/ui-auth-payments-ai/template/package.json +2 -2
  64. package/src/templates/shared/config/web/next.config.mjs +0 -1
  65. package/src/templates/web/ai-platform/template/package.json +3 -4
  66. package/src/templates/web/ai-platform/template/src/app/chat/page.tsx +5 -2
  67. package/src/templates/web/ai-platform/template/src/app/playground/page.tsx +5 -2
  68. package/src/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +2 -5
  69. package/src/templates/web/base/template/package.json +3 -3
  70. package/src/templates/web/iot-dashboard/template/package.json +3 -4
  71. package/src/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +2 -5
  72. package/src/templates/web/marketplace/template/package.json +3 -4
  73. package/src/templates/web/marketplace/template/src/components/providers/app-providers.tsx +2 -5
  74. package/src/templates/web/micro-saas/template/package.json +3 -4
  75. package/src/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +2 -5
  76. package/src/templates/web/ui-auth/template/package.json +3 -3
  77. package/src/templates/web/ui-auth-ai/template/package.json +3 -3
  78. package/src/templates/web/ui-auth-payments/template/package.json +3 -3
  79. package/src/templates/web/ui-auth-payments-ai/template/package.json +3 -3
  80. package/src/templates/web/ui-auth-payments-audio/template/package.json +3 -3
  81. package/src/templates/web/ui-auth-payments-video/template/package.json +3 -3
  82. package/src/templates/web/ui-only/template/package.json +2 -2
  83. package/src/templates/web/ui-package-test/template/package.json +3 -3
  84. package/dist/cli/commands/add.d.ts +0 -6
  85. package/dist/cli/commands/add.d.ts.map +0 -1
  86. package/dist/cli/commands/add.js +0 -39
  87. package/dist/cli/commands/add.js.map +0 -1
  88. package/dist/cli/commands/create.d.ts +0 -49
  89. package/dist/cli/commands/create.d.ts.map +0 -1
  90. package/dist/cli/commands/create.js +0 -173
  91. package/dist/cli/commands/create.js.map +0 -1
  92. package/dist/cli/commands/index.d.ts +0 -4
  93. package/dist/cli/commands/index.d.ts.map +0 -1
  94. package/dist/cli/commands/index.js +0 -20
  95. package/dist/cli/commands/index.js.map +0 -1
  96. package/dist/cli/commands/update.d.ts +0 -6
  97. package/dist/cli/commands/update.d.ts.map +0 -1
  98. package/dist/cli/commands/update.js +0 -68
  99. package/dist/cli/commands/update.js.map +0 -1
  100. package/dist/cli/index.d.ts +0 -4
  101. package/dist/cli/index.d.ts.map +0 -1
  102. package/dist/cli/index.js +0 -63
  103. package/dist/cli/index.js.map +0 -1
  104. package/dist/cli/prompts/index.d.ts +0 -2
  105. package/dist/cli/prompts/index.d.ts.map +0 -1
  106. package/dist/cli/prompts/index.js +0 -18
  107. package/dist/cli/prompts/index.js.map +0 -1
  108. package/dist/cli/prompts/project-setup.d.ts +0 -5
  109. package/dist/cli/prompts/project-setup.d.ts.map +0 -1
  110. package/dist/cli/prompts/project-setup.js +0 -359
  111. package/dist/cli/prompts/project-setup.js.map +0 -1
  112. package/dist/cli/utils/git.d.ts +0 -9
  113. package/dist/cli/utils/git.d.ts.map +0 -1
  114. package/dist/cli/utils/git.js +0 -77
  115. package/dist/cli/utils/git.js.map +0 -1
  116. package/dist/cli/utils/index.d.ts +0 -5
  117. package/dist/cli/utils/index.d.ts.map +0 -1
  118. package/dist/cli/utils/index.js +0 -21
  119. package/dist/cli/utils/index.js.map +0 -1
  120. package/dist/cli/utils/logger.d.ts +0 -16
  121. package/dist/cli/utils/logger.d.ts.map +0 -1
  122. package/dist/cli/utils/logger.js +0 -55
  123. package/dist/cli/utils/logger.js.map +0 -1
  124. package/dist/cli/utils/package-manager.d.ts +0 -8
  125. package/dist/cli/utils/package-manager.d.ts.map +0 -1
  126. package/dist/cli/utils/package-manager.js +0 -92
  127. package/dist/cli/utils/package-manager.js.map +0 -1
  128. package/dist/cli/utils/spinner.d.ts +0 -7
  129. package/dist/cli/utils/spinner.d.ts.map +0 -1
  130. package/dist/cli/utils/spinner.js +0 -48
  131. package/dist/cli/utils/spinner.js.map +0 -1
  132. package/dist/cli/validators/dependencies.d.ts +0 -15
  133. package/dist/cli/validators/dependencies.d.ts.map +0 -1
  134. package/dist/cli/validators/dependencies.js +0 -108
  135. package/dist/cli/validators/dependencies.js.map +0 -1
  136. package/dist/cli/validators/index.d.ts +0 -3
  137. package/dist/cli/validators/index.d.ts.map +0 -1
  138. package/dist/cli/validators/index.js +0 -19
  139. package/dist/cli/validators/index.js.map +0 -1
  140. package/dist/cli/validators/project-name.d.ts +0 -5
  141. package/dist/cli/validators/project-name.d.ts.map +0 -1
  142. package/dist/cli/validators/project-name.js +0 -151
  143. package/dist/cli/validators/project-name.js.map +0 -1
  144. package/dist/generators/file-processor.d.ts +0 -28
  145. package/dist/generators/file-processor.d.ts.map +0 -1
  146. package/dist/generators/file-processor.js +0 -224
  147. package/dist/generators/file-processor.js.map +0 -1
  148. package/dist/generators/index.d.ts +0 -4
  149. package/dist/generators/index.d.ts.map +0 -1
  150. package/dist/generators/index.js +0 -20
  151. package/dist/generators/index.js.map +0 -1
  152. package/dist/generators/package-installer.d.ts +0 -29
  153. package/dist/generators/package-installer.d.ts.map +0 -1
  154. package/dist/generators/package-installer.js +0 -177
  155. package/dist/generators/package-installer.js.map +0 -1
  156. package/dist/generators/template-generator.d.ts +0 -86
  157. package/dist/generators/template-generator.d.ts.map +0 -1
  158. package/dist/generators/template-generator.js +0 -943
  159. package/dist/generators/template-generator.js.map +0 -1
@@ -1,943 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TemplateGenerator = void 0;
7
- const path_1 = __importDefault(require("path"));
8
- const fs_extra_1 = __importDefault(require("fs-extra"));
9
- const mustache_1 = __importDefault(require("mustache"));
10
- const glob_1 = require("glob");
11
- const logger_1 = require("../cli/utils/logger");
12
- /**
13
- * Tier-based feature configuration
14
- * Determines which DLL packages and features are included per tier
15
- */
16
- const TIER_FEATURES = {
17
- micro: {
18
- platformCore: true,
19
- appSdk: true,
20
- auth: true,
21
- payments: false,
22
- ai: false,
23
- observability: false,
24
- workers: false,
25
- beta: true,
26
- },
27
- starter: {
28
- platformCore: true,
29
- appSdk: true,
30
- auth: true,
31
- payments: true,
32
- ai: false,
33
- observability: false,
34
- workers: false,
35
- beta: true,
36
- },
37
- pro: {
38
- platformCore: true,
39
- appSdk: true,
40
- auth: true,
41
- payments: true,
42
- ai: true,
43
- observability: true,
44
- workers: false,
45
- beta: true,
46
- },
47
- enterprise: {
48
- platformCore: true,
49
- appSdk: true,
50
- auth: true,
51
- payments: true,
52
- ai: true,
53
- observability: true,
54
- workers: true,
55
- beta: true,
56
- },
57
- };
58
- class TemplateGenerator {
59
- constructor(config) {
60
- this.config = config;
61
- // Always use the templates directory relative to __dirname
62
- // In the built package, templates are copied to dist/templates
63
- // __dirname is dist/generators, so we need to go up one level
64
- this.templatesDir = path_1.default.join(__dirname, '..', 'templates');
65
- this.context = this.createTemplateContext();
66
- }
67
- createTemplateContext() {
68
- const projectName = this.config.name;
69
- const packageName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
70
- const className = this.toPascalCase(projectName);
71
- const camelCaseName = this.toCamelCase(projectName);
72
- const kebabCaseName = packageName;
73
- const titleCaseName = this.toTitleCase(projectName);
74
- const slugNameCamelCase = this.toCamelCase(packageName);
75
- // Get tier features
76
- const tier = this.config.tier || 'starter';
77
- const features = TIER_FEATURES[tier];
78
- const dependencies = this.getDependencies();
79
- const generatedDate = new Date().toLocaleDateString('en-US', {
80
- year: 'numeric',
81
- month: 'short',
82
- day: 'numeric',
83
- });
84
- // Determine tier description for template
85
- const tierDescriptions = {
86
- micro: 'minimal',
87
- starter: 'standard',
88
- pro: 'AI-powered',
89
- enterprise: 'enterprise-grade',
90
- };
91
- return {
92
- projectName,
93
- platform: this.config.platform,
94
- template: this.config.template,
95
- tier,
96
- auth: this.config.auth,
97
- database: this.config.database,
98
- theme: this.config.theme,
99
- themeColor: this.config.themeColor,
100
- defaultTheme: this.config.defaultTheme,
101
- landingStyle: this.config.landingStyle || 'product',
102
- motion: this.config.motion || 'standard',
103
- ai: {
104
- enabled: this.config.ai.enabled || features.ai,
105
- capabilities: this.config.ai.capabilities,
106
- provider: this.config.ai.provider,
107
- hasText: this.config.ai.capabilities.includes('text'),
108
- hasAudio: this.config.ai.capabilities.includes('audio'),
109
- hasVideo: this.config.ai.capabilities.includes('video'),
110
- },
111
- features,
112
- packageName,
113
- className,
114
- camelCaseName,
115
- kebabCaseName,
116
- titleCaseName,
117
- slugNameCamelCase,
118
- description: `A ${tierDescriptions[tier]} SaaS application built with DLL Platform`,
119
- author: 'Digi Logic Labs',
120
- year: new Date().getFullYear(),
121
- generatedDate,
122
- generatorVersion: '2.0.0',
123
- // DLL package versions
124
- platformCoreVersion: dependencies['@digilogiclabs/platform-core']?.replace('^', '') || '1.14.0',
125
- appSdkVersion: dependencies['@digilogiclabs/app-sdk']?.replace('^', '') || '1.0.5',
126
- uiVersion: dependencies['@digilogiclabs/saas-factory-ui']?.replace('^', '') || '1.6.0',
127
- authVersion: '5.1.0', // deprecated — kept for backward-compatible mustache templates
128
- paymentsVersion: '5.1.0', // deprecated — kept for backward-compatible mustache templates
129
- aiVersion: dependencies['@digilogiclabs/saas-factory-ai']?.replace('^', '') || '8.0.0',
130
- dependencies,
131
- devDependencies: this.getDevDependencies(),
132
- scripts: this.getScripts(),
133
- };
134
- }
135
- getDependencies() {
136
- const baseDeps = {};
137
- const tier = this.config.tier || 'starter';
138
- const features = TIER_FEATURES[tier];
139
- // ═══════════════════════════════════════════════════════════════
140
- // DLL PLATFORM CORE PACKAGES (tier-based)
141
- // ═══════════════════════════════════════════════════════════════
142
- // Platform Core - Always included for all tiers (infrastructure abstraction)
143
- if (features.platformCore) {
144
- baseDeps['@digilogiclabs/platform-core'] = '^1.14.0';
145
- }
146
- // App SDK - Always included for React apps (unified hooks)
147
- if (features.appSdk && (this.config.platform === 'web' || this.config.platform === 'both')) {
148
- baseDeps['@digilogiclabs/app-sdk'] = '^1.1.0';
149
- }
150
- // UI Components - included for most templates
151
- if ([
152
- 'base',
153
- 'ui-only',
154
- 'ui-auth',
155
- 'ui-auth-ai',
156
- 'ui-auth-payments',
157
- 'ui-auth-payments-audio',
158
- 'ui-auth-payments-video',
159
- 'ui-auth-payments-ai',
160
- // Vertical templates
161
- 'micro-saas',
162
- 'marketplace',
163
- 'ai-platform',
164
- 'iot-dashboard',
165
- ].includes(this.config.template)) {
166
- baseDeps['@digilogiclabs/saas-factory-ui'] = '^1.8.1';
167
- }
168
- // AI packages (tier: pro+ or AI-focused templates)
169
- if (features.ai ||
170
- this.config.ai.enabled ||
171
- this.config.template.includes('-ai') ||
172
- this.config.template === 'ai-platform') {
173
- baseDeps['@digilogiclabs/saas-factory-ai'] = '^8.0.1';
174
- baseDeps['@digilogiclabs/saas-factory-ai-types'] = '^1.0.0';
175
- // Add AI SDK for ai-platform template
176
- if (this.config.template === 'ai-platform') {
177
- baseDeps['ai'] = '^6.0.0';
178
- baseDeps['@ai-sdk/openai'] = '^3.0.0';
179
- baseDeps['@ai-sdk/anthropic'] = '^3.0.0';
180
- }
181
- }
182
- // IoT Dashboard specific dependencies
183
- if (this.config.template === 'iot-dashboard') {
184
- baseDeps['recharts'] = '^2.14.0';
185
- baseDeps['date-fns'] = '^4.1.0';
186
- }
187
- // Marketplace specific dependencies
188
- if (this.config.template === 'marketplace') {
189
- baseDeps['stripe'] = '^17.4.0';
190
- }
191
- // ═══════════════════════════════════════════════════════════════
192
- // PLATFORM DEPENDENCIES (web/mobile)
193
- // ═══════════════════════════════════════════════════════════════
194
- if (this.config.platform === 'web' || this.config.platform === 'both') {
195
- Object.assign(baseDeps, {
196
- next: '^15.5.0',
197
- react: '^19.0.0',
198
- 'react-dom': '^19.0.0',
199
- tailwindcss: '^4.1.0',
200
- '@tailwindcss/postcss': '^4.1.0',
201
- typescript: '^5.8.0',
202
- clsx: '^2.1.0',
203
- 'class-variance-authority': '^0.7.0',
204
- 'tailwind-merge': '^2.6.0',
205
- 'next-themes': '^0.4.0',
206
- 'lucide-react': '^0.460.0',
207
- zod: '^4.1.0',
208
- });
209
- }
210
- if (this.config.platform === 'mobile' || this.config.platform === 'both') {
211
- Object.assign(baseDeps, {
212
- expo: '~52.0.0',
213
- 'react-native': '0.76.0',
214
- '@expo/vector-icons': '^14.0.0',
215
- '@react-navigation/native': '^7.0.0',
216
- '@react-navigation/bottom-tabs': '^7.0.0',
217
- 'react-native-screens': '~4.0.0',
218
- 'react-native-safe-area-context': '4.12.0',
219
- 'react-native-gesture-handler': '~2.20.0',
220
- 'expo-router': '~4.0.0',
221
- });
222
- // Stripe for mobile if payments enabled
223
- if (features.payments) {
224
- baseDeps['@stripe/stripe-react-native'] = '^0.39.0';
225
- }
226
- }
227
- // ═══════════════════════════════════════════════════════════════
228
- // AUTH PROVIDER SDKS
229
- // ═══════════════════════════════════════════════════════════════
230
- if (this.config.auth === 'firebase') {
231
- baseDeps['firebase'] = '^11.0.0';
232
- }
233
- else if (this.config.auth === 'supabase') {
234
- baseDeps['@supabase/supabase-js'] = '^2.46.0';
235
- if (this.config.platform === 'web' || this.config.platform === 'both') {
236
- baseDeps['@supabase/ssr'] = '^0.5.0';
237
- }
238
- }
239
- else if (this.config.auth === 'keycloak') {
240
- if (this.config.platform === 'web' || this.config.platform === 'both') {
241
- baseDeps['next-auth'] = '^5.0.0-beta.30';
242
- }
243
- }
244
- // ═══════════════════════════════════════════════════════════════
245
- // INFRASTRUCTURE DEPENDENCIES
246
- // ═══════════════════════════════════════════════════════════════
247
- // Redis (starter+ tiers — rate limiting, caching)
248
- if (features.payments || features.ai || features.observability) {
249
- baseDeps['ioredis'] = '^5.6.0';
250
- }
251
- // Database (PostgreSQL + Drizzle)
252
- if (this.config.database === 'postgresql') {
253
- baseDeps['drizzle-orm'] = '^0.43.0';
254
- baseDeps['postgres'] = '^3.4.0';
255
- baseDeps['dotenv'] = '^16.4.0';
256
- }
257
- // Stripe (starter+ tiers — payments)
258
- if (features.payments) {
259
- baseDeps['stripe'] = '^17.4.0';
260
- if (this.config.platform === 'web' || this.config.platform === 'both') {
261
- baseDeps['@stripe/stripe-js'] = '^8.0.0';
262
- baseDeps['@stripe/react-stripe-js'] = '^5.6.0';
263
- }
264
- }
265
- // Resend (starter+ tiers — transactional email)
266
- if (features.payments) {
267
- baseDeps['resend'] = '^4.0.0';
268
- }
269
- // Server-only guard
270
- baseDeps['server-only'] = '^0.0.1';
271
- return baseDeps;
272
- }
273
- getDevDependencies() {
274
- const baseDeps = {
275
- '@types/node': '^22.0.0',
276
- eslint: '^9.0.0',
277
- prettier: '^3.4.0',
278
- typescript: '^5.7.0',
279
- };
280
- if (this.config.platform === 'web' || this.config.platform === 'both') {
281
- Object.assign(baseDeps, {
282
- '@types/react': '^19.0.0',
283
- '@types/react-dom': '^19.0.0',
284
- '@eslint/eslintrc': '^3.0.0',
285
- 'eslint-config-next': '^15.0.0',
286
- });
287
- }
288
- // Drizzle Kit for PostgreSQL
289
- if (this.config.database === 'postgresql') {
290
- baseDeps['drizzle-kit'] = '^0.30.0';
291
- }
292
- // Add testing dependencies for pro+ tiers
293
- const tier = this.config.tier || 'starter';
294
- if (['pro', 'enterprise'].includes(tier)) {
295
- Object.assign(baseDeps, {
296
- vitest: '^2.1.0',
297
- '@testing-library/react': '^16.0.0',
298
- '@testing-library/jest-dom': '^6.6.0',
299
- });
300
- }
301
- return baseDeps;
302
- }
303
- getScripts() {
304
- const scripts = {};
305
- if (this.config.platform === 'web' || this.config.platform === 'both') {
306
- Object.assign(scripts, {
307
- dev: 'next dev',
308
- build: 'next build',
309
- start: 'next start',
310
- lint: 'next lint',
311
- });
312
- }
313
- if (this.config.platform === 'mobile' || this.config.platform === 'both') {
314
- Object.assign(scripts, {
315
- start: 'expo start',
316
- android: 'expo start --android',
317
- ios: 'expo start --ios',
318
- web: 'expo start --web',
319
- });
320
- }
321
- return scripts;
322
- }
323
- async generate(outputPath) {
324
- try {
325
- // Ensure output directory exists
326
- await fs_extra_1.default.ensureDir(outputPath);
327
- // Generate based on platform
328
- if (this.config.platform === 'web') {
329
- await this.generateWebProject(outputPath);
330
- }
331
- else if (this.config.platform === 'mobile') {
332
- await this.generateMobileProject(outputPath);
333
- }
334
- else if (this.config.platform === 'both') {
335
- await this.generateWebProject(outputPath);
336
- await this.generateMobileProject(path_1.default.join(outputPath, 'mobile'));
337
- }
338
- // Copy shared resources
339
- await this.copySharedResources(outputPath);
340
- // Patch package.json with aligned dependency versions
341
- await this.patchPackageJson(outputPath);
342
- logger_1.logger.debug('Template generation completed');
343
- }
344
- catch (error) {
345
- logger_1.logger.error('Template generation failed:', error);
346
- throw error;
347
- }
348
- }
349
- async generateWebProject(outputPath) {
350
- const templatePath = path_1.default.join(this.templatesDir, 'web', this.config.template);
351
- await this.copyTemplate(templatePath, outputPath);
352
- }
353
- async generateMobileProject(outputPath) {
354
- // Map web templates to mobile templates where applicable
355
- let mobileTemplate = this.config.template;
356
- // If we don't have a mobile version, try to find the closest match
357
- if (!(await fs_extra_1.default.pathExists(path_1.default.join(this.templatesDir, 'mobile', mobileTemplate)))) {
358
- // Map web templates to available mobile templates
359
- if (mobileTemplate.includes('-ai') || mobileTemplate === 'ui-auth-payments-ai') {
360
- mobileTemplate = 'ui-auth-payments-ai';
361
- }
362
- else if (mobileTemplate.startsWith('ui-auth-payments')) {
363
- mobileTemplate = 'ui-auth-payments';
364
- }
365
- else if (mobileTemplate.startsWith('ui-auth')) {
366
- // Use the ui-auth-payments template for other ui-auth variants
367
- mobileTemplate = 'ui-auth-payments';
368
- }
369
- else if (mobileTemplate.startsWith('ui-')) {
370
- mobileTemplate = 'base';
371
- }
372
- }
373
- const templatePath = path_1.default.join(this.templatesDir, 'mobile', mobileTemplate);
374
- await this.copyTemplate(templatePath, outputPath);
375
- // Create assets directory and basic assets for mobile projects
376
- await this.createMobileAssets(outputPath);
377
- }
378
- async copyTemplate(templatePath, outputPath) {
379
- const templateDir = path_1.default.join(templatePath, 'template');
380
- if (!(await fs_extra_1.default.pathExists(templateDir))) {
381
- throw new Error(`Template not found: ${templateDir}`);
382
- }
383
- await this.copyAndProcessFiles(templateDir, outputPath);
384
- }
385
- async copyAndProcessFiles(sourceDir, targetDir) {
386
- if (!(await fs_extra_1.default.pathExists(sourceDir))) {
387
- logger_1.logger.debug(`Template source directory not found: ${sourceDir}`);
388
- return;
389
- }
390
- const files = await (0, glob_1.glob)('**/*', {
391
- cwd: sourceDir,
392
- dot: true,
393
- nodir: true,
394
- });
395
- for (const file of files) {
396
- const sourcePath = path_1.default.join(sourceDir, file);
397
- const targetPath = path_1.default.join(targetDir, file);
398
- await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
399
- const content = await fs_extra_1.default.readFile(sourcePath, 'utf-8');
400
- if (this.isTextFile(file)) {
401
- // Check if file contains mustache variables before processing
402
- const hasMustacheVars = content.includes('{{') && content.includes('}}');
403
- const ext = path_1.default.extname(file).toLowerCase();
404
- // For JSX/TSX files without mustache variables, skip processing to preserve template literals
405
- if (['.tsx', '.jsx'].includes(ext) && !hasMustacheVars) {
406
- await fs_extra_1.default.writeFile(targetPath, content);
407
- }
408
- else if (hasMustacheVars) {
409
- // Only process files that actually have mustache variables
410
- // For JSX/TSX files, we need to protect template literals during mustache processing
411
- if (['.tsx', '.jsx'].includes(ext)) {
412
- // Step 1: Replace template literals and JSX object literals with placeholders
413
- const templateLiterals = [];
414
- let protectedContent = content.replace(/\$\{([^}]+)\}/g, (match, _variable) => {
415
- templateLiterals.push(match);
416
- return `__TEMPLATE_LITERAL_${templateLiterals.length - 1}__`;
417
- });
418
- // Step 1.5: Protect JSX object literals {{ }} from mustache processing
419
- const jsxObjectLiterals = [];
420
- protectedContent = this.protectJSXObjectLiterals(protectedContent, jsxObjectLiterals);
421
- // Step 2: Process mustache variables
422
- const processedContent = mustache_1.default.render(protectedContent, this.context);
423
- // Step 3: Restore template literals and JSX object literals
424
- let finalContent = processedContent.replace(/__TEMPLATE_LITERAL_(\d+)__/g, (match, index) => {
425
- return templateLiterals[parseInt(index)];
426
- });
427
- // Step 4: Restore JSX object literals
428
- finalContent = finalContent.replace(/__JSX_OBJECT_(\d+)__/g, (match, index) => {
429
- return jsxObjectLiterals[parseInt(index)];
430
- });
431
- await fs_extra_1.default.writeFile(targetPath, finalContent);
432
- }
433
- else {
434
- // For non-JSX files, process normally
435
- const processedContent = mustache_1.default.render(content, this.context);
436
- await fs_extra_1.default.writeFile(targetPath, processedContent);
437
- }
438
- }
439
- else {
440
- // Copy other text files directly
441
- await fs_extra_1.default.writeFile(targetPath, content);
442
- }
443
- }
444
- else {
445
- await fs_extra_1.default.copy(sourcePath, targetPath);
446
- }
447
- }
448
- }
449
- async copySharedResources(outputPath) {
450
- // Skip copying shared resources for minimal/test templates
451
- if (this.config.template === 'ui-only' || this.config.template === 'ui-package-test') {
452
- logger_1.logger.debug('Skipping shared resources for minimal template');
453
- return;
454
- }
455
- const sharedPath = path_1.default.join(this.templatesDir, 'shared');
456
- const isWeb = this.config.platform === 'web' || this.config.platform === 'both';
457
- const tier = this.config.tier || 'starter';
458
- const features = TIER_FEATURES[tier];
459
- // ═══════════════════════════════════════════════════════════════
460
- // AUTH — provider-specific config (Keycloak: auth.config.ts + auth.ts + federated-logout)
461
- // ═══════════════════════════════════════════════════════════════
462
- if (isWeb) {
463
- const authPath = path_1.default.join(sharedPath, 'auth', this.config.auth, 'web');
464
- await this.copyAndProcessFiles(authPath, outputPath);
465
- }
466
- // ═══════════════════════════════════════════════════════════════
467
- // MOCK — mock mode utilities (mock user, NEXT_PUBLIC_MOCK_MODE)
468
- // ═══════════════════════════════════════════════════════════════
469
- if (isWeb) {
470
- const mockPath = path_1.default.join(sharedPath, 'mock', 'web');
471
- if (await fs_extra_1.default.pathExists(mockPath)) {
472
- await this.copyAndProcessFiles(mockPath, outputPath);
473
- logger_1.logger.debug('Copied mock mode utilities');
474
- }
475
- }
476
- // ═══════════════════════════════════════════════════════════════
477
- // DATABASE — provider-specific setup (PostgreSQL/Drizzle or Supabase)
478
- // ═══════════════════════════════════════════════════════════════
479
- if (isWeb) {
480
- const dbPath = path_1.default.join(sharedPath, 'database', this.config.database, 'web');
481
- await this.copyAndProcessFiles(dbPath, outputPath);
482
- }
483
- // ═══════════════════════════════════════════════════════════════
484
- // THEME — theme-specific configuration
485
- // ═══════════════════════════════════════════════════════════════
486
- const themePath = path_1.default.join(sharedPath, 'themes', this.config.theme);
487
- await this.copyAndProcessFiles(themePath, outputPath);
488
- // ═══════════════════════════════════════════════════════════════
489
- // DESIGN — design.config.ts (design system foundation)
490
- // ═══════════════════════════════════════════════════════════════
491
- if (isWeb) {
492
- const designPath = path_1.default.join(sharedPath, 'design', 'web');
493
- if (await fs_extra_1.default.pathExists(designPath)) {
494
- await this.copyAndProcessFiles(designPath, outputPath);
495
- logger_1.logger.debug('Copied design config template');
496
- }
497
- }
498
- // ═══════════════════════════════════════════════════════════════
499
- // QUALITY — Lighthouse CI + axe-core accessibility test stubs (pro+ only — needs testing deps)
500
- // ═══════════════════════════════════════════════════════════════
501
- if (isWeb && ['pro', 'enterprise'].includes(tier)) {
502
- const qualityPath = path_1.default.join(sharedPath, 'quality', 'web');
503
- if (await fs_extra_1.default.pathExists(qualityPath)) {
504
- await this.copyAndProcessFiles(qualityPath, outputPath);
505
- logger_1.logger.debug('Copied quality tooling stubs');
506
- }
507
- }
508
- // ═══════════════════════════════════════════════════════════════
509
- // LANDING — shared landing page components (LandingPage, PricingSection)
510
- // ═══════════════════════════════════════════════════════════════
511
- if (isWeb && features.payments) {
512
- const landingPath = path_1.default.join(sharedPath, 'landing', 'web');
513
- if (await fs_extra_1.default.pathExists(landingPath)) {
514
- await this.copyAndProcessFiles(landingPath, path_1.default.join(outputPath));
515
- logger_1.logger.debug('Copied landing page components');
516
- }
517
- }
518
- // ═══════════════════════════════════════════════════════════════
519
- // BETA GATE — all tiers (API routes + client wrapper)
520
- // ═══════════════════════════════════════════════════════════════
521
- if (features.beta && isWeb) {
522
- const betaPath = path_1.default.join(sharedPath, 'beta', 'web');
523
- if (await fs_extra_1.default.pathExists(betaPath)) {
524
- await this.copyAndProcessFiles(betaPath, outputPath);
525
- logger_1.logger.debug('Copied beta gate templates');
526
- }
527
- }
528
- // ═══════════════════════════════════════════════════════════════
529
- // ENVIRONMENT CONFIG — centralized env validation (all tiers)
530
- // ═══════════════════════════════════════════════════════════════
531
- if (isWeb) {
532
- const configPath = path_1.default.join(sharedPath, 'config', 'web');
533
- if (await fs_extra_1.default.pathExists(configPath)) {
534
- await this.copyAndProcessFiles(configPath, outputPath);
535
- logger_1.logger.debug('Copied environment config template');
536
- }
537
- }
538
- // ═══════════════════════════════════════════════════════════════
539
- // REDIS — client + rate limit store (starter+ tiers)
540
- // ═══════════════════════════════════════════════════════════════
541
- if (isWeb && (features.payments || features.ai || features.observability)) {
542
- const redisPath = path_1.default.join(sharedPath, 'redis', 'web');
543
- if (await fs_extra_1.default.pathExists(redisPath)) {
544
- await this.copyAndProcessFiles(redisPath, outputPath);
545
- logger_1.logger.debug('Copied Redis client template');
546
- }
547
- }
548
- // ═══════════════════════════════════════════════════════════════
549
- // API SECURITY — wrapper + rate limiting (all tiers)
550
- // ═══════════════════════════════════════════════════════════════
551
- if (isWeb) {
552
- const securityPath = path_1.default.join(sharedPath, 'security', 'web');
553
- if (await fs_extra_1.default.pathExists(securityPath)) {
554
- await this.copyAndProcessFiles(securityPath, outputPath);
555
- logger_1.logger.debug('Copied API security wrapper template');
556
- }
557
- }
558
- // ═══════════════════════════════════════════════════════════════
559
- // MIDDLEWARE — route protection + auth (all tiers)
560
- // ═══════════════════════════════════════════════════════════════
561
- if (isWeb && this.config.auth === 'keycloak') {
562
- const middlewarePath = path_1.default.join(sharedPath, 'middleware', 'web');
563
- if (await fs_extra_1.default.pathExists(middlewarePath)) {
564
- await this.copyAndProcessFiles(middlewarePath, outputPath);
565
- logger_1.logger.debug('Copied middleware template');
566
- }
567
- }
568
- // ═══════════════════════════════════════════════════════════════
569
- // HEALTH CHECK — container readiness (all tiers)
570
- // ═══════════════════════════════════════════════════════════════
571
- if (isWeb) {
572
- const healthPath = path_1.default.join(sharedPath, 'health', 'web');
573
- if (await fs_extra_1.default.pathExists(healthPath)) {
574
- await this.copyAndProcessFiles(healthPath, outputPath);
575
- logger_1.logger.debug('Copied health check endpoint template');
576
- }
577
- }
578
- // ═══════════════════════════════════════════════════════════════
579
- // STRIPE WEBHOOKS — payment lifecycle (starter+ tiers)
580
- // ═══════════════════════════════════════════════════════════════
581
- if (isWeb && features.payments) {
582
- const paymentsPath = path_1.default.join(sharedPath, 'payments', 'web');
583
- if (await fs_extra_1.default.pathExists(paymentsPath)) {
584
- await this.copyAndProcessFiles(paymentsPath, outputPath);
585
- logger_1.logger.debug('Copied Stripe webhook handler template');
586
- }
587
- }
588
- // ═══════════════════════════════════════════════════════════════
589
- // AUDIT LOGGING — admin visibility (all authenticated apps)
590
- // ═══════════════════════════════════════════════════════════════
591
- if (isWeb && features.auth) {
592
- const auditPath = path_1.default.join(sharedPath, 'audit', 'web');
593
- if (await fs_extra_1.default.pathExists(auditPath)) {
594
- await this.copyAndProcessFiles(auditPath, outputPath);
595
- logger_1.logger.debug('Copied audit logging template');
596
- }
597
- }
598
- // ═══════════════════════════════════════════════════════════════
599
- // EMAIL BRANDING — transactional emails (all authenticated apps)
600
- // ═══════════════════════════════════════════════════════════════
601
- if (isWeb && features.auth) {
602
- const emailPath = path_1.default.join(sharedPath, 'email', 'web');
603
- if (await fs_extra_1.default.pathExists(emailPath)) {
604
- await this.copyAndProcessFiles(emailPath, outputPath);
605
- logger_1.logger.debug('Copied email branding template');
606
- }
607
- }
608
- // ═══════════════════════════════════════════════════════════════
609
- // PLATFORM-CORE INIT — singleton adapter wiring (all tiers)
610
- // ═══════════════════════════════════════════════════════════════
611
- if (isWeb) {
612
- const platformPath = path_1.default.join(sharedPath, 'platform', 'web');
613
- if (await fs_extra_1.default.pathExists(platformPath)) {
614
- await this.copyAndProcessFiles(platformPath, outputPath);
615
- logger_1.logger.debug('Copied platform-core initialization template');
616
- }
617
- }
618
- // ═══════════════════════════════════════════════════════════════
619
- // LEGAL PAGES — Terms of Service + Privacy Policy (all tiers)
620
- // ═══════════════════════════════════════════════════════════════
621
- if (isWeb) {
622
- const legalPath = path_1.default.join(sharedPath, 'legal', 'web');
623
- if (await fs_extra_1.default.pathExists(legalPath)) {
624
- await this.copyAndProcessFiles(legalPath, outputPath);
625
- logger_1.logger.debug('Copied legal page templates (terms, privacy)');
626
- }
627
- }
628
- // ═══════════════════════════════════════════════════════════════
629
- // ERROR PAGES — 404, error boundary, global error (all tiers)
630
- // ═══════════════════════════════════════════════════════════════
631
- if (isWeb) {
632
- const errorPagesPath = path_1.default.join(sharedPath, 'error-pages', 'web');
633
- if (await fs_extra_1.default.pathExists(errorPagesPath)) {
634
- await this.copyAndProcessFiles(errorPagesPath, outputPath);
635
- logger_1.logger.debug('Copied error page templates (not-found, error, global-error)');
636
- }
637
- }
638
- // ═══════════════════════════════════════════════════════════════
639
- // SEO — sitemap.ts + robots.ts (all tiers)
640
- // ═══════════════════════════════════════════════════════════════
641
- if (isWeb) {
642
- const seoPath = path_1.default.join(sharedPath, 'seo', 'web');
643
- if (await fs_extra_1.default.pathExists(seoPath)) {
644
- await this.copyAndProcessFiles(seoPath, outputPath);
645
- logger_1.logger.debug('Copied SEO templates (sitemap, robots)');
646
- }
647
- }
648
- // ═══════════════════════════════════════════════════════════════
649
- // UTILS — common utility functions + API response helpers (all tiers)
650
- // ═══════════════════════════════════════════════════════════════
651
- if (isWeb) {
652
- const utilsPath = path_1.default.join(sharedPath, 'utils', 'web');
653
- if (await fs_extra_1.default.pathExists(utilsPath)) {
654
- await this.copyAndProcessFiles(utilsPath, outputPath);
655
- logger_1.logger.debug('Copied utility templates (utils, api-response)');
656
- }
657
- }
658
- // ═══════════════════════════════════════════════════════════════
659
- // COOKIE CONSENT — GDPR compliance banner (all tiers)
660
- // ═══════════════════════════════════════════════════════════════
661
- if (isWeb) {
662
- const cookiePath = path_1.default.join(sharedPath, 'cookie-consent', 'web');
663
- if (await fs_extra_1.default.pathExists(cookiePath)) {
664
- await this.copyAndProcessFiles(cookiePath, outputPath);
665
- logger_1.logger.debug('Copied cookie consent component template');
666
- }
667
- }
668
- // ═══════════════════════════════════════════════════════════════
669
- // OBSERVABILITY — audit + error reporting helpers (all tiers)
670
- // ═══════════════════════════════════════════════════════════════
671
- if (isWeb) {
672
- const obsPath = path_1.default.join(sharedPath, 'observability', 'web');
673
- if (await fs_extra_1.default.pathExists(obsPath)) {
674
- await this.copyAndProcessFiles(obsPath, outputPath);
675
- logger_1.logger.debug('Copied observability template');
676
- }
677
- }
678
- // ═══════════════════════════════════════════════════════════════
679
- // CACHE — TTL presets + HTTP cache headers (all tiers)
680
- // ═══════════════════════════════════════════════════════════════
681
- if (isWeb) {
682
- const cachePath = path_1.default.join(sharedPath, 'cache', 'web');
683
- if (await fs_extra_1.default.pathExists(cachePath)) {
684
- await this.copyAndProcessFiles(cachePath, outputPath);
685
- logger_1.logger.debug('Copied cache utility template');
686
- }
687
- }
688
- // ═══════════════════════════════════════════════════════════════
689
- // LOADING — skeleton components + default loading.tsx (all tiers)
690
- // ═══════════════════════════════════════════════════════════════
691
- if (isWeb) {
692
- const loadingPath = path_1.default.join(sharedPath, 'loading', 'web');
693
- if (await fs_extra_1.default.pathExists(loadingPath)) {
694
- await this.copyAndProcessFiles(loadingPath, outputPath);
695
- logger_1.logger.debug('Copied loading skeleton templates');
696
- }
697
- }
698
- // ═══════════════════════════════════════════════════════════════
699
- // ADMIN — layout + nav component (keycloak auth only)
700
- // ═══════════════════════════════════════════════════════════════
701
- if (isWeb && this.config.auth === 'keycloak') {
702
- const adminPath = path_1.default.join(sharedPath, 'admin', 'web');
703
- if (await fs_extra_1.default.pathExists(adminPath)) {
704
- await this.copyAndProcessFiles(adminPath, outputPath);
705
- logger_1.logger.debug('Copied admin layout + nav templates');
706
- }
707
- }
708
- // ═══════════════════════════════════════════════════════════════
709
- // CONTACT FORM — page + API route with Zod validation & email (all authenticated apps)
710
- // ═══════════════════════════════════════════════════════════════
711
- if (isWeb && features.auth) {
712
- const contactPath = path_1.default.join(sharedPath, 'contact', 'web');
713
- if (await fs_extra_1.default.pathExists(contactPath)) {
714
- await this.copyAndProcessFiles(contactPath, outputPath);
715
- logger_1.logger.debug('Copied contact form templates (page + API route)');
716
- }
717
- }
718
- // ═══════════════════════════════════════════════════════════════
719
- // .env.example — dynamic env var reference
720
- // ═══════════════════════════════════════════════════════════════
721
- if (isWeb) {
722
- const envExample = this.generateEnvExample(features);
723
- await fs_extra_1.default.writeFile(path_1.default.join(outputPath, '.env.example'), envExample);
724
- logger_1.logger.debug('Generated .env.example');
725
- }
726
- }
727
- /**
728
- * Patch the generated package.json to merge aligned dependency versions.
729
- * Base templates may have stale versions — this ensures the output
730
- * always matches production-aligned versions from getDependencies().
731
- */
732
- async patchPackageJson(outputPath) {
733
- const pkgPath = path_1.default.join(outputPath, 'package.json');
734
- if (!(await fs_extra_1.default.pathExists(pkgPath)))
735
- return;
736
- const pkg = await fs_extra_1.default.readJson(pkgPath);
737
- const deps = this.context.dependencies;
738
- const devDeps = this.context.devDependencies;
739
- // Merge dependencies — our versions take precedence over template defaults
740
- pkg.dependencies = { ...(pkg.dependencies || {}), ...deps };
741
- pkg.devDependencies = { ...(pkg.devDependencies || {}), ...devDeps };
742
- // Clean up any obsolete/duplicate deps that moved between deps and devDeps
743
- // (e.g., typescript might be in both)
744
- for (const key of Object.keys(pkg.devDependencies)) {
745
- if (key === 'typescript')
746
- continue; // typescript can be in both
747
- if (pkg.dependencies[key]) {
748
- delete pkg.devDependencies[key];
749
- }
750
- }
751
- await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
752
- logger_1.logger.debug('Patched package.json with aligned dependency versions');
753
- }
754
- /**
755
- * Generate .env.example dynamically based on auth, database, and tier selections.
756
- */
757
- generateEnvExample(features) {
758
- const lines = [
759
- '# ═══════════════════════════════════════════════════════════════',
760
- `# ${this.config.name} — Environment Variables`,
761
- '# Copy this file to .env.local and fill in the values.',
762
- '# ═══════════════════════════════════════════════════════════════',
763
- '',
764
- '# ── App ──────────────────────────────────────────────────────',
765
- 'NODE_ENV=development',
766
- 'NEXT_PUBLIC_APP_URL=http://localhost:3000',
767
- '',
768
- ];
769
- // Auth
770
- if (this.config.auth === 'keycloak') {
771
- lines.push('# ── Auth (Keycloak + Auth.js) ─────────────────────────────────', 'AUTH_SECRET= # openssl rand -base64 32', 'KEYCLOAK_ISSUER=https://auth.example.com/realms/my-realm', 'KEYCLOAK_CLIENT_ID=my-app', 'KEYCLOAK_CLIENT_SECRET=', '');
772
- }
773
- else if (this.config.auth === 'supabase') {
774
- lines.push('# ── Auth (Supabase) ───────────────────────────────────────────', 'NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co', 'NEXT_PUBLIC_SUPABASE_ANON_KEY=', 'SUPABASE_SERVICE_ROLE_KEY=', '');
775
- }
776
- else if (this.config.auth === 'firebase') {
777
- lines.push('# ── Auth (Firebase) ───────────────────────────────────────────', 'NEXT_PUBLIC_FIREBASE_API_KEY=', 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=', 'NEXT_PUBLIC_FIREBASE_PROJECT_ID=', '');
778
- }
779
- // Database
780
- if (this.config.database === 'postgresql') {
781
- lines.push('# ── Database (PostgreSQL) ─────────────────────────────────────', 'DATABASE_URL=postgres://user:password@localhost:5432/myapp', '');
782
- }
783
- else if (this.config.database === 'supabase') {
784
- lines.push('# ── Database (Supabase) ───────────────────────────────────────', 'NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co', 'NEXT_PUBLIC_SUPABASE_ANON_KEY=', 'SUPABASE_SERVICE_ROLE_KEY=', '');
785
- }
786
- // Redis (starter+)
787
- if (features.payments || features.ai || features.observability) {
788
- lines.push('# ── Cache (Redis) ─────────────────────────────────────────────', 'REDIS_URL=redis://localhost:6379', 'REDIS_KEY_PREFIX=myapp: # Isolate keys per app', '');
789
- }
790
- // Payments (starter+)
791
- if (features.payments) {
792
- lines.push('# ── Payments (Stripe) ─────────────────────────────────────────', 'STRIPE_PUBLISHABLE_KEY=pk_test_...', 'STRIPE_SECRET_KEY=sk_test_...', 'STRIPE_WEBHOOK_SECRET=whsec_...', '');
793
- }
794
- // Email (all authenticated apps)
795
- if (features.auth) {
796
- lines.push('# ── Email (Resend) ────────────────────────────────────────────', 'RESEND_API_KEY=re_...', 'EMAIL_FROM=noreply@example.com', 'ADMIN_EMAIL=admin@example.com # Contact form submissions', '');
797
- }
798
- // AI (pro+)
799
- if (features.ai) {
800
- lines.push('# ── AI ────────────────────────────────────────────────────────', 'OPENAI_API_KEY=sk-...', '# ANTHROPIC_API_KEY=sk-ant-...', '');
801
- }
802
- // Admin / Cron secrets
803
- lines.push('# ── Security ──────────────────────────────────────────────────', 'ADMIN_SECRET= # openssl rand -base64 32', 'CRON_SECRET= # openssl rand -base64 32', '');
804
- // Beta (if enabled)
805
- if (features.beta) {
806
- lines.push('# ── Beta Access ───────────────────────────────────────────────', 'BETA_ENABLED=true', 'BETA_CODES=CODE1,CODE2 # Comma-separated invite codes', '');
807
- }
808
- return lines.join('\n');
809
- }
810
- protectJSXObjectLiterals(content, jsxObjectLiterals) {
811
- let result = '';
812
- let i = 0;
813
- while (i < content.length) {
814
- if (content.substr(i, 2) === '{{') {
815
- // Found potential JSX object literal
816
- let braceCount = 0;
817
- const start = i;
818
- let j = i;
819
- // Count braces to find the complete object literal
820
- while (j < content.length) {
821
- if (content[j] === '{') {
822
- braceCount++;
823
- }
824
- else if (content[j] === '}') {
825
- braceCount--;
826
- if (braceCount === 0) {
827
- break;
828
- }
829
- }
830
- j++;
831
- }
832
- if (braceCount === 0 && j < content.length) {
833
- // Found complete {{ ... }} block
834
- const fullMatch = content.substring(start, j + 1);
835
- const innerContent = content.substring(start + 2, j - 1);
836
- // Check if this looks like a JSX object literal (not a mustache variable)
837
- if (this.isJSXObjectLiteral(innerContent)) {
838
- jsxObjectLiterals.push(fullMatch);
839
- result += `__JSX_OBJECT_${jsxObjectLiterals.length - 1}__`;
840
- i = j + 1;
841
- }
842
- else {
843
- // Keep mustache variable as-is
844
- result += content[i];
845
- i++;
846
- }
847
- }
848
- else {
849
- // Incomplete braces, just add the character
850
- result += content[i];
851
- i++;
852
- }
853
- }
854
- else {
855
- result += content[i];
856
- i++;
857
- }
858
- }
859
- return result;
860
- }
861
- isJSXObjectLiteral(content) {
862
- // JSX object literals typically have:
863
- // - Property assignments with colons
864
- // - Commas separating properties
865
- // - Whitespace/newlines
866
- // - Array literals with square brackets
867
- // - Function calls
868
- // Simple heuristics to distinguish JSX from mustache variables
869
- return (content.includes(':') || // Property assignments
870
- content.includes(',') || // Multiple properties
871
- content.includes('[') || // Arrays
872
- content.includes('(') || // Function calls
873
- content.match(/\s+/) !== null || // Whitespace
874
- content.includes('\\n') || // Newlines
875
- content.length > 20 // Complex expressions
876
- );
877
- }
878
- isTextFile(filename) {
879
- const textExtensions = [
880
- '.js',
881
- '.jsx',
882
- '.ts',
883
- '.tsx',
884
- '.json',
885
- '.md',
886
- '.txt',
887
- '.yml',
888
- '.yaml',
889
- '.xml',
890
- '.html',
891
- '.css',
892
- '.scss',
893
- '.sass',
894
- '.less',
895
- '.mjs',
896
- '.cjs',
897
- '.env',
898
- '.gitignore',
899
- '.eslintrc',
900
- '.prettierrc',
901
- '.editorconfig',
902
- '.nvmrc',
903
- // Infrastructure files (Terraform, Kubernetes)
904
- '.tf',
905
- '.tfvars',
906
- '.hcl',
907
- ];
908
- const ext = path_1.default.extname(filename).toLowerCase();
909
- return textExtensions.includes(ext) || !ext;
910
- }
911
- toPascalCase(str) {
912
- return str
913
- .replace(/[^a-zA-Z0-9]/g, ' ')
914
- .split(' ')
915
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
916
- .join('');
917
- }
918
- toCamelCase(str) {
919
- const pascal = this.toPascalCase(str);
920
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
921
- }
922
- toTitleCase(str) {
923
- return str
924
- .replace(/[^a-zA-Z0-9]/g, ' ')
925
- .split(' ')
926
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
927
- .join(' ');
928
- }
929
- async createMobileAssets(outputPath) {
930
- const assetsDir = path_1.default.join(outputPath, 'assets');
931
- await fs_extra_1.default.ensureDir(assetsDir);
932
- // Create a simple placeholder image (1x1 pixel transparent PNG)
933
- const placeholderImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', 'base64');
934
- // Create all required asset files
935
- const assetFiles = ['icon.png', 'splash.png', 'adaptive-icon.png', 'favicon.png'];
936
- for (const assetFile of assetFiles) {
937
- await fs_extra_1.default.writeFile(path_1.default.join(assetsDir, assetFile), placeholderImage);
938
- }
939
- logger_1.logger.debug(`Created assets directory with ${assetFiles.length} placeholder files`);
940
- }
941
- }
942
- exports.TemplateGenerator = TemplateGenerator;
943
- //# sourceMappingURL=template-generator.js.map