create-fullstack-setup 1.0.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.
Files changed (57) hide show
  1. package/README.md +391 -0
  2. package/bin/index.js +175 -0
  3. package/package.json +19 -0
  4. package/templates/backend/express-js/.env.example +6 -0
  5. package/templates/backend/express-js/Utils/ApiError.js +17 -0
  6. package/templates/backend/express-js/Utils/ApiResponse.js +12 -0
  7. package/templates/backend/express-js/Utils/AsyncHandler.js +8 -0
  8. package/templates/backend/express-js/Utils/Cloudinary.js +31 -0
  9. package/templates/backend/express-js/app.js +35 -0
  10. package/templates/backend/express-js/config/db.js +18 -0
  11. package/templates/backend/express-js/middlewares/Auth.middleware.js +21 -0
  12. package/templates/backend/express-js/middlewares/Multer.middleware.js +18 -0
  13. package/templates/backend/express-js/package.json +17 -0
  14. package/templates/backend/express-js/server.js +10 -0
  15. package/templates/backend/express-ts/.env.example +3 -0
  16. package/templates/backend/express-ts/README.md +0 -0
  17. package/templates/backend/express-ts/nodemon.json +5 -0
  18. package/templates/backend/express-ts/package.json +20 -0
  19. package/templates/backend/express-ts/src/app.ts +14 -0
  20. package/templates/backend/express-ts/src/config/db.ts +0 -0
  21. package/templates/backend/express-ts/src/middlewares/auth.middleware.ts +23 -0
  22. package/templates/backend/express-ts/src/routes/index.ts +0 -0
  23. package/templates/backend/express-ts/src/server.ts +8 -0
  24. package/templates/backend/express-ts/src/utils/ApiError.ts +21 -0
  25. package/templates/backend/express-ts/src/utils/ApiResponse.ts +13 -0
  26. package/templates/backend/express-ts/src/utils/AsyncHandler.ts +9 -0
  27. package/templates/backend/express-ts/src/utils/Cloudinary.ts +11 -0
  28. package/templates/backend/express-ts/tsconfig.json +13 -0
  29. package/templates/frontend/next-js/app/layout.js +14 -0
  30. package/templates/frontend/next-js/app/page.js +8 -0
  31. package/templates/frontend/next-js/jsconfig.json +8 -0
  32. package/templates/frontend/next-js/next.config.js +6 -0
  33. package/templates/frontend/next-js/package.json +16 -0
  34. package/templates/frontend/next-ts/app/layout.tsx +18 -0
  35. package/templates/frontend/next-ts/app/page.tsx +7 -0
  36. package/templates/frontend/next-ts/next.config.js +6 -0
  37. package/templates/frontend/next-ts/package.json +22 -0
  38. package/templates/frontend/next-ts/tsconfig.json +17 -0
  39. package/templates/frontend/react-js/index.html +12 -0
  40. package/templates/frontend/react-js/package.json +19 -0
  41. package/templates/frontend/react-js/src/App.jsx +8 -0
  42. package/templates/frontend/react-js/src/index.css +0 -0
  43. package/templates/frontend/react-js/src/main.jsx +10 -0
  44. package/templates/frontend/react-js/vite.config.js +6 -0
  45. package/templates/frontend/react-ts/index.html +12 -0
  46. package/templates/frontend/react-ts/package.json +21 -0
  47. package/templates/frontend/react-ts/src/App.tsx +9 -0
  48. package/templates/frontend/react-ts/src/index.css +0 -0
  49. package/templates/frontend/react-ts/src/main.tsx +12 -0
  50. package/templates/frontend/react-ts/tsconfig.json +10 -0
  51. package/templates/frontend/react-ts/vite.config.ts +6 -0
  52. package/utils/features.config.js +72 -0
  53. package/utils/fileOps.js +0 -0
  54. package/utils/injectFeatures.js +171 -0
  55. package/utils/installer.js +157 -0
  56. package/utils/prompts.js +59 -0
  57. package/utils/replacePlaceholders.js +54 -0
