@dropthis/cli 0.3.4 → 0.4.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.
@@ -2,35 +2,6 @@
2
2
  import { stat as stat2 } from "fs/promises";
3
3
  import { basename as basename2 } from "path";
4
4
 
5
- // src/case.ts
6
- function toCamelKey(key) {
7
- return key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
8
- }
9
- function toSnakeKey(key) {
10
- return key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
11
- }
12
- function toCamelCase(value) {
13
- if (Array.isArray(value)) return value.map(toCamelCase);
14
- if (value && typeof value === "object") {
15
- return Object.fromEntries(
16
- Object.entries(value).map(([key, item]) => [
17
- toCamelKey(key),
18
- toCamelCase(item)
19
- ])
20
- );
21
- }
22
- return value;
23
- }
24
- function toSnakeCase(value) {
25
- if (Array.isArray(value)) return value.map(toSnakeCase);
26
- if (value && typeof value === "object") {
27
- return Object.fromEntries(
28
- Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [toSnakeKey(key), toSnakeCase(item)])
29
- );
30
- }
31
- return value;
32
- }
33
-
34
5
  // src/publish/checksum.ts
35
6
  import { createHash } from "crypto";
36
7
  import { createReadStream } from "fs";
@@ -294,14 +265,41 @@ function explicitFileBytes(file) {
294
265
  if (file.bytes !== void 0) return file.bytes;
295
266
  throw new TypeError(`Explicit file ${file.path} is missing content bytes`);
296
267
  }
268
+ var VALID_VISIBILITIES = ["public", "unlisted"];
269
+ var MAX_TITLE_LENGTH = 500;
270
+ function validatePublishOptions(options) {
271
+ if (options.title !== void 0 && options.title !== null && options.title.length > MAX_TITLE_LENGTH) {
272
+ throw new Error(`Title must be ${MAX_TITLE_LENGTH} characters or less.`);
273
+ }
274
+ if (options.visibility !== void 0 && options.visibility !== null) {
275
+ if (!VALID_VISIBILITIES.includes(options.visibility)) {
276
+ throw new Error(
277
+ `Invalid visibility "${options.visibility}". Use "public" or "unlisted".`
278
+ );
279
+ }
280
+ }
281
+ if (options.expiresAt !== void 0 && options.expiresAt !== null) {
282
+ if (options.expiresAt instanceof Date) {
283
+ if (Number.isNaN(options.expiresAt.getTime())) {
284
+ throw new Error("Invalid expiresAt date.");
285
+ }
286
+ } else if (typeof options.expiresAt === "string") {
287
+ const parsed = new Date(options.expiresAt);
288
+ if (Number.isNaN(parsed.getTime())) {
289
+ throw new Error("Invalid expiresAt date.");
290
+ }
291
+ }
292
+ }
293
+ }
297
294
  function optionsBody(options) {
298
- return toSnakeCase({
295
+ validatePublishOptions(options);
296
+ return {
299
297
  title: options.title,
300
298
  visibility: options.visibility,
301
299
  password: options.password,
302
300
  noindex: options.noindex,
303
301
  expiresAt: options.expiresAt instanceof Date ? options.expiresAt.toISOString() : options.expiresAt
304
- });
302
+ };
305
303
  }
306
304
 
307
305
  // src/publish/upload.ts
@@ -324,25 +322,81 @@ function createErrorResult(code, message, statusCode, extra = {}) {
324
322
  ...extra.detail !== void 0 ? { detail: extra.detail } : {},
325
323
  ...extra.param !== void 0 ? { param: extra.param } : {},
326
324
  ...extra.currentRevision !== void 0 ? { currentRevision: extra.currentRevision } : {},
327
- ...extra.requestId !== void 0 ? { requestId: extra.requestId } : {}
325
+ ...extra.requestId !== void 0 ? { requestId: extra.requestId } : {},
326
+ ...extra.suggestion !== void 0 ? { suggestion: extra.suggestion } : {},
327
+ ...extra.retryable !== void 0 ? { retryable: extra.retryable } : {}
328
328
  },
