@mvp-kit/create 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -19
- package/dist/create.js +861 -569
- package/dist/create.js.map +1 -1
- package/dist/download.js +75 -0
- package/dist/download.js.map +1 -0
- package/dist/index.js +24 -42
- package/dist/index.js.map +1 -1
- package/dist/template.js +75 -156
- package/dist/template.js.map +1 -1
- package/dist/utils.js +0 -88
- package/dist/utils.js.map +1 -1
- package/package.json +21 -21
- package/dist/database.js +0 -105
- package/dist/database.js.map +0 -1
- package/dist/template-downloader.js +0 -248
- package/dist/template-downloader.js.map +0 -1
- package/dist/test.js +0 -359
- package/dist/test.js.map +0 -1
package/dist/create.js
CHANGED
|
@@ -1,629 +1,921 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (title) {
|
|
17
|
-
lines.push(drawBoxLine(title, true));
|
|
18
|
-
lines.push(drawBoxLine(''));
|
|
19
|
-
}
|
|
20
|
-
// Content lines with wrapping
|
|
21
|
-
content.forEach(line => {
|
|
22
|
-
const wrappedLines = wrapText(line, BOX_WIDTH - 4);
|
|
23
|
-
wrappedLines.forEach(wrappedLine => {
|
|
24
|
-
lines.push(drawBoxLine(wrappedLine));
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
// Add empty line if we have content
|
|
28
|
-
if (content.length > 0) {
|
|
29
|
-
lines.push(drawBoxLine(''));
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import prompts from 'prompts';
|
|
8
|
+
import { processTemplate, shouldSkipFile, getTargetFilePath } from './template.js';
|
|
9
|
+
import { downloadTemplate } from './download.js';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
const SUPPORTED_TEMPLATES = ['core', 'saas'];
|
|
13
|
+
function validateTemplateName(template) {
|
|
14
|
+
if (!SUPPORTED_TEMPLATES.includes(template)) {
|
|
15
|
+
throw new Error(`Unsupported template "${template}". Supported templates: ${SUPPORTED_TEMPLATES.join(', ')}`);
|
|
30
16
|
}
|
|
31
|
-
// Bottom border
|
|
32
|
-
lines.push(BOX_INDENT + '└' + '─'.repeat(BOX_WIDTH - 2) + '┘');
|
|
33
|
-
return lines.join('\n');
|
|
34
17
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Get path to template directory
|
|
20
|
+
* Supports multiple ways to specify template location:
|
|
21
|
+
* 1. --template-path CLI option (passed via options.templatePath)
|
|
22
|
+
* 2. MVPKIT_TEMPLATE_PATH environment variable
|
|
23
|
+
* 3. Download from GitHub releases using --template option
|
|
24
|
+
*/
|
|
25
|
+
async function getTemplatePath(options = {}) {
|
|
26
|
+
// 1. CLI option takes highest priority
|
|
27
|
+
if (options.templatePath) {
|
|
28
|
+
const templatePath = path.resolve(options.templatePath);
|
|
29
|
+
if (fs.existsSync(templatePath)) {
|
|
30
|
+
return templatePath;
|
|
45
31
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
throw new Error(`Template path not found: ${templatePath}`);
|
|
33
|
+
}
|
|
34
|
+
// 2. Environment variable
|
|
35
|
+
if (process.env.MVPKIT_TEMPLATE_PATH) {
|
|
36
|
+
const templatePath = path.resolve(process.env.MVPKIT_TEMPLATE_PATH);
|
|
37
|
+
if (fs.existsSync(templatePath)) {
|
|
38
|
+
return templatePath;
|
|
50
39
|
}
|
|
40
|
+
throw new Error(`Template path from MVPKIT_TEMPLATE_PATH not found: ${templatePath}`);
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Check for lock files in priority order
|
|
73
|
-
if (fs.existsSync(path.join(dir, 'bun.lockb'))) {
|
|
74
|
-
return { pm: 'bun', version: getPackageVersion('bun') };
|
|
75
|
-
}
|
|
76
|
-
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
|
|
77
|
-
return { pm: 'pnpm', version: getPackageVersion('pnpm') };
|
|
78
|
-
}
|
|
79
|
-
// Default to pnpm
|
|
80
|
-
return { pm: 'pnpm', version: '10.14.0' };
|
|
42
|
+
// 3. Local monorepo fallback for core/saas templates (dev workflow)
|
|
43
|
+
const template = options.template || 'core';
|
|
44
|
+
if (SUPPORTED_TEMPLATES.includes(template)) {
|
|
45
|
+
const cliDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
46
|
+
const monorepoTemplatePath = path.resolve(cliDir, '..', template);
|
|
47
|
+
if (fs.existsSync(monorepoTemplatePath)) {
|
|
48
|
+
return monorepoTemplatePath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 4. Download from GitHub based on template option
|
|
52
|
+
const cacheDir = path.join(os.tmpdir(), 'mvpkit-template-cache');
|
|
53
|
+
const templateDir = path.join(cacheDir, template);
|
|
54
|
+
// Check if caching is disabled or if we don't have a cached template
|
|
55
|
+
const useCache = options.cache !== false;
|
|
56
|
+
const hasCachedTemplate = useCache && fs.existsSync(templateDir) && fs.readdirSync(templateDir).length > 0;
|
|
57
|
+
if (hasCachedTemplate) {
|
|
58
|
+
return templateDir;
|
|
59
|
+
}
|
|
60
|
+
// Download the template (this will clear the directory if it exists)
|
|
61
|
+
return await downloadTemplate(template, templateDir);
|
|
81
62
|
}
|
|
82
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Read template configuration from template.json
|
|
65
|
+
*/
|
|
66
|
+
async function readTemplateConfig(templateDir) {
|
|
83
67
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
68
|
+
const configPath = path.join(templateDir, 'template.json');
|
|
69
|
+
if (fs.existsSync(configPath)) {
|
|
70
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
71
|
+
return JSON.parse(configContent);
|
|
72
|
+
}
|
|
86
73
|
}
|
|
87
|
-
catch {
|
|
88
|
-
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.log(chalk.yellow('⚠️ Could not read template configuration'));
|
|
89
76
|
}
|
|
90
|
-
}
|
|
91
|
-
function detectInvocationMethod() {
|
|
92
|
-
// Check process.execArgv and process.argv to see how we were invoked
|
|
93
|
-
const execPath = process.execPath;
|
|
94
|
-
const argv = process.argv;
|
|
95
|
-
const userAgent = process.env.npm_config_user_agent || '';
|
|
96
|
-
// Check if invoked via bunx
|
|
97
|
-
if (execPath.includes('bun') || userAgent.startsWith('bun')) {
|
|
98
|
-
return { pm: 'bun', version: extractVersion(userAgent) || 'latest' };
|
|
99
|
-
}
|
|
100
|
-
// Check argv for package manager indicators
|
|
101
|
-
const commandLine = argv.join(' ');
|
|
102
|
-
if (commandLine.includes('bunx') || commandLine.includes('bun run')) {
|
|
103
|
-
return { pm: 'bun', version: extractVersion(userAgent) || 'latest' };
|
|
104
|
-
}
|
|
105
|
-
if (commandLine.includes('pnpm') || commandLine.includes('pnpx')) {
|
|
106
|
-
return { pm: 'pnpm', version: extractVersion(userAgent) || '10.14.0' };
|
|
107
|
-
}
|
|
108
|
-
if (commandLine.includes('npx') || commandLine.includes('npm create')) {
|
|
109
|
-
return { pm: 'pnpm', version: '10.14.0' };
|
|
110
|
-
}
|
|
111
|
-
// Check user agent string
|
|
112
|
-
if (userAgent.startsWith('pnpm'))
|
|
113
|
-
return { pm: 'pnpm', version: extractVersion(userAgent) || '10.14.0' };
|
|
114
|
-
if (userAgent.startsWith('bun'))
|
|
115
|
-
return { pm: 'bun', version: extractVersion(userAgent) || 'latest' };
|
|
116
|
-
if (userAgent.startsWith('npm'))
|
|
117
|
-
return { pm: 'pnpm', version: '10.14.0' };
|
|
118
77
|
return null;
|
|
119
78
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
export async function createProject(projectName, options = {}) {
|
|
135
|
-
const targetDir = path.resolve(projectName);
|
|
136
|
-
// Show MVPKit branding
|
|
137
|
-
console.log();
|
|
138
|
-
const logoWidth = BOX_WIDTH - 2; // Account for box borders and padding
|
|
139
|
-
const logoIndent = ' ';
|
|
140
|
-
console.log(chalk.cyan.bold(logoIndent + '╔' + '═'.repeat(logoWidth) + '╗'));
|
|
141
|
-
console.log(chalk.cyan.bold(logoIndent + '║') + chalk.bold.white(' '.repeat(Math.floor((logoWidth - 6) / 2)) + 'MVPKit' + ' '.repeat(Math.ceil((logoWidth - 6) / 2))) + chalk.cyan.bold('║'));
|
|
142
|
-
console.log(chalk.cyan.bold(logoIndent + '║') + chalk.gray(' '.repeat(Math.floor((logoWidth - 35) / 2)) + 'Cloudflare-native React Starter Kit' + ' '.repeat(Math.ceil((logoWidth - 35) / 2))) + chalk.cyan.bold('║'));
|
|
143
|
-
console.log(chalk.cyan.bold(logoIndent + '╚' + '═'.repeat(logoWidth) + '╝'));
|
|
144
|
-
console.log();
|
|
145
|
-
console.log(chalk.blue.bold(' 🚀 Ship production apps in minutes [DEV]'));
|
|
146
|
-
console.log();
|
|
147
|
-
// Validate project name and directory
|
|
148
|
-
if (fs.existsSync(targetDir)) {
|
|
149
|
-
throw new Error(`Directory ${projectName} already exists`);
|
|
150
|
-
}
|
|
151
|
-
// Always use core template (only template available)
|
|
152
|
-
const templateName = 'core';
|
|
153
|
-
const template = TEMPLATE_REGISTRY[templateName];
|
|
154
|
-
if (!template) {
|
|
155
|
-
throw new Error(`Core template not found`);
|
|
156
|
-
}
|
|
157
|
-
// Detect package manager (we need this early for setup files)
|
|
158
|
-
const packageManagerInfo = detectPackageManager(process.cwd());
|
|
159
|
-
const pmCommand = getPackageManagerCommand(packageManagerInfo);
|
|
160
|
-
// Show template info
|
|
161
|
-
console.log(drawBox([
|
|
162
|
-
`🆓 ${template.displayName}`,
|
|
163
|
-
template.description,
|
|
164
|
-
`Features: ${template.features.slice(0, 2).join(' • ')}`
|
|
165
|
-
], 'Template Selected'));
|
|
166
|
-
console.log();
|
|
167
|
-
// Generate template variables
|
|
168
|
-
let variables = generateTemplateVariables(projectName, options, packageManagerInfo.pm, packageManagerInfo.version);
|
|
169
|
-
// Interactive mode for project customization (default unless explicitly disabled)
|
|
170
|
-
if (options.interactive !== false) {
|
|
171
|
-
const { updatedVariables, updatedOptions } = await runInteractivePrompts(projectName, variables, options);
|
|
172
|
-
variables = updatedVariables;
|
|
173
|
-
Object.assign(options, updatedOptions);
|
|
79
|
+
/**
|
|
80
|
+
* Collect missing template variables interactively
|
|
81
|
+
*/
|
|
82
|
+
async function collectVariables(variables, options) {
|
|
83
|
+
// If non-interactive mode, use simple detection without prompts
|
|
84
|
+
if (options.interactive === false) {
|
|
85
|
+
const { packageManager, version: packageManagerVersion } = await detectPackageManager();
|
|
86
|
+
return {
|
|
87
|
+
...variables,
|
|
88
|
+
packageManager,
|
|
89
|
+
packageManagerVersion,
|
|
90
|
+
componentPack: options.componentPack || 'basic',
|
|
91
|
+
git: options.git !== false
|
|
92
|
+
};
|
|
174
93
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
94
|
+
const normalizeHandle = (handle) => {
|
|
95
|
+
if (!handle)
|
|
96
|
+
return undefined;
|
|
97
|
+
const trimmed = handle.trim();
|
|
98
|
+
if (!trimmed)
|
|
99
|
+
return undefined;
|
|
100
|
+
return trimmed.startsWith('@') ? trimmed : `@${trimmed}`;
|
|
101
|
+
};
|
|
102
|
+
const toSocialUrl = (platform, handle) => {
|
|
103
|
+
const normalized = normalizeHandle(handle);
|
|
104
|
+
if (!normalized)
|
|
105
|
+
return undefined;
|
|
106
|
+
const username = normalized.slice(1);
|
|
107
|
+
if (!username)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (platform === 'tiktok')
|
|
110
|
+
return `https://tiktok.com/@${username}`;
|
|
111
|
+
if (platform === 'x')
|
|
112
|
+
return `https://x.com/${username}`;
|
|
113
|
+
if (platform === 'instagram')
|
|
114
|
+
return `https://instagram.com/${username}`;
|
|
115
|
+
return `https://github.com/${username}`;
|
|
116
|
+
};
|
|
117
|
+
const fieldsToCollect = [
|
|
118
|
+
// 1. Template selection (if not passed via CLI)
|
|
119
|
+
{
|
|
120
|
+
name: 'template',
|
|
121
|
+
message: 'Select template',
|
|
122
|
+
type: !options.template && !options.templatePath ? 'select' : null,
|
|
123
|
+
choices: [
|
|
124
|
+
{ title: 'Core', value: 'core' },
|
|
125
|
+
{ title: 'SaaS', value: 'saas' }
|
|
126
|
+
],
|
|
127
|
+
initial: 0
|
|
128
|
+
},
|
|
129
|
+
// 2. Project description
|
|
130
|
+
{
|
|
131
|
+
name: 'description',
|
|
132
|
+
message: 'Project description',
|
|
133
|
+
type: 'text',
|
|
134
|
+
initial: options.description || `A full-stack application built with MVPKit`
|
|
135
|
+
},
|
|
136
|
+
// 3. Domain name
|
|
137
|
+
{
|
|
138
|
+
name: 'domain',
|
|
139
|
+
message: 'Website domain (for deployment)',
|
|
140
|
+
type: 'text',
|
|
141
|
+
initial: options.domain || `${variables.name}.localhost`
|
|
142
|
+
},
|
|
143
|
+
// 4. Tagline
|
|
144
|
+
{
|
|
145
|
+
name: 'tagline',
|
|
146
|
+
message: 'Tagline',
|
|
147
|
+
type: 'text',
|
|
148
|
+
initial: variables.tagline || 'Build and deploy MVPs faster.'
|
|
149
|
+
},
|
|
150
|
+
// 5. Component pack selection
|
|
151
|
+
{
|
|
152
|
+
name: 'componentPack',
|
|
153
|
+
message: 'Choose component pack',
|
|
154
|
+
type: !options.componentPack ? 'select' : null,
|
|
155
|
+
choices: [
|
|
156
|
+
{ title: 'Essentials', value: 'basic' },
|
|
157
|
+
{ title: 'All', value: 'all' }
|
|
158
|
+
],
|
|
159
|
+
initial: 0
|
|
160
|
+
},
|
|
161
|
+
// 6. SaaS social handles
|
|
162
|
+
{
|
|
163
|
+
name: 'socialGithubHandle',
|
|
164
|
+
message: 'GitHub handle (@username)',
|
|
165
|
+
type: (_prev, values) => (options.template || values.template || variables.template) === 'saas' ? 'text' : null,
|
|
166
|
+
initial: variables.social?.github?.handle || '@mvp-kit'
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'socialXHandle',
|
|
170
|
+
message: 'X handle (@username)',
|
|
171
|
+
type: (_prev, values) => (options.template || values.template || variables.template) === 'saas' ? 'text' : null,
|
|
172
|
+
initial: variables.social?.x?.handle || '@mvpkithq'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'socialInstagramHandle',
|
|
176
|
+
message: 'Instagram handle (@username)',
|
|
177
|
+
type: (_prev, values) => (options.template || values.template || variables.template) === 'saas' ? 'text' : null,
|
|
178
|
+
initial: variables.social?.instagram?.handle || '@mvpkit'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'socialTiktokHandle',
|
|
182
|
+
message: 'TikTok handle (@username)',
|
|
183
|
+
type: (_prev, values) => (options.template || values.template || variables.template) === 'saas' ? 'text' : null,
|
|
184
|
+
initial: variables.social?.tiktok?.handle || '@mvpkittok'
|
|
185
|
+
},
|
|
186
|
+
// 7. Git initialization
|
|
187
|
+
{
|
|
188
|
+
name: 'git',
|
|
189
|
+
message: 'Initialize git repository?',
|
|
190
|
+
type: options.git === undefined ? 'confirm' : null,
|
|
191
|
+
initial: options.git !== false,
|
|
192
|
+
},
|
|
193
|
+
// 8. Package manager (last, after user sees what will be installed)
|
|
194
|
+
{
|
|
195
|
+
name: 'packageManager',
|
|
196
|
+
message: 'Choose package manager',
|
|
197
|
+
type: 'select',
|
|
198
|
+
choices: [
|
|
199
|
+
{ title: 'pnpm [stable]', value: 'pnpm' },
|
|
200
|
+
{ title: 'bun', value: 'bun' }
|
|
201
|
+
],
|
|
202
|
+
initial: 0
|
|
203
|
+
}
|
|
204
|
+
];
|
|
194
205
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
// Check if we can use prompts (TTY available)
|
|
207
|
+
if (!process.stdin.isTTY) {
|
|
208
|
+
console.log(chalk.yellow('⚠️ Non-interactive environment detected, using defaults'));
|
|
209
|
+
const { packageManager, version: packageManagerVersion } = await detectPackageManager();
|
|
210
|
+
return {
|
|
211
|
+
...variables,
|
|
212
|
+
packageManager,
|
|
213
|
+
packageManagerVersion
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
console.log(chalk.cyan('🔧 Project Configuration'));
|
|
217
|
+
console.log(chalk.white.dim('═'.repeat(50)));
|
|
218
|
+
let responses;
|
|
219
|
+
try {
|
|
220
|
+
responses = await prompts(fieldsToCollect, {
|
|
221
|
+
onCancel: () => {
|
|
222
|
+
console.log(chalk.yellow('\n⚠️ Operation cancelled by user'));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (promptError) {
|
|
228
|
+
console.log(chalk.yellow('⚠️ Interactive prompts failed, using defaults'));
|
|
229
|
+
console.log(chalk.white.dim(` Error: ${promptError instanceof Error ? promptError.message : 'Unknown error'}`));
|
|
230
|
+
const { packageManager, version: packageManagerVersion } = await detectPackageManager();
|
|
231
|
+
return {
|
|
232
|
+
...variables,
|
|
233
|
+
packageManager,
|
|
234
|
+
packageManagerVersion
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Get package manager version
|
|
238
|
+
let packageManagerVersion = '9.0.0'; // default
|
|
239
|
+
if (responses.packageManager === 'bun') {
|
|
240
|
+
try {
|
|
241
|
+
packageManagerVersion = execSync('bun --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
packageManagerVersion = '1.0.0';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
try {
|
|
249
|
+
packageManagerVersion = execSync('pnpm --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
packageManagerVersion = '9.0.0';
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const socialGithubHandle = normalizeHandle(responses.socialGithubHandle || variables.social?.github?.handle);
|
|
256
|
+
const socialXHandle = normalizeHandle(responses.socialXHandle || variables.social?.x?.handle);
|
|
257
|
+
const socialInstagramHandle = normalizeHandle(responses.socialInstagramHandle || variables.social?.instagram?.handle);
|
|
258
|
+
const socialTiktokHandle = normalizeHandle(responses.socialTiktokHandle || variables.social?.tiktok?.handle);
|
|
259
|
+
return {
|
|
260
|
+
...variables,
|
|
261
|
+
template: responses.template || options.template || variables.template,
|
|
262
|
+
description: responses.description || variables.description,
|
|
263
|
+
domain: responses.domain || variables.domain,
|
|
264
|
+
tagline: responses.tagline || variables.tagline,
|
|
265
|
+
social: {
|
|
266
|
+
github: {
|
|
267
|
+
handle: socialGithubHandle,
|
|
268
|
+
url: toSocialUrl('github', socialGithubHandle),
|
|
269
|
+
},
|
|
270
|
+
x: {
|
|
271
|
+
handle: socialXHandle,
|
|
272
|
+
url: toSocialUrl('x', socialXHandle),
|
|
273
|
+
},
|
|
274
|
+
instagram: {
|
|
275
|
+
handle: socialInstagramHandle,
|
|
276
|
+
url: toSocialUrl('instagram', socialInstagramHandle),
|
|
277
|
+
},
|
|
278
|
+
tiktok: {
|
|
279
|
+
handle: socialTiktokHandle,
|
|
280
|
+
url: toSocialUrl('tiktok', socialTiktokHandle),
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
packageManager: responses.packageManager || 'pnpm',
|
|
284
|
+
packageManagerVersion,
|
|
285
|
+
componentPack: responses.componentPack || options.componentPack,
|
|
286
|
+
git: responses.git !== undefined ? responses.git : (options.git !== false)
|
|
287
|
+
};
|
|
197
288
|
}
|
|
198
289
|
catch (error) {
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
290
|
+
console.log(chalk.yellow('\n⚠️ Prompt collection failed, using defaults'));
|
|
291
|
+
const { packageManager, version: packageManagerVersion } = await detectPackageManager();
|
|
292
|
+
return {
|
|
293
|
+
...variables,
|
|
294
|
+
packageManager,
|
|
295
|
+
packageManagerVersion
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Complete project setup after template is copied
|
|
301
|
+
* Runs the same steps for both CLI and TUI modes
|
|
302
|
+
*/
|
|
303
|
+
async function completeProjectSetup(targetDir, variables, options) {
|
|
304
|
+
const packageManager = variables.packageManager;
|
|
305
|
+
// Step 1: Copy package manager specific files
|
|
306
|
+
await copyPackageManagerFiles(targetDir, variables, options);
|
|
307
|
+
// Step 2: Install dependencies
|
|
208
308
|
if (options.install !== false) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
309
|
+
await installDependencies(targetDir, packageManager);
|
|
310
|
+
}
|
|
311
|
+
// Step 3: Setup database
|
|
312
|
+
const apiPath = path.join(targetDir, 'services', 'api');
|
|
313
|
+
if (fs.existsSync(apiPath) && options.install !== false) {
|
|
314
|
+
await setupDatabase(apiPath, packageManager);
|
|
315
|
+
}
|
|
316
|
+
// Step 4: Initialize shadcn in UI package
|
|
317
|
+
const uiPath = path.join(targetDir, 'packages', 'ui');
|
|
318
|
+
const hasPreconfiguredUI = fs.existsSync(path.join(uiPath, 'components.json')) &&
|
|
319
|
+
fs.existsSync(path.join(uiPath, 'src', 'components', 'ui'));
|
|
320
|
+
if (fs.existsSync(uiPath)) {
|
|
321
|
+
if (!hasPreconfiguredUI) {
|
|
322
|
+
await initShadcnInUIPackage(uiPath, 'new-york', options.interactive !== false);
|
|
213
323
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
324
|
+
}
|
|
325
|
+
// Step 5: Install component pack
|
|
326
|
+
const componentPack = variables.componentPack;
|
|
327
|
+
if (componentPack && fs.existsSync(uiPath)) {
|
|
328
|
+
// The core template already includes the baseline "basic" set.
|
|
329
|
+
const shouldInstallPack = componentPack === 'all' || !hasPreconfiguredUI;
|
|
330
|
+
if (shouldInstallPack) {
|
|
331
|
+
const templateConfig = await readTemplateConfig(targetDir);
|
|
332
|
+
await installComponentPack(uiPath, componentPack, options.interactive !== false, templateConfig);
|
|
219
333
|
}
|
|
220
334
|
}
|
|
221
|
-
// Step
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
335
|
+
// Step 6: Cleanup tasks
|
|
336
|
+
await cleanup(targetDir, packageManager);
|
|
337
|
+
// Step 7: Initialize git at the end
|
|
338
|
+
const shouldInitGit = variables.git;
|
|
339
|
+
if (shouldInitGit !== false) {
|
|
340
|
+
await initializeGitWithCommit(targetDir);
|
|
225
341
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
342
|
+
}
|
|
343
|
+
async function cleanup(targetDir, packageManager) {
|
|
344
|
+
// Only inherit theme name from UI to web
|
|
345
|
+
const uiPath = path.join(targetDir, 'packages', 'ui');
|
|
346
|
+
const webPath = path.join(targetDir, 'apps', 'web');
|
|
347
|
+
if (fs.existsSync(uiPath) && fs.existsSync(webPath)) {
|
|
348
|
+
await inheritUIConfigToWeb(uiPath, webPath);
|
|
349
|
+
}
|
|
350
|
+
// Run lint fix
|
|
351
|
+
await runLintFixCommand(targetDir, packageManager);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Detect package manager based on how CLI was invoked
|
|
355
|
+
* - npx: Ask user to choose
|
|
356
|
+
* - pnpm create/pnpm dlx: Use pnpm
|
|
357
|
+
* - bunx: Use bun
|
|
358
|
+
*/
|
|
359
|
+
async function detectPackageManager() {
|
|
360
|
+
// Check how the CLI was invoked using environment variables and process.argv
|
|
361
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
362
|
+
const invocation = process.argv.join(' ');
|
|
363
|
+
// Check user agent for package manager detection
|
|
364
|
+
if (userAgent.includes('bun/')) {
|
|
232
365
|
try {
|
|
233
|
-
execSync(
|
|
366
|
+
const bunVersion = execSync('bun --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
367
|
+
return { packageManager: 'bun', version: bunVersion };
|
|
234
368
|
}
|
|
235
|
-
catch
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
console.log(chalk.yellow(' Check the error messages above and fix any issues'));
|
|
240
|
-
throw new Error(`Build failed: ${errorMessage}`);
|
|
369
|
+
catch {
|
|
370
|
+
console.log(chalk.yellow('⚠️ Bun not available, falling back to pnpm'));
|
|
371
|
+
const pnpmVersion = await getPnpmVersion();
|
|
372
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
241
373
|
}
|
|
242
|
-
|
|
374
|
+
}
|
|
375
|
+
if (userAgent.includes('pnpm/')) {
|
|
376
|
+
const pnpmVersion = await getPnpmVersion();
|
|
377
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
378
|
+
}
|
|
379
|
+
// Check argv for direct invocations (fallback)
|
|
380
|
+
if (invocation.includes('bunx') || invocation.includes('bun create')) {
|
|
243
381
|
try {
|
|
244
|
-
execSync(
|
|
245
|
-
|
|
382
|
+
const bunVersion = execSync('bun --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
383
|
+
return { packageManager: 'bun', version: bunVersion };
|
|
246
384
|
}
|
|
247
|
-
catch
|
|
248
|
-
|
|
385
|
+
catch {
|
|
386
|
+
console.log(chalk.yellow('⚠️ Bun not available, falling back to pnpm'));
|
|
387
|
+
const pnpmVersion = await getPnpmVersion();
|
|
388
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
249
389
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
'🎨 Your Cloudflare-native MVP awaits!',
|
|
269
|
-
'⚡ Powered by MVPKit'
|
|
270
|
-
], 'Project Complete!'));
|
|
271
|
-
console.log();
|
|
272
|
-
printNextSteps(projectName, options, dbSetupCompleted, targetPackageManagerInfo.pm);
|
|
390
|
+
}
|
|
391
|
+
if (invocation.includes('pnpm create') || invocation.includes('pnpm dlx')) {
|
|
392
|
+
const pnpmVersion = await getPnpmVersion();
|
|
393
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
394
|
+
}
|
|
395
|
+
// For npx (npm user agent) or unknown, ask user to choose
|
|
396
|
+
if (userAgent.includes('npm/') || invocation.includes('npx')) {
|
|
397
|
+
return await promptForPackageManager();
|
|
398
|
+
}
|
|
399
|
+
// Fallback: try to detect what's available
|
|
400
|
+
try {
|
|
401
|
+
const bunVersion = execSync('bun --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
402
|
+
return { packageManager: 'bun', version: bunVersion };
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
const pnpmVersion = await getPnpmVersion();
|
|
406
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
407
|
+
}
|
|
273
408
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Get pnpm version with fallback
|
|
411
|
+
*/
|
|
412
|
+
async function getPnpmVersion() {
|
|
413
|
+
try {
|
|
414
|
+
return execSync('pnpm --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return '9.0.0'; // Default version
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Prompt user to choose package manager (for npx invocation)
|
|
422
|
+
*/
|
|
423
|
+
async function promptForPackageManager() {
|
|
424
|
+
const inquirer = await import('inquirer');
|
|
425
|
+
// Check what's available
|
|
426
|
+
const bunAvailable = await checkCommandAvailable('bun');
|
|
427
|
+
const pnpmAvailable = await checkCommandAvailable('pnpm');
|
|
428
|
+
if (!bunAvailable && !pnpmAvailable) {
|
|
429
|
+
console.log(chalk.yellow('⚠️ Neither bun nor pnpm found, defaulting to pnpm'));
|
|
430
|
+
return { packageManager: 'pnpm', version: '9.0.0' };
|
|
431
|
+
}
|
|
432
|
+
const choices = [];
|
|
433
|
+
if (pnpmAvailable)
|
|
434
|
+
choices.push({ name: 'pnpm (recommended for most projects)', value: 'pnpm' });
|
|
435
|
+
if (bunAvailable)
|
|
436
|
+
choices.push({ name: 'bun (fast and modern)', value: 'bun' });
|
|
437
|
+
const { packageManager } = await inquirer.default.prompt([{
|
|
438
|
+
type: 'list',
|
|
439
|
+
name: 'packageManager',
|
|
440
|
+
message: 'Which package manager would you like to use?',
|
|
441
|
+
choices,
|
|
442
|
+
default: 'pnpm'
|
|
443
|
+
}]);
|
|
444
|
+
if (packageManager === 'bun') {
|
|
445
|
+
const bunVersion = execSync('bun --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
446
|
+
return { packageManager: 'bun', version: bunVersion };
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
const pnpmVersion = await getPnpmVersion();
|
|
450
|
+
return { packageManager: 'pnpm', version: pnpmVersion };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Check if a command is available
|
|
455
|
+
*/
|
|
456
|
+
async function checkCommandAvailable(command) {
|
|
457
|
+
try {
|
|
458
|
+
execSync(`${command} --version`, { stdio: 'pipe' });
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Create project from template
|
|
467
|
+
*/
|
|
468
|
+
export async function createProject(projectName, options = {}) {
|
|
469
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
470
|
+
const selectedTemplate = options.template || 'core';
|
|
471
|
+
// Validate target directory
|
|
472
|
+
if (fs.existsSync(targetDir)) {
|
|
473
|
+
throw new Error(`Directory ${projectName} already exists`);
|
|
474
|
+
}
|
|
475
|
+
if (!options.templatePath && !process.env.MVPKIT_TEMPLATE_PATH) {
|
|
476
|
+
validateTemplateName(selectedTemplate);
|
|
477
|
+
}
|
|
478
|
+
// Build initial template variables
|
|
479
|
+
const variables = {
|
|
480
|
+
template: selectedTemplate,
|
|
481
|
+
name: projectName,
|
|
482
|
+
description: options.description || `A full-stack application built with MVPKit`,
|
|
483
|
+
domain: options.domain || `${projectName}.localhost`,
|
|
484
|
+
executionDate: new Date().toISOString().slice(0, 10),
|
|
485
|
+
tagline: 'Build and deploy MVPs faster.',
|
|
486
|
+
social: {
|
|
487
|
+
github: {
|
|
488
|
+
handle: '@mvp-kit',
|
|
489
|
+
url: 'https://github.com/mvp-kit'
|
|
490
|
+
},
|
|
491
|
+
x: {
|
|
492
|
+
handle: '@mvpkithq',
|
|
493
|
+
url: 'https://x.com/mvpkithq'
|
|
494
|
+
},
|
|
495
|
+
instagram: {
|
|
496
|
+
handle: '@mvpkit',
|
|
497
|
+
url: 'https://instagram.com/mvpkit'
|
|
498
|
+
},
|
|
499
|
+
tiktok: {
|
|
500
|
+
handle: '@mvpkittok',
|
|
501
|
+
url: 'https://tiktok.com/@mvpkittok'
|
|
303
502
|
}
|
|
304
503
|
},
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
name: 'git',
|
|
308
|
-
message: 'Create git repository?',
|
|
309
|
-
default: true
|
|
310
|
-
},
|
|
311
|
-
]);
|
|
312
|
-
console.log(); // Add spacing after prompts
|
|
313
|
-
const updatedVariables = {
|
|
314
|
-
...variables,
|
|
315
|
-
projectDescription: answers.description,
|
|
316
|
-
domainName: answers.domain || undefined,
|
|
317
|
-
packageScope: answers.packageScope,
|
|
318
|
-
frontendPackageName: `${answers.packageScope}/frontend`,
|
|
319
|
-
backendPackageName: `${answers.packageScope}/backend`,
|
|
320
|
-
apiPackageName: `${answers.packageScope}/api`,
|
|
321
|
-
configPackageName: `${answers.packageScope}/config`
|
|
322
|
-
};
|
|
323
|
-
// Ensure projectKebabCase is never empty or undefined
|
|
324
|
-
if (!updatedVariables.projectKebabCase) {
|
|
325
|
-
console.warn('Warning: projectKebabCase is empty, using projectName as fallback');
|
|
326
|
-
updatedVariables.projectKebabCase = projectName.toLowerCase().replace(/[_\s]+/g, '-');
|
|
327
|
-
}
|
|
328
|
-
const updatedOptions = {
|
|
329
|
-
...options,
|
|
330
|
-
install: true, // Always install dependencies
|
|
331
|
-
git: answers.git,
|
|
332
|
-
setupDb: true, // Always setup database locally
|
|
333
|
-
setupShadcn: true // Always setup shadcn
|
|
504
|
+
packageManager: 'pnpm', // Default, will be updated by prompts if needed
|
|
505
|
+
packageManagerVersion: '9.0.0' // Default, will be updated by prompts if needed
|
|
334
506
|
};
|
|
335
|
-
|
|
507
|
+
// CLI mode with interactive prompts
|
|
508
|
+
// Collect missing variables interactively
|
|
509
|
+
const finalVariables = await collectVariables(variables, options);
|
|
510
|
+
const templateLabel = (finalVariables.template || 'core').toUpperCase();
|
|
511
|
+
console.log(chalk.blue(`🚀 Creating ${projectName} with MVPKit ${templateLabel}\n`));
|
|
512
|
+
try {
|
|
513
|
+
// Resolve template path once to avoid duplicate downloads/work.
|
|
514
|
+
const resolvedTemplatePath = await getTemplatePath({ ...options, template: finalVariables.template });
|
|
515
|
+
const resolvedOptions = {
|
|
516
|
+
...options,
|
|
517
|
+
template: finalVariables.template,
|
|
518
|
+
templatePath: resolvedTemplatePath
|
|
519
|
+
};
|
|
520
|
+
// Copy template and complete setup using shared functionality
|
|
521
|
+
await copyTemplate(targetDir, finalVariables, resolvedOptions);
|
|
522
|
+
await completeProjectSetup(targetDir, finalVariables, resolvedOptions);
|
|
523
|
+
// Success!
|
|
524
|
+
console.log('\n' + chalk.green('🎉 Project Created Successfully!'));
|
|
525
|
+
console.log(chalk.white.dim('═'.repeat(50)));
|
|
526
|
+
console.log(chalk.green(`✅ ${projectName} is ready to go!`));
|
|
527
|
+
// Next steps
|
|
528
|
+
const extendedOptions = { ...options, componentPack: finalVariables.componentPack };
|
|
529
|
+
printNextSteps(projectName, extendedOptions, finalVariables.packageManager);
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
// Clean up on failure
|
|
533
|
+
if (fs.existsSync(targetDir)) {
|
|
534
|
+
await fs.remove(targetDir);
|
|
535
|
+
}
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
336
538
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
539
|
+
/**
|
|
540
|
+
* Initialize shadcn/ui ONLY in packages/ui using the official shadcn CLI
|
|
541
|
+
*/
|
|
542
|
+
async function initShadcnInUIPackage(uiPath, style, interactive) {
|
|
543
|
+
const viteConfigPath = path.join(uiPath, 'vite.config.ts');
|
|
544
|
+
try {
|
|
545
|
+
// Create temporary vite.config.ts for framework detection
|
|
546
|
+
const viteConfig = `import { defineConfig } from 'vite'
|
|
547
|
+
import react from '@vitejs/plugin-react'
|
|
548
|
+
|
|
549
|
+
export default defineConfig({
|
|
550
|
+
plugins: [react()],
|
|
551
|
+
})
|
|
552
|
+
`;
|
|
553
|
+
fs.writeFileSync(viteConfigPath, viteConfig);
|
|
554
|
+
if (interactive) {
|
|
555
|
+
// In interactive mode, let users choose through prompts, force override existing config
|
|
556
|
+
execSync('npx shadcn@latest init -f -s', {
|
|
557
|
+
cwd: uiPath,
|
|
558
|
+
stdio: 'inherit',
|
|
559
|
+
timeout: 60000 // 60 second timeout for CLI init
|
|
560
|
+
});
|
|
357
561
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
562
|
+
else {
|
|
563
|
+
// In non-interactive mode, use specific defaults (new-york, zinc) with -y flag and force override
|
|
564
|
+
execSync('npx shadcn@latest init -y -f -s -b zinc', {
|
|
565
|
+
cwd: uiPath,
|
|
566
|
+
stdio: 'pipe',
|
|
567
|
+
timeout: 60000 // 60 second timeout for CLI init
|
|
568
|
+
});
|
|
361
569
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
throw new Error(`Failed to initialize shadcn/ui in UI package: ${error instanceof Error ? error.message : String(error)}`);
|
|
573
|
+
}
|
|
574
|
+
finally {
|
|
575
|
+
// Clean up temporary vite.config.ts
|
|
576
|
+
if (fs.existsSync(viteConfigPath)) {
|
|
577
|
+
fs.unlinkSync(viteConfigPath);
|
|
369
578
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Install a component pack in the UI package
|
|
583
|
+
*/
|
|
584
|
+
async function installComponentPack(uiPath, packName, interactive, templateConfig) {
|
|
585
|
+
const spinner = ora(` Adding ${packName} component library...`).start();
|
|
586
|
+
try {
|
|
587
|
+
if (packName === 'all') {
|
|
588
|
+
// Use shadcn --all flag for bulk installation
|
|
589
|
+
const addArgs = [
|
|
590
|
+
'add',
|
|
591
|
+
interactive ? '' : '-y',
|
|
592
|
+
'--overwrite',
|
|
593
|
+
'--all'
|
|
594
|
+
].filter(Boolean);
|
|
595
|
+
execSync(`npx shadcn@latest ${addArgs.join(' ')}`, {
|
|
596
|
+
cwd: uiPath,
|
|
597
|
+
stdio: 'pipe',
|
|
598
|
+
timeout: 120000 // 2 minute timeout for bulk install
|
|
599
|
+
});
|
|
373
600
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const fileName = path.basename(sourceFile);
|
|
382
|
-
const transformFunction = getFileTransformFunction(fileName, isTemplateFile);
|
|
383
|
-
// Transform content
|
|
384
|
-
const transformedContent = transformFunction(content, variables);
|
|
385
|
-
// Special handling for package.json - remove workspaces to prevent nested workspace issues
|
|
386
|
-
if (targetRelativePath.endsWith('package.json') && !targetRelativePath.includes('node_modules')) {
|
|
387
|
-
try {
|
|
388
|
-
const pkg = JSON.parse(transformedContent);
|
|
389
|
-
// Remove workspaces from generated projects to prevent nested workspace conflicts
|
|
390
|
-
if (pkg.workspaces) {
|
|
391
|
-
delete pkg.workspaces;
|
|
392
|
-
}
|
|
393
|
-
// Also remove pnpm overrides if they exist
|
|
394
|
-
if (pkg.pnpm && pkg.pnpm.overrides) {
|
|
395
|
-
delete pkg.pnpm.overrides;
|
|
396
|
-
}
|
|
397
|
-
const finalContent = JSON.stringify(pkg, null, 2);
|
|
398
|
-
await fs.writeFile(targetFile, finalContent);
|
|
399
|
-
}
|
|
400
|
-
catch {
|
|
401
|
-
// If JSON parsing fails, write as-is
|
|
402
|
-
await fs.writeFile(targetFile, transformedContent);
|
|
403
|
-
}
|
|
601
|
+
else {
|
|
602
|
+
// Use template config if available, otherwise use default component packs
|
|
603
|
+
const componentPacks = templateConfig?.componentPacks || {
|
|
604
|
+
basic: { components: ['button', 'card', 'input', 'label', 'separator'] }
|
|
605
|
+
};
|
|
606
|
+
if (!(packName in componentPacks)) {
|
|
607
|
+
throw new Error(`Unknown component pack: ${packName}. Available packs: ${Object.keys(componentPacks).join(', ')}`);
|
|
404
608
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
609
|
+
const components = componentPacks[packName].components;
|
|
610
|
+
// Install components individually
|
|
611
|
+
for (const component of components) {
|
|
612
|
+
const addArgs = [
|
|
613
|
+
'add',
|
|
614
|
+
interactive ? '' : '-y',
|
|
615
|
+
'--overwrite',
|
|
616
|
+
component
|
|
617
|
+
].filter(Boolean);
|
|
618
|
+
execSync(`npx shadcn@latest ${addArgs.join(' ')}`, {
|
|
619
|
+
cwd: uiPath,
|
|
620
|
+
stdio: 'pipe',
|
|
621
|
+
timeout: 30000 // 30 second timeout per component
|
|
622
|
+
});
|
|
408
623
|
}
|
|
409
624
|
}
|
|
410
|
-
|
|
411
|
-
// If file is binary or can't be read as text, copy as-is
|
|
412
|
-
await fs.copy(sourcePath, targetFile);
|
|
413
|
-
}
|
|
625
|
+
spinner.succeed(` ✅ ${packName} component library ready`);
|
|
414
626
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
627
|
+
catch (error) {
|
|
628
|
+
spinner.fail(` ❌ ${packName} component library installation failed`);
|
|
629
|
+
throw new Error(`Failed to install ${packName} component library: ${error instanceof Error ? error.message : String(error)}`);
|
|
418
630
|
}
|
|
419
631
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
${
|
|
456
|
-
|
|
457
|
-
# Set up environment variables
|
|
458
|
-
cp apps/frontend/.env.example apps/frontend/.env.local
|
|
459
|
-
cp apps/backend/.dev.vars.example apps/backend/.dev.vars
|
|
460
|
-
|
|
461
|
-
# Set up database (if not done during project creation)
|
|
462
|
-
# cd apps/backend
|
|
463
|
-
# ${getPackageManagerCommand(packageManagerInfo)} db:migrate:local
|
|
464
|
-
# ${getPackageManagerCommand(packageManagerInfo)} db:seed:local
|
|
465
|
-
|
|
466
|
-
# Start development servers
|
|
467
|
-
${getPackageManagerCommand(packageManagerInfo)} dev
|
|
468
|
-
\`\`\`
|
|
469
|
-
|
|
470
|
-
Visit [http://localhost:5173](http://localhost:5173) to see your application!
|
|
471
|
-
|
|
472
|
-
## Project Structure
|
|
473
|
-
|
|
474
|
-
- \`apps/frontend/\` - React application with Vite
|
|
475
|
-
- \`apps/backend/\` - Cloudflare Workers API with Hono
|
|
476
|
-
- \`packages/api/\` - Shared API types
|
|
477
|
-
- \`packages/config/\` - Shared configuration
|
|
478
|
-
|
|
479
|
-
## Features
|
|
480
|
-
|
|
481
|
-
- 🚀 **Cloudflare Stack**: Workers, Pages, D1, KV, R2
|
|
482
|
-
- ⚡ **Modern Frontend**: React 19, TanStack Router & Query
|
|
483
|
-
- 🎨 **Styling**: Tailwind CSS v4 + shadcn/ui components
|
|
484
|
-
- 🔐 **Authentication**: Better Auth with social providers
|
|
485
|
-
- 🗄️ **Database**: D1 with Drizzle ORM
|
|
486
|
-
- 📡 **Type-Safe APIs**: tRPC for end-to-end type safety
|
|
487
|
-
- 🏗️ **Monorepo**: Turbo for fast builds and caching
|
|
488
|
-
|
|
489
|
-
## Development Commands
|
|
490
|
-
|
|
491
|
-
\`\`\`bash
|
|
492
|
-
${getPackageManagerCommand(packageManagerInfo)} dev # Start development servers
|
|
493
|
-
${getPackageManagerCommand(packageManagerInfo)} build # Build all packages
|
|
494
|
-
${getPackageManagerCommand(packageManagerInfo)} lint # Lint all packages
|
|
495
|
-
${getPackageManagerCommand(packageManagerInfo)} typecheck # Type check all packages
|
|
496
|
-
${getPackageManagerCommand(packageManagerInfo)} test # Run tests
|
|
497
|
-
\`\`\`
|
|
498
|
-
|
|
499
|
-
## Deployment
|
|
500
|
-
|
|
501
|
-
\`\`\`bash
|
|
502
|
-
# Deploy backend to Cloudflare Workers
|
|
503
|
-
${getPackageManagerCommand(packageManagerInfo)} deploy:backend
|
|
504
|
-
|
|
505
|
-
# Deploy frontend to Cloudflare Pages
|
|
506
|
-
${getPackageManagerCommand(packageManagerInfo)} deploy:frontend
|
|
507
|
-
|
|
508
|
-
# Deploy both
|
|
509
|
-
${getPackageManagerCommand(packageManagerInfo)} deploy:apps
|
|
510
|
-
\`\`\`
|
|
511
|
-
|
|
512
|
-
## Learn More
|
|
513
|
-
|
|
514
|
-
- [MVPKit Website](https://mvpkit.dev)
|
|
515
|
-
- [MVPKit Documentation](https://docs.mvpkit.dev)
|
|
516
|
-
- [MVPKit Examples](https://github.com/mvp-kit/core/examples)
|
|
517
|
-
`;
|
|
518
|
-
await fs.writeFile(path.join(targetDir, 'README.md'), readmeContent);
|
|
632
|
+
/**
|
|
633
|
+
* Inherit UI package configuration to web app components.json
|
|
634
|
+
* Web app gets config but NOT styles.css (styles come from UI package)
|
|
635
|
+
*/
|
|
636
|
+
async function inheritUIConfigToWeb(uiPath, webPath) {
|
|
637
|
+
const uiComponentsPath = path.join(uiPath, 'components.json');
|
|
638
|
+
const webComponentsPath = path.join(webPath, 'components.json');
|
|
639
|
+
if (!fs.existsSync(uiComponentsPath)) {
|
|
640
|
+
throw new Error('UI package components.json not found - cannot inherit configuration');
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const uiConfig = JSON.parse(fs.readFileSync(uiComponentsPath, 'utf-8'));
|
|
644
|
+
const webConfigCurrent = fs.existsSync(webComponentsPath)
|
|
645
|
+
? JSON.parse(fs.readFileSync(webComponentsPath, 'utf-8'))
|
|
646
|
+
: {};
|
|
647
|
+
// Create web-specific config inheriting style/theme from UI while preserving web paths.
|
|
648
|
+
const webConfig = {
|
|
649
|
+
...webConfigCurrent,
|
|
650
|
+
style: uiConfig.style ?? webConfigCurrent.style,
|
|
651
|
+
rsc: uiConfig.rsc ?? webConfigCurrent.rsc,
|
|
652
|
+
tsx: uiConfig.tsx ?? webConfigCurrent.tsx,
|
|
653
|
+
tailwind: {
|
|
654
|
+
...(webConfigCurrent.tailwind || {}),
|
|
655
|
+
...uiConfig.tailwind,
|
|
656
|
+
css: webConfigCurrent?.tailwind?.css || 'src/styles.css'
|
|
657
|
+
},
|
|
658
|
+
aliases: {
|
|
659
|
+
...(uiConfig.aliases || {}),
|
|
660
|
+
...(webConfigCurrent.aliases || {})
|
|
661
|
+
},
|
|
662
|
+
registries: webConfigCurrent.registries || {}
|
|
663
|
+
};
|
|
664
|
+
await fs.writeJson(webComponentsPath, webConfig, { spaces: 2 });
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
throw new Error(`Failed to inherit UI config to web: ${error instanceof Error ? error.message : String(error)}`);
|
|
668
|
+
}
|
|
519
669
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
], 'Component Setup'));
|
|
526
|
-
console.log();
|
|
527
|
-
const frontendDir = path.join(targetDir, 'apps', 'frontend');
|
|
528
|
-
let componentSpinner = null;
|
|
670
|
+
/**
|
|
671
|
+
* Copy template files and process .template files
|
|
672
|
+
*/
|
|
673
|
+
export async function copyTemplate(targetDir, variables, options = {}) {
|
|
674
|
+
const spinner = ora('Setting up project structure...').start();
|
|
529
675
|
try {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
676
|
+
const coreDir = await getTemplatePath(options);
|
|
677
|
+
// Get all files from core directory
|
|
678
|
+
const files = await glob('**/*', {
|
|
679
|
+
cwd: coreDir,
|
|
680
|
+
dot: true,
|
|
681
|
+
nodir: true,
|
|
682
|
+
absolute: false
|
|
683
|
+
});
|
|
684
|
+
// Process each file
|
|
685
|
+
for (const sourceFile of files) {
|
|
686
|
+
const sourcePath = path.join(coreDir, sourceFile);
|
|
687
|
+
// Skip unwanted files based on .templateignore
|
|
688
|
+
if (shouldSkipFile(sourceFile, coreDir)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
// Determine target path
|
|
692
|
+
const targetFile = path.join(targetDir, getTargetFilePath(sourceFile));
|
|
693
|
+
// Ensure target directory exists
|
|
694
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
695
|
+
// Process template files
|
|
696
|
+
if (sourceFile.endsWith('.template')) {
|
|
697
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
698
|
+
const processedContent = processTemplate(content, variables);
|
|
699
|
+
await fs.writeFile(targetFile, processedContent);
|
|
538
700
|
}
|
|
539
701
|
else {
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
console.log(chalk.gray('⌨️ Press Ctrl+C to cancel shadcn setup if needed'));
|
|
543
|
-
execSync('npx shadcn@latest init -s', {
|
|
544
|
-
cwd: frontendDir,
|
|
545
|
-
stdio: 'inherit',
|
|
546
|
-
killSignal: 'SIGKILL'
|
|
547
|
-
});
|
|
702
|
+
// Copy regular files as-is
|
|
703
|
+
await fs.copy(sourcePath, targetFile);
|
|
548
704
|
}
|
|
549
705
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
706
|
+
spinner.succeed('Project structure created');
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
spinner.fail('Project structure creation failed');
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Copy package manager specific files from _packageManagers directory
|
|
715
|
+
*/
|
|
716
|
+
export async function copyPackageManagerFiles(targetDir, variables, options = {}) {
|
|
717
|
+
const spinner = ora('Configuring package manager...').start();
|
|
718
|
+
try {
|
|
719
|
+
const coreDir = await getTemplatePath(options);
|
|
720
|
+
const packageManagerDir = path.join(coreDir, '_packageManagers', variables.packageManager);
|
|
721
|
+
// Check if package manager directory exists
|
|
722
|
+
if (!fs.existsSync(packageManagerDir)) {
|
|
723
|
+
spinner.succeed('Package manager configuration complete');
|
|
724
|
+
return;
|
|
560
725
|
}
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
726
|
+
// Get all files from package manager directory
|
|
727
|
+
const files = await glob('**/*', {
|
|
728
|
+
cwd: packageManagerDir,
|
|
729
|
+
dot: true,
|
|
730
|
+
nodir: true,
|
|
731
|
+
absolute: false
|
|
732
|
+
});
|
|
733
|
+
// Copy each file to the target directory root
|
|
734
|
+
for (const file of files) {
|
|
735
|
+
const sourcePath = path.join(packageManagerDir, file);
|
|
736
|
+
const targetFile = path.join(targetDir, file);
|
|
737
|
+
// Ensure target directory exists
|
|
738
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
739
|
+
// Process template files if they have .template extension
|
|
740
|
+
if (file.endsWith('.template')) {
|
|
741
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
742
|
+
const processedContent = processTemplate(content, variables);
|
|
743
|
+
const finalTargetFile = targetFile.replace(/\.template$/, '');
|
|
744
|
+
await fs.writeFile(finalTargetFile, processedContent);
|
|
570
745
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
cwd: frontendDir,
|
|
575
|
-
stdio: 'inherit'
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
catch (error) {
|
|
579
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
580
|
-
console.log(chalk.yellow(`⚠️ Failed to install ${component}`));
|
|
581
|
-
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
582
|
-
}
|
|
746
|
+
else {
|
|
747
|
+
// Copy regular files as-is
|
|
748
|
+
await fs.copy(sourcePath, targetFile);
|
|
583
749
|
}
|
|
584
750
|
}
|
|
585
|
-
|
|
751
|
+
spinner.succeed(`Configured for ${variables.packageManager}`);
|
|
586
752
|
}
|
|
587
753
|
catch (error) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
console.log(chalk.yellow('💡 Run manually: npx shadcn@latest init'));
|
|
591
|
-
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
754
|
+
spinner.fail('Package manager configuration failed');
|
|
755
|
+
console.log(chalk.yellow(` Warning: ${error instanceof Error ? error.message : String(error)}`));
|
|
592
756
|
}
|
|
593
757
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
758
|
+
/**
|
|
759
|
+
* Install dependencies
|
|
760
|
+
*/
|
|
761
|
+
async function installDependencies(targetDir, packageManager) {
|
|
762
|
+
const spinner = ora('Installing packages...').start();
|
|
763
|
+
try {
|
|
764
|
+
const command = packageManager === 'bun' ? 'bun install' : 'pnpm install';
|
|
765
|
+
execSync(command, {
|
|
766
|
+
cwd: targetDir,
|
|
767
|
+
stdio: 'pipe'
|
|
768
|
+
});
|
|
769
|
+
spinner.succeed('All packages installed');
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
spinner.fail('Package installation failed');
|
|
773
|
+
console.log(chalk.yellow(`\n💡 Install packages manually: cd ${path.basename(targetDir)} && ${packageManager} install\n`));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Setup database with Drizzle ORM
|
|
778
|
+
*/
|
|
779
|
+
async function setupDatabase(apiPath, packageManager) {
|
|
780
|
+
const spinner = ora('Setting up database...').start();
|
|
781
|
+
try {
|
|
782
|
+
const pmCommand = packageManager === 'bun' ? 'bun' : 'pnpm';
|
|
783
|
+
// Initialize local database
|
|
784
|
+
execSync(`${pmCommand} run db:init`, {
|
|
785
|
+
cwd: apiPath,
|
|
786
|
+
stdio: 'pipe',
|
|
787
|
+
timeout: 30000
|
|
788
|
+
});
|
|
789
|
+
// Generate database schema
|
|
790
|
+
execSync(`${pmCommand} run db:generate`, {
|
|
791
|
+
cwd: apiPath,
|
|
792
|
+
stdio: 'pipe',
|
|
793
|
+
timeout: 60000
|
|
794
|
+
});
|
|
795
|
+
// Run local migrations
|
|
796
|
+
execSync(`${pmCommand} run db:migrate:local`, {
|
|
797
|
+
cwd: apiPath,
|
|
798
|
+
stdio: 'pipe',
|
|
799
|
+
timeout: 60000
|
|
800
|
+
});
|
|
801
|
+
spinner.succeed('Database setup completed');
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
spinner.fail('Database setup failed');
|
|
805
|
+
console.log(chalk.yellow(`\n💡 Setup database manually: cd ${path.basename(apiPath)} && ${packageManager} run db:generate && ${packageManager} run db:migrate:local\n`));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Run lint:fix command if available
|
|
810
|
+
*/
|
|
811
|
+
async function runLintFixCommand(targetDir, packageManager) {
|
|
812
|
+
const spinner = ora('Fixing code formatting...').start();
|
|
813
|
+
try {
|
|
814
|
+
const pmCommand = packageManager === 'pnpm' ? 'pnpm' : 'bun';
|
|
815
|
+
execSync(`${pmCommand} run lint:fix`, {
|
|
816
|
+
cwd: targetDir,
|
|
817
|
+
stdio: 'pipe',
|
|
818
|
+
timeout: 60000 // 60 second timeout
|
|
819
|
+
});
|
|
820
|
+
spinner.succeed('Code formatted and linted');
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
// If lint:fix fails or doesn't exist, try just lint
|
|
824
|
+
try {
|
|
825
|
+
const pmCommand = packageManager === 'pnpm' ? 'pnpm' : 'bun';
|
|
826
|
+
execSync(`${pmCommand} run lint`, {
|
|
827
|
+
cwd: targetDir,
|
|
828
|
+
stdio: 'pipe',
|
|
829
|
+
timeout: 60000
|
|
830
|
+
});
|
|
831
|
+
spinner.succeed('Code formatting verified');
|
|
832
|
+
}
|
|
833
|
+
catch (lintError) {
|
|
834
|
+
// If no lint command exists, skip silently
|
|
835
|
+
spinner.succeed('Code formatting completed');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Initialize git repository with commit (after lint:fix)
|
|
841
|
+
*/
|
|
842
|
+
async function initializeGitWithCommit(targetDir) {
|
|
843
|
+
const spinner = ora('Initializing version control...').start();
|
|
844
|
+
try {
|
|
845
|
+
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
846
|
+
execSync('git add .', { cwd: targetDir, stdio: 'pipe' });
|
|
847
|
+
execSync('git commit -m "Initial commit from MVPKit Create CLI"', { cwd: targetDir, stdio: 'pipe' });
|
|
848
|
+
spinner.succeed('Version control initialized with initial commit');
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
spinner.fail('Version control initialization failed');
|
|
852
|
+
console.log(chalk.yellow('\n💡 Initialize version control manually if needed\n'));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Initialize git repository (legacy function for compatibility)
|
|
857
|
+
*/
|
|
858
|
+
async function initializeGit(targetDir) {
|
|
859
|
+
const spinner = ora('Initializing version control...').start();
|
|
860
|
+
try {
|
|
861
|
+
execSync('git init', { cwd: targetDir, stdio: 'pipe' });
|
|
862
|
+
execSync('git add .', { cwd: targetDir, stdio: 'pipe' });
|
|
863
|
+
execSync('git commit -m "Initial commit"', { cwd: targetDir, stdio: 'pipe' });
|
|
864
|
+
spinner.succeed('Version control initialized');
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
spinner.fail('Version control initialization failed');
|
|
868
|
+
console.log(chalk.yellow('\n💡 Initialize version control manually if needed\n'));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Print next steps
|
|
873
|
+
*/
|
|
874
|
+
function printNextSteps(projectName, options, packageManager) {
|
|
875
|
+
const pmCommand = packageManager === 'bun' ? 'bun' : 'pnpm';
|
|
876
|
+
console.log('\n' + chalk.cyan('🚀 Get Started'));
|
|
877
|
+
console.log(chalk.white.dim('═'.repeat(50)));
|
|
878
|
+
// Step-by-step instructions
|
|
879
|
+
console.log(chalk.bold('Next steps:'));
|
|
880
|
+
console.log(chalk.green(` 1. cd ${projectName}`));
|
|
881
|
+
if (options.install === false) {
|
|
882
|
+
console.log(chalk.green(` 2. ${pmCommand} install`));
|
|
883
|
+
console.log(chalk.green(` 3. ${pmCommand} dev`));
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
console.log(chalk.green(` 2. ${pmCommand} dev`));
|
|
887
|
+
}
|
|
888
|
+
console.log(chalk.green(` 3. Open http://localhost:3001 (web) and http://localhost:3002 (app)`));
|
|
889
|
+
// What's included section
|
|
890
|
+
console.log('\n' + chalk.whiteBright.bold('🎁 What\'s Included:'));
|
|
891
|
+
const features = [
|
|
892
|
+
'⚡ Full-stack TypeScript with hot reload',
|
|
893
|
+
'🗄️ Database with Drizzle ORM + migrations',
|
|
894
|
+
'🎨 shadcn/ui components with new-york theme',
|
|
895
|
+
'🔐 Authentication with better-auth',
|
|
896
|
+
'🌐 Dual surfaces: apps/web (marketing) + apps/app (authenticated app)',
|
|
897
|
+
'📦 Monorepo structure (apps + packages + services)',
|
|
898
|
+
'🔧 ESLint, Prettier, and TypeScript configured'
|
|
899
|
+
];
|
|
900
|
+
if (options.componentPack && options.componentPack === 'all') {
|
|
901
|
+
features.splice(2, 1, `🎨 shadcn/ui complete component library + new-york theme`);
|
|
902
|
+
}
|
|
903
|
+
else if (options.componentPack) {
|
|
904
|
+
features.splice(2, 1, `🎨 shadcn/ui essentials component pack + new-york theme`);
|
|
905
|
+
}
|
|
906
|
+
features.forEach(feature => {
|
|
907
|
+
console.log(chalk.white(` ${feature}`));
|
|
908
|
+
});
|
|
909
|
+
// Quick tips
|
|
910
|
+
console.log('\n' + chalk.whiteBright.bold('💡 Quick Tips:'));
|
|
911
|
+
console.log(chalk.white(' • Database is already set up and migrated'));
|
|
912
|
+
console.log(chalk.white(' • Add more components: npx shadcn@latest add <component>'));
|
|
913
|
+
console.log(chalk.white(' • Customize UI theme in packages/ui/src/styles.css'));
|
|
914
|
+
console.log(chalk.white(' • API handlers are in services/api/src/trpc/ and services/api/src/app.ts'));
|
|
915
|
+
console.log('\n' + chalk.bold('📚 Learn More:'));
|
|
916
|
+
console.log(chalk.blue(' https://docs.mvpkit.dev'));
|
|
917
|
+
console.log(chalk.white(' https://ui.shadcn.com (for UI components)'));
|
|
918
|
+
console.log(chalk.white(' https://orm.drizzle.team (for database)'));
|
|
919
|
+
console.log(chalk.white(''));
|
|
628
920
|
}
|
|
629
921
|
//# sourceMappingURL=create.js.map
|