@buildinternet/uploads 0.47.0 → 0.48.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/client.d.ts CHANGED
@@ -439,6 +439,23 @@ export interface UsageResult {
439
439
  storageRemainingBytes?: number;
440
440
  maxUploadsPerPeriod?: number;
441
441
  uploadsRemaining?: number;
442
+ /** Bytes still on hosted storage (shared-lane residue). */
443
+ sharedBytes?: number;
444
+ /** "shared" = BYO bucket active: the storage cap meters only hosted
445
+ * residue; the customer's own bucket is unmetered. */
446
+ storageBudgetBasis?: "total" | "shared";
447
+ /** Bearer-safe lane summary (issue #775; servers ≥ this field's release). */
448
+ storage?: {
449
+ mode: "shared" | "byo";
450
+ /** Demoted former-active lanes that still serve previously uploaded files. */
451
+ fallbackLanes: number;
452
+ health: {
453
+ ok: boolean;
454
+ code?: string;
455
+ message?: string;
456
+ since?: string;
457
+ };
458
+ };
442
459
  /** File scopes of the presented token (servers ≥ this field's release). */
443
460
  scopes?: Array<TokenScope>;
444
461
  /**
@@ -615,16 +615,18 @@ export interface DoctorReport {
615
615
  /** Workspace/token mismatch warning (also present in hints). */
616
616
  warning?: string;
617
617
  /**
618
- * Bring-your-own-bucket storage status (issue #583 Phase 3). `GET
619
- * /me/workspaces/:name/storage` is session-gated (Better Auth cookie or
620
- * bearer via the AUTH service see `session-auth.ts`); the CLI only ever
621
- * holds a minted `up_<workspace>_…` workspace token, never a session
622
- * bearer, so doctor cannot reach that route today. Until a token-authed
623
- * read path exists, this is an honest "can't check from here" rather than
624
- * a fabricated mode.
618
+ * Storage-lane summary (issue #775): the usage endpoint carries a
619
+ * bearer-safe `storage` object (mode + fallback-lane count + health), so
620
+ * doctor reports it from the same call it already makes. `checked` is
621
+ * false when usage failed or the server predates the field — then `note`
622
+ * falls back to the honest "can't check from here" line (the full
623
+ * projection on `GET /me/workspaces/:name/storage` stays session-gated).
625
624
  */
626
625
  storage: {
627
- checked: false;
626
+ checked: boolean;
627
+ mode?: "shared" | "byo";
628
+ fallbackLanes?: number;
629
+ healthy?: boolean;
628
630
  note: string;
629
631
  };
630
632
  hints: string[];
package/dist/commands.js CHANGED
@@ -3483,6 +3483,10 @@ export async function buildDoctorReport(config, client, detectRoots) {
3483
3483
  }
3484
3484
  }
3485
3485
  let usage;
3486
+ let storage = {
3487
+ checked: false,
3488
+ note: "not checked from the CLI — this server doesn't report a storage summary on the usage endpoint; sign in on the web (Account → workspace → Settings) to view mode and verification status",
3489
+ };
3486
3490
  let scopes;
