@tinycloud/cli 1.1.0-beta.12 → 1.1.0-beta.13

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.
@@ -59,7 +59,9 @@ var init_constants = __esm({
59
59
  NOT_FOUND: 4,
60
60
  PERMISSION_DENIED: 5,
61
61
  NETWORK_ERROR: 6,
62
- NODE_ERROR: 7
62
+ NODE_ERROR: 7,
63
+ /** A write was refused because the owner's TinyCloud storage is full. */
64
+ STORAGE_FULL: 10
63
65
  };
64
66
  }
65
67
  });
@@ -258,6 +260,86 @@ var init_formatter = __esm({
258
260
  }
259
261
  });
260
262
 
263
+ // src/output/storage.ts
264
+ function storageRejection(error) {
265
+ let node = error;
266
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
267
+ const record = node;
268
+ let code3;
269
+ if (record.code === "STORAGE_QUOTA_EXCEEDED" || record.code === "STORAGE_LIMIT_REACHED") {
270
+ code3 = record.code;
271
+ } else if (record.code !== void 0) {
272
+ return void 0;
273
+ } else if (isStorageStatus(record)) {
274
+ code3 = storageCodeFromText(record.message);
275
+ }
276
+ if (code3) {
277
+ const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
278
+ return account ? { code: code3, account } : { code: code3 };
279
+ }
280
+ node = record.cause;
281
+ }
282
+ return void 0;
283
+ }
284
+ function describeStorageRejection(rejection, progress) {
285
+ const reason = rejection.code === "STORAGE_LIMIT_REACHED" ? "This write is larger than the TinyCloud storage you have left" : "TinyCloud storage is full";
286
+ const message = progress ? `${progress}${reason}.` : `${reason}; nothing was written.`;
287
+ const { account } = rejection;
288
+ const totals = account ? `${formatStorageBytes(account.usedBytes)} used of ${formatStorageBytes(account.limitBytes)}${account.plan ? ` (${account.plan} plan)` : ""}. ` : "";
289
+ return {
290
+ message,
291
+ hint: `${totals}Reading still works.
292
+ Free up space or upgrade: ${MANAGE_STORAGE_URL}`
293
+ };
294
+ }
295
+ function formatStorageBytes(bytes) {
296
+ const units = ["KiB", "MiB", "GiB", "TiB"];
297
+ if (bytes < 1024) return `${bytes} B`;
298
+ let value = bytes / 1024;
299
+ let unit = 0;
300
+ while (value >= 1024 && unit < units.length - 1) {
301
+ value /= 1024;
302
+ unit += 1;
303
+ }
304
+ return `${value.toFixed(1).replace(/\.0$/, "")} ${units[unit]}`;
305
+ }
306
+ function storageCodeFromText(message) {
307
+ if (typeof message !== "string") return void 0;
308
+ if (message.includes("larger than the TinyCloud storage you have left")) return "STORAGE_LIMIT_REACHED";
309
+ if (message.includes("TinyCloud storage is full") || /storage quota exceeded/i.test(message)) {
310
+ return "STORAGE_QUOTA_EXCEEDED";
311
+ }
312
+ return void 0;
313
+ }
314
+ function isStorageStatus(record) {
315
+ const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
316
+ const statuses = [
317
+ "status" in record ? record.status : void 0,
318
+ "statusCode" in record ? record.statusCode : void 0,
319
+ "status" in meta ? meta.status : void 0
320
+ ];
321
+ return statuses.some((value) => value === 402 || value === 413);
322
+ }
323
+ function accountTotals(meta) {
324
+ if (typeof meta !== "object" || meta === null) return void 0;
325
+ const account = meta.account;
326
+ if (typeof account !== "object" || account === null) return void 0;
327
+ const { usedBytes, limitBytes, plan } = account;
328
+ if (!isByteCount(usedBytes) || !isByteCount(limitBytes) || limitBytes === 0) return void 0;
329
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
330
+ }
331
+ function isByteCount(value) {
332
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
333
+ }
334
+ var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
335
+ var init_storage2 = __esm({
336
+ "src/output/storage.ts"() {
337
+ "use strict";
338
+ MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/billing";
339
+ MAX_CAUSE_DEPTH = 4;
340
+ }
341
+ });
342
+
261
343
  // src/output/errors.ts
262
344
  import { readFileSync, readdirSync } from "fs";
263
345
  import { join as join2 } from "path";
@@ -268,6 +350,8 @@ function setActiveProfileName(name) {
268
350
  }
269
351
  function cliErrorFromService(error, message = error.message) {
270
352
  if (error instanceof CLIError) return error;
353
+ const storageFull = storageFullError(error);
354
+ if (storageFull) return storageFull;
271
355
  const meta = { ...error.meta };
272
356
  delete meta.hint;
273
357
  const status = error.status ?? error.statusCode;
@@ -286,6 +370,8 @@ function cliErrorFromService(error, message = error.message) {
286
370
  );
287
371
  }
