@dropthis/cli 0.9.3 → 0.10.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/README.md +39 -2
- package/dist/cli.cjs +147 -17
- package/dist/cli.cjs.map +1 -1
- package/node_modules/@dropthis/node/README.md +99 -7
- package/node_modules/@dropthis/node/dist/{drops-BWA0D1IW.d.cts → drops-D0MLPPF9.d.cts} +106 -15
- package/node_modules/@dropthis/node/dist/{drops-BWA0D1IW.d.ts → drops-D0MLPPF9.d.ts} +106 -15
- package/node_modules/@dropthis/node/dist/edge.cjs +219 -38
- package/node_modules/@dropthis/node/dist/edge.cjs.map +1 -1
- package/node_modules/@dropthis/node/dist/edge.d.cts +1 -1
- package/node_modules/@dropthis/node/dist/edge.d.ts +1 -1
- package/node_modules/@dropthis/node/dist/edge.mjs +219 -38
- package/node_modules/@dropthis/node/dist/edge.mjs.map +1 -1
- package/node_modules/@dropthis/node/dist/index.cjs +226 -40
- package/node_modules/@dropthis/node/dist/index.cjs.map +1 -1
- package/node_modules/@dropthis/node/dist/index.d.cts +10 -7
- package/node_modules/@dropthis/node/dist/index.d.ts +10 -7
- package/node_modules/@dropthis/node/dist/index.mjs +226 -40
- package/node_modules/@dropthis/node/dist/index.mjs.map +1 -1
- package/node_modules/@dropthis/node/package.json +1 -1
- package/package.json +2 -2
|
@@ -307,10 +307,49 @@ async function resolveInMemory(input, options = {}) {
|
|
|
307
307
|
});
|
|
308
308
|
return buildStagedRequest(files, options, input.entry);
|
|
309
309
|
}
|
|
310
|
+
var UPLOAD_CONCURRENCY = 5;
|
|
310
311
|
function errorResult(result) {
|
|
311
312
|
if (!result.error) throw new Error("Expected an error result");
|
|
312
313
|
return { data: null, error: result.error, headers: result.headers };
|
|
313
314
|
}
|
|
315
|
+
async function uploadStagedFiles(transport, jobs) {
|
|
316
|
+
let nextIndex = 0;
|
|
317
|
+
let failed = false;
|
|
318
|
+
const failures = /* @__PURE__ */ new Map();
|
|
319
|
+
async function worker() {
|
|
320
|
+
while (!failed) {
|
|
321
|
+
const index = nextIndex;
|
|
322
|
+
nextIndex += 1;
|
|
323
|
+
if (index >= jobs.length) return;
|
|
324
|
+
const job = jobs[index];
|
|
325
|
+
if (!job) return;
|
|
326
|
+
const put = await transport.putSignedUrl(
|
|
327
|
+
job.target.upload.url,
|
|
328
|
+
await job.file.getBody(),
|
|
329
|
+
job.target.upload.headers
|
|
330
|
+
);
|
|
331
|
+
if (put.error) {
|
|
332
|
+
failed = true;
|
|
333
|
+
failures.set(index, {
|
|
334
|
+
data: null,
|
|
335
|
+
error: {
|
|
336
|
+
...put.error,
|
|
337
|
+
message: `Upload failed for ${job.file.path}: ${put.error.message}`
|
|
338
|
+
},
|
|
339
|
+
headers: put.headers
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const workers = Array.from(
|
|
345
|
+
{ length: Math.min(UPLOAD_CONCURRENCY, jobs.length) },
|
|
346
|
+
() => worker()
|
|
347
|
+
);
|
|
348
|
+
await Promise.all(workers);
|
|
349
|
+
if (failures.size === 0) return null;
|
|
350
|
+
const firstIndex = Math.min(...failures.keys());
|
|
351
|
+
return failures.get(firstIndex) ?? null;
|
|
352
|
+
}
|
|
314
353
|
async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
315
354
|
const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
|
|
316
355
|
const createResult = await transport.request(
|
|
@@ -322,6 +361,7 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
322
361
|
}
|
|
323
362
|
);
|
|
324
363
|
if (createResult.error) return errorResult(createResult);
|
|
364
|
+
const jobs = [];
|
|
325
365
|
for (const file of prepared.files) {
|
|
326
366
|
const target = createResult.data.files.find((f) => f.path === file.path);
|
|
327
367
|
if (!target) {
|
|
@@ -334,22 +374,18 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
334
374
|
if (target.upload.strategy !== "single_put") {
|
|
335
375
|
return createErrorResult(
|
|
336
376
|
"unsupported_upload_strategy",
|
|
337
|
-
`
|
|
377
|
+
`Unexpected upload strategy "${target.upload.strategy}" for ${file.path}. The dropthis upload contract is one signed PUT per file; update @dropthis/node.`,
|
|
338
378
|
null
|
|
339
379
|
);
|
|
340
380
|
}
|
|
341
|
-
|
|
342
|
-
target.upload.url,
|
|
343
|
-
await file.getBody(),
|
|
344
|
-
target.upload.headers
|
|
345
|
-
);
|
|
346
|
-
if (put.error) return errorResult(put);
|
|
381
|
+
jobs.push({ file, target });
|
|
347
382
|
}
|
|
383
|
+
const uploadFailure = await uploadStagedFiles(transport, jobs);
|
|
384
|
+
if (uploadFailure) return uploadFailure;
|
|
348
385
|
const completeResult = await transport.request(
|
|
349
386
|
"POST",
|
|
350
387
|
`/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
|
|
351
388
|
{
|
|
352
|
-
body: { files: {} },
|
|
353
389
|
idempotencyKey: `${baseKey}:complete-upload`
|
|
354
390
|
}
|
|
355
391
|
);
|
|
@@ -408,6 +444,7 @@ var ApiKeysResource = class {
|
|
|
408
444
|
create(input) {
|
|
409
445
|
return this.transport.request("POST", "/api-keys", { body: input });
|
|
410
446
|
}
|
|
447
|
+
/** Revoke an API key. 204 No Content — data is null on success. */
|
|
411
448
|
delete(keyId) {
|
|
412
449
|
return this.transport.request(
|
|
413
450
|
"DELETE",
|
|
@@ -472,12 +509,29 @@ var CursorPage = class {
|
|
|
472
509
|
|
|
473
510
|
// src/resources/drops.ts
|
|
474
511
|
var DropsResource = class {
|
|
475
|
-
constructor(transport,
|
|
512
|
+
constructor(transport, resolveInput) {
|
|
476
513
|
this.transport = transport;
|
|
477
|
-
this.
|
|
514
|
+
this.resolveInput = resolveInput;
|
|
478
515
|
}
|
|
479
516
|
transport;
|
|
480
|
-
|
|
517
|
+
resolveInput;
|
|
518
|
+
parentHosts;
|
|
519
|
+
/**
|
|
520
|
+
* Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
|
|
521
|
+
* viewer domain plus the configured baseUrl's host (covers staging/self-hosted
|
|
522
|
+
* setups where drops are served under the API's own domain).
|
|
523
|
+
*/
|
|
524
|
+
allowedParentHosts() {
|
|
525
|
+
if (!this.parentHosts) {
|
|
526
|
+
const hosts = /* @__PURE__ */ new Set([CANONICAL_VIEWER_HOST]);
|
|
527
|
+
try {
|
|
528
|
+
hosts.add(new URL(this.transport.baseUrl).hostname);
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
this.parentHosts = hosts;
|
|
532
|
+
}
|
|
533
|
+
return this.parentHosts;
|
|
534
|
+
}
|
|
481
535
|
/**
|
|
482
536
|
* Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
|
|
483
537
|
* Use to publish / share / post / put online / make public a report, dashboard, site, or file.
|
|
@@ -488,7 +542,7 @@ var DropsResource = class {
|
|
|
488
542
|
async publish(input, options = {}) {
|
|
489
543
|
let prepared;
|
|
490
544
|
try {
|
|
491
|
-
prepared = await this.
|
|
545
|
+
prepared = await this.resolveInput(input, options);
|
|
492
546
|
} catch (e) {
|
|
493
547
|
return toErrorResult(e);
|
|
494
548
|
}
|
|
@@ -505,7 +559,7 @@ var DropsResource = class {
|
|
|
505
559
|
const opts = updateContentOptions(options);
|
|
506
560
|
let prepared;
|
|
507
561
|
try {
|
|
508
|
-
prepared = await this.
|
|
562
|
+
prepared = await this.resolveInput(input, opts);
|
|
509
563
|
} catch (e) {
|
|
510
564
|
return toErrorResult(e);
|
|
511
565
|
}
|
|
@@ -533,6 +587,55 @@ var DropsResource = class {
|
|
|
533
587
|
`/drops/${encodeURIComponent(dropId)}`
|
|
534
588
|
);
|
|
535
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Resolve a drop URL (or bare slug) back to the drop — the way to recover a lost
|
|
592
|
+
* `drop_…` id. The slug is parsed client-side from the first hostname label of a
|
|
593
|
+
* dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
|
|
594
|
+
* the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
|
|
595
|
+
* Returns the drop, or `data: null` when no drop of yours has that slug.
|
|
596
|
+
* Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
|
|
597
|
+
* not resolvable yet.
|
|
598
|
+
*/
|
|
599
|
+
async resolve(urlOrSlug) {
|
|
600
|
+
const slug = parseSlug(urlOrSlug, this.allowedParentHosts());
|
|
601
|
+
if (slug === null) {
|
|
602
|
+
return createErrorResult(
|
|
603
|
+
"invalid_drop_url",
|
|
604
|
+
`Could not resolve a drop slug from "${urlOrSlug}". Pass a canonical drop URL like https://<slug>.dropthis.app/ or the bare slug \u2014 custom-domain URLs cannot be resolved yet.`,
|
|
605
|
+
null
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
const result = await this.transport.request("GET", "/drops", { params: { slug } });
|
|
609
|
+
if (result.error)
|
|
610
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
611
|
+
return {
|
|
612
|
+
data: result.data.drops[0] ?? null,
|
|
613
|
+
error: null,
|
|
614
|
+
headers: result.headers
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
async getContent(dropId, options = {}) {
|
|
618
|
+
const base = options.deploymentId !== void 0 ? `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(options.deploymentId)}/content` : `/drops/${encodeURIComponent(dropId)}/content`;
|
|
619
|
+
if (options.path === void 0)
|
|
620
|
+
return this.transport.request("GET", base);
|
|
621
|
+
const path = options.path;
|
|
622
|
+
const result = await this.transport.requestBytes(base, {
|
|
623
|
+
params: { path }
|
|
624
|
+
});
|
|
625
|
+
if (result.error)
|
|
626
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
627
|
+
const { bytes, contentType } = result.data;
|
|
628
|
+
return {
|
|
629
|
+
data: {
|
|
630
|
+
path,
|
|
631
|
+
contentType,
|
|
632
|
+
bytes,
|
|
633
|
+
text: () => new TextDecoder().decode(bytes)
|
|
634
|
+
},
|
|
635
|
+
error: null,
|
|
636
|
+
headers: result.headers
|
|
637
|
+
};
|
|
638
|
+
}
|
|
536
639
|
/**
|
|
537
640
|
* Change an EXISTING drop's settings — title, visibility, password, noindex, expiry,
|
|
538
641
|
* metadata — by its `drop_…` id. Does not touch content; replace that with {@link updateContent}.
|
|
@@ -560,6 +663,25 @@ var DropsResource = class {
|
|
|
560
663
|
);
|
|
561
664
|
}
|
|
562
665
|
};
|
|
666
|
+
var CANONICAL_VIEWER_HOST = "dropthis.app";
|
|
667
|
+
function parseSlug(input, allowedParents) {
|
|
668
|
+
const value = input.trim();
|
|
669
|
+
if (!value) return null;
|
|
670
|
+
if (!/[./]/.test(value)) return value;
|
|
671
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
672
|
+
let hostname;
|
|
673
|
+
try {
|
|
674
|
+
hostname = new URL(withScheme).hostname;
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
hostname = hostname.replace(/\.$/, "");
|
|
679
|
+
const dot = hostname.indexOf(".");
|
|
680
|
+
if (dot <= 0) return null;
|
|
681
|
+
const slug = hostname.slice(0, dot);
|
|
682
|
+
const parent = hostname.slice(dot + 1);
|
|
683
|
+
return allowedParents.has(parent) ? slug : null;
|
|
684
|
+
}
|
|
563
685
|
var SETTINGS = [
|
|
564
686
|
"title",
|
|
565
687
|
"visibility",
|
|
@@ -612,7 +734,7 @@ function toSnakeCase(value) {
|
|
|
612
734
|
|
|
613
735
|
// src/transport.ts
|
|
614
736
|
var DEFAULT_BASE_URL = "https://api.dropthis.app";
|
|
615
|
-
var SDK_VERSION = "0.
|
|
737
|
+
var SDK_VERSION = "0.10.0";
|
|
616
738
|
var Transport = class {
|
|
617
739
|
apiKey;
|
|
618
740
|
baseUrl;
|
|
@@ -672,6 +794,63 @@ var Transport = class {
|
|
|
672
794
|
clearTimeout(timeout);
|
|
673
795
|
}
|
|
674
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Authenticated GET that returns the raw response bytes untouched (no JSON parsing,
|
|
799
|
+
* no case conversion). Error responses are still parsed as problem+json. Used for
|
|
800
|
+
* content read-back (`drops.getContent` with a file path).
|
|
801
|
+
*/
|
|
802
|
+
async requestBytes(path, options = {}) {
|
|
803
|
+
if (!this.apiKey) {
|
|
804
|
+
return createErrorResult(
|
|
805
|
+
"missing_api_key",
|
|
806
|
+
"No API key provided. Pass apiKey or set DROPTHIS_API_KEY.",
|
|
807
|
+
null
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
const headers = {
|
|
811
|
+
"user-agent": `dropthis-node/${SDK_VERSION}`,
|
|
812
|
+
authorization: `Bearer ${this.apiKey}`
|
|
813
|
+
};
|
|
814
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
815
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
816
|
+
if (value !== void 0 && value !== null)
|
|
817
|
+
url.searchParams.set(key, String(value));
|
|
818
|
+
}
|
|
819
|
+
const controller = new AbortController();
|
|
820
|
+
const timeout = setTimeout(
|
|
821
|
+
() => controller.abort(),
|
|
822
|
+
options.timeoutMs ?? this.timeoutMs
|
|
823
|
+
);
|
|
824
|
+
try {
|
|
825
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
826
|
+
method: "GET",
|
|
827
|
+
headers,
|
|
828
|
+
signal: controller.signal
|
|
829
|
+
});
|
|
830
|
+
const responseHeaders = headersToObject(response.headers);
|
|
831
|
+
if (!response.ok) {
|
|
832
|
+
const text = await response.text();
|
|
833
|
+
return problemResult(response, text, responseHeaders);
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
data: {
|
|
837
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
838
|
+
contentType: response.headers.get("content-type") ?? responseHeaders["content-type"] ?? null
|
|
839
|
+
},
|
|
840
|
+
error: null,
|
|
841
|
+
headers: responseHeaders
|
|
842
|
+
};
|
|
843
|
+
} catch (error) {
|
|
844
|
+
const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
|
|
845
|
+
return createErrorResult(
|
|
846
|
+
error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
|
|
847
|
+
message,
|
|
848
|
+
null
|
|
849
|
+
);
|
|
850
|
+
} finally {
|
|
851
|
+
clearTimeout(timeout);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
675
854
|
async request(method, path, options = {}) {
|
|
676
855
|
const authenticated = options.authenticated ?? true;
|
|
677
856
|
if (authenticated && !this.apiKey) {
|
|
@@ -718,31 +897,10 @@ var Transport = class {
|
|
|
718
897
|
const response = await this.fetchImpl(url.toString(), request);
|
|
719
898
|
const responseHeaders = headersToObject(response.headers);
|
|
720
899
|
const text = await response.text();
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
724
|
-
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
725
|
-
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
726
|
-
const currentRevision = numberValue(body.current_revision);
|
|
727
|
-
const type = stringValue(body.type);
|
|
728
|
-
const title = stringValue(body.title);
|
|
729
|
-
const instance = stringValue(body.instance);
|
|
730
|
-
return createErrorResult(code, message, response.status, {
|
|
731
|
-
body,
|
|
732
|
-
...type !== null ? { type } : {},
|
|
733
|
-
...title !== null ? { title } : {},
|
|
734
|
-
...instance !== null ? { instance } : {},
|
|
735
|
-
detail: stringValue(body.detail),
|
|
736
|
-
param: stringValue(body.param),
|
|
737
|
-
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
738
|
-
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
739
|
-
suggestion: stringValue(body.suggestion),
|
|
740
|
-
retryable: booleanValue(body.retryable),
|
|
741
|
-
headers: responseHeaders
|
|
742
|
-
});
|
|
743
|
-
}
|
|
900
|
+
if (!response.ok)
|
|
901
|
+
return problemResult(response, text, responseHeaders);
|
|
744
902
|
return {
|
|
745
|
-
data: toCamelCase(
|
|
903
|
+
data: toCamelCase(parseJson(text)),
|
|
746
904
|
error: null,
|
|
747
905
|
headers: responseHeaders
|
|
748
906
|
};
|
|
@@ -758,6 +916,29 @@ var Transport = class {
|
|
|
758
916
|
}
|
|
759
917
|
}
|
|
760
918
|
};
|
|
919
|
+
function problemResult(response, text, responseHeaders) {
|
|
920
|
+
const parsed = parseJson(text);
|
|
921
|
+
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
922
|
+
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
923
|
+
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
924
|
+
const currentRevision = numberValue(body.current_revision);
|
|
925
|
+
const type = stringValue(body.type);
|
|
926
|
+
const title = stringValue(body.title);
|
|
927
|
+
const instance = stringValue(body.instance);
|
|
928
|
+
return createErrorResult(code, message, response.status, {
|
|
929
|
+
body,
|
|
930
|
+
...type !== null ? { type } : {},
|
|
931
|
+
...title !== null ? { title } : {},
|
|
932
|
+
...instance !== null ? { instance } : {},
|
|
933
|
+
detail: stringValue(body.detail),
|
|
934
|
+
param: stringValue(body.param),
|
|
935
|
+
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
936
|
+
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
937
|
+
suggestion: stringValue(body.suggestion),
|
|
938
|
+
retryable: booleanValue(body.retryable),
|
|
939
|
+
headers: responseHeaders
|
|
940
|
+
});
|
|
941
|
+
}
|
|
761
942
|
function headersToObject(headers) {
|
|
762
943
|
const result = {};
|
|
763
944
|
headers.forEach((value, key) => {
|