@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.
- package/CHANGELOG.md +17 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +1870 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/mobile/base/template/App.tsx +7 -4
- package/dist/templates/mobile/base/template/app/checkout.tsx +5 -2
- package/dist/templates/mobile/base/template/package.json +2 -2
- package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/_layout.tsx +6 -2
- package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/billing.tsx +7 -3
- package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/index.tsx +5 -2
- package/dist/templates/mobile/ui-auth-payments/template/app/(tabs)/profile.tsx +7 -2
- package/dist/templates/mobile/ui-auth-payments/template/app/_layout.tsx +2 -4
- package/dist/templates/mobile/ui-auth-payments/template/app/auth/login.tsx +6 -3
- package/dist/templates/mobile/ui-auth-payments/template/app/auth/signup.tsx +6 -3
- package/dist/templates/mobile/ui-auth-payments/template/package.json +2 -2
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/_layout.tsx +6 -2
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/billing.tsx +7 -3
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/index.tsx +5 -2
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/profile.tsx +7 -2
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/_layout.tsx +2 -4
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/auth/login.tsx +6 -3
- package/dist/templates/mobile/ui-auth-payments-ai/template/app/auth/signup.tsx +6 -3
- package/dist/templates/mobile/ui-auth-payments-ai/template/package.json +2 -2
- package/dist/templates/shared/config/web/next.config.mjs +0 -1
- package/dist/templates/web/ai-platform/template/package.json +3 -4
- package/dist/templates/web/ai-platform/template/src/app/chat/page.tsx +5 -2
- package/dist/templates/web/ai-platform/template/src/app/playground/page.tsx +5 -2
- package/dist/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +2 -5
- package/dist/templates/web/base/template/package.json +3 -3
- package/dist/templates/web/iot-dashboard/template/package.json +3 -4
- package/dist/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +2 -5
- package/dist/templates/web/marketplace/template/package.json +3 -4
- package/dist/templates/web/marketplace/template/src/components/providers/app-providers.tsx +2 -5
- package/dist/templates/web/micro-saas/template/package.json +3 -4
- package/dist/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +2 -5
- package/dist/templates/web/ui-auth/template/package.json +3 -3
- package/dist/templates/web/ui-auth-ai/template/package.json +3 -3
- package/dist/templates/web/ui-auth-payments/template/package.json +3 -3
- package/dist/templates/web/ui-auth-payments-ai/template/package.json +3 -3
- package/dist/templates/web/ui-auth-payments-audio/template/package.json +3 -3
- package/dist/templates/web/ui-auth-payments-video/template/package.json +3 -3
- package/dist/templates/web/ui-only/template/package.json +2 -2
- package/dist/templates/web/ui-package-test/template/package.json +3 -3
- package/package.json +1 -1
- package/src/templates/mobile/base/template/App.tsx +7 -4
- package/src/templates/mobile/base/template/app/checkout.tsx +5 -2
- package/src/templates/mobile/base/template/package.json +2 -2
- package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/_layout.tsx +6 -2
- package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/billing.tsx +7 -3
- package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/index.tsx +5 -2
- package/src/templates/mobile/ui-auth-payments/template/app/(tabs)/profile.tsx +7 -2
- package/src/templates/mobile/ui-auth-payments/template/app/_layout.tsx +2 -4
- package/src/templates/mobile/ui-auth-payments/template/app/auth/login.tsx +6 -3
- package/src/templates/mobile/ui-auth-payments/template/app/auth/signup.tsx +6 -3
- package/src/templates/mobile/ui-auth-payments/template/package.json +2 -2
- package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/_layout.tsx +6 -2
- package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/billing.tsx +7 -3
- package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/index.tsx +5 -2
- package/src/templates/mobile/ui-auth-payments-ai/template/app/(tabs)/profile.tsx +7 -2
- package/src/templates/mobile/ui-auth-payments-ai/template/app/_layout.tsx +2 -4
- package/src/templates/mobile/ui-auth-payments-ai/template/app/auth/login.tsx +6 -3
- package/src/templates/mobile/ui-auth-payments-ai/template/app/auth/signup.tsx +6 -3
- package/src/templates/mobile/ui-auth-payments-ai/template/package.json +2 -2
- package/src/templates/shared/config/web/next.config.mjs +0 -1
- package/src/templates/web/ai-platform/template/package.json +3 -4
- package/src/templates/web/ai-platform/template/src/app/chat/page.tsx +5 -2
- package/src/templates/web/ai-platform/template/src/app/playground/page.tsx +5 -2
- package/src/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +2 -5
- package/src/templates/web/base/template/package.json +3 -3
- package/src/templates/web/iot-dashboard/template/package.json +3 -4
- package/src/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +2 -5
- package/src/templates/web/marketplace/template/package.json +3 -4
- package/src/templates/web/marketplace/template/src/components/providers/app-providers.tsx +2 -5
- package/src/templates/web/micro-saas/template/package.json +3 -4
- package/src/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +2 -5
- package/src/templates/web/ui-auth/template/package.json +3 -3
- package/src/templates/web/ui-auth-ai/template/package.json +3 -3
- package/src/templates/web/ui-auth-payments/template/package.json +3 -3
- package/src/templates/web/ui-auth-payments-ai/template/package.json +3 -3
- package/src/templates/web/ui-auth-payments-audio/template/package.json +3 -3
- package/src/templates/web/ui-auth-payments-video/template/package.json +3 -3
- package/src/templates/web/ui-only/template/package.json +2 -2
- package/src/templates/web/ui-package-test/template/package.json +3 -3
- package/dist/cli/commands/add.d.ts +0 -6
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -39
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/create.d.ts +0 -49
- package/dist/cli/commands/create.d.ts.map +0 -1
- package/dist/cli/commands/create.js +0 -173
- package/dist/cli/commands/create.js.map +0 -1
- package/dist/cli/commands/index.d.ts +0 -4
- package/dist/cli/commands/index.d.ts.map +0 -1
- package/dist/cli/commands/index.js +0 -20
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -6
- package/dist/cli/commands/update.d.ts.map +0 -1
- package/dist/cli/commands/update.js +0 -68
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -63
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/prompts/index.d.ts +0 -2
- package/dist/cli/prompts/index.d.ts.map +0 -1
- package/dist/cli/prompts/index.js +0 -18
- package/dist/cli/prompts/index.js.map +0 -1
- package/dist/cli/prompts/project-setup.d.ts +0 -5
- package/dist/cli/prompts/project-setup.d.ts.map +0 -1
- package/dist/cli/prompts/project-setup.js +0 -359
- package/dist/cli/prompts/project-setup.js.map +0 -1
- package/dist/cli/utils/git.d.ts +0 -9
- package/dist/cli/utils/git.d.ts.map +0 -1
- package/dist/cli/utils/git.js +0 -77
- package/dist/cli/utils/git.js.map +0 -1
- package/dist/cli/utils/index.d.ts +0 -5
- package/dist/cli/utils/index.d.ts.map +0 -1
- package/dist/cli/utils/index.js +0 -21
- package/dist/cli/utils/index.js.map +0 -1
- package/dist/cli/utils/logger.d.ts +0 -16
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/cli/utils/logger.js +0 -55
- package/dist/cli/utils/logger.js.map +0 -1
- package/dist/cli/utils/package-manager.d.ts +0 -8
- package/dist/cli/utils/package-manager.d.ts.map +0 -1
- package/dist/cli/utils/package-manager.js +0 -92
- package/dist/cli/utils/package-manager.js.map +0 -1
- package/dist/cli/utils/spinner.d.ts +0 -7
- package/dist/cli/utils/spinner.d.ts.map +0 -1
- package/dist/cli/utils/spinner.js +0 -48
- package/dist/cli/utils/spinner.js.map +0 -1
- package/dist/cli/validators/dependencies.d.ts +0 -15
- package/dist/cli/validators/dependencies.d.ts.map +0 -1
- package/dist/cli/validators/dependencies.js +0 -108
- package/dist/cli/validators/dependencies.js.map +0 -1
- package/dist/cli/validators/index.d.ts +0 -3
- package/dist/cli/validators/index.d.ts.map +0 -1
- package/dist/cli/validators/index.js +0 -19
- package/dist/cli/validators/index.js.map +0 -1
- package/dist/cli/validators/project-name.d.ts +0 -5
- package/dist/cli/validators/project-name.d.ts.map +0 -1
- package/dist/cli/validators/project-name.js +0 -151
- package/dist/cli/validators/project-name.js.map +0 -1
- package/dist/generators/file-processor.d.ts +0 -28
- package/dist/generators/file-processor.d.ts.map +0 -1
- package/dist/generators/file-processor.js +0 -224
- package/dist/generators/file-processor.js.map +0 -1
- package/dist/generators/index.d.ts +0 -4
- package/dist/generators/index.d.ts.map +0 -1
- package/dist/generators/index.js +0 -20
- package/dist/generators/index.js.map +0 -1
- package/dist/generators/package-installer.d.ts +0 -29
- package/dist/generators/package-installer.d.ts.map +0 -1
- package/dist/generators/package-installer.js +0 -177
- package/dist/generators/package-installer.js.map +0 -1
- package/dist/generators/template-generator.d.ts +0 -86
- package/dist/generators/template-generator.d.ts.map +0 -1
- package/dist/generators/template-generator.js +0 -943
- 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
|