@daileyos/cli 0.1.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/dist/api.d.ts +12 -0
- package/dist/api.js +98 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +40 -0
- package/dist/auth.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +106 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +58 -0
- package/dist/commands/billing.js.map +1 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.js +191 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +72 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/domains.d.ts +2 -0
- package/dist/commands/domains.js +48 -0
- package/dist/commands/domains.js.map +1 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +54 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +160 -0
- package/dist/commands/jobs.js.map +1 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +19 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/platform.d.ts +2 -0
- package/dist/commands/platform.js +57 -0
- package/dist/commands/platform.js.map +1 -0
- package/dist/commands/projects.d.ts +2 -0
- package/dist/commands/projects.js +91 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/scale.d.ts +2 -0
- package/dist/commands/scale.js +39 -0
- package/dist/commands/scale.js.map +1 -0
- package/dist/commands/storage.d.ts +2 -0
- package/dist/commands/storage.js +128 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/commands/usage.d.ts +2 -0
- package/dist/commands/usage.js +25 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.js +72 -0
- package/dist/util.js.map +1 -0
- package/package.json +27 -0
- package/src/api.ts +124 -0
- package/src/auth.ts +53 -0
- package/src/commands/auth.ts +117 -0
- package/src/commands/billing.ts +75 -0
- package/src/commands/db.ts +237 -0
- package/src/commands/deploy.ts +91 -0
- package/src/commands/domains.ts +62 -0
- package/src/commands/env.ts +69 -0
- package/src/commands/jobs.ts +287 -0
- package/src/commands/open.ts +22 -0
- package/src/commands/platform.ts +88 -0
- package/src/commands/projects.ts +115 -0
- package/src/commands/scale.ts +48 -0
- package/src/commands/storage.ts +214 -0
- package/src/commands/usage.ts +30 -0
- package/src/index.ts +39 -0
- package/src/util.ts +92 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { formatTable, handleJson, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
export function registerBillingCommands(program: Command): void {
|
|
8
|
+
const billing = program
|
|
9
|
+
.command('billing')
|
|
10
|
+
.description('Show current plan and usage')
|
|
11
|
+
.option('--json', 'Output as JSON')
|
|
12
|
+
.action(
|
|
13
|
+
withErrorHandler(async (opts: { json?: boolean }) => {
|
|
14
|
+
const spinner = ora('Fetching billing info...').start();
|
|
15
|
+
const data = await api<Record<string, unknown>>('/billing');
|
|
16
|
+
spinner.stop();
|
|
17
|
+
|
|
18
|
+
handleJson(opts.json, data);
|
|
19
|
+
|
|
20
|
+
console.log(chalk.bold('Billing Overview:'));
|
|
21
|
+
for (const [key, value] of Object.entries(data)) {
|
|
22
|
+
if (typeof value !== 'object') {
|
|
23
|
+
console.log(` ${chalk.blue(key + ':')} ${value}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
billing
|
|
30
|
+
.command('invoices')
|
|
31
|
+
.description('List invoice history')
|
|
32
|
+
.option('--json', 'Output as JSON')
|
|
33
|
+
.action(
|
|
34
|
+
withErrorHandler(async (opts: { json?: boolean }) => {
|
|
35
|
+
const spinner = ora('Fetching invoices...').start();
|
|
36
|
+
const data = await api<{ invoices: Record<string, string>[] }>('/billing/invoices');
|
|
37
|
+
spinner.stop();
|
|
38
|
+
|
|
39
|
+
handleJson(opts.json, data.invoices);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.bold('Invoices:'));
|
|
42
|
+
console.log(
|
|
43
|
+
formatTable(data.invoices ?? [], [
|
|
44
|
+
{ key: 'id', label: 'ID' },
|
|
45
|
+
{ key: 'date', label: 'Date' },
|
|
46
|
+
{ key: 'amount', label: 'Amount' },
|
|
47
|
+
{ key: 'status', label: 'Status' },
|
|
48
|
+
]),
|
|
49
|
+
);
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
billing
|
|
54
|
+
.command('estimate')
|
|
55
|
+
.description('Estimate cost for scaling')
|
|
56
|
+
.option('--action <action>', 'Action to estimate', 'scale')
|
|
57
|
+
.option('--replicas <n>', 'Number of replicas', '3')
|
|
58
|
+
.option('--json', 'Output as JSON')
|
|
59
|
+
.action(
|
|
60
|
+
withErrorHandler(async (opts: { action: string; replicas: string; json?: boolean }) => {
|
|
61
|
+
const spinner = ora('Calculating estimate...').start();
|
|
62
|
+
const data = await api<Record<string, unknown>>('/billing/estimate', {
|
|
63
|
+
params: { action: opts.action, replicas: opts.replicas },
|
|
64
|
+
});
|
|
65
|
+
spinner.stop();
|
|
66
|
+
|
|
67
|
+
handleJson(opts.json, data);
|
|
68
|
+
|
|
69
|
+
console.log(chalk.bold('Cost Estimate:'));
|
|
70
|
+
for (const [key, value] of Object.entries(data)) {
|
|
71
|
+
console.log(` ${chalk.blue(key + ':')} ${value}`);
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { resolveProject, handleJson, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
function printQueryRows(data: any): void {
|
|
8
|
+
console.log(chalk.gray(`${data.row_count} row(s) in ${data.duration_ms}ms`));
|
|
9
|
+
if (data.read_only) {
|
|
10
|
+
const note = data.auto_limited ? 'read-only mode, limit applied automatically' : 'read-only mode';
|
|
11
|
+
console.log(chalk.gray(`${note}\n`));
|
|
12
|
+
} else {
|
|
13
|
+
console.log();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!data.rows || data.rows.length === 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const keys = Object.keys(data.rows[0]);
|
|
21
|
+
const widths = keys.map((k) => Math.max(k.length, ...data.rows.map((r: any) => String(r[k] ?? 'NULL').length)));
|
|
22
|
+
|
|
23
|
+
console.log(keys.map((k, i) => chalk.bold(k.padEnd(widths[i]))).join(' '));
|
|
24
|
+
console.log(keys.map((_k, i) => '─'.repeat(widths[i])).join(' '));
|
|
25
|
+
|
|
26
|
+
for (const row of data.rows.slice(0, 50)) {
|
|
27
|
+
console.log(keys.map((k, i) => String(row[k] ?? chalk.gray('NULL')).padEnd(widths[i])).join(' '));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (data.rows.length > 50) {
|
|
31
|
+
console.log(chalk.gray(`\n... and ${data.rows.length - 50} more rows`));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function registerDbCommands(program: Command): void {
|
|
36
|
+
const db = program.command('db').description('Database management');
|
|
37
|
+
|
|
38
|
+
db.command('info <idOrName>')
|
|
39
|
+
.description('Show database connection info')
|
|
40
|
+
.option('--json', 'Output as JSON')
|
|
41
|
+
.action(
|
|
42
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
43
|
+
const project = await resolveProject(String(idOrName));
|
|
44
|
+
const spinner = ora('Fetching database info...').start();
|
|
45
|
+
const data = await api<Record<string, unknown>>(`/projects/${project.id}/database`);
|
|
46
|
+
spinner.stop();
|
|
47
|
+
|
|
48
|
+
handleJson(opts.json, data);
|
|
49
|
+
|
|
50
|
+
console.log(chalk.bold(`Database for ${project.name}:`));
|
|
51
|
+
const fields = ['host', 'port', 'database', 'username', 'status', 'size'];
|
|
52
|
+
for (const f of fields) {
|
|
53
|
+
if (data[f] !== undefined) {
|
|
54
|
+
console.log(` ${chalk.blue(f + ':')} ${data[f]}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
db.command('schema <idOrName>')
|
|
61
|
+
.description('Show database schema — tables, columns, indexes')
|
|
62
|
+
.option('--json', 'Output as JSON')
|
|
63
|
+
.action(
|
|
64
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
65
|
+
const project = await resolveProject(String(idOrName));
|
|
66
|
+
const spinner = ora('Fetching schema...').start();
|
|
67
|
+
const data = await api<any>(`/projects/${project.id}/database/schema`);
|
|
68
|
+
spinner.stop();
|
|
69
|
+
|
|
70
|
+
handleJson(opts.json, data);
|
|
71
|
+
|
|
72
|
+
console.log(chalk.bold(`Schema: ${data.database}`));
|
|
73
|
+
console.log(chalk.gray(`${data.tables?.length || 0} tables, ${data.total_rows || 0} rows, ${data.total_size_kb || 0} KB\n`));
|
|
74
|
+
|
|
75
|
+
for (const table of data.tables || []) {
|
|
76
|
+
console.log(chalk.bold.blue(` ${table.name}`) + chalk.gray(` (${table.rows} rows, ${table.size_kb} KB)`));
|
|
77
|
+
for (const col of table.columns || []) {
|
|
78
|
+
const keyBadge = col.key === 'PRI' ? chalk.yellow(' PK') : col.key === 'UNI' ? chalk.cyan(' UQ') : col.key === 'MUL' ? chalk.gray(' IDX') : '';
|
|
79
|
+
const nullable = col.nullable ? chalk.gray(' NULL') : '';
|
|
80
|
+
const extra = col.extra ? chalk.gray(` ${col.extra}`) : '';
|
|
81
|
+
console.log(` ${chalk.white(col.name.padEnd(25))} ${chalk.gray(col.type)}${keyBadge}${nullable}${extra}`);
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
db.command('query <idOrName> <sql>')
|
|
89
|
+
.description('Run a read-only SQL query against the project database')
|
|
90
|
+
.option('--json', 'Output as JSON')
|
|
91
|
+
.action(
|
|
92
|
+
withErrorHandler(async (idOrName: unknown, sql: unknown, opts: { json?: boolean }) => {
|
|
93
|
+
const project = await resolveProject(String(idOrName));
|
|
94
|
+
const spinner = ora('Executing query...').start();
|
|
95
|
+
const data = await api<any>(`/projects/${project.id}/database/query`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: { sql: String(sql) },
|
|
98
|
+
});
|
|
99
|
+
spinner.stop();
|
|
100
|
+
|
|
101
|
+
if (data.error) {
|
|
102
|
+
console.error(chalk.red(`Error: ${data.error}`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
handleJson(opts.json, data);
|
|
107
|
+
printQueryRows(data);
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
db.command('recall <idOrName> <sql>')
|
|
112
|
+
.description('Recall records with an explicitly read-only SQL query')
|
|
113
|
+
.option('--limit <n>', 'Auto-limit SELECT/WITH queries without LIMIT (default: 100)')
|
|
114
|
+
.option('--json', 'Output as JSON')
|
|
115
|
+
.action(
|
|
116
|
+
withErrorHandler(async (idOrName: unknown, sql: unknown, opts: { json?: boolean; limit?: string }) => {
|
|
117
|
+
const project = await resolveProject(String(idOrName));
|
|
118
|
+
const spinner = ora('Recalling records...').start();
|
|
119
|
+
const body: Record<string, unknown> = { sql: String(sql) };
|
|
120
|
+
if (opts.limit) body.limit = Number(opts.limit);
|
|
121
|
+
const data = await api<any>(`/projects/${project.id}/database/recall`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body,
|
|
124
|
+
});
|
|
125
|
+
spinner.stop();
|
|
126
|
+
|
|
127
|
+
if (data.error) {
|
|
128
|
+
console.error(chalk.red(`Error: ${data.error}`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
handleJson(opts.json, data);
|
|
133
|
+
printQueryRows(data);
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
db.command('migrations <idOrName>')
|
|
138
|
+
.description('Show migration status for a project')
|
|
139
|
+
.option('--json', 'Output as JSON')
|
|
140
|
+
.action(
|
|
141
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
142
|
+
const project = await resolveProject(String(idOrName));
|
|
143
|
+
const spinner = ora('Checking migrations...').start();
|
|
144
|
+
const data = await api<any>(`/projects/${project.id}/database/migrations`);
|
|
145
|
+
spinner.stop();
|
|
146
|
+
|
|
147
|
+
handleJson(opts.json, data);
|
|
148
|
+
|
|
149
|
+
if (!data.has_migration_table) {
|
|
150
|
+
console.log(chalk.yellow('No _migrations table found.'));
|
|
151
|
+
console.log(chalk.gray('This project may not use the auto-migration pattern.'));
|
|
152
|
+
console.log(chalk.gray('Add a migrations/ directory with SQL files to enable auto-migrations.'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(chalk.bold(`Migrations for ${project.name}:`));
|
|
157
|
+
console.log(chalk.gray(`${data.total_applied} applied\n`));
|
|
158
|
+
|
|
159
|
+
for (const m of data.applied || []) {
|
|
160
|
+
const date = m.applied_at ? new Date(m.applied_at).toLocaleString() : 'unknown';
|
|
161
|
+
console.log(` ${chalk.green('✓')} ${m.name} ${chalk.gray(date)}`);
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
db.command('validate <idOrName> <sqlFile>')
|
|
167
|
+
.description('Validate a migration SQL file against the current schema')
|
|
168
|
+
.option('--json', 'Output as JSON')
|
|
169
|
+
.action(
|
|
170
|
+
withErrorHandler(async (idOrName: unknown, sqlFile: unknown, opts: { json?: boolean }) => {
|
|
171
|
+
const fs = await import('fs');
|
|
172
|
+
const filePath = String(sqlFile);
|
|
173
|
+
|
|
174
|
+
if (!fs.existsSync(filePath)) {
|
|
175
|
+
console.error(chalk.red(`File not found: ${filePath}`));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const sql = fs.readFileSync(filePath, 'utf8');
|
|
180
|
+
const project = await resolveProject(String(idOrName));
|
|
181
|
+
const spinner = ora('Validating migration...').start();
|
|
182
|
+
const data = await api<any>(`/projects/${project.id}/database/validate`, {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
body: { sql },
|
|
185
|
+
});
|
|
186
|
+
spinner.stop();
|
|
187
|
+
|
|
188
|
+
handleJson(opts.json, data);
|
|
189
|
+
|
|
190
|
+
console.log(chalk.bold(data.summary));
|
|
191
|
+
console.log();
|
|
192
|
+
|
|
193
|
+
for (const stmt of data.statements || []) {
|
|
194
|
+
const icon = !stmt.valid ? chalk.red('✗') : stmt.warning ? chalk.yellow('⚠') : chalk.green('✓');
|
|
195
|
+
console.log(` ${icon} ${chalk.gray(stmt.sql)}`);
|
|
196
|
+
if (stmt.error) console.log(` ${chalk.red(stmt.error)}`);
|
|
197
|
+
if (stmt.warning) console.log(` ${chalk.yellow(stmt.warning)}`);
|
|
198
|
+
if (stmt.info) console.log(` ${chalk.gray(stmt.info)}`);
|
|
199
|
+
}
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
db.command('connect <idOrName>')
|
|
204
|
+
.description('Show connection details for external database tools')
|
|
205
|
+
.action(
|
|
206
|
+
withErrorHandler(async (idOrName: unknown) => {
|
|
207
|
+
const project = await resolveProject(String(idOrName));
|
|
208
|
+
const spinner = ora('Fetching connection info...').start();
|
|
209
|
+
const data = await api<any>(`/projects/${project.id}/database`);
|
|
210
|
+
spinner.stop();
|
|
211
|
+
|
|
212
|
+
if (!data.database) {
|
|
213
|
+
console.log(chalk.yellow('This project does not have a database.'));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(chalk.bold('Database Connection Details'));
|
|
218
|
+
console.log(chalk.gray('Use these with Sequel Ace, TablePlus, DBeaver, or any MySQL client.\n'));
|
|
219
|
+
|
|
220
|
+
console.log(` ${chalk.blue('Host:')} ${data.host || 'auto-injected'}`);
|
|
221
|
+
console.log(` ${chalk.blue('Port:')} ${data.port || 3306}`);
|
|
222
|
+
console.log(` ${chalk.blue('Database:')} ${data.database}`);
|
|
223
|
+
console.log(` ${chalk.blue('User:')} ${data.username || data.user || 'see env vars'}`);
|
|
224
|
+
console.log(` ${chalk.blue('Password:')} ${chalk.gray('(available in your app as DB_PASSWORD env var)')}`);
|
|
225
|
+
|
|
226
|
+
console.log(chalk.gray('\nNote: Direct database access requires network connectivity'));
|
|
227
|
+
console.log(chalk.gray('to the Dailey infrastructure. Use the web console in the'));
|
|
228
|
+
console.log(chalk.gray('dashboard for browser-based access, or `dailey db query`'));
|
|
229
|
+
console.log(chalk.gray('for quick queries from the CLI.'));
|
|
230
|
+
|
|
231
|
+
console.log(chalk.bold('\nQuick queries:'));
|
|
232
|
+
console.log(chalk.gray(` dailey db query ${String(idOrName)} "SELECT * FROM users LIMIT 10"`));
|
|
233
|
+
console.log(chalk.gray(` dailey db schema ${String(idOrName)}`));
|
|
234
|
+
console.log(chalk.gray(` dailey db migrations ${String(idOrName)}`));
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { resolveProject, formatTable, handleJson, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
export function registerDeployCommands(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command('deploy <idOrName>')
|
|
10
|
+
.description('Trigger a deploy (shows push instructions)')
|
|
11
|
+
.action(
|
|
12
|
+
withErrorHandler(async (idOrName: unknown) => {
|
|
13
|
+
const project = await resolveProject(String(idOrName));
|
|
14
|
+
console.log(chalk.bold(`Deploy "${project.name}"`));
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(chalk.gray('Deploys are triggered automatically when you push to the configured branch.'));
|
|
17
|
+
console.log(chalk.gray('Push your code to trigger a new deployment:'));
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(chalk.blue(` git push origin ${project.branch || 'main'}`));
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.gray(`View deploy history with: ${chalk.white(`dailey deploys ${project.name}`)}`));
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command('logs <idOrName>')
|
|
27
|
+
.description('View pod logs')
|
|
28
|
+
.option('--tail <lines>', 'Number of lines to tail', '100')
|
|
29
|
+
.option('--json', 'Output as JSON')
|
|
30
|
+
.action(
|
|
31
|
+
withErrorHandler(async (idOrName: unknown, opts: { tail: string; json?: boolean }) => {
|
|
32
|
+
const project = await resolveProject(String(idOrName));
|
|
33
|
+
const spinner = ora('Fetching logs...').start();
|
|
34
|
+
const data = await api<{ logs: string[] }>(`/projects/${project.id}/logs`, {
|
|
35
|
+
params: { tail: opts.tail },
|
|
36
|
+
});
|
|
37
|
+
spinner.stop();
|
|
38
|
+
|
|
39
|
+
handleJson(opts.json, data.logs);
|
|
40
|
+
|
|
41
|
+
if (!data.logs || data.logs.length === 0) {
|
|
42
|
+
console.log(chalk.gray('No logs available.'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const line of data.logs) {
|
|
47
|
+
console.log(line);
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
program
|
|
53
|
+
.command('deploys <idOrName>')
|
|
54
|
+
.description('List deploy history')
|
|
55
|
+
.option('--json', 'Output as JSON')
|
|
56
|
+
.action(
|
|
57
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
58
|
+
const project = await resolveProject(String(idOrName));
|
|
59
|
+
const spinner = ora('Fetching deploys...').start();
|
|
60
|
+
const data = await api<{ builds: Record<string, string>[] }>(`/projects/${project.id}/deploys`);
|
|
61
|
+
spinner.stop();
|
|
62
|
+
|
|
63
|
+
handleJson(opts.json, data.builds);
|
|
64
|
+
|
|
65
|
+
console.log(chalk.bold(`Deploys for ${project.name}:`));
|
|
66
|
+
console.log(
|
|
67
|
+
formatTable(data.builds, [
|
|
68
|
+
{ key: 'id', label: 'Build ID' },
|
|
69
|
+
{ key: 'status', label: 'Status' },
|
|
70
|
+
{ key: 'commit', label: 'Commit' },
|
|
71
|
+
{ key: 'created_at', label: 'Date' },
|
|
72
|
+
]),
|
|
73
|
+
);
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
program
|
|
78
|
+
.command('rollback <idOrName> <buildId>')
|
|
79
|
+
.description('Rollback to a previous build')
|
|
80
|
+
.action(
|
|
81
|
+
withErrorHandler(async (idOrName: unknown, buildId: unknown) => {
|
|
82
|
+
const project = await resolveProject(String(idOrName));
|
|
83
|
+
const spinner = ora('Rolling back...').start();
|
|
84
|
+
await api(`/projects/${project.id}/rollback`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: { build_id: String(buildId) },
|
|
87
|
+
});
|
|
88
|
+
spinner.succeed(chalk.green(`Rolled back "${project.name}" to build ${buildId}.`));
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { resolveProject, formatTable, handleJson, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
export function registerDomainCommands(program: Command): void {
|
|
8
|
+
const domains = program.command('domains').description('Manage custom domains');
|
|
9
|
+
|
|
10
|
+
domains
|
|
11
|
+
.command('list <idOrName>')
|
|
12
|
+
.description('List custom domains')
|
|
13
|
+
.option('--json', 'Output as JSON')
|
|
14
|
+
.action(
|
|
15
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
16
|
+
const project = await resolveProject(String(idOrName));
|
|
17
|
+
const spinner = ora('Fetching domains...').start();
|
|
18
|
+
const data = await api<{ domains: Record<string, string>[] }>(`/projects/${project.id}/domains`);
|
|
19
|
+
spinner.stop();
|
|
20
|
+
|
|
21
|
+
handleJson(opts.json, data.domains);
|
|
22
|
+
|
|
23
|
+
console.log(chalk.bold(`Domains for ${project.name}:`));
|
|
24
|
+
console.log(
|
|
25
|
+
formatTable(data.domains, [
|
|
26
|
+
{ key: 'domain', label: 'Domain' },
|
|
27
|
+
{ key: 'status', label: 'Status' },
|
|
28
|
+
]),
|
|
29
|
+
);
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
domains
|
|
34
|
+
.command('add <idOrName> <domain>')
|
|
35
|
+
.description('Add a custom domain')
|
|
36
|
+
.action(
|
|
37
|
+
withErrorHandler(async (idOrName: unknown, domain: unknown) => {
|
|
38
|
+
const project = await resolveProject(String(idOrName));
|
|
39
|
+
const spinner = ora(`Adding domain ${domain}...`).start();
|
|
40
|
+
await api(`/projects/${project.id}/domains`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: { domain: String(domain) },
|
|
43
|
+
});
|
|
44
|
+
spinner.succeed(chalk.green(`Added domain "${domain}" to "${project.name}".`));
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
domains
|
|
49
|
+
.command('remove <idOrName> <domain>')
|
|
50
|
+
.description('Remove a custom domain')
|
|
51
|
+
.action(
|
|
52
|
+
withErrorHandler(async (idOrName: unknown, domain: unknown) => {
|
|
53
|
+
const project = await resolveProject(String(idOrName));
|
|
54
|
+
const spinner = ora(`Removing domain ${domain}...`).start();
|
|
55
|
+
await api(`/projects/${project.id}/domains`, {
|
|
56
|
+
method: 'DELETE',
|
|
57
|
+
body: { domain: String(domain) },
|
|
58
|
+
});
|
|
59
|
+
spinner.succeed(chalk.green(`Removed domain "${domain}" from "${project.name}".`));
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { resolveProject, formatTable, handleJson, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
export function registerEnvCommands(program: Command): void {
|
|
8
|
+
const env = program.command('env').description('Manage environment variables');
|
|
9
|
+
|
|
10
|
+
env
|
|
11
|
+
.command('list <idOrName>')
|
|
12
|
+
.description('List environment variables')
|
|
13
|
+
.option('--json', 'Output as JSON')
|
|
14
|
+
.action(
|
|
15
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
16
|
+
const project = await resolveProject(String(idOrName));
|
|
17
|
+
const spinner = ora('Fetching env vars...').start();
|
|
18
|
+
const data = await api<{ env_vars: Record<string, string>[] }>(`/projects/${project.id}/env`);
|
|
19
|
+
spinner.stop();
|
|
20
|
+
|
|
21
|
+
handleJson(opts.json, data.env_vars);
|
|
22
|
+
|
|
23
|
+
console.log(chalk.bold(`Environment variables for ${project.name}:`));
|
|
24
|
+
console.log(
|
|
25
|
+
formatTable(data.env_vars, [
|
|
26
|
+
{ key: 'key', label: 'Key' },
|
|
27
|
+
{ key: 'updated_at', label: 'Updated' },
|
|
28
|
+
]),
|
|
29
|
+
);
|
|
30
|
+
console.log(chalk.gray('\nValues stay hidden here by design. Update them with `dailey env set`.'));
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
env
|
|
35
|
+
.command('set <idOrName> <keyValue>')
|
|
36
|
+
.description('Set an environment variable (KEY=VALUE)')
|
|
37
|
+
.action(
|
|
38
|
+
withErrorHandler(async (idOrName: unknown, keyValue: unknown) => {
|
|
39
|
+
const kv = String(keyValue);
|
|
40
|
+
const eqIdx = kv.indexOf('=');
|
|
41
|
+
if (eqIdx === -1) {
|
|
42
|
+
console.error(chalk.red('Format must be KEY=VALUE'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const key = kv.slice(0, eqIdx);
|
|
46
|
+
const value = kv.slice(eqIdx + 1);
|
|
47
|
+
|
|
48
|
+
const project = await resolveProject(String(idOrName));
|
|
49
|
+
const spinner = ora(`Setting ${key}...`).start();
|
|
50
|
+
await api(`/projects/${project.id}/env`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: { key, value },
|
|
53
|
+
});
|
|
54
|
+
spinner.succeed(chalk.green(`Set ${key} on "${project.name}".`));
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
env
|
|
59
|
+
.command('unset <idOrName> <key>')
|
|
60
|
+
.description('Remove an environment variable')
|
|
61
|
+
.action(
|
|
62
|
+
withErrorHandler(async (idOrName: unknown, key: unknown) => {
|
|
63
|
+
const project = await resolveProject(String(idOrName));
|
|
64
|
+
const spinner = ora(`Removing ${key}...`).start();
|
|
65
|
+
await api(`/projects/${project.id}/env/${String(key)}`, { method: 'DELETE' });
|
|
66
|
+
spinner.succeed(chalk.green(`Removed ${key} from "${project.name}".`));
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
}
|