nexforge-cli 1.0.10 ā 1.1.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 +19 -9
- package/index.js +23 -12
- package/package.json +4 -2
- package/src/commands/create.js +197 -293
- package/src/commands/deploy.js +10 -10
- package/src/commands/domains.js +7 -7
- package/src/commands/env.js +7 -7
- package/src/commands/info.js +7 -7
- package/src/commands/init.js +7 -6
- package/src/commands/login.js +6 -5
- package/src/commands/logs.js +5 -5
- package/src/commands/open.js +8 -8
- package/src/commands/rename.js +7 -6
- package/src/commands/rollback.js +7 -6
- package/src/config.js +14 -12
- package/src/templates/backend/express-mongodb/package.json +39 -0
- package/src/templates/backend/express-mongodb/src/config/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/controllers/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/middlewares/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/models/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/routes/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/server.js +12 -0
- package/src/templates/backend/express-mongodb/src/services/.gitkeep +0 -0
- package/src/templates/backend/express-mongodb/src/utils/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/package.json +39 -0
- package/src/templates/backend/express-postgres/src/config/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/controllers/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/middlewares/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/models/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/routes/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/server.js +16 -0
- package/src/templates/backend/express-postgres/src/services/.gitkeep +0 -0
- package/src/templates/backend/express-postgres/src/utils/.gitkeep +0 -0
- package/src/templates/backend/express-sql/package.json +39 -0
- package/src/templates/backend/express-sql/src/config/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/controllers/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/middlewares/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/models/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/routes/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/server.js +15 -0
- package/src/templates/backend/express-sql/src/services/.gitkeep +0 -0
- package/src/templates/backend/express-sql/src/utils/.gitkeep +0 -0
- package/src/templates/frontend/react/index.html +12 -0
- package/src/templates/frontend/react/package.json +37 -0
- package/src/templates/frontend/react/postcss.config.js +1 -0
- package/src/templates/frontend/react/src/App.jsx +11 -0
- package/src/templates/frontend/react/src/assets/.gitkeep +0 -0
- package/src/templates/frontend/react/src/components/.gitkeep +0 -0
- package/src/templates/frontend/react/src/hooks/.gitkeep +0 -0
- package/src/templates/frontend/react/src/index.css +3 -0
- package/src/templates/frontend/react/src/lib/utils.js +6 -0
- package/src/templates/frontend/react/src/main.jsx +10 -0
- package/src/templates/frontend/react/src/pages/.gitkeep +0 -0
- package/src/templates/frontend/react/src/redux/.gitkeep +0 -0
- package/src/templates/frontend/react/src/services/.gitkeep +0 -0
- package/src/templates/frontend/react/src/utils/.gitkeep +0 -0
- package/src/templates/frontend/react/tailwind.config.js +6 -0
- package/src/templates/frontend/react/vite.config.js +6 -0
- package/src/templates/frontend/vue/index.html +13 -0
- package/src/templates/frontend/vue/package.json +18 -0
- package/src/templates/frontend/vue/src/App.vue +11 -0
- package/src/templates/frontend/vue/src/main.js +5 -0
- package/src/templates/frontend/vue/src/style.css +1 -0
- package/src/templates/frontend/vue/vite.config.js +6 -0
package/README.md
CHANGED
|
@@ -4,6 +4,13 @@ The official Command Line Interface for **NexForge** - Your custom Platform as a
|
|
|
4
4
|
Deploy, manage, and scale your applications directly from your terminal with zero friction!
|
|
5
5
|
<a href="https://nexforge-sandy.vercel.app/">Visit Nexforge</a>
|
|
6
6
|
|
|
7
|
+
---
|
|
8
|
+
### š Special Feature: Project Folder Structure Generator
|
|
9
|
+
Try **`nexforge create`** to instantly scaffold Fullstack, React, Next.js, Vue, MongoDB, MySQL, and Postgres projects! It features an interactive UI and a **Zero-Bloat Dependency Selection** system that injects only the packages you want (Redux, GSAP, AI SDKs) for lightning-fast setups!
|
|
10
|
+
|
|
11
|
+
*(š” **Note:** `nexforge create` is a standalone tool. You can use it to generate projects instantly without needing to log in with GitHub or connect to a NexForge account!)*
|
|
12
|
+
---
|
|
13
|
+
|
|
7
14
|
## Installation
|
|
8
15
|
|
|
9
16
|
You can install the CLI globally via NPM:
|
|
@@ -52,7 +59,7 @@ nexforge init
|
|
|
52
59
|
|
|
53
60
|
### šļø 3. `nexforge create`
|
|
54
61
|
|
|
55
|
-
**Description:** Scaffold a
|
|
62
|
+
**Description:** The ultimate project generator. Scaffold a fully configured project (React, Next.js, Vue, MERN, Postgres, MySQL) with an interactive UI, blazing fast copy, and **Zero Bloat** dependency selection!
|
|
56
63
|
**Usage:**
|
|
57
64
|
|
|
58
65
|
```bash
|
|
@@ -61,14 +68,17 @@ nexforge create
|
|
|
61
68
|
|
|
62
69
|
**How it works (Step-by-Step):**
|
|
63
70
|
|
|
64
|
-
1. **Run the Command:** Type `nexforge create` in your terminal.
|
|
65
|
-
2. **Project Name:**
|
|
66
|
-
3. **Select your Stack:**
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
1. **Run the Command:** Type `nexforge create` in your terminal. You'll be greeted by a beautiful, interactive CLI.
|
|
72
|
+
2. **Project Name:** Enter a name for your new app.
|
|
73
|
+
3. **Select your Stack:** Choose from 3 primary modes:
|
|
74
|
+
- **Fullstack:** Automatically generates a combined frontend + backend repository.
|
|
75
|
+
- **Frontend Only:** Choose between **React + Vite**, **Next.js**, or **Vue + Vite**.
|
|
76
|
+
- **Backend Only:** Choose between **Express + MongoDB**, **Express + PostgreSQL**, or **Express + MySQL**.
|
|
77
|
+
4. **Zero-Bloat Dependency Selection:** Instead of downloading a massive, bloated template, the CLI asks you exactly what you need:
|
|
78
|
+
- *Need Animations?* Select yes to inject GSAP & Framer Motion.
|
|
79
|
+
- *Need State Management?* Select yes to inject Redux Toolkit.
|
|
80
|
+
- *Need AI?* Select yes to inject OpenAI & Groq SDKs.
|
|
81
|
+
- The CLI intelligently injects *only* the packages you selected into your `package.json` for a lightning-fast install.
|
|
72
82
|
5. **Ready to Deploy:** Once finished, simply `cd` into your new folder, run `nexforge init` to connect it to your account, and `nexforge deploy` to take it live!
|
|
73
83
|
|
|
74
84
|
---
|
package/index.js
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { program } from "commander"
|
|
4
4
|
|
|
5
5
|
program
|
|
6
6
|
.name("nexforge")
|
|
7
7
|
.description("CLI to some next-level deployment magic for NexForge")
|
|
8
8
|
.version("1.0.0")
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
import loginCmd from "./src/commands/login.js";
|
|
11
|
+
loginCmd(program);
|
|
12
|
+
import initCmd from "./src/commands/init.js";
|
|
13
|
+
initCmd(program);
|
|
14
|
+
import deployCmd from "./src/commands/deploy.js";
|
|
15
|
+
deployCmd(program);
|
|
16
|
+
import envCmd from "./src/commands/env.js";
|
|
17
|
+
envCmd(program);
|
|
18
|
+
import logsCmd from "./src/commands/logs.js";
|
|
19
|
+
logsCmd(program);
|
|
20
|
+
import rollbackCmd from "./src/commands/rollback.js";
|
|
21
|
+
rollbackCmd(program);
|
|
22
|
+
import renameCmd from "./src/commands/rename.js";
|
|
23
|
+
renameCmd(program);
|
|
24
|
+
import createCmd from "./src/commands/create.js";
|
|
25
|
+
createCmd(program);
|
|
26
|
+
import infoCmd from "./src/commands/info.js";
|
|
27
|
+
infoCmd(program);
|
|
28
|
+
import domainsCmd from "./src/commands/domains.js";
|
|
29
|
+
domainsCmd(program);
|
|
30
|
+
import openCmd from "./src/commands/open.js";
|
|
31
|
+
openCmd(program);
|
|
21
32
|
|
|
22
33
|
// Parse the arguments passed by the user in the terminal
|
|
23
34
|
program.parse(process.argv)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexforge-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Custom Deployment CLI for NexForge",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
],
|
|
17
17
|
"author": "Manav Sharma",
|
|
18
18
|
"license": "ISC",
|
|
19
|
-
"type": "
|
|
19
|
+
"type": "module",
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@clack/prompts": "^1.7.0",
|
|
21
22
|
"archiver": "^5.3.1",
|
|
22
23
|
"axios": "^1.18.1",
|
|
23
24
|
"chalk": "^4.1.2",
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
"ignore": "^7.0.5",
|
|
28
29
|
"inquirer": "^8.2.6",
|
|
29
30
|
"ora": "^5.4.1",
|
|
31
|
+
"picocolors": "^1.1.1",
|
|
30
32
|
"socket.io-client": "^4.8.3"
|
|
31
33
|
}
|
|
32
34
|
}
|
package/src/commands/create.js
CHANGED
|
@@ -1,318 +1,222 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const BACKEND_DEPS = {
|
|
9
|
-
"@aws-sdk/client-s3": "^3.1075.0",
|
|
10
|
-
"@aws-sdk/lib-storage": "^3.1075.0",
|
|
11
|
-
"@google/genai": "^2.10.0",
|
|
12
|
-
"axios": "^1.18.0",
|
|
13
|
-
"bullmq": "^5.79.1",
|
|
14
|
-
"cookie-parser": "^1.4.7",
|
|
15
|
-
"cors": "^2.8.6",
|
|
16
|
-
"dotenv": "^17.4.2",
|
|
17
|
-
"express": "^5.2.1",
|
|
18
|
-
"express-rate-limit": "^8.5.2",
|
|
19
|
-
"extract-zip": "^2.0.1",
|
|
20
|
-
"groq-sdk": "^1.3.0",
|
|
21
|
-
"http-proxy-middleware": "^4.1.1",
|
|
22
|
-
"ioredis": "^5.11.1",
|
|
23
|
-
"jsonwebtoken": "^9.0.3",
|
|
24
|
-
"mime-types": "^3.0.2",
|
|
25
|
-
"mongoose": "^9.7.1",
|
|
26
|
-
"multer": "^2.2.0",
|
|
27
|
-
"node-cron": "^4.5.0",
|
|
28
|
-
"openai": "^6.44.0",
|
|
29
|
-
"os-utils": "^0.0.14",
|
|
30
|
-
"pm2": "^7.0.1",
|
|
31
|
-
"portfinder": "^1.0.38",
|
|
32
|
-
"redis": "^6.0.0",
|
|
33
|
-
"socket.io": "^4.8.3"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const FRONTEND_DEPS = {
|
|
37
|
-
"@heroicons/react": "^2.2.0",
|
|
38
|
-
"axios": "^1.18.0",
|
|
39
|
-
"clsx": "^2.1.1",
|
|
40
|
-
"framer-motion": "^12.40.0",
|
|
41
|
-
"gsap": "^3.15.0",
|
|
42
|
-
"lenis": "^1.3.23",
|
|
43
|
-
"lucide-react": "^1.21.0",
|
|
44
|
-
"react-icons": "^5.6.0",
|
|
45
|
-
"react-redux": "^9.1.1",
|
|
46
|
-
"@reduxjs/toolkit": "^2.2.3",
|
|
47
|
-
"react-toastify": "^11.1.0",
|
|
48
|
-
"recharts": "^3.8.1",
|
|
49
|
-
"socket.io-client": "^4.8.3",
|
|
50
|
-
"tailwind-merge": "^3.6.0"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const NEXTJS_DEPS = { ...FRONTEND_DEPS, ...BACKEND_DEPS }
|
|
54
|
-
|
|
55
|
-
const createFolderStructure = (baseDir, folders) => {
|
|
56
|
-
folders.forEach((folder) => {
|
|
57
|
-
fs.mkdirSync(path.join(baseDir, folder), { recursive: true })
|
|
58
|
-
})
|
|
59
|
-
}
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { execSync } from "child_process"
|
|
5
|
+
import * as p from "@clack/prompts"
|
|
6
|
+
import pc from "picocolors"
|
|
60
7
|
|
|
61
8
|
const runCommand = (command, cwd) => {
|
|
62
9
|
try {
|
|
63
|
-
execSync(command, { cwd, stdio: "
|
|
10
|
+
execSync(command, { cwd, stdio: "ignore" }) // use ignore to keep it silent since we'll use a spinner
|
|
64
11
|
} catch (error) {
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const setupBackend = (targetDir) => {
|
|
70
|
-
console.log(chalk.cyan("\nš Scaffolding Backend (Express/Node)..."))
|
|
71
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
72
|
-
createFolderStructure(path.join(targetDir, "src"), [
|
|
73
|
-
"controllers",
|
|
74
|
-
"models",
|
|
75
|
-
"routes",
|
|
76
|
-
"middlewares",
|
|
77
|
-
"services",
|
|
78
|
-
"utils",
|
|
79
|
-
"config",
|
|
80
|
-
])
|
|
81
|
-
|
|
82
|
-
const pkgJson = {
|
|
83
|
-
name: "backend",
|
|
84
|
-
version: "1.0.0",
|
|
85
|
-
main: "src/server.js",
|
|
86
|
-
scripts: {
|
|
87
|
-
dev: "nodemon src/server.js",
|
|
88
|
-
start: "node src/server.js",
|
|
89
|
-
},
|
|
90
|
-
dependencies: BACKEND_DEPS,
|
|
91
|
-
devDependencies: {
|
|
92
|
-
nodemon: "^3.1.14",
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2))
|
|
97
|
-
fs.writeFileSync(path.join(targetDir, ".env"), "PORT=8000\nMONGO_URI=")
|
|
98
|
-
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\n.env\n")
|
|
99
|
-
|
|
100
|
-
const serverJs = `const express = require('express');
|
|
101
|
-
const cors = require('cors');
|
|
102
|
-
require('dotenv').config();
|
|
103
|
-
|
|
104
|
-
const app = express();
|
|
105
|
-
app.use(cors());
|
|
106
|
-
app.use(express.json());
|
|
107
|
-
|
|
108
|
-
app.get('/', (req, res) => res.send('NexForge Backend API is running!'));
|
|
109
|
-
|
|
110
|
-
const PORT = process.env.PORT || 8000;
|
|
111
|
-
app.listen(PORT, () => console.log(\`Server running on port \${PORT}\`));
|
|
112
|
-
`
|
|
113
|
-
fs.writeFileSync(path.join(targetDir, "src", "server.js"), serverJs)
|
|
114
|
-
|
|
115
|
-
console.log(chalk.yellow("š¦ Installing backend dependencies (this may take a while)..."))
|
|
116
|
-
runCommand("npm install", targetDir)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const setupFrontend = (targetDir) => {
|
|
120
|
-
console.log(chalk.cyan("\nāļø Scaffolding Frontend (React + Vite)..."))
|
|
121
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
122
|
-
|
|
123
|
-
createFolderStructure(path.join(targetDir, "src"), [
|
|
124
|
-
"assets",
|
|
125
|
-
"components",
|
|
126
|
-
"hooks",
|
|
127
|
-
"lib",
|
|
128
|
-
"pages",
|
|
129
|
-
"redux",
|
|
130
|
-
"services",
|
|
131
|
-
"utils",
|
|
132
|
-
])
|
|
133
|
-
|
|
134
|
-
// Create Vite Boilerplate
|
|
135
|
-
const viteConfig = `import { defineConfig } from 'vite'
|
|
136
|
-
import react from '@vitejs/plugin-react'
|
|
137
|
-
|
|
138
|
-
export default defineConfig({
|
|
139
|
-
plugins: [react()],
|
|
140
|
-
})`
|
|
141
|
-
fs.writeFileSync(path.join(targetDir, "vite.config.js"), viteConfig)
|
|
142
|
-
|
|
143
|
-
const indexHtml = `<!doctype html>
|
|
144
|
-
<html lang="en">
|
|
145
|
-
<head>
|
|
146
|
-
<meta charset="UTF-8" />
|
|
147
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
148
|
-
<title>NexForge App</title>
|
|
149
|
-
</head>
|
|
150
|
-
<body>
|
|
151
|
-
<div id="root"></div>
|
|
152
|
-
<script type="module" src="/src/main.jsx"></script>
|
|
153
|
-
</body>
|
|
154
|
-
</html>`
|
|
155
|
-
fs.writeFileSync(path.join(targetDir, "index.html"), indexHtml)
|
|
156
|
-
|
|
157
|
-
const mainJsx = `import React from 'react'
|
|
158
|
-
import ReactDOM from 'react-dom/client'
|
|
159
|
-
import App from './App.jsx'
|
|
160
|
-
import './index.css'
|
|
161
|
-
|
|
162
|
-
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
163
|
-
<React.StrictMode>
|
|
164
|
-
<App />
|
|
165
|
-
</React.StrictMode>,
|
|
166
|
-
)`
|
|
167
|
-
fs.writeFileSync(path.join(targetDir, "src", "main.jsx"), mainJsx)
|
|
168
|
-
|
|
169
|
-
const appJsx = `import React from 'react'
|
|
170
|
-
|
|
171
|
-
function App() {
|
|
172
|
-
return (
|
|
173
|
-
<div className="min-h-screen flex items-center justify-center bg-gray-900 text-white">
|
|
174
|
-
<h1 className="text-4xl font-bold">Welcome to NexForge</h1>
|
|
175
|
-
</div>
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default App`
|
|
180
|
-
fs.writeFileSync(path.join(targetDir, "src", "App.jsx"), appJsx)
|
|
181
|
-
|
|
182
|
-
// Merge package.json
|
|
183
|
-
const pkgJson = {
|
|
184
|
-
name: "frontend",
|
|
185
|
-
private: true,
|
|
186
|
-
version: "0.0.0",
|
|
187
|
-
type: "module",
|
|
188
|
-
scripts: {
|
|
189
|
-
dev: "vite",
|
|
190
|
-
build: "vite build",
|
|
191
|
-
preview: "vite preview"
|
|
192
|
-
},
|
|
193
|
-
dependencies: { ...FRONTEND_DEPS, "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.18.0" },
|
|
194
|
-
devDependencies: { "@vitejs/plugin-react": "^6.0.1", "vite": "^8.0.12", tailwindcss: "^3.4.19", postcss: "^8.5.15", autoprefixer: "^10.5.0" }
|
|
12
|
+
throw new Error(`Failed to execute: ${command}`)
|
|
195
13
|
}
|
|
196
|
-
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2))
|
|
197
|
-
|
|
198
|
-
// Tailwind config
|
|
199
|
-
const twConfig = `/** @type {import('tailwindcss').Config} */
|
|
200
|
-
export default {
|
|
201
|
-
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
|
202
|
-
theme: { extend: {} },
|
|
203
|
-
plugins: [],
|
|
204
|
-
}`
|
|
205
|
-
fs.writeFileSync(path.join(targetDir, "tailwind.config.js"), twConfig)
|
|
206
|
-
fs.writeFileSync(path.join(targetDir, "postcss.config.js"), `export default { plugins: { tailwindcss: {}, autoprefixer: {} } }`)
|
|
207
|
-
|
|
208
|
-
const css = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`
|
|
209
|
-
fs.writeFileSync(path.join(targetDir, "src", "index.css"), css)
|
|
210
|
-
|
|
211
|
-
// Utils
|
|
212
|
-
const utilsJs = `import { clsx } from "clsx"
|
|
213
|
-
import { twMerge } from "tailwind-merge"
|
|
214
|
-
|
|
215
|
-
export function cn(...inputs) {
|
|
216
|
-
return twMerge(clsx(inputs))
|
|
217
14
|
}
|
|
218
|
-
`
|
|
219
|
-
fs.writeFileSync(path.join(targetDir, "src", "lib", "utils.js"), utilsJs)
|
|
220
|
-
|
|
221
|
-
console.log(chalk.yellow("š¦ Installing frontend dependencies..."))
|
|
222
|
-
runCommand("npm install", targetDir)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const setupNextJs = (targetDir) => {
|
|
226
|
-
console.log(chalk.cyan("\nš Scaffolding Next.js App Router Project..."))
|
|
227
|
-
// We use npx create-next-app
|
|
228
|
-
const command = `npx create-next-app@latest ${path.basename(targetDir)} --js --tailwind --eslint --app --src-dir --import-alias "@/*"`
|
|
229
|
-
runCommand(command, path.dirname(targetDir))
|
|
230
|
-
|
|
231
|
-
// Create custom folders
|
|
232
|
-
createFolderStructure(path.join(targetDir, "src"), [
|
|
233
|
-
"app/api",
|
|
234
|
-
"components",
|
|
235
|
-
"hooks",
|
|
236
|
-
"lib",
|
|
237
|
-
"store",
|
|
238
|
-
"services",
|
|
239
|
-
"utils",
|
|
240
|
-
])
|
|
241
|
-
|
|
242
|
-
// Merge package.json
|
|
243
|
-
const pkgPath = path.join(targetDir, "package.json")
|
|
244
|
-
if (fs.existsSync(pkgPath)) {
|
|
245
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
246
|
-
pkg.dependencies = { ...pkg.dependencies, ...NEXTJS_DEPS }
|
|
247
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const utilsJs = `import { clsx } from "clsx"
|
|
251
|
-
import { twMerge } from "tailwind-merge"
|
|
252
15
|
|
|
253
|
-
|
|
254
|
-
|
|
16
|
+
const copyTemplate = (src, dest) => {
|
|
17
|
+
fs.cpSync(src, dest, { recursive: true })
|
|
255
18
|
}
|
|
256
|
-
`
|
|
257
|
-
fs.writeFileSync(path.join(targetDir, "src", "lib", "utils.js"), utilsJs)
|
|
258
19
|
|
|
259
|
-
|
|
260
|
-
runCommand("npm install", targetDir)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
module.exports = (program) => {
|
|
20
|
+
export default (program) => {
|
|
264
21
|
program
|
|
265
22
|
.command("create")
|
|
266
23
|
.description("Generate a standardized project with industry folders and NexForge libraries")
|
|
267
24
|
.action(async () => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
25
|
+
p.intro(pc.bgCyan(pc.black(" ⨠NexForge Project Generator ")))
|
|
26
|
+
|
|
27
|
+
const project = await p.group(
|
|
271
28
|
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
29
|
+
name: () =>
|
|
30
|
+
p.text({
|
|
31
|
+
message: "What is your project name?",
|
|
32
|
+
placeholder: "my-awesome-app",
|
|
33
|
+
validate: (value) => {
|
|
34
|
+
if (!value) return "Please enter a project name."
|
|
35
|
+
if (fs.existsSync(path.join(process.cwd(), value))) {
|
|
36
|
+
return `Directory ${value} already exists.`
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
stackType: () =>
|
|
41
|
+
p.select({
|
|
42
|
+
message: "Which stack would you like to build?",
|
|
43
|
+
initialValue: "frontend",
|
|
44
|
+
options: [
|
|
45
|
+
{ value: "fullstack", label: "Fullstack (Frontend + Backend)" },
|
|
46
|
+
{ value: "frontend", label: "Frontend Only" },
|
|
47
|
+
{ value: "backend", label: "Backend Only" },
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
frontendFramework: ({ results }) => {
|
|
51
|
+
if (results.stackType === "backend") return
|
|
52
|
+
return p.select({
|
|
53
|
+
message: "Choose a Frontend framework",
|
|
54
|
+
options: [
|
|
55
|
+
{ value: "react", label: "React + Vite" },
|
|
56
|
+
{ value: "nextjs", label: "Next.js" },
|
|
57
|
+
{ value: "vue", label: "Vue + Vite" },
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
backendFramework: ({ results }) => {
|
|
62
|
+
if (results.stackType === "frontend" || results.frontendFramework === "nextjs") return
|
|
63
|
+
return p.select({
|
|
64
|
+
message: "Choose a Backend stack",
|
|
65
|
+
options: [
|
|
66
|
+
{ value: "express-mongodb", label: "Node.js + Express + MongoDB" },
|
|
67
|
+
{ value: "express-postgres", label: "Node.js + Express + PostgreSQL" },
|
|
68
|
+
{ value: "express-sql", label: "Node.js + Express + MySQL" },
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
},
|
|
276
72
|
},
|
|
277
73
|
{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
])
|
|
74
|
+
onCancel: () => {
|
|
75
|
+
p.cancel("Operation cancelled.")
|
|
76
|
+
process.exit(0)
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const targetDir = path.join(process.cwd(), project.name)
|
|
82
|
+
fs.mkdirSync(targetDir, { recursive: true })
|
|
289
83
|
|
|
290
|
-
|
|
291
|
-
const
|
|
84
|
+
// Define dependency groups
|
|
85
|
+
const OPTIONAL_DEPS = {
|
|
86
|
+
frontend: {
|
|
87
|
+
animations: { "framer-motion": "^12.40.0", "gsap": "^3.15.0", "lenis": "^1.3.23" },
|
|
88
|
+
state: { "react-redux": "^9.1.1", "@reduxjs/toolkit": "^2.2.3" },
|
|
89
|
+
websockets: { "socket.io-client": "^4.8.3" },
|
|
90
|
+
charts: { "recharts": "^3.8.1" },
|
|
91
|
+
ui: { "react-toastify": "^11.1.0", "react-icons": "^5.6.0", "@heroicons/react": "^2.2.0" }
|
|
92
|
+
},
|
|
93
|
+
backend: {
|
|
94
|
+
ai: { "@google/genai": "^2.10.0", "openai": "^6.44.0", "groq-sdk": "^1.3.0" },
|
|
95
|
+
redis: { "redis": "^6.0.0", "ioredis": "^5.11.1", "bullmq": "^5.79.1" },
|
|
96
|
+
storage: { "@aws-sdk/client-s3": "^3.1075.0", "@aws-sdk/lib-storage": "^3.1075.0", "multer": "^2.2.0" },
|
|
97
|
+
websockets: { "socket.io": "^4.8.3" }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
292
100
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
101
|
+
// Ask Yes/No questions based on stack
|
|
102
|
+
const features = { frontend: {}, backend: {} }
|
|
103
|
+
|
|
104
|
+
if (project.stackType === "frontend" || project.stackType === "fullstack") {
|
|
105
|
+
p.note("Frontend Features")
|
|
106
|
+
features.frontend.animations = await p.confirm({ message: "Add Animations? (Framer Motion, GSAP, Lenis)" })
|
|
107
|
+
features.frontend.state = await p.confirm({ message: "Add State Management? (Redux Toolkit)" })
|
|
108
|
+
features.frontend.ui = await p.confirm({ message: "Add UI Extras? (Toastify, React Icons)" })
|
|
109
|
+
features.frontend.websockets = await p.confirm({ message: "Add Websockets? (Socket.io Client)" })
|
|
296
110
|
}
|
|
297
111
|
|
|
298
|
-
|
|
112
|
+
if (project.stackType === "backend" || project.stackType === "fullstack") {
|
|
113
|
+
p.note("Backend Features")
|
|
114
|
+
features.backend.ai = await p.confirm({ message: "Add AI SDKs? (OpenAI, Gemini, Groq)" })
|
|
115
|
+
features.backend.redis = await p.confirm({ message: "Add Redis & Queues? (BullMQ, ioredis)" })
|
|
116
|
+
features.backend.storage = await p.confirm({ message: "Add S3 Storage? (AWS SDK, Multer)" })
|
|
117
|
+
features.backend.websockets = await p.confirm({ message: "Add Websockets? (Socket.io)" })
|
|
118
|
+
}
|
|
299
119
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
120
|
+
const s = p.spinner()
|
|
121
|
+
s.start("Scaffolding your project...")
|
|
122
|
+
|
|
123
|
+
const injectDependencies = (dir, stack) => {
|
|
124
|
+
const pkgPath = path.join(dir, "package.json")
|
|
125
|
+
if (!fs.existsSync(pkgPath)) return
|
|
126
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
127
|
+
|
|
128
|
+
// Remove all optional deps first to make it a clean base
|
|
129
|
+
Object.values(OPTIONAL_DEPS[stack]).forEach(group => {
|
|
130
|
+
Object.keys(group).forEach(dep => {
|
|
131
|
+
if (pkg.dependencies) delete pkg.dependencies[dep]
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Inject only the selected ones
|
|
136
|
+
Object.keys(features[stack]).forEach(feature => {
|
|
137
|
+
if (features[stack][feature]) {
|
|
138
|
+
pkg.dependencies = { ...pkg.dependencies, ...OPTIONAL_DEPS[stack][feature] }
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
|
|
310
143
|
}
|
|
311
144
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
145
|
+
try {
|
|
146
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
147
|
+
const __dirname = path.dirname(__filename)
|
|
148
|
+
const resolvedTemplatesDir = path.join(__dirname, '..', 'templates')
|
|
149
|
+
|
|
150
|
+
if (project.stackType === "fullstack") {
|
|
151
|
+
if (project.frontendFramework === "nextjs") {
|
|
152
|
+
s.message("Bootstrapping Next.js... (this might take a minute)")
|
|
153
|
+
runCommand(`npx create-next-app@latest ${project.name} --js --tailwind --eslint --app --src-dir --import-alias "@/*"`, process.cwd())
|
|
154
|
+
injectDependencies(targetDir, "frontend")
|
|
155
|
+
s.message("Installing selected NexForge dependencies for Next.js...")
|
|
156
|
+
runCommand("npm install", targetDir)
|
|
157
|
+
} else {
|
|
158
|
+
// Fullstack dynamic routing
|
|
159
|
+
const frontendTemplate = project.frontendFramework === "vue" ? "vue" : "react"
|
|
160
|
+
const backendTemplate = project.backendFramework || "express-mongodb"
|
|
161
|
+
|
|
162
|
+
copyTemplate(path.join(resolvedTemplatesDir, "frontend", frontendTemplate), path.join(targetDir, "frontend"))
|
|
163
|
+
copyTemplate(path.join(resolvedTemplatesDir, "backend", backendTemplate), path.join(targetDir, "backend"))
|
|
164
|
+
|
|
165
|
+
injectDependencies(path.join(targetDir, "frontend"), "frontend")
|
|
166
|
+
injectDependencies(path.join(targetDir, "backend"), "backend")
|
|
167
|
+
|
|
168
|
+
s.message("Installing frontend dependencies...")
|
|
169
|
+
runCommand("npm install", path.join(targetDir, "frontend"))
|
|
170
|
+
s.message("Installing backend dependencies...")
|
|
171
|
+
runCommand("npm install", path.join(targetDir, "backend"))
|
|
172
|
+
}
|
|
173
|
+
} else if (project.stackType === "frontend") {
|
|
174
|
+
if (project.frontendFramework === "nextjs") {
|
|
175
|
+
s.message("Bootstrapping Next.js... (this might take a minute)")
|
|
176
|
+
runCommand(`npx create-next-app@latest ${project.name} --js --tailwind --eslint --app --src-dir --import-alias "@/*"`, process.cwd())
|
|
177
|
+
injectDependencies(targetDir, "frontend")
|
|
178
|
+
s.message("Installing selected NexForge dependencies for Next.js...")
|
|
179
|
+
runCommand("npm install", targetDir)
|
|
180
|
+
} else if (project.frontendFramework === "react") {
|
|
181
|
+
copyTemplate(path.join(resolvedTemplatesDir, "frontend", "react"), targetDir)
|
|
182
|
+
injectDependencies(targetDir, "frontend")
|
|
183
|
+
s.message("Installing dependencies...")
|
|
184
|
+
runCommand("npm install", targetDir)
|
|
185
|
+
} else if (project.frontendFramework === "vue") {
|
|
186
|
+
copyTemplate(path.join(resolvedTemplatesDir, "frontend", "vue"), targetDir)
|
|
187
|
+
injectDependencies(targetDir, "frontend")
|
|
188
|
+
s.message("Installing dependencies...")
|
|
189
|
+
runCommand("npm install", targetDir)
|
|
190
|
+
} else {
|
|
191
|
+
s.stop("Framework template not yet available.")
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
} else if (project.stackType === "backend") {
|
|
195
|
+
const backendTemplate = project.backendFramework || "express-mongodb"
|
|
196
|
+
copyTemplate(path.join(resolvedTemplatesDir, "backend", backendTemplate), targetDir)
|
|
197
|
+
injectDependencies(targetDir, "backend")
|
|
198
|
+
s.message("Installing dependencies...")
|
|
199
|
+
runCommand("npm install", targetDir)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
s.stop(pc.green("Project scaffolded successfully!"))
|
|
203
|
+
|
|
204
|
+
let nextSteps = `cd ${project.name}\n`
|
|
205
|
+
if (project.stackType === "fullstack" && project.frontendFramework !== "nextjs") {
|
|
206
|
+
nextSteps += `cd frontend && npm run dev\n`
|
|
207
|
+
nextSteps += `cd ../backend && npm run dev`
|
|
208
|
+
} else {
|
|
209
|
+
nextSteps += `npm run dev`
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
p.note(nextSteps, "Next steps")
|
|
213
|
+
|
|
214
|
+
p.outro(pc.cyan("Ready to deploy? Run \`nexforge init\` and \`nexforge deploy\`!"))
|
|
215
|
+
|
|
216
|
+
} catch (err) {
|
|
217
|
+
s.stop(pc.red("An error occurred during scaffolding."))
|
|
218
|
+
p.log.error(err.message)
|
|
219
|
+
process.exit(1)
|
|
220
|
+
}
|
|
317
221
|
})
|
|
318
222
|
}
|