@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
|
@@ -331,10 +331,49 @@ async function resolveInMemory(input, options = {}) {
|
|
|
331
331
|
});
|
|
332
332
|
return buildStagedRequest(files, options, input.entry);
|
|
333
333
|
}
|
|
334
|
+
var UPLOAD_CONCURRENCY = 5;
|
|
334
335
|
function errorResult(result) {
|
|
335
336
|
if (!result.error) throw new Error("Expected an error result");
|
|
336
337
|
return { data: null, error: result.error, headers: result.headers };
|
|
337
338
|
}
|
|
339
|
+
async function uploadStagedFiles(transport, jobs) {
|
|
340
|
+
let nextIndex = 0;
|
|
341
|
+
let failed = false;
|
|
342
|
+
const failures = /* @__PURE__ */ new Map();
|
|
343
|
+
async function worker() {
|
|
344
|
+
while (!failed) {
|
|
345
|
+
const index = nextIndex;
|
|
346
|
+
nextIndex += 1;
|
|
347
|
+
if (index >= jobs.length) return;
|
|
348
|
+
const job = jobs[index];
|
|
349
|
+
if (!job) return;
|
|
350
|
+
const put = await transport.putSignedUrl(
|
|
351
|
+
job.target.upload.url,
|
|
352
|
+
await job.file.getBody(),
|
|
353
|
+
job.target.upload.headers
|
|
354
|
+
);
|
|
355
|
+
if (put.error) {
|
|
356
|
+
failed = true;
|
|
357
|
+
failures.set(index, {
|
|
358
|
+
data: null,
|
|
359
|
+
error: {
|
|
360
|
+
...put.error,
|
|
361
|
+
message: `Upload failed for ${job.file.path}: ${put.error.message}`
|
|
362
|
+
},
|
|
363
|
+
headers: put.headers
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const workers = Array.from(
|
|
369
|
+
{ length: Math.min(UPLOAD_CONCURRENCY, jobs.length) },
|
|
370
|
+
() => worker()
|
|
371
|
+
);
|
|
372
|
+
await Promise.all(workers);
|
|
373
|
+
if (failures.size === 0) return null;
|
|
374
|
+
const firstIndex = Math.min(...failures.keys());
|
|
375
|
+
return failures.get(firstIndex) ?? null;
|
|
376
|
+
}
|
|
338
377
|
async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
339
378
|
const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
|
|
340
379
|
const createResult = await transport.request(
|
|
@@ -346,6 +385,7 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
346
385
|
}
|
|
347
386
|
);
|
|
348
387
|
if (createResult.error) return errorResult(createResult);
|
|
388
|
+
const jobs = [];
|
|
349
389
|
for (const file of prepared.files) {
|
|
350
390
|
const target = createResult.data.files.find((f) => f.path === file.path);
|
|
351
391
|
if (!target) {
|
|
@@ -358,22 +398,18 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
358
398
|
if (target.upload.strategy !== "single_put") {
|
|
359
399
|
return createErrorResult(
|
|
360
400
|
"unsupported_upload_strategy",
|
|
361
|
-
`
|
|
401
|
+
`Unexpected upload strategy "${target.upload.strategy}" for ${file.path}. The dropthis upload contract is one signed PUT per file; update @dropthis/node.`,
|
|
362
402
|
null
|
|
363
403
|
);
|
|
364
404
|
}
|
|
365
|
-
|
|
366
|
-
target.upload.url,
|
|
367
|
-
await file.getBody(),
|
|
368
|
-
target.upload.headers
|
|
369
|
-
);
|
|
370
|
-
if (put.error) return errorResult(put);
|
|
405
|
+
jobs.push({ file, target });
|
|
371
406
|
}
|
|
407
|
+
const uploadFailure = await uploadStagedFiles(transport, jobs);
|
|
408
|
+
if (uploadFailure) return uploadFailure;
|
|
372
409
|
const completeResult = await transport.request(
|
|
373
410
|
"POST",
|
|
374
411
|
`/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
|
|
375
412
|
{
|
|
376
|
-
body: { files: {} },
|
|
377
413
|
idempotencyKey: `${baseKey}:complete-upload`
|
|
378
414
|
}
|
|
379
415
|
);
|
|
@@ -584,6 +620,7 @@ var ApiKeysResource = class {
|
|
|
584
620
|
create(input) {
|
|
585
621
|
return this.transport.request("POST", "/api-keys", { body: input });
|
|
586
622
|
}
|
|
623
|
+
/** Revoke an API key. 204 No Content — data is null on success. */
|
|
587
624
|
delete(keyId) {
|
|
588
625
|
return this.transport.request(
|
|
589
626
|
"DELETE",
|
|
@@ -610,6 +647,7 @@ var AuthResource = class {
|
|
|
610
647
|
authenticated: false
|
|
611
648
|
});
|
|
612
649
|
}
|
|
650
|
+
/** Revoke the current `at_` session token. 204 No Content — data is null on success. */
|
|
613
651
|
logout() {
|
|
614
652
|
return this.transport.request("DELETE", "/auth/session");
|
|
615
653
|
}
|
|
@@ -671,12 +709,29 @@ var CursorPage = class {
|
|
|
671
709
|
|
|
672
710
|
// src/resources/drops.ts
|
|
673
711
|
var DropsResource = class {
|
|
674
|
-
constructor(transport,
|
|
712
|
+
constructor(transport, resolveInput2) {
|
|
675
713
|
this.transport = transport;
|
|
676
|
-
this.
|
|
714
|
+
this.resolveInput = resolveInput2;
|
|
677
715
|
}
|
|
678
716
|
transport;
|
|
679
|
-
|
|
717
|
+
resolveInput;
|
|
718
|
+
parentHosts;
|
|
719
|
+
/**
|
|
720
|
+
* Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
|
|
721
|
+
* viewer domain plus the configured baseUrl's host (covers staging/self-hosted
|
|
722
|
+
* setups where drops are served under the API's own domain).
|
|
723
|
+
*/
|
|
724
|
+
allowedParentHosts() {
|
|
725
|
+
if (!this.parentHosts) {
|
|
726
|
+
const hosts = /* @__PURE__ */ new Set([CANONICAL_VIEWER_HOST]);
|
|
727
|
+
try {
|
|
728
|
+
hosts.add(new URL(this.transport.baseUrl).hostname);
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
731
|
+
this.parentHosts = hosts;
|
|
732
|
+
}
|
|
733
|
+
return this.parentHosts;
|
|
734
|
+
}
|
|
680
735
|
/**
|
|
681
736
|
* Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
|
|
682
737
|
* Use to publish / share / post / put online / make public a report, dashboard, site, or file.
|
|
@@ -687,7 +742,7 @@ var DropsResource = class {
|
|
|
687
742
|
async publish(input, options = {}) {
|
|
688
743
|
let prepared;
|
|
689
744
|
try {
|
|
690
|
-
prepared = await this.
|
|
745
|
+
prepared = await this.resolveInput(input, options);
|
|
691
746
|
} catch (e) {
|
|
692
747
|
return toErrorResult(e);
|
|
693
748
|
}
|
|
@@ -704,7 +759,7 @@ var DropsResource = class {
|
|
|
704
759
|
const opts = updateContentOptions(options);
|
|
705
760
|
let prepared;
|
|
706
761
|
try {
|
|
707
|
-
prepared = await this.
|
|
762
|
+
prepared = await this.resolveInput(input, opts);
|
|
708
763
|
} catch (e) {
|
|
709
764
|
return toErrorResult(e);
|
|
710
765
|
}
|
|
@@ -732,6 +787,55 @@ var DropsResource = class {
|
|
|
732
787
|
`/drops/${encodeURIComponent(dropId)}`
|
|
733
788
|
);
|
|
734
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Resolve a drop URL (or bare slug) back to the drop — the way to recover a lost
|
|
792
|
+
* `drop_…` id. The slug is parsed client-side from the first hostname label of a
|
|
793
|
+
* dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
|
|
794
|
+
* the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
|
|
795
|
+
* Returns the drop, or `data: null` when no drop of yours has that slug.
|
|
796
|
+
* Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
|
|
797
|
+
* not resolvable yet.
|
|
798
|
+
*/
|
|
799
|
+
async resolve(urlOrSlug) {
|
|
800
|
+
const slug = parseSlug(urlOrSlug, this.allowedParentHosts());
|
|
801
|
+
if (slug === null) {
|
|
802
|
+
return createErrorResult(
|
|
803
|
+
"invalid_drop_url",
|
|
804
|
+
`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.`,
|
|
805
|
+
null
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const result = await this.transport.request("GET", "/drops", { params: { slug } });
|
|
809
|
+
if (result.error)
|
|
810
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
811
|
+
return {
|
|
812
|
+
data: result.data.drops[0] ?? null,
|
|
813
|
+
error: null,
|
|
814
|
+
headers: result.headers
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
async getContent(dropId, options = {}) {
|
|
818
|
+
const base = options.deploymentId !== void 0 ? `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(options.deploymentId)}/content` : `/drops/${encodeURIComponent(dropId)}/content`;
|
|
819
|
+
if (options.path === void 0)
|
|
820
|
+
return this.transport.request("GET", base);
|
|
821
|
+
const path = options.path;
|
|
822
|
+
const result = await this.transport.requestBytes(base, {
|
|
823
|
+
params: { path }
|
|
824
|
+
});
|
|
825
|
+
if (result.error)
|
|
826
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
827
|
+
const { bytes, contentType } = result.data;
|
|
828
|
+
return {
|
|
829
|
+
data: {
|
|
830
|
+
path,
|
|
831
|
+
contentType,
|
|
832
|
+
bytes,
|
|
833
|
+
text: () => new TextDecoder().decode(bytes)
|
|
834
|
+
},
|
|
835
|
+
error: null,
|
|
836
|
+
headers: result.headers
|
|
837
|
+
};
|
|
838
|
+
}
|
|
735
839
|
/**
|
|
736
840
|
* Change an EXISTING drop's settings — title, visibility, password, noindex, expiry,
|
|
737
841
|
* metadata — by its `drop_…` id. Does not touch content; replace that with {@link updateContent}.
|
|
@@ -759,6 +863,25 @@ var DropsResource = class {
|
|
|
759
863
|
);
|
|
760
864
|
}
|
|
761
865
|
};
|
|
866
|
+
var CANONICAL_VIEWER_HOST = "dropthis.app";
|
|
867
|
+
function parseSlug(input, allowedParents) {
|
|
868
|
+
const value = input.trim();
|
|
869
|
+
if (!value) return null;
|
|
870
|
+
if (!/[./]/.test(value)) return value;
|
|
871
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
872
|
+
let hostname;
|
|
873
|
+
try {
|
|
874
|
+
hostname = new URL(withScheme).hostname;
|
|
875
|
+
} catch {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
hostname = hostname.replace(/\.$/, "");
|
|
879
|
+
const dot = hostname.indexOf(".");
|
|
880
|
+
if (dot <= 0) return null;
|
|
881
|
+
const slug = hostname.slice(0, dot);
|
|
882
|
+
const parent = hostname.slice(dot + 1);
|
|
883
|
+
return allowedParents.has(parent) ? slug : null;
|
|
884
|
+
}
|
|
762
885
|
var SETTINGS = [
|
|
763
886
|
"title",
|
|
764
887
|
"visibility",
|
|
@@ -794,8 +917,12 @@ var UploadsResource = class {
|
|
|
794
917
|
`/uploads/${encodeURIComponent(uploadId)}`
|
|
795
918
|
);
|
|
796
919
|
}
|
|
797
|
-
|
|
798
|
-
|
|
920
|
+
/**
|
|
921
|
+
* Verify staged objects and mark the session complete. Takes no request body —
|
|
922
|
+
* the server verifies the uploaded bytes against the staged manifest.
|
|
923
|
+
*/
|
|
924
|
+
complete(uploadId, options = {}) {
|
|
925
|
+
const requestOptions = {};
|
|
799
926
|
if (options.idempotencyKey)
|
|
800
927
|
requestOptions.idempotencyKey = options.idempotencyKey;
|
|
801
928
|
return this.transport.request(
|
|
@@ -847,7 +974,7 @@ function toSnakeCase(value) {
|
|
|
847
974
|
|
|
848
975
|
// src/transport.ts
|
|
849
976
|
var DEFAULT_BASE_URL = "https://api.dropthis.app";
|
|
850
|
-
var SDK_VERSION = "0.
|
|
977
|
+
var SDK_VERSION = "0.10.0";
|
|
851
978
|
var Transport = class {
|
|
852
979
|
apiKey;
|
|
853
980
|
baseUrl;
|
|
@@ -907,6 +1034,63 @@ var Transport = class {
|
|
|
907
1034
|
clearTimeout(timeout);
|
|
908
1035
|
}
|
|
909
1036
|
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Authenticated GET that returns the raw response bytes untouched (no JSON parsing,
|
|
1039
|
+
* no case conversion). Error responses are still parsed as problem+json. Used for
|
|
1040
|
+
* content read-back (`drops.getContent` with a file path).
|
|
1041
|
+
*/
|
|
1042
|
+
async requestBytes(path, options = {}) {
|
|
1043
|
+
if (!this.apiKey) {
|
|
1044
|
+
return createErrorResult(
|
|
1045
|
+
"missing_api_key",
|
|
1046
|
+
"No API key provided. Pass apiKey or set DROPTHIS_API_KEY.",
|
|
1047
|
+
null
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
const headers = {
|
|
1051
|
+
"user-agent": `dropthis-node/${SDK_VERSION}`,
|
|
1052
|
+
authorization: `Bearer ${this.apiKey}`
|
|
1053
|
+
};
|
|
1054
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
1055
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
1056
|
+
if (value !== void 0 && value !== null)
|
|
1057
|
+
url.searchParams.set(key, String(value));
|
|
1058
|
+
}
|
|
1059
|
+
const controller = new AbortController();
|
|
1060
|
+
const timeout = setTimeout(
|
|
1061
|
+
() => controller.abort(),
|
|
1062
|
+
options.timeoutMs ?? this.timeoutMs
|
|
1063
|
+
);
|
|
1064
|
+
try {
|
|
1065
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
1066
|
+
method: "GET",
|
|
1067
|
+
headers,
|
|
1068
|
+
signal: controller.signal
|
|
1069
|
+
});
|
|
1070
|
+
const responseHeaders = headersToObject(response.headers);
|
|
1071
|
+
if (!response.ok) {
|
|
1072
|
+
const text = await response.text();
|
|
1073
|
+
return problemResult(response, text, responseHeaders);
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
data: {
|
|
1077
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
1078
|
+
contentType: response.headers.get("content-type") ?? responseHeaders["content-type"] ?? null
|
|
1079
|
+
},
|
|
1080
|
+
error: null,
|
|
1081
|
+
headers: responseHeaders
|
|
1082
|
+
};
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
|
|
1085
|
+
return createErrorResult(
|
|
1086
|
+
error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
|
|
1087
|
+
message,
|
|
1088
|
+
null
|
|
1089
|
+
);
|
|
1090
|
+
} finally {
|
|
1091
|
+
clearTimeout(timeout);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
910
1094
|
async request(method, path, options = {}) {
|
|
911
1095
|
const authenticated = options.authenticated ?? true;
|
|
912
1096
|
if (authenticated && !this.apiKey) {
|
|
@@ -953,31 +1137,10 @@ var Transport = class {
|
|
|
953
1137
|
const response = await this.fetchImpl(url.toString(), request);
|
|
954
1138
|
const responseHeaders = headersToObject(response.headers);
|
|
955
1139
|
const text = await response.text();
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
959
|
-
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
960
|
-
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
961
|
-
const currentRevision = numberValue(body.current_revision);
|
|
962
|
-
const type = stringValue(body.type);
|
|
963
|
-
const title = stringValue(body.title);
|
|
964
|
-
const instance = stringValue(body.instance);
|
|
965
|
-
return createErrorResult(code, message, response.status, {
|
|
966
|
-
body,
|
|
967
|
-
...type !== null ? { type } : {},
|
|
968
|
-
...title !== null ? { title } : {},
|
|
969
|
-
...instance !== null ? { instance } : {},
|
|
970
|
-
detail: stringValue(body.detail),
|
|
971
|
-
param: stringValue(body.param),
|
|
972
|
-
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
973
|
-
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
974
|
-
suggestion: stringValue(body.suggestion),
|
|
975
|
-
retryable: booleanValue(body.retryable),
|
|
976
|
-
headers: responseHeaders
|
|
977
|
-
});
|
|
978
|
-
}
|
|
1140
|
+
if (!response.ok)
|
|
1141
|
+
return problemResult(response, text, responseHeaders);
|
|
979
1142
|
return {
|
|
980
|
-
data: toCamelCase(
|
|
1143
|
+
data: toCamelCase(parseJson(text)),
|
|
981
1144
|
error: null,
|
|
982
1145
|
headers: responseHeaders
|
|
983
1146
|
};
|
|
@@ -993,6 +1156,29 @@ var Transport = class {
|
|
|
993
1156
|
}
|
|
994
1157
|
}
|
|
995
1158
|
};
|
|
1159
|
+
function problemResult(response, text, responseHeaders) {
|
|
1160
|
+
const parsed = parseJson(text);
|
|
1161
|
+
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
1162
|
+
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
1163
|
+
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
1164
|
+
const currentRevision = numberValue(body.current_revision);
|
|
1165
|
+
const type = stringValue(body.type);
|
|
1166
|
+
const title = stringValue(body.title);
|
|
1167
|
+
const instance = stringValue(body.instance);
|
|
1168
|
+
return createErrorResult(code, message, response.status, {
|
|
1169
|
+
body,
|
|
1170
|
+
...type !== null ? { type } : {},
|
|
1171
|
+
...title !== null ? { title } : {},
|
|
1172
|
+
...instance !== null ? { instance } : {},
|
|
1173
|
+
detail: stringValue(body.detail),
|
|
1174
|
+
param: stringValue(body.param),
|
|
1175
|
+
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
1176
|
+
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
1177
|
+
suggestion: stringValue(body.suggestion),
|
|
1178
|
+
retryable: booleanValue(body.retryable),
|
|
1179
|
+
headers: responseHeaders
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
996
1182
|
function headersToObject(headers) {
|
|
997
1183
|
const result = {};
|
|
998
1184
|
headers.forEach((value, key) => {
|