329
329
  headers: extra.headers ?? {}
330
330
  };
331
331
  }
332
332
 
333
- // src/publish/upload.ts
333
+ // src/publish/multipart.ts
334
334
  var MAX_PART_TARGETS_PER_REQUEST = 100;
335
+ async function uploadMultipartFile(transport, uploadId, fileId, bytes, partSize) {
336
+ if (!partSize || partSize < 1) {
337
+ return createErrorResult(
338
+ "invalid_upload_target",
339
+ "Multipart upload target did not include a valid part size.",
340
+ null
341
+ );
342
+ }
343
+ const partNumbers = Array.from(
344
+ { length: Math.ceil(bytes.byteLength / partSize) },
345
+ (_, index) => index + 1
346
+ );
347
+ const parts = [];
348
+ for (let startIndex = 0; startIndex < partNumbers.length; startIndex += MAX_PART_TARGETS_PER_REQUEST) {
349
+ const partNumberBatch = partNumbers.slice(
350
+ startIndex,
351
+ startIndex + MAX_PART_TARGETS_PER_REQUEST
352
+ );
353
+ const targetsResult = await transport.request(
354
+ "POST",
355
+ `/uploads/${encodeURIComponent(uploadId)}/parts`,
356
+ { body: { fileId, partNumbers: partNumberBatch } }
357
+ );
358
+ if (targetsResult.error) return errorResult(targetsResult);
359
+ for (const target of targetsResult.data.parts) {
360
+ const start = (target.partNumber - 1) * partSize;
361
+ const end = Math.min(start + partSize, bytes.byteLength);
362
+ const uploadResult = await transport.putSignedUrl(
363
+ target.url,
364
+ bytes.slice(start, end),
365
+ target.headers
366
+ );
367
+ if (uploadResult.error) return errorResult(uploadResult);
368
+ if (!uploadResult.data.etag) {
369
+ return createErrorResult(
370
+ "missing_upload_etag",
371
+ "Signed upload response did not include an ETag.",
372
+ null
373
+ );
374
+ }
375
+ parts.push({
376
+ partNumber: target.partNumber,
377
+ etag: uploadResult.data.etag
378
+ });
379
+ }
380
+ }
381
+ return { data: parts, error: null, headers: {} };
382
+ }
383
+ function errorResult(result) {
384
+ if (!result.error) throw new Error("Expected an error result");
385
+ return { data: null, error: result.error, headers: result.headers };
386
+ }
387
+
388
+ // src/publish/upload.ts
335
389
  async function publishStaged(transport, prepared, finalPath, options = {}) {
336
390
  const baseKey = options.idempotencyKey ?? `sdk-${randomUUID()}`;
337
391
  const createResult = await transport.request(
338
392
  "POST",
339
393
  "/uploads",
340
394
  {
341
- body: toSnakeCase(prepared.manifest),
395
+ body: prepared.manifest,
342
396
  idempotencyKey: `${baseKey}:create-upload`
343
397
  }
344
398
  );
345
- if (createResult.error) return errorResult(createResult);
399
+ if (createResult.error) return errorResult2(createResult);
346
400
  const completedFiles = {};
347
401
  for (const file of prepared.files) {
348
402
  const target = createResult.data.files.find(
@@ -356,14 +410,15 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
356
410
  );
357
411
  }
358
412
  if (target.upload.strategy === "multipart") {
413
+ const bytes2 = await fileBytes(file);
359
414
  const partsResult = await uploadMultipartFile(
360
415
  transport,
361
416
  createResult.data.uploadId,
362
417
  target.fileId,
363
- file,
418
+ bytes2,
364
419
  target.upload.partSize
365
420
  );
366
- if (partsResult.error) return errorResult(partsResult);
421
+ if (partsResult.error) return errorResult2(partsResult);
367
422
  completedFiles[target.fileId] = { parts: partsResult.data };
368
423
  continue;
369
424
  }
@@ -373,20 +428,20 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
373
428
  bytes,
374
429
  target.upload.headers
375
430
  );
