@joytreesite/joytree 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 ADDED
@@ -0,0 +1,169 @@
1
+ # 🌳 Joytree CLI
2
+
3
+ Deploy and manage your Joytree-hosted sites from the terminal.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @joytreeapp/joytree
9
+ ```
10
+
11
+ Or run without installing:
12
+ ```bash
13
+ npx @joytreeapp/joytree login
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # 1. Authenticate
22
+ joytree login
23
+
24
+ # 2. Deploy a GitHub repo
25
+ joytree deploy --repo https://github.com/you/my-site --name my-site
26
+
27
+ # 3. View your projects
28
+ joytree projects
29
+
30
+ # 4. Stream logs
31
+ joytree logs my-site --follow
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Commands
37
+
38
+ ### Account
39
+
40
+ | Command | Description |
41
+ |---|---|
42
+ | `joytree login [--api-key <key>]` | Authenticate with your Joytree API key |
43
+ | `joytree logout` | Remove saved credentials |
44
+ | `joytree whoami` | Show current account info |
45
+ | `joytree status` | Show account status and project list |
46
+
47
+ > Find your API key at: **your-joytree-dashboard → Settings → API Key**
48
+
49
+ ---
50
+
51
+ ### Deploy
52
+
53
+ | Command | Description |
54
+ |---|---|
55
+ | `joytree deploy` | Deploy a GitHub repo (interactive) |
56
+ | `joytree deploy --repo <url> --name <name>` | Deploy with flags |
57
+ | `joytree deploy --static` | Deploy as a static site |
58
+ | `joytree redeploy <project-id>` | Trigger a fresh redeployment |
59
+ | `joytree deployments [project-id]` | Show recent deployments |
60
+ | `joytree open <project-id>` | Open the live URL in your browser |
61
+
62
+ **Deploy flags:**
63
+ ```
64
+ -r, --repo <url> GitHub repository URL
65
+ -b, --branch <branch> Branch to deploy (default: main)
66
+ -n, --name <name> Project name / subdomain
67
+ --build <cmd> Build command, e.g. "npm run build"
68
+ --start <cmd> Start command, e.g. "node server.js"
69
+ --static Mark as a static site
70
+ -m, --message <msg> Deployment message
71
+ ```
72
+
73
+ ---
74
+
75
+ ### Projects
76
+
77
+ | Command | Description |
78
+ |---|---|
79
+ | `joytree projects` | List all projects |
80
+ | `joytree projects --json` | Output raw JSON |
81
+ | `joytree inspect <project-id>` | Show full project details |
82
+ | `joytree delete <project-id>` | Delete a project |
83
+
84
+ ---
85
+
86
+ ### Logs
87
+
88
+ | Command | Description |
89
+ |---|---|
90
+ | `joytree logs <project-id>` | Fetch recent runtime logs |
91
+ | `joytree logs <project-id> --lines 100` | Fetch last 100 lines |
92
+ | `joytree logs <project-id> --follow` | Stream live logs (poll every 3s) |
93
+
94
+ ---
95
+
96
+ ### Environment Variables
97
+
98
+ | Command | Description |
99
+ |---|---|
100
+ | `joytree env list <project-id>` | List all env var keys |
101
+ | `joytree env set <project-id> KEY=VALUE` | Set one or more env vars |
102
+ | `joytree env delete <project-id> KEY` | Delete an env var |
103
+ | `joytree env push <project-id>` | Push a local `.env` file |
104
+ | `joytree env push <project-id> --file prod.env --force` | Push & overwrite all |
105
+
106
+ ```bash
107
+ # Set multiple at once
108
+ joytree env set my-site DATABASE_URL=postgres://... SECRET_KEY=abc123
109
+
110
+ # Push your .env file
111
+ joytree env push my-site
112
+ ```
113
+
114
+ ---
115
+
116
+ ### Domains
117
+
118
+ | Command | Description |
119
+ |---|---|
120
+ | `joytree domains list` | List your custom domains |
121
+ | `joytree domains attach <domain> <project-id>` | Attach a domain to a project |
122
+ | `joytree domains verify <domain>` | Trigger DNS verification |
123
+ | `joytree domains remove <domain>` | Remove a custom domain |
124
+ | `joytree domains check <domain>` | Check domain availability |
125
+
126
+ ---
127
+
128
+ ### Databases
129
+
130
+ | Command | Description |
131
+ |---|---|
132
+ | `joytree db list` | List all databases |
133
+ | `joytree db create --type postgres --name mydb` | Create a database |
134
+ | `joytree db start <db-id>` | Start a stopped database |
135
+ | `joytree db stop <db-id>` | Stop a running database |
136
+ | `joytree db restart <db-id>` | Restart a database |
137
+ | `joytree db logs <db-id>` | Fetch recent database logs |
138
+ | `joytree db delete <db-id>` | Delete a database |
139
+
140
+ ---
141
+
142
+ ## Configuration
143
+
144
+ Credentials are stored at `~/.joytree/credentials.json` (mode 600).
145
+
146
+ You can also use environment variables:
147
+ ```bash
148
+ export JOYTREE_API_KEY=jtk_your_key_here
149
+ export JOYTREE_BASE_URL=https://joytree.app
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Publishing to npm
155
+
156
+ To publish this CLI so users can `npm install -g @joytreeapp/joytree`:
157
+
158
+ ```bash
159
+ cd joytree-cli
160
+ npm login # login to npm as @joytreeapp
161
+ npm publish --access public
162
+ ```
163
+
164
+ Then users install with:
165
+ ```bash
166
+ npm install -g @joytreeapp/joytree
167
+ # or
168
+ npx @joytreeapp/joytree login
169
+ ```
package/bin/joytree.js ADDED
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { program } = require('commander');
5
+ const pkg = require('../package.json');
6
+
7
+ // Commands
8
+ const auth = require('../commands/auth');
9
+ const deploy = require('../commands/deploy');
10
+ const projects= require('../commands/projects');
11
+ const envCmd = require('../commands/env');
12
+ const logs = require('../commands/logs');
13
+ const domains = require('../commands/domains');
14
+ const db = require('../commands/db');
15
+ const account = require('../commands/account');
16
+ const github = require('../commands/github');
17
+ const webhook = require('../commands/webhook');
18
+ const ssh = require('../commands/ssh');
19
+ const extras = require('../commands/extras');
20
+ const ui = require('../lib/ui');
21
+
22
+ // ── Custom help screen ────────────────────────────────────────────────
23
+ function showHelp() {
24
+ const c = ui.c;
25
+ ui.logo();
26
+
27
+ console.log(`${c.bold}USAGE${c.reset}`);
28
+ console.log(` joytree ${c.cyan}<command>${c.reset} ${c.dim}[options]${c.reset}\n`);
29
+
30
+ const section = (title, icon) => {
31
+ console.log(`${c.bold}${c.white}${icon} ${title}${c.reset}`);
32
+ };
33
+
34
+ const cmd = (name, args, desc) => {
35
+ const nameCol = `${c.cyan} joytree ${c.bold}${name}${c.reset}`;
36
+ const argsCol = args ? ` ${c.dim}${args}${c.reset}` : '';
37
+ const pad = Math.max(0, 42 - name.length - (args ? args.length + 1 : 0));
38
+ console.log(`${nameCol}${argsCol}${' '.repeat(pad)}${c.dim}${desc}${c.reset}`);
39
+ };
40
+
41
+ // ── ACCOUNT ──
42
+ section('Account', 'â—†');
43
+ cmd('login', '--api-key <key>', 'Validate and save your Joytree API key');
44
+ cmd('logout', '', 'Remove local credentials');
45
+ cmd('whoami', '', 'Show the active account and API key scope');
46
+ cmd('status', '', 'Show account status and project overview');
47
+ console.log();
48
+
49
+ // ── API KEY ──
50
+ section('API Key', 'â—†');
51
+ cmd('apikey show', '', 'View your current API key and usage stats');
52
+ cmd('apikey rotate', '', 'Revoke old key and generate a new one');
53
+ console.log();
54
+
55
+ // ── DEPLOY ──
56
+ section('Deploy', 'â—†');
57
+ cmd('deploy', '-r <repo> -n <name>', 'Deploy a GitHub repo to Joytree');
58
+ cmd('redeploy', '<project-id>', 'Trigger a fresh redeployment');
59
+ cmd('stop', '<deploy-id>', 'Cancel a currently running deployment');
60
+ cmd('autodeploy', '<project-id> --enable', 'Toggle GitHub push auto-deploy on/off');
61
+ cmd('deployments', '[project-id]', 'Show recent deployment history');
62
+ cmd('open', '<project-id>', 'Open the live URL in your browser');
63
+ console.log();
64
+
65
+ // ── PROJECTS ──
66
+ section('Projects', 'â—†');
67
+ cmd('projects', '', 'List all your projects');
68
+ cmd('inspect', '<project-id>', 'Show full project details');
69
+ cmd('delete', '<project-id>', 'Delete a project (irreversible)');
70
+ console.log();
71
+
72
+ // ── LOGS ──
73
+ section('Logs', 'â—†');
74
+ cmd('logs', '<project-id>', 'Fetch recent runtime logs');
75
+ cmd('logs', '<project-id> --follow', 'Stream live logs in real time');
76
+ console.log();
77
+
78
+ // ── ENV VARS ──
79
+ section('Environment Variables', 'â—†');
80
+ cmd('env list', '<project-id>', 'List all env var keys');
81
+ cmd('env set', '<project-id> KEY=VALUE', 'Set one or more env vars');
82
+ cmd('env delete', '<project-id> <KEY>', 'Delete an env var');
83
+ cmd('env push', '<project-id>', 'Push a local .env file to a project');
84
+ console.log();
85
+
86
+ // ── GITHUB ──
87
+ section('GitHub', 'â—†');
88
+ cmd('pull repos', '', 'List your linked GitHub repositories');
89
+ cmd('pull branches', '<repo-url>', 'List branches for a repository');
90
+ console.log();
91
+
92
+ // ── DOMAINS ──
93
+ section('Domains', 'â—†');
94
+ cmd('domains list', '', 'List your custom domains');
95
+ cmd('domains attach', '<domain> <project-id>', 'Attach a domain to a project');
96
+ cmd('domains verify', '<domain>', 'Trigger DNS verification');
97
+ cmd('domains remove', '<domain>', 'Remove a custom domain');
98
+ cmd('domains check', '<domain>', 'Check domain availability');
99
+ console.log();
100
+
101
+ // ── DATABASES ──
102
+ section('Databases', 'â—†');
103
+ cmd('db list', '', 'List all databases');
104
+ cmd('db create', '--type', 'Create a new database (postgres/mysql/redis/mongo)');
105
+ cmd('db start', '<db-id>', 'Start a stopped database');
106
+ cmd('db stop', '<db-id>', 'Stop a running database');
107
+ cmd('db restart', '<db-id>', 'Restart a database');
108
+ cmd('db logs', '<db-id>', 'Fetch recent database logs');
109
+ cmd('db delete', '<db-id>', 'Delete a database (irreversible)');
110
+ console.log();
111
+
112
+ // ── WEBHOOK ──
113
+ section('Webhooks', 'â—†');
114
+ cmd('webhook secret', '', 'Show your global webhook secret');
115
+ cmd('webhook rotate', '', 'Regenerate your webhook secret');
116
+ console.log();
117
+
118
+ // ── SSH ──
119
+ section('SSH Keys', 'â—†');
120
+ cmd('ssh list', '', 'List all SSH keys');
121
+ cmd('ssh generate', '--name', 'Generate a new SSH key pair');
122
+ cmd('ssh delete', '<key-id>', 'Delete an SSH key');
123
+ console.log();
124
+
125
+ // ── ACTIVITY ──
126
+ section('Activity', 'â—†');
127
+ cmd('activity', '--limit <n>', 'Show recent platform activity feed');
128
+ console.log();
129
+
130
+ // ── FOOTER ──
131
+ console.log(`${c.dim} Run ${c.cyan}joytree <command> --help${c.dim} for detailed options on any command.${c.reset}`);
132
+ console.log(`${c.dim} Docs: ${c.cyan}https://docs.joytree.app${c.reset}\n`);
133
+ }
134
+
135
+ program
136
+ .name('joytree')
137
+ .description('Joytree CLI — deploy and manage your projects from the terminal')
138
+ .version(pkg.version)
139
+ .helpOption(false)
140
+ .addHelpCommand(false)
141
+ .option('-h, --help', 'Show this help screen')
142
+ .hook('preAction', (thisCommand) => {
143
+ if (thisCommand.opts().help) { showHelp(); process.exit(0); }
144
+ });
145
+
146
+ // Override default --help
147
+ program.on('--help', showHelp);
148
+
149
+ // Show custom help when no args given
150
+ if (process.argv.length === 2) {
151
+ showHelp();
152
+ process.exit(0);
153
+ }
154
+
155
+ // ── Auth ──────────────────────────────────────────────────────────────
156
+ program
157
+ .command('login')
158
+ .description('Authenticate with your Joytree API key')
159
+ .option('--api-key <key>', 'Provide API key directly (skips prompt)')
160
+ .action(auth.login);
161
+
162
+ program
163
+ .command('logout')
164
+ .description('Remove saved credentials')
165
+ .action(auth.logout);
166
+
167
+ program
168
+ .command('whoami')
169
+ .description('Show the active account and API key scope')
170
+ .action(auth.whoami);
171
+
172
+ program
173
+ .command('status')
174
+ .description('Show account status, plan, and project count')
175
+ .action(account.status);
176
+
177
+ // ── API Key ───────────────────────────────────────────────────────────
178
+ const apikeyGroup = program
179
+ .command('apikey')
180
+ .description('Manage your Joytree API key');
181
+
182
+ apikeyGroup.command('show').description('Show your current API key and usage info').action(extras.apiKey);
183
+ apikeyGroup.command('rotate').description('Generate a new API key and revoke the old one').action(extras.rotateApiKey);
184
+
185
+ // ── Activity ──────────────────────────────────────────────────────────
186
+ program
187
+ .command('activity')
188
+ .description('Show recent platform activity')
189
+ .option('--limit <n>', 'Number of events to show', '20')
190
+ .action(extras.activity);
191
+
192
+ // ── GitHub ────────────────────────────────────────────────────────────
193
+ const pullGroup = program.command('pull').description('Browse connected GitHub repos and branches');
194
+ pullGroup.command('repos').description('List your linked GitHub repositories').action(github.repos);
195
+ pullGroup.command('branches <repo-url>').description('List branches for a repository').action(github.branches);
196
+
197
+ // ── Deploy ────────────────────────────────────────────────────────────
198
+ program
199
+ .command('deploy')
200
+ .description('Deploy a GitHub repository to Joytree')
201
+ .option('-r, --repo <url>', 'GitHub repository URL')
202
+ .option('-b, --branch <branch>', 'Branch to deploy', 'main')
203
+ .option('-n, --name <name>', 'Project name / subdomain')
204
+ .option('--build <cmd>', 'Build command')
205
+ .option('--start <cmd>', 'Start command')
206
+ .option('--static', 'Mark as a static site')
207
+ .option('-m, --message <msg>', 'Deployment message')
208
+ .action(deploy.deployGit);
209
+
210
+ program.command('redeploy <project-id>').description('Trigger a fresh deployment').action(deploy.redeploy);
211
+ program.command('stop <deploy-id>').description('Stop a currently running deployment').action(extras.stopDeploy);
212
+ program
213
+ .command('autodeploy <project-id>')
214
+ .description('Toggle GitHub push auto-deploy')
215
+ .option('--enable', 'Enable auto-deploy')
216
+ .option('--disable', 'Disable auto-deploy')
217
+ .action(extras.autodeploy);
218
+ program.command('open [project-id]').description('Open the live URL in your browser').action(deploy.open);
219
+ program
220
+ .command('deployments [project-id]')
221
+ .description('Show recent deployments')
222
+ .option('--limit <n>', 'Number to show', '10')
223
+ .action(deploy.listDeployments);
224
+
225
+ // ── Projects ──────────────────────────────────────────────────────────
226
+ program.command('projects').description('List all your projects').option('--json', 'Output raw JSON').action(projects.list);
227
+ program.command('inspect <project-id>').description('Show full project details').action(projects.inspect);
228
+ program.command('delete <project-id>').description('Delete a project (irreversible)').option('-y, --yes', 'Skip confirmation').action(projects.deleteProject);
229
+
230
+ // ── Logs ──────────────────────────────────────────────────────────────
231
+ program
232
+ .command('logs <project-id>')
233
+ .description('Fetch runtime logs for a project')
234
+ .option('--lines <n>', 'Number of lines', '50')
235
+ .option('-f, --follow', 'Stream live logs')
236
+ .action(logs.fetchLogs);
237
+
238
+ // ── Env ───────────────────────────────────────────────────────────────
239
+ const envGroup = program.command('env').description('Manage environment variables');
240
+ envGroup.command('list <project-id>').description('List all env var keys').action(envCmd.list);
241
+ envGroup.command('set <project-id> <KEY=VALUE...>').description('Set one or more env vars').action(envCmd.set);
242
+ envGroup.command('delete <project-id> <KEY>').description('Delete an env var').action(envCmd.del);
243
+ envGroup.command('push <project-id>').description('Push a .env file').option('--file <path>', 'Path to .env file', '.env').option('--force', 'Overwrite all existing vars').action(envCmd.push);
244
+
245
+ // ── Domains ───────────────────────────────────────────────────────────
246
+ const domainGroup = program.command('domains').description('Manage custom domains');
247
+ domainGroup.command('list').description('List your custom domains').action(domains.list);
248
+ domainGroup.command('attach <domain> <project-id>').description('Attach a domain to a project').action(domains.attach);
249
+ domainGroup.command('verify <domain>').description('Trigger DNS verification').action(domains.verify);
250
+ domainGroup.command('remove <domain>').description('Remove a custom domain').action(domains.remove);
251
+ domainGroup.command('check <domain>').description('Check domain availability').action(domains.check);
252
+
253
+ // ── Databases ─────────────────────────────────────────────────────────
254
+ const dbGroup = program.command('db').description('Manage databases');
255
+ dbGroup.command('list').description('List all databases').action(db.list);
256
+ dbGroup.command('create').description('Create a new database').option('--type <type>', 'Database type', 'postgres').option('--name <name>', 'Database name').action(db.create);
257
+ dbGroup.command('start <db-id>').description('Start a stopped database').action(db.start);
258
+ dbGroup.command('stop <db-id>').description('Stop a running database').action(db.stop);
259
+ dbGroup.command('restart <db-id>').description('Restart a database').action(db.restart);
260
+ dbGroup.command('logs <db-id>').description('Fetch database logs').action(db.fetchLogs);
261
+ dbGroup.command('delete <db-id>').description('Delete a database').option('-y, --yes', 'Skip confirmation').action(db.del);
262
+
263
+ // ── Webhook ───────────────────────────────────────────────────────────
264
+ const webhookGroup = program.command('webhook').description('Manage GitHub webhook secrets');
265
+ webhookGroup.command('secret').description('Show your global webhook secret').action(webhook.getSecret);
266
+ webhookGroup.command('rotate').description('Regenerate your webhook secret').action(webhook.rotateSecret);
267
+
268
+ // ── SSH ───────────────────────────────────────────────────────────────
269
+ const sshGroup = program.command('ssh').description('Manage SSH keys');
270
+ sshGroup.command('list').description('List all SSH keys').action(ssh.list);
271
+ sshGroup.command('generate').description('Generate a new SSH key pair').option('--name <name>', 'Key name/label').action(ssh.generate);
272
+ sshGroup.command('delete <key-id>').description('Delete an SSH key').option('-y, --yes', 'Skip confirmation').action(ssh.del);
273
+
274
+ program.parseAsync(process.argv).catch(err => {
275
+ console.error('Error:', err.message);
276
+ process.exit(1);
277
+ });
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const config = require('../lib/config');
4
+ const { validateApiKey } = require('../lib/api');
5
+ const ui = require('../lib/ui');
6
+
7
+ async function status() {
8
+ const apiKey = config.getApiKey();
9
+ const baseUrl = config.getBaseUrl();
10
+ if (!apiKey) { ui.warn('Not logged in. Run: joytree login'); return; }
11
+
12
+ const spin = ui.spinner('Loading account status');
13
+ try {
14
+ const data = await validateApiKey(apiKey, baseUrl);
15
+ spin.stop();
16
+
17
+ const projects = Array.isArray(data.projects) ? data.projects : [];
18
+ const email = config.load().email || '—';
19
+
20
+ ui.header('Account Status');
21
+ ui.divider();
22
+ ui.label('Email', email);
23
+ ui.label('Host', baseUrl);
24
+ ui.label('Projects', String(projects.length));
25
+ ui.label('API Key', apiKey.slice(0, 8) + '…' + apiKey.slice(-4));
26
+ console.log();
27
+
28
+ if (projects.length) {
29
+ ui.header('Projects');
30
+ ui.divider();
31
+ const base = baseUrl.replace(/^https?:\/\//, '');
32
+ projects.slice(0, 10).forEach(p => {
33
+ const sub = p.subdomain || p.name || p.id;
34
+ console.log(` ${ui.c.bold}${sub}${ui.c.reset} ${ui.c.dim}https://${sub}.${base}${ui.c.reset}`);
35
+ });
36
+ if (projects.length > 10) ui.info(`… and ${projects.length - 10} more. Run: joytree projects`);
37
+ console.log();
38
+ }
39
+ } catch (err) {
40
+ spin.stop();
41
+ ui.error(`Failed: ${err.message}`);
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ module.exports = { status };
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const config = require('../lib/config');
5
+ const { validateApiKey } = require('../lib/api');
6
+ const ui = require('../lib/ui');
7
+
8
+ async function prompt(question) {
9
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10
+ return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
11
+ }
12
+
13
+ async function login(opts) {
14
+ ui.logo();
15
+
16
+ let apiKey = opts.apiKey;
17
+ let baseUrl = config.getBaseUrl();
18
+
19
+ if (!apiKey) {
20
+ console.log(`${ui.c.dim}Find your API key at: ${baseUrl}/dashboard → Settings → API Key${ui.c.reset}\n`);
21
+ apiKey = await prompt(`${ui.c.bold}Paste your Joytree API key:${ui.c.reset} `);
22
+ }
23
+
24
+ if (!apiKey || !apiKey.startsWith('jtk_')) {
25
+ ui.error('Invalid API key format. Keys start with jtk_');
26
+ process.exit(1);
27
+ }
28
+
29
+ const spin = ui.spinner('Validating API key');
30
+ try {
31
+ const data = await validateApiKey(apiKey, baseUrl);
32
+ spin.stop();
33
+ const email = (data.projects && data.email) || (Array.isArray(data) ? '' : data.email) || '';
34
+ const projects = Array.isArray(data.projects) ? data.projects.length : (data.projectCount || 0);
35
+ config.setCredentials({ apiKey, baseUrl, email });
36
+ ui.success(`Logged in${email ? ' as ' + ui.c.bold + email + ui.c.reset : ''}`);
37
+ ui.info(`${projects} project(s) in your workspace`);
38
+ console.log(`\n${ui.c.dim}Run ${ui.c.cyan}joytree projects${ui.c.reset}${ui.c.dim} to see them.${ui.c.reset}\n`);
39
+ } catch (err) {
40
+ spin.stop();
41
+ ui.error(`Authentication failed: ${err.message}`);
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ function logout() {
47
+ config.clearCredentials();
48
+ ui.success('Logged out. Credentials removed.');
49
+ }
50
+
51
+ function whoami() {
52
+ const creds = config.load();
53
+ if (!creds.apiKey) {
54
+ ui.warn('Not logged in. Run: joytree login');
55
+ return;
56
+ }
57
+ ui.header('Current Session');
58
+ if (creds.email) ui.label('Email', creds.email);
59
+ ui.label('API Key', creds.apiKey.slice(0, 8) + '…' + creds.apiKey.slice(-4));
60
+ ui.label('Host', creds.baseUrl || 'https://joytree.app');
61
+ ui.label('Config', config.CONFIG_FILE);
62
+ console.log();
63
+ }
64
+
65
+ module.exports = { login, logout, whoami };
package/commands/db.js ADDED
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const { api } = require('../lib/api');
5
+ const ui = require('../lib/ui');
6
+
7
+ async function prompt(q) {
8
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9
+ return new Promise(r => rl.question(q, a => { rl.close(); r(a.trim()); }));
10
+ }
11
+
12
+ async function list() {
13
+ const spin = ui.spinner('Fetching databases');
14
+ try {
15
+ const data = await api.get('/api/databases');
16
+ spin.stop();
17
+ const items = Array.isArray(data) ? data : (data.databases || []);
18
+ if (!items.length) { ui.info('No databases yet. Create one: joytree db create'); return; }
19
+ ui.header(`Databases (${items.length})`);
20
+ ui.divider();
21
+ items.forEach(d => {
22
+ console.log(` ${ui.statusBadge(d.status)} ${ui.c.bold}${d.name}${ui.c.reset} ${ui.c.dim}[${d.type || 'db'}] ${d.id}${ui.c.reset}`);
23
+ if (d.host) console.log(` ${ui.c.dim}host: ${d.host}:${d.port || '—'}${ui.c.reset}`);
24
+ });
25
+ console.log();
26
+ } catch (err) {
27
+ spin.stop();
28
+ ui.error(`Failed: ${err.message}`);
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ async function create(opts) {
34
+ let { type, name } = opts;
35
+ if (!name) {
36
+ name = await prompt(`${ui.c.bold}Database name:${ui.c.reset} `);
37
+ if (!name) { ui.error('Name is required.'); process.exit(1); }
38
+ }
39
+ const spin = ui.spinner(`Creating ${type} database "${name}"`);
40
+ try {
41
+ const data = await api.post('/api/databases', { type: type || 'postgres', name });
42
+ spin.stop(`Database ${ui.c.bold}${name}${ui.c.reset} created!`);
43
+ if (data.id) ui.label('ID', data.id);
44
+ if (data.host) ui.label('Host', `${data.host}:${data.port || '—'}`);
45
+ if (data.connectionString) ui.label('DSN', data.connectionString);
46
+ console.log();
47
+ } catch (err) {
48
+ spin.stop();
49
+ ui.error(`Failed: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ async function dbAction(dbId, action) {
55
+ const spin = ui.spinner(`${action} database ${dbId}`);
56
+ try {
57
+ await api.post(`/api/databases/${encodeURIComponent(dbId)}/${action}`, {});
58
+ spin.stop(`Database ${ui.c.bold}${dbId}${ui.c.reset} ${action}ped.`);
59
+ } catch (err) {
60
+ spin.stop();
61
+ ui.error(`Failed: ${err.message}`);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ const start = dbId => dbAction(dbId, 'start');
67
+ const stop = dbId => dbAction(dbId, 'stop');
68
+ const restart = dbId => dbAction(dbId, 'restart');
69
+
70
+ async function fetchLogs(dbId) {
71
+ const spin = ui.spinner(`Fetching logs for ${dbId}`);
72
+ try {
73
+ const data = await api.get(`/api/databases/${encodeURIComponent(dbId)}/logs`);
74
+ spin.stop();
75
+ const lines = Array.isArray(data) ? data : (data.logs || data.lines || []);
76
+ ui.header(`DB Logs — ${dbId}`);
77
+ ui.divider();
78
+ lines.forEach(l => {
79
+ const msg = typeof l === 'string' ? l : (l.message || l.text || JSON.stringify(l));
80
+ console.log(` ${ui.c.dim}${msg}${ui.c.reset}`);
81
+ });
82
+ console.log();
83
+ } catch (err) {
84
+ spin.stop();
85
+ ui.error(`Failed: ${err.message}`);
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ async function del(dbId, opts) {
91
+ if (!opts.yes) {
92
+ const ans = await prompt(`${ui.c.yellow}Delete database "${dbId}"? This is irreversible. Type yes to confirm: ${ui.c.reset}`);
93
+ if (ans.toLowerCase() !== 'yes') { ui.info('Cancelled.'); return; }
94
+ }
95
+ const spin = ui.spinner(`Deleting ${dbId}`);
96
+ try {
97
+ await api.post(`/api/databases/${encodeURIComponent(dbId)}/delete`, {});
98
+ spin.stop(`Database ${ui.c.bold}${dbId}${ui.c.reset} deleted.`);
99
+ } catch (err) {
100
+ spin.stop();
101
+ ui.error(`Failed: ${err.message}`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ module.exports = { list, create, start, stop, restart, fetchLogs, del };