@@ -0,0 +1,171 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { FEATURES } from "./features.config.js";
4
+
5
+ /* -------------------------------------------------------------------------- */
6
+ /* Helpers (JS / TS aware) */
7
+ /* -------------------------------------------------------------------------- */
8
+
9
+ function isTypeScriptBackend(serverPath) {
10
+ return (
11
+ fs.existsSync(path.join(serverPath, "tsconfig.json")) ||
12
+ fs.existsSync(path.join(serverPath, "src"))
13
+ );
14
+ }
15
+
16
+ function findAppFile(serverPath) {
17
+ const possible = ["src/app.ts", "src/app.js", "app.ts", "app.js"];
18
+ for (const file of possible) {
19
+ const full = path.join(serverPath, file);
20
+ if (fs.existsSync(full)) return full;
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function findServerFile(serverPath) {
26
+ const possible = ["src/server.ts", "src/server.js", "server.ts", "server.js"];
27
+ for (const file of possible) {
28
+ const full = path.join(serverPath, file);
29
+ if (fs.existsSync(full)) return full;
30
+ }
31
+ return null;
32
+ }
33
+
34
+ /* -------------------------------------------------------------------------- */
35
+ /* Inject Features */
36
+ /* -------------------------------------------------------------------------- */
37
+
38
+ export function injectFeatures(serverPath, selectedFeatures = []) {
39
+ if (!Array.isArray(selectedFeatures) || selectedFeatures.length === 0) return;
40
+
41
+ const appFile = findAppFile(serverPath);
42
+ const serverFile = findServerFile(serverPath);
43
+ const isTS = isTypeScriptBackend(serverPath);
44
+
45
+ if (!appFile) {
46
+ console.warn("⚠️ app file not found, skipping feature injection");
47
+ return;
48
+ }
49
+
50
+ let appContent = fs.readFileSync(appFile, "utf8");
51
+ let serverContent = serverFile
52
+ ? fs.readFileSync(serverFile, "utf8")
53
+ : "";
54
+
55
+ let middlewareCode = "";
56
+ let envVars = [];
57
+
58
+ /* ---------------------------------------------------------------------- */
59
+ /* Process each feature */
60
+ /* ---------------------------------------------------------------------- */
61
+
62
+ for (const feature of selectedFeatures) {
63
+ const config = FEATURES[feature];
64
+ if (!config) continue;
65
+
66
+ const featureKey = feature.toLowerCase(); // 🔥 normalize
67
+
68
+ /* --------------------------- Middlewares ---------------------------- */
69
+ if (config.middleware && !appContent.includes(config.middleware)) {
70
+ middlewareCode += `${config.middleware}\n`;
71
+ }
72
+
73
+ /* ------------------------------ Files -------------------------------- */
74
+ if (config.files) {
75
+ const files = isTS ? config.files.ts : config.files.js;
76
+
77
+ if (Array.isArray(files)) {
78
+ for (const file of files) {
79
+ const filePath = path.join(serverPath, file.path);
80
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
81
+
82
+ if (fs.existsSync(filePath)) continue;
83
+
84
+ // ✅ FIX: support fromTemplate
85
+ if (file.fromTemplate) {
86
+ const templatePath = path.join(
87
+ process.cwd(),
88
+ "templates",
89
+ "backend",
90
+ isTS ? "express-ts" : "express-js",
91
+ file.path
92
+ );
93
+
94
+ if (!fs.existsSync(templatePath)) {
95
+ console.warn(`⚠️ Missing template: ${templatePath}`);
96
+ continue;
97
+ }
98
+
99
+ fs.copyFileSync(templatePath, filePath);
100
+ } else if (file.content) {
101
+ fs.writeFileSync(filePath, file.content.trim());
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ /* ------------------------------ Env ---------------------------------- */
108
+ if (Array.isArray(config.env)) {
109
+ envVars.push(...config.env);
110
+ }
111
+
112
+ /* --------------------------- Cloudinary ------------------------------ */
113
+ if (featureKey === "Cloudinary" && serverFile) {
114
+ if (!serverContent.includes("connectCloudinary")) {
115
+ const importPath = isTS
116
+ ? "./utils/Cloudinary"
117
+ : "./Utils/Cloudinary.js";
118
+
119
+ serverContent = serverContent.replace(
120
+ /app\.listen|server\.listen/,
121
+ `
122
+ import { connectCloudinary } from "${importPath}";
123
+ connectCloudinary();
124
+
125
+ $&`
126
+ );
127
+ }
128
+ }
129
+ }
130
+
131
+ /* ---------------------------------------------------------------------- */
132
+ /* Inject middlewares */
133
+ /* ---------------------------------------------------------------------- */
134
+
135
+ if (middlewareCode && appContent.includes("__INJECT_MIDDLEWARES__")) {
136
+ appContent = appContent.replace(
137
+ "// __INJECT_MIDDLEWARES__",
138
+ middlewareCode + "\n// __INJECT_MIDDLEWARES__"
139
+ );
140
+ }
141
+
142
+ fs.writeFileSync(appFile, appContent, "utf8");
143
+
144
+ /* ---------------------------------------------------------------------- */
145
+ /* Write server file */
146
+ /* ---------------------------------------------------------------------- */
147
+
148
+ if (serverFile && serverContent) {
149
+ fs.writeFileSync(serverFile, serverContent, "utf8");
150
+ }
151
+
152
+ /* ---------------------------------------------------------------------- */
153
+ /* Append env variables */
154
+ /* ---------------------------------------------------------------------- */
155
+
156
+ const envExamplePath = path.join(serverPath, ".env.example");
157
+
158
+ if (envVars.length && fs.existsSync(envExamplePath)) {
159
+ const existingEnv = fs.readFileSync(envExamplePath, "utf8");
160
+
161
+ const newVars = envVars.filter(
162
+ (v) => !existingEnv.includes(v.split("=")[0])
163
+ );
164
+
165
+ if (newVars.length) {
166
+ fs.appendFileSync(envExamplePath, "\n" + newVars.join("\n"));
167
+ }
168
+ }
169
+
170
+ console.log("🔧 Backend features injected successfully");
171
+ }
@@ -0,0 +1,157 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { execSync } from "child_process";
4
+ import { fileURLToPath } from "url";
5
+ import ora from "ora";
6
+
7
+ import { injectFeatures } from "./injectFeatures.js";
8
+ import { replacePlaceholders } from "./replacePlaceholders.js";
9
+
10
+ /* -------------------------------------------------------------------------- */
11
+ /* ESM __dirname */
12
+ /* -------------------------------------------------------------------------- */
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ /* -------------------------------------------------------------------------- */
18
+ /* Template Root */
19
+ /* -------------------------------------------------------------------------- */
20
+
21
+ const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
22
+
23
+ /* -------------------------------------------------------------------------- */
24
+ /* Template Registries */
25
+ /* -------------------------------------------------------------------------- */
26
+
27
+ const FRONTEND_TEMPLATES = {
28
+ "react-js": path.join(TEMPLATES_DIR, "frontend", "react-js"),
29
+ "react-ts": path.join(TEMPLATES_DIR, "frontend", "react-ts"),
30
+ "next-js": path.join(TEMPLATES_DIR, "frontend", "next-js"),
31
+ "next-ts": path.join(TEMPLATES_DIR, "frontend", "next-ts")
32
+ };
33
+
34
+ const BACKEND_TEMPLATES = {
35
+ "express-js": path.join(TEMPLATES_DIR, "backend", "express-js"),
36
+ "express-ts": path.join(TEMPLATES_DIR, "backend", "express-ts")
37
+ };
38
+
39
+ /* -------------------------------------------------------------------------- */
40
+ /* Main Project Creator */
41
+ /* -------------------------------------------------------------------------- */
42
+
43
+ export async function createProject(config) {
44
+ const root =
45
+ config.projectName === "."
46
+ ? process.cwd()
47
+ : path.join(process.cwd(), config.projectName);
48
+
49
+ const clientPath = path.join(root, "client");
50
+ const serverPath = path.join(root, "server");
51
+
52
+ console.log(`\n🚀 Creating project: ${config.projectName}\n`);
53
+
54
+ await fs.ensureDir(root);
55
+
56
+ /* ----------------------------- Frontend -------------------------------- */
57
+
58
+ if (config.frontend && config.frontend !== "None") {
59
+ const frontendTemplatePath = FRONTEND_TEMPLATES[config.frontend];
60
+
61
+ if (!frontendTemplatePath || !fs.existsSync(frontendTemplatePath)) {
62
+ throw new Error(`❌ Frontend template "${config.frontend}" not found`);
63
+ }
64
+
65
+ const frontendSpinner = ora(
66
+ `Setting up frontend (${config.frontend})...`
67
+ ).start();
68
+
69
+ await fs.copy(frontendTemplatePath, clientPath);
70
+
71
+ frontendSpinner.succeed("Frontend ready");
72
+
73
+ await replacePlaceholders(clientPath, {
74
+ PROJECT_NAME: config.projectName
75
+ });
76
+ }
77
+
78
+ /* ----------------------------- Backend --------------------------------- */
79
+
80
+ const backendTemplatePath = BACKEND_TEMPLATES[config.backend];
81
+
82
+ if (!backendTemplatePath || !fs.existsSync(backendTemplatePath)) {
83
+ throw new Error(`❌ Backend template "${config.backend}" not found`);
84
+ }
85
+
86
+ const backendSpinner = ora(
87
+ `Setting up backend (${config.backend})...`
88
+ ).start();
89
+
90
+ await fs.copy(backendTemplatePath, serverPath);
91
+
92
+ backendSpinner.succeed("Backend ready");
93
+
94
+ /* --------------------------- .env handling ------------------------------ */
95
+
96
+ const envExamplePath = path.join(serverPath, ".env.example");
97
+ const envPath = path.join(serverPath, ".env");
98
+
99
+ if (fs.existsSync(envExamplePath)) {
100
+ await fs.copy(envExamplePath, envPath);
101
+ }
102
+
103
+ /* ----------------------- Replace placeholders --------------------------- */
104
+
105
+ await replacePlaceholders(serverPath, {
106
+ PROJECT_NAME: `${config.projectName}-server`
107
+ });
108
+
109
+ /* ----------------------- Install Dependencies --------------------------- */
110
+
111
+ if (Array.isArray(config.backendFeatures)) {
112
+ installDependencies(serverPath, config.backendFeatures);
113
+ }
114
+
115
+ console.log("\n✅ Project setup complete!");
116
+ console.log(`👉 cd ${config.projectName}`);
117
+ }
118
+
119
+ /* -------------------------------------------------------------------------- */
120
+ /* Dependency Installation */
121
+ /* -------------------------------------------------------------------------- */
122
+
123
+ const BACKEND_DEPENDENCY_MAP = {
124
+ JWT: "jsonwebtoken",
125
+ CORS: "cors",
126
+ "Cookie-Parser": "cookie-parser",
127
+ Cloudinary: "cloudinary",
128
+ Mongoose: "mongoose",
129
+ Zod: "zod",
130
+ Dotenv: "dotenv",
131
+ Bcrypt: "bcrypt",
132
+ Multer: "multer"
133
+ };
134
+
135
+ function installDependencies(serverPath, backendFeatures) {
136
+ const deps = new Set();
137
+
138
+ for (const feature of backendFeatures) {
139
+ const pkg = BACKEND_DEPENDENCY_MAP[feature];
140
+ if (pkg) deps.add(pkg);
141
+ }
142
+
143
+ if (!deps.size) return;
144
+
145
+ const installSpinner = ora(
146
+ "Installing backend dependencies..."
147
+ ).start();
148
+
149
+ execSync(`npm install ${[...deps].join(" ")}`, {
150
+ cwd: serverPath,
151
+ stdio: "ignore"
152
+ });
153
+
154
+ installSpinner.succeed("Dependencies installed");
155
+
156
+ injectFeatures(serverPath, backendFeatures);
157
+ }
@@ -0,0 +1,59 @@
1
+ import { type } from "os";
2
+
3
+ const answers = await inquirer.prompt([
4
+ {
5
+ type:"confirm",
6
+ name:"express",
7
+ message:"install express ?",
8
+ default:true
9
+ },
10
+
11
+ {
12
+ type: "confirm",
13
+ name: "JWT",
14
+ message: "Use JWT authentication?",
15
+ default: true
16
+ },
17
+ {
18
+ type: "confirm",
19
+ name: "CORS",
20
+ message: "Enable CORS?",
21
+ default: true
22
+ },
23
+ {
24
+ type: "confirm",
25
+ name: "CookieParser",
26
+ message: "Use Cookie Parser?",
27
+ default: true
28
+ },
29
+ {
30
+ type: "confirm",
31
+ name: "dotenv",
32
+ message: "Use environment variable ?",
33
+ default: true
34
+ },
35
+ {
36
+ type: "confirm",
37
+ name: "Zod",
38
+ message: "Use Zod validator?",
39
+ default: true
40
+ },
41
+ {
42
+ type: "confirm",
43
+ name: "multer",
44
+ message: "Use multer ?",
45
+ default: true
46
+ },
47
+ {
48
+ type: "confirm",
49
+ name: "Mongoose",
50
+ message: "Use MongoDB (Mongoose)?",
51
+ default: false
52
+ },
53
+ {
54
+ type: "confirm",
55
+ name: "Bcrypt",
56
+ message: "Use Bcrypt for password hashing?",
57
+ default: true
58
+ }
59
+ ]);
@@ -0,0 +1,54 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+
4
+ const IGNORE_DIRS = [
5
+ "node_modules",
6
+ ".git",
7
+ "dist",
8
+ "build"
9
+ ];
10
+
11
+ const IGNORE_EXTENSIONS = [
12
+ ".png",
13
+ ".jpg",
14
+ ".jpeg",
15
+ ".gif",
16
+ ".ico",
17
+ ".zip"
18
+ ];
19
+
20
+ /**
21
+ * Recursively replace placeholders in text files
22
+ */
23
+ export async function replacePlaceholders(dir, replacements) {
24
+ if (!fs.existsSync(dir)) return;
25
+
26
+ const files = await fs.readdir(dir);
27
+
28
+ for (const file of files) {
29
+ if (IGNORE_DIRS.includes(file)) continue;
30
+
31
+ const fullPath = path.join(dir, file);
32
+ const stat = await fs.stat(fullPath);
33
+
34
+ if (stat.isDirectory()) {
35
+ await replacePlaceholders(fullPath, replacements);
36
+ } else {
37
+ if (IGNORE_EXTENSIONS.some(ext => file.endsWith(ext))) continue;
38
+
39
+ let content;
40
+ try {
41
+ content = await fs.readFile(fullPath, "utf8");
42
+ } catch {
43
+ continue; // skip non-text files safely
44
+ }
45
+
46
+ for (const [key, value] of Object.entries(replacements)) {
47
+ const regex = new RegExp(`__${key}__`, "g");
48
+ content = content.replace(regex, value);
49
+ }
50
+
51
+ await fs.writeFile(fullPath, content);
52
+ }
53
+ }
54
+ }