nexforge-cli 1.0.4 → 1.0.6

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 CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  The official Command Line Interface for **NexForge** - Your custom Platform as a Service (PaaS).
4
4
  Deploy, manage, and scale your applications directly from your terminal with zero friction!
5
+ <a href="https://nexforge-zeta.vercel.app/">Visit Nexforge</a>
5
6
 
6
7
  ## Installation
7
8
 
@@ -16,12 +17,16 @@ npm install -g nexforge-cli
16
17
  Here is the complete list of commands available in the NexForge CLI:
17
18
 
18
19
  ### 🔐 1. `nexforge login`
20
+
19
21
  **Description:** Authenticate your local machine with your NexForge account.
20
22
  **Usage:**
23
+
21
24
  ```bash
22
25
  nexforge login
23
26
  ```
24
- **Details:**
27
+
28
+ **Details:**
29
+
25
30
  - Prompts you for your personal **CLI Token** (which you can generate from your NexForge Dashboard Settings).
26
31
  - Prompts you for your **Project ID**.
27
32
  - Saves your credentials securely in `~/.nexforge/config.json`.
@@ -29,25 +34,51 @@ nexforge login
29
34
  ---
30
35
 
31
36
  ### ✨ 2. `nexforge init`
37
+
32
38
  **Description:** Initialize a new NexForge project right from your terminal without opening the browser.
33
39
  **Usage:**
40
+
34
41
  ```bash
35
42
  nexforge init
36
43
  ```
37
- **Details:**
44
+
45
+ **Details:**
46
+
38
47
  - Asks for your **Project Name**.
39
48
  - Asks for the **Framework** you are using (e.g., React, Vue, Express, Node.js).
40
49
  - Automatically registers the project on NexForge servers and links your current directory to the new Project ID.
41
50
 
42
51
  ---
43
52
 
44
- ### 🚀 3. `nexforge deploy`
53
+ ### 🏗️ 3. `nexforge create`
54
+
55
+ **Description:** Scaffold a new project (Next.js, MERN, React, or Express) with industry-standard folder structures and NexForge's rich dependencies.
56
+ **Usage:**
57
+
58
+ ```bash
59
+ nexforge create
60
+ ```
61
+
62
+ **Details:**
63
+
64
+ - Interactively asks for your **Project Name** and **Tech Stack** (Next.js, MERN, Frontend, Backend).
65
+ - Generates a fully configured folder structure matching NexForge's custom standards.
66
+ - Pre-installs all necessary libraries (Tailwind, Framer Motion, AWS SDK, Mongoose, AI SDKs, Socket.io, etc.).
67
+ - Automatically sets up boilerplate code like `server.js` or `App.jsx` and configures TailwindCSS.
68
+
69
+ ---
70
+
71
+ ### 🚀 4. `nexforge deploy`
72
+
45
73
  **Description:** Package and deploy your current directory to NexForge.
46
74
  **Usage:**
75
+
47
76
  ```bash
48
77
  nexforge deploy
49
78
  ```
79
+
50
80
  **Details:**
81
+
51
82
  - Instantly zips your source code (ignoring `node_modules`, `.git`, and `.env`).
52
83
  - Uploads the code to NexForge backend servers.
53
84
  - Automatically connects to the live **WebSocket log stream** so you can watch your build progress in real-time.
@@ -55,54 +86,71 @@ nexforge deploy
55
86
 
56
87
  ---
57
88
 
58
- ### 🌐 4. `nexforge env`
89
+ ### 🌐 5. `nexforge env`
90
+
59
91
  **Description:** Manage your environment variables securely.
60
92
 
61
93
  #### Push Variables
94
+
62
95
  ```bash
63
96
  nexforge env push
64
97
  ```
98
+
65
99
  - Reads your local `.env` file and securely pushes all variables to your NexForge project.
66
100
 
67
101
  #### Pull Variables
102
+
68
103
  ```bash
69
104
  nexforge env pull
70
105
  ```
106
+
71
107
  - Fetches all environment variables from your live NexForge project and saves them into a local `.env` file.
72
108
 
73
109
  ---
74
110
 
75
- ### 📜 5. `nexforge logs`
111
+ ### 📜 6. `nexforge logs`
112
+
76
113
  **Description:** Stream live build and deployment logs for your project.
77
114
  **Usage:**