288
372
  function wrapError(error) {
373
+ const storageFull = storageFullError(error);
374
+ if (storageFull) return storageFull;
289
375
  if (error instanceof CLIError) return error;
290
376
  const message = error instanceof Error ? error.message : String(error);
291
377
  const verdict = authorizationVerdictOf(error);
@@ -332,6 +418,13 @@ function missingPrivateKeyError() {
332
418
  { hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
333
419
  );
334
420
  }
421
+ function storageFullError(error, progress) {
422
+ if (error instanceof CLIError && error.exitCode === ExitCode.STORAGE_FULL) return error;
423
+ const rejection = storageRejection(error);
424
+ if (!rejection) return void 0;
425
+ const { message, hint } = describeStorageRejection(rejection, progress);
426
+ return new CLIError(rejection.code, message, ExitCode.STORAGE_FULL, { hint });
427
+ }
335
428
  function handleError(error) {
336
429
  const cliError = wrapError(error);
337
430
  const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
@@ -414,6 +507,7 @@ var init_errors = __esm({
414
507
  "use strict";
415
508
  init_constants();
416
509
  init_formatter();
510
+ init_storage2();
417
511
  CLIError = class extends Error {
418
512
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
419
513
  super(message);
@@ -7055,7 +7149,7 @@ function validatedCapabilityOf2(error) {
7055
7149
  if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
7056
7150
  const seen = /* @__PURE__ */ new Set();
7057
7151
  let node = error;
7058
- for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7152
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7059
7153
  seen.add(node);
7060
7154
  const record = node;
7061
7155
  const status = errorStatusOf(record);
@@ -7075,7 +7169,7 @@ function validatedCapabilityOf2(error) {
7075
7169
  }
7076
7170
  return void 0;
7077
7171
  }
7078
- var MAX_CAUSE_DEPTH = 8;
7172
+ var MAX_CAUSE_DEPTH2 = 8;
7079
7173
  function errorStatusOf(node) {
7080
7174
  const meta = node.meta;
7081
7175
  const candidates = [
@@ -7094,7 +7188,7 @@ function authorizationVerdictOf2(error) {
7094
7188
  const seen = /* @__PURE__ */ new Set();
7095
7189
  let sawUnauthorizedCode = false;
7096
7190
  let node = error;
7097
- for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7191
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH2 && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
7098
7192
  seen.add(node);
7099
7193
  const record = node;
7100
7194
  const status = errorStatusOf(record);
@@ -17531,15 +17625,6 @@ async function throwKvError(error, spaceUri, profileName) {
17531
17625
  if (hosted) throw hosted;
17532
17626
  throw cliErrorFromService(error);
17533
17627
  }
17534
- function isByteCount(value) {
17535
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
17536
- }
17537
- function storageQuotaError(error) {
17538
- if (error.code !== "STORAGE_QUOTA_EXCEEDED") return void 0;
17539
- const { usedBytes, limitBytes } = typeof error.meta === "object" && error.meta !== null ? error.meta : {};
17540
- const sizes = isByteCount(usedBytes) && isByteCount(limitBytes) ? ` (${formatBytes(usedBytes)} used of ${formatBytes(limitBytes)} limit)` : "";
17541
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was written`, ExitCode.ERROR);
17542
- }
17543
17628
  function assertAddressableKey(key) {
17544
17629
  for (let i = 0; i < key.length; i++) {
17545
17630
  const code3 = key.charCodeAt(i);
@@ -17635,8 +17720,6 @@ function registerKvCommand(program) {
17635
17720
  const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
17636
17721
  const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
17637
17722
  if (!result.ok) {
17638
- const quota = storageQuotaError(result.error);
17639
- if (quota) throw quota;
17640
17723
  await throwKvError(result.error, spaceUri, ctx.profile);
17641
17724
  }
17642
17725
  outputJson({ key, written: true });
@@ -29312,7 +29395,6 @@ var MAX_INLINE_BYTES2 = 256 * 1024;
29312
29395
 
29313
29396
  // src/commands/share.ts
29314
29397
  import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
29315
- init_formatter();
29316
29398
  init_errors();
29317
29399
 
29318
29400
  // src/share/output.ts
@@ -29575,9 +29657,8 @@ function shareCliError(error, operation = "publish") {
29575
29657
  if (failure.kind === "registry-rejected") {
29576
29658
  return new CLIError("REGISTRY_REJECTED", "the TinyCloud location registry rejected this session's location record, so nothing was shared; retrying will not help. Log in again, and report the problem if it persists", 6);
29577
29659
  }
29578
- if (failure.kind === "storage-quota-exceeded") {
29579
- const sizes = failure.usedBytes === void 0 || failure.limitBytes === void 0 ? "" : ` (${formatBytes(failure.usedBytes)} used of ${formatBytes(failure.limitBytes)} limit)`;
29580
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
29660
+ if (failure.kind === "storage-full") {
29661
+ return storageFullError(failure);
29581
29662
  }
29582
29663
  if (failure.kind === "upload-failed") {
29583
29664
  return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
@@ -29597,6 +29678,8 @@ function shareCliError(error, operation = "publish") {
29597
29678
  const code3 = error.code === "fetch-failed" ? "NOT_FOUND" : error.code.replaceAll("-", "_").toUpperCase();
29598
29679
  return new CLIError(code3, error.message, exit);
29599
29680
  }
29681
+ const storageFull = storageFullError(error);
29682
+ if (storageFull) return storageFull;
29600
29683
  const message = error instanceof Error ? error.message : String(error);
29601
29684
  const nodeCode = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
29602
29685
  const known = {
@@ -30054,6 +30137,8 @@ async function dbHandle(node, dbName, spaceInput, profileName) {
30054
30137
  return { handle: sql.db(dbName), spaceUri };
30055
30138
  }
30056
30139
  async function throwSqlError(error, spaceUri, profileName, prefix) {
30140
+ const storageFull = storageFullError(error, prefix);
30141
+ if (storageFull) throw storageFull;
30057
30142
  const hosted = await unhostedSpaceError(error, spaceUri, profileName);
30058
30143
  if (hosted) throw hosted;
30059
30144
  const message = prefix ? `${prefix}${error.message}` : error.message;