@dropthis/cli 0.3.5 → 0.4.1

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.
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CursorPage: () => CursorPage,
33
34
  DeploymentsResource: () => DeploymentsResource,
34
35
  Dropthis: () => Dropthis,
35
36
  UploadsResource: () => UploadsResource,
@@ -42,35 +43,6 @@ module.exports = __toCommonJS(index_exports);
42
43
  var import_promises2 = require("fs/promises");
43
44
  var import_node_path2 = require("path");
44
45
 
45
- // src/case.ts
46
- function toCamelKey(key) {
47
- return key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
48
- }
49
- function toSnakeKey(key) {
50
- return key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
51
- }
52
- function toCamelCase(value) {
53
- if (Array.isArray(value)) return value.map(toCamelCase);
54
- if (value && typeof value === "object") {
55
- return Object.fromEntries(
56
- Object.entries(value).map(([key, item]) => [
57
- toCamelKey(key),
58
- toCamelCase(item)
59
- ])
60
- );
61
- }
62
- return value;
63
- }
64
- function toSnakeCase(value) {
65
- if (Array.isArray(value)) return value.map(toSnakeCase);
66
- if (value && typeof value === "object") {
67
- return Object.fromEntries(
68
- Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [toSnakeKey(key), toSnakeCase(item)])
69
- );
70
- }
71
- return value;
72
- }
73
-
74
46
  // src/publish/checksum.ts
75
47
  var import_node_crypto = require("crypto");
76
48
  var import_node_fs = require("fs");
@@ -334,14 +306,41 @@ function explicitFileBytes(file) {
334
306
  if (file.bytes !== void 0) return file.bytes;
335
307
  throw new TypeError(`Explicit file ${file.path} is missing content bytes`);
336
308
  }
309
+ var VALID_VISIBILITIES = ["public", "unlisted"];
310
+ var MAX_TITLE_LENGTH = 500;
311
+ function validatePublishOptions(options) {
312
+ if (options.title !== void 0 && options.title !== null && options.title.length > MAX_TITLE_LENGTH) {
313
+ throw new Error(`Title must be ${MAX_TITLE_LENGTH} characters or less.`);
314
+ }
315
+ if (options.visibility !== void 0 && options.visibility !== null) {
316
+ if (!VALID_VISIBILITIES.includes(options.visibility)) {
317
+ throw new Error(
318
+ `Invalid visibility "${options.visibility}". Use "public" or "unlisted".`
319
+ );
320
+ }
321
+ }
322
+ if (options.expiresAt !== void 0 && options.expiresAt !== null) {
323
+ if (options.expiresAt instanceof Date) {
324
+ if (Number.isNaN(options.expiresAt.getTime())) {
325
+ throw new Error("Invalid expiresAt date.");
326
+ }
327
+ } else if (typeof options.expiresAt === "string") {
328
+ const parsed = new Date(options.expiresAt);
329
+ if (Number.isNaN(parsed.getTime())) {
330
+ throw new Error("Invalid expiresAt date.");
331
+ }
332
+ }
333
+ }
334
+ }
337
335
  function optionsBody(options) {
338
- return toSnakeCase({
336
+ validatePublishOptions(options);
337
+ return {
339
338
  title: options.title,
340
339
  visibility: options.visibility,
341
340
  password: options.password,
342
341
  noindex: options.noindex,
343
342
  expiresAt: options.expiresAt instanceof Date ? options.expiresAt.toISOString() : options.expiresAt
344
- });
343
+ };
345
344
  }
346
345
 
347
346
  // src/publish/upload.ts
@@ -364,25 +363,81 @@ function createErrorResult(code, message, statusCode, extra = {}) {
364
363
  ...extra.detail !== void 0 ? { detail: extra.detail } : {},
365
364
  ...extra.param !== void 0 ? { param: extra.param } : {},
366
365
  ...extra.currentRevision !== void 0 ? { currentRevision: extra.currentRevision } : {},
367
- ...extra.requestId !== void 0 ? { requestId: extra.requestId } : {}
366
+ ...extra.requestId !== void 0 ? { requestId: extra.requestId } : {},
367
+ ...extra.suggestion !== void 0 ? { suggestion: extra.suggestion } : {},
368
+ ...extra.retryable !== void 0 ? { retryable: extra.retryable } : {}
368
369
  },