115
+
78
116
  ```bash
79
117
  nexforge logs
80
118
  ```
81
- **Details:**
119
+
120
+ **Details:**
121
+
82
122
  - Connects to the NexForge WebSocket server and streams real-time logs for any ongoing deployment pipelines.
83
123
 
84
124
  ---
85
125
 
86
- ### ⏪ 6. `nexforge rollback`
126
+ ### ⏪ 7. `nexforge rollback`
127
+
87
128
  **Description:** Instantly revert your live website to a previous stable deployment.
88
129
  **Usage:**
130
+
89
131
  ```bash
90
132
  nexforge rollback
91
133
  ```
134
+
92
135
  **Details:**
136
+
93
137
  - Fetches a list of your recent successful and failed deployments.
94
138
  - Prompts you to select a version.
95
139
  - Instantly updates the symbolic links on the server to serve the older version with **zero downtime** and **no rebuild required**.
96
140
 
97
141
  ---
98
142
 
99
- ### ✏️ 7. `nexforge rename`
143
+ ### ✏️ 8. `nexforge rename`
144
+
100
145
  **Description:** Rename the subdomain of your project.
101
146
  **Usage:**
147
+
102
148
  ```bash
103
149
  nexforge rename
104
150
  ```
151
+
105
152
  **Details:**
153
+
106
154
  - Asks for your new preferred subdomain.
107
155
  - Validates availability on the NexForge servers.
108
156
  - Instantly updates your live URL to the new subdomain.
@@ -110,4 +158,3 @@ nexforge rename
110
158
  ---
111
159
 
112
160
  ### Built With ❤️ by Manav Sharma
113
-
package/index.js CHANGED
@@ -1,535 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { program } = require("commander")
4
- const axios = require("axios")
5
- const FormData = require("form-data")
6
- const archiver = require("archiver")
7
- const fs = require("fs")
8
- const path = require("path")
9
- const chalk = require("chalk")
10
- const ora = require("ora")
11
-
12
- // store user config to our home directory
13
- const os = require("os")
14
- const CONFIG_DIR = path.join(os.homedir(), ".nexforge")
15
- const CONFIG_FILE = path.join(CONFIG_DIR, "config.json")
16
-
17
- const API_BASE_URL = "https://nexforge-backend.up.railway.app/api" // Make sure this points to your deployed backend URL in production
18
-
19
- // Intercept axios requests to attach the CLI Token if it exists
20
- axios.interceptors.request.use((config) => {
21
- if (fs.existsSync(CONFIG_FILE)) {
22
- const userConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
23
- if (userConfig.cliToken) {
24
- config.headers.Authorization = `Bearer ${userConfig.cliToken}`
25
- }
26
- }
27
- return config
28
- })
29
4
 
30
5
  program
31
6
  .name("nexforge")
32
7
  .description("CLI to some next-level deployment magic for NexForge")
33
8
  .version("1.0.0")
34
9
 
