novacloud22-cli 1.0.2 → 1.0.4
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/index.js +100 -100
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const open = require('open')
|
|
|
13
13
|
const os = require('os')
|
|
14
14
|
|
|
15
15
|
const API_BASE = 'https://backend-vjzu.onrender.com'
|
|
16
|
+
const SITES_API = 'https://novacloud22-sites.onrender.com'
|
|
16
17
|
const FRONTEND_URL = 'https://novacloud22.web.app'
|
|
17
18
|
const CONFIG_DIR = path.join(os.homedir(), '.novacloud22')
|
|
18
19
|
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json')
|
|
@@ -86,114 +87,78 @@ program
|
|
|
86
87
|
.description('Authenticate with your NovaCloud22 account')
|
|
87
88
|
.action(async () => {
|
|
88
89
|
banner()
|
|
89
|
-
const spinner = ora('Starting authentication...').start()
|
|
90
90
|
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const token = url.searchParams.get('token')
|
|
91
|
+
// Generate unique state ID for polling
|
|
92
|
+
const state = require('crypto').randomBytes(16).toString('hex')
|
|
93
|
+
const loginUrl = `${FRONTEND_URL}/cli-auth?state=${state}`
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
console.log(chalk.bold('\n NovaCloud22 CLI — Login\n'))
|
|
96
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
97
|
+
console.log(chalk.cyan('\n Login URL:'))
|
|
98
|
+
console.log(chalk.blue.underline(` ${loginUrl}\n`))
|
|
99
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
100
|
+
|
|
101
|
+
const { action } = await inquirer.prompt([{
|
|
102
|
+
type: 'list',
|
|
103
|
+
name: 'action',
|
|
104
|
+
message: 'What would you like to do?',
|
|
105
|
+
choices: [
|
|
106
|
+
{ name: '🌐 Open in browser', value: 'open' },
|
|
107
|
+
{ name: '📋 Copy link', value: 'copy' },
|
|
108
|
+
{ name: '✕ Cancel', value: 'cancel' },
|
|
109
|
+
]
|
|
110
|
+
}])
|
|
111
|
+
|
|
112
|
+
if (action === 'cancel') {
|
|
113
|
+
console.log(chalk.gray('\n Login cancelled.\n'))
|
|
114
|
+
process.exit(0)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (action === 'open') {
|
|
118
|
+
await open(loginUrl)
|
|
119
|
+
console.log(chalk.gray('\n Browser opened. Complete sign in there.\n'))
|
|
120
|
+
} else if (action === 'copy') {
|
|
121
|
+
const { execSync } = require('child_process')
|
|
122
|
+
try {
|
|
123
|
+
const platform = process.platform
|
|
124
|
+
if (platform === 'darwin') execSync(`echo '${loginUrl}' | pbcopy`)
|
|
125
|
+
else if (platform === 'win32') execSync(`echo ${loginUrl} | clip`)
|
|
126
|
+
else execSync(`echo '${loginUrl}' | xclip -selection clipboard`)
|
|
127
|
+
console.log(chalk.green('\n ✓ Link copied! Paste it in your browser.\n'))
|
|
128
|
+
} catch {
|
|
129
|
+
console.log(chalk.yellow('\n Copy this link manually:'))
|
|
130
|
+
console.log(chalk.blue.underline(` ${loginUrl}\n`))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Poll backend every 2s until token received (max 5 min)
|
|
135
|
+
const spinner = ora('Waiting for authentication...').start()
|
|
136
|
+
const maxWait = 150
|
|
137
|
+
let waited = 0
|
|
138
|
+
|
|
139
|
+
while (waited < maxWait) {
|
|
140
|
+
await new Promise(r => setTimeout(r, 2000))
|
|
141
|
+
waited++
|
|
142
|
+
try {
|
|
143
|
+
const res = await axios.get(`${API_BASE}/cli-auth/poll?state=${state}`)
|
|
144
|
+
if (res.data.ready && res.data.token) {
|
|
145
|
+
const token = res.data.token
|
|
99
146
|
const verifyRes = await axios.get(`${API_BASE}/user/profile`, {
|
|
100
147
|
headers: { Authorization: `Bearer ${token}` }
|
|
101
148
|
})
|
|
102
149
|
saveToken(token)
|
|
103
|
-
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
104
|
-
res.end(`
|
|
105
|
-
<html><body style="font-family:sans-serif;text-align:center;padding:3rem;background:#0f0c29;color:white">
|
|
106
|
-
<h2>✅ Logged in successfully!</h2>
|
|
107
|
-
<p>You can close this tab and return to the terminal.</p>
|
|
108
|
-
<p style="color:rgba(255,255,255,0.5);font-size:0.9rem">NovaCloud22 CLI</p>
|
|
109
|
-
</body></html>
|
|
110
|
-
`)
|
|
111
|
-
server.close()
|
|
112
150
|
spinner.succeed(chalk.green(`Logged in as ${verifyRes.data.email}`))
|
|
113
151
|
console.log(chalk.gray('\n Run novacloud22 init to set up your project\n'))
|
|
114
|
-
|
|
115
|
-
res.writeHead(400, { 'Content-Type': 'text/html' })
|
|
116
|
-
res.end('<html><body>Authentication failed. Please try again.</body></html>')
|
|
117
|
-
server.close()
|
|
118
|
-
spinner.fail('Authentication failed')
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
res.writeHead(400)
|
|
122
|
-
res.end('No token received')
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
server.listen(9876, async () => {
|
|
127
|
-
spinner.stop()
|
|
128
|
-
const loginUrl = `${FRONTEND_URL}/cli-auth?callback=http://localhost:9876`
|
|
129
|
-
|
|
130
|
-
console.log(chalk.bold('\n NovaCloud22 CLI — Login\n'))
|
|
131
|
-
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
132
|
-
console.log(chalk.cyan('\n Login URL:'))
|
|
133
|
-
console.log(chalk.blue.underline(` ${loginUrl}\n`))
|
|
134
|
-
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
135
|
-
|
|
136
|
-
const { action } = await inquirer.prompt([{
|
|
137
|
-
type: 'list',
|
|
138
|
-
name: 'action',
|
|
139
|
-
message: 'What would you like to do?',
|
|
140
|
-
choices: [
|
|
141
|
-
{ name: '🌐 Open in browser', value: 'open' },
|
|
142
|
-
{ name: '📋 Copy link', value: 'copy' },
|
|
143
|
-
{ name: '✕ Cancel', value: 'cancel' },
|
|
144
|
-
]
|
|
145
|
-
}])
|
|
146
|
-
|
|
147
|
-
if (action === 'open') {
|
|
148
|
-
await open(loginUrl)
|
|
149
|
-
console.log(chalk.gray('\n Browser opened. Complete sign in there.\n'))
|
|
150
|
-
console.log(chalk.gray(' Waiting for authentication...'))
|
|
151
|
-
} else if (action === 'copy') {
|
|
152
|
-
// Copy to clipboard using pbcopy (mac) / clip (win) / xclip (linux)
|
|
153
|
-
const { execSync } = require('child_process')
|
|
154
|
-
try {
|
|
155
|
-
const platform = process.platform
|
|
156
|
-
if (platform === 'darwin') execSync(`echo '${loginUrl}' | pbcopy`)
|
|
157
|
-
else if (platform === 'win32') execSync(`echo ${loginUrl} | clip`)
|
|
158
|
-
else execSync(`echo '${loginUrl}' | xclip -selection clipboard`)
|
|
159
|
-
console.log(chalk.green('\n ✓ Link copied to clipboard!'))
|
|
160
|
-
console.log(chalk.gray(' Paste it in your browser to sign in.\n'))
|
|
161
|
-
console.log(chalk.gray(' Waiting for authentication...'))
|
|
162
|
-
} catch {
|
|
163
|
-
console.log(chalk.yellow('\n Could not copy automatically. Copy this link manually:'))
|
|
164
|
-
console.log(chalk.blue.underline(` ${loginUrl}\n`))
|
|
152
|
+
process.exit(0)
|
|
165
153
|
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
server.close()
|
|
169
|
-
process.exit(0)
|
|
154
|
+
} catch {
|
|
155
|
+
// keep polling
|
|
170
156
|
}
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
server.on('error', () => {
|
|
174
|
-
spinner.fail('Could not start local server on port 9876')
|
|
175
|
-
console.log(chalk.yellow('\n Manual login: Go to ' + FRONTEND_URL + '/signin'))
|
|
176
|
-
console.log(chalk.yellow(' Then run: novacloud22 login --token YOUR_TOKEN\n'))
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
// LOGIN WITH TOKEN (manual fallback)
|
|
181
|
-
program
|
|
182
|
-
.command('login:token <token>')
|
|
183
|
-
.description('Login with a token directly')
|
|
184
|
-
.action(async (token) => {
|
|
185
|
-
const spinner = ora('Verifying token...').start()
|
|
186
|
-
try {
|
|
187
|
-
const res = await axios.get(`${API_BASE}/user/profile`, {
|
|
188
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
189
|
-
})
|
|
190
|
-
saveToken(token)
|
|
191
|
-
spinner.succeed(chalk.green(`Logged in as ${res.data.email}`))
|
|
192
|
-
} catch {
|
|
193
|
-
spinner.fail('Invalid token')
|
|
194
157
|
}
|
|
195
|
-
})
|
|
196
158
|
|
|
159
|
+
spinner.fail('Login timed out. Please try again.')
|
|
160
|
+
process.exit(1)
|
|
161
|
+
})
|
|
197
162
|
// INIT
|
|
198
163
|
program
|
|
199
164
|
.command('init')
|
|
@@ -215,7 +180,7 @@ program
|
|
|
215
180
|
const spinner = ora('Fetching your sites...').start()
|
|
216
181
|
let sites = []
|
|
217
182
|
try {
|
|
218
|
-
const res = await axios.get(`${
|
|
183
|
+
const res = await axios.get(`${SITES_API}/p/my-sites`, { headers })
|
|
219
184
|
sites = res.data.sites || []
|
|
220
185
|
spinner.stop()
|
|
221
186
|
} catch {
|
|
@@ -257,6 +222,41 @@ program
|
|
|
257
222
|
])
|
|
258
223
|
|
|
259
224
|
saveProjectConfig(answers)
|
|
225
|
+
|
|
226
|
+
// Auto-fix vite.config.js to use relative paths
|
|
227
|
+
const viteConfigs = ['vite.config.js', 'vite.config.ts']
|
|
228
|
+
let viteFixed = false
|
|
229
|
+
for (const configFile of viteConfigs) {
|
|
230
|
+
if (fs.existsSync(configFile)) {
|
|
231
|
+
const content = fs.readFileSync(configFile, 'utf8')
|
|
232
|
+
if (!content.includes("base:")) {
|
|
233
|
+
// Add base: './' to existing config
|
|
234
|
+
const updated = content.replace(
|
|
235
|
+
/defineConfig\s*\(\s*\{/,
|
|
236
|
+
`defineConfig({\n base: './',`
|
|
237
|
+
)
|
|
238
|
+
if (updated !== content) {
|
|
239
|
+
fs.writeFileSync(configFile, updated)
|
|
240
|
+
console.log(chalk.green(` ✓ Updated ${configFile} with base: './'`))
|
|
241
|
+
viteFixed = true
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.gray(` ✓ ${configFile} already has base config`))
|
|
245
|
+
viteFixed = true
|
|
246
|
+
}
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// No vite config found — create one
|
|
251
|
+
if (!viteFixed && fs.existsSync('package.json')) {
|
|
252
|
+
const pkg = fs.readJsonSync('package.json')
|
|
253
|
+
const hasVite = pkg.devDependencies?.vite || pkg.dependencies?.vite
|
|
254
|
+
if (hasVite) {
|
|
255
|
+
fs.writeFileSync('vite.config.js', `import { defineConfig } from 'vite'\n\nexport default defineConfig({\n base: './',\n})\n`)
|
|
256
|
+
console.log(chalk.green(" \u2713 Created vite.config.js with base: './'"))
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
260
|
console.log(chalk.green(`\n ✓ Created novacloud22.json`))
|
|
261
261
|
console.log(chalk.gray(` Site: ${FRONTEND_URL}/p/${answers.slug}`))
|
|
262
262
|
console.log(chalk.gray(` Run: novacloud22 deploy\n`))
|
|
@@ -305,7 +305,7 @@ program
|
|
|
305
305
|
spinner.text = `Uploading ${relativePath}...`
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
const res = await axios.post(`${
|
|
308
|
+
const res = await axios.post(`${SITES_API}/p/deploy`, form, {
|
|
309
309
|
headers: { ...headers, ...form.getHeaders() },
|
|
310
310
|
maxContentLength: Infinity,
|
|
311
311
|
maxBodyLength: Infinity,
|
|
@@ -337,7 +337,7 @@ program
|
|
|
337
337
|
const headers = authHeaders()
|
|
338
338
|
const spinner = ora('Fetching your sites...').start()
|
|
339
339
|
try {
|
|
340
|
-
const res = await axios.get(`${
|
|
340
|
+
const res = await axios.get(`${SITES_API}/p/my-sites`, { headers })
|
|
341
341
|
const sites = res.data.sites || []
|
|
342
342
|
spinner.stop()
|
|
343
343
|
|
|
@@ -434,7 +434,7 @@ program
|
|
|
434
434
|
|
|
435
435
|
const spinner = ora('Fetching status...').start()
|
|
436
436
|
try {
|
|
437
|
-
const res = await axios.get(`${
|
|
437
|
+
const res = await axios.get(`${SITES_API}/p/info/${config.slug}`, { headers })
|
|
438
438
|
spinner.stop()
|
|
439
439
|
const site = res.data
|
|
440
440
|
console.log(chalk.bold(` Site: /${site.slug}`))
|
package/package.json
CHANGED