@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
|
@@ -373,10 +373,49 @@ async function resolveInMemory(input, options = {}) {
|
|
|
373
373
|
});
|
|
374
374
|
return buildStagedRequest(files, options, input.entry);
|
|
375
375
|
}
|
|
376
|
+
var UPLOAD_CONCURRENCY = 5;
|
|
376
377
|
function errorResult(result) {
|
|
377
378
|
if (!result.error) throw new Error("Expected an error result");
|
|
378
379
|
return { data: null, error: result.error, headers: result.headers };
|
|
379
380
|
}
|
|
381
|
+
async function uploadStagedFiles(transport, jobs) {
|
|
382
|
+
let nextIndex = 0;
|
|
383
|
+
let failed = false;
|
|
384
|
+
const failures = /* @__PURE__ */ new Map();
|
|
385
|
+
async function worker() {
|
|
386
|
+
while (!failed) {
|
|
387
|
+
const index = nextIndex;
|
|
388
|
+
nextIndex += 1;
|
|
389
|
+
if (index >= jobs.length) return;
|
|
390
|
+
const job = jobs[index];
|
|
391
|
+
if (!job) return;
|
|
392
|
+
const put = await transport.putSignedUrl(
|
|
393
|
+
job.target.upload.url,
|
|
394
|
+
await job.file.getBody(),
|
|
395
|
+
job.target.upload.headers
|
|
396
|
+
);
|
|
397
|
+
if (put.error) {
|
|
398
|
+
failed = true;
|
|
399
|
+
failures.set(index, {
|
|
400
|
+
data: null,
|
|
401
|
+
error: {
|
|
402
|
+
...put.error,
|
|
403
|
+
message: `Upload failed for ${job.file.path}: ${put.error.message}`
|
|
404
|
+
},
|
|
405
|
+
headers: put.headers
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const workers = Array.from(
|
|
411
|
+
{ length: Math.min(UPLOAD_CONCURRENCY, jobs.length) },
|
|
412
|
+
() => worker()
|
|
413
|
+
);
|
|
414
|
+
await Promise.all(workers);
|
|
415
|
+
if (failures.size === 0) return null;
|
|
416
|
+
const firstIndex = Math.min(...failures.keys());
|
|
417
|
+
return failures.get(firstIndex) ?? null;
|
|
418
|
+
}
|
|
380
419
|
async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
381
420
|
const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
|
|
382
421
|
const createResult = await transport.request(
|
|
@@ -388,6 +427,7 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
388
427
|
}
|
|
389
428
|
);
|
|
390
429
|
if (createResult.error) return errorResult(createResult);
|
|
430
|
+
const jobs = [];
|
|
391
431
|
for (const file of prepared.files) {
|
|
392
432
|
const target = createResult.data.files.find((f) => f.path === file.path);
|
|
393
433
|
if (!target) {
|
|
@@ -400,22 +440,18 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
|
|
|
400
440
|
if (target.upload.strategy !== "single_put") {
|
|
401
441
|
return createErrorResult(
|
|
402
442
|
"unsupported_upload_strategy",
|
|
403
|
-
`
|
|
443
|
+
`Unexpected upload strategy "${target.upload.strategy}" for ${file.path}. The dropthis upload contract is one signed PUT per file; update @dropthis/node.`,
|
|
404
444
|
null
|
|
405
445
|
);
|
|
406
446
|
}
|
|
407
|
-
|
|
408
|
-
target.upload.url,
|
|
409
|
-
await file.getBody(),
|
|
410
|
-
target.upload.headers
|
|
411
|
-
);
|
|
412
|
-
if (put.error) return errorResult(put);
|
|
447
|
+
jobs.push({ file, target });
|
|
413
448
|
}
|
|
449
|
+
const uploadFailure = await uploadStagedFiles(transport, jobs);
|
|
450
|
+
if (uploadFailure) return uploadFailure;
|
|
414
451
|
const completeResult = await transport.request(
|
|
415
452
|
"POST",
|
|
416
453
|
`/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
|
|
417
454
|
{
|
|
418
|
-
body: { files: {} },
|
|
419
455
|
idempotencyKey: `${baseKey}:complete-upload`
|
|
420
456
|
}
|
|
421
457
|
);
|
|
@@ -626,6 +662,7 @@ var ApiKeysResource = class {
|
|
|
626
662
|
create(input) {
|
|
627
663
|
return this.transport.request("POST", "/api-keys", { body: input });
|
|
628
664
|
}
|
|
665
|
+
/** Revoke an API key. 204 No Content — data is null on success. */
|
|
629
666
|
delete(keyId) {
|
|
630
667
|
return this.transport.request(
|
|
631
668
|
"DELETE",
|
|
@@ -652,6 +689,7 @@ var AuthResource = class {
|
|
|
652
689
|
authenticated: false
|
|
653
690
|
});
|
|
654
691
|
}
|
|
692
|
+
/** Revoke the current `at_` session token. 204 No Content — data is null on success. */
|
|
655
693
|
logout() {
|
|
656
694
|
return this.transport.request("DELETE", "/auth/session");
|
|
657
695
|
}
|
|
@@ -713,12 +751,29 @@ var CursorPage = class {
|
|
|
713
751
|
|
|
714
752
|
// src/resources/drops.ts
|
|
715
753
|
var DropsResource = class {
|
|
716
|
-
constructor(transport,
|
|
754
|
+
constructor(transport, resolveInput2) {
|
|
717
755
|
this.transport = transport;
|
|
718
|
-
this.
|
|
756
|
+
this.resolveInput = resolveInput2;
|
|
719
757
|
}
|
|
720
758
|
transport;
|
|
721
|
-
|
|
759
|
+
resolveInput;
|
|
760
|
+
parentHosts;
|
|
761
|
+
/**
|
|
762
|
+
* Hostnames the SDK can attribute to dropthis for slug parsing: the canonical
|
|
763
|
+
* viewer domain plus the configured baseUrl's host (covers staging/self-hosted
|
|
764
|
+
* setups where drops are served under the API's own domain).
|
|
765
|
+
*/
|
|
766
|
+
allowedParentHosts() {
|
|
767
|
+
if (!this.parentHosts) {
|
|
768
|
+
const hosts = /* @__PURE__ */ new Set([CANONICAL_VIEWER_HOST]);
|
|
769
|
+
try {
|
|
770
|
+
hosts.add(new URL(this.transport.baseUrl).hostname);
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
this.parentHosts = hosts;
|
|
774
|
+
}
|
|
775
|
+
return this.parentHosts;
|
|
776
|
+
}
|
|
722
777
|
/**
|
|
723
778
|
* Publish content to a NEW permanent public URL; returns the created drop (with its `drop_…` id).
|
|
724
779
|
* Use to publish / share / post / put online / make public a report, dashboard, site, or file.
|
|
@@ -729,7 +784,7 @@ var DropsResource = class {
|
|
|
729
784
|
async publish(input, options = {}) {
|
|
730
785
|
let prepared;
|
|
731
786
|
try {
|
|
732
|
-
prepared = await this.
|
|
787
|
+
prepared = await this.resolveInput(input, options);
|
|
733
788
|
} catch (e) {
|
|
734
789
|
return toErrorResult(e);
|
|
735
790
|
}
|
|
@@ -746,7 +801,7 @@ var DropsResource = class {
|
|
|
746
801
|
const opts = updateContentOptions(options);
|
|
747
802
|
let prepared;
|
|
748
803
|
try {
|
|
749
|
-
prepared = await this.
|
|
804
|
+
prepared = await this.resolveInput(input, opts);
|
|
750
805
|
} catch (e) {
|
|
751
806
|
return toErrorResult(e);
|
|
752
807
|
}
|
|
@@ -774,6 +829,55 @@ var DropsResource = class {
|
|
|
774
829
|
`/drops/${encodeURIComponent(dropId)}`
|
|
775
830
|
);
|
|
776
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Resolve a drop URL (or bare slug) back to the drop — the way to recover a lost
|
|
834
|
+
* `drop_…` id. The slug is parsed client-side from the first hostname label of a
|
|
835
|
+
* dropthis-attributable hostname (`https://<slug>.dropthis.app/…`, or `<slug>.` +
|
|
836
|
+
* the configured baseUrl's host), then matched owner-scoped via GET /drops?slug=.
|
|
837
|
+
* Returns the drop, or `data: null` when no drop of yours has that slug.
|
|
838
|
+
* Custom-domain URLs are rejected client-side with `invalid_drop_url` — they are
|
|
839
|
+
* not resolvable yet.
|
|
840
|
+
*/
|
|
841
|
+
async resolve(urlOrSlug) {
|
|
842
|
+
const slug = parseSlug(urlOrSlug, this.allowedParentHosts());
|
|
843
|
+
if (slug === null) {
|
|
844
|
+
return createErrorResult(
|
|
845
|
+
"invalid_drop_url",
|
|
846
|
+
`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.`,
|
|
847
|
+
null
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
const result = await this.transport.request("GET", "/drops", { params: { slug } });
|
|
851
|
+
if (result.error)
|
|
852
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
853
|
+
return {
|
|
854
|
+
data: result.data.drops[0] ?? null,
|
|
855
|
+
error: null,
|
|
856
|
+
headers: result.headers
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
async getContent(dropId, options = {}) {
|
|
860
|
+
const base = options.deploymentId !== void 0 ? `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(options.deploymentId)}/content` : `/drops/${encodeURIComponent(dropId)}/content`;
|
|
861
|
+
if (options.path === void 0)
|
|
862
|
+
return this.transport.request("GET", base);
|
|
863
|
+
const path = options.path;
|
|
864
|
+
const result = await this.transport.requestBytes(base, {
|
|
865
|
+
params: { path }
|
|
866
|
+
});
|
|
867
|
+
if (result.error)
|
|
868
|
+
return { data: null, error: result.error, headers: result.headers };
|
|
869
|
+
const { bytes, contentType } = result.data;
|
|
870
|
+
return {
|
|
871
|
+
data: {
|
|
872
|
+
path,
|
|
873
|
+
contentType,
|
|
874
|
+
bytes,
|
|
875
|
+
text: () => new TextDecoder().decode(bytes)
|
|
876
|
+
},
|
|
877
|
+
error: null,
|
|
878
|
+
headers: result.headers
|
|
879
|
+
};
|
|
880
|
+
}
|
|
777
881
|
/**
|
|
778
882
|
* Change an EXISTING drop's settings — title, visibility, password, noindex, expiry,
|
|
779
883
|
* metadata — by its `drop_…` id. Does not touch content; replace that with {@link updateContent}.
|
|
@@ -801,6 +905,25 @@ var DropsResource = class {
|
|
|
801
905
|
);
|
|
802
906
|
}
|
|
803
907
|
};
|
|
908
|
+
var CANONICAL_VIEWER_HOST = "dropthis.app";
|
|
909
|
+
function parseSlug(input, allowedParents) {
|
|
910
|
+
const value = input.trim();
|
|
911
|
+
if (!value) return null;
|
|
912
|
+
if (!/[./]/.test(value)) return value;
|
|
913
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
914
|
+
let hostname;
|
|
915
|
+
try {
|
|
916
|
+
hostname = new URL(withScheme).hostname;
|
|
917
|
+
} catch {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
hostname = hostname.replace(/\.$/, "");
|
|
921
|
+
const dot = hostname.indexOf(".");
|
|
922
|
+
if (dot <= 0) return null;
|
|
923
|
+
const slug = hostname.slice(0, dot);
|
|
924
|
+
const parent = hostname.slice(dot + 1);
|
|
925
|
+
return allowedParents.has(parent) ? slug : null;
|
|
926
|
+
}
|
|
804
927
|
var SETTINGS = [
|
|
805
928
|
"title",
|
|
806
929
|
"visibility",
|
|
@@ -836,8 +959,12 @@ var UploadsResource = class {
|
|
|
836
959
|
`/uploads/${encodeURIComponent(uploadId)}`
|
|
837
960
|
);
|
|
838
961
|
}
|
|
839
|
-
|
|
840
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Verify staged objects and mark the session complete. Takes no request body —
|
|
964
|
+
* the server verifies the uploaded bytes against the staged manifest.
|
|
965
|
+
*/
|
|
966
|
+
complete(uploadId, options = {}) {
|
|
967
|
+
const requestOptions = {};
|
|
841
968
|
if (options.idempotencyKey)
|
|
842
969
|
requestOptions.idempotencyKey = options.idempotencyKey;
|
|
843
970
|
return this.transport.request(
|
|
@@ -889,7 +1016,7 @@ function toSnakeCase(value) {
|
|
|
889
1016
|
|
|
890
1017
|
// src/transport.ts
|
|
891
1018
|
var DEFAULT_BASE_URL = "https://api.dropthis.app";
|
|
892
|
-
var SDK_VERSION = "0.
|
|
1019
|
+
var SDK_VERSION = "0.10.0";
|
|
893
1020
|
var Transport = class {
|
|
894
1021
|
apiKey;
|
|
895
1022
|
baseUrl;
|
|
@@ -949,6 +1076,63 @@ var Transport = class {
|
|
|
949
1076
|
clearTimeout(timeout);
|
|
950
1077
|
}
|
|
951
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Authenticated GET that returns the raw response bytes untouched (no JSON parsing,
|
|
1081
|
+
* no case conversion). Error responses are still parsed as problem+json. Used for
|
|
1082
|
+
* content read-back (`drops.getContent` with a file path).
|
|
1083
|
+
*/
|
|
1084
|
+
async requestBytes(path, options = {}) {
|
|
1085
|
+
if (!this.apiKey) {
|
|
1086
|
+
return createErrorResult(
|
|
1087
|
+
"missing_api_key",
|
|
1088
|
+
"No API key provided. Pass apiKey or set DROPTHIS_API_KEY.",
|
|
1089
|
+
null
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
const headers = {
|
|
1093
|
+
"user-agent": `dropthis-node/${SDK_VERSION}`,
|
|
1094
|
+
authorization: `Bearer ${this.apiKey}`
|
|
1095
|
+
};
|
|
1096
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
1097
|
+
for (const [key, value] of Object.entries(options.params ?? {})) {
|
|
1098
|
+
if (value !== void 0 && value !== null)
|
|
1099
|
+
url.searchParams.set(key, String(value));
|
|
1100
|
+
}
|
|
1101
|
+
const controller = new AbortController();
|
|
1102
|
+
const timeout = setTimeout(
|
|
1103
|
+
() => controller.abort(),
|
|
1104
|
+
options.timeoutMs ?? this.timeoutMs
|
|
1105
|
+
);
|
|
1106
|
+
try {
|
|
1107
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
1108
|
+
method: "GET",
|
|
1109
|
+
headers,
|
|
1110
|
+
signal: controller.signal
|
|
1111
|
+
});
|
|
1112
|
+
const responseHeaders = headersToObject(response.headers);
|
|
1113
|
+
if (!response.ok) {
|
|
1114
|
+
const text = await response.text();
|
|
1115
|
+
return problemResult(response, text, responseHeaders);
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
data: {
|
|
1119
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
1120
|
+
contentType: response.headers.get("content-type") ?? responseHeaders["content-type"] ?? null
|
|
1121
|
+
},
|
|
1122
|
+
error: null,
|
|
1123
|
+
headers: responseHeaders
|
|
1124
|
+
};
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
|
|
1127
|
+
return createErrorResult(
|
|
1128
|
+
error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
|
|
1129
|
+
message,
|
|
1130
|
+
null
|
|
1131
|
+
);
|
|
1132
|
+
} finally {
|
|
1133
|
+
clearTimeout(timeout);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
952
1136
|
async request(method, path, options = {}) {
|
|
953
1137
|
const authenticated = options.authenticated ?? true;
|
|
954
1138
|
if (authenticated && !this.apiKey) {
|
|
@@ -995,31 +1179,10 @@ var Transport = class {
|
|
|
995
1179
|
const response = await this.fetchImpl(url.toString(), request);
|
|
996
1180
|
const responseHeaders = headersToObject(response.headers);
|
|
997
1181
|
const text = await response.text();
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
1001
|
-
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
1002
|
-
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
1003
|
-
const currentRevision = numberValue(body.current_revision);
|
|
1004
|
-
const type = stringValue(body.type);
|
|
1005
|
-
const title = stringValue(body.title);
|
|
1006
|
-
const instance = stringValue(body.instance);
|
|
1007
|
-
return createErrorResult(code, message, response.status, {
|
|
1008
|
-
body,
|
|
1009
|
-
...type !== null ? { type } : {},
|
|
1010
|
-
...title !== null ? { title } : {},
|
|
1011
|
-
...instance !== null ? { instance } : {},
|
|
1012
|
-
detail: stringValue(body.detail),
|
|
1013
|
-
param: stringValue(body.param),
|
|
1014
|
-
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
1015
|
-
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
1016
|
-
suggestion: stringValue(body.suggestion),
|
|
1017
|
-
retryable: booleanValue(body.retryable),
|
|
1018
|
-
headers: responseHeaders
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1182
|
+
if (!response.ok)
|
|
1183
|
+
return problemResult(response, text, responseHeaders);
|
|
1021
1184
|
return {
|
|
1022
|
-
data: toCamelCase(
|
|
1185
|
+
data: toCamelCase(parseJson(text)),
|
|
1023
1186
|
error: null,
|
|
1024
1187
|
headers: responseHeaders
|
|
1025
1188
|
};
|
|
@@ -1035,6 +1198,29 @@ var Transport = class {
|
|
|
1035
1198
|
}
|
|
1036
1199
|
}
|
|
1037
1200
|
};
|
|
1201
|
+
function problemResult(response, text, responseHeaders) {
|
|
1202
|
+
const parsed = parseJson(text);
|
|
1203
|
+
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
1204
|
+
const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
|
|
1205
|
+
const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
|
|
1206
|
+
const currentRevision = numberValue(body.current_revision);
|
|
1207
|
+
const type = stringValue(body.type);
|
|
1208
|
+
const title = stringValue(body.title);
|
|
1209
|
+
const instance = stringValue(body.instance);
|
|
1210
|
+
return createErrorResult(code, message, response.status, {
|
|
1211
|
+
body,
|
|
1212
|
+
...type !== null ? { type } : {},
|
|
1213
|
+
...title !== null ? { title } : {},
|
|
1214
|
+
...instance !== null ? { instance } : {},
|
|
1215
|
+
detail: stringValue(body.detail),
|
|
1216
|
+
param: stringValue(body.param),
|
|
1217
|
+
...currentRevision !== void 0 ? { currentRevision } : {},
|
|
1218
|
+
requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
|
|
1219
|
+
suggestion: stringValue(body.suggestion),
|
|
1220
|
+
retryable: booleanValue(body.retryable),
|
|
1221
|
+
headers: responseHeaders
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1038
1224
|
function headersToObject(headers) {
|
|
1039
1225
|
const result = {};
|
|
1040
1226
|
headers.forEach((value, key) => {
|