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.
Files changed (3) hide show
  1. package/README.md +52 -0
  2. package/index.js +447 -0
  3. 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
+ }