novacloud22-cli 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.
- package/README.md +52 -0
- package/index.js +447 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# NovaCloud22 CLI
|
|
2
|
+
|
|
3
|
+
Deploy static websites to NovaCloud22 — like Firebase CLI but for NovaCloud22.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g novacloud22-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Login
|
|
15
|
+
novacloud22 login
|
|
16
|
+
|
|
17
|
+
# 2. Go to your project
|
|
18
|
+
cd my-website
|
|
19
|
+
|
|
20
|
+
# 3. Initialize
|
|
21
|
+
novacloud22 init
|
|
22
|
+
# ? Site name (slug): rishav
|
|
23
|
+
# ? Folder to deploy: dist
|
|
24
|
+
# ? Personal Drive to use: drive_1
|
|
25
|
+
|
|
26
|
+
# 4. Build
|
|
27
|
+
npm run build
|
|
28
|
+
|
|
29
|
+
# 5. Deploy
|
|
30
|
+
novacloud22 deploy
|
|
31
|
+
# ✓ Live at: https://novacloud22.web.app/p/rishav
|
|
32
|
+
|
|
33
|
+
# Other commands
|
|
34
|
+
novacloud22 open # open site in browser
|
|
35
|
+
novacloud22 status # check deploy status
|
|
36
|
+
novacloud22 delete # delete site only (not your account)
|
|
37
|
+
novacloud22 whoami # show logged in user
|
|
38
|
+
novacloud22 logout # log out
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Node.js 14+
|
|
44
|
+
- NovaCloud22 account
|
|
45
|
+
- Personal Google Drive connected in dashboard
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- Files stored in your Personal Google Drive under `NovaCloud22Sites/{slug}/`
|
|
50
|
+
- Cache updates only on new deploy — visitors see same version until you redeploy
|
|
51
|
+
- Delete only removes site folder + record, nothing else touched
|
|
52
|
+
- Works with any static site: HTML, React, Vue, Svelte, Next.js export, etc.
|
package/index.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
const ora = require('ora')
|
|
6
|
+
const inquirer = require('inquirer')
|
|
7
|
+
const axios = require('axios')
|
|
8
|
+
const FormData = require('form-data')
|
|
9
|
+
const fs = require('fs-extra')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const http = require('http')
|
|
12
|
+
const open = require('open')
|
|
13
|
+
const os = require('os')
|
|
14
|
+
|
|
15
|
+
const API_BASE = 'https://backend-vjzu.onrender.com'
|
|
16
|
+
const FRONTEND_URL = 'https://novacloud22.web.app'
|
|
17
|
+
const CONFIG_DIR = path.join(os.homedir(), '.novacloud22')
|
|
18
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json')
|
|
19
|
+
const PROJECT_CONFIG = 'novacloud22.json'
|
|
20
|
+
|
|
21
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function saveToken(token) {
|
|
24
|
+
fs.ensureDirSync(CONFIG_DIR)
|
|
25
|
+
fs.writeJsonSync(TOKEN_FILE, { token, saved_at: new Date().toISOString() })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getToken() {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(TOKEN_FILE)) return null
|
|
31
|
+
return fs.readJsonSync(TOKEN_FILE).token
|
|
32
|
+
} catch {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getProjectConfig() {
|
|
38
|
+
try {
|
|
39
|
+
if (!fs.existsSync(PROJECT_CONFIG)) return null
|
|
40
|
+
return fs.readJsonSync(PROJECT_CONFIG)
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function saveProjectConfig(config) {
|
|
47
|
+
fs.writeJsonSync(PROJECT_CONFIG, config, { spaces: 2 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function authHeaders() {
|
|
51
|
+
const token = getToken()
|
|
52
|
+
if (!token) {
|
|
53
|
+
console.log(chalk.red('\n✗ Not logged in. Run: novacloud22 login\n'))
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
return { Authorization: `Bearer ${token}` }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
60
|
+
const results = []
|
|
61
|
+
const items = fs.readdirSync(dir)
|
|
62
|
+
for (const item of items) {
|
|
63
|
+
const fullPath = path.join(dir, item)
|
|
64
|
+
const stat = fs.statSync(fullPath)
|
|
65
|
+
if (stat.isDirectory()) {
|
|
66
|
+
results.push(...getAllFiles(fullPath, baseDir))
|
|
67
|
+
} else {
|
|
68
|
+
results.push({ fullPath, relativePath: path.relative(baseDir, fullPath).replace(/\\/g, '/') })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return results
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Banner ────────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function banner() {
|
|
77
|
+
console.log(chalk.bold.blue('\n NovaCloud22 CLI'))
|
|
78
|
+
console.log(chalk.gray(' Deploy static sites to NovaCloud22\n'))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
// LOGIN
|
|
84
|
+
program
|
|
85
|
+
.command('login')
|
|
86
|
+
.description('Authenticate with your NovaCloud22 account')
|
|
87
|
+
.action(async () => {
|
|
88
|
+
banner()
|
|
89
|
+
const spinner = ora('Starting authentication...').start()
|
|
90
|
+
|
|
91
|
+
// Start local server to receive token
|
|
92
|
+
const server = http.createServer(async (req, res) => {
|
|
93
|
+
const url = new URL(req.url, 'http://localhost:9876')
|
|
94
|
+
const token = url.searchParams.get('token')
|
|
95
|
+
|
|
96
|
+
if (token) {
|
|
97
|
+
// Verify token with backend
|
|
98
|
+
try {
|
|
99
|
+
const verifyRes = await axios.get(`${API_BASE}/user/profile`, {
|
|
100
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
101
|
+
})
|
|
102
|
+
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
|
+
spinner.succeed(chalk.green(`Logged in as ${verifyRes.data.email}`))
|
|
113
|
+
console.log(chalk.gray('\n Run novacloud22 init to set up your project\n'))
|
|
114
|
+
} catch {
|
|
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.text = 'Opening browser...'
|
|
128
|
+
const loginUrl = `${FRONTEND_URL}/signin?cli=1&callback=http://localhost:9876`
|
|
129
|
+
await open(loginUrl)
|
|
130
|
+
spinner.text = 'Waiting for login in browser...'
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
server.on('error', () => {
|
|
134
|
+
spinner.fail('Could not start local server on port 9876')
|
|
135
|
+
console.log(chalk.yellow('\n Manual login: Go to ' + FRONTEND_URL + '/signin'))
|
|
136
|
+
console.log(chalk.yellow(' Then run: novacloud22 login --token YOUR_TOKEN\n'))
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// LOGIN WITH TOKEN (manual fallback)
|
|
141
|
+
program
|
|
142
|
+
.command('login:token <token>')
|
|
143
|
+
.description('Login with a token directly')
|
|
144
|
+
.action(async (token) => {
|
|
145
|
+
const spinner = ora('Verifying token...').start()
|
|
146
|
+
try {
|
|
147
|
+
const res = await axios.get(`${API_BASE}/user/profile`, {
|
|
148
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
149
|
+
})
|
|
150
|
+
saveToken(token)
|
|
151
|
+
spinner.succeed(chalk.green(`Logged in as ${res.data.email}`))
|
|
152
|
+
} catch {
|
|
153
|
+
spinner.fail('Invalid token')
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// INIT
|
|
158
|
+
program
|
|
159
|
+
.command('init')
|
|
160
|
+
.description('Initialize NovaCloud22 in your project')
|
|
161
|
+
.action(async () => {
|
|
162
|
+
banner()
|
|
163
|
+
const headers = authHeaders()
|
|
164
|
+
|
|
165
|
+
const existing = getProjectConfig()
|
|
166
|
+
if (existing) {
|
|
167
|
+
console.log(chalk.yellow(` Already initialized: slug="${existing.slug}", deploy="${existing.deploy_dir}"\n`))
|
|
168
|
+
const { reinit } = await inquirer.prompt([{
|
|
169
|
+
type: 'confirm', name: 'reinit', message: 'Reinitialize?', default: false
|
|
170
|
+
}])
|
|
171
|
+
if (!reinit) return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Fetch registered slugs from dashboard
|
|
175
|
+
const spinner = ora('Fetching your sites...').start()
|
|
176
|
+
let sites = []
|
|
177
|
+
try {
|
|
178
|
+
const res = await axios.get(`${API_BASE}/p/my-sites`, { headers })
|
|
179
|
+
sites = res.data.sites || []
|
|
180
|
+
spinner.stop()
|
|
181
|
+
} catch {
|
|
182
|
+
spinner.stop()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (sites.length === 0) {
|
|
186
|
+
console.log(chalk.yellow('\n No site names registered yet.'))
|
|
187
|
+
console.log(chalk.gray(' Go to NovaCloud22 dashboard → My Website → Add New Site Name'))
|
|
188
|
+
console.log(chalk.gray(' Then run: novacloud22 init\n'))
|
|
189
|
+
process.exit(0)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Show sites like firebase projects:list
|
|
193
|
+
console.log(chalk.bold('\n Your NovaCloud22 Sites:'))
|
|
194
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)))
|
|
195
|
+
sites.forEach((s, i) => {
|
|
196
|
+
const status = s.deployed_at ? chalk.green('● deployed') : chalk.yellow('○ registered')
|
|
197
|
+
console.log(` ${chalk.cyan(('/' + s.slug).padEnd(25))} ${status}`)
|
|
198
|
+
})
|
|
199
|
+
console.log('')
|
|
200
|
+
|
|
201
|
+
const answers = await inquirer.prompt([
|
|
202
|
+
{
|
|
203
|
+
type: 'list',
|
|
204
|
+
name: 'slug',
|
|
205
|
+
message: 'Select site to deploy to:',
|
|
206
|
+
choices: sites.map(s => ({
|
|
207
|
+
name: `/${s.slug} ${s.deployed_at ? chalk.gray('(deployed)') : chalk.yellow('(not deployed yet)')}`,
|
|
208
|
+
value: s.slug
|
|
209
|
+
}))
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'input',
|
|
213
|
+
name: 'deploy_dir',
|
|
214
|
+
message: 'Folder to deploy:',
|
|
215
|
+
default: existing?.deploy_dir || 'dist',
|
|
216
|
+
}
|
|
217
|
+
])
|
|
218
|
+
|
|
219
|
+
saveProjectConfig(answers)
|
|
220
|
+
console.log(chalk.green(`\n ✓ Created novacloud22.json`))
|
|
221
|
+
console.log(chalk.gray(` Site: ${FRONTEND_URL}/p/${answers.slug}`))
|
|
222
|
+
console.log(chalk.gray(` Run: novacloud22 deploy\n`))
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// DEPLOY
|
|
226
|
+
program
|
|
227
|
+
.command('deploy')
|
|
228
|
+
.description('Deploy your site to NovaCloud22')
|
|
229
|
+
.action(async () => {
|
|
230
|
+
banner()
|
|
231
|
+
const headers = authHeaders()
|
|
232
|
+
const config = getProjectConfig()
|
|
233
|
+
|
|
234
|
+
if (!config) {
|
|
235
|
+
console.log(chalk.red(' ✗ Not initialized. Run: novacloud22 init\n'))
|
|
236
|
+
process.exit(1)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { slug, deploy_dir, drive_id } = config
|
|
240
|
+
const deployPath = path.resolve(process.cwd(), deploy_dir)
|
|
241
|
+
|
|
242
|
+
if (!fs.existsSync(deployPath)) {
|
|
243
|
+
console.log(chalk.red(` ✗ Deploy folder not found: ${deployPath}`))
|
|
244
|
+
console.log(chalk.yellow(` Run your build command first (e.g. npm run build)\n`))
|
|
245
|
+
process.exit(1)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const files = getAllFiles(deployPath)
|
|
249
|
+
if (files.length === 0) {
|
|
250
|
+
console.log(chalk.red(` ✗ No files found in ${deployPath}\n`))
|
|
251
|
+
process.exit(1)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(chalk.gray(` Deploying ${files.length} files from ${deploy_dir}/ → /${slug}\n`))
|
|
255
|
+
|
|
256
|
+
const spinner = ora('Uploading files...').start()
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const form = new FormData()
|
|
260
|
+
form.append('slug', slug)
|
|
261
|
+
|
|
262
|
+
for (const { fullPath, relativePath } of files) {
|
|
263
|
+
const content = fs.readFileSync(fullPath)
|
|
264
|
+
form.append('files', content, { filename: relativePath })
|
|
265
|
+
spinner.text = `Uploading ${relativePath}...`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const res = await axios.post(`${API_BASE}/p/deploy`, form, {
|
|
269
|
+
headers: { ...headers, ...form.getHeaders() },
|
|
270
|
+
maxContentLength: Infinity,
|
|
271
|
+
maxBodyLength: Infinity,
|
|
272
|
+
timeout: 120000
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
spinner.succeed(chalk.green('Deploy complete!'))
|
|
276
|
+
console.log('')
|
|
277
|
+
console.log(chalk.bold(' 🌐 Live at:'), chalk.blue.underline(res.data.url))
|
|
278
|
+
console.log(chalk.gray(` Files: ${res.data.files_deployed}`))
|
|
279
|
+
console.log(chalk.gray(` Deploy ID: ${res.data.deploy_id}`))
|
|
280
|
+
console.log(chalk.gray(` Time: ${new Date(res.data.deployed_at).toLocaleString()}`))
|
|
281
|
+
console.log('')
|
|
282
|
+
|
|
283
|
+
} catch (e) {
|
|
284
|
+
spinner.fail('Deploy failed')
|
|
285
|
+
const msg = e.response?.data?.detail || e.message
|
|
286
|
+
console.log(chalk.red(` ✗ ${msg}\n`))
|
|
287
|
+
process.exit(1)
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// LIST
|
|
292
|
+
program
|
|
293
|
+
.command('list')
|
|
294
|
+
.description('List all your registered sites')
|
|
295
|
+
.action(async () => {
|
|
296
|
+
banner()
|
|
297
|
+
const headers = authHeaders()
|
|
298
|
+
const spinner = ora('Fetching your sites...').start()
|
|
299
|
+
try {
|
|
300
|
+
const res = await axios.get(`${API_BASE}/p/my-sites`, { headers })
|
|
301
|
+
const sites = res.data.sites || []
|
|
302
|
+
spinner.stop()
|
|
303
|
+
|
|
304
|
+
if (sites.length === 0) {
|
|
305
|
+
console.log(chalk.yellow(' No sites registered yet.'))
|
|
306
|
+
console.log(chalk.gray(' Go to NovaCloud22 dashboard → My Website → Add New Site Name\n'))
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log(chalk.bold(` ${'Site Name'.padEnd(25)} ${'Status'.padEnd(12)} ${'Files'.padEnd(8)} URL`))
|
|
311
|
+
console.log(chalk.gray(' ' + '─'.repeat(80)))
|
|
312
|
+
|
|
313
|
+
for (const site of sites) {
|
|
314
|
+
const status = site.deployed_at ? chalk.green('● deployed') : chalk.yellow('○ registered')
|
|
315
|
+
const files = String(site.file_count || 0).padEnd(8)
|
|
316
|
+
const slug = chalk.cyan(('/' + site.slug).padEnd(25))
|
|
317
|
+
const url = chalk.gray(site.url || '')
|
|
318
|
+
console.log(` ${slug} ${status.padEnd(20)} ${files} ${url}`)
|
|
319
|
+
}
|
|
320
|
+
console.log('')
|
|
321
|
+
} catch (e) {
|
|
322
|
+
spinner.fail('Failed to fetch sites')
|
|
323
|
+
console.log(chalk.red(` ✗ ${e.response?.data?.detail || e.message}\n`))
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// DELETE
|
|
328
|
+
program
|
|
329
|
+
.command('delete')
|
|
330
|
+
.description('Delete your deployed site (only removes site files, not your account)')
|
|
331
|
+
.action(async () => {
|
|
332
|
+
banner()
|
|
333
|
+
const headers = authHeaders()
|
|
334
|
+
const config = getProjectConfig()
|
|
335
|
+
|
|
336
|
+
if (!config) {
|
|
337
|
+
console.log(chalk.red(' ✗ Not initialized. Run: novacloud22 init\n'))
|
|
338
|
+
process.exit(1)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const { slug } = config
|
|
342
|
+
|
|
343
|
+
const { confirm } = await inquirer.prompt([{
|
|
344
|
+
type: 'confirm',
|
|
345
|
+
name: 'confirm',
|
|
346
|
+
message: chalk.yellow(`Delete site /${slug}? This only removes the site folder and record.`),
|
|
347
|
+
default: false
|
|
348
|
+
}])
|
|
349
|
+
|
|
350
|
+
if (!confirm) {
|
|
351
|
+
console.log(chalk.gray(' Cancelled.\n'))
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const spinner = ora(`Deleting /${slug}...`).start()
|
|
356
|
+
try {
|
|
357
|
+
await axios.delete(`${API_BASE}/p/delete/${slug}`, { headers })
|
|
358
|
+
spinner.succeed(chalk.green(`Site /${slug} deleted`))
|
|
359
|
+
console.log(chalk.gray(' Your account and other files are untouched.\n'))
|
|
360
|
+
} catch (e) {
|
|
361
|
+
spinner.fail('Delete failed')
|
|
362
|
+
console.log(chalk.red(` ✗ ${e.response?.data?.detail || e.message}\n`))
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// OPEN
|
|
367
|
+
program
|
|
368
|
+
.command('open')
|
|
369
|
+
.description('Open your deployed site in browser')
|
|
370
|
+
.action(async () => {
|
|
371
|
+
const config = getProjectConfig()
|
|
372
|
+
if (!config) {
|
|
373
|
+
console.log(chalk.red(' ✗ Not initialized. Run: novacloud22 init\n'))
|
|
374
|
+
process.exit(1)
|
|
375
|
+
}
|
|
376
|
+
const url = `${FRONTEND_URL}/p/${config.slug}`
|
|
377
|
+
console.log(chalk.blue(` Opening ${url}...\n`))
|
|
378
|
+
await open(url)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// STATUS
|
|
382
|
+
program
|
|
383
|
+
.command('status')
|
|
384
|
+
.description('Show current site status')
|
|
385
|
+
.action(async () => {
|
|
386
|
+
banner()
|
|
387
|
+
const headers = authHeaders()
|
|
388
|
+
const config = getProjectConfig()
|
|
389
|
+
|
|
390
|
+
if (!config) {
|
|
391
|
+
console.log(chalk.red(' ✗ Not initialized. Run: novacloud22 init\n'))
|
|
392
|
+
process.exit(1)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const spinner = ora('Fetching status...').start()
|
|
396
|
+
try {
|
|
397
|
+
const res = await axios.get(`${API_BASE}/p/info/${config.slug}`, { headers })
|
|
398
|
+
spinner.stop()
|
|
399
|
+
const site = res.data
|
|
400
|
+
console.log(chalk.bold(` Site: /${site.slug}`))
|
|
401
|
+
console.log(chalk.gray(` URL: ${site.url}`))
|
|
402
|
+
console.log(chalk.gray(` Files: ${site.file_count}`))
|
|
403
|
+
console.log(chalk.gray(` Last deployed: ${new Date(site.deployed_at).toLocaleString()}`))
|
|
404
|
+
console.log(chalk.gray(` Deploy ID: ${site.deploy_id}\n`))
|
|
405
|
+
} catch (e) {
|
|
406
|
+
spinner.fail('Site not found or not deployed yet')
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
// WHOAMI
|
|
411
|
+
program
|
|
412
|
+
.command('whoami')
|
|
413
|
+
.description('Show logged in user')
|
|
414
|
+
.action(async () => {
|
|
415
|
+
const headers = authHeaders()
|
|
416
|
+
try {
|
|
417
|
+
const res = await axios.get(`${API_BASE}/user/profile`, { headers })
|
|
418
|
+
console.log(chalk.green(` Logged in as: ${res.data.email}\n`))
|
|
419
|
+
} catch {
|
|
420
|
+
console.log(chalk.red(' Not logged in or token expired. Run: novacloud22 login\n'))
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// LOGOUT
|
|
425
|
+
program
|
|
426
|
+
.command('logout')
|
|
427
|
+
.description('Log out')
|
|
428
|
+
.action(() => {
|
|
429
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
430
|
+
fs.removeSync(TOKEN_FILE)
|
|
431
|
+
console.log(chalk.green(' Logged out.\n'))
|
|
432
|
+
} else {
|
|
433
|
+
console.log(chalk.gray(' Not logged in.\n'))
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
program
|
|
438
|
+
.name('novacloud22')
|
|
439
|
+
.version('1.0.0')
|
|
440
|
+
.description('NovaCloud22 CLI — Deploy static sites')
|
|
441
|
+
|
|
442
|
+
program.parse(process.argv)
|
|
443
|
+
|
|
444
|
+
if (!process.argv.slice(2).length) {
|
|
445
|
+
banner()
|
|
446
|
+
program.outputHelp()
|
|
447
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "novacloud22-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Deploy static websites to NovaCloud22",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"novacloud22": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"No tests yet\""
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axios": "^1.6.0",
|
|
14
|
+
"chalk": "^4.1.2",
|
|
15
|
+
"commander": "^11.0.0",
|
|
16
|
+
"form-data": "^4.0.0",
|
|
17
|
+
"fs-extra": "^11.1.1",
|
|
18
|
+
"inquirer": "^8.2.6",
|
|
19
|
+
"open": "^8.4.2",
|
|
20
|
+
"ora": "^5.4.1"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"novacloud22",
|
|
24
|
+
"deploy",
|
|
25
|
+
"hosting",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "NovaCloud22",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=14.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|