@tinycloud/cli 1.1.0-beta.11 → 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.
package/dist/index.js CHANGED
@@ -32,7 +32,9 @@ var init_constants = __esm({
32
32
  NOT_FOUND: 4,
33
33
  PERMISSION_DENIED: 5,
34
34
  NETWORK_ERROR: 6,
35
- NODE_ERROR: 7
35
+ NODE_ERROR: 7,
36
+ /** A write was refused because the owner's TinyCloud storage is full. */
37
+ STORAGE_FULL: 10
36
38
  };
37
39
  }
38
40
  });
@@ -96,11 +98,6 @@ function outputError(code3, message, hint, meta) {
96
98
  function isInteractive() {
97
99
  return Boolean(process.stdout.isTTY);
98
100
  }
99
- function formatBytes(bytes) {
100
- if (bytes < 1024) return `${bytes} B`;
101
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
102
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
103
- }
104
101
  var init_formatter = __esm({
105
102
  "src/output/formatter.ts"() {
106
103
  "use strict";
@@ -108,6 +105,86 @@ var init_formatter = __esm({
108
105
  }
109
106
  });
110
107
 
108
+ // src/output/storage.ts
109
+ function storageRejection(error) {
110
+ let node = error;
111
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null; depth += 1) {
112
+ const record = node;
113
+ let code3;
114
+ if (record.code === "STORAGE_QUOTA_EXCEEDED" || record.code === "STORAGE_LIMIT_REACHED") {
115
+ code3 = record.code;
116
+ } else if (record.code !== void 0) {
117
+ return void 0;
118
+ } else if (isStorageStatus(record)) {
119
+ code3 = storageCodeFromText(record.message);
120
+ }
121
+ if (code3) {
122
+ const account = accountTotals(record) ?? accountTotals(record.meta) ?? accountTotals(record.metadata);
123
+ return account ? { code: code3, account } : { code: code3 };
124
+ }
125
+ node = record.cause;
126
+ }
127
+ return void 0;
128
+ }
129
+ function describeStorageRejection(rejection, progress) {
130
+ const reason = rejection.code === "STORAGE_LIMIT_REACHED" ? "This write is larger than the TinyCloud storage you have left" : "TinyCloud storage is full";
131
+ const message = progress ? `${progress}${reason}.` : `${reason}; nothing was written.`;
132
+ const { account } = rejection;
133
+ const totals = account ? `${formatStorageBytes(account.usedBytes)} used of ${formatStorageBytes(account.limitBytes)}${account.plan ? ` (${account.plan} plan)` : ""}. ` : "";
134
+ return {
135
+ message,
136
+ hint: `${totals}Reading still works.
137
+ Free up space or upgrade: ${MANAGE_STORAGE_URL}`
138
+ };
139
+ }
140
+ function formatStorageBytes(bytes) {
141
+ const units = ["KiB", "MiB", "GiB", "TiB"];
142
+ if (bytes < 1024) return `${bytes} B`;
143
+ let value = bytes / 1024;
144
+ let unit = 0;
145
+ while (value >= 1024 && unit < units.length - 1) {
146
+ value /= 1024;
147
+ unit += 1;
148
+ }
149
+ return `${value.toFixed(1).replace(/\.0$/, "")} ${units[unit]}`;
150
+ }
151
+ function storageCodeFromText(message) {
152
+ if (typeof message !== "string") return void 0;
153
+ if (message.includes("larger than the TinyCloud storage you have left")) return "STORAGE_LIMIT_REACHED";
154
+ if (message.includes("TinyCloud storage is full") || /storage quota exceeded/i.test(message)) {
155
+ return "STORAGE_QUOTA_EXCEEDED";
156
+ }
157
+ return void 0;
158
+ }
159
+ function isStorageStatus(record) {
160
+ const meta = "meta" in record && typeof record.meta === "object" && record.meta !== null ? record.meta : {};
161
+ const statuses = [
162
+ "status" in record ? record.status : void 0,
163
+ "statusCode" in record ? record.statusCode : void 0,
164
+ "status" in meta ? meta.status : void 0
165
+ ];
166
+ return statuses.some((value) => value === 402 || value === 413);
167
+ }
168
+ function accountTotals(meta) {
169
+ if (typeof meta !== "object" || meta === null) return void 0;
170
+ const account = meta.account;
171
+ if (typeof account !== "object" || account === null) return void 0;
172
+ const { usedBytes, limitBytes, plan } = account;
173
+ if (!isByteCount(usedBytes) || !isByteCount(limitBytes) || limitBytes === 0) return void 0;
174
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
175
+ }
176
+ function isByteCount(value) {
177
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
178
+ }
179
+ var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
180
+ var init_storage = __esm({
181
+ "src/output/storage.ts"() {
182
+ "use strict";
183
+ MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/billing";
184
+ MAX_CAUSE_DEPTH = 4;
185
+ }
186
+ });
187
+
111
188
  // src/output/errors.ts
112
189
  import { readFileSync, readdirSync } from "fs";
113
190
  import { join } from "path";
@@ -117,6 +194,8 @@ function setActiveProfileName(name) {
117
194
  activeProfileName = name;
118
195
  }
119
196
  function wrapError(error) {
197
+ const storageFull = storageFullError(error);
198
+ if (storageFull) return storageFull;
120
199
  if (error instanceof CLIError) return error;
121
200
  const message = error instanceof Error ? error.message : String(error);
122
201
  const verdict = authorizationVerdictOf(error);
@@ -163,6 +242,13 @@ function missingPrivateKeyError() {
163
242
  { hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
164
243
  );
165
244
  }
245
+ function storageFullError(error, progress) {
246
+ if (error instanceof CLIError && error.exitCode === ExitCode.STORAGE_FULL) return error;
247
+ const rejection = storageRejection(error);
248
+ if (!rejection) return void 0;
249
+ const { message, hint } = describeStorageRejection(rejection, progress);
250
+ return new CLIError(rejection.code, message, ExitCode.STORAGE_FULL, { hint });
251
+ }
166
252
  function handleError(error) {
167
253
  const cliError = wrapError(error);
168
254
  const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
@@ -245,6 +331,7 @@ var init_errors = __esm({
245
331
  "use strict";
246
332
  init_constants();
247
333
  init_formatter();
334
+ init_storage();
248
335
  CLIError = class extends Error {
249
336
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
250
337
  super(message);
@@ -312,7 +399,7 @@ async function listDirs(dirPath) {
312
399
  }
313
400
  }
314
401
  var PRIVATE_FILE_MODE, PRIVATE_DIR_MODE;
315
- var init_storage = __esm({
402
+ var init_storage2 = __esm({
316
403
  "src/config/storage.ts"() {
317
404
  "use strict";
318
405
  PRIVATE_FILE_MODE = 384;
@@ -570,7 +657,7 @@ var init_profiles = __esm({
570
657
  "src/config/profiles.ts"() {
571
658
  "use strict";
572
659
  init_constants();
573
- init_storage();
660
+ init_storage2();
574
661
  init_errors();
575
662
  ProfileManager = class _ProfileManager {
576
663
  // ── Initialization ──────────────────────────────────────────────────
@@ -5750,7 +5837,7 @@ var init_permissions = __esm({
5750
5837
  "src/lib/permissions.ts"() {
5751
5838
  "use strict";
5752
5839
  init_constants();
5753
- init_storage();
5840
+ init_storage2();
5754
5841
  init_profiles();
5755
5842
  init_errors();
5756
5843
  init_constants();
@@ -16699,7 +16786,6 @@ function parseDuration(input) {
16699
16786
  }
16700
16787
 
16701
16788
  // src/commands/share.ts
16702
- init_formatter();
16703
16789
  init_errors();
16704
16790
 
16705
16791
  // src/share/output.ts
@@ -16965,9 +17051,8 @@ function shareCliError(error, operation = "publish") {
16965
17051
  if (failure.kind === "registry-rejected") {
16966
17052
  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);
16967
17053
  }
16968
- if (failure.kind === "storage-quota-exceeded") {
16969
- const sizes = failure.usedBytes === void 0 || failure.limitBytes === void 0 ? "" : ` (${formatBytes(failure.usedBytes)} used of ${formatBytes(failure.limitBytes)} limit)`;
16970
- return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
17054
+ if (failure.kind === "storage-full") {
17055
+ return storageFullError(failure);
16971
17056
  }
16972
17057
  if (failure.kind === "upload-failed") {
16973
17058
  return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
@@ -16987,6 +17072,8 @@ function shareCliError(error, operation = "publish") {
16987
17072
  const code3 = error.code === "fetch-failed" ? "NOT_FOUND" : error.code.replaceAll("-", "_").toUpperCase();
16988
17073
  return new CLIError(code3, error.message, exit);
16989
17074
  }
17075
+ const storageFull = storageFullError(error);
17076
+ if (storageFull) return storageFull;
16990
17077
  const message = error instanceof Error ? error.message : String(error);
16991
17078
  const nodeCode = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
16992
17079
  const known = {
@@ -17280,6 +17367,7 @@ function registerShareCommand(program2) {
17280
17367
  }
17281
17368
 
17282
17369
  // src/share/adapters.ts
17370
+ init_storage();
17283
17371
  init_profiles();
17284
17372
  init_constants();
17285
17373
  import { readFile as readFile4 } from "fs/promises";
@@ -17293,15 +17381,9 @@ function requiredKvAction(meta) {
17293
17381
  const action = meta.requiredAction;
17294
17382
  return action === "tinycloud.kv/put" || action === "tinycloud.kv/get" ? action : void 0;
17295
17383
  }
17296
- function isByteCount(value) {
17297
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
17298
- }
17299
17384
  function throwKvUploadFailure(error) {
17300
- if (typeof error === "object" && error !== null && "code" in error && error.code === "STORAGE_QUOTA_EXCEEDED") {
17301
- const meta = "meta" in error && typeof error.meta === "object" && error.meta !== null ? error.meta : {};
17302
- const { usedBytes, limitBytes } = meta;
17303
- throw new SharePublishAuthorityError(isByteCount(usedBytes) && isByteCount(limitBytes) ? { kind: "storage-quota-exceeded", usedBytes, limitBytes } : { kind: "storage-quota-exceeded" });
17304
- }
17385
+ const rejection = storageRejection(error);
17386
+ if (rejection) throw new SharePublishAuthorityError({ kind: "storage-full", ...rejection });
17305
17387
  throw new SharePublishAuthorityError({ kind: "upload-failed" });
17306
17388
  }
17307
17389
  var DEFAULT_SHARE_ORIGIN = "https://share.tinycloud.xyz";