@eve-horizon/cli 0.2.0 → 0.2.5

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.
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleBuild = handleBuild;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ // ---------------------------------------------------------------------------
8
+ // Entry point
9
+ // ---------------------------------------------------------------------------
10
+ async function handleBuild(subcommand, positionals, flags, context) {
11
+ const json = Boolean(flags.json);
12
+ switch (subcommand) {
13
+ case 'create':
14
+ return handleCreate(flags, context, json);
15
+ case 'list':
16
+ return handleList(positionals, flags, context, json);
17
+ case 'show':
18
+ return handleShow(positionals, flags, context, json);
19
+ case 'run':
20
+ return handleRun(positionals, flags, context, json);
21
+ case 'runs':
22
+ return handleRuns(positionals, flags, context, json);
23
+ case 'logs':
24
+ return handleLogs(positionals, flags, context, json);
25
+ case 'artifacts':
26
+ return handleArtifacts(positionals, flags, context, json);
27
+ case 'diagnose':
28
+ return handleDiagnose(positionals, flags, context, json);
29
+ case 'cancel':
30
+ return handleCancel(positionals, flags, context, json);
31
+ default:
32
+ throw new Error('Usage: eve build <create|list|show|run|runs|logs|artifacts|diagnose|cancel>\n' +
33
+ ' create --project <id> --ref <sha> [--services <list>] - create a build spec\n' +
34
+ ' list [--project <id>] - list build specs\n' +
35
+ ' show <build_id> - show build spec details\n' +
36
+ ' run <build_id> - start a build run\n' +
37
+ ' runs <build_id> - list runs for a build\n' +
38
+ ' logs <build_id> [--run <id>] - show build logs\n' +
39
+ ' artifacts <build_id> - list build artifacts\n' +
40
+ ' diagnose <build_id> - show full build state\n' +
41
+ ' cancel <build_id> - cancel active build run');
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Subcommands
46
+ // ---------------------------------------------------------------------------
47
+ async function handleCreate(flags, context, json) {
48
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
49
+ const ref = (0, args_1.getStringFlag)(flags, ['ref']);
50
+ const manifestHash = (0, args_1.getStringFlag)(flags, ['manifest-hash', 'manifest']);
51
+ const services = (0, args_1.getStringFlag)(flags, ['services']);
52
+ if (!projectId || !ref || !manifestHash) {
53
+ throw new Error('Usage: eve build create --project <id> --ref <sha> --manifest-hash <hash> [--services <s1,s2>]');
54
+ }
55
+ const body = { git_sha: ref, manifest_hash: manifestHash };
56
+ if (services) {
57
+ body.services = services.split(',').map(s => s.trim());
58
+ }
59
+ const build = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds`, { method: 'POST', body });
60
+ if (json) {
61
+ (0, output_1.outputJson)(build, json);
62
+ return;
63
+ }
64
+ console.log(`Build created: ${build.id}`);
65
+ console.log(` Project: ${build.project_id}`);
66
+ console.log(` SHA: ${build.git_sha}`);
67
+ if (build.manifest_hash) {
68
+ console.log(` Manifest: ${build.manifest_hash.substring(0, 12)}...`);
69
+ }
70
+ console.log(` Created: ${build.created_at}`);
71
+ }
72
+ async function handleList(positionals, flags, context, json) {
73
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
74
+ if (!projectId) {
75
+ throw new Error('Usage: eve build list [--project <id>]');
76
+ }
77
+ const query = buildQuery({
78
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
79
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
80
+ });
81
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/builds${query}`);
82
+ if (json) {
83
+ (0, output_1.outputJson)(response, json);
84
+ return;
85
+ }
86
+ if (response.data.length === 0) {
87
+ console.log('No builds found.');
88
+ return;
89
+ }
90
+ formatBuildList(response.data);
91
+ }
92
+ async function handleShow(positionals, flags, context, json) {
93
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
94
+ if (!buildId) {
95
+ throw new Error('Usage: eve build show <build_id>');
96
+ }
97
+ const build = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
98
+ if (json) {
99
+ (0, output_1.outputJson)(build, json);
100
+ return;
101
+ }
102
+ formatBuildDetail(build);
103
+ }
104
+ async function handleRun(positionals, flags, context, json) {
105
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
106
+ if (!buildId) {
107
+ throw new Error('Usage: eve build run <build_id>');
108
+ }
109
+ const run = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs`, { method: 'POST' });
110
+ if (json) {
111
+ (0, output_1.outputJson)(run, json);
112
+ return;
113
+ }
114
+ console.log(`Build run started: ${run.id}`);
115
+ console.log(` Build: ${run.build_id}`);
116
+ console.log(` Status: ${run.status}`);
117
+ if (run.backend) {
118
+ console.log(` Backend: ${run.backend}`);
119
+ }
120
+ }
121
+ async function handleRuns(positionals, flags, context, json) {
122
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
123
+ if (!buildId) {
124
+ throw new Error('Usage: eve build runs <build_id>');
125
+ }
126
+ const query = buildQuery({
127
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
128
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
129
+ });
130
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs${query}`);
131
+ if (json) {
132
+ (0, output_1.outputJson)(response, json);
133
+ return;
134
+ }
135
+ if (response.data.length === 0) {
136
+ console.log('No runs found for this build.');
137
+ return;
138
+ }
139
+ formatRunList(response.data);
140
+ }
141
+ async function handleLogs(positionals, flags, context, json) {
142
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
143
+ const runId = (0, args_1.getStringFlag)(flags, ['run']);
144
+ if (!buildId) {
145
+ throw new Error('Usage: eve build logs <build_id> [--run <run_id>]');
146
+ }
147
+ const query = runId ? `?run_id=${encodeURIComponent(runId)}` : '';
148
+ const logs = await (0, client_1.requestJson)(context, `/builds/${buildId}/logs${query}`);
149
+ if (json) {
150
+ (0, output_1.outputJson)(logs, json);
151
+ return;
152
+ }
153
+ if (logs.logs.length === 0) {
154
+ console.log('No logs available.');
155
+ return;
156
+ }
157
+ for (const entry of logs.logs) {
158
+ const line = entry.line;
159
+ if (typeof line.message === 'string') {
160
+ console.log(line.message);
161
+ continue;
162
+ }
163
+ if (Array.isArray(line.lines)) {
164
+ for (const item of line.lines) {
165
+ if (typeof item === 'string') {
166
+ console.log(item);
167
+ }
168
+ }
169
+ continue;
170
+ }
171
+ console.log(JSON.stringify(line));
172
+ }
173
+ }
174
+ async function handleArtifacts(positionals, flags, context, json) {
175
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
176
+ if (!buildId) {
177
+ throw new Error('Usage: eve build artifacts <build_id>');
178
+ }
179
+ const response = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
180
+ if (json) {
181
+ (0, output_1.outputJson)(response, json);
182
+ return;
183
+ }
184
+ if (response.data.length === 0) {
185
+ console.log('No artifacts found for this build.');
186
+ return;
187
+ }
188
+ formatArtifactList(response.data);
189
+ }
190
+ async function handleCancel(positionals, flags, context, json) {
191
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
192
+ if (!buildId) {
193
+ throw new Error('Usage: eve build cancel <build_id>');
194
+ }
195
+ const result = await (0, client_1.requestJson)(context, `/builds/${buildId}/cancel`, { method: 'POST' });
196
+ if (json) {
197
+ (0, output_1.outputJson)(result, json);
198
+ return;
199
+ }
200
+ console.log(`Build cancelled: ${buildId}`);
201
+ }
202
+ async function handleDiagnose(positionals, flags, context, json) {
203
+ const buildId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['id']);
204
+ if (!buildId) {
205
+ throw new Error('Usage: eve build diagnose <build_id>');
206
+ }
207
+ const spec = await (0, client_1.requestJson)(context, `/builds/${buildId}`);
208
+ const runs = await (0, client_1.requestJson)(context, `/builds/${buildId}/runs?limit=20&offset=0`);
209
+ const artifacts = await (0, client_1.requestJson)(context, `/builds/${buildId}/artifacts`);
210
+ const latestRun = runs.data[0];
211
+ const logs = latestRun
212
+ ? await (0, client_1.requestJson)(context, `/builds/${buildId}/logs?run_id=${encodeURIComponent(latestRun.id)}`)
213
+ : { logs: [] };
214
+ const payload = { spec, runs: runs.data, artifacts: artifacts.data, logs: logs.logs };
215
+ if (json) {
216
+ (0, output_1.outputJson)(payload, json);
217
+ return;
218
+ }
219
+ console.log(`Build diagnose: ${spec.id}`);
220
+ formatBuildDetail(spec);
221
+ console.log('');
222
+ if (runs.data.length > 0) {
223
+ formatRunList(runs.data);
224
+ }
225
+ else {
226
+ console.log('No runs found.');
227
+ }
228
+ console.log('');
229
+ if (artifacts.data.length > 0) {
230
+ formatArtifactList(artifacts.data);
231
+ }
232
+ else {
233
+ console.log('No artifacts found.');
234
+ }
235
+ console.log('');
236
+ if (logs.logs.length > 0) {
237
+ console.log('Recent logs:');
238
+ for (const entry of logs.logs.slice(-50)) {
239
+ const line = entry.line;
240
+ if (typeof line.message === 'string') {
241
+ console.log(line.message);
242
+ continue;
243
+ }
244
+ if (Array.isArray(line.lines)) {
245
+ for (const item of line.lines) {
246
+ if (typeof item === 'string') {
247
+ console.log(item);
248
+ }
249
+ }
250
+ continue;
251
+ }
252
+ console.log(JSON.stringify(line));
253
+ }
254
+ }
255
+ else {
256
+ console.log('No logs found.');
257
+ }
258
+ }
259
+ // ---------------------------------------------------------------------------
260
+ // Formatters
261
+ // ---------------------------------------------------------------------------
262
+ function formatBuildList(builds) {
263
+ console.log('Build ID'.padEnd(30) + 'SHA'.padEnd(12) + 'Created By'.padEnd(20) + 'Created');
264
+ console.log('-'.repeat(90));
265
+ for (const build of builds) {
266
+ const id = build.id.padEnd(30);
267
+ const sha = build.git_sha.substring(0, 8).padEnd(12);
268
+ const creator = (build.created_by ?? '-').padEnd(20);
269
+ const created = new Date(build.created_at).toLocaleString();
270
+ console.log(`${id}${sha}${creator}${created}`);
271
+ }
272
+ console.log('');
273
+ console.log(`Total: ${builds.length} builds`);
274
+ }
275
+ function formatBuildDetail(build) {
276
+ console.log(`Build: ${build.id}`);
277
+ console.log(` Project: ${build.project_id}`);
278
+ console.log(` SHA: ${build.git_sha}`);
279
+ console.log(` Manifest Hash: ${build.manifest_hash}`);
280
+ console.log(` Created By: ${build.created_by ?? '-'}`);
281
+ console.log(` Created: ${build.created_at}`);
282
+ if (build.services_json) {
283
+ console.log(` Services: ${JSON.stringify(build.services_json)}`);
284
+ }
285
+ if (build.inputs_json) {
286
+ console.log(` Inputs: ${JSON.stringify(build.inputs_json)}`);
287
+ }
288
+ if (build.registry_json) {
289
+ console.log(` Registry: ${JSON.stringify(build.registry_json)}`);
290
+ }
291
+ if (build.cache_json) {
292
+ console.log(` Cache: ${JSON.stringify(build.cache_json)}`);
293
+ }
294
+ }
295
+ function formatRunList(runs) {
296
+ console.log('Run ID'.padEnd(30) + 'Status'.padEnd(15) + 'Backend'.padEnd(15) + 'Started');
297
+ console.log('-'.repeat(80));
298
+ for (const run of runs) {
299
+ const id = run.id.padEnd(30);
300
+ const status = run.status.padEnd(15);
301
+ const backend = (run.backend ?? '-').padEnd(15);
302
+ const started = run.started_at ? new Date(run.started_at).toLocaleString() : '-';
303
+ console.log(`${id}${status}${backend}${started}`);
304
+ if (run.error_message) {
305
+ console.log(` Error: ${run.error_message}`);
306
+ }
307
+ }
308
+ console.log('');
309
+ console.log(`Total: ${runs.length} runs`);
310
+ }
311
+ function formatArtifactList(artifacts) {
312
+ console.log('Service'.padEnd(25) + 'Image'.padEnd(50) + 'Size');
313
+ console.log('-'.repeat(90));
314
+ for (const artifact of artifacts) {
315
+ const service = artifact.service_name.padEnd(25);
316
+ const image = artifact.image_ref.padEnd(50);
317
+ const size = artifact.size_bytes ? formatBytes(artifact.size_bytes) : '-';
318
+ console.log(`${service}${image}${size}`);
319
+ if (artifact.digest) {
320
+ console.log(` Digest: ${artifact.digest}`);
321
+ }
322
+ if (artifact.platforms_json && artifact.platforms_json.length > 0) {
323
+ console.log(` Platforms: ${artifact.platforms_json.join(', ')}`);
324
+ }
325
+ }
326
+ console.log('');
327
+ console.log(`Total: ${artifacts.length} artifacts`);
328
+ }
329
+ function formatBytes(bytes) {
330
+ if (bytes < 1024)
331
+ return `${bytes} B`;
332
+ if (bytes < 1024 * 1024)
333
+ return `${(bytes / 1024).toFixed(1)} KB`;
334
+ if (bytes < 1024 * 1024 * 1024)
335
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
336
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
337
+ }
338
+ // ---------------------------------------------------------------------------
339
+ // Utilities
340
+ // ---------------------------------------------------------------------------
341
+ function buildQuery(params) {
342
+ const search = new URLSearchParams();
343
+ Object.entries(params).forEach(([key, value]) => {
344
+ if (value === undefined || value === '')
345
+ return;
346
+ search.set(key, value);
347
+ });
348
+ const query = search.toString();
349
+ return query ? `?${query}` : '';
350
+ }
@@ -55,6 +55,8 @@ async function handleEnv(subcommand, positionals, flags, context) {
55
55
  return handleDeploy(positionals, flags, context, json);
56
56
  case 'logs':
57
57
  return handleLogs(positionals, flags, context, json);
58
+ case 'diagnose':
59
+ return handleDiagnose(positionals, flags, context, json);
58
60
  case 'delete':
59
61
  return handleDelete(positionals, flags, context, json);
60
62
  default:
@@ -64,6 +66,7 @@ async function handleEnv(subcommand, positionals, flags, context) {
64
66
  ' create <name> --type=<type> [options] - create an environment\n' +
65
67
  ' deploy <env> --ref <sha> [--direct] [--inputs <json>] - deploy to an environment\n' +
66
68
  ' logs <project> <env> <service> [--since <seconds>] [--tail <n>] [--grep <text>] - get service logs\n' +
69
+ ' diagnose <project> <env> - diagnose deployment health and events\n' +
67
70
  ' delete <name> [--project=<id>] [--force] - delete an environment');
68
71
  }