376
- if (uploadResult.error) return errorResult(uploadResult);
431
+ if (uploadResult.error) return errorResult2(uploadResult);
377
432
  }
378
433
  const completeResult = await transport.request(
379
434
  "POST",
380
435
  `/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
381
436
  {
382
- body: toSnakeCase({ files: completedFiles }),
437
+ body: { files: completedFiles },
383
438
  idempotencyKey: `${baseKey}:complete-upload`
384
439
  }
385
440
  );
386
- if (completeResult.error) return errorResult(completeResult);
441
+ if (completeResult.error) return errorResult2(completeResult);
387
442
  const finalOptions = {
388
443
  body: {
389
- upload_id: createResult.data.uploadId,
444
+ uploadId: createResult.data.uploadId,
390
445
  options: prepared.options,
391
446
  ...prepared.metadata ? { metadata: prepared.metadata } : {}
392
447
  },
@@ -396,60 +451,11 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
396
451
  finalOptions.ifRevision = options.ifRevision;
397
452
  return transport.request("POST", finalPath, finalOptions);
398
453
  }
399
- async function uploadMultipartFile(transport, uploadId, fileId, file, partSize) {
400
- if (!partSize || partSize < 1) {
401
- return createErrorResult(
402
- "invalid_upload_target",
403
- "Multipart upload target did not include a valid part size.",
404
- null
405
- );
406
- }
407
- const bytes = await fileBytes(file);
408
- const partNumbers = Array.from(
409
- { length: Math.ceil(bytes.byteLength / partSize) },
410
- (_, index) => index + 1
411
- );
412
- const parts = [];
413
- for (let startIndex = 0; startIndex < partNumbers.length; startIndex += MAX_PART_TARGETS_PER_REQUEST) {
414
- const partNumberBatch = partNumbers.slice(
415
- startIndex,
416
- startIndex + MAX_PART_TARGETS_PER_REQUEST
417
- );
418
- const targetsResult = await transport.request(
419
- "POST",
420
- `/uploads/${encodeURIComponent(uploadId)}/parts`,
421
- { body: toSnakeCase({ fileId, partNumbers: partNumberBatch }) }
422
- );
423
- if (targetsResult.error) return errorResult(targetsResult);
424
- for (const target of targetsResult.data.parts) {
425
- const start = (target.partNumber - 1) * partSize;
426
- const end = Math.min(start + partSize, bytes.byteLength);
427
- const uploadResult = await transport.putSignedUrl(
428
- target.url,
429
- bytes.slice(start, end),
430
- target.headers
431
- );
432
- if (uploadResult.error) return errorResult(uploadResult);
433
- if (!uploadResult.data.etag) {
434
- return createErrorResult(
435
- "missing_upload_etag",
436
- "Signed upload response did not include an ETag.",
437
- null
438
- );
439
- }
440
- parts.push({
441
- partNumber: target.partNumber,
442
- etag: uploadResult.data.etag
443
- });
444
- }
445
- }
446
- return { data: parts, error: null, headers: {} };
447
- }
448
454
  async function fileBytes(file) {
449
455
  if (file.source.kind === "bytes") return file.source.bytes;
450
456
  return readFile2(file.source.path);
451
457
  }
452
- function errorResult(result) {
458
+ function errorResult2(result) {
453
459
  if (!result.error) throw new Error("Expected an error result");
454
460
  return { data: null, error: result.error, headers: result.headers };
455
461
  }
@@ -464,9 +470,7 @@ var AccountResource = class {
464
470
  return this.transport.request("GET", "/account");
465
471
  }
466
472
  update(input) {
467
- return this.transport.request("PATCH", "/account", {
468
- body: toSnakeCase(input)
469
- });
473
+ return this.transport.request("PATCH", "/account", { body: input });
470
474
  }
471
475
  delete() {
472
476
  return this.transport.request("DELETE", "/account");
@@ -523,9 +527,7 @@ var DeploymentsResource = class {
523
527
  }
524
528
  transport;
525
529
  create(dropId, body, options = {}) {
526
- const requestOptions = {
527
- body: toSnakeCase(body)
528
- };
530
+ const requestOptions = { body };
529
531
  if (options.idempotencyKey)
530
532
  requestOptions.idempotencyKey = options.idempotencyKey;
531
533
  if (options.ifRevision !== void 0)
@@ -537,7 +539,10 @@ var DeploymentsResource = class {
537
539
  );
538
540
  }
539
541
  createRaw(dropId, body, options = {}) {
540
- const requestOptions = { body };
542
+ const requestOptions = {
543
+ body,
544
+ bodyCase: "raw"
545
+ };
541
546
  if (options.idempotencyKey)
542
547
  requestOptions.idempotencyKey = options.idempotencyKey;
543
548
  if (options.ifRevision !== void 0)
@@ -552,9 +557,7 @@ var DeploymentsResource = class {
552
557
  return this.transport.request(
553
558
  "GET",
554
559
  `/drops/${encodeURIComponent(dropId)}/deployments`,
555
- {
556
- params: toSnakeCase(params)
557
- }
560
+ { params }
558
561
  );
559
562
  }
560
563
  get(dropId, deploymentId) {
@@ -565,7 +568,7 @@ var DeploymentsResource = class {
565
568
  }
566
569
  };
567
570
 
568
- // src/types.ts
571
+ // src/pagination.ts
569
572
  var CursorPage = class {
570
573
  object = "list";
571
574
  data;
@@ -605,22 +608,23 @@ var DropsResource = class {
605
608
  }
606
609
  transport;
607
610
  create(body, options = {}) {
608
- const requestOptions = {
609
- body: toSnakeCase(body)
610
- };
611
+ const requestOptions = { body };
611
612
  if (options.idempotencyKey)
612
613
  requestOptions.idempotencyKey = options.idempotencyKey;
613
614
  return this.transport.request("POST", "/drops", requestOptions);
614
615
  }
615
616
  createRaw(body, options = {}) {
616
- const requestOptions = { body };
617
+ const requestOptions = {
618
+ body,
619
+ bodyCase: "raw"
620
+ };
617
621
  if (options.idempotencyKey)
618
622
  requestOptions.idempotencyKey = options.idempotencyKey;
619
623
  return this.transport.request("POST", "/drops", requestOptions);
620
624
  }
621
625
  async list(params = {}) {
622
626
  const result = await this.transport.request("GET", "/drops", {
623
- params: toSnakeCase(params)
627
+ params
624
628
  });
625
629
  if (result.error) return result;
626
630
  const page = new CursorPage({
@@ -637,21 +641,14 @@ var DropsResource = class {
637
641
  `/drops/${encodeURIComponent(dropId)}`
638
642
  );
639
643
  }
640
- update(dropId, body, options = {}) {
641
- const bodyRecord = body;
642
- if (hasContent(bodyRecord)) {
643
- throw new TypeError(
644
- "drops.update() only patches drop options; use deployments.create() or update() for content"
645
- );
646
- }
644
+ update(dropId, options = {}) {
647
645
  const requestOptions = {
648
- body: updateBody(body)
646
+ body: updateBody(options)
649
647
  };
650
- const bodyOptions = body;
651
- const idempotencyKey = options.idempotencyKey ?? bodyOptions.idempotencyKey;
652
- const ifRevision = options.ifRevision ?? bodyOptions.ifRevision;
653
- if (idempotencyKey) requestOptions.idempotencyKey = idempotencyKey;
654
- if (ifRevision !== void 0) requestOptions.ifRevision = ifRevision;
648
+ if (options.idempotencyKey)
649
+ requestOptions.idempotencyKey = options.idempotencyKey;
650
+ if (options.ifRevision !== void 0)
651
+ requestOptions.ifRevision = options.ifRevision;
655
652
  return this.transport.request(
656
653
  "PATCH",
657
654
  `/drops/${encodeURIComponent(dropId)}`,
@@ -665,24 +662,15 @@ var DropsResource = class {
665
662
  );
666
663
  }
667
664
  };
668
- function updateBody(body) {
665
+ function updateBody(options) {
669
666
  const {
670
667
  metadata,
671
668
  idempotencyKey: _idempotencyKey,
672
669
  ifRevision: _ifRevision,
673
- ignore: _ignore,
674
- ignoreDefaults: _ignoreDefaults,
675
- contentType: _contentType,
676
- path: _path,
677
670
  entry: _entry,
678
671
  ...dropOptions
679
- } = body;
680
- return toSnakeCase({ options: dropOptions, metadata });
681
- }
682
- function hasContent(body) {
683
- return ["content", "contentType", "content_type", "entry", "files"].some(
684
- (field) => body[field] !== void 0
685
- );
672
+ } = options;
673
+ return { options: dropOptions, metadata };
686
674
  }
687
675
 
688
676
  // src/resources/uploads.ts
@@ -692,9 +680,7 @@ var UploadsResource = class {
692
680
  }
693
681
  transport;
694
682
  create(body, options = {}) {
695
- const requestOptions = {
696
- body: toSnakeCase(body)
697
- };
683
+ const requestOptions = { body };
698
684
  if (options.idempotencyKey)
699
685
  requestOptions.idempotencyKey = options.idempotencyKey;
700
686
  return this.transport.request("POST", "/uploads", requestOptions);
@@ -709,13 +695,11 @@ var UploadsResource = class {
709
695
  return this.transport.request(
710
696
  "POST",
711
697
  `/uploads/${encodeURIComponent(uploadId)}/parts`,
712
- { body: toSnakeCase(body) }
698
+ { body }
713
699
  );
714
700
  }
715
701
  complete(uploadId, body, options = {}) {
716
- const requestOptions = {
717
- body: toSnakeCase(body)
718
- };
702
+ const requestOptions = { body };
719
703
  if (options.idempotencyKey)
720
704
  requestOptions.idempotencyKey = options.idempotencyKey;
721
705
  return this.transport.request(
@@ -732,9 +716,42 @@ var UploadsResource = class {
732
716
  }
733
717
  };
734
718
 
719
+ // src/case.ts
720
+ function toCamelKey(key) {
721
+ return key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
722
+ }
723
+ function toSnakeKey(key) {
724
+ return key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
725
+ }
726
+ var PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["metadata"]);
727
+ function toCamelCase(value) {
728
+ if (Array.isArray(value)) return value.map(toCamelCase);
729
+ if (value && typeof value === "object") {
730
+ return Object.fromEntries(
731
+ Object.entries(value).map(([key, item]) => [
732
+ toCamelKey(key),
733
+ PASSTHROUGH_KEYS.has(key) ? item : toCamelCase(item)
734
+ ])
735
+ );
736
+ }
737
+ return value;
738
+ }
739
+ function toSnakeCase(value) {
740
+ if (Array.isArray(value)) return value.map(toSnakeCase);
741
+ if (value && typeof value === "object") {
742
+ return Object.fromEntries(
743
+ Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [
744
+ toSnakeKey(key),
745
+ PASSTHROUGH_KEYS.has(key) ? item : toSnakeCase(item)
746
+ ])
747
+ );
748
+ }
749
+ return value;
750
+ }
751
+
735
752
  // src/transport.ts
736
753
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
737
- var SDK_VERSION = "0.1.0";
754
+ var SDK_VERSION = "0.3.0";
738
755
  var Transport = class {
739
756
  apiKey;
740
757
  baseUrl;
@@ -811,7 +828,8 @@ var Transport = class {
811
828
  if (options.body !== void 0 && !(options.body instanceof FormData))
812
829
  headers["content-type"] = "application/json";
813
830
  const url = new URL(`${this.baseUrl}${path}`);
814
- for (const [key, value] of Object.entries(options.params ?? {})) {
831
+ const wireParams = options.bodyCase === "raw" ? options.params : toSnakeCase(options.params ?? {});
832
+ for (const [key, value] of Object.entries(wireParams ?? {})) {
815
833
  if (value !== void 0 && value !== null)
816
834
  url.searchParams.set(key, String(value));
817
835
  }
@@ -829,7 +847,8 @@ var Transport = class {
829
847
  if (options.body instanceof FormData) {
830
848
  request.body = options.body;
831
849
  } else if (options.body !== void 0) {
832
- request.body = JSON.stringify(options.body);
850
+ const wireBody = options.bodyCase === "raw" ? options.body : toSnakeCase(options.body);
851
+ request.body = JSON.stringify(wireBody);
833
852
  }
834
853
  const response = await this.fetchImpl(url.toString(), request);
835
854
  const responseHeaders = headersToObject(response.headers);
@@ -844,7 +863,9 @@ var Transport = class {
844
863
  detail: body,
845
864
  param: stringValue(body.param),
846
865
  ...currentRevision !== void 0 ? { currentRevision } : {},
847
- requestId: responseHeaders["x-request-id"] ?? null,
866
+ requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
867
+ suggestion: stringValue(body.suggestion),
868
+ retryable: booleanValue(body.retryable),
848
869
  headers: responseHeaders
849
870
  });
850
871
  }
@@ -886,6 +907,9 @@ function stringValue(value) {
886
907
  function numberValue(value) {
887
908
  return typeof value === "number" ? value : void 0;
888
909
  }
910
+ function booleanValue(value) {
911
+ return typeof value === "boolean" ? value : null;
912
+ }
889
913
 
890
914
  // src/dropthis.ts
891
915
  var Dropthis = class {
@@ -936,30 +960,26 @@ var Dropthis = class {
936
960
  const prepared = await preparePublishRequest(input, options);
937
961
  return publishStaged(this.transport, prepared, "/drops", options);
938
962
  }
939
- async update(dropId, inputOrOptions = {}, options = {}) {
940
- if (looksLikeOptionsOnly(inputOrOptions)) {
941
- return this.drops.update(dropId, inputOrOptions, options);
942
- }
943
- const prepared = await preparePublishRequest(
944
- inputOrOptions,
945
- options
946
- );
963
+ async deploy(dropId, input, options = {}) {
964
+ const prepared = await preparePublishRequest(input, options);
947
965
  const path = `/drops/${encodeURIComponent(dropId)}/deployments`;
948
966
  return publishStaged(this.transport, prepared, path, options);
949
967
  }
968
+ async update(dropId, options = {}) {
969
+ return this.drops.update(dropId, options);
970
+ }
950
971
  request(method, path, options = {}) {
951
- return this.transport.request(method, path, options);
972
+ return this.transport.request(method, path, {
973
+ ...options,
974
+ bodyCase: "raw"
975
+ });
952
976
  }
953
977
  requestSignedUpload(url, bytes, headers) {
954
978
  return this.transport.putSignedUrl(url, bytes, headers);
955
979
  }
956
980
  };
957
- function looksLikeOptionsOnly(input) {
958
- if (input instanceof Uint8Array) return false;
959
- if (typeof input !== "object" || input === null) return false;
960
- return !("content" in input || "files" in input);
961
- }
962
981
  export {
982
+ CursorPage,
963
983
  DeploymentsResource,
964
984
  Dropthis,
965
985
  UploadsResource,