35
- // LOGIN COMMAND
36
-
37
- program
38
- .command("login")
39
- .description("Log in to your NexForge account")
40
- .action(async () => {
41
- const readline = require("readline").createInterface({
42
- input: process.stdin,
43
- output: process.stdout
44
- })
45
-
46
- console.log(chalk.cyan("🚀 Welcome to NexForge CLI!"))
47
-
48
- readline.question("Please enter your personal CLI Token (Generate this from Dashboard Settings): ", (cliToken) => {
49
- if (!cliToken) {
50
- console.log(chalk.red("CLI Token cannot be empty!"))
51
- readline.close()
52
- return
53
- }
54
-
55
- readline.question("Please enter your Project ID: ", (projectId) => {
56
- if (!projectId) {
57
- console.log(chalk.red("Project ID cannot be empty!"))
58
- readline.close()
59
- return
60
- }
61
-
62
- if (!fs.existsSync(CONFIG_DIR)) {
63
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
64
- }
65
-
66
- // Save both Token and Project ID
67
- fs.writeFileSync(CONFIG_FILE, JSON.stringify({ cliToken, projectId }))
68
-
69
- console.log(chalk.green("✅ Successfully logged in! You can now run `nexforge deploy`"))
70
- readline.close()
71
- })
72
- })
73
- })
74
-
75
- // INIT COMMAND
76
- // This allows a user to create a brand new NexForge project without leaving the terminal
77
- program
78
- .command("init")
79
- .description("Initialize a new NexForge project in the current directory")
80
- .action(async () => {
81
- const readline = require("readline").createInterface({
82
- input: process.stdin,
83
- output: process.stdout
84
- })
85
-
86
- console.log(chalk.cyan("✨ Let's set up a new NexForge project!"))
87
-
88
- readline.question("What is your project name? (e.g. my-app): ", async (projectName) => {
89
- if (!projectName) {
90
- console.log(chalk.red("Project name cannot be empty!"))
91
- readline.close()
92
- return
93
- }
94
-
95
- readline.question("What framework are you using? (React, Vue, Express, Node, Next.js): ", async (framework) => {
96
- if (!framework) {
97
- console.log(chalk.red("Framework cannot be empty!"))
98
- readline.close()
99
- return
100
- }
101
-
102
- const spinner = ora("Creating project on NexForge servers...").start()
103
-
104
- try {
105
- const response = await axios.post(`${API_BASE_URL}/cli/init`, {
106
- projectName,
107
- framework,
108
- projectType: ["express", "node"].includes(framework.toLowerCase()) ? "NODE" : "STATIC"
109
- })
110
-
111
- const { projectId } = response.data
112
-
113
- // Ensure the config directory exists before saving
114
- if (!fs.existsSync(CONFIG_DIR)) {
115
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
116
- }
117
-
118
- // We preserve the existing CLI Token if it exists, and just update the projectId
119
- let currentConfig = {}
120
- if (fs.existsSync(CONFIG_FILE)) {
121
- currentConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
122
- }
123
-
124
- fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...currentConfig, projectId }))
125
-
126
- spinner.succeed(chalk.green("🎉 Project created successfully!"))
127
- console.log(chalk.cyan(`Project ID: ${projectId} has been saved locally.`))
128
- console.log(chalk.yellow(`You can now run \`nexforge deploy\` to push your code.`))
129
-
130
- } catch (error) {
131
- spinner.fail(chalk.red("Failed to create project!"))
132
- if (error.response && error.response.data) {
133
- console.log(chalk.red(`Server Error: ${JSON.stringify(error.response.data)}`))
134
- } else {
135
- console.error(error.message)
136
- }
137
- } finally {
138
- readline.close()
139
- }
140
- })
141
- })
142
- })
143
-
144
-
145
- // DEPLOY COMMAND
146
- // This is where the magic happens. We package the current folder and fire it up to our backend servers.
147
-
148
- program
149
- .command("deploy")
150
- .description("Deploy the current directory to NexForge")
151
- .action(async () => {
152
- // First we check if they are logged in by reading the config file
153
- if (!fs.existsSync(CONFIG_FILE)) {
154
- console.log(chalk.red("❌ You are not logged in. Please run `nexforge login` first."))
155
- return
156
- }
157
-
158
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
159
- const projectId = config.projectId
160
-
161
- const spinner = ora("Packaging your project...").start()
162
- const zipPath = path.join(process.cwd(), "nexforge-build.zip")
163
-
164
- try {
165
- // We create a write stream to zip the contents of the current folder
166
- const output = fs.createWriteStream(zipPath)
167
- const archive = archiver("zip", { zlib: { level: 9 } }) // Maximum compression
168
-
169
- // We need to wait for the archive to finish writing
170
- const archivePromise = new Promise((resolve, reject) => {
171
- output.on("close", resolve)
172
- archive.on("error", reject)
173
- })
174
-
175
- archive.pipe(output)
176
-
177
- // We grab everything in the current directory EXCEPT node_modules and hidden files
178
- // Skipping node_modules is super important otherwise the upload would take forever
179
- archive.glob("**/*", {
180
- cwd: process.cwd(),
181
- ignore: ["node_modules/**", ".git/**", "nexforge-build.zip", ".env"]
182
- })
183
-
184
- await archive.finalize()
185
- await archivePromise
186
-
187
- spinner.text = "Uploading to NexForge servers..."
188
-
189
- // Now we prepare the multipart form-data request to send the zip file
190
- const formData = new FormData()
191
- formData.append("projectZip", fs.createReadStream(zipPath))
192
-
193
- const response = await axios.post(`${API_BASE_URL}/cli/deploy/${projectId}`, formData, {
194
- headers: {
195
- ...formData.getHeaders()
196
- }
197
- })
198
-
199
- spinner.succeed(chalk.green("🎉 Deployment queued successfully!"))
200
- console.log(chalk.cyan(`Live URL: ${response.data.liveUrl}`))
201
- console.log(chalk.gray(`Streaming live build logs...\n`))
202
-
203
- // Automatically stream logs after deployment
204
- const io = require("socket.io-client")
205
- const socket = io(API_BASE_URL.replace("/api", ""), {
206
- transports: ["websocket", "polling"]
207
- })
208
-
209
- socket.emit("joinProject", projectId)
210
-
211
- socket.on("new-log", (entry) => {
212
- const color = entry.level === "ERROR" ? chalk.red : entry.level === "WARN" ? chalk.yellow : chalk.white
213
- console.log(color(`[${new Date(entry.timestamp).toLocaleTimeString()}] ${entry.message}`))
214
- })
215
-
216
- socket.on("status-change", (data) => {
217
- if (data.status === "LIVE") {
218
- console.log(chalk.green(`\n✅ Deployment successful and Live!`))
219
- socket.disconnect()
220
- process.exit(0)
221
- } else if (data.status === "FAILED") {
222
- console.log(chalk.red(`\n❌ Deployment failed!`))
223
- socket.disconnect()
224
- process.exit(1)
225
- }
226
- })
227
-
228
- } catch (error) {
229
- spinner.fail(chalk.red("Deployment failed!"))
230
-
231
- // If our API returned a specific error message we want to show that to the user
232
- if (error.response && error.response.data) {
233
- console.log(chalk.red(`Server Error: ${JSON.stringify(error.response.data)}`))
234
- } else {
235
- console.error(error.message)
236
- }
237
- } finally {
238
- // Clean up the temporary zip file so we don't leave trash on the user's PC
239
- if (fs.existsSync(zipPath)) {
240
- fs.unlinkSync(zipPath)
241
- }
242
- }
243
- })
244
-
245
- // ROLLBACK COMMAND
246
-
247
- program
248
- .command("rollback")
249
- .description("Rollback to the previous stable deployment")
250
- .action(async () => {
251
- console.log(chalk.yellow("🚧 Rollback command is under construction."))
252
- console.log(chalk.cyan("Wait for the backend versioning architecture to be finalized before using this."))
253
- })
254
-
255
- // ENV COMMANDS
256
- // Allows user to push and pull environment variables
257
- const envCommand = program
258
- .command("env")
259
- .description("Manage your environment variables")
260
-
261
- envCommand
262
- .command("push")
263
- .description("Push local .env file to NexForge")
264
- .action(async () => {
265
- if (!fs.existsSync(CONFIG_FILE)) {
266
- console.log(chalk.red("❌ You are not logged in. Please run `nexforge login` first."))
267
- return
268
- }
269
-
270
- const envPath = path.join(process.cwd(), ".env")
271
- if (!fs.existsSync(envPath)) {
272
- console.log(chalk.red("❌ No .env file found in the current directory."))
273
- return
274
- }
275
-
276
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
277
- const projectId = config.projectId
278
-
279
- const spinner = ora("Pushing environment variables...").start()
280
- try {
281
- const dotenv = require("dotenv")
282
- const envConfig = dotenv.parse(fs.readFileSync(envPath))
283
-
284
- const envs = Object.keys(envConfig).map(key => ({
285
- key,
286
- value: envConfig[key]
287
- }))
288
-
289
- await axios.post(`${API_BASE_URL}/cli/env/push/${projectId}`, { envs })
290
-
291
- spinner.succeed(chalk.green("🎉 Environment variables pushed successfully!"))
292
- } catch (error) {
293
- spinner.fail(chalk.red("Failed to push environment variables!"))
294
- console.error(error.message)
295
- }
296
- })
297
-
298
- envCommand
299
- .command("pull")
300
- .description("Pull environment variables from NexForge to local .env")
301
- .action(async () => {
302
- if (!fs.existsSync(CONFIG_FILE)) {
303
- console.log(chalk.red("❌ You are not logged in. Please run `nexforge login` first."))
304
- return
305
- }
306
-
307
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
308
- const projectId = config.projectId
309
-
310
- const spinner = ora("Pulling environment variables...").start()
311
- try {
312
- const response = await axios.get(`${API_BASE_URL}/cli/env/pull/${projectId}`)
313
- const envs = response.data.envs
314
-
315
- if (!envs || envs.length === 0) {
316
- spinner.info(chalk.yellow("No environment variables found on the server."))
317
- return
318
- }
319
-
320
- let envContent = ""
321
- envs.forEach(e => {
322
- envContent += `${e.key}=${e.value}\n`
323
- })
324
-
325
- const envPath = path.join(process.cwd(), ".env")
326
- fs.writeFileSync(envPath, envContent)
327
-
328
- spinner.succeed(chalk.green("🎉 Environment variables pulled successfully!"))
329
- console.log(chalk.cyan(`Saved to ${envPath}`))
330
- } catch (error) {
331
- spinner.fail(chalk.red("Failed to pull environment variables!"))
332
- console.error(error.message)
333
- }
334
- })
335
-
336
- // LOGS COMMAND
337
- // Allows user to view live build logs for their project
338
- program
339
- .command("logs")
340
- .description("Stream live build logs for your NexForge project")
341
- .action(async () => {
342
- if (!fs.existsSync(CONFIG_FILE)) {
343
- console.log(chalk.red("❌ You are not logged in. Please run `nexforge login` first."))
344
- return
345
- }
346
-
347
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
348
- const projectId = config.projectId
349
-
350
- console.log(chalk.cyan(`🔌 Connecting to live log stream for project ${projectId}...`))
351
-
352
- const io = require("socket.io-client")
353
- const socket = io(API_BASE_URL.replace("/api", ""), {
354
- transports: ["websocket", "polling"]
355
- })
356
-
357
- socket.emit("joinProject", projectId)
358
-
359
- socket.on("initial-logs", (initialLogs) => {
360
- initialLogs.forEach(entry => {
361
- const color = entry.level === "ERROR" ? chalk.red : entry.level === "WARN" ? chalk.yellow : chalk.white
362
- console.log(color(`[${new Date(entry.timestamp).toLocaleTimeString()}] ${entry.message}`))
363
- })
364
- })
365
-
366
- socket.on("new-log", (entry) => {
367
- const color = entry.level === "ERROR" ? chalk.red : entry.level === "WARN" ? chalk.yellow : chalk.white
368
- console.log(color(`[${new Date(entry.timestamp).toLocaleTimeString()}] ${entry.message}`))
369
- })
370
-
371
- socket.on("status-change", (data) => {
372
- if (data.status === "LIVE" || data.status === "FAILED") {
373
- console.log(chalk.gray(`\nPipeline finished with status: ${data.status}`))
374
- process.exit(0)
375
- }
376
- })
377
- })
378
-
379
- // ROLLBACK COMMAND
380
- // Revert to a previous deployment version instantly
381
- program
382
- .command("rollback")
383
- .description("Rollback your live website to a previous deployment")
384
- .action(async () => {
385
- if (!fs.existsSync(CONFIG_FILE)) {
386
- console.log(chalk.red("❌ You are not logged in. Please run `nexforge login` first."))
387
- return
388
- }
389
-
390
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
391
- const projectId = config.projectId
392
-
393
- const spinner = ora("Fetching previous deployments...").start()
394
- try {
395
- const response = await axios.get(`${API_BASE_URL}/cli/deployments/${projectId}`)
396
- const deployments = response.data.deployments
397
-
398
- spinner.stop()
399
-
400
- if (!deployments || deployments.length === 0) {
401
- console.log(chalk.yellow("No previous deployments found."))
402
- return
403
- }
404
-
405
- console.log(chalk.cyan("\nRecent Deployments:"))
406
- deployments.forEach((dep, index) => {
407
- const date = new Date(dep.createdAt).toLocaleString()
408
- const statusColor = dep.status === "LIVE" ? chalk.green : dep.status === "FAILED" ? chalk.red : chalk.yellow
409
- console.log(`[${index + 1}] ${dep._id} - ${statusColor(dep.status)} - ${date}`)
410
- })
411
-
412
- const readline = require("readline").createInterface({
413
- input: process.stdin,
414
- output: process.stdout
415
- })
416
-
417
- readline.question("\nEnter the number of the deployment you want to rollback to (or 'q' to cancel): ", async (answer) => {
418
- if (answer.toLowerCase() === 'q') {
419
- readline.close()
420
- return
421
- }
422
-
423
- const index = parseInt(answer) - 1
424
- if (isNaN(index) || index < 0 || index >= deployments.length) {
425
- console.log(chalk.red("Invalid selection."))
426
- readline.close()
427
- return
428
- }
429
-
430
- const selectedDeployment = deployments[index]
431
-
432
- const rollbackSpinner = ora(`Rolling back to ${selectedDeployment._id}...`).start()
433
- try {
434
- await axios.post(`${API_BASE_URL}/cli/rollback/${projectId}`, {
435
- deploymentId: selectedDeployment._id
436
- })
437
- rollbackSpinner.succeed(chalk.green(`🎉 Successfully rolled back to deployment ${selectedDeployment._id}!`))
438
- console.log(chalk.cyan("Your website has been instantly updated. No rebuild required."))
439
- } catch (error) {
440
- rollbackSpinner.fail(chalk.red("Rollback failed!"))
441
- if (error.response && error.response.data) {
442
- console.log(chalk.red(`Error: ${error.response.data.error}`))
443
- } else {
444
- console.error(error.message)
445
- }
446
- } finally {
447
- readline.close()
448
- }
449
- })
450
- } catch (error) {
451
- spinner.fail(chalk.red("Failed to fetch deployments!"))
452
- console.error(error.message)
453
- }
454
- })
455
-
456
- // change name of subdomain
457
- program
458
- .command("rename")
459
- .description("Rename the subdomain of your project")
460
- .action(async () => {
461
- const readline = require("readline").createInterface({
462
- input: process.stdin,
463
- output: process.stdout
464
- })
465
-
466
- console.log(chalk.cyan("✨ Let's rename your project subdomain!"))
467
-
468
- let defaultProjectId = ""
469
- if (fs.existsSync(CONFIG_FILE)) {
470
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
471
- defaultProjectId = config.projectId || ""
472
- }
473
-
474
- const promptText = defaultProjectId
475
- ? `What is your project ID? (${defaultProjectId}) `
476
- : "What is your project ID? "
477
-
478
- readline.question(promptText, async (inputProjectId) => {
479
- const projectId = inputProjectId.trim() || defaultProjectId
480
-
481
- if (!projectId) {
482
- console.log(chalk.red("Project ID cannot be empty!"))
483
- readline.close()
484
- return
485
- }
486
-
487
- readline.question("What is your new subdomain? ", async (newSubdomain) => {
488
- if (!newSubdomain) {
489
- console.log(chalk.red("Subdomain cannot be empty!"))
490
- readline.close()
491
- return
492
- }
493
-
494
- const spinner = ora("Renaming project subdomain...").start()
495
-
496
- try {
497
- const response = await axios.post(`${API_BASE_URL}/cli/rename/${projectId}`, {
498
- newSubdomain
499
- })
500
-
501
- const { projectId } = response.data
502
-
503
- // Ensure the config directory exists before saving
504
- if (!fs.existsSync(CONFIG_DIR)) {
505
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
506
- }
507
-
508
- // We preserve the existing CLI Token if it exists, and just update the projectId
509
- let currentConfig = {}
510
- if (fs.existsSync(CONFIG_FILE)) {
511
- currentConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
512
- }
513
-
514
- fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...currentConfig, projectId }))
515
-
516
- spinner.succeed(chalk.green("🎉 Project subdomain renamed successfully!"))
517
- console.log(chalk.cyan(`Project ID: ${projectId} has been saved locally.`))
518
- console.log(chalk.yellow(`You can now run \`nexforge deploy\` to push your code.`))
519
-
520
- } catch (error) {
521
- spinner.fail(chalk.red("Failed to rename project subdomain!"))
522
- if (error.response && error.response.data) {
523
- console.log(chalk.red(`Server Error: ${JSON.stringify(error.response.data)}`))
524
- } else {
525
- console.error(error.message)
526
- }
527
- } finally {
528
- readline.close()
529
- }
530
- })
531
- })
532
- })
10
+ require("./src/commands/login")(program)
11
+ require("./src/commands/init")(program)
12
+ require("./src/commands/deploy")(program)
13
+ require("./src/commands/env")(program)
14
+ require("./src/commands/logs")(program)
15
+ require("./src/commands/rollback")(program)
16
+ require("./src/commands/rename")(program)
17
+ require("./src/commands/create")(program)
533
18
 
534
19
  // Parse the arguments passed by the user in the terminal
535
20
  program.parse(process.argv)