69
72
  }
@@ -106,11 +109,12 @@ async function handleShow(positionals, flags, context, json) {
106
109
  throw new Error('Usage: eve env show <project> <name> [--project=<id>] [--name=<name>]');
107
110
  }
108
111
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}`);
112
+ const health = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/health`);
109
113
  if (json) {
110
- (0, output_1.outputJson)(response, json);
114
+ (0, output_1.outputJson)({ ...response, health }, json);
111
115
  }
112
116
  else {
113
- formatEnvironmentDetails(response);
117
+ formatEnvironmentDetails(response, health);
114
118
  }
115
119
  }
116
120
  /**
@@ -157,7 +161,7 @@ async function handleCreate(positionals, flags, context, json) {
157
161
  }
158
162
  }
159
163
  /**
160
- * eve env deploy [project] <name> --ref <sha> [--direct] [--inputs <json>]
164
+ * eve env deploy [project] <name> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>]
161
165
  * Deploy to an environment
162
166
  * If project is in profile, can use: eve env deploy <name> --ref <sha>
163
167
  */
@@ -190,7 +194,7 @@ async function handleDeploy(positionals, flags, context, json) {
190
194
  envName = flagName;
191
195
  }
192
196
  if (!projectId || !envName) {
193
- throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--project=<id>]');
197
+ throw new Error('Usage: eve env deploy <env> --ref <sha> [--direct] [--inputs <json>] [--image-tag <tag>] [--project=<id>]');
194
198
  }
