@glash/cli 0.2.13 → 0.2.15

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.
@@ -7,6 +7,7 @@ import { join, basename } from 'node:path';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { openAsBlob } from 'node:fs';
9
9
  import { access, readFile, rm, stat } from 'node:fs/promises';
10
+ import { stdout } from 'node:process';
10
11
 
11
12
  function currentCommit() {
12
13
  try {
@@ -179,6 +180,9 @@ export async function deploy({ flags }) {
179
180
  const start = Date.now();
180
181
  let pollDelayMs = 5000;
181
182
  let lastProgressAt = Date.now();
183
+ let currentPhase = 'queued';
184
+ const indicator = createDeployIndicator({ start, getPhase: () => currentPhase });
185
+ indicator.start();
182
186
  while (Date.now() - start < 15 * 60 * 1000) {
183
187
  let pipe;
184
188
  try {
@@ -203,24 +207,32 @@ export async function deploy({ flags }) {
203
207
  const key = `${ev.id ?? ev.createdAt}:${ev.step}:${ev.status}`;
204
208
  if (seen.has(key)) continue;
205
209
  seen.add(key);
210
+ indicator.clear();
206
211
  const tag = stepBadge(ev.status);
207
212
  info(` ${tag} ${color.dim(ev.step.padEnd(8))} ${ev.message ?? ''}`);
208
213
  lastProgressAt = Date.now();
214
+ currentPhase = phaseFromEvent(ev);
209
215
  }
210
216
 
211
217
  const status = pipe.deployStatus ?? pipe.buildStatus ?? '';
212
218
  if (status && status !== lastStatus) lastStatus = status;
213
219
  if (['ready', 'live', 'success'].includes(status)) {
220
+ indicator.stop();
214
221
  const url = pipe.hostings?.[0]?.domains?.[0]?.domain;
215
222
  ok(`Live${url ? ` → ${color.cyan(`https://${url.replace(/^https?:\/\//, '')}`)}` : ''}`);
216
223
  return;
217
224
  }
218
225
  if (['error', 'failed'].includes(status)) {
219
- return fail(`Deployment failed (${status}). Run \`glash logs ${dep.id}\` for details.`);
226
+ indicator.stop();
227
+ const lastError = [...(pipe.events ?? [])].reverse().find((ev) => ev.status === 'error' || ev.status === 'failed');
228
+ const detail = lastError?.message ? `: ${lastError.message}` : '';
229
+ return fail(`Deployment failed (${status})${detail}. Run \`glash logs ${dep.id}\` for details.`);
220
230
  }
231
+ currentPhase = phaseFromPipeline(pipe, currentPhase);
221
232
  pollDelayMs = nextPollDelay(pipe);
222
- await sleep(pollDelayMs);
233
+ await indicator.wait(pollDelayMs);
223
234
  }
235
+ indicator.stop();
224
236
  warn('Timed out waiting for deployment. Check `glash status <id>`.');
225
237
  }
226
238
 
@@ -300,4 +312,67 @@ function nextPollDelay(pipe) {
300
312
  return 5000;
301
313
  }
302
314
 
315
+ function phaseFromEvent(ev) {
316
+ if (ev.step === 'detect') return 'detecting';
317
+ if (ev.step === 'host') return 'assigning host';
318
+ if (ev.step === 'package') return ev.status === 'queued' ? 'queued' : 'packaging';
319
+ if (ev.step === 'build') return 'building';
320
+ if (ev.step === 'deploy') return ev.status === 'ready' ? 'finalizing' : 'deploying';
321
+ if (ev.step === 'route' || ev.step === 'edge' || ev.step === 'ssl') return 'routing';
322
+ if (ev.step === 'runtime') return 'checking runtime';
323
+ return ev.step || 'deploying';
324
+ }
325
+
326
+ function phaseFromPipeline(pipe, fallback) {
327
+ const status = pipe.deployStatus ?? pipe.buildStatus ?? '';
328
+ if (status === 'queued') return 'queued';
329
+ if (status === 'building') return 'building';
330
+ if (status === 'rolling-out') return 'deploying';
331
+ const last = [...(pipe.events ?? [])].reverse().find(Boolean);
332
+ return last ? phaseFromEvent(last) : fallback;
333
+ }
334
+
335
+ function createDeployIndicator({ start, getPhase }) {
336
+ const interactive = Boolean(stdout.isTTY);
337
+ let frame = 0;
338
+ let timer;
339
+ let plainLast = 0;
340
+ const frames = ['.', '..', '...'];
341
+
342
+ function message() {
343
+ const elapsed = Math.floor((Date.now() - start) / 1000);
344
+ const dots = frames[frame++ % frames.length].padEnd(3, ' ');
345
+ return ` ${color.cyan('•')} ${color.dim('deploy'.padEnd(8))} ${getPhase()}${dots} ${color.dim(`${elapsed}s elapsed`)}`;
346
+ }
347
+
348
+ function render() {
349
+ if (!interactive) {
350
+ if (Date.now() - plainLast > 30_000) {
351
+ plainLast = Date.now();
352
+ info(message().trimEnd());
353
+ }
354
+ return;
355
+ }
356
+ stdout.write(`\r\x1b[2K${message()}`);
357
+ }
358
+
359
+ return {
360
+ start() {
361
+ render();
362
+ timer = setInterval(render, 1000);
363
+ },
364
+ clear() {
365
+ if (interactive) stdout.write('\r\x1b[2K');
366
+ },
367
+ stop() {
368
+ if (timer) clearInterval(timer);
369
+ timer = undefined;
370
+ if (interactive) stdout.write('\r\x1b[2K');
371
+ },
372
+ wait(ms) {
373
+ return sleep(ms);
374
+ },
375
+ };
376
+ }
377
+
303
378
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
package/lib/version.mjs CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.13';
1
+ export const VERSION = '0.2.15';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "glashDB command-line interface — deploy projects, pull source + env, manage env vars.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://glashdb.com",