369
370
  headers: extra.headers ?? {}
370
371
  };
371
372
  }
372
373
 
373
- // src/publish/upload.ts
374
+ // src/publish/multipart.ts
374
375
  var MAX_PART_TARGETS_PER_REQUEST = 100;
376
+ async function uploadMultipartFile(transport, uploadId, fileId, bytes, partSize) {
377
+ if (!partSize || partSize < 1) {
378
+ return createErrorResult(
379
+ "invalid_upload_target",
380
+ "Multipart upload target did not include a valid part size.",
381
+ null
382
+ );
383
+ }
384
+ const partNumbers = Array.from(
385
+ { length: Math.ceil(bytes.byteLength / partSize) },
386
+ (_, index) => index + 1
387
+ );
388
+ const parts = [];
389
+ for (let startIndex = 0; startIndex < partNumbers.length; startIndex += MAX_PART_TARGETS_PER_REQUEST) {
390
+ const partNumberBatch = partNumbers.slice(
391
+ startIndex,
392
+ startIndex + MAX_PART_TARGETS_PER_REQUEST
393
+ );
394
+ const targetsResult = await transport.request(
395
+ "POST",
396
+ `/uploads/${encodeURIComponent(uploadId)}/parts`,
397
+ { body: { fileId, partNumbers: partNumberBatch } }
398
+ );
399
+ if (targetsResult.error) return errorResult(targetsResult);
400
+ for (const target of targetsResult.data.parts) {
401
+ const start = (target.partNumber - 1) * partSize;
402
+ const end = Math.min(start + partSize, bytes.byteLength);
403
+ const uploadResult = await transport.putSignedUrl(
404
+ target.url,
405
+ bytes.slice(start, end),
406
+ target.headers
407
+ );
408
+ if (uploadResult.error) return errorResult(uploadResult);
409
+ if (!uploadResult.data.etag) {
410
+ return createErrorResult(
411
+ "missing_upload_etag",
412
+ "Signed upload response did not include an ETag.",
413
+ null
414
+ );
415
+ }
416
+ parts.push({
417
+ partNumber: target.partNumber,
418
+ etag: uploadResult.data.etag
419
+ });
420
+ }
421
+ }
422
+ return { data: parts, error: null, headers: {} };
423
+ }
424
+ function errorResult(result) {
425
+ if (!result.error) throw new Error("Expected an error result");
426
+ return { data: null, error: result.error, headers: result.headers };
427
+ }
428
+
429
+ // src/publish/upload.ts
375
430
  async function publishStaged(transport, prepared, finalPath, options = {}) {
376
431
  const baseKey = options.idempotencyKey ?? `sdk-${(0, import_node_crypto2.randomUUID)()}`;
377
432
  const createResult = await transport.request(
378
433
  "POST",
379
434
  "/uploads",
380
435
  {
381
- body: toSnakeCase(prepared.manifest),
436
+ body: prepared.manifest,
382
437
  idempotencyKey: `${baseKey}:create-upload`
383
438
  }
384
439
  );
385
- if (createResult.error) return errorResult(createResult);
440
+ if (createResult.error) return errorResult2(createResult);
386
441
  const completedFiles = {};
387
442
  for (const file of prepared.files) {
388
443
  const target = createResult.data.files.find(
@@ -396,14 +451,15 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
396
451
  );
397
452
  }
398
453
  if (target.upload.strategy === "multipart") {
454
+ const bytes2 = await fileBytes(file);
399
455
  const partsResult = await uploadMultipartFile(
400
456
  transport,
401
457
  createResult.data.uploadId,
402
458
  target.fileId,
403
- file,
459
+ bytes2,
404
460
  target.upload.partSize
405
461
  );
406
- if (partsResult.error) return errorResult(partsResult);
462
+ if (partsResult.error) return errorResult2(partsResult);
407
463
  completedFiles[target.fileId] = { parts: partsResult.data };
408
464
  continue;
409
465
  }
@@ -413,20 +469,20 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
413
469
  bytes,
414
470
  target.upload.headers
415
471
  );
