nexforge-cli 1.0.5 → 1.0.7

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.
@@ -0,0 +1,50 @@
1
+ const fs = require("fs")
2
+ const chalk = require("chalk")
3
+ const { CONFIG_DIR, CONFIG_FILE } = require("../config")
4
+
5
+ module.exports = (program) => {
6
+ program
7
+ .command("login")
8
+ .description("Log in to your NexForge account")
9
+ .action(async () => {
10
+ const readline = require("readline").createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout,
13
+ })
14
+
15
+ console.log(chalk.cyan("🚀 Welcome to NexForge CLI!"))
16
+
17
+ readline.question(
18
+ "Please enter your personal CLI Token (Generate this from Dashboard Settings): ",
19
+ (cliToken) => {
20
+ if (!cliToken) {
21
+ console.log(chalk.red("CLI Token cannot be empty!"))
22
+ readline.close()
23
+ return
24
+ }
25
+
26
+ readline.question("Please enter your Project ID: ", (projectId) => {
27
+ if (!projectId) {
28
+ console.log(chalk.red("Project ID cannot be empty!"))
29
+ readline.close()
30
+ return
31
+ }
32
+
33
+ if (!fs.existsSync(CONFIG_DIR)) {
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
35
+ }
36
+
37
+ // Save both Token and Project ID
38
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify({ cliToken, projectId }))
39
+
40
+ console.log(
41
+ chalk.green(
42
+ "✅ Successfully logged in! You can now run `nexforge deploy`",
43
+ ),
44
+ )
45
+ readline.close()
46
+ })
47
+ },
48
+ )
49
+ })
50
+ }
@@ -0,0 +1,74 @@
1
+ const fs = require("fs")
2
+ const chalk = require("chalk")
3
+ const { API_BASE_URL, CONFIG_FILE } = require("../config")
4
+
5
+ module.exports = (program) => {
6
+ program
7
+ .command("logs")
8
+ .description("Stream live build logs for your NexForge project")
9
+ .action(async () => {
10
+ if (!fs.existsSync(CONFIG_FILE)) {
11
+ console.log(
12
+ chalk.red(
13
+ "❌ You are not logged in. Please run `nexforge login` first.",
14
+ ),
15
+ )
16
+ return
17
+ }
18
+
19
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
20
+ const projectId = config.projectId
21
+
22
+ console.log(
23
+ chalk.cyan(
24
+ `🔌 Connecting to live log stream for project ${projectId}...`,
25
+ ),
26
+ )
27
+
28
+ const io = require("socket.io-client")
29
+ const socket = io(API_BASE_URL.replace("/api", ""), {
30
+ transports: ["websocket", "polling"],
31
+ })
32
+
33
+ socket.emit("joinProject", projectId)
34
+
35
+ socket.on("initial-logs", (initialLogs) => {
36
+ initialLogs.forEach((entry) => {
37
+ const color =
38
+ entry.level === "ERROR"
39
+ ? chalk.red
40
+ : entry.level === "WARN"
41
+ ? chalk.yellow
42
+ : chalk.white
43
+ console.log(
44
+ color(
45
+ `[${new Date(entry.timestamp).toLocaleTimeString()}] ${entry.message}`,
46
+ ),
47
+ )
48
+ })
49
+ })
50
+
51
+ socket.on("new-log", (entry) => {
52
+ const color =
53
+ entry.level === "ERROR"
54
+ ? chalk.red
55
+ : entry.level === "WARN"
56
+ ? chalk.yellow
57
+ : chalk.white
58
+ console.log(
59
+ color(
60
+ `[${new Date(entry.timestamp).toLocaleTimeString()}] ${entry.message}`,
61
+ ),
62
+ )
63
+ })
64
+
65
+ socket.on("status-change", (data) => {
66
+ if (data.status === "LIVE" || data.status === "FAILED") {
67
+ console.log(
68
+ chalk.gray(`\nPipeline finished with status: ${data.status}`),
69
+ )
70
+ process.exit(0)
71
+ }
72
+ })
73
+ })
74
+ }
@@ -0,0 +1,103 @@
1
+ const fs = require("fs")
2
+ const chalk = require("chalk")
3
+ const ora = require("ora")
4
+ const { axios, API_BASE_URL, CONFIG_DIR, CONFIG_FILE } = require("../config")
5
+
6
+ module.exports = (program) => {
7
+ program
8
+ .command("rename")
9
+ .description("Rename the subdomain of your project")
10
+ .action(async () => {
11
+ const readline = require("readline").createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ })
15
+
16
+ console.log(chalk.cyan("✨ Let's rename your project subdomain!"))
17
+
18
+ let defaultProjectId = ""
19
+ if (fs.existsSync(CONFIG_FILE)) {
20
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
21
+ defaultProjectId = config.projectId || ""
22
+ }
23
+
24
+ const promptText = defaultProjectId
25
+ ? `What is your project ID? (${defaultProjectId}) `
26
+ : "What is your project ID? "
27
+
28
+ readline.question(promptText, async (inputProjectId) => {
29
+ const projectId = inputProjectId.trim() || defaultProjectId
30
+
31
+ if (!projectId) {
32
+ console.log(chalk.red("Project ID cannot be empty!"))
33
+ readline.close()
34
+ return
35
+ }
36
+
37
+ readline.question(
38
+ "What is your new subdomain? ",
39
+ async (newSubdomain) => {
40
+ if (!newSubdomain) {
41
+ console.log(chalk.red("Subdomain cannot be empty!"))
42
+ readline.close()
43
+ return
44
+ }
45
+
46
+ const spinner = ora("Renaming project subdomain...").start()
47
+
48
+ try {
49
+ const response = await axios.post(
50
+ `${API_BASE_URL}/cli/rename/${projectId}`,
51
+ {
52
+ newSubdomain,
53
+ },
54
+ )
55
+
56
+ const { projectId: returnedProjectId } = response.data
57
+
58
+ // Ensure the config directory exists before saving
59
+ if (!fs.existsSync(CONFIG_DIR)) {
60
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
61
+ }
62
+
63
+ // We preserve the existing CLI Token if it exists, and just update the projectId
64
+ let currentConfig = {}
65
+ if (fs.existsSync(CONFIG_FILE)) {
66
+ currentConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
67
+ }
68
+
69
+ fs.writeFileSync(
70
+ CONFIG_FILE,
71
+ JSON.stringify({ ...currentConfig, projectId: returnedProjectId }),
72
+ )
73
+
74
+ spinner.succeed(
75
+ chalk.green("🎉 Project subdomain renamed successfully!"),
76
+ )
77
+ console.log(
78
+ chalk.cyan(`Project ID: ${returnedProjectId} has been saved locally.`),
79
+ )
80
+ console.log(
81
+ chalk.yellow(
82
+ `You can now run \`nexforge deploy\` to push your code.`,
83
+ ),
84
+ )
85
+ } catch (error) {
86
+ spinner.fail(chalk.red("Failed to rename project subdomain!"))
87
+ if (error.response && error.response.data) {
88
+ console.log(
89
+ chalk.red(
90
+ `Server Error: ${JSON.stringify(error.response.data)}`,
91
+ ),
92
+ )
93
+ } else {
94
+ console.error(error.message)
95
+ }
96
+ } finally {
97
+ readline.close()
98
+ }
99
+ },
100
+ )
101
+ })
102
+ })
103
+ }
@@ -0,0 +1,107 @@
1
+ const fs = require("fs")
2
+ const chalk = require("chalk")
3
+ const ora = require("ora")
4
+ const { axios, API_BASE_URL, CONFIG_FILE } = require("../config")
5
+
6
+ module.exports = (program) => {
7
+ program
8
+ .command("rollback")
9
+ .description("Rollback your live website to a previous deployment")
10
+ .action(async () => {
11
+ if (!fs.existsSync(CONFIG_FILE)) {
12
+ console.log(
13
+ chalk.red(
14
+ "❌ You are not logged in. Please run `nexforge login` first.",
15
+ ),
16
+ )
17
+ return
18
+ }
19
+
20
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
21
+ const projectId = config.projectId
22
+
23
+ const spinner = ora("Fetching previous deployments...").start()
24
+ try {
25
+ const response = await axios.get(
26
+ `${API_BASE_URL}/cli/deployments/${projectId}`,
27
+ )
28
+ const deployments = response.data.deployments
29
+
30
+ spinner.stop()
31
+
32
+ if (!deployments || deployments.length === 0) {
33
+ console.log(chalk.yellow("No previous deployments found."))
34
+ return
35
+ }
36
+
37
+ console.log(chalk.cyan("\nRecent Deployments:"))
38
+ deployments.forEach((dep, index) => {
39
+ const date = new Date(dep.createdAt).toLocaleString()
40
+ const statusColor =
41
+ dep.status === "LIVE"
42
+ ? chalk.green
43
+ : dep.status === "FAILED"
44
+ ? chalk.red
45
+ : chalk.yellow
46
+ console.log(
47
+ `[${index + 1}] ${dep._id} - ${statusColor(dep.status)} - ${date}`,
48
+ )
49
+ })
50
+
51
+ const readline = require("readline").createInterface({
52
+ input: process.stdin,
53
+ output: process.stdout,
54
+ })
55
+
56
+ readline.question(
57
+ "\nEnter the number of the deployment you want to rollback to (or 'q' to cancel): ",
58
+ async (answer) => {
59
+ if (answer.toLowerCase() === "q") {
60
+ readline.close()
61
+ return
62
+ }
63
+
64
+ const index = parseInt(answer) - 1
65
+ if (isNaN(index) || index < 0 || index >= deployments.length) {
66
+ console.log(chalk.red("Invalid selection."))
67
+ readline.close()
68
+ return
69
+ }
70
+
71
+ const selectedDeployment = deployments[index]
72
+
73
+ const rollbackSpinner = ora(
74
+ `Rolling back to ${selectedDeployment._id}...`,
75
+ ).start()
76
+ try {
77
+ await axios.post(`${API_BASE_URL}/cli/rollback/${projectId}`, {
78
+ deploymentId: selectedDeployment._id,
79
+ })
80
+ rollbackSpinner.succeed(
81
+ chalk.green(
82
+ `🎉 Successfully rolled back to deployment ${selectedDeployment._id}!`,
83
+ ),
84
+ )
85
+ console.log(
86
+ chalk.cyan(
87
+ "Your website has been instantly updated. No rebuild required.",
88
+ ),
89
+ )
90
+ } catch (error) {
91
+ rollbackSpinner.fail(chalk.red("Rollback failed!"))
92
+ if (error.response && error.response.data) {
93
+ console.log(chalk.red(`Error: ${error.response.data.error}`))
94
+ } else {
95
+ console.error(error.message)
96
+ }
97
+ } finally {
98
+ readline.close()
99
+ }
100
+ },
101
+ )
102
+ } catch (error) {
103
+ spinner.fail(chalk.red("Failed to fetch deployments!"))
104
+ console.error(error.message)
105
+ }
106
+ })
107
+ }
package/src/config.js ADDED
@@ -0,0 +1,27 @@
1
+ const axios = require("axios")
2
+ const fs = require("fs")
3
+ const path = require("path")
4
+ const os = require("os")
5
+
6
+ const CONFIG_DIR = path.join(os.homedir(), ".nexforge")
7
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json")
8
+
9
+ const API_BASE_URL = "https://nexforge-lbxg.onrender.com/api"
10
+
11
+ // Intercept axios requests to attach the CLI Token if it exists
12
+ axios.interceptors.request.use((config) => {
13
+ if (fs.existsSync(CONFIG_FILE)) {
14
+ const userConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"))
15
+ if (userConfig.cliToken) {
16
+ config.headers.Authorization = `Bearer ${userConfig.cliToken}`
17
+ }
18
+ }
19
+ return config
20
+ })
21
+
22
+ module.exports = {
23
+ CONFIG_DIR,
24
+ CONFIG_FILE,
25
+ API_BASE_URL,
26
+ axios
27
+ }