@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,287 @@
|
|
|
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, resolveProject, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
interface ProjectRef {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface JobContract {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
trigger_count: number;
|
|
19
|
+
enabled_trigger_count: number;
|
|
20
|
+
created_at: string | null;
|
|
21
|
+
updated_at: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface JobExecution {
|
|
25
|
+
id: string;
|
|
26
|
+
contract_id: string | null;
|
|
27
|
+
contract_name: string | null;
|
|
28
|
+
contract_slug: string | null;
|
|
29
|
+
status: string;
|
|
30
|
+
environment: string | null;
|
|
31
|
+
pool_slug: string | null;
|
|
32
|
+
attempt: number;
|
|
33
|
+
max_attempts: number;
|
|
34
|
+
created_by_principal: string | null;
|
|
35
|
+
created_at: string | null;
|
|
36
|
+
started_at: string | null;
|
|
37
|
+
finished_at: string | null;
|
|
38
|
+
exit_code: number | null;
|
|
39
|
+
error_summary: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface JobListResponse {
|
|
43
|
+
project: ProjectRef;
|
|
44
|
+
available: boolean;
|
|
45
|
+
reason?: string;
|
|
46
|
+
app_id?: string;
|
|
47
|
+
contracts: JobContract[];
|
|
48
|
+
executions: JobExecution[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface JobDetailResponse {
|
|
52
|
+
project: ProjectRef;
|
|
53
|
+
app_id: string;
|
|
54
|
+
execution: {
|
|
55
|
+
id: string;
|
|
56
|
+
contract_id: string | null;
|
|
57
|
+
status: string;
|
|
58
|
+
environment: string | null;
|
|
59
|
+
pool_slug: string | null;
|
|
60
|
+
attempt: number;
|
|
61
|
+
max_attempts: number;
|
|
62
|
+
created_by_principal: string | null;
|
|
63
|
+
created_at: string | null;
|
|
64
|
+
started_at: string | null;
|
|
65
|
+
finished_at: string | null;
|
|
66
|
+
exit_code: number | null;
|
|
67
|
+
error_summary: string | null;
|
|
68
|
+
metering: unknown;
|
|
69
|
+
};
|
|
70
|
+
contract: {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
slug: string;
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
spec: unknown;
|
|
76
|
+
created_at: string | null;
|
|
77
|
+
updated_at: string | null;
|
|
78
|
+
} | null;
|
|
79
|
+
execution_spec: {
|
|
80
|
+
created_at: string | null;
|
|
81
|
+
updated_at: string | null;
|
|
82
|
+
execution: unknown;
|
|
83
|
+
} | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface JobLogsResponse {
|
|
87
|
+
project: ProjectRef;
|
|
88
|
+
execution_id: string;
|
|
89
|
+
logs: Array<{
|
|
90
|
+
id: string;
|
|
91
|
+
stream: string | null;
|
|
92
|
+
line: string | null;
|
|
93
|
+
ts: string | null;
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatTime(value: string | null | undefined): string {
|
|
98
|
+
if (!value) return '-';
|
|
99
|
+
const date = new Date(value);
|
|
100
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
101
|
+
return date.toLocaleString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function shortId(value: string | null | undefined): string {
|
|
105
|
+
if (!value) return '-';
|
|
106
|
+
return value.length > 12 ? value.slice(0, 12) : value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function statusColor(status: string): (value: string) => string {
|
|
110
|
+
switch (status) {
|
|
111
|
+
case 'success':
|
|
112
|
+
return chalk.green;
|
|
113
|
+
case 'running':
|
|
114
|
+
case 'queued':
|
|
115
|
+
return chalk.blue;
|
|
116
|
+
case 'failed':
|
|
117
|
+
case 'timed_out':
|
|
118
|
+
case 'canceled':
|
|
119
|
+
return chalk.red;
|
|
120
|
+
default:
|
|
121
|
+
return chalk.yellow;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function registerJobCommands(program: Command): void {
|
|
126
|
+
const jobs = program.command('jobs').description('Project-scoped Burst and Cron visibility');
|
|
127
|
+
|
|
128
|
+
jobs
|
|
129
|
+
.command('list <idOrName>')
|
|
130
|
+
.description('List job contracts and recent executions for a project')
|
|
131
|
+
.option('--status <status>', 'Filter executions by status')
|
|
132
|
+
.option('--limit <n>', 'Maximum number of executions to return', '25')
|
|
133
|
+
.option('--json', 'Output as JSON')
|
|
134
|
+
.action(
|
|
135
|
+
withErrorHandler(async (idOrName: unknown, opts: { status?: string; limit?: string; json?: boolean }) => {
|
|
136
|
+
const project = await resolveProject(String(idOrName));
|
|
137
|
+
const spinner = opts.json ? null : ora('Loading project jobs...').start();
|
|
138
|
+
const params: Record<string, string> = {
|
|
139
|
+
limit: String(opts.limit || '25'),
|
|
140
|
+
};
|
|
141
|
+
if (opts.status) {
|
|
142
|
+
params.status = opts.status;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const data = await api<JobListResponse>(`/projects/${project.id}/jobs`, { params });
|
|
146
|
+
spinner?.stop();
|
|
147
|
+
|
|
148
|
+
handleJson(opts.json, data);
|
|
149
|
+
|
|
150
|
+
console.log(chalk.bold(`Jobs: ${data.project.name}`));
|
|
151
|
+
console.log(chalk.gray(`${data.project.slug}${data.app_id ? ` • app ${data.app_id}` : ''}\n`));
|
|
152
|
+
|
|
153
|
+
if (!data.available) {
|
|
154
|
+
console.log(chalk.yellow(data.reason || 'Job history is not available for this project yet.'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(chalk.bold('Contracts'));
|
|
159
|
+
console.log(formatTable(
|
|
160
|
+
data.contracts.map((contract) => ({
|
|
161
|
+
slug: contract.slug,
|
|
162
|
+
enabled: contract.enabled ? 'yes' : 'no',
|
|
163
|
+
triggers: `${contract.enabled_trigger_count}/${contract.trigger_count}`,
|
|
164
|
+
updated: formatTime(contract.updated_at),
|
|
165
|
+
})),
|
|
166
|
+
[
|
|
167
|
+
{ key: 'slug', label: 'Contract' },
|
|
168
|
+
{ key: 'enabled', label: 'Enabled' },
|
|
169
|
+
{ key: 'triggers', label: 'Enabled/Total Triggers' },
|
|
170
|
+
{ key: 'updated', label: 'Updated' },
|
|
171
|
+
],
|
|
172
|
+
));
|
|
173
|
+
|
|
174
|
+
console.log(`\n${chalk.bold('Executions')}`);
|
|
175
|
+
console.log(formatTable(
|
|
176
|
+
data.executions.map((execution) => ({
|
|
177
|
+
id: shortId(execution.id),
|
|
178
|
+
contract: execution.contract_slug || execution.contract_name || '-',
|
|
179
|
+
status: execution.status,
|
|
180
|
+
pool: execution.pool_slug || '-',
|
|
181
|
+
attempt: `${execution.attempt}/${execution.max_attempts || execution.attempt || 1}`,
|
|
182
|
+
created: formatTime(execution.created_at),
|
|
183
|
+
})),
|
|
184
|
+
[
|
|
185
|
+
{ key: 'id', label: 'Execution' },
|
|
186
|
+
{ key: 'contract', label: 'Contract' },
|
|
187
|
+
{ key: 'status', label: 'Status' },
|
|
188
|
+
{ key: 'pool', label: 'Pool' },
|
|
189
|
+
{ key: 'attempt', label: 'Attempt' },
|
|
190
|
+
{ key: 'created', label: 'Created' },
|
|
191
|
+
],
|
|
192
|
+
));
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
jobs
|
|
197
|
+
.command('show <idOrName> <jobId>')
|
|
198
|
+
.description('Show detail for one execution')
|
|
199
|
+
.option('--json', 'Output as JSON')
|
|
200
|
+
.action(
|
|
201
|
+
withErrorHandler(async (idOrName: unknown, jobId: unknown, opts: { json?: boolean }) => {
|
|
202
|
+
const project = await resolveProject(String(idOrName));
|
|
203
|
+
const spinner = opts.json ? null : ora('Loading job detail...').start();
|
|
204
|
+
const data = await api<JobDetailResponse>(`/projects/${project.id}/jobs/${String(jobId)}`);
|
|
205
|
+
spinner?.stop();
|
|
206
|
+
|
|
207
|
+
handleJson(opts.json, data);
|
|
208
|
+
|
|
209
|
+
const statusPainter = statusColor(data.execution.status);
|
|
210
|
+
console.log(chalk.bold(`Job: ${data.project.name}`));
|
|
211
|
+
console.log(chalk.gray(`${data.project.slug} • ${data.execution.id}\n`));
|
|
212
|
+
console.log(` ${chalk.blue('Status:')} ${statusPainter(data.execution.status)}`);
|
|
213
|
+
console.log(` ${chalk.blue('Contract:')} ${data.contract?.slug || data.contract?.name || '-'}`);
|
|
214
|
+
console.log(` ${chalk.blue('Pool:')} ${data.execution.pool_slug || '-'}`);
|
|
215
|
+
console.log(` ${chalk.blue('Environment:')} ${data.execution.environment || '-'}`);
|
|
216
|
+
console.log(` ${chalk.blue('Attempt:')} ${data.execution.attempt}/${data.execution.max_attempts || data.execution.attempt || 1}`);
|
|
217
|
+
console.log(` ${chalk.blue('Created By:')} ${data.execution.created_by_principal || '-'}`);
|
|
218
|
+
console.log(` ${chalk.blue('Created:')} ${formatTime(data.execution.created_at)}`);
|
|
219
|
+
console.log(` ${chalk.blue('Started:')} ${formatTime(data.execution.started_at)}`);
|
|
220
|
+
console.log(` ${chalk.blue('Finished:')} ${formatTime(data.execution.finished_at)}`);
|
|
221
|
+
console.log(` ${chalk.blue('Exit Code:')} ${data.execution.exit_code === null ? '-' : data.execution.exit_code}`);
|
|
222
|
+
|
|
223
|
+
if (data.execution.error_summary) {
|
|
224
|
+
console.log(`\n${chalk.red('Error Summary')}`);
|
|
225
|
+
console.log(` ${data.execution.error_summary}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (data.execution.metering) {
|
|
229
|
+
console.log(`\n${chalk.bold('Metering')}`);
|
|
230
|
+
console.log(JSON.stringify(data.execution.metering, null, 2));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (data.contract?.spec) {
|
|
234
|
+
console.log(`\n${chalk.bold('Contract Spec')}`);
|
|
235
|
+
console.log(JSON.stringify(data.contract.spec, null, 2));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (data.execution_spec?.execution) {
|
|
239
|
+
console.log(`\n${chalk.bold('Execution Spec')}`);
|
|
240
|
+
console.log(JSON.stringify(data.execution_spec.execution, null, 2));
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
jobs
|
|
246
|
+
.command('logs <idOrName> <jobId>')
|
|
247
|
+
.description('Show logs for one execution')
|
|
248
|
+
.option('--limit <n>', 'Maximum number of log lines to return', '200')
|
|
249
|
+
.option('--since-ts <ts>', 'Only show logs after the given timestamp')
|
|
250
|
+
.option('--json', 'Output as JSON')
|
|
251
|
+
.action(
|
|
252
|
+
withErrorHandler(async (
|
|
253
|
+
idOrName: unknown,
|
|
254
|
+
jobId: unknown,
|
|
255
|
+
opts: { limit?: string; sinceTs?: string; json?: boolean },
|
|
256
|
+
) => {
|
|
257
|
+
const project = await resolveProject(String(idOrName));
|
|
258
|
+
const spinner = opts.json ? null : ora('Loading job logs...').start();
|
|
259
|
+
const params: Record<string, string> = {
|
|
260
|
+
limit: String(opts.limit || '200'),
|
|
261
|
+
};
|
|
262
|
+
if (opts.sinceTs) {
|
|
263
|
+
params.since_ts = opts.sinceTs;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const data = await api<JobLogsResponse>(`/projects/${project.id}/jobs/${String(jobId)}/logs`, { params });
|
|
267
|
+
spinner?.stop();
|
|
268
|
+
|
|
269
|
+
handleJson(opts.json, data);
|
|
270
|
+
|
|
271
|
+
console.log(chalk.bold(`Logs: ${data.project.name}`));
|
|
272
|
+
console.log(chalk.gray(`${data.project.slug} • ${data.execution_id}\n`));
|
|
273
|
+
|
|
274
|
+
if (data.logs.length === 0) {
|
|
275
|
+
console.log(chalk.gray('No logs returned.'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const log of data.logs) {
|
|
280
|
+
const ts = formatTime(log.ts);
|
|
281
|
+
const stream = log.stream || 'log';
|
|
282
|
+
const line = log.line || '';
|
|
283
|
+
console.log(`${chalk.gray(ts)} ${chalk.blue(`[${stream}]`)} ${line}`);
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { resolveProject, withErrorHandler } from '../util.js';
|
|
5
|
+
|
|
6
|
+
export function registerOpenCommand(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('open <idOrName>')
|
|
9
|
+
.description('Open project URL in browser')
|
|
10
|
+
.action(
|
|
11
|
+
withErrorHandler(async (idOrName: unknown) => {
|
|
12
|
+
const project = await resolveProject(String(idOrName));
|
|
13
|
+
const url = project.url as string | undefined;
|
|
14
|
+
if (!url) {
|
|
15
|
+
console.error(chalk.red(`No URL found for project "${project.name}".`));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
console.log(chalk.blue(`Opening ${url}...`));
|
|
19
|
+
await open(url);
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import { handleJson, resolveProject, withErrorHandler } from '../util.js';
|
|
6
|
+
|
|
7
|
+
interface PlatformCapability {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
status: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
interfaces?: string[];
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PlatformOverview {
|
|
17
|
+
project: {
|
|
18
|
+
name: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
status: string;
|
|
21
|
+
url: string;
|
|
22
|
+
};
|
|
23
|
+
safety: {
|
|
24
|
+
model: string;
|
|
25
|
+
guarantees: string[];
|
|
26
|
+
restrictions: string[];
|
|
27
|
+
};
|
|
28
|
+
capabilities: PlatformCapability[];
|
|
29
|
+
next_step?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function statusColor(status: string): (value: string) => string {
|
|
33
|
+
if (status === 'configured') return chalk.green;
|
|
34
|
+
if (status === 'available') return chalk.blue;
|
|
35
|
+
return chalk.yellow;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function registerPlatformCommands(program: Command): void {
|
|
39
|
+
const platform = program.command('platform').description('Project-scoped platform capabilities');
|
|
40
|
+
|
|
41
|
+
platform
|
|
42
|
+
.command('info <idOrName>')
|
|
43
|
+
.description('Show the safe platform capabilities available to a project')
|
|
44
|
+
.option('--json', 'Output as JSON')
|
|
45
|
+
.action(
|
|
46
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
47
|
+
const project = await resolveProject(String(idOrName));
|
|
48
|
+
const spinner = opts.json ? null : ora('Loading platform overview...').start();
|
|
49
|
+
const data = await api<PlatformOverview>(`/projects/${project.id}/platform`);
|
|
50
|
+
spinner?.stop();
|
|
51
|
+
|
|
52
|
+
handleJson(opts.json, data);
|
|
53
|
+
|
|
54
|
+
console.log(chalk.bold(`Platform: ${data.project.name}`));
|
|
55
|
+
console.log(chalk.gray(`${data.project.slug} • ${data.project.status} • ${data.project.url}\n`));
|
|
56
|
+
|
|
57
|
+
console.log(chalk.bold('Safety Model'));
|
|
58
|
+
console.log(` ${chalk.blue('Mode:')} ${data.safety.model}`);
|
|
59
|
+
for (const line of data.safety.guarantees || []) {
|
|
60
|
+
console.log(` ${chalk.green('✓')} ${line}`);
|
|
61
|
+
}
|
|
62
|
+
for (const line of data.safety.restrictions || []) {
|
|
63
|
+
console.log(` ${chalk.yellow('!')} ${line}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`\n${chalk.bold('Capabilities')}`);
|
|
67
|
+
for (const capability of data.capabilities || []) {
|
|
68
|
+
const color = statusColor(capability.status);
|
|
69
|
+
console.log(` ${color(capability.label)} ${chalk.gray(`[${capability.status}]`)}`);
|
|
70
|
+
console.log(` ${capability.summary}`);
|
|
71
|
+
if (capability.interfaces && capability.interfaces.length > 0) {
|
|
72
|
+
console.log(` Interfaces: ${capability.interfaces.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
const details = capability.details || {};
|
|
75
|
+
for (const [key, value] of Object.entries(details)) {
|
|
76
|
+
if (value === null || value === undefined || value === '') continue;
|
|
77
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
78
|
+
console.log(` ${key}: ${Array.isArray(value) ? value.join(', ') : String(value)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (data.next_step) {
|
|
83
|
+
console.log(`\n${chalk.bold('Next Step')}`);
|
|
84
|
+
console.log(` ${data.next_step}`);
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { resolveProject, formatTable, handleJson, withErrorHandler } from '../util.js';
|
|
7
|
+
|
|
8
|
+
function ask(question: string): Promise<string> {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function registerProjectCommands(program: Command): void {
|
|
19
|
+
const projects = program
|
|
20
|
+
.command('projects')
|
|
21
|
+
.description('List all projects')
|
|
22
|
+
.option('--json', 'Output as JSON')
|
|
23
|
+
.action(
|
|
24
|
+
withErrorHandler(async (opts: { json?: boolean }) => {
|
|
25
|
+
const spinner = ora('Fetching projects...').start();
|
|
26
|
+
const data = await api<{ projects: Record<string, string>[] }>('/projects');
|
|
27
|
+
spinner.stop();
|
|
28
|
+
|
|
29
|
+
handleJson(opts.json, data.projects);
|
|
30
|
+
|
|
31
|
+
if (data.projects.length === 0) {
|
|
32
|
+
console.log(chalk.gray('No projects yet. Create one with `dailey projects create <name>`.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(
|
|
37
|
+
formatTable(data.projects, [
|
|
38
|
+
{ key: 'id', label: 'ID' },
|
|
39
|
+
{ key: 'name', label: 'Name' },
|
|
40
|
+
{ key: 'status', label: 'Status' },
|
|
41
|
+
{ key: 'replicas', label: 'Replicas' },
|
|
42
|
+
]),
|
|
43
|
+
);
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
projects
|
|
48
|
+
.command('create <name>')
|
|
49
|
+
.description('Create a new project')
|
|
50
|
+
.option('--json', 'Output as JSON')
|
|
51
|
+
.action(
|
|
52
|
+
withErrorHandler(async (name: unknown, opts: { json?: boolean }) => {
|
|
53
|
+
const repoUrl = await ask('Repository URL: ');
|
|
54
|
+
const branch = (await ask('Branch (main): ')) || 'main';
|
|
55
|
+
const dbAnswer = await ask('Needs database? (y/N): ');
|
|
56
|
+
const needsDatabase = dbAnswer.toLowerCase() === 'y';
|
|
57
|
+
|
|
58
|
+
const spinner = ora('Creating project...').start();
|
|
59
|
+
const data = await api<{ project: Record<string, unknown> }>('/projects', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: {
|
|
62
|
+
name: String(name),
|
|
63
|
+
repo_url: repoUrl,
|
|
64
|
+
branch,
|
|
65
|
+
needs_database: needsDatabase,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
spinner.succeed(chalk.green(`Project "${name}" created.`));
|
|
69
|
+
|
|
70
|
+
handleJson(opts.json, data.project);
|
|
71
|
+
|
|
72
|
+
console.log(` ${chalk.blue('ID:')} ${data.project.id}`);
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
projects
|
|
77
|
+
.command('info <idOrName>')
|
|
78
|
+
.description('Show project detail')
|
|
79
|
+
.option('--json', 'Output as JSON')
|
|
80
|
+
.action(
|
|
81
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
82
|
+
const spinner = ora('Fetching project...').start();
|
|
83
|
+
const project = await resolveProject(String(idOrName));
|
|
84
|
+
spinner.stop();
|
|
85
|
+
|
|
86
|
+
handleJson(opts.json, project);
|
|
87
|
+
|
|
88
|
+
console.log(chalk.bold(`Project: ${project.name}`));
|
|
89
|
+
const fields = ['id', 'slug', 'status', 'repo_url', 'branch', 'replicas', 'url', 'created_at'];
|
|
90
|
+
for (const f of fields) {
|
|
91
|
+
if (project[f] !== undefined) {
|
|
92
|
+
console.log(` ${chalk.blue(f + ':')} ${project[f]}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
projects
|
|
99
|
+
.command('delete <idOrName>')
|
|
100
|
+
.description('Delete a project')
|
|
101
|
+
.action(
|
|
102
|
+
withErrorHandler(async (idOrName: unknown) => {
|
|
103
|
+
const project = await resolveProject(String(idOrName));
|
|
104
|
+
const confirm = await ask(`Delete project "${project.name}"? This cannot be undone. (yes/no): `);
|
|
105
|
+
if (confirm !== 'yes') {
|
|
106
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const spinner = ora('Deleting project...').start();
|
|
111
|
+
await api(`/projects/${project.id}`, { method: 'DELETE' });
|
|
112
|
+
spinner.succeed(chalk.green(`Project "${project.name}" deleted.`));
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
export function registerScaleCommands(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command('scale <idOrName> <replicas>')
|
|
10
|
+
.description('Scale replicas for a project')
|
|
11
|
+
.action(
|
|
12
|
+
withErrorHandler(async (idOrName: unknown, replicas: unknown) => {
|
|
13
|
+
const project = await resolveProject(String(idOrName));
|
|
14
|
+
const count = parseInt(String(replicas), 10);
|
|
15
|
+
if (isNaN(count) || count < 0) {
|
|
16
|
+
console.error(chalk.red('Replicas must be a non-negative integer.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const spinner = ora(`Scaling "${project.name}" to ${count} replicas...`).start();
|
|
21
|
+
await api(`/projects/${project.id}/scale`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: { replicas: count },
|
|
24
|
+
});
|
|
25
|
+
spinner.succeed(chalk.green(`Scaled "${project.name}" to ${count} replicas.`));
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('status <idOrName>')
|
|
31
|
+
.description('Show app status (pods, replicas, health)')
|
|
32
|
+
.option('--json', 'Output as JSON')
|
|
33
|
+
.action(
|
|
34
|
+
withErrorHandler(async (idOrName: unknown, opts: { json?: boolean }) => {
|
|
35
|
+
const project = await resolveProject(String(idOrName));
|
|
36
|
+
|
|
37
|
+
handleJson(opts.json, project);
|
|
38
|
+
|
|
39
|
+
console.log(chalk.bold(`Status: ${project.name}`));
|
|
40
|
+
console.log(` ${chalk.blue('Status:')} ${project.status ?? 'unknown'}`);
|
|
41
|
+
console.log(` ${chalk.blue('Replicas:')} ${project.replicas ?? 'N/A'}`);
|
|
42
|
+
console.log(` ${chalk.blue('URL:')} ${project.url ?? 'N/A'}`);
|
|
43
|
+
if (project.health) {
|
|
44
|
+
console.log(` ${chalk.blue('Health:')} ${project.health}`);
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
}
|