create-fs-cli 1.0.0 → 1.1.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/package.json +1 -1
- package/src/commands/create.js +18 -11
- package/src/generators/backend.js +9 -10
- package/src/generators/backendStatus.js +255 -111
- package/src/generators/frontend.js +7 -7
- package/src/index.js +2 -2
- package/src/utils/messages.js +35 -38
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -18,6 +18,7 @@ export async function createProject(projectName) {
|
|
|
18
18
|
type: 'input',
|
|
19
19
|
name: 'projectName',
|
|
20
20
|
message: 'Project name:',
|
|
21
|
+
prefix: chalk.cyan('?'),
|
|
21
22
|
default: 'my-fullstack-app',
|
|
22
23
|
validate: (input) => {
|
|
23
24
|
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
@@ -33,16 +34,18 @@ export async function createProject(projectName) {
|
|
|
33
34
|
type: 'list',
|
|
34
35
|
name: 'frontend',
|
|
35
36
|
message: 'Choose frontend framework:',
|
|
37
|
+
prefix: chalk.cyan('?'),
|
|
36
38
|
choices: [
|
|
37
39
|
{ name: 'Next.js', value: 'nextjs' },
|
|
38
40
|
{ name: 'React + Vite', value: 'react-vite' },
|
|
39
|
-
{ name: '
|
|
41
|
+
{ name: 'SvelteKit', value: 'svelte' }
|
|
40
42
|
]
|
|
41
43
|
},
|
|
42
44
|
{
|
|
43
45
|
type: 'list',
|
|
44
46
|
name: 'backend',
|
|
45
47
|
message: 'Choose backend framework:',
|
|
48
|
+
prefix: chalk.cyan('?'),
|
|
46
49
|
choices: [
|
|
47
50
|
{ name: 'Next.js API Routes (integrated)', value: 'nextjs-api' },
|
|
48
51
|
{ name: 'Express', value: 'express' },
|
|
@@ -54,6 +57,7 @@ export async function createProject(projectName) {
|
|
|
54
57
|
type: 'list',
|
|
55
58
|
name: 'database',
|
|
56
59
|
message: 'Choose database:',
|
|
60
|
+
prefix: chalk.cyan('?'),
|
|
57
61
|
choices: [
|
|
58
62
|
{ name: 'PostgreSQL', value: 'postgres' },
|
|
59
63
|
{ name: 'MongoDB', value: 'mongodb' },
|
|
@@ -66,6 +70,7 @@ export async function createProject(projectName) {
|
|
|
66
70
|
|
|
67
71
|
// Step 1: Gather all information
|
|
68
72
|
const answers = await inquirer.prompt(prompts);
|
|
73
|
+
console.log(); // Add spacing after prompts
|
|
69
74
|
|
|
70
75
|
// Use CLI argument if provided, otherwise use prompted value
|
|
71
76
|
if (projectName) {
|
|
@@ -74,7 +79,7 @@ export async function createProject(projectName) {
|
|
|
74
79
|
|
|
75
80
|
// Validate backend/database combination
|
|
76
81
|
if (answers.backend === 'fastapi' && answers.database === 'mysql') {
|
|
77
|
-
console.log(chalk.yellow('
|
|
82
|
+
console.log(`\n ${chalk.yellow('⚠')} ${chalk.bold('Warning')} ${chalk.dim('· FastAPI template does not support MySQL. Defaulting to PostgreSQL.')}\n`);
|
|
78
83
|
answers.database = 'postgres';
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -86,20 +91,21 @@ export async function createProject(projectName) {
|
|
|
86
91
|
{
|
|
87
92
|
type: 'confirm',
|
|
88
93
|
name: 'overwrite',
|
|
94
|
+
prefix: chalk.yellow('⚠'),
|
|
89
95
|
message: `Directory ${answers.projectName} already exists. Overwrite?`,
|
|
90
96
|
default: false
|
|
91
97
|
}
|
|
92
98
|
]);
|
|
93
99
|
|
|
94
100
|
if (!overwrite) {
|
|
95
|
-
console.log(chalk.red('Aborted
|
|
101
|
+
console.log(chalk.red('\n Aborted.\n'));
|
|
96
102
|
process.exit(1);
|
|
97
103
|
}
|
|
98
104
|
await fs.remove(projectPath);
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
await fs.ensureDir(projectPath);
|
|
102
|
-
console.log(chalk.green(
|
|
108
|
+
console.log(`\n ${chalk.green('✔')} ${chalk.bold('Project Directory')} ${chalk.dim('·')} ${answers.projectName}`);
|
|
103
109
|
|
|
104
110
|
// Step 2: Generate frontend
|
|
105
111
|
await generateFrontend(answers, projectPath);
|
|
@@ -119,10 +125,11 @@ export async function createProject(projectName) {
|
|
|
119
125
|
await initializeGit(projectPath);
|
|
120
126
|
|
|
121
127
|
// Step 7: Show success message
|
|
128
|
+
console.log(); // Empty line before success message
|
|
122
129
|
showSuccessMessage(answers);
|
|
123
130
|
|
|
124
131
|
} catch (error) {
|
|
125
|
-
console.error(chalk.red('
|
|
132
|
+
console.error(`\n ${chalk.red('✖')} ${chalk.bold('Error')} ${chalk.dim('·')} ${error.message}\n`);
|
|
126
133
|
process.exit(1);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
@@ -215,7 +222,7 @@ The frontend includes a visual indicator in the top-right corner showing backend
|
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
async function cleanupProject(projectPath) {
|
|
218
|
-
const spinner = ora('Cleaning up...').start();
|
|
225
|
+
const spinner = ora({ text: 'Cleaning up...', color: 'cyan' }).start();
|
|
219
226
|
|
|
220
227
|
try {
|
|
221
228
|
// Remove .git from frontend (created by create-next-app, create-vite, etc.)
|
|
@@ -230,22 +237,22 @@ async function cleanupProject(projectPath) {
|
|
|
230
237
|
await fs.remove(backendGit);
|
|
231
238
|
}
|
|
232
239
|
|
|
233
|
-
spinner.succeed('Project cleaned up
|
|
240
|
+
spinner.succeed(chalk.dim('Project cleaned up'));
|
|
234
241
|
} catch (error) {
|
|
235
|
-
spinner.warn('Cleanup warning: ' + error.message);
|
|
242
|
+
spinner.warn(chalk.yellow('Cleanup warning: ' + error.message));
|
|
236
243
|
}
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
async function initializeGit(projectPath) {
|
|
240
|
-
const spinner = ora('Initializing git repository...').start();
|
|
247
|
+
const spinner = ora({ text: 'Initializing git repository...', color: 'cyan' }).start();
|
|
241
248
|
|
|
242
249
|
try {
|
|
243
250
|
await execa('git', ['init'], { cwd: projectPath });
|
|
244
251
|
await execa('git', ['add', '.'], { cwd: projectPath });
|
|
245
252
|
await execa('git', ['commit', '-m', 'Initial commit from FullStack CLI'], { cwd: projectPath });
|
|
246
253
|
|
|
247
|
-
spinner.succeed('Git repository initialized
|
|
254
|
+
spinner.succeed(chalk.dim('Git repository initialized'));
|
|
248
255
|
} catch (error) {
|
|
249
|
-
spinner.warn('Git init skipped: ' + error.message);
|
|
256
|
+
spinner.warn(chalk.yellow('Git init skipped: ' + error.message));
|
|
250
257
|
}
|
|
251
258
|
}
|
|
@@ -23,15 +23,17 @@ const DATABASE_BRANCHES = {
|
|
|
23
23
|
export async function generateBackend(answers, projectPath) {
|
|
24
24
|
const { backend, database } = answers;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
console.log(`\n${chalk.cyan('◯')} ${chalk.bold('Backend')} ${chalk.dim('· Setting up')} ${chalk.white(backend)}`);
|
|
27
|
+
|
|
28
|
+
const spinner = ora({ text: `Initializing...`, color: 'cyan' }).start();
|
|
27
29
|
|
|
28
30
|
try {
|
|
29
31
|
const templateUrl = TEMPLATES[backend];
|
|
30
32
|
const branch = DATABASE_BRANCHES[database];
|
|
31
33
|
const backendPath = path.join(projectPath, 'backend');
|
|
32
34
|
const isPython = backend === 'fastapi';
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
+
|
|
36
|
+
// Update spinner text
|
|
35
37
|
spinner.text = `Cloning ${backend} template (${branch} branch)...`;
|
|
36
38
|
|
|
37
39
|
await execa('git', [
|
|
@@ -66,19 +68,16 @@ export async function generateBackend(answers, projectPath) {
|
|
|
66
68
|
|
|
67
69
|
if (isPython) {
|
|
68
70
|
// For Python, just show message (user needs venv)
|
|
69
|
-
spinner.succeed(
|
|
70
|
-
console.log(chalk.yellow('
|
|
71
|
+
spinner.succeed(chalk.dim('Backend ready'));
|
|
72
|
+
console.log(` ${chalk.yellow('⚠')} ${chalk.dim('Run: cd backend && pip install -r requirements.txt')}\n`);
|
|
71
73
|
} else {
|
|
72
74
|
// For Node.js backends, auto-install
|
|
73
75
|
await execa('npm', ['install'], { cwd: backendPath });
|
|
74
|
-
spinner.succeed(
|
|
76
|
+
spinner.succeed(chalk.dim('Backend ready (dependencies installed)'));
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
console.log(chalk.gray(` Template: ${templateUrl}`));
|
|
78
|
-
console.log(chalk.gray(` Branch: ${branch}\n`));
|
|
79
78
|
|
|
80
79
|
} catch (error) {
|
|
81
|
-
spinner.fail('Backend setup failed');
|
|
80
|
+
spinner.fail(chalk.red('Backend setup failed'));
|
|
82
81
|
throw new Error(`Failed to setup backend: ${error.message}`);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
|
|
6
6
|
export async function injectBackendStatus(frontendPath, framework, isTypeScript, isIntegrated) {
|
|
7
|
-
const spinner = ora('Adding BackendStatus component...').start();
|
|
7
|
+
const spinner = ora({ text: 'Adding BackendStatus component...', color: 'cyan' }).start();
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
10
|
switch (framework) {
|
|
@@ -18,10 +18,9 @@ export async function injectBackendStatus(frontendPath, framework, isTypeScript,
|
|
|
18
18
|
await injectSvelte(frontendPath, isTypeScript);
|
|
19
19
|
break;
|
|
20
20
|
}
|
|
21
|
-
spinner.succeed('BackendStatus component added
|
|
21
|
+
spinner.succeed(chalk.dim('BackendStatus component added'));
|
|
22
22
|
} catch (error) {
|
|
23
|
-
spinner.warn(`Could not auto-inject BackendStatus: ${error.message}`);
|
|
24
|
-
console.log(chalk.yellow(' You can manually add the BackendStatus component later.\n'));
|
|
23
|
+
spinner.warn(chalk.yellow(`Could not auto-inject BackendStatus: ${error.message}`));
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -71,30 +70,45 @@ import { useEffect, useState } from 'react'
|
|
|
71
70
|
type Status = 'checking' | 'connected' | 'disconnected'
|
|
72
71
|
|
|
73
72
|
export default function BackendStatus() {
|
|
74
|
-
const [
|
|
73
|
+
const [backendStatus, setBackendStatus] = useState<Status>('checking')
|
|
74
|
+
const [dbStatus, setDbStatus] = useState<Status | null>(null)
|
|
75
75
|
|
|
76
76
|
useEffect(() => {
|
|
77
|
-
const
|
|
77
|
+
const checkHealth = async () => {
|
|
78
78
|
try {
|
|
79
79
|
const res = await fetch('${backendUrl}')
|
|
80
|
-
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
const data = await res.json()
|
|
82
|
+
setBackendStatus('connected')
|
|
83
|
+
if (typeof data.database === 'boolean') {
|
|
84
|
+
setDbStatus(data.database ? 'connected' : 'disconnected')
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
setBackendStatus('disconnected')
|
|
88
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
89
|
+
}
|
|
81
90
|
} catch {
|
|
82
|
-
|
|
91
|
+
setBackendStatus('disconnected')
|
|
92
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
|
|
87
|
-
const interval = setInterval(
|
|
96
|
+
checkHealth()
|
|
97
|
+
const interval = setInterval(checkHealth, 5000)
|
|
88
98
|
return () => clearInterval(interval)
|
|
89
99
|
}, [])
|
|
90
100
|
|
|
91
|
-
const
|
|
92
|
-
checking:
|
|
93
|
-
connected:
|
|
94
|
-
disconnected:
|
|
101
|
+
const colors: Record<Status, string> = {
|
|
102
|
+
checking: '#fbbf24',
|
|
103
|
+
connected: '#22c55e',
|
|
104
|
+
disconnected: '#ef4444'
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
const
|
|
107
|
+
const labels: Record<Status, string> = {
|
|
108
|
+
checking: 'Checking...',
|
|
109
|
+
connected: 'Connected',
|
|
110
|
+
disconnected: 'Disconnected'
|
|
111
|
+
}
|
|
98
112
|
|
|
99
113
|
return (
|
|
100
114
|
<div style={{
|
|
@@ -106,10 +120,10 @@ export default function BackendStatus() {
|
|
|
106
120
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
107
121
|
color: 'rgba(255, 255, 255, 0.9)',
|
|
108
122
|
padding: '0.5rem 0.875rem',
|
|
109
|
-
borderRadius: '
|
|
123
|
+
borderRadius: '0.75rem',
|
|
110
124
|
display: 'flex',
|
|
111
|
-
|
|
112
|
-
gap: '0.
|
|
125
|
+
flexDirection: 'column',
|
|
126
|
+
gap: '0.375rem',
|
|
113
127
|
zIndex: 50,
|
|
114
128
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
115
129
|
fontSize: '0.8125rem',
|
|
@@ -117,14 +131,28 @@ export default function BackendStatus() {
|
|
|
117
131
|
letterSpacing: '-0.01em',
|
|
118
132
|
transition: 'all 0.2s ease'
|
|
119
133
|
}}>
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
135
|
+
<span style={{
|
|
136
|
+
width: '8px',
|
|
137
|
+
height: '8px',
|
|
138
|
+
borderRadius: '50%',
|
|
139
|
+
backgroundColor: colors[backendStatus],
|
|
140
|
+
boxShadow: \`0 0 8px \${colors[backendStatus]}\`
|
|
141
|
+
}} />
|
|
142
|
+
<span>Backend: {labels[backendStatus]}</span>
|
|
143
|
+
</div>
|
|
144
|
+
{dbStatus && (
|
|
145
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
146
|
+
<span style={{
|
|
147
|
+
width: '8px',
|
|
148
|
+
height: '8px',
|
|
149
|
+
borderRadius: '50%',
|
|
150
|
+
backgroundColor: colors[dbStatus],
|
|
151
|
+
boxShadow: \`0 0 8px \${colors[dbStatus]}\`
|
|
152
|
+
}} />
|
|
153
|
+
<span>Database: {labels[dbStatus]}</span>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
128
156
|
</div>
|
|
129
157
|
)
|
|
130
158
|
}
|
|
@@ -135,30 +163,45 @@ export default function BackendStatus() {
|
|
|
135
163
|
import { useEffect, useState } from 'react'
|
|
136
164
|
|
|
137
165
|
export default function BackendStatus() {
|
|
138
|
-
const [
|
|
166
|
+
const [backendStatus, setBackendStatus] = useState('checking')
|
|
167
|
+
const [dbStatus, setDbStatus] = useState(null)
|
|
139
168
|
|
|
140
169
|
useEffect(() => {
|
|
141
|
-
const
|
|
170
|
+
const checkHealth = async () => {
|
|
142
171
|
try {
|
|
143
172
|
const res = await fetch('${backendUrl}')
|
|
144
|
-
|
|
173
|
+
if (res.ok) {
|
|
174
|
+
const data = await res.json()
|
|
175
|
+
setBackendStatus('connected')
|
|
176
|
+
if (typeof data.database === 'boolean') {
|
|
177
|
+
setDbStatus(data.database ? 'connected' : 'disconnected')
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
setBackendStatus('disconnected')
|
|
181
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
182
|
+
}
|
|
145
183
|
} catch {
|
|
146
|
-
|
|
184
|
+
setBackendStatus('disconnected')
|
|
185
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
147
186
|
}
|
|
148
187
|
}
|
|
149
188
|
|
|
150
|
-
|
|
151
|
-
const interval = setInterval(
|
|
189
|
+
checkHealth()
|
|
190
|
+
const interval = setInterval(checkHealth, 5000)
|
|
152
191
|
return () => clearInterval(interval)
|
|
153
192
|
}, [])
|
|
154
193
|
|
|
155
|
-
const
|
|
156
|
-
checking:
|
|
157
|
-
connected:
|
|
158
|
-
disconnected:
|
|
194
|
+
const colors = {
|
|
195
|
+
checking: '#fbbf24',
|
|
196
|
+
connected: '#22c55e',
|
|
197
|
+
disconnected: '#ef4444'
|
|
159
198
|
}
|
|
160
199
|
|
|
161
|
-
const
|
|
200
|
+
const labels = {
|
|
201
|
+
checking: 'Checking...',
|
|
202
|
+
connected: 'Connected',
|
|
203
|
+
disconnected: 'Disconnected'
|
|
204
|
+
}
|
|
162
205
|
|
|
163
206
|
return (
|
|
164
207
|
<div style={{
|
|
@@ -170,10 +213,10 @@ export default function BackendStatus() {
|
|
|
170
213
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
171
214
|
color: 'rgba(255, 255, 255, 0.9)',
|
|
172
215
|
padding: '0.5rem 0.875rem',
|
|
173
|
-
borderRadius: '
|
|
216
|
+
borderRadius: '0.75rem',
|
|
174
217
|
display: 'flex',
|
|
175
|
-
|
|
176
|
-
gap: '0.
|
|
218
|
+
flexDirection: 'column',
|
|
219
|
+
gap: '0.375rem',
|
|
177
220
|
zIndex: 50,
|
|
178
221
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
179
222
|
fontSize: '0.8125rem',
|
|
@@ -181,14 +224,28 @@ export default function BackendStatus() {
|
|
|
181
224
|
letterSpacing: '-0.01em',
|
|
182
225
|
transition: 'all 0.2s ease'
|
|
183
226
|
}}>
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
227
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
228
|
+
<span style={{
|
|
229
|
+
width: '8px',
|
|
230
|
+
height: '8px',
|
|
231
|
+
borderRadius: '50%',
|
|
232
|
+
backgroundColor: colors[backendStatus],
|
|
233
|
+
boxShadow: \`0 0 8px \${colors[backendStatus]}\`
|
|
234
|
+
}} />
|
|
235
|
+
<span>Backend: {labels[backendStatus]}</span>
|
|
236
|
+
</div>
|
|
237
|
+
{dbStatus && (
|
|
238
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
239
|
+
<span style={{
|
|
240
|
+
width: '8px',
|
|
241
|
+
height: '8px',
|
|
242
|
+
borderRadius: '50%',
|
|
243
|
+
backgroundColor: colors[dbStatus],
|
|
244
|
+
boxShadow: \`0 0 8px \${colors[dbStatus]}\`
|
|
245
|
+
}} />
|
|
246
|
+
<span>Database: {labels[dbStatus]}</span>
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
192
249
|
</div>
|
|
193
250
|
)
|
|
194
251
|
}
|
|
@@ -258,30 +315,45 @@ function getReactBackendStatusCode(isTypeScript) {
|
|
|
258
315
|
type Status = 'checking' | 'connected' | 'disconnected'
|
|
259
316
|
|
|
260
317
|
export default function BackendStatus() {
|
|
261
|
-
const [
|
|
318
|
+
const [backendStatus, setBackendStatus] = useState<Status>('checking')
|
|
319
|
+
const [dbStatus, setDbStatus] = useState<Status | null>(null)
|
|
262
320
|
|
|
263
321
|
useEffect(() => {
|
|
264
|
-
const
|
|
322
|
+
const checkHealth = async () => {
|
|
265
323
|
try {
|
|
266
324
|
const res = await fetch('http://localhost:5000/api/health')
|
|
267
|
-
|
|
325
|
+
if (res.ok) {
|
|
326
|
+
const data = await res.json()
|
|
327
|
+
setBackendStatus('connected')
|
|
328
|
+
if (typeof data.database === 'boolean') {
|
|
329
|
+
setDbStatus(data.database ? 'connected' : 'disconnected')
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
setBackendStatus('disconnected')
|
|
333
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
334
|
+
}
|
|
268
335
|
} catch {
|
|
269
|
-
|
|
336
|
+
setBackendStatus('disconnected')
|
|
337
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
270
338
|
}
|
|
271
339
|
}
|
|
272
340
|
|
|
273
|
-
|
|
274
|
-
const interval = setInterval(
|
|
341
|
+
checkHealth()
|
|
342
|
+
const interval = setInterval(checkHealth, 5000)
|
|
275
343
|
return () => clearInterval(interval)
|
|
276
344
|
}, [])
|
|
277
345
|
|
|
278
|
-
const
|
|
279
|
-
checking:
|
|
280
|
-
connected:
|
|
281
|
-
disconnected:
|
|
346
|
+
const colors: Record<Status, string> = {
|
|
347
|
+
checking: '#fbbf24',
|
|
348
|
+
connected: '#22c55e',
|
|
349
|
+
disconnected: '#ef4444'
|
|
282
350
|
}
|
|
283
351
|
|
|
284
|
-
const
|
|
352
|
+
const labels: Record<Status, string> = {
|
|
353
|
+
checking: 'Checking...',
|
|
354
|
+
connected: 'Connected',
|
|
355
|
+
disconnected: 'Disconnected'
|
|
356
|
+
}
|
|
285
357
|
|
|
286
358
|
return (
|
|
287
359
|
<div style={{
|
|
@@ -293,23 +365,37 @@ export default function BackendStatus() {
|
|
|
293
365
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
294
366
|
color: 'rgba(255, 255, 255, 0.9)',
|
|
295
367
|
padding: '0.5rem 0.875rem',
|
|
296
|
-
borderRadius: '
|
|
368
|
+
borderRadius: '0.75rem',
|
|
297
369
|
display: 'flex',
|
|
298
|
-
|
|
299
|
-
gap: '0.
|
|
370
|
+
flexDirection: 'column',
|
|
371
|
+
gap: '0.375rem',
|
|
300
372
|
zIndex: 50,
|
|
301
373
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
302
374
|
fontSize: '0.8125rem',
|
|
303
375
|
fontWeight: 500
|
|
304
376
|
}}>
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
377
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
378
|
+
<span style={{
|
|
379
|
+
width: '8px',
|
|
380
|
+
height: '8px',
|
|
381
|
+
borderRadius: '50%',
|
|
382
|
+
backgroundColor: colors[backendStatus],
|
|
383
|
+
boxShadow: \`0 0 8px \${colors[backendStatus]}\`
|
|
384
|
+
}} />
|
|
385
|
+
<span>Backend: {labels[backendStatus]}</span>
|
|
386
|
+
</div>
|
|
387
|
+
{dbStatus && (
|
|
388
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
389
|
+
<span style={{
|
|
390
|
+
width: '8px',
|
|
391
|
+
height: '8px',
|
|
392
|
+
borderRadius: '50%',
|
|
393
|
+
backgroundColor: colors[dbStatus],
|
|
394
|
+
boxShadow: \`0 0 8px \${colors[dbStatus]}\`
|
|
395
|
+
}} />
|
|
396
|
+
<span>Database: {labels[dbStatus]}</span>
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
313
399
|
</div>
|
|
314
400
|
)
|
|
315
401
|
}
|
|
@@ -319,30 +405,45 @@ export default function BackendStatus() {
|
|
|
319
405
|
return `import { useEffect, useState } from 'react'
|
|
320
406
|
|
|
321
407
|
export default function BackendStatus() {
|
|
322
|
-
const [
|
|
408
|
+
const [backendStatus, setBackendStatus] = useState('checking')
|
|
409
|
+
const [dbStatus, setDbStatus] = useState(null)
|
|
323
410
|
|
|
324
411
|
useEffect(() => {
|
|
325
|
-
const
|
|
412
|
+
const checkHealth = async () => {
|
|
326
413
|
try {
|
|
327
414
|
const res = await fetch('http://localhost:5000/api/health')
|
|
328
|
-
|
|
415
|
+
if (res.ok) {
|
|
416
|
+
const data = await res.json()
|
|
417
|
+
setBackendStatus('connected')
|
|
418
|
+
if (typeof data.database === 'boolean') {
|
|
419
|
+
setDbStatus(data.database ? 'connected' : 'disconnected')
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
setBackendStatus('disconnected')
|
|
423
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
424
|
+
}
|
|
329
425
|
} catch {
|
|
330
|
-
|
|
426
|
+
setBackendStatus('disconnected')
|
|
427
|
+
setDbStatus(prev => prev !== null ? 'disconnected' : null)
|
|
331
428
|
}
|
|
332
429
|
}
|
|
333
430
|
|
|
334
|
-
|
|
335
|
-
const interval = setInterval(
|
|
431
|
+
checkHealth()
|
|
432
|
+
const interval = setInterval(checkHealth, 5000)
|
|
336
433
|
return () => clearInterval(interval)
|
|
337
434
|
}, [])
|
|
338
435
|
|
|
339
|
-
const
|
|
340
|
-
checking:
|
|
341
|
-
connected:
|
|
342
|
-
disconnected:
|
|
436
|
+
const colors = {
|
|
437
|
+
checking: '#fbbf24',
|
|
438
|
+
connected: '#22c55e',
|
|
439
|
+
disconnected: '#ef4444'
|
|
343
440
|
}
|
|
344
441
|
|
|
345
|
-
const
|
|
442
|
+
const labels = {
|
|
443
|
+
checking: 'Checking...',
|
|
444
|
+
connected: 'Connected',
|
|
445
|
+
disconnected: 'Disconnected'
|
|
446
|
+
}
|
|
346
447
|
|
|
347
448
|
return (
|
|
348
449
|
<div style={{
|
|
@@ -354,23 +455,37 @@ export default function BackendStatus() {
|
|
|
354
455
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
355
456
|
color: 'rgba(255, 255, 255, 0.9)',
|
|
356
457
|
padding: '0.5rem 0.875rem',
|
|
357
|
-
borderRadius: '
|
|
458
|
+
borderRadius: '0.75rem',
|
|
358
459
|
display: 'flex',
|
|
359
|
-
|
|
360
|
-
gap: '0.
|
|
460
|
+
flexDirection: 'column',
|
|
461
|
+
gap: '0.375rem',
|
|
361
462
|
zIndex: 50,
|
|
362
463
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
363
464
|
fontSize: '0.8125rem',
|
|
364
465
|
fontWeight: 500
|
|
365
466
|
}}>
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
467
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
468
|
+
<span style={{
|
|
469
|
+
width: '8px',
|
|
470
|
+
height: '8px',
|
|
471
|
+
borderRadius: '50%',
|
|
472
|
+
backgroundColor: colors[backendStatus],
|
|
473
|
+
boxShadow: \`0 0 8px \${colors[backendStatus]}\`
|
|
474
|
+
}} />
|
|
475
|
+
<span>Backend: {labels[backendStatus]}</span>
|
|
476
|
+
</div>
|
|
477
|
+
{dbStatus && (
|
|
478
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
479
|
+
<span style={{
|
|
480
|
+
width: '8px',
|
|
481
|
+
height: '8px',
|
|
482
|
+
borderRadius: '50%',
|
|
483
|
+
backgroundColor: colors[dbStatus],
|
|
484
|
+
boxShadow: \`0 0 8px \${colors[dbStatus]}\`
|
|
485
|
+
}} />
|
|
486
|
+
<span>Database: {labels[dbStatus]}</span>
|
|
487
|
+
</div>
|
|
488
|
+
)}
|
|
374
489
|
</div>
|
|
375
490
|
)
|
|
376
491
|
}
|
|
@@ -441,43 +556,66 @@ function getSvelteBackendStatusCode() {
|
|
|
441
556
|
return `<script>
|
|
442
557
|
import { onMount, onDestroy } from 'svelte';
|
|
443
558
|
|
|
444
|
-
let
|
|
559
|
+
let backendStatus = 'checking';
|
|
560
|
+
let dbStatus = null;
|
|
445
561
|
let interval;
|
|
446
562
|
|
|
447
|
-
const
|
|
448
|
-
checking:
|
|
449
|
-
connected:
|
|
450
|
-
disconnected:
|
|
563
|
+
const colors = {
|
|
564
|
+
checking: '#fbbf24',
|
|
565
|
+
connected: '#22c55e',
|
|
566
|
+
disconnected: '#ef4444'
|
|
451
567
|
};
|
|
452
568
|
|
|
453
|
-
|
|
569
|
+
const labels = {
|
|
570
|
+
checking: 'Checking...',
|
|
571
|
+
connected: 'Connected',
|
|
572
|
+
disconnected: 'Disconnected'
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
async function checkHealth() {
|
|
454
576
|
try {
|
|
455
577
|
const res = await fetch('http://localhost:5000/api/health');
|
|
456
|
-
|
|
578
|
+
if (res.ok) {
|
|
579
|
+
const data = await res.json();
|
|
580
|
+
backendStatus = 'connected';
|
|
581
|
+
if (typeof data.database === 'boolean') {
|
|
582
|
+
dbStatus = data.database ? 'connected' : 'disconnected';
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
backendStatus = 'disconnected';
|
|
586
|
+
if (dbStatus !== null) dbStatus = 'disconnected';
|
|
587
|
+
}
|
|
457
588
|
} catch {
|
|
458
|
-
|
|
589
|
+
backendStatus = 'disconnected';
|
|
590
|
+
if (dbStatus !== null) dbStatus = 'disconnected';
|
|
459
591
|
}
|
|
460
592
|
}
|
|
461
593
|
|
|
462
594
|
onMount(() => {
|
|
463
|
-
|
|
464
|
-
interval = setInterval(
|
|
595
|
+
checkHealth();
|
|
596
|
+
interval = setInterval(checkHealth, 5000);
|
|
465
597
|
});
|
|
466
598
|
|
|
467
599
|
onDestroy(() => {
|
|
468
600
|
if (interval) clearInterval(interval);
|
|
469
601
|
});
|
|
470
|
-
|
|
471
|
-
$: config = statusConfig[status];
|
|
472
602
|
</script>
|
|
473
603
|
|
|
474
|
-
<div class="
|
|
475
|
-
<
|
|
476
|
-
|
|
604
|
+
<div class="status-container">
|
|
605
|
+
<div class="status-row">
|
|
606
|
+
<span class="dot" style="background-color: {colors[backendStatus]}; box-shadow: 0 0 8px {colors[backendStatus]};"></span>
|
|
607
|
+
<span class="text">Backend: {labels[backendStatus]}</span>
|
|
608
|
+
</div>
|
|
609
|
+
{#if dbStatus}
|
|
610
|
+
<div class="status-row">
|
|
611
|
+
<span class="dot" style="background-color: {colors[dbStatus]}; box-shadow: 0 0 8px {colors[dbStatus]};"></span>
|
|
612
|
+
<span class="text">Database: {labels[dbStatus]}</span>
|
|
613
|
+
</div>
|
|
614
|
+
{/if}
|
|
477
615
|
</div>
|
|
478
616
|
|
|
479
617
|
<style>
|
|
480
|
-
.
|
|
618
|
+
.status-container {
|
|
481
619
|
position: fixed;
|
|
482
620
|
top: 1rem;
|
|
483
621
|
right: 1rem;
|
|
@@ -486,17 +624,23 @@ function getSvelteBackendStatusCode() {
|
|
|
486
624
|
-webkit-backdrop-filter: blur(8px);
|
|
487
625
|
color: rgba(255, 255, 255, 0.9);
|
|
488
626
|
padding: 0.5rem 0.875rem;
|
|
489
|
-
border-radius:
|
|
627
|
+
border-radius: 0.75rem;
|
|
490
628
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
491
629
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3);
|
|
492
630
|
display: flex;
|
|
493
|
-
|
|
494
|
-
gap: 0.
|
|
631
|
+
flex-direction: column;
|
|
632
|
+
gap: 0.375rem;
|
|
495
633
|
z-index: 50;
|
|
496
634
|
font-family: system-ui, -apple-system, sans-serif;
|
|
497
635
|
font-size: 0.75rem;
|
|
498
636
|
}
|
|
499
637
|
|
|
638
|
+
.status-row {
|
|
639
|
+
display: flex;
|
|
640
|
+
align-items: center;
|
|
641
|
+
gap: 0.5rem;
|
|
642
|
+
}
|
|
643
|
+
|
|
500
644
|
.dot {
|
|
501
645
|
width: 8px;
|
|
502
646
|
height: 8px;
|
|
@@ -8,7 +8,7 @@ import { injectBackendStatus } from './backendStatus.js';
|
|
|
8
8
|
export async function generateFrontend(answers, projectPath) {
|
|
9
9
|
const { frontend } = answers;
|
|
10
10
|
|
|
11
|
-
console.log(chalk.cyan('
|
|
11
|
+
console.log(`\n${chalk.cyan('◯')} ${chalk.bold('Frontend')} ${chalk.dim('· Setting up')} ${chalk.white(frontend)}`);
|
|
12
12
|
|
|
13
13
|
switch (frontend) {
|
|
14
14
|
case 'nextjs':
|
|
@@ -24,7 +24,7 @@ export async function generateFrontend(answers, projectPath) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async function generateNextJS(answers, projectPath) {
|
|
27
|
-
console.log(chalk.
|
|
27
|
+
console.log(chalk.gray(' › Running create-next-app (follow the prompts)...\n'));
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
30
|
// Run Next.js CLI interactively
|
|
@@ -33,7 +33,7 @@ async function generateNextJS(answers, projectPath) {
|
|
|
33
33
|
stdio: 'inherit'
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
console.log(chalk.green('
|
|
36
|
+
console.log(`\n ${chalk.green('✔')} ${chalk.dim('Next.js project created')}\n`);
|
|
37
37
|
|
|
38
38
|
// Detect if TypeScript was chosen
|
|
39
39
|
const frontendPath = path.join(projectPath, 'frontend');
|
|
@@ -49,7 +49,7 @@ async function generateNextJS(answers, projectPath) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async function generateReactVite(answers, projectPath) {
|
|
52
|
-
console.log(chalk.
|
|
52
|
+
console.log(chalk.gray(' › Running create-vite (follow the prompts)...\n'));
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
55
|
// Run Vite CLI interactively
|
|
@@ -58,7 +58,7 @@ async function generateReactVite(answers, projectPath) {
|
|
|
58
58
|
stdio: 'inherit'
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
console.log(chalk.green('
|
|
61
|
+
console.log(`\n ${chalk.green('✔')} ${chalk.dim('React + Vite project created')}\n`);
|
|
62
62
|
|
|
63
63
|
// Detect if TypeScript was chosen
|
|
64
64
|
const frontendPath = path.join(projectPath, 'frontend');
|
|
@@ -74,7 +74,7 @@ async function generateReactVite(answers, projectPath) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
async function generateSvelte(answers, projectPath) {
|
|
77
|
-
console.log(chalk.
|
|
77
|
+
console.log(chalk.gray(' › Running create-svelte (follow the prompts)...\n'));
|
|
78
78
|
|
|
79
79
|
try {
|
|
80
80
|
// Run SvelteKit CLI interactively
|
|
@@ -83,7 +83,7 @@ async function generateSvelte(answers, projectPath) {
|
|
|
83
83
|
stdio: 'inherit'
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
console.log(chalk.green('
|
|
86
|
+
console.log(`\n ${chalk.green('✔')} ${chalk.dim('SvelteKit project created')}\n`);
|
|
87
87
|
|
|
88
88
|
// Detect if TypeScript was chosen
|
|
89
89
|
const frontendPath = path.join(projectPath, 'frontend');
|
package/src/index.js
CHANGED
|
@@ -10,10 +10,10 @@ const program = new Command();
|
|
|
10
10
|
// Display banner
|
|
11
11
|
console.log(
|
|
12
12
|
chalk.cyan(
|
|
13
|
-
figlet.textSync('
|
|
13
|
+
figlet.textSync('create-fs-cli', { font: 'Standard' })
|
|
14
14
|
)
|
|
15
15
|
);
|
|
16
|
-
console.log(chalk.
|
|
16
|
+
console.log(chalk.dim('\n ▷ Rapidly scaffold full-stack applications\n'));
|
|
17
17
|
|
|
18
18
|
program
|
|
19
19
|
.name('create-fs-cli')
|
package/src/utils/messages.js
CHANGED
|
@@ -17,61 +17,58 @@ export function showSuccessMessage(answers) {
|
|
|
17
17
|
const hasBackend = backend !== 'nextjs-api';
|
|
18
18
|
const isPython = backend === 'fastapi';
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const log = [];
|
|
21
|
+
|
|
22
|
+
// Title
|
|
23
|
+
log.push(`${chalk.bgGreen.black.bold(' SUCCESS ')} ${chalk.green(`Created ${chalk.bold(projectName)}`)}\n`);
|
|
24
|
+
|
|
25
|
+
// Tech Stack (Tree style)
|
|
26
|
+
log.push(chalk.cyan.bold('Tech Stack'));
|
|
27
|
+
log.push(`${chalk.gray('├─')} Frontend ${chalk.white(frontendName)}`);
|
|
28
|
+
log.push(`${chalk.gray('├─')} Backend ${chalk.white(backendName)}`);
|
|
29
|
+
log.push(`${chalk.gray('└─')} Database ${chalk.white(dbName)}\n`);
|
|
30
|
+
|
|
31
|
+
// Next Steps
|
|
32
|
+
log.push(chalk.cyan.bold('Next Steps'));
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
message += chalk.gray(` Backend: ${backendName}\n`);
|
|
25
|
-
message += chalk.gray(` Database: ${dbName}\n\n`);
|
|
34
|
+
let stepNum = 1;
|
|
35
|
+
log.push(`${chalk.gray(`${stepNum++}.`)} cd ${projectName}`);
|
|
26
36
|
|
|
27
37
|
if (hasBackend && database !== 'none') {
|
|
28
|
-
|
|
29
|
-
message += chalk.white(` Edit ${chalk.cyan('backend/.env')} with your credentials\n\n`);
|
|
38
|
+
log.push(`${chalk.gray(`${stepNum++}.`)} Configure database inside ${chalk.white.bold('backend/.env')}`);
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
message += chalk.white(` cd ${projectName}\n`);
|
|
34
|
-
message += chalk.white(` cd frontend && npm install\n`);
|
|
41
|
+
log.push(`${chalk.gray(`${stepNum++}.`)} Start development servers:\n`);
|
|
35
42
|
|
|
36
43
|
if (hasBackend) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
log.push(chalk.gray(' # Terminal 1 (Backend)'));
|
|
45
|
+
log.push(` ${chalk.cyan('cd')} backend`);
|
|
46
|
+
log.push(` ${chalk.cyan(isPython ? 'uvicorn main:app --reload --port 5000' : 'npm run dev')}\n`);
|
|
47
|
+
|
|
48
|
+
log.push(chalk.gray(' # Terminal 2 (Frontend)'));
|
|
49
|
+
log.push(` ${chalk.cyan('cd')} frontend`);
|
|
50
|
+
log.push(` ${chalk.cyan('npm run dev')}\n`);
|
|
42
51
|
} else {
|
|
43
|
-
|
|
52
|
+
log.push(` ${chalk.cyan('cd')} frontend`);
|
|
53
|
+
log.push(` ${chalk.cyan('npm run dev')}\n`);
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
// Footer info
|
|
57
|
+
log.push(chalk.gray('─'.repeat(45)));
|
|
58
|
+
log.push(`${chalk.gray('Local:')} ${chalk.white('http://localhost:3000')}`);
|
|
47
59
|
|
|
48
60
|
if (hasBackend) {
|
|
49
|
-
|
|
50
|
-
message += chalk.white(` cd backend && ${isPython ? 'uvicorn main:app --reload --port 5000' : 'npm run dev'}\n\n`);
|
|
51
|
-
message += chalk.gray(' # Terminal 2 - Frontend:\n');
|
|
61
|
+
log.push(`${chalk.gray('API:')} ${chalk.white('http://localhost:5000')}`);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
message += chalk.cyan('🌐 URLs:\n');
|
|
57
|
-
message += chalk.white(' Frontend: http://localhost:3000\n');
|
|
58
|
-
|
|
59
|
-
if (hasBackend) {
|
|
60
|
-
message += chalk.white(' Backend: http://localhost:5000\n');
|
|
61
|
-
message += chalk.white(' Health: http://localhost:5000/api/health\n\n');
|
|
62
|
-
} else {
|
|
63
|
-
message += '\n';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
message += chalk.magenta('👁️ Backend Status Indicator:\n');
|
|
67
|
-
message += chalk.gray(' Look for the status badge in the top-right corner!');
|
|
64
|
+
log.push(`${chalk.gray('Note:')} Check the UI status badge for connection status.`);
|
|
68
65
|
|
|
69
66
|
console.log(
|
|
70
|
-
boxen(
|
|
71
|
-
padding: 1,
|
|
72
|
-
margin: 1,
|
|
67
|
+
boxen(log.join('\n'), {
|
|
68
|
+
padding: { top: 1, bottom: 1, left: 2, right: 3 },
|
|
69
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
73
70
|
borderStyle: 'round',
|
|
74
|
-
borderColor: '
|
|
71
|
+
borderColor: 'gray'
|
|
75
72
|
})
|
|
76
73
|
);
|
|
77
74
|
}
|