create-parachute 1.0.2
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 +27 -0
- package/bin/index.js +474 -0
- package/package.json +29 -0
- package/templates/mysql/client/index.html +12 -0
- package/templates/mysql/client/package.json +18 -0
- package/templates/mysql/client/src/App.jsx +5 -0
- package/templates/mysql/client/src/api/client.js +6 -0
- package/templates/mysql/client/src/components/Navbar.jsx +57 -0
- package/templates/mysql/client/src/components/ProtectedRoute.jsx +10 -0
- package/templates/mysql/client/src/layouts/DashboardLayout.jsx +21 -0
- package/templates/mysql/client/src/main.jsx +9 -0
- package/templates/mysql/client/src/pages/Dashboard.jsx +38 -0
- package/templates/mysql/client/src/pages/Login.jsx +138 -0
- package/templates/mysql/client/src/pages/Page1.jsx +38 -0
- package/templates/mysql/client/src/pages/Page2.jsx +38 -0
- package/templates/mysql/client/src/pages/Page3.jsx +38 -0
- package/templates/mysql/client/src/router/index.jsx +43 -0
- package/templates/mysql/client/src/utils/auth.js +16 -0
- package/templates/mysql/client/vite.config.js +6 -0
- package/templates/mysql/server/config/db.js +13 -0
- package/templates/mysql/server/controllers/authController.js +66 -0
- package/templates/mysql/server/index.js +39 -0
- package/templates/mysql/server/middleware/authMiddleware.js +7 -0
- package/templates/mysql/server/package.json +18 -0
- package/templates/mysql/server/routes/authRoutes.js +14 -0
- package/templates/sequelize/client/index.html +12 -0
- package/templates/sequelize/client/package.json +18 -0
- package/templates/sequelize/client/src/App.jsx +5 -0
- package/templates/sequelize/client/src/api/client.js +6 -0
- package/templates/sequelize/client/src/components/Navbar.jsx +57 -0
- package/templates/sequelize/client/src/components/ProtectedRoute.jsx +10 -0
- package/templates/sequelize/client/src/layouts/DashboardLayout.jsx +21 -0
- package/templates/sequelize/client/src/main.jsx +9 -0
- package/templates/sequelize/client/src/pages/Dashboard.jsx +38 -0
- package/templates/sequelize/client/src/pages/Login.jsx +138 -0
- package/templates/sequelize/client/src/pages/Page1.jsx +38 -0
- package/templates/sequelize/client/src/pages/Page2.jsx +38 -0
- package/templates/sequelize/client/src/pages/Page3.jsx +38 -0
- package/templates/sequelize/client/src/router/index.jsx +43 -0
- package/templates/sequelize/client/src/utils/auth.js +16 -0
- package/templates/sequelize/client/vite.config.js +6 -0
- package/templates/sequelize/server/config/db.js +13 -0
- package/templates/sequelize/server/config/sequelize.js +17 -0
- package/templates/sequelize/server/controllers/authController.js +62 -0
- package/templates/sequelize/server/index.js +42 -0
- package/templates/sequelize/server/middleware/authMiddleware.js +7 -0
- package/templates/sequelize/server/models/User.js +24 -0
- package/templates/sequelize/server/package.json +19 -0
- package/templates/sequelize/server/routes/authRoutes.js +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
# Create Parachute
|
|
3
|
+
|
|
4
|
+
A clean fullstack starter generator for students.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx create-parachute my-project
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The generator creates:
|
|
13
|
+
|
|
14
|
+
- Express backend
|
|
15
|
+
- React + Vite frontend
|
|
16
|
+
- Raw MySQL or Sequelize option
|
|
17
|
+
- Session authentication
|
|
18
|
+
- Username login
|
|
19
|
+
- Hashed passwords
|
|
20
|
+
- React Router navigation
|
|
21
|
+
- Protected routes
|
|
22
|
+
- Dashboard + 3 pages
|
|
23
|
+
- `schema.sql`
|
|
24
|
+
- `instructions.md`
|
|
25
|
+
- `tailwind.md`
|
|
26
|
+
- `sql.md`
|
|
27
|
+
- Optional dependency installation
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
let projectName = process.argv[2];
|
|
13
|
+
|
|
14
|
+
if (!projectName) {
|
|
15
|
+
const projectAnswer = await inquirer.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: "input",
|
|
18
|
+
name: "projectName",
|
|
19
|
+
message: "Project name:",
|
|
20
|
+
validate: (input) => {
|
|
21
|
+
if (!input.trim()) {
|
|
22
|
+
return "Project name is required.";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(input)) {
|
|
26
|
+
return "Only letters, numbers, hyphens, and underscores are allowed.";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
},
|
|
31
|
+
filter: (input) => input.trim()
|
|
32
|
+
}
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
projectName = projectAnswer.projectName;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const answers = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: "input",
|
|
41
|
+
name: "dbName",
|
|
42
|
+
message: "Database name:",
|
|
43
|
+
default: `${projectName.replace(/-/g, "_")}_db`
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: "input",
|
|
47
|
+
name: "serverPort",
|
|
48
|
+
message: "Backend port:",
|
|
49
|
+
default: "5000"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "input",
|
|
53
|
+
name: "clientPort",
|
|
54
|
+
message: "Frontend port:",
|
|
55
|
+
default: "5173"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "list",
|
|
59
|
+
name: "database",
|
|
60
|
+
message: "Choose database style:",
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: "Raw MySQL", value: "mysql" },
|
|
63
|
+
{ name: "Sequelize", value: "sequelize" }
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "list",
|
|
68
|
+
name: "theme",
|
|
69
|
+
message: "Choose theme:",
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: chalk.blue("Blue"), value: "#2563EB" },
|
|
72
|
+
{ name: chalk.green("Green"), value: "#16A34A" },
|
|
73
|
+
{ name: chalk.red("Red"), value: "#DC2626" },
|
|
74
|
+
{ name: chalk.magenta("Purple"), value: "#9333EA" },
|
|
75
|
+
{ name: chalk.yellow("Yellow"), value: "#CA8A04" },
|
|
76
|
+
{ name: chalk.cyan("Cyan"), value: "#0891B2" },
|
|
77
|
+
{ name: chalk.hex("#800000")("Maroon"), value: "#800000" }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "confirm",
|
|
82
|
+
name: "installDeps",
|
|
83
|
+
message: "Install backend and frontend dependencies now?",
|
|
84
|
+
default: true
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const template = path.join(__dirname, "..", "templates", answers.database);
|
|
89
|
+
const target = path.join(process.cwd(), projectName);
|
|
90
|
+
|
|
91
|
+
if (fs.existsSync(target)) {
|
|
92
|
+
console.log(chalk.red("Folder already exists. Choose another project name."));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await fs.copy(template, target);
|
|
97
|
+
|
|
98
|
+
const env = `PORT=${answers.serverPort}
|
|
99
|
+
DB_HOST=localhost
|
|
100
|
+
DB_USER=root
|
|
101
|
+
DB_PASSWORD=
|
|
102
|
+
DB_NAME=${answers.dbName}
|
|
103
|
+
CLIENT_URL=http://localhost:${answers.clientPort}
|
|
104
|
+
SESSION_SECRET=parachute_secret`;
|
|
105
|
+
|
|
106
|
+
await fs.writeFile(path.join(target, "server/.env"), env);
|
|
107
|
+
|
|
108
|
+
const schema = `CREATE DATABASE IF NOT EXISTS ${answers.dbName};
|
|
109
|
+
USE ${answers.dbName};
|
|
110
|
+
|
|
111
|
+
CREATE TABLE Users(
|
|
112
|
+
UserID INT AUTO_INCREMENT PRIMARY KEY,
|
|
113
|
+
Username VARCHAR(255) UNIQUE NOT NULL,
|
|
114
|
+
Password VARCHAR(255) NOT NULL
|
|
115
|
+
);`;
|
|
116
|
+
|
|
117
|
+
await fs.writeFile(path.join(target, "schema.sql"), schema);
|
|
118
|
+
|
|
119
|
+
const instructions = `# ${projectName}
|
|
120
|
+
|
|
121
|
+
## 1. Run the database file
|
|
122
|
+
|
|
123
|
+
Open MySQL and run schema.sql.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
SOURCE /full/path/to/${projectName}/schema.sql;
|
|
128
|
+
|
|
129
|
+
## 2. Start backend
|
|
130
|
+
|
|
131
|
+
cd server
|
|
132
|
+
npm run dev
|
|
133
|
+
|
|
134
|
+
## 3. Start frontend
|
|
135
|
+
|
|
136
|
+
cd client
|
|
137
|
+
npm run dev
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- Backend runs on port ${answers.serverPort}
|
|
142
|
+
- Frontend runs on port ${answers.clientPort}
|
|
143
|
+
- Auth uses Username and hashed Password
|
|
144
|
+
- Add your own backend features inside controllers and routes
|
|
145
|
+
- Add your own frontend screens inside client/src/pages
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
await fs.writeFile(path.join(target, "instructions.md"), instructions);
|
|
149
|
+
|
|
150
|
+
const tailwindCheat = `# Tailwind Cheat Sheet
|
|
151
|
+
|
|
152
|
+
This project uses simple CSS for maximum beginner-friendliness, but this file helps you style fast if you add Tailwind later.
|
|
153
|
+
|
|
154
|
+
## Layout
|
|
155
|
+
|
|
156
|
+
flex
|
|
157
|
+
grid
|
|
158
|
+
block
|
|
159
|
+
hidden
|
|
160
|
+
relative
|
|
161
|
+
absolute
|
|
162
|
+
fixed
|
|
163
|
+
sticky
|
|
164
|
+
|
|
165
|
+
## Flexbox
|
|
166
|
+
|
|
167
|
+
flex
|
|
168
|
+
flex-col
|
|
169
|
+
flex-row
|
|
170
|
+
items-center
|
|
171
|
+
items-start
|
|
172
|
+
items-end
|
|
173
|
+
justify-center
|
|
174
|
+
justify-between
|
|
175
|
+
justify-end
|
|
176
|
+
gap-2
|
|
177
|
+
gap-4
|
|
178
|
+
gap-6
|
|
179
|
+
|
|
180
|
+
## Grid
|
|
181
|
+
|
|
182
|
+
grid
|
|
183
|
+
grid-cols-1
|
|
184
|
+
grid-cols-2
|
|
185
|
+
grid-cols-3
|
|
186
|
+
md:grid-cols-2
|
|
187
|
+
lg:grid-cols-3
|
|
188
|
+
|
|
189
|
+
## Spacing
|
|
190
|
+
|
|
191
|
+
p-2
|
|
192
|
+
p-4
|
|
193
|
+
p-6
|
|
194
|
+
p-8
|
|
195
|
+
px-4
|
|
196
|
+
px-6
|
|
197
|
+
py-2
|
|
198
|
+
py-3
|
|
199
|
+
py-4
|
|
200
|
+
m-2
|
|
201
|
+
m-4
|
|
202
|
+
mt-4
|
|
203
|
+
mb-4
|
|
204
|
+
mx-auto
|
|
205
|
+
|
|
206
|
+
## Width and Height
|
|
207
|
+
|
|
208
|
+
w-full
|
|
209
|
+
w-screen
|
|
210
|
+
max-w-md
|
|
211
|
+
max-w-xl
|
|
212
|
+
max-w-6xl
|
|
213
|
+
min-h-screen
|
|
214
|
+
h-screen
|
|
215
|
+
|
|
216
|
+
## Typography
|
|
217
|
+
|
|
218
|
+
text-sm
|
|
219
|
+
text-base
|
|
220
|
+
text-lg
|
|
221
|
+
text-xl
|
|
222
|
+
text-2xl
|
|
223
|
+
font-medium
|
|
224
|
+
font-semibold
|
|
225
|
+
font-bold
|
|
226
|
+
text-center
|
|
227
|
+
|
|
228
|
+
## Colors
|
|
229
|
+
|
|
230
|
+
bg-white
|
|
231
|
+
bg-black
|
|
232
|
+
bg-gray-50
|
|
233
|
+
bg-gray-100
|
|
234
|
+
text-black
|
|
235
|
+
text-white
|
|
236
|
+
text-gray-500
|
|
237
|
+
text-gray-700
|
|
238
|
+
|
|
239
|
+
## Borders and Radius
|
|
240
|
+
|
|
241
|
+
border
|
|
242
|
+
border-gray-200
|
|
243
|
+
rounded
|
|
244
|
+
rounded-lg
|
|
245
|
+
rounded-xl
|
|
246
|
+
rounded-2xl
|
|
247
|
+
|
|
248
|
+
## Shadows
|
|
249
|
+
|
|
250
|
+
shadow
|
|
251
|
+
shadow-md
|
|
252
|
+
shadow-lg
|
|
253
|
+
|
|
254
|
+
## Buttons
|
|
255
|
+
|
|
256
|
+
bg-black text-white px-4 py-2 rounded-lg
|
|
257
|
+
bg-blue-600 text-white px-4 py-2 rounded-lg
|
|
258
|
+
|
|
259
|
+
## Forms
|
|
260
|
+
|
|
261
|
+
border rounded-lg px-4 py-2 w-full
|
|
262
|
+
focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
263
|
+
|
|
264
|
+
## Responsive Examples
|
|
265
|
+
|
|
266
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div className="p-4 md:p-8">
|
|
270
|
+
</div>
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
await fs.writeFile(path.join(target, "tailwind.md"), tailwindCheat);
|
|
274
|
+
|
|
275
|
+
const sqlCheat = `# SQL Cheat Sheet
|
|
276
|
+
|
|
277
|
+
## Create Database
|
|
278
|
+
|
|
279
|
+
CREATE DATABASE school_db;
|
|
280
|
+
USE school_db;
|
|
281
|
+
|
|
282
|
+
## Create Table
|
|
283
|
+
|
|
284
|
+
CREATE TABLE Students(
|
|
285
|
+
StudentID INT AUTO_INCREMENT PRIMARY KEY,
|
|
286
|
+
StudentName VARCHAR(100) NOT NULL,
|
|
287
|
+
Age INT
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
## Insert
|
|
291
|
+
|
|
292
|
+
INSERT INTO Students (StudentName, Age)
|
|
293
|
+
VALUES ('Aime', 20);
|
|
294
|
+
|
|
295
|
+
## Select All
|
|
296
|
+
|
|
297
|
+
SELECT * FROM Students;
|
|
298
|
+
|
|
299
|
+
## Select Specific Columns
|
|
300
|
+
|
|
301
|
+
SELECT StudentName, Age FROM Students;
|
|
302
|
+
|
|
303
|
+
## Filter with WHERE
|
|
304
|
+
|
|
305
|
+
SELECT * FROM Students
|
|
306
|
+
WHERE Age > 18;
|
|
307
|
+
|
|
308
|
+
## Search with LIKE
|
|
309
|
+
|
|
310
|
+
SELECT * FROM Students
|
|
311
|
+
WHERE StudentName LIKE '%ai%';
|
|
312
|
+
|
|
313
|
+
## Sort
|
|
314
|
+
|
|
315
|
+
SELECT * FROM Students
|
|
316
|
+
ORDER BY StudentName ASC;
|
|
317
|
+
|
|
318
|
+
SELECT * FROM Students
|
|
319
|
+
ORDER BY Age DESC;
|
|
320
|
+
|
|
321
|
+
## Limit
|
|
322
|
+
|
|
323
|
+
SELECT * FROM Students
|
|
324
|
+
LIMIT 5;
|
|
325
|
+
|
|
326
|
+
## Update
|
|
327
|
+
|
|
328
|
+
UPDATE Students
|
|
329
|
+
SET Age = 21
|
|
330
|
+
WHERE StudentID = 1;
|
|
331
|
+
|
|
332
|
+
## Delete
|
|
333
|
+
|
|
334
|
+
DELETE FROM Students
|
|
335
|
+
WHERE StudentID = 1;
|
|
336
|
+
|
|
337
|
+
## Count
|
|
338
|
+
|
|
339
|
+
SELECT COUNT(*) AS TotalStudents
|
|
340
|
+
FROM Students;
|
|
341
|
+
|
|
342
|
+
## Sum
|
|
343
|
+
|
|
344
|
+
SELECT SUM(Amount) AS TotalAmount
|
|
345
|
+
FROM Payments;
|
|
346
|
+
|
|
347
|
+
## Average
|
|
348
|
+
|
|
349
|
+
SELECT AVG(Marks) AS AverageMarks
|
|
350
|
+
FROM Results;
|
|
351
|
+
|
|
352
|
+
## Group By
|
|
353
|
+
|
|
354
|
+
SELECT ClassID, COUNT(*) AS TotalStudents
|
|
355
|
+
FROM Students
|
|
356
|
+
GROUP BY ClassID;
|
|
357
|
+
|
|
358
|
+
## Having
|
|
359
|
+
|
|
360
|
+
SELECT ClassID, COUNT(*) AS TotalStudents
|
|
361
|
+
FROM Students
|
|
362
|
+
GROUP BY ClassID
|
|
363
|
+
HAVING COUNT(*) > 5;
|
|
364
|
+
|
|
365
|
+
## Inner Join
|
|
366
|
+
|
|
367
|
+
SELECT Students.StudentName, Classes.ClassName
|
|
368
|
+
FROM Students
|
|
369
|
+
INNER JOIN Classes
|
|
370
|
+
ON Students.ClassID = Classes.ClassID;
|
|
371
|
+
|
|
372
|
+
## Left Join
|
|
373
|
+
|
|
374
|
+
SELECT Students.StudentName, Payments.Amount
|
|
375
|
+
FROM Students
|
|
376
|
+
LEFT JOIN Payments
|
|
377
|
+
ON Students.StudentID = Payments.StudentID;
|
|
378
|
+
|
|
379
|
+
## Multiple Joins
|
|
380
|
+
|
|
381
|
+
SELECT Students.StudentName, Classes.ClassName, Payments.Amount
|
|
382
|
+
FROM Students
|
|
383
|
+
JOIN Classes ON Students.ClassID = Classes.ClassID
|
|
384
|
+
JOIN Payments ON Students.StudentID = Payments.StudentID;
|
|
385
|
+
|
|
386
|
+
## Subquery
|
|
387
|
+
|
|
388
|
+
SELECT * FROM Students
|
|
389
|
+
WHERE ClassID IN (
|
|
390
|
+
SELECT ClassID FROM Classes WHERE ClassName = 'S6'
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
## Between
|
|
394
|
+
|
|
395
|
+
SELECT * FROM Payments
|
|
396
|
+
WHERE Amount BETWEEN 1000 AND 5000;
|
|
397
|
+
|
|
398
|
+
## Dates
|
|
399
|
+
|
|
400
|
+
SELECT * FROM Orders
|
|
401
|
+
WHERE DATE(CreatedAt) = CURDATE();
|
|
402
|
+
|
|
403
|
+
## Beginner Exam CRUD Pattern
|
|
404
|
+
|
|
405
|
+
GET all:
|
|
406
|
+
SELECT * FROM TableName;
|
|
407
|
+
|
|
408
|
+
GET one:
|
|
409
|
+
SELECT * FROM TableName WHERE ID = ?;
|
|
410
|
+
|
|
411
|
+
CREATE:
|
|
412
|
+
INSERT INTO TableName (Column1, Column2) VALUES (?, ?);
|
|
413
|
+
|
|
414
|
+
UPDATE:
|
|
415
|
+
UPDATE TableName SET Column1 = ?, Column2 = ? WHERE ID = ?;
|
|
416
|
+
|
|
417
|
+
DELETE:
|
|
418
|
+
DELETE FROM TableName WHERE ID = ?;
|
|
419
|
+
`;
|
|
420
|
+
|
|
421
|
+
await fs.writeFile(path.join(target, "sql.md"), sqlCheat);
|
|
422
|
+
|
|
423
|
+
async function inject(dir) {
|
|
424
|
+
const files = await fs.readdir(dir);
|
|
425
|
+
|
|
426
|
+
for (const file of files) {
|
|
427
|
+
const full = path.join(dir, file);
|
|
428
|
+
const stat = await fs.stat(full);
|
|
429
|
+
|
|
430
|
+
if (stat.isDirectory()) {
|
|
431
|
+
await inject(full);
|
|
432
|
+
} else {
|
|
433
|
+
try {
|
|
434
|
+
let content = await fs.readFile(full, "utf8");
|
|
435
|
+
content = content.replaceAll("__THEME__", answers.theme);
|
|
436
|
+
content = content.replaceAll("__PORT__", answers.serverPort);
|
|
437
|
+
await fs.writeFile(full, content);
|
|
438
|
+
} catch {}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
await inject(target);
|
|
444
|
+
|
|
445
|
+
console.log(chalk.white("\nProject created successfully.\n"));
|
|
446
|
+
|
|
447
|
+
if (answers.installDeps) {
|
|
448
|
+
console.log(chalk.white("Installing backend dependencies..."));
|
|
449
|
+
const serverInstall = spawnSync("npm", ["install"], {
|
|
450
|
+
cwd: path.join(target, "server"),
|
|
451
|
+
stdio: "inherit",
|
|
452
|
+
shell: true
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
if (serverInstall.status !== 0) {
|
|
456
|
+
console.log(chalk.red("Backend dependency installation failed. You can run npm install manually inside server."));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log(chalk.white("Installing frontend dependencies..."));
|
|
460
|
+
const clientInstall = spawnSync("npm", ["install"], {
|
|
461
|
+
cwd: path.join(target, "client"),
|
|
462
|
+
stdio: "inherit",
|
|
463
|
+
shell: true
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (clientInstall.status !== 0) {
|
|
467
|
+
console.log(chalk.red("Frontend dependency installation failed. You can run npm install manually inside client."));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
console.log(chalk.white("Next steps:"));
|
|
472
|
+
console.log(chalk.gray(`1. Run schema.sql in MySQL`));
|
|
473
|
+
console.log(chalk.gray(`2. cd ${projectName}/server && npm run dev`));
|
|
474
|
+
console.log(chalk.gray(`3. cd ${projectName}/client && npm run dev`));
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-parachute",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Student-friendly fullstack exam starter generator",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-parachute": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"student",
|
|
15
|
+
"exam",
|
|
16
|
+
"react",
|
|
17
|
+
"vite",
|
|
18
|
+
"express",
|
|
19
|
+
"mysql",
|
|
20
|
+
"sequelize"
|
|
21
|
+
],
|
|
22
|
+
"author": "Aime",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"fs-extra": "^11.2.0",
|
|
27
|
+
"inquirer": "^9.2.23"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Parachute</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"scripts": {
|
|
4
|
+
"dev": "vite",
|
|
5
|
+
"build": "vite build",
|
|
6
|
+
"preview": "vite preview"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"axios": "^1.7.2",
|
|
10
|
+
"react": "^18.3.1",
|
|
11
|
+
"react-dom": "^18.3.1",
|
|
12
|
+
"react-router-dom": "^6.30.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
16
|
+
"vite": "^5.3.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
2
|
+
import { api } from "../api/client";
|
|
3
|
+
import { clearUser, getUser } from "../utils/auth";
|
|
4
|
+
|
|
5
|
+
export default function Navbar() {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const user = getUser();
|
|
8
|
+
|
|
9
|
+
const logout = async () => {
|
|
10
|
+
try {
|
|
11
|
+
await api.post("/auth/logout");
|
|
12
|
+
} catch {}
|
|
13
|
+
|
|
14
|
+
clearUser();
|
|
15
|
+
navigate("/login");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<nav style={{
|
|
20
|
+
background: "white",
|
|
21
|
+
borderBottom: "1px solid #e5e7eb",
|
|
22
|
+
padding: "18px 32px",
|
|
23
|
+
display: "flex",
|
|
24
|
+
gap: "24px",
|
|
25
|
+
alignItems: "center"
|
|
26
|
+
}}>
|
|
27
|
+
<strong style={{ marginRight: "12px" }}>Parachute</strong>
|
|
28
|
+
|
|
29
|
+
<Link to="/">Dashboard</Link>
|
|
30
|
+
<Link to="/page1">Page 1</Link>
|
|
31
|
+
<Link to="/page2">Page 2</Link>
|
|
32
|
+
<Link to="/page3">Page 3</Link>
|
|
33
|
+
|
|
34
|
+
<span style={{
|
|
35
|
+
marginLeft: "auto",
|
|
36
|
+
color: "#6b7280",
|
|
37
|
+
fontSize: "14px"
|
|
38
|
+
}}>
|
|
39
|
+
{user?.Username}
|
|
40
|
+
</span>
|
|
41
|
+
|
|
42
|
+
<button
|
|
43
|
+
onClick={logout}
|
|
44
|
+
style={{
|
|
45
|
+
background: "__THEME__",
|
|
46
|
+
color: "white",
|
|
47
|
+
border: "none",
|
|
48
|
+
padding: "10px 16px",
|
|
49
|
+
borderRadius: "10px",
|
|
50
|
+
cursor: "pointer"
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
Logout
|
|
54
|
+
</button>
|
|
55
|
+
</nav>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Navbar from "../components/Navbar";
|
|
2
|
+
|
|
3
|
+
export default function DashboardLayout({ children }) {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{
|
|
6
|
+
minHeight: "100vh",
|
|
7
|
+
background: "#f8fafc",
|
|
8
|
+
fontFamily: "Arial, sans-serif"
|
|
9
|
+
}}>
|
|
10
|
+
<Navbar />
|
|
11
|
+
|
|
12
|
+
<main style={{
|
|
13
|
+
padding: "40px",
|
|
14
|
+
maxWidth: "1100px",
|
|
15
|
+
margin: "0 auto"
|
|
16
|
+
}}>
|
|
17
|
+
{children}
|
|
18
|
+
</main>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import DashboardLayout from "../layouts/DashboardLayout";
|
|
2
|
+
|
|
3
|
+
export default function Dashboard() {
|
|
4
|
+
return (
|
|
5
|
+
<DashboardLayout>
|
|
6
|
+
<section style={{
|
|
7
|
+
background: "white",
|
|
8
|
+
padding: "32px",
|
|
9
|
+
borderRadius: "18px",
|
|
10
|
+
border: "1px solid #e5e7eb",
|
|
11
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
|
|
12
|
+
}}>
|
|
13
|
+
<p style={{
|
|
14
|
+
color: "__THEME__",
|
|
15
|
+
fontWeight: "700",
|
|
16
|
+
marginBottom: "8px"
|
|
17
|
+
}}>
|
|
18
|
+
Ready to customize
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: "32px",
|
|
23
|
+
marginBottom: "12px"
|
|
24
|
+
}}>
|
|
25
|
+
Welcome Dashboard
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<p style={{
|
|
29
|
+
color: "#4b5563",
|
|
30
|
+
lineHeight: "1.7"
|
|
31
|
+
}}>
|
|
32
|
+
Start building your own features from this page. Add your forms,
|
|
33
|
+
tables, buttons, and API calls here.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
</DashboardLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|