@mvp-kit/create 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/dist/create.js +549 -0
- package/dist/create.js.map +1 -0
- package/dist/database.js +131 -0
- package/dist/database.js.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/template-downloader.js +231 -0
- package/dist/template-downloader.js.map +1 -0
- package/dist/template.js +160 -0
- package/dist/template.js.map +1 -0
- package/dist/test.js +359 -0
- package/dist/test.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.js +100 -0
- package/dist/utils.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# create-mvpkit
|
|
2
|
+
|
|
3
|
+
> The official CLI for creating MVPKit applications
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/create-mvpkit)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Create a new MVPKit application
|
|
12
|
+
npx create-mvpkit my-app
|
|
13
|
+
|
|
14
|
+
# Or with pnpm
|
|
15
|
+
pnpm create mvpkit my-app
|
|
16
|
+
|
|
17
|
+
# Or with yarn
|
|
18
|
+
yarn create mvpkit my-app
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
🆓 **Free Core Template**
|
|
24
|
+
- React 19 with modern concurrent features
|
|
25
|
+
- Cloudflare Workers for edge computing
|
|
26
|
+
- tRPC for end-to-end type safety
|
|
27
|
+
- Drizzle ORM with D1 database
|
|
28
|
+
- Better Auth for authentication
|
|
29
|
+
- Tailwind CSS + shadcn/ui components
|
|
30
|
+
|
|
31
|
+
💰 **Premium Templates** (License Required)
|
|
32
|
+
- **SaaS**: Payments, subscriptions, multi-tenancy
|
|
33
|
+
- **AI**: LLM integrations, vector databases, AI components
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Basic Commands
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# List available templates
|
|
41
|
+
npx create-mvpkit list
|
|
42
|
+
|
|
43
|
+
# Create with specific template
|
|
44
|
+
npx create-mvpkit my-app --template core
|
|
45
|
+
npx create-mvpkit my-saas --template saas --license YOUR_LICENSE
|
|
46
|
+
npx create-mvpkit my-ai --template ai --license YOUR_LICENSE
|
|
47
|
+
|
|
48
|
+
# Interactive mode
|
|
49
|
+
npx create-mvpkit my-app --interactive
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
| Option | Description |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| `--template <name>` | Template to use (core, saas, ai) |
|
|
57
|
+
| `--license <key>` | License key for paid templates |
|
|
58
|
+
| `--description <text>` | Project description |
|
|
59
|
+
| `--domain <domain>` | Domain name for the application |
|
|
60
|
+
| `--no-install` | Skip dependency installation |
|
|
61
|
+
| `--no-git` | Skip git initialization |
|
|
62
|
+
| `--setup-db` | Setup database and run migrations |
|
|
63
|
+
| `--seed-db` | Seed database with sample data |
|
|
64
|
+
| `--no-interactive` | Skip interactive mode (use defaults) |
|
|
65
|
+
| `--local` | Use local template files (development) |
|
|
66
|
+
|
|
67
|
+
### Examples
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Minimal setup
|
|
71
|
+
npx create-mvpkit my-app --no-install --no-git
|
|
72
|
+
|
|
73
|
+
# Full setup with database
|
|
74
|
+
npx create-mvpkit my-app --setup-db --seed-db
|
|
75
|
+
|
|
76
|
+
# Non-interactive with custom options
|
|
77
|
+
npx create-mvpkit my-app \
|
|
78
|
+
--description "My awesome app" \
|
|
79
|
+
--domain "myapp.com" \
|
|
80
|
+
--no-interactive
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Templates
|
|
84
|
+
|
|
85
|
+
### 🆓 Core (Free)
|
|
86
|
+
Perfect for MVPs and small projects:
|
|
87
|
+
- **Frontend**: React 19, TanStack Router, TanStack Query
|
|
88
|
+
- **Backend**: Cloudflare Workers, Hono, tRPC
|
|
89
|
+
- **Database**: D1 with Drizzle ORM
|
|
90
|
+
- **Auth**: Better Auth with social providers
|
|
91
|
+
- **Styling**: Tailwind CSS v4 + shadcn/ui
|
|
92
|
+
- **Deployment**: Cloudflare Pages + Workers
|
|
93
|
+
|
|
94
|
+
### 💰 SaaS (Premium)
|
|
95
|
+
Everything in Core plus:
|
|
96
|
+
- Stripe payments integration
|
|
97
|
+
- Subscription management
|
|
98
|
+
- Multi-tenant architecture
|
|
99
|
+
- Admin dashboard
|
|
100
|
+
- User management
|
|
101
|
+
- Billing portal
|
|
102
|
+
|
|
103
|
+
### 💰 AI (Premium)
|
|
104
|
+
Everything in Core plus:
|
|
105
|
+
- OpenAI API integration
|
|
106
|
+
- Vector database (Pinecone/Weaviate)
|
|
107
|
+
- AI chat components
|
|
108
|
+
- Document processing
|
|
109
|
+
- Embeddings management
|
|
110
|
+
- AI workflow tools
|
|
111
|
+
|
|
112
|
+
## Get a License
|
|
113
|
+
|
|
114
|
+
Visit [mvpkit.dev](https://mvpkit.dev) to purchase licenses for premium templates.
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
For local development of the CLI itself:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Clone the repository
|
|
122
|
+
git clone https://github.com/mvp-kit/cli.git
|
|
123
|
+
cd cli
|
|
124
|
+
|
|
125
|
+
# Install dependencies
|
|
126
|
+
pnpm install
|
|
127
|
+
|
|
128
|
+
# Build the CLI
|
|
129
|
+
pnpm build
|
|
130
|
+
|
|
131
|
+
# Test locally
|
|
132
|
+
npx create-mvpkit test-app --local
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See [CLI_TESTING_GUIDE.md](./CLI_TESTING_GUIDE.md) for detailed development instructions.
|
|
136
|
+
|
|
137
|
+
## Support
|
|
138
|
+
|
|
139
|
+
- 🌐 [Website](https://mvpkit.dev)
|
|
140
|
+
- 📖 [Documentation](https://docs.mvpkit.dev)
|
|
141
|
+
- 🔗 [Examples](https://github.com/mvp-kit/core/examples)
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT © [MVPKit](https://mvpkit.dev)
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
// Enhanced Box System
|
|
9
|
+
const BOX_WIDTH = 72;
|
|
10
|
+
const BOX_INDENT = ' ';
|
|
11
|
+
function drawBox(content, title) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
// Top border
|
|
14
|
+
lines.push(BOX_INDENT + '┌' + '─'.repeat(BOX_WIDTH - 2) + '┐');
|
|
15
|
+
// Title if provided
|
|
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(''));
|
|
30
|
+
}
|
|
31
|
+
// Bottom border
|
|
32
|
+
lines.push(BOX_INDENT + '└' + '─'.repeat(BOX_WIDTH - 2) + '┘');
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
function wrapText(text, maxWidth) {
|
|
36
|
+
if (text.length <= maxWidth) {
|
|
37
|
+
return [text];
|
|
38
|
+
}
|
|
39
|
+
const words = text.split(' ');
|
|
40
|
+
const lines = [];
|
|
41
|
+
let currentLine = '';
|
|
42
|
+
for (const word of words) {
|
|
43
|
+
if ((currentLine + (currentLine ? ' ' : '') + word).length <= maxWidth) {
|
|
44
|
+
currentLine += (currentLine ? ' ' : '') + word;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (currentLine)
|
|
48
|
+
lines.push(currentLine);
|
|
49
|
+
currentLine = word;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (currentLine)
|
|
53
|
+
lines.push(currentLine);
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
function drawBoxLine(content, isTitle = false) {
|
|
57
|
+
const availableWidth = BOX_WIDTH - 4; // Account for borders and spaces
|
|
58
|
+
const padding = availableWidth - content.length;
|
|
59
|
+
const coloredText = isTitle ? chalk.bold.cyan(content) : chalk.white(content);
|
|
60
|
+
return BOX_INDENT + '│ ' + coloredText + ' '.repeat(padding) + ' │';
|
|
61
|
+
}
|
|
62
|
+
import { generateTemplateVariables, getCoreRepositoryPath, shouldSkipFile, DEFAULT_SKIP_PATTERNS, transformFileName } from './utils.js';
|
|
63
|
+
import { getFileTransformFunction } from './template.js';
|
|
64
|
+
import { setupDatabase } from './database.js';
|
|
65
|
+
import { TemplateDownloader, TEMPLATE_REGISTRY } from './template-downloader.js';
|
|
66
|
+
function detectPackageManager(dir) {
|
|
67
|
+
// Check how CLI was invoked (highest priority)
|
|
68
|
+
const invocation = detectInvocationMethod();
|
|
69
|
+
if (invocation) {
|
|
70
|
+
return invocation;
|
|
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' };
|
|
81
|
+
}
|
|
82
|
+
function getPackageVersion(pm) {
|
|
83
|
+
try {
|
|
84
|
+
const result = execSync(`${pm} --version`, { encoding: 'utf8', stdio: 'pipe' });
|
|
85
|
+
return result.trim();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return '10.14.0';
|
|
89
|
+
}
|
|
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('yarn') || commandLine.includes('yarn create') || 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('yarn') || userAgent.startsWith('npm'))
|
|
117
|
+
return { pm: 'pnpm', version: '10.14.0' };
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function extractVersion(userAgent) {
|
|
121
|
+
// Extract version from user agent string
|
|
122
|
+
// Examples: "npm/8.19.2 node/v18.17.0 linux x64", "pnpm/8.6.0", "yarn/1.22.19", "bun/1.0.0"
|
|
123
|
+
const match = userAgent.match(/^(npm|pnpm|yarn|bun)\/([^\s]+)/);
|
|
124
|
+
return match ? match[2] : '10.14.0';
|
|
125
|
+
}
|
|
126
|
+
function getPackageManagerCommand(pmInfo) {
|
|
127
|
+
const commands = {
|
|
128
|
+
npm: 'npm',
|
|
129
|
+
yarn: 'yarn',
|
|
130
|
+
pnpm: 'pnpm',
|
|
131
|
+
bun: 'bun'
|
|
132
|
+
};
|
|
133
|
+
return commands[pmInfo.pm];
|
|
134
|
+
}
|
|
135
|
+
export async function createProject(projectName, options = {}) {
|
|
136
|
+
const targetDir = path.resolve(projectName);
|
|
137
|
+
// Show MVPKit branding
|
|
138
|
+
console.log();
|
|
139
|
+
const logoWidth = BOX_WIDTH - 2; // Account for box borders and padding
|
|
140
|
+
const logoIndent = ' ';
|
|
141
|
+
console.log(chalk.cyan.bold(logoIndent + '╔' + '═'.repeat(logoWidth) + '╗'));
|
|
142
|
+
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('║'));
|
|
143
|
+
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('║'));
|
|
144
|
+
console.log(chalk.cyan.bold(logoIndent + '╚' + '═'.repeat(logoWidth) + '╝'));
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(chalk.blue.bold(' 🚀 Ship production apps in minutes'));
|
|
147
|
+
console.log();
|
|
148
|
+
// Validate project name and directory
|
|
149
|
+
if (fs.existsSync(targetDir)) {
|
|
150
|
+
throw new Error(`Directory ${projectName} already exists`);
|
|
151
|
+
}
|
|
152
|
+
// Validate template
|
|
153
|
+
const templateName = options.template || 'core';
|
|
154
|
+
const template = TEMPLATE_REGISTRY[templateName];
|
|
155
|
+
if (!template) {
|
|
156
|
+
throw new Error(`Unknown template: ${templateName}`);
|
|
157
|
+
}
|
|
158
|
+
// Detect package manager (we need this early for setup files)
|
|
159
|
+
const packageManagerInfo = detectPackageManager(process.cwd());
|
|
160
|
+
const pmCommand = getPackageManagerCommand(packageManagerInfo);
|
|
161
|
+
// Show template info
|
|
162
|
+
console.log(drawBox([
|
|
163
|
+
`${template.license === 'free' ? '🆓' : '💰'} ${template.displayName}`,
|
|
164
|
+
template.description,
|
|
165
|
+
`Features: ${template.features.slice(0, 2).join(' • ')}`
|
|
166
|
+
], 'Template Selected'));
|
|
167
|
+
console.log();
|
|
168
|
+
// Generate template variables
|
|
169
|
+
let variables = generateTemplateVariables(projectName, options, packageManagerInfo.pm, packageManagerInfo.version);
|
|
170
|
+
// Interactive mode for project customization (default unless explicitly disabled)
|
|
171
|
+
if (options.interactive !== false) {
|
|
172
|
+
const { updatedVariables, updatedOptions } = await runInteractivePrompts(projectName, variables, options);
|
|
173
|
+
variables = updatedVariables;
|
|
174
|
+
Object.assign(options, updatedOptions);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Set opinionated defaults for non-interactive mode
|
|
178
|
+
options.install = true; // Always install dependencies
|
|
179
|
+
options.git = options.git !== false; // Create git repo unless explicitly disabled
|
|
180
|
+
options.setupDb = true; // Always setup database locally
|
|
181
|
+
options.setupShadcn = true; // Setup shadcn by default
|
|
182
|
+
}
|
|
183
|
+
// Project creation message
|
|
184
|
+
console.log(drawBox([
|
|
185
|
+
`🎯 Project: ${variables.projectDisplayName}`,
|
|
186
|
+
`📁 Location: ${targetDir}`,
|
|
187
|
+
`📋 Template: ${template.displayName}`,
|
|
188
|
+
'',
|
|
189
|
+
'🚀 Steps: Download → Git → Install → Build → Setup'
|
|
190
|
+
], 'Creating Your Project'));
|
|
191
|
+
// Step 1: Download and copy template
|
|
192
|
+
const downloader = new TemplateDownloader();
|
|
193
|
+
const templatePath = await downloader.downloadTemplate(templateName, options.license, options.local);
|
|
194
|
+
const copySpinner = ora('Copying template files...').start();
|
|
195
|
+
try {
|
|
196
|
+
await copyAndTransformTemplate(targetDir, variables, templatePath, packageManagerInfo);
|
|
197
|
+
copySpinner.succeed('Template files copied and transformed');
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
copySpinner.fail('Failed to copy template files');
|
|
201
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
202
|
+
console.error(chalk.red('Error details:'), errorMessage);
|
|
203
|
+
throw new Error(`Template copy failed: ${errorMessage}`);
|
|
204
|
+
}
|
|
205
|
+
// Update package manager for target directory
|
|
206
|
+
const targetPackageManagerInfo = detectPackageManager(targetDir);
|
|
207
|
+
const targetPmCommand = getPackageManagerCommand(targetPackageManagerInfo);
|
|
208
|
+
// Step 2: Initialize git (if requested)
|
|
209
|
+
if (options.git) {
|
|
210
|
+
const gitSpinner = ora('Initializing git repository...').start();
|
|
211
|
+
try {
|
|
212
|
+
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
213
|
+
execSync('git add .', { cwd: targetDir, stdio: 'ignore' });
|
|
214
|
+
execSync('git commit -m "Initial commit from MVPKit"', { cwd: targetDir, stdio: 'ignore' });
|
|
215
|
+
gitSpinner.succeed('Git repository initialized');
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
gitSpinner.warn('Git initialization failed (optional)');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Step 3: Install dependencies (always install unless explicitly disabled)
|
|
222
|
+
if (options.install !== false) {
|
|
223
|
+
const installSpinner = ora('Installing dependencies...').start();
|
|
224
|
+
try {
|
|
225
|
+
execSync(`${targetPmCommand} install`, { cwd: targetDir, stdio: 'ignore' });
|
|
226
|
+
installSpinner.succeed('Dependencies installed');
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
installSpinner.fail('Failed to install dependencies');
|
|
230
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
231
|
+
console.log(chalk.yellow(`💡 You can install them manually with: ${targetPmCommand} install`));
|
|
232
|
+
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Step 4: Setup database
|
|
236
|
+
let dbSetupCompleted = false;
|
|
237
|
+
if (options.setupDb !== false && !options.noDb) {
|
|
238
|
+
dbSetupCompleted = await setupDatabase(targetDir, variables, false);
|
|
239
|
+
}
|
|
240
|
+
// Step 5: Setup shadcn/ui
|
|
241
|
+
await setupShadcn(targetDir, targetPackageManagerInfo.pm);
|
|
242
|
+
// Cloudflare setup is documented in README for when users are ready to deploy
|
|
243
|
+
// Success message
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(drawBox([
|
|
246
|
+
`🎉 ${variables.projectDisplayName} is ready!`,
|
|
247
|
+
'',
|
|
248
|
+
'🎨 Your Cloudflare-native MVP awaits!',
|
|
249
|
+
'⚡ Powered by MVPKit'
|
|
250
|
+
], 'Project Complete!'));
|
|
251
|
+
console.log();
|
|
252
|
+
printNextSteps(projectName, options, dbSetupCompleted, targetPackageManagerInfo.pm);
|
|
253
|
+
}
|
|
254
|
+
async function runInteractivePrompts(projectName, variables, options) {
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(drawBox([
|
|
257
|
+
'Just a few quick questions to get you started'
|
|
258
|
+
], 'Configure Your Project'));
|
|
259
|
+
console.log();
|
|
260
|
+
const answers = await inquirer.prompt([
|
|
261
|
+
{
|
|
262
|
+
type: 'input',
|
|
263
|
+
name: 'description',
|
|
264
|
+
message: `Brief description for "${projectName}":`,
|
|
265
|
+
default: variables.projectDescription
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: 'input',
|
|
269
|
+
name: 'domain',
|
|
270
|
+
message: 'Production domain name (e.g. myapp.com, leave empty to skip):',
|
|
271
|
+
default: variables.domainName || ''
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: 'input',
|
|
275
|
+
name: 'packageScope',
|
|
276
|
+
message: 'Package namespace (e.g. @yourcompany):',
|
|
277
|
+
default: variables.packageScope,
|
|
278
|
+
validate: (input) => {
|
|
279
|
+
if (!input.startsWith('@')) {
|
|
280
|
+
return 'Must start with @ (e.g. @yourcompany)';
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'confirm',
|
|
287
|
+
name: 'git',
|
|
288
|
+
message: 'Create git repository?',
|
|
289
|
+
default: true
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
console.log(); // Add spacing after prompts
|
|
293
|
+
const updatedVariables = {
|
|
294
|
+
...variables,
|
|
295
|
+
projectDescription: answers.description,
|
|
296
|
+
domainName: answers.domain || undefined,
|
|
297
|
+
packageScope: answers.packageScope,
|
|
298
|
+
frontendPackageName: `${answers.packageScope}/frontend`,
|
|
299
|
+
backendPackageName: `${answers.packageScope}/backend`,
|
|
300
|
+
apiPackageName: `${answers.packageScope}/api`,
|
|
301
|
+
configPackageName: `${answers.packageScope}/config`
|
|
302
|
+
};
|
|
303
|
+
const updatedOptions = {
|
|
304
|
+
...options,
|
|
305
|
+
install: true, // Always install dependencies
|
|
306
|
+
git: answers.git,
|
|
307
|
+
setupDb: true, // Always setup database locally
|
|
308
|
+
setupShadcn: true // Always setup shadcn
|
|
309
|
+
};
|
|
310
|
+
return { updatedVariables, updatedOptions };
|
|
311
|
+
}
|
|
312
|
+
async function copyAndTransformTemplate(targetDir, variables, templatePath, packageManagerInfo) {
|
|
313
|
+
const coreDir = templatePath || getCoreRepositoryPath();
|
|
314
|
+
if (!fs.existsSync(coreDir)) {
|
|
315
|
+
throw new Error(`Template directory not found at ${coreDir}`);
|
|
316
|
+
}
|
|
317
|
+
// Get all files from core directory with optimized glob pattern
|
|
318
|
+
const files = await glob('**/*', {
|
|
319
|
+
cwd: coreDir,
|
|
320
|
+
dot: true,
|
|
321
|
+
nodir: true,
|
|
322
|
+
absolute: false
|
|
323
|
+
});
|
|
324
|
+
// Process each file
|
|
325
|
+
for (const sourceFile of files) {
|
|
326
|
+
const sourcePath = path.join(coreDir, sourceFile);
|
|
327
|
+
const relativePath = sourceFile;
|
|
328
|
+
// Skip files we don't want to copy
|
|
329
|
+
if (shouldSkipFile(relativePath, DEFAULT_SKIP_PATTERNS)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
// Transform the file path if needed
|
|
333
|
+
const transformedRelativePath = transformFileName(relativePath, variables);
|
|
334
|
+
const targetFile = path.join(targetDir, transformedRelativePath);
|
|
335
|
+
// Ensure target directory exists
|
|
336
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
337
|
+
try {
|
|
338
|
+
// Read source file
|
|
339
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
340
|
+
// Get appropriate transformation function
|
|
341
|
+
const fileName = path.basename(sourceFile);
|
|
342
|
+
const transformFunction = getFileTransformFunction(fileName);
|
|
343
|
+
// Transform content
|
|
344
|
+
const transformedContent = transformFunction(content, variables);
|
|
345
|
+
// Write transformed content
|
|
346
|
+
await fs.writeFile(targetFile, transformedContent);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
// If file is binary or can't be read as text, copy as-is
|
|
350
|
+
await fs.copy(sourcePath, targetFile);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Create additional setup files
|
|
354
|
+
if (packageManagerInfo) {
|
|
355
|
+
await createSetupFiles(targetDir, variables, packageManagerInfo);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function createSetupFiles(targetDir, variables, packageManagerInfo) {
|
|
359
|
+
// Create .env.example files with proper variable names
|
|
360
|
+
const frontendEnvExample = `# Frontend Environment Variables
|
|
361
|
+
VITE_API_URL=http://localhost:8787
|
|
362
|
+
VITE_APP_NAME="${variables.projectDisplayName}"
|
|
363
|
+
${variables.domainName ? `VITE_APP_DOMAIN=${variables.domainName}` : '# VITE_APP_DOMAIN=your-domain.com'}
|
|
364
|
+
`;
|
|
365
|
+
const backendDevVarsExample = `# Backend Development Variables
|
|
366
|
+
# Database
|
|
367
|
+
DATABASE_URL="file:./dev.db"
|
|
368
|
+
|
|
369
|
+
# Auth (generate your own secrets)
|
|
370
|
+
# AUTH_SECRET="your-secret-key-here"
|
|
371
|
+
# GITHUB_CLIENT_ID="your-github-client-id"
|
|
372
|
+
# GITHUB_CLIENT_SECRET="your-github-client-secret"
|
|
373
|
+
|
|
374
|
+
# Email (optional)
|
|
375
|
+
# SMTP_HOST="smtp.gmail.com"
|
|
376
|
+
# SMTP_PORT="587"
|
|
377
|
+
# SMTP_USER="your-email@gmail.com"
|
|
378
|
+
# SMTP_PASSWORD="your-password"
|
|
379
|
+
`;
|
|
380
|
+
await fs.writeFile(path.join(targetDir, 'apps/frontend/.env.example'), frontendEnvExample);
|
|
381
|
+
await fs.writeFile(path.join(targetDir, 'apps/backend/.dev.vars.example'), backendDevVarsExample);
|
|
382
|
+
// Create a project-specific README
|
|
383
|
+
const readmeContent = `# ${variables.projectDisplayName}
|
|
384
|
+
|
|
385
|
+
${variables.projectDescription}
|
|
386
|
+
|
|
387
|
+
Built with [MVPKit Core](https://mvpkit.dev) - A production-ready Cloudflare-native starter.
|
|
388
|
+
|
|
389
|
+
## Quick Start
|
|
390
|
+
|
|
391
|
+
\`\`\`bash
|
|
392
|
+
# Install dependencies
|
|
393
|
+
${getPackageManagerCommand(packageManagerInfo)} install
|
|
394
|
+
|
|
395
|
+
# Set up environment variables
|
|
396
|
+
cp apps/frontend/.env.example apps/frontend/.env.local
|
|
397
|
+
cp apps/backend/.dev.vars.example apps/backend/.dev.vars
|
|
398
|
+
|
|
399
|
+
# Set up database (if not done during project creation)
|
|
400
|
+
# cd apps/backend
|
|
401
|
+
# ${getPackageManagerCommand(packageManagerInfo)} db:migrate:local
|
|
402
|
+
# ${getPackageManagerCommand(packageManagerInfo)} db:seed:local
|
|
403
|
+
|
|
404
|
+
# Start development servers
|
|
405
|
+
${getPackageManagerCommand(packageManagerInfo)} dev
|
|
406
|
+
\`\`\`
|
|
407
|
+
|
|
408
|
+
Visit [http://localhost:5173](http://localhost:5173) to see your application!
|
|
409
|
+
|
|
410
|
+
## Project Structure
|
|
411
|
+
|
|
412
|
+
- \`apps/frontend/\` - React application with Vite
|
|
413
|
+
- \`apps/backend/\` - Cloudflare Workers API with Hono
|
|
414
|
+
- \`packages/api/\` - Shared API types
|
|
415
|
+
- \`packages/config/\` - Shared configuration
|
|
416
|
+
|
|
417
|
+
## Features
|
|
418
|
+
|
|
419
|
+
- 🚀 **Cloudflare Stack**: Workers, Pages, D1, KV, R2
|
|
420
|
+
- ⚡ **Modern Frontend**: React 19, TanStack Router & Query
|
|
421
|
+
- 🎨 **Styling**: Tailwind CSS v4 + shadcn/ui components
|
|
422
|
+
- 🔐 **Authentication**: Better Auth with social providers
|
|
423
|
+
- 🗄️ **Database**: D1 with Drizzle ORM
|
|
424
|
+
- 📡 **Type-Safe APIs**: tRPC for end-to-end type safety
|
|
425
|
+
- 🏗️ **Monorepo**: Turbo for fast builds and caching
|
|
426
|
+
|
|
427
|
+
## Development Commands
|
|
428
|
+
|
|
429
|
+
\`\`\`bash
|
|
430
|
+
${getPackageManagerCommand(packageManagerInfo)} dev # Start development servers
|
|
431
|
+
${getPackageManagerCommand(packageManagerInfo)} build # Build all packages
|
|
432
|
+
${getPackageManagerCommand(packageManagerInfo)} lint # Lint all packages
|
|
433
|
+
${getPackageManagerCommand(packageManagerInfo)} typecheck # Type check all packages
|
|
434
|
+
${getPackageManagerCommand(packageManagerInfo)} test # Run tests
|
|
435
|
+
\`\`\`
|
|
436
|
+
|
|
437
|
+
## Deployment
|
|
438
|
+
|
|
439
|
+
\`\`\`bash
|
|
440
|
+
# Deploy backend to Cloudflare Workers
|
|
441
|
+
${getPackageManagerCommand(packageManagerInfo)} deploy:backend
|
|
442
|
+
|
|
443
|
+
# Deploy frontend to Cloudflare Pages
|
|
444
|
+
${getPackageManagerCommand(packageManagerInfo)} deploy:frontend
|
|
445
|
+
|
|
446
|
+
# Deploy both
|
|
447
|
+
${getPackageManagerCommand(packageManagerInfo)} deploy:apps
|
|
448
|
+
\`\`\`
|
|
449
|
+
|
|
450
|
+
## Learn More
|
|
451
|
+
|
|
452
|
+
- [MVPKit Website](https://mvpkit.dev)
|
|
453
|
+
- [MVPKit Documentation](https://docs.mvpkit.dev)
|
|
454
|
+
- [MVPKit Examples](https://github.com/mvp-kit/core/examples)
|
|
455
|
+
`;
|
|
456
|
+
await fs.writeFile(path.join(targetDir, 'README.md'), readmeContent);
|
|
457
|
+
}
|
|
458
|
+
async function setupShadcn(targetDir, packageManager) {
|
|
459
|
+
console.log();
|
|
460
|
+
console.log(drawBox([
|
|
461
|
+
'Setting up shadcn/ui component library',
|
|
462
|
+
'Configure your preferences, then we\'ll install essential components'
|
|
463
|
+
], 'Component Setup'));
|
|
464
|
+
console.log();
|
|
465
|
+
const shadcnSpinner = ora('Setting up components...').start();
|
|
466
|
+
try {
|
|
467
|
+
const frontendDir = path.join(targetDir, 'apps', 'frontend');
|
|
468
|
+
// Initialize shadcn interactively
|
|
469
|
+
try {
|
|
470
|
+
execSync('npx shadcn@latest init', {
|
|
471
|
+
cwd: frontendDir,
|
|
472
|
+
stdio: 'inherit'
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
477
|
+
console.log(chalk.yellow('⚠️ shadcn init had issues, continuing with component installation'));
|
|
478
|
+
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
479
|
+
}
|
|
480
|
+
// Install shadcn components
|
|
481
|
+
const componentSpinner = ora('Installing components...').start();
|
|
482
|
+
const components = ['alert', 'badge', 'button', 'card', 'input', 'label'];
|
|
483
|
+
for (const component of components) {
|
|
484
|
+
try {
|
|
485
|
+
execSync(`npx shadcn@latest add ${component} --overwrite`, {
|
|
486
|
+
cwd: frontendDir,
|
|
487
|
+
stdio: 'ignore'
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
try {
|
|
492
|
+
execSync(`npx shadcn@latest add ${component}`, {
|
|
493
|
+
cwd: frontendDir,
|
|
494
|
+
stdio: 'ignore'
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
499
|
+
console.log(chalk.yellow(`⚠️ Failed to install ${component}`));
|
|
500
|
+
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
componentSpinner.succeed('Components installed');
|
|
505
|
+
shadcnSpinner.succeed('Setup completed');
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
shadcnSpinner.fail('Setup failed');
|
|
509
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
510
|
+
console.log(chalk.yellow('💡 Run manually: npx shadcn@latest init'));
|
|
511
|
+
console.log(chalk.gray(` Error: ${errorMessage}`));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function printNextSteps(projectName, options, dbSetupCompleted = false, packageManager = 'pnpm') {
|
|
515
|
+
const pmCommand = getPackageManagerCommand({ pm: packageManager, version: packageManager === 'pnpm' ? '10.14.0' : 'latest' });
|
|
516
|
+
// Build next steps list
|
|
517
|
+
const steps = [`1. cd ${projectName}`];
|
|
518
|
+
let step = 2;
|
|
519
|
+
if (!options.install) {
|
|
520
|
+
steps.push(`${step}. ${pmCommand} install`);
|
|
521
|
+
step++;
|
|
522
|
+
}
|
|
523
|
+
steps.push(`${step}. cp apps/frontend/.env.example apps/frontend/.env.local`);
|
|
524
|
+
step++;
|
|
525
|
+
steps.push(`${step}. cp apps/backend/.dev.vars.example apps/backend/.dev.vars`);
|
|
526
|
+
step++;
|
|
527
|
+
if (!dbSetupCompleted) {
|
|
528
|
+
steps.push(`${step}. cd apps/backend`);
|
|
529
|
+
step++;
|
|
530
|
+
steps.push(`${step}. ${pmCommand} db:generate`);
|
|
531
|
+
step++;
|
|
532
|
+
steps.push(`${step}. ${pmCommand} db:migrate:local`);
|
|
533
|
+
step++;
|
|
534
|
+
steps.push(`${step}. cd ../..`);
|
|
535
|
+
step++;
|
|
536
|
+
}
|
|
537
|
+
steps.push(`${step}. ${pmCommand} dev`);
|
|
538
|
+
console.log(drawBox(steps, 'Next Steps'));
|
|
539
|
+
console.log();
|
|
540
|
+
console.log(drawBox([
|
|
541
|
+
'🌐 Website: https://mvpkit.dev',
|
|
542
|
+
'📖 Documentation: https://docs.mvpkit.dev',
|
|
543
|
+
'🔗 Examples: https://github.com/mvp-kit/core/examples'
|
|
544
|
+
], 'Resources'));
|
|
545
|
+
console.log();
|
|
546
|
+
console.log(chalk.bold.green('🚀 Happy building with MVPKit!'));
|
|
547
|
+
console.log();
|
|
548
|
+
}
|
|
549
|
+
//# sourceMappingURL=create.js.map
|