195
199
  // --ref flag is now required
196
200
  const gitSha = (0, args_1.getStringFlag)(flags, ['ref']);
@@ -222,6 +226,8 @@ async function handleDeploy(positionals, flags, context, json) {
222
226
  'Example: --inputs \'{"release_id":"rel_xxx","smoke_test":false}\'');
223
227
  }
224
228
  }
229
+ // Parse --image-tag flag (optional)
230
+ const imageTag = (0, args_1.getStringFlag)(flags, ['image-tag', 'image_tag', 'imageTag']);
225
231
  // POST deployment
226
232
  const body = {
227
233
  git_sha: gitSha,
@@ -233,6 +239,9 @@ async function handleDeploy(positionals, flags, context, json) {
233
239
  if (inputs) {
234
240
  body.inputs = inputs;
235
241
  }
242
+ if (imageTag) {
243
+ body.image_tag = imageTag;
244
+ }
236
245
  const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/deploy`, {
237
246
  method: 'POST',
238
247
  body,
@@ -241,11 +250,46 @@ async function handleDeploy(positionals, flags, context, json) {
241
250
  (0, output_1.outputJson)(response, json);
242
251
  }
243
252
  else {
253
+ if (response.pipeline_run) {
254
+ console.log('');
255
+ console.log('Pipeline deployment queued.');
256
+ console.log(` Pipeline Run: ${response.pipeline_run.run.id}`);
257
+ console.log(` Pipeline: ${response.pipeline_run.run.pipeline_name}`);
258
+ console.log(` Status: ${response.pipeline_run.run.status}`);
259
+ console.log(` Environment: ${response.environment.name}`);
260
+ return;
261
+ }
244
262
  console.log('');
245
- console.log(`Deployment successful!`);
246
- console.log(` Release ID: ${response.release.id}`);
263
+ console.log(`Deployment submitted.`);
264
+ if (response.release) {
265
+ console.log(` Release ID: ${response.release.id}`);
266
+ }
247
267
  console.log(` Environment: ${response.environment.name}`);
248
268
  console.log(` Namespace: ${response.environment.namespace || '(none)'}`);
269
+ if (response.deployment_status?.k8s_status) {
270
+ const status = response.deployment_status.k8s_status;
271
+ const readiness = `${status.available_replicas}/${status.desired_replicas}`;
272
+ console.log(` Status: ${response.deployment_status.state} (${readiness} ready)`);
273
+ }
274
+ else if (response.deployment_status?.state) {
275
+ console.log(` Status: ${response.deployment_status.state}`);
276
+ }
277
+ const warnings = response.warnings ?? getDeploymentWarnings(response.deployment_status);
278
+ if (warnings.length > 0) {
279
+ console.log('');
280
+ console.log('Warnings:');
281
+ for (const warning of warnings) {
282
+ console.log(` - ${warning}`);
283
+ }
284
+ }
285
+ const watchFlag = (0, args_1.toBoolean)(flags.watch);
286
+ const shouldWatch = (watchFlag ?? true)
287
+ && response.deployment_status?.state !== 'ready';
288
+ if (shouldWatch) {
289
+ const timeoutRaw = (0, args_1.getStringFlag)(flags, ['timeout']);
290
+ const timeoutSeconds = timeoutRaw ? parseInt(timeoutRaw, 10) : 120;
291
+ await watchDeploymentStatus(context, projectId, envName, Number.isFinite(timeoutSeconds) ? timeoutSeconds : 120);
292
+ }
249
293
  }
250
294
  }
251
295
  /**
@@ -277,6 +321,26 @@ async function handleLogs(positionals, flags, context, json) {
277
321
  console.log(`[${entry.timestamp}] ${entry.line}`);
278
322
  }
279
323
  }
324
+ /**
325
+ * eve env diagnose <project> <env>
326
+ * Diagnose deployment health for an environment (k8s-only)
327
+ */
328
+ async function handleDiagnose(positionals, flags, context, json) {
329
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
330
+ const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['env', 'name']);
331
+ if (!projectId || !envName) {
332
+ throw new Error('Usage: eve env diagnose <project> <env> [--events <n>]');
333
+ }
334
+ const query = buildQuery({
335
+ events: (0, args_1.getStringFlag)(flags, ['events']),
336
+ });
337
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/diagnose${query}`);
338
+ if (json) {
339
+ (0, output_1.outputJson)(response, json);
340
+ return;
341
+ }
342
+ formatEnvDiagnose(response);
343
+ }
280
344
  /**
281
345
  * eve env delete <name> [--project=<id>] [--force]
282
346
  * Delete an environment
@@ -399,7 +463,7 @@ function formatEnvironmentsTable(environments) {
399
463
  /**
400
464
  * Format a single environment's details
401
465
  */
