create-fullstack-setup 1.0.8 ā 1.0.10
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/bin/index.js +10 -78
- package/package.json +4 -2
- package/utils/features.config.js +11 -35
- package/utils/injectFeatures.js +16 -38
package/bin/index.js
CHANGED
|
@@ -6,117 +6,67 @@ import { createProject } from "../utils/installer.js";
|
|
|
6
6
|
|
|
7
7
|
console.log(chalk.green("\nš Welcome to FullStack App Generator\n"));
|
|
8
8
|
|
|
9
|
-
/* -------------------------------------------------------------------------- */
|
|
10
|
-
/* PROMPTS */
|
|
11
|
-
/* -------------------------------------------------------------------------- */
|
|
12
|
-
|
|
13
9
|
const answers = await inquirer.prompt([
|
|
14
10
|
{
|
|
15
11
|
type: "input",
|
|
16
12
|
name: "projectName",
|
|
17
13
|
message: "Project name:",
|
|
18
|
-
validate: input => (input ? true : "Project name is required")
|
|
14
|
+
validate: (input) => (input ? true : "Project name is required")
|
|
19
15
|
},
|
|
20
16
|
{
|
|
21
|
-
type: "
|
|
17
|
+
type: "rawlist",
|
|
22
18
|
name: "frontend",
|
|
23
19
|
message: "Choose frontend:",
|
|
24
20
|
choices: ["React", "Next.js", "None"]
|
|
25
21
|
},
|
|
26
22
|
{
|
|
27
|
-
type: "
|
|
23
|
+
type: "rawlist",
|
|
28
24
|
name: "backend",
|
|
29
25
|
message: "Choose backend:",
|
|
30
|
-
choices: ["Express"]
|
|
26
|
+
choices: ["Express"]
|
|
31
27
|
},
|
|
32
28
|
{
|
|
33
|
-
type: "
|
|
29
|
+
type: "rawlist",
|
|
34
30
|
name: "language",
|
|
35
31
|
message: "Choose language:",
|
|
36
32
|
choices: ["JavaScript", "TypeScript"]
|
|
37
33
|
},
|
|
38
|
-
|
|
39
|
-
/* ----------------------- Backend Feature Gate ---------------------------- */
|
|
40
|
-
|
|
41
34
|
{
|
|
42
35
|
type: "confirm",
|
|
43
36
|
name: "useBackendFeatures",
|
|
44
37
|
message: "Select backend features?",
|
|
45
38
|
default: true
|
|
46
39
|
},
|
|
47
|
-
|
|
48
|
-
/* ---------------------- Conditional Feature Prompts ---------------------- */
|
|
49
|
-
|
|
50
40
|
{
|
|
51
41
|
type: "confirm",
|
|
52
42
|
name: "useJWT",
|
|
53
43
|
message: "Use JWT authentication?",
|
|
54
44
|
default: true,
|
|
55
|
-
when: ans => ans.useBackendFeatures
|
|
45
|
+
when: (ans) => ans.useBackendFeatures
|
|
56
46
|
},
|
|
57
47
|
{
|
|
58
48
|
type: "confirm",
|
|
59
49
|
name: "useCORS",
|
|
60
50
|
message: "Enable CORS?",
|
|
61
51
|
default: true,
|
|
62
|
-
when: ans => ans.useBackendFeatures
|
|
52
|
+
when: (ans) => ans.useBackendFeatures
|
|
63
53
|
},
|
|
64
54
|
{
|
|
65
55
|
type: "confirm",
|
|
66
56
|
name: "useCookieParser",
|
|
67
57
|
message: "Use Cookie Parser?",
|
|
68
58
|
default: true,
|
|
69
|
-
when: ans => ans.useBackendFeatures
|
|
59
|
+
when: (ans) => ans.useBackendFeatures
|
|
70
60
|
},
|
|
71
61
|
{
|
|
72
62
|
type: "confirm",
|
|
73
63
|
name: "useCloudinary",
|
|
74
64
|
message: "Use Cloudinary?",
|
|
75
65
|
default: true,
|
|
76
|
-
when: ans => ans.useBackendFeatures
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
type: "confirm",
|
|
80
|
-
name: "useMongoose",
|
|
81
|
-
message: "Use MongoDB (Mongoose)?",
|
|
82
|
-
default: true,
|
|
83
|
-
when: ans => ans.useBackendFeatures
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
type: "confirm",
|
|
87
|
-
name: "useZod",
|
|
88
|
-
message: "Use Zod validation?",
|
|
89
|
-
default: true,
|
|
90
|
-
when: ans => ans.useBackendFeatures
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
type: "confirm",
|
|
94
|
-
name: "useDotenv",
|
|
95
|
-
message: "Use Dotenv?",
|
|
96
|
-
default: true,
|
|
97
|
-
when: ans => ans.useBackendFeatures
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
type: "confirm",
|
|
101
|
-
name: "useBcrypt",
|
|
102
|
-
message: "Use Bcrypt for password hashing?",
|
|
103
|
-
default: true,
|
|
104
|
-
when: ans => ans.useBackendFeatures
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
type: "confirm",
|
|
108
|
-
name: "useMulter",
|
|
109
|
-
message: "Use Multer for file uploads?",
|
|
110
|
-
default: true,
|
|
111
|
-
when: ans => ans.useBackendFeatures
|
|
66
|
+
when: (ans) => ans.useBackendFeatures
|
|
112
67
|
}
|
|
113
68
|
]);
|
|
114
69
|
|
|
115
|
-
|
|
116
|
-
/* -------------------------------------------------------------------------- */
|
|
117
|
-
/* MAP ANSWERS ā TEMPLATE KEYS */
|
|
118
|
-
/* -------------------------------------------------------------------------- */
|
|
119
|
-
|
|
120
70
|
const frontendMap = {
|
|
121
71
|
React: {
|
|
122
72
|
JavaScript: "react-js",
|
|
@@ -136,28 +86,14 @@ const backendMap = {
|
|
|
136
86
|
}
|
|
137
87
|
};
|
|
138
88
|
|
|
139
|
-
/* -------------------------------------------------------------------------- */
|
|
140
|
-
/* BUILD backendFeatures ARRAY (IMPORTANT) */
|
|
141
|
-
/* -------------------------------------------------------------------------- */
|
|
142
|
-
|
|
143
89
|
const backendFeatures = [];
|
|
144
|
-
|
|
145
90
|
if (answers.useBackendFeatures) {
|
|
146
91
|
if (answers.useJWT) backendFeatures.push("JWT");
|
|
147
92
|
if (answers.useCORS) backendFeatures.push("CORS");
|
|
148
93
|
if (answers.useCookieParser) backendFeatures.push("Cookie-Parser");
|
|
149
|
-
if (answers.useMongoose) backendFeatures.push("Mongoose");
|
|
150
94
|
if (answers.useCloudinary) backendFeatures.push("Cloudinary");
|
|
151
|
-
if (answers.useZod) backendFeatures.push("Zod");
|
|
152
|
-
if (answers.useDotenv) backendFeatures.push("Dotenv");
|
|
153
|
-
if (answers.useBcrypt) backendFeatures.push("Bcrypt");
|
|
154
|
-
if (answers.useMulter) backendFeatures.push("Multer");
|
|
155
95
|
}
|
|
156
96
|
|
|
157
|
-
/* -------------------------------------------------------------------------- */
|
|
158
|
-
/* BUILD CONFIG OBJECT */
|
|
159
|
-
/* -------------------------------------------------------------------------- */
|
|
160
|
-
|
|
161
97
|
const config = {
|
|
162
98
|
projectName: answers.projectName,
|
|
163
99
|
frontend:
|
|
@@ -165,11 +101,7 @@ const config = {
|
|
|
165
101
|
? "None"
|
|
166
102
|
: frontendMap[answers.frontend][answers.language],
|
|
167
103
|
backend: backendMap[answers.backend][answers.language],
|
|
168
|
-
backendFeatures
|
|
104
|
+
backendFeatures
|
|
169
105
|
};
|
|
170
106
|
|
|
171
|
-
/* -------------------------------------------------------------------------- */
|
|
172
|
-
/* CREATE PROJECT */
|
|
173
|
-
/* -------------------------------------------------------------------------- */
|
|
174
|
-
|
|
175
107
|
await createProject(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fullstack-setup",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "CLI to generate ready-to-run fullstack or backend applications",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-fullstack-setup": "bin/index.js"
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"chalk": "^5.6.2",
|
|
35
|
-
"
|
|
35
|
+
"fs-extra": "^11.3.3",
|
|
36
|
+
"inquirer": "^13.2.1",
|
|
37
|
+
"ora": "^9.1.0"
|
|
36
38
|
}
|
|
37
39
|
}
|
package/utils/features.config.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/* -------------------------------------------------------------------------- */
|
|
2
|
-
/* Backend Feature Configuration */
|
|
3
|
-
/* -------------------------------------------------------------------------- */
|
|
4
|
-
|
|
5
1
|
export const FEATURES = {
|
|
6
|
-
/* =============================== CORS ================================= */
|
|
7
|
-
|
|
8
2
|
CORS: {
|
|
9
3
|
middleware: `
|
|
10
4
|
import cors from "cors";
|
|
@@ -13,8 +7,6 @@ app.use(cors());
|
|
|
13
7
|
env: []
|
|
14
8
|
},
|
|
15
9
|
|
|
16
|
-
/* =========================== COOKIE PARSER ============================= */
|
|
17
|
-
|
|
18
10
|
"Cookie-Parser": {
|
|
19
11
|
middleware: `
|
|
20
12
|
import cookieParser from "cookie-parser";
|
|
@@ -23,48 +15,32 @@ app.use(cookieParser());
|
|
|
23
15
|
env: []
|
|
24
16
|
},
|
|
25
17
|
|
|
26
|
-
/* ================================ JWT ================================= */
|
|
27
|
-
|
|
28
18
|
JWT: {
|
|
29
19
|
middleware: "",
|
|
30
20
|
files: {
|
|
31
21
|
js: [
|
|
32
|
-
{
|
|
33
|
-
path: "middlewares/Auth.middleware.js",
|
|
34
|
-
fromTemplate:true,
|
|
35
|
-
}
|
|
22
|
+
{ path: "middlewares/Auth.middleware.js", fromTemplate: true }
|
|
36
23
|
],
|
|
37
24
|
ts: [
|
|
38
|
-
{
|
|
39
|
-
path: "src/middlewares/auth.middleware.ts",
|
|
40
|
-
fromTemplate:true,
|
|
41
|
-
}
|
|
25
|
+
{ path: "src/middlewares/auth.middleware.ts", fromTemplate: true }
|
|
42
26
|
]
|
|
43
27
|
},
|
|
28
|
+
env: ["ACCESS_TOKEN_SECRET=your_secret"]
|
|
44
29
|
},
|
|
45
30
|
|
|
46
|
-
/* ============================ CLOUDINARY ============================== */
|
|
47
|
-
|
|
48
31
|
Cloudinary: {
|
|
49
32
|
middleware: "",
|
|
50
33
|
files: {
|
|
51
|
-
js: [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
path: "src/utils/Cloudinary.ts",
|
|
60
|
-
fromTemplate:true,
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
|
34
|
+
js: [{ path: "Utils/Cloudinary.js", fromTemplate: true }],
|
|
35
|
+
ts: [{ path: "src/utils/Cloudinary.ts", fromTemplate: true }]
|
|
36
|
+
},
|
|
37
|
+
env: [
|
|
38
|
+
"CLOUDINARY_NAME=",
|
|
39
|
+
"CLOUDINARY_API_KEY=",
|
|
40
|
+
"CLOUDINARY_API_SECRET="
|
|
41
|
+
]
|
|
64
42
|
},
|
|
65
43
|
|
|
66
|
-
/* ============================== DOTENV ================================ */
|
|
67
|
-
|
|
68
44
|
Dotenv: {
|
|
69
45
|
middleware: `import "dotenv/config";`,
|
|
70
46
|
env: ["PORT=5000"]
|
package/utils/injectFeatures.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
3
4
|
import { FEATURES } from "./features.config.js";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const CLI_ROOT = path.join(__dirname, "..");
|
|
8
9
|
|
|
9
10
|
function isTypeScriptBackend(serverPath) {
|
|
10
11
|
return (
|
|
@@ -31,10 +32,6 @@ function findServerFile(serverPath) {
|
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/* -------------------------------------------------------------------------- */
|
|
35
|
-
/* Inject Features */
|
|
36
|
-
/* -------------------------------------------------------------------------- */
|
|
37
|
-
|
|
38
35
|
export function injectFeatures(serverPath, selectedFeatures = []) {
|
|
39
36
|
if (!Array.isArray(selectedFeatures) || selectedFeatures.length === 0) return;
|
|
40
37
|
|
|
@@ -48,29 +45,23 @@ export function injectFeatures(serverPath, selectedFeatures = []) {
|
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
let appContent = fs.readFileSync(appFile, "utf8");
|
|
51
|
-
let serverContent = serverFile
|
|
52
|
-
? fs.readFileSync(serverFile, "utf8")
|
|
53
|
-
: "";
|
|
48
|
+
let serverContent = serverFile ? fs.readFileSync(serverFile, "utf8") : "";
|
|
54
49
|
|
|
55
50
|
let middlewareCode = "";
|
|
56
51
|
let envVars = [];
|
|
57
52
|
|
|
58
|
-
/* ---------------------------------------------------------------------- */
|
|
59
|
-
/* Process each feature */
|
|
60
|
-
/* ---------------------------------------------------------------------- */
|
|
61
|
-
|
|
62
53
|
for (const feature of selectedFeatures) {
|
|
63
54
|
const config = FEATURES[feature];
|
|
64
55
|
if (!config) continue;
|
|
65
56
|
|
|
66
|
-
const featureKey = feature.toLowerCase();
|
|
57
|
+
const featureKey = feature.toLowerCase();
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
// Middlewares
|
|
69
60
|
if (config.middleware && !appContent.includes(config.middleware)) {
|
|
70
61
|
middlewareCode += `${config.middleware}\n`;
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
// Files
|
|
74
65
|
if (config.files) {
|
|
75
66
|
const files = isTS ? config.files.ts : config.files.js;
|
|
76
67
|
|
|
@@ -81,10 +72,9 @@ export function injectFeatures(serverPath, selectedFeatures = []) {
|
|
|
81
72
|
|
|
82
73
|
if (fs.existsSync(filePath)) continue;
|
|
83
74
|
|
|
84
|
-
// ā
FIX: support fromTemplate
|
|
85
75
|
if (file.fromTemplate) {
|
|
86
76
|
const templatePath = path.join(
|
|
87
|
-
|
|
77
|
+
CLI_ROOT,
|
|
88
78
|
"templates",
|
|
89
79
|
"backend",
|
|
90
80
|
isTS ? "express-ts" : "express-js",
|
|
@@ -98,23 +88,21 @@ export function injectFeatures(serverPath, selectedFeatures = []) {
|
|
|
98
88
|
|
|
99
89
|
fs.copyFileSync(templatePath, filePath);
|
|
100
90
|
} else if (file.content) {
|
|
101
|
-
fs.writeFileSync(filePath, file.content.trim());
|
|
91
|
+
fs.writeFileSync(filePath, file.content.trim(), "utf8");
|
|
102
92
|
}
|
|
103
93
|
}
|
|
104
94
|
}
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
|
|
97
|
+
// ENV
|
|
108
98
|
if (Array.isArray(config.env)) {
|
|
109
99
|
envVars.push(...config.env);
|
|
110
100
|
}
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
if (featureKey === "
|
|
102
|
+
// Cloudinary injection
|
|
103
|
+
if (featureKey === "cloudinary" && serverFile) {
|
|
114
104
|
if (!serverContent.includes("connectCloudinary")) {
|
|
115
|
-
const importPath = isTS
|
|
116
|
-
? "./utils/Cloudinary"
|
|
117
|
-
: "./Utils/Cloudinary.js";
|
|
105
|
+
const importPath = isTS ? "./utils/Cloudinary" : "./Utils/Cloudinary.js";
|
|
118
106
|
|
|
119
107
|
serverContent = serverContent.replace(
|
|
120
108
|
/app\.listen|server\.listen/,
|
|
@@ -128,10 +116,7 @@ $&`
|
|
|
128
116
|
}
|
|
129
117
|
}
|
|
130
118
|
|
|
131
|
-
|
|
132
|
-
/* Inject middlewares */
|
|
133
|
-
/* ---------------------------------------------------------------------- */
|
|
134
|
-
|
|
119
|
+
// Inject middleware into app file
|
|
135
120
|
if (middlewareCode && appContent.includes("__INJECT_MIDDLEWARES__")) {
|
|
136
121
|
appContent = appContent.replace(
|
|
137
122
|
"// __INJECT_MIDDLEWARES__",
|
|
@@ -141,18 +126,11 @@ $&`
|
|
|
141
126
|
|
|
142
127
|
fs.writeFileSync(appFile, appContent, "utf8");
|
|
143
128
|
|
|
144
|
-
/* ---------------------------------------------------------------------- */
|
|
145
|
-
/* Write server file */
|
|
146
|
-
/* ---------------------------------------------------------------------- */
|
|
147
|
-
|
|
148
129
|
if (serverFile && serverContent) {
|
|
149
130
|
fs.writeFileSync(serverFile, serverContent, "utf8");
|
|
150
131
|
}
|
|
151
132
|
|
|
152
|
-
|
|
153
|
-
/* Append env variables */
|
|
154
|
-
/* ---------------------------------------------------------------------- */
|
|
155
|
-
|
|
133
|
+
// Append ENV vars
|
|
156
134
|
const envExamplePath = path.join(serverPath, ".env.example");
|
|
157
135
|
|
|
158
136
|
if (envVars.length && fs.existsSync(envExamplePath)) {
|