@glash/cli 0.2.13 → 0.2.14

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,30 @@ 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)) {
226
+ indicator.stop();
219
227
  return fail(`Deployment failed (${status}). Run \`glash logs ${dep.id}\` for details.`);
220
228
  }
229
+ currentPhase = phaseFromPipeline(pipe, currentPhase);
221
230
  pollDelayMs = nextPollDelay(pipe);
222
- await sleep(pollDelayMs);
231
+ await indicator.wait(pollDelayMs);
223
232
  }
233
+ indicator.stop();
224
234
  warn('Timed out waiting for deployment. Check `glash status <id>`.');
225
235
  }
226
236
 
@@ -300,4 +310,67 @@ function nextPollDelay(pipe) {
300
310
  return 5000;
301
311
  }
302
312
 
313
+ function phaseFromEvent(ev) {
314
+ if (ev.step === 'detect') return 'detecting';
315
+ if (ev.step === 'host') return 'assigning host';
316
+ if (ev.step === 'package') return ev.status === 'queued' ? 'queued' : 'packaging';
317
+ if (ev.step === 'build') return 'building';
318
+ if (ev.step === 'deploy') return ev.status === 'ready' ? 'finalizing' : 'deploying';
319
+ if (ev.step === 'route' || ev.step === 'edge' || ev.step === 'ssl') return 'routing';
320
+ if (ev.step === 'runtime') return 'checking runtime';
321
+ return ev.step || 'deploying';
322
+ }
323
+
324
+ function phaseFromPipeline(pipe, fallback) {
325
+ const status = pipe.deployStatus ?? pipe.buildStatus ?? '';
326
+ if (status === 'queued') return 'queued';
327
+ if (status === 'building') return 'building';
328
+ if (status === 'rolling-out') return 'deploying';
329
+ const last = [...(pipe.events ?? [])].reverse().find(Boolean);
330
+ return last ? phaseFromEvent(last) : fallback;
331
+ }
332
+
333
+ function createDeployIndicator({ start, getPhase }) {
334
+ const interactive = Boolean(stdout.isTTY);
335
+ let frame = 0;
336
+ let timer;
337
+ let plainLast = 0;
338
+ const frames = ['.', '..', '...'];
339
+
340
+ function message() {
341
+ const elapsed = Math.floor((Date.now() - start) / 1000);
342
+ const dots = frames[frame++ % frames.length].padEnd(3, ' ');
343
+ return ` ${color.cyan('•')} ${color.dim('deploy'.padEnd(8))} ${getPhase()}${dots} ${color.dim(`${elapsed}s elapsed`)}`;
344
+ }
345
+
346
+ function render() {
347
+ if (!interactive) {
348
+ if (Date.now() - plainLast > 30_000) {
349
+ plainLast = Date.now();
350
+ info(message().trimEnd());
351
+ }
352
+ return;
353
+ }
354
+ stdout.write(`\r\x1b[2K${message()}`);
355
+ }
356
+
357
+ return {
358
+ start() {
359
+ render();
360
+ timer = setInterval(render, 1000);
361
+ },
362
+ clear() {
363
+ if (interactive) stdout.write('\r\x1b[2K');
364
+ },
365
+ stop() {
366
+ if (timer) clearInterval(timer);
367
+ timer = undefined;
368
+ if (interactive) stdout.write('\r\x1b[2K');
369
+ },
370
+ wait(ms) {
371
+ return sleep(ms);
372
+ },
373
+ };
374
+ }
375
+
303
376
  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.14';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
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",