402
- function formatEnvironmentDetails(env) {
466
+ function formatEnvironmentDetails(env, health) {
403
467
  console.log(`Environment: ${env.name}`);
404
468
  console.log('');
405
469
  console.log(` ID: ${env.id}`);
@@ -408,6 +472,19 @@ function formatEnvironmentDetails(env) {
408
472
  console.log(` Namespace: ${env.namespace || '(none)'}`);
409
473
  console.log(` Database Ref: ${env.db_ref || '(none)'}`);
410
474
  console.log(` Current Release: ${env.current_release_id || '(none)'}`);
475
+ if (health) {
476
+ console.log('');
477
+ console.log(` Deployment Status: ${health.status}`);
478
+ if (health.deployment) {
479
+ console.log(` Deployment Ready: ${health.deployment.available_replicas}/${health.deployment.desired_replicas}`);
480
+ }
481
+ if (health.warnings && health.warnings.length > 0) {
482
+ console.log(' Warnings:');
483
+ for (const warning of health.warnings) {
484
+ console.log(` - ${warning}`);
485
+ }
486
+ }
487
+ }
411
488
  if (env.overrides && Object.keys(env.overrides).length > 0) {
412
489
  console.log('');
413
490
  console.log(' Overrides:');
@@ -419,6 +496,118 @@ function formatEnvironmentDetails(env) {
419
496
  console.log(` Created: ${formatDate(env.created_at)}`);
420
497
  console.log(` Updated: ${formatDate(env.updated_at)}`);
421
498
  }
499
+ function formatEnvDiagnose(report) {
500
+ console.log(`Environment Diagnose: ${report.env_name}`);
501
+ console.log('');
502
+ console.log(` Namespace: ${report.namespace || '(none)'}`);
503
+ console.log(` Status: ${report.status}`);
504
+ console.log(` Ready: ${report.ready ? 'yes' : 'no'}`);
505
+ console.log(` K8s: ${report.k8s_available ? 'available' : 'unavailable'}`);
506
+ if (report.warnings && report.warnings.length > 0) {
507
+ console.log('');
508
+ console.log('Warnings:');
509
+ for (const warning of report.warnings) {
510
+ console.log(` - ${warning}`);
511
+ }
512
+ }
513
+ if (report.deployments.length > 0) {
514
+ console.log('');
515
+ console.log('Deployments:');
516
+ const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
517
+ const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
518
+ const header = [
519
+ padRight('Name', nameWidth),
520
+ padRight('Ready', readyWidth),
521
+ 'Status',
522
+ ].join(' ');
523
+ console.log(header);
524
+ console.log('-'.repeat(header.length));
525
+ for (const deployment of report.deployments) {
526
+ const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
527
+ const status = deployment.ready ? 'ready' : 'not-ready';
528
+ console.log([
529
+ padRight(deployment.name, nameWidth),
530
+ padRight(readiness, readyWidth),
531
+ status,
532
+ ].join(' '));
533
+ }
534
+ }
535
+ if (report.pods.length > 0) {
536
+ console.log('');
537
+ console.log('Pods:');
538
+ const nameWidth = Math.max(4, ...report.pods.map((p) => p.name.length));
539
+ const phaseWidth = Math.max(5, ...report.pods.map((p) => p.phase.length));
540
+ const restartsWidth = Math.max(8, ...report.pods.map((p) => String(p.restarts).length));
541
+ const header = [
542
+ padRight('Name', nameWidth),
543
+ padRight('Phase', phaseWidth),
544
+ padRight('Restarts', restartsWidth),
545
+ 'Ready',
546
+ 'Age',
547
+ ].join(' ');
548
+ console.log(header);
549
+ console.log('-'.repeat(header.length));
550
+ for (const pod of report.pods) {
551
+ console.log([
552
+ padRight(pod.name, nameWidth),
553
+ padRight(pod.phase, phaseWidth),
554
+ padRight(String(pod.restarts), restartsWidth),
555
+ pod.ready ? 'yes' : 'no',
556
+ pod.age,
557
+ ].join(' '));
558
+ }
559
+ }
560
+ if (report.events.length > 0) {
561
+ console.log('');
562
+ console.log('Events:');
563
+ for (const event of report.events) {
564
+ const timestamp = event.timestamp ?? 'unknown';
565
+ const reason = event.reason ?? 'Unknown';
566
+ const message = event.message ?? '';
567
+ console.log(` [${timestamp}] ${event.type} ${reason}: ${message}`);
568
+ }
569
+ }
570
+ }
571
+ function getDeploymentWarnings(status) {
572
+ if (!status)
573
+ return [];
574
+ const warnings = [];
575
+ if (status.state !== 'ready') {
576
+ warnings.push(`Deployment state: ${status.state}`);
577
+ }
578
+ if (status.k8s_status) {
579
+ const { available_replicas, desired_replicas, ready, conditions } = status.k8s_status;
580
+ if (!ready) {
581
+ warnings.push(`Deployment replicas not ready (${available_replicas}/${desired_replicas})`);
582
+ }
583
+ for (const condition of conditions) {
584
+ if (condition.status !== 'True' && condition.message) {
585
+ warnings.push(`${condition.type}: ${condition.message}`);
586
+ }
587
+ }
588
+ }
589
+ return Array.from(new Set(warnings));
590
+ }
591
+ async function watchDeploymentStatus(context, projectId, envName, timeoutSeconds) {
592
+ const start = Date.now();
593
+ const pollIntervalMs = 3000;
594
+ console.log('');
595
+ console.log('Watching deployment status...');
596
+ while ((Date.now() - start) / 1000 < timeoutSeconds) {
597
+ const health = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/health`);
598
+ const elapsed = Math.floor((Date.now() - start) / 1000);
599
+ const readiness = health.deployment
600
+ ? `${health.deployment.available_replicas}/${health.deployment.desired_replicas}`
601
+ : 'n/a';
602
+ console.log(` [${elapsed}s] ${health.status} (${readiness} ready)`);
603
+ if (health.ready) {
604
+ console.log(' Deployment is ready.');
605
+ return;
606
+ }
607
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
608
+ }
609
+ console.log(` Timeout after ${timeoutSeconds}s. Run "eve env diagnose ${projectId} ${envName}" for details.`);
610
+ }
422
611
  /**
423
612
  * Format a date string for display
424
613
  */