416
- if (uploadResult.error) return errorResult(uploadResult);
472
+ if (uploadResult.error) return errorResult2(uploadResult);
417
473
  }
418
474
  const completeResult = await transport.request(
419
475
  "POST",
420
476
  `/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
421
477
  {
422
- body: toSnakeCase({ files: completedFiles }),
478
+ body: { files: completedFiles },
423
479
  idempotencyKey: `${baseKey}:complete-upload`
424
480
  }
425
481
  );
426
- if (completeResult.error) return errorResult(completeResult);
482
+ if (completeResult.error) return errorResult2(completeResult);
427
483
  const finalOptions = {
428
484
  body: {
429
- upload_id: createResult.data.uploadId,
485
+ uploadId: createResult.data.uploadId,
430
486
  options: prepared.options,
431
487
  ...prepared.metadata ? { metadata: prepared.metadata } : {}
432
488
  },
@@ -436,60 +492,11 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
436
492
  finalOptions.ifRevision = options.ifRevision;
437
493
  return transport.request("POST", finalPath, finalOptions);
438
494
  }
439
- async function uploadMultipartFile(transport, uploadId, fileId, file, partSize) {
440
- if (!partSize || partSize < 1) {
441
- return createErrorResult(
442
- "invalid_upload_target",
443
- "Multipart upload target did not include a valid part size.",
444
- null
445
- );
446
- }
447
- const bytes = await fileBytes(file);
448
- const partNumbers = Array.from(
449
- { length: Math.ceil(bytes.byteLength / partSize) },
450
- (_, index) => index + 1
451
- );
452
- const parts = [];
453
- for (let startIndex = 0; startIndex < partNumbers.length; startIndex += MAX_PART_TARGETS_PER_REQUEST) {
454
- const partNumberBatch = partNumbers.slice(
455
- startIndex,
456
- startIndex + MAX_PART_TARGETS_PER_REQUEST
457
- );
458
- const targetsResult = await transport.request(
459
- "POST",
460
- `/uploads/${encodeURIComponent(uploadId)}/parts`,
461
- { body: toSnakeCase({ fileId, partNumbers: partNumberBatch }) }
462
- );
463
- if (targetsResult.error) return errorResult(targetsResult);
464
- for (const target of targetsResult.data.parts) {
465
- const start = (target.partNumber - 1) * partSize;
466
- const end = Math.min(start + partSize, bytes.byteLength);
467
- const uploadResult = await transport.putSignedUrl(
468
- target.url,
469
- bytes.slice(start, end),
470
- target.headers
471
- );
472
- if (uploadResult.error) return errorResult(uploadResult);
473
- if (!uploadResult.data.etag) {
474
- return createErrorResult(
475
- "missing_upload_etag",
476
- "Signed upload response did not include an ETag.",
477
- null
478
- );
479
- }
480
- parts.push({
481
- partNumber: target.partNumber,
482
- etag: uploadResult.data.etag
483
- });
484
- }
485
- }
486
- return { data: parts, error: null, headers: {} };
487
- }
488
495
  async function fileBytes(file) {
489
496
  if (file.source.kind === "bytes") return file.source.bytes;
490
497
  return (0, import_promises3.readFile)(file.source.path);
491
498
  }
492
- function errorResult(result) {
499
+ function errorResult2(result) {
493
500
  if (!result.error) throw new Error("Expected an error result");
494
501
  return { data: null, error: result.error, headers: result.headers };
495
502
  }
@@ -504,9 +511,7 @@ var AccountResource = class {
504
511
  return this.transport.request("GET", "/account");
505
512
  }
506
513
  update(input) {
507
- return this.transport.request("PATCH", "/account", {
508
- body: toSnakeCase(input)
509
- });
514
+ return this.transport.request("PATCH", "/account", { body: input });
510
515
  }
511
516
  delete() {
512
517
  return this.transport.request("DELETE", "/account");
@@ -563,9 +568,7 @@ var DeploymentsResource = class {
563
568
  }
564
569
  transport;
565
570
  create(dropId, body, options = {}) {
566
- const requestOptions = {
567
- body: toSnakeCase(body)
568
- };
571
+ const requestOptions = { body };
569
572
  if (options.idempotencyKey)
570
573
  requestOptions.idempotencyKey = options.idempotencyKey;
571
574
  if (options.ifRevision !== void 0)
@@ -577,7 +580,10 @@ var DeploymentsResource = class {
577
580
  );
578
581
  }
579
582
  createRaw(dropId, body, options = {}) {
580
- const requestOptions = { body };
583
+ const requestOptions = {
584
+ body,
585
+ bodyCase: "raw"
586
+ };
581
587
  if (options.idempotencyKey)
582
588
  requestOptions.idempotencyKey = options.idempotencyKey;
583
589
  if (options.ifRevision !== void 0)
@@ -592,9 +598,7 @@ var DeploymentsResource = class {
592
598
  return this.transport.request(
593
599
  "GET",
594
600
  `/drops/${encodeURIComponent(dropId)}/deployments`,
595
- {
596
- params: toSnakeCase(params)
597
- }
601
+ { params }
598
602
  );
599
603
  }
600
604
  get(dropId, deploymentId) {
@@ -605,7 +609,7 @@ var DeploymentsResource = class {
605
609
  }
606
610
  };
607
611
 
608
- // src/types.ts
612
+ // src/pagination.ts
609
613
  var CursorPage = class {
610
614
  object = "list";
611
615
  data;
@@ -645,22 +649,23 @@ var DropsResource = class {
645
649
  }
646
650
  transport;
647
651
  create(body, options = {}) {
648
- const requestOptions = {
649
- body: toSnakeCase(body)
650
- };
652
+ const requestOptions = { body };
651
653
  if (options.idempotencyKey)
652
654
  requestOptions.idempotencyKey = options.idempotencyKey;
653
655
  return this.transport.request("POST", "/drops", requestOptions);
654
656
  }
655
657
  createRaw(body, options = {}) {
656
- const requestOptions = { body };
658
+ const requestOptions = {
659
+ body,
660
+ bodyCase: "raw"
661
+ };
657
662
  if (options.idempotencyKey)
658
663
  requestOptions.idempotencyKey = options.idempotencyKey;
659
664
  return this.transport.request("POST", "/drops", requestOptions);
660
665
  }
661
666
  async list(params = {}) {
662
667
  const result = await this.transport.request("GET", "/drops", {
663
- params: toSnakeCase(params)
668
+ params
664
669
  });
665
670
  if (result.error) return result;
666
671
  const page = new CursorPage({
@@ -677,21 +682,14 @@ var DropsResource = class {
677
682
  `/drops/${encodeURIComponent(dropId)}`
678
683
  );
679
684
  }
680
- update(dropId, body, options = {}) {
681
- const bodyRecord = body;
682
- if (hasContent(bodyRecord)) {
683
- throw new TypeError(
684
- "drops.update() only patches drop options; use deployments.create() or update() for content"
685
- );
686
- }
685
+ update(dropId, options = {}) {
687
686
  const requestOptions = {
688
- body: updateBody(body)
687
+ body: updateBody(options)
689
688
  };
690
- const bodyOptions = body;
691
- const idempotencyKey = options.idempotencyKey ?? bodyOptions.idempotencyKey;
692
- const ifRevision = options.ifRevision ?? bodyOptions.ifRevision;
693
- if (idempotencyKey) requestOptions.idempotencyKey = idempotencyKey;
694
- if (ifRevision !== void 0) requestOptions.ifRevision = ifRevision;
689
+ if (options.idempotencyKey)
690
+ requestOptions.idempotencyKey = options.idempotencyKey;
691
+ if (options.ifRevision !== void 0)
692
+ requestOptions.ifRevision = options.ifRevision;
695
693
  return this.transport.request(
696
694
  "PATCH",
697
695
  `/drops/${encodeURIComponent(dropId)}`,
@@ -705,24 +703,15 @@ var DropsResource = class {
705
703
  );
706
704
  }
707
705
  };
708
- function updateBody(body) {
706
+ function updateBody(options) {
709
707
  const {
710
708
  metadata,
711
709
  idempotencyKey: _idempotencyKey,
712
710
  ifRevision: _ifRevision,
713
- ignore: _ignore,
714
- ignoreDefaults: _ignoreDefaults,
715
- contentType: _contentType,
716
- path: _path,
717
711
  entry: _entry,
718
712
  ...dropOptions
719
- } = body;
720
- return toSnakeCase({ options: dropOptions, metadata });
721
- }
722
- function hasContent(body) {
723
- return ["content", "contentType", "content_type", "entry", "files"].some(
724
- (field) => body[field] !== void 0
725
- );
713
+ } = options;
714
+ return { options: dropOptions, metadata };
726
715
  }
727
716
 
728
717
  // src/resources/uploads.ts
@@ -732,9 +721,7 @@ var UploadsResource = class {
732
721
  }
733
722
  transport;
734
723
  create(body, options = {}) {
735
- const requestOptions = {
736
- body: toSnakeCase(body)
737
- };
724
+ const requestOptions = { body };
738
725
  if (options.idempotencyKey)
739
726
  requestOptions.idempotencyKey = options.idempotencyKey;
740
727
  return this.transport.request("POST", "/uploads", requestOptions);
@@ -749,13 +736,11 @@ var UploadsResource = class {
749
736
  return this.transport.request(
750
737
  "POST",
751
738
  `/uploads/${encodeURIComponent(uploadId)}/parts`,
752
- { body: toSnakeCase(body) }
739
+ { body }
753
740
  );
754
741
  }
755
742
  complete(uploadId, body, options = {}) {
756
- const requestOptions = {
757
- body: toSnakeCase(body)
758
- };
743
+ const requestOptions = { body };
759
744
  if (options.idempotencyKey)
760
745
  requestOptions.idempotencyKey = options.idempotencyKey;
761
746
  return this.transport.request(
@@ -772,9 +757,42 @@ var UploadsResource = class {
772
757
  }
773
758
  };
774
759
 
760
+ // src/case.ts
761
+ function toCamelKey(key) {
762
+ return key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
763
+ }
764
+ function toSnakeKey(key) {
765
+ return key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
766
+ }
767
+ var PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["metadata"]);
768
+ function toCamelCase(value) {
769
+ if (Array.isArray(value)) return value.map(toCamelCase);
770
+ if (value && typeof value === "object") {
771
+ return Object.fromEntries(
772
+ Object.entries(value).map(([key, item]) => [
773
+ toCamelKey(key),
774
+ PASSTHROUGH_KEYS.has(key) ? item : toCamelCase(item)
775
+ ])
776
+ );
777
+ }
778
+ return value;
779
+ }
780
+ function toSnakeCase(value) {
781
+ if (Array.isArray(value)) return value.map(toSnakeCase);
782
+ if (value && typeof value === "object") {
783
+ return Object.fromEntries(
784
+ Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [
785
+ toSnakeKey(key),
786
+ PASSTHROUGH_KEYS.has(key) ? item : toSnakeCase(item)
787
+ ])
788
+ );
789
+ }
790
+ return value;
791
+ }
792
+
775
793
  // src/transport.ts
776
794
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
777
- var SDK_VERSION = "0.1.0";
795
+ var SDK_VERSION = "0.3.0";
778
796
  var Transport = class {
779
797
  apiKey;
780
798
  baseUrl;
@@ -851,7 +869,8 @@ var Transport = class {
851
869
  if (options.body !== void 0 && !(options.body instanceof FormData))
852
870
  headers["content-type"] = "application/json";
853
871
  const url = new URL(`${this.baseUrl}${path}`);
854
- for (const [key, value] of Object.entries(options.params ?? {})) {
872
+ const wireParams = options.bodyCase === "raw" ? options.params : toSnakeCase(options.params ?? {});
873
+ for (const [key, value] of Object.entries(wireParams ?? {})) {
855
874
  if (value !== void 0 && value !== null)
856
875
  url.searchParams.set(key, String(value));
857
876
  }
@@ -869,7 +888,8 @@ var Transport = class {
869
888
  if (options.body instanceof FormData) {
870
889
  request.body = options.body;
871
890
  } else if (options.body !== void 0) {
872
- request.body = JSON.stringify(options.body);
891
+ const wireBody = options.bodyCase === "raw" ? options.body : toSnakeCase(options.body);
892
+ request.body = JSON.stringify(wireBody);
873
893
  }
874
894
  const response = await this.fetchImpl(url.toString(), request);
875
895
  const responseHeaders = headersToObject(response.headers);
@@ -884,7 +904,9 @@ var Transport = class {
884
904
  detail: body,
885
905
  param: stringValue(body.param),
886
906
  ...currentRevision !== void 0 ? { currentRevision } : {},
887
- requestId: responseHeaders["x-request-id"] ?? null,
907
+ requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
908
+ suggestion: stringValue(body.suggestion),
909
+ retryable: booleanValue(body.retryable),
888
910
  headers: responseHeaders
889
911
  });
890
912
  }
@@ -926,6 +948,9 @@ function stringValue(value) {
926
948
  function numberValue(value) {
927
949
  return typeof value === "number" ? value : void 0;
928
950
  }
951
+ function booleanValue(value) {
952
+ return typeof value === "boolean" ? value : null;
953
+ }
929
954
 
930
955
  // src/dropthis.ts
931
956
  var Dropthis = class {
@@ -976,31 +1001,27 @@ var Dropthis = class {
976
1001
  const prepared = await preparePublishRequest(input, options);
977
1002
  return publishStaged(this.transport, prepared, "/drops", options);
978
1003
  }
979
- async update(dropId, inputOrOptions = {}, options = {}) {
980
- if (looksLikeOptionsOnly(inputOrOptions)) {
981
- return this.drops.update(dropId, inputOrOptions, options);
982
- }
983
- const prepared = await preparePublishRequest(
984
- inputOrOptions,
985
- options
986
- );
1004
+ async deploy(dropId, input, options = {}) {
1005
+ const prepared = await preparePublishRequest(input, options);
987
1006
  const path = `/drops/${encodeURIComponent(dropId)}/deployments`;
988
1007
  return publishStaged(this.transport, prepared, path, options);
989
1008
  }
1009
+ async update(dropId, options = {}) {
1010
+ return this.drops.update(dropId, options);
1011
+ }
990
1012
  request(method, path, options = {}) {
991
- return this.transport.request(method, path, options);
1013
+ return this.transport.request(method, path, {
1014
+ ...options,
1015
+ bodyCase: "raw"
1016
+ });
992
1017
  }
993
1018
  requestSignedUpload(url, bytes, headers) {
994
1019
  return this.transport.putSignedUrl(url, bytes, headers);
995
1020
  }
996
1021
  };
997
- function looksLikeOptionsOnly(input) {
998
- if (input instanceof Uint8Array) return false;
999
- if (typeof input !== "object" || input === null) return false;
1000
- return !("content" in input || "files" in input);
1001
- }
1002
1022
  // Annotate the CommonJS export names for ESM import in node:
1003
1023
  0 && (module.exports = {
1024
+ CursorPage,
1004
1025
  DeploymentsResource,
1005
1026
  Dropthis,
1006
1027
  UploadsResource,