@glash/cli 0.2.29 → 0.2.30

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.
Files changed (2) hide show
  1. package/commands/deploy.mjs +63 -10
  2. package/package.json +1 -1
@@ -134,10 +134,11 @@ export async function deploy({ flags }) {
134
134
  const commitHash = flags.commit || (remoteDeploy ? undefined : currentCommit());
135
135
  const gitBranch = flags.branch || currentBranch();
136
136
  const target = flags.prod || flags.production ? 'production' : (flags.target || 'production');
137
+ const requestedAssetMode = assetMode(flags);
137
138
 
138
139
  let dep;
139
140
  let envSummary = { count: 0, skipped: Boolean(flags['no-env']) };
140
- let assetSummary = { mode: assetMode(flags), offloaded: false, count: 0, bytes: 0, skipped: 0 };
141
+ let assetSummary = { mode: requestedAssetMode, status: requestedAssetMode === 'inline' ? 'inline' : 'pending', offloaded: false, count: 0, bytes: 0, skipped: 0 };
141
142
  let currentPhase = 'doing';
142
143
  const start = Date.now();
143
144
  const indicator = createDeployIndicator();
@@ -177,8 +178,11 @@ export async function deploy({ flags }) {
177
178
  assetSummary = await offloadPublicAssetsBeforeDeploy(projectId, flags);
178
179
  report.assetSummary = assetSummary;
179
180
  } catch (e) {
181
+ const failedAssetSummary = e.assetSummary ?? { ...assetSummary, status: 'failed', error: e.message };
182
+ assetSummary = failedAssetSummary;
183
+ report.assetSummary = failedAssetSummary;
180
184
  indicator.fail('failed');
181
- printDeployReport({ ...report, assetSummary, status: 'failed', error: `Public asset offload failed: ${e.message}` });
185
+ printDeployReport({ ...report, assetSummary: failedAssetSummary, status: 'failed', error: `Public asset offload failed: ${e.message}` });
182
186
  return fail(`Public asset offload failed: ${e.message}`);
183
187
  }
184
188
  } else if (assetSummary.mode !== 'inline') {
@@ -335,16 +339,16 @@ async function syncEnvBeforeDeploy(projectId, target, flags) {
335
339
 
336
340
  async function offloadPublicAssetsBeforeDeploy(projectId, flags) {
337
341
  const mode = assetMode(flags);
338
- const empty = { mode, offloaded: false, count: 0, bytes: 0, skipped: 0 };
342
+ const empty = { mode, status: mode === 'inline' ? 'inline' : 'pending', offloaded: false, count: 0, bytes: 0, skipped: 0 };
339
343
  if (mode === 'inline') return empty;
340
344
 
341
345
  const files = await publicAssetFiles(process.cwd());
342
- if (!files.length) return empty;
346
+ if (!files.length) return { ...empty, status: 'no-public-assets' };
343
347
 
344
348
  const totalBytes = files.reduce((sum, file) => sum + file.sizeBytes, 0);
345
349
  const threshold = assetOffloadThresholdBytes(flags);
346
350
  if (mode === 'auto' && totalBytes < threshold) {
347
- return { ...empty, bytes: totalBytes };
351
+ return { ...empty, status: 'below-threshold', count: files.length, bytes: totalBytes, thresholdBytes: threshold };
348
352
  }
349
353
 
350
354
  const assets = [];
@@ -360,7 +364,12 @@ async function offloadPublicAssetsBeforeDeploy(projectId, flags) {
360
364
  let uploaded = 0;
361
365
  let skipped = 0;
362
366
  for (const batch of chunk(assets, 100)) {
363
- const prepared = await apiPost(`/projects/${projectId}/public-assets/prepare`, { assets: batch });
367
+ let prepared;
368
+ try {
369
+ prepared = await apiPost(`/projects/${projectId}/public-assets/prepare`, { assets: batch });
370
+ } catch (e) {
371
+ throw assetOffloadFailure(e, { mode, status: 'prepare-failed', offloaded: false, count: assets.length, uploaded, skipped, bytes: totalBytes });
372
+ }
364
373
  for (const asset of prepared.assets ?? []) {
365
374
  if (asset.skipped) {
366
375
  skipped += 1;
@@ -377,15 +386,23 @@ async function offloadPublicAssetsBeforeDeploy(projectId, flags) {
377
386
  });
378
387
  if (!res.ok) {
379
388
  const detail = await res.text().catch(() => '');
380
- throw new Error(`Upload ${asset.path} failed with HTTP ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ''}`);
389
+ throw assetOffloadFailure(
390
+ new Error(`Upload ${asset.path} failed with HTTP ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ''}`),
391
+ { mode, status: 'upload-failed', offloaded: false, count: assets.length, uploaded, skipped, bytes: totalBytes },
392
+ );
381
393
  }
382
394
  uploaded += 1;
383
395
  }
384
- await apiPost(`/projects/${projectId}/public-assets/complete`, { assets: batch });
396
+ try {
397
+ await apiPost(`/projects/${projectId}/public-assets/complete`, { assets: batch });
398
+ } catch (e) {
399
+ throw assetOffloadFailure(e, { mode, status: 'complete-failed', offloaded: false, count: assets.length, uploaded, skipped, bytes: totalBytes });
400
+ }
385
401
  }
386
402
 
387
403
  return {
388
404
  mode,
405
+ status: 'offloaded',
389
406
  offloaded: true,
390
407
  count: assets.length,
391
408
  uploaded,
@@ -394,6 +411,26 @@ async function offloadPublicAssetsBeforeDeploy(projectId, flags) {
394
411
  };
395
412
  }
396
413
 
414
+ function assetOffloadFailure(error, assetSummary) {
415
+ const message = friendlyAssetOffloadError(error);
416
+ const wrapped = new Error(message);
417
+ wrapped.cause = error;
418
+ wrapped.status = error?.status;
419
+ wrapped.assetSummary = { ...assetSummary, error: message };
420
+ return wrapped;
421
+ }
422
+
423
+ function friendlyAssetOffloadError(error) {
424
+ const message = error?.message || String(error);
425
+ if (error?.status === 404 && /public-assets\/prepare|public-assets/i.test(message)) {
426
+ return 'This GlashDB API does not support public asset offload yet. Deploy the backend public-assets route and router update, then retry.';
427
+ }
428
+ if (error?.status === 503 && /Public asset storage is not configured|storage is not configured/i.test(message)) {
429
+ return 'Public asset storage is not configured on this GlashDB API. Set the R2 asset storage secrets, redeploy the backend, then retry.';
430
+ }
431
+ return message;
432
+ }
433
+
397
434
  function assetMode(flags) {
398
435
  const raw = String(flags['asset-mode'] ?? flags.assets ?? flags['public-assets'] ?? 'inline').toLowerCase();
399
436
  if (['inline', 'offload', 'auto'].includes(raw)) return raw;
@@ -635,6 +672,7 @@ function printDeployReport(report) {
635
672
  const liveUrl = report.liveUrl ? terminalLink(report.liveUrl) : '-';
636
673
  const rows = [
637
674
  ['Status', formatReportStatus(report.status)],
675
+ ['Project ID', report.projectId || '-'],
638
676
  ['Deployment ID', report.deploymentId || '-'],
639
677
  ['Target', report.target || 'production'],
640
678
  ['Source', report.source || '-'],
@@ -673,14 +711,29 @@ function formatPackagesUploaded(report) {
673
711
  }
674
712
 
675
713
  function formatAssetSummary(assetSummary) {
676
- if (!assetSummary || assetSummary.mode === 'inline') return 'inline';
714
+ if (!assetSummary || assetSummary.mode === 'inline') return 'inline (bundled with source)';
715
+ if (assetSummary.status === 'pending') return `${assetSummary.mode} requested`;
716
+ if (assetSummary.status === 'no-public-assets') return `${assetSummary.mode}, no public assets found`;
717
+ if (assetSummary.status === 'below-threshold') {
718
+ return `auto kept inline (${formatBytes(assetSummary.bytes)} below ${formatBytes(assetSummary.thresholdBytes)})`;
719
+ }
720
+ if (assetSummary.status === 'prepare-failed') {
721
+ return `prepare failed (${(assetSummary.count ?? 0).toLocaleString()} assets, ${formatBytes(assetSummary.bytes)})`;
722
+ }
723
+ if (assetSummary.status === 'upload-failed') {
724
+ return `upload failed (${(assetSummary.uploaded ?? 0).toLocaleString()}/${(assetSummary.count ?? 0).toLocaleString()} uploaded, ${formatBytes(assetSummary.bytes)})`;
725
+ }
726
+ if (assetSummary.status === 'complete-failed') {
727
+ return `activation failed (${(assetSummary.uploaded ?? 0).toLocaleString()} uploaded, ${formatBytes(assetSummary.bytes)})`;
728
+ }
729
+ if (assetSummary.status === 'failed') return `failed${assetSummary.error ? ` (${assetSummary.error})` : ''}`;
677
730
  if (!assetSummary.offloaded) {
678
731
  const size = assetSummary.bytes ? ` (${formatBytes(assetSummary.bytes)})` : '';
679
732
  return `${assetSummary.mode}, no offload${size}`;
680
733
  }
681
734
  const uploaded = assetSummary.uploaded ?? Math.max(0, (assetSummary.count ?? 0) - (assetSummary.skipped ?? 0));
682
735
  const skipped = assetSummary.skipped ? `, ${assetSummary.skipped.toLocaleString()} reused` : '';
683
- return `${assetSummary.count.toLocaleString()} offloaded (${formatBytes(assetSummary.bytes)}; ${uploaded.toLocaleString()} uploaded${skipped})`;
736
+ return `${assetSummary.count.toLocaleString()} assets offloaded (${formatBytes(assetSummary.bytes)}; ${uploaded.toLocaleString()} uploaded${skipped})`;
684
737
  }
685
738
 
686
739
  function formatDeploymentSize(report) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
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",