@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
|
@@ -333,10 +333,49 @@ async function resolveInMemory(input, options = {}) {
|
|
|
333
333
|
});
|
|
334
334
|
return buildStagedRequest(files, options, input.entry);
|
|
335
335
|
}
|
|
336
|
+
var UPLOAD_CONCURRENCY = 5;
|
|
336
337
|
function errorResult(result) {
|
|
337
338
|
if (!result.error) throw new Error("Expected an error result");
|
|
338
339
|
return { data: null, error: result.error, headers: result.headers };
|
|
339
340
|
}
|
|
341
|
+
async function uploadStagedFiles(transport, jobs) {
|
|
342
|
+
let nextIndex = 0;
|
|
343
|
+
let failed = false;
|
|
344
|
+
const failures = /* @__PURE__ */ new Map();
|
|
345
|
+
async function worker() {
|
|
346
|
+
while (!failed) {
|
|
347
|
+
const index = nextIndex;
|
|
348
|
+
nextIndex += 1;
|
|
349
|
+
if (index >= jobs.length) return;
|
|
350
|
+
const job = jobs[index];
|
|
351
|
+
if (!job) return;
|
|
352
|
+
const put = await transport.putSignedUrl(
|
|
353
|
+
job.target.upload.url,
|
|
354
|
+
await job.file.getBody(),
|
|
355
|
+
job.target.upload.headers
|
|
356
|
+
);
|
|
357
|
+
if (put.error) {
|
|
358
|
+
failed = true;
|
|
359
|
+
failures.set(index, {
|
|
360
|
+
data: null,
|
|
361
|
+
error: {
|
|
362
|
+
...put.error,
|
|
363
|
+
message: `Upload failed for ${job.file.path}: ${put.error.message}`
|
|
364
|
+
},
|
|
365
|
+
headers: put.headers
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const workers = Array.from(
|
|
371
|
+
{ length: Math.min(UPLOAD_CONCURRENCY, jobs.length) },
|
|
372
|
+
() => worker()
|
|
373
|
+
);
|
|
374
|
+
await Promise.all(workers);
|
|
375
|
+
if (failures.size === 0) return null;
|
|
376
|
+
const firstIndex = Math.min(...failures.keys());
|
|
377
|
+
return failures.get(firstIndex) ?? null;
|
|
378
|
+
}
|
|
340
379
|
async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
341
380
|
const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
|
|
342
381
|
const createResult = await transport.request(
|
|
@@ -348,6 +387,7 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
348
387
|
}
|
|
349
388
|
);
|
|
350
389
|
if (createResult.error) return errorResult(createResult);
|
|
390
|
+
const jobs = [];
|
|
351
391
|
for (const file of prepared.files) {
|
|
352
392
|
const target = createResult.data.files.find((f) => f.path === file.path);
|
|
353
393
|
if (!target) {
|
|
@@ -360,22 +400,18 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
360
400
|
if (target.upload.strategy !== "single_put") {
|
|
361
401
|
return createErrorResult(
|
|
362
402
|
"unsupported_upload_strategy",
|
|
363
|
-
`
|
|
403
|
+
`Unexpected upload strategy "${target.upload.strategy}" for ${file.path}. The dropthis upload contract is one signed PUT per file; update @dropthis/node.`,
|
|
364
404
|
null
|
|
365
405
|
);
|
|
366
406
|
}
|
|
367
|
-
|
|
368
|
-
target.upload.url,
|
|
369
|
-
await file.getBody(),
|
|
370
|
-
target.upload.headers
|
|
371
|
-
);
|
|
372
|
-
if (put.error) return errorResult(put);
|
|
407
|
+
jobs.push({ file, target });
|
|
373
408
|
}
|
|
409
|
+
const uploadFailure = await uploadStagedFiles(transport, jobs);
|
|
410
|
+
if (uploadFailure) return uploadFailure;
|
|
374
411
|
const completeResult = await transport.request(
|
|
375
412
|
"POST",
|
|
376
413
|
`/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
|
|
377
414
|
{
|
|
378
|
-
body: { files: {} },
|
|
379
415
|
idempotencyKey: `${baseKey}:complete-upload`
|
|
380
416
|
}
|
|
381
417
|
);
|
|
@@ -434,6 +470,7 @@ var ApiKeysResource = class {
|
|
|
434
470
|
create(input) {
|
|
435
471
|
return this.transport.request("POST", "/api-keys", { body: input });
|
|
436
472
|
}
|
|
473
|
+
/** Revoke an API key. 204 No Content — data is null on success. */
|
|
437
474
|
delete(keyId) {
|
|
438
475
|
return this.transport.request(
|
|
439
476
|
"DELETE",
|
|
@@ -498,12 +535,29 @@ var CursorPage = class {
|
|
|
498
535
|
|
|
499
536
|
// src/resources/drops.ts
|
|
500
537
|
var DropsResource = class {
|
|
501
|
-
constructor(transport,
|
|
538
|
+
constructor(transport, resolveInput) {
|
|
502
539
|
this.transport = transport;
|
|
503
|
-
this.
|
|
540
|
+
this.resolveInput = resolveInput;
|
|
504
541
|
}
|
|
505
542
|
transport;
|
|
506
|
-
|
|
543
|
+
resolveInput;
|
|
544
|
+
parentHosts;
|
|
545
|
+
/**
|
|
546
|
+
* Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
|
|
547
|
+
* viewer domain plus the configured baseUrl's host (covers staging/self-hosted
|
|
548
|
+
* setups where drops are served under the API's own domain).
|
|
549
|
+
*/
|
|
550
|
+
allowedParentHosts() {
|
|
551
|
+
if (!this.parentHosts) {
|
|
552
|
+
const hosts = /* @__PURE__ */ new Set([CANONICAL_VIEWER_HOST]);
|
|
553
|
+
try {
|
|
554
|
+
hosts.add(new URL(this.transport.baseUrl).hostname);
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
this.parentHosts = hosts;
|
|
558
|
+
}
|
|
559
|
+
return this.parentHosts;
|
|
560
|
+
}
|
|
507
561
|
/**
|
|
508
562
|
* Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
|
|
509
563
|
* Use to publish / share / post / put online / make public a report, dashboard, site, or file.
|
|
@@ -514,7 +568,7 @@ var DropsResource = class {
|
|
|
514
568
|
async publish(input, options = {}) {
|
|
515
569
|
let prepared;
|
|
516
570
|
try {
|
|
517
|
-
prepared = await this.
|
|
571
|
+
prepared = await this.resolveInput(input, options);
|
|
518
572
|
} catch (e) {
|
|
519
573
|
return toErrorResult(e);
|
|
520
574
|
}
|
|
@@ -531,7 +585,7 @@ var DropsResource = class {
|
|
|
531
585
|
const opts = updateContentOptions(options);
|
|
532
586
|
let prepared;
|
|
533
587
|
try {
|
|
534
|
-
prepared = await this.
|
|
588
|
+
prepared = await this.resolveInput(input, opts);
|
|
535
589
|
} catch (e) {
|
|
536
590
|
return toErrorResult(e);
|
|
537
591
|
}
|
|
@@ -559,6 +613,55 @@ var DropsResource = class {
|
|
|
559
613
|
`/drops/${encodeURIComponent(dropId)}`
|
|
560
614
|
);
|
|
561
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Resolve a drop URL (or bare slug) back to the drop — the way to recover a lost
|
|
618
|
+
* `drop_…` id. The slug is parsed client-side from the first hostname label of a
|
|
619
|
+
* dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
|
|
620
|
+
* the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
|
|
621
|
+
* Returns the drop, or `data: null` when no drop of yours has that slug.
|
|
622
|
+
* Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
|
|
623
|
+
* not resolvable yet.
|
|
624
|
+
*/
|
|
625
|
+
async resolve(urlOrSlug) {
|
|
626
|
+
const slug = parseSlug(urlOrSlug, this.allowedParentHosts());
|
|
627
|
+
if (slug === null) {
|
|
628
|
+
return createErrorResult(
|
|
629
|
+
"invalid_drop_url",
|
|
630
|
+
`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.`,
|
|
631
|
+
null
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const result = await this.transport.request("GET", "/drops", { params: { slug } });
|
|
635
|
+
if (result.error)
|
|
636
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
637
|
+
return {
|
|
638
|
+
data: result.data.drops[0] ?? null,
|
|
639
|
+
error: null,
|
|
640
|
+
headers: result.headers
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async getContent(dropId, options = {}) {
|
|
644
|
+
const base = options.deploymentId !== void 0 ? `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(options.deploymentId)}/content` : `/drops/${encodeURIComponent(dropId)}/content`;
|
|
645
|
+
if (options.path === void 0)
|
|
646
|
+
return this.transport.request("GET", base);
|
|
647
|
+
const path = options.path;
|
|
648
|
+
const result = await this.transport.requestBytes(base, {
|
|
649
|
+
params: { path }
|
|
650
|
+
});
|
|
651
|
+
if (result.error)
|
|
652
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
653
|
+
const { bytes, contentType } = result.data;
|
|
654
|
+
return {
|
|
655
|
+
data: {
|
|
656
|
+
path,
|
|
657
|
+
contentType,
|
|
658
|
+
bytes,
|
|
659
|
+
text: () => new TextDecoder().decode(bytes)
|
|
660
|
+
},
|
|
661
|
+
error: null,
|
|
662
|
+
headers: result.headers
|
|
663
|
+
};
|
|
664
|
+
}
|
|
562
665
|
/**
|
|
563
666
|
* Change an EXISTING drop's settings — title, visibility, password, noindex, expiry,
|
|
564
667
|
* metadata — by its `drop_…` id. Does not touch content; replace that with {@link updateContent}.
|
|
@@ -586,6 +689,25 @@ var DropsResource = class {
|
|
|
586
689
|
);
|
|
587
690
|
}
|
|
588
691
|
};
|
|
692
|
+
var CANONICAL_VIEWER_HOST = "dropthis.app";
|
|
693
|
+
function parseSlug(input, allowedParents) {
|
|
694
|
+
const value = input.trim();
|
|
695
|
+
if (!value) return null;
|
|
696
|
+
if (!/[./]/.test(value)) return value;
|
|
697
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
698
|
+
let hostname;
|
|
699
|
+
try {
|
|
700
|
+
hostname = new URL(withScheme).hostname;
|
|
701
|
+
} catch {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
hostname = hostname.replace(/\.$/, "");
|
|
705
|
+
const dot = hostname.indexOf(".");
|
|
706
|
+
if (dot <= 0) return null;
|
|
707
|
+
const slug = hostname.slice(0, dot);
|
|
708
|
+
const parent = hostname.slice(dot + 1);
|
|
709
|
+
return allowedParents.has(parent) ? slug : null;
|
|
710
|
+
}
|
|
589
711
|
var SETTINGS = [
|
|
590
712
|
"title",
|
|
591
713
|
"visibility",
|
|
@@ -638,7 +760,7 @@ function toSnakeCase(value) {
|
|
|
638
760
|
|
|
639
761
|
// src/transport.ts
|
|
640
762
|
var DEFAULT_BASE_URL = "https://api.dropthis.app";
|
|
641
|
-
var SDK_VERSION = "0.
|
|
763
|
+
var SDK_VERSION = "0.10.0";
|
|
642
764
|
var Transport = class {
|
|
643
765
|
apiKey;
|
|
644
766
|
baseUrl;
|
|
@@ -698,6 +820,63 @@ var Transport = class {
|
|
|
698
820
|
clearTimeout(timeout);
|
|
699
821
|
}
|
|
700
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Authenticated GET that returns the raw response bytes untouched (no JSON parsing,
|
|
825
|
+
* no case conversion). Error responses are still parsed as problem+json. Used for
|
|
826
|
+
* content read-back (`drops.getContent` with a file path).
|
|
827
|
+
*/
|
|
828
|
+
async requestBytes(path, options = {}) {
|
|
829
|
+
if (!this.apiKey) {
|
|
830
|
+
return createErrorResult(
|
|
831
|
+
"missing_api_key",
|
|
832
|
+
"No API key provided. Pass apiKey or set DROPTHIS_API_KEY.",
|
|
833
|
+
null
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const headers = {
|
|
837
|
+
"user-agent": `dropthis-node/${SDK_VERSION}`,
|
|
838
|
+
authorization: `Bearer ${this.apiKey}`
|
|
839
|
+
};
|
|
840
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
841
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
842
|
+
if (value !== void 0 && value !== null)
|
|
843
|
+
url.searchParams.set(key, String(value));
|
|
844
|
+
}
|
|
845
|
+
const controller = new AbortController();
|
|
846
|
+
const timeout = setTimeout(
|
|
847
|
+
() => controller.abort(),
|
|
848
|
+
options.timeoutMs ?? this.timeoutMs
|
|
849
|
+
);
|
|
850
|
+
try {
|
|
851
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
852
|
+
method: "GET",
|
|
853
|
+
headers,
|
|
854
|
+
signal: controller.signal
|
|
855
|
+
});
|
|
856
|
+
const responseHeaders = headersToObject(response.headers);
|
|
857
|
+
if (!response.ok) {
|
|
858
|
+
const text = await response.text();
|
|
859
|
+
return problemResult(response, text, responseHeaders);
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
data: {
|
|
863
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
864
|
+
contentType: response.headers.get("content-type") ?? responseHeaders["content-type"] ?? null
|
|
865
|
+
},
|
|
866
|
+
error: null,
|
|
867
|
+
headers: responseHeaders
|
|
868
|
+
};
|
|
869
|
+
} catch (error) {
|
|
870
|
+
const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
|
|
871
|
+
return createErrorResult(
|
|
872
|
+
error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
|
|
873
|
+
message,
|
|
874
|
+
null
|
|
875
|
+
);
|
|
876
|
+
} finally {
|
|
877
|
+
clearTimeout(timeout);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
701
880
|
async request(method, path, options = {}) {
|
|
702
881
|
const authenticated = options.authenticated ?? true;
|
|
703
882
|
if (authenticated && !this.apiKey) {
|
|
@@ -744,31 +923,10 @@ var Transport = class {
|
|
|
744
923
|
const response = await this.fetchImpl(url.toString(), request);
|
|
745
924
|
const responseHeaders = headersToObject(response.headers);
|
|
746
925
|
const text = await response.text();
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
750
|
-
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
751
|
-
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
752
|
-
const currentRevision = numberValue(body.current_revision);
|
|
753
|
-
const type = stringValue(body.type);
|
|
754
|
-
const title = stringValue(body.title);
|
|
755
|
-
const instance = stringValue(body.instance);
|
|
756
|
-
return createErrorResult(code, message, response.status, {
|
|
757
|
-
body,
|
|
758
|
-
...type !== null ? { type } : {},
|
|
759
|
-
...title !== null ? { title } : {},
|
|
760
|
-
...instance !== null ? { instance } : {},
|
|
761
|
-
detail: stringValue(body.detail),
|
|
762
|
-
param: stringValue(body.param),
|
|
763
|
-
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
764
|
-
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
765
|
-
suggestion: stringValue(body.suggestion),
|
|
766
|
-
retryable: booleanValue(body.retryable),
|
|
767
|
-
headers: responseHeaders
|
|
768
|
-
});
|
|
769
|
-
}
|
|
926
|
+
if (!response.ok)
|
|
927
|
+
return problemResult(response, text, responseHeaders);
|
|
770
928
|
return {
|
|
771
|
-
data: toCamelCase(
|
|
929
|
+
data: toCamelCase(parseJson(text)),
|
|
772
930
|
error: null,
|
|
773
931
|
headers: responseHeaders
|
|
774
932
|
};
|
|
@@ -784,6 +942,29 @@ var Transport = class {
|
|
|
784
942
|
}
|
|
785
943
|
}
|
|
786
944
|
};
|
|
945
|
+
function problemResult(response, text, responseHeaders) {
|
|
946
|
+
const parsed = parseJson(text);
|
|
947
|
+
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
948
|
+
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
949
|
+
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
950
|
+
const currentRevision = numberValue(body.current_revision);
|
|
951
|
+
const type = stringValue(body.type);
|
|
952
|
+
const title = stringValue(body.title);
|
|
953
|
+
const instance = stringValue(body.instance);
|
|
954
|
+
return createErrorResult(code, message, response.status, {
|
|
955
|
+
body,
|
|
956
|
+
...type !== null ? { type } : {},
|
|
957
|
+
...title !== null ? { title } : {},
|
|
958
|
+
...instance !== null ? { instance } : {},
|
|
959
|
+
detail: stringValue(body.detail),
|
|
960
|
+
param: stringValue(body.param),
|
|
961
|
+
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
962
|
+
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
963
|
+
suggestion: stringValue(body.suggestion),
|
|
964
|
+
retryable: booleanValue(body.retryable),
|
|
965
|
+
headers: responseHeaders
|
|
966
|
+
});
|
|
967
|
+
}
|
|
787
968
|
function headersToObject(headers) {
|
|
788
969
|
const result = {};
|
|
789
970
|
headers.forEach((value, key) => {
|