3487
3491
  if (authOk) {
3488
3492
  try {
@@ -3493,6 +3497,23 @@ export async function buildDoctorReport(config, client, detectRoots) {
3493
3497
  objects: snap.objects,
3494
3498
  uploadsInPeriod: snap.uploadsInPeriod,
3495
3499
  };
3500
+ if (snap.storage) {
3501
+ const laneNote = snap.storage.fallbackLanes > 0
3502
+ ? ` (${snap.storage.fallbackLanes} previous lane${snap.storage.fallbackLanes === 1 ? "" : "s"} still serving old files)`
3503
+ : "";
3504
+ const healthNote = snap.storage.health.ok
3505
+ ? ""
3506
+ : " — not working; rotate credentials on the web settings page";
3507
+ storage = {
3508
+ checked: true,
3509
+ mode: snap.storage.mode,
3510
+ fallbackLanes: snap.storage.fallbackLanes,
3511
+ healthy: snap.storage.health.ok,
3512
+ note: (snap.storage.mode === "byo" ? "your bucket" : "hosted storage") +
3513
+ laneNote +
3514
+ healthNote,
3515
+ };
3516
+ }
3496
3517
  scopes = snap.scopes;
3497
3518
  if (scopes && !scopes.includes("files:delete")) {
3498
3519
  hints.push("token lacks files:delete (`uploads delete` will be forbidden) — re-run `uploads login` for a full-scope token");
@@ -3522,10 +3543,7 @@ export async function buildDoctorReport(config, client, detectRoots) {
3522
3543
  usage,
3523
3544
  scopes,
3524
3545
  warning: mismatch,
3525
- storage: {
3526
- checked: false,
3527
- note: "not checked from the CLI — storage settings (shared vs. bring-your-own-bucket) live behind a signed-in session; sign in on the web (Account → workspace → Settings) to view mode and verification status",
3528
- },
3546
+ storage,
3529
3547
  hints,
3530
3548
  browser,
3531
3549
  };
@@ -9,6 +9,11 @@ export type UsageSnapshotLike = {
9
9
  storageRemainingBytes?: number;
10
10
  maxUploadsPerPeriod?: number;
11
11
  uploadsRemaining?: number;
12
+ /** Bytes still on hosted storage (shared-lane residue). */
13
+ sharedBytes?: number;
14
+ /** "shared" = BYO bucket active: the storage cap meters only hosted
15
+ * residue; bytes in the customer's own bucket are unmetered. */
16
+ storageBudgetBasis?: "total" | "shared";
12
17
  /** Catalog plan id when the API reports it (`free` | `pro`). */
13
18
  plan?: string;
14
19
  };
@@ -11,9 +11,14 @@ import { formatByteSize, formatMarketedBytes } from "./format-bytes.js";
11
11
  import { BRAND } from "./cli-brand.js";
12
12
  /** True when the API reported any cumulative workspace quota. */
13
13
  export function isUsageMetered(result) {
14
- return (usagePct(result.bytes, result.maxStorageBytes) !== null ||
14
+ return (usagePct(storageMeteredBytes(result), result.maxStorageBytes) !== null ||
15
15
  usagePct(result.uploadsInPeriod, result.maxUploadsPerPeriod) !== null);
16
16
  }
17
+ /** The usage number the storage cap actually meters — hosted residue only
18
+ * when a BYO bucket is active (`storageBudgetBasis: "shared"`). */
19
+ function storageMeteredBytes(result) {
20
+ return result.storageBudgetBasis === "shared" ? (result.sharedBytes ?? 0) : result.bytes;
21
+ }
17
22
  /** 0–100, one decimal. Missing/invalid caps → no bar. Matches web `usagePct`. */
18
23
  export function usagePct(value, max) {
19
24
  if (typeof max !== "number" || !(max > 0) || !Number.isFinite(value))
@@ -112,21 +117,26 @@ export function formatUsageHuman(result, opts = {}) {
112
117
  const label = planLabel(result.plan);
113
118
  if (label)
114
119
  lines.push(`plan: ${label}`);
115
- const storagePct = usagePct(result.bytes, result.maxStorageBytes);
120
+ const byoActive = result.storageBudgetBasis === "shared";
121
+ const meteredBytes = storageMeteredBytes(result);
122
+ const storagePct = usagePct(meteredBytes, result.maxStorageBytes);
116
123
  if (storagePct !== null && result.maxStorageBytes != null) {
117
124
  // Caps (and remaining-against-cap) use SI marketed formatting so Free's
118
125
  // 250_000_000 reads as "250 MB", not binary "238.4 MB". Used bytes share
119
126
  // the same base on this line so the three numbers stay coherent.
120
- const detail = `${formatMarketedBytes(result.bytes)} / ${formatMarketedBytes(result.maxStorageBytes)}` +
127
+ const detail = `${formatMarketedBytes(meteredBytes)} / ${formatMarketedBytes(result.maxStorageBytes)}` +
121
128
  (result.storageRemainingBytes != null
122
129
  ? ` (${formatMarketedBytes(result.storageRemainingBytes)} free)`
123
130
  : "");
124
131
  const bar = formatProgressBar(storagePct, { width, color });
125
- lines.push(`storage: ${bar} ${detail}`);
132
+ lines.push(`storage: ${bar} ${detail}${byoActive ? " on hosted storage" : ""}`);
126
133
  }
127
134
  else {
128
135
  lines.push(`storage: ${formatByteSize(result.bytes)}`);
129
136
  }
137
+ if (byoActive) {
138
+ lines.push("note: your own bucket is unmetered — the storage quota only counts files on hosted storage");
139
+ }
130
140
  lines.push(`objects: ${formatCount(result.objects)}`);
131
141
  const uploadsPct = usagePct(result.uploadsInPeriod, result.maxUploadsPerPeriod);
132
142
  if (uploadsPct !== null && result.maxUploadsPerPeriod != null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/uploads",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "description": "CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds",
5
5
  "type": "module",
6
6
  "sideEffects": false,