drop2run 0.9.0 → 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 +15 -0
- package/dist/index.js +180 -43
- package/dist/{package-LdzYr7IV.js → package-CGI7Yt81.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,9 @@ drop2run rm <site> --yes Delete a site and everything on it
|
|
|
32
32
|
drop2run comments [site] Read the comments left on a site's pages
|
|
33
33
|
drop2run comments reply <thread> <text> Answer a thread as the owner [--resolve]
|
|
34
34
|
drop2run comments resolve|reopen <thread> Resolve a thread, or open it again
|
|
35
|
+
drop2run people [site] Who the site is shared with
|
|
36
|
+
drop2run invite <email>... [--role comment] Share it with people; each is emailed
|
|
37
|
+
drop2run uninvite <email> Take someone off the list
|
|
35
38
|
drop2run token list List your access tokens
|
|
36
39
|
drop2run whoami Check the token and whose it is
|
|
37
40
|
drop2run where Show which token source is in use
|
|
@@ -40,6 +43,18 @@ drop2run --version Print the version
|
|
|
40
43
|
|
|
41
44
|
`--json` on any command prints machine-readable output instead of text.
|
|
42
45
|
|
|
46
|
+
## Sharing for review
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
drop2run invite dana@example.com --role comment --site calm-cedar # emailed an invitation
|
|
50
|
+
drop2run set invite-only on --site calm-cedar # only the list gets in
|
|
51
|
+
drop2run set comments invited --site calm-cedar # the people invited to comment can
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`drop2run people` shows the list and `drop2run uninvite` takes someone off it. `set comments anyone` lets
|
|
55
|
+
anybody signed in comment instead, on a site you keep public or behind a password. Both are plan features,
|
|
56
|
+
refused with the reason where the plan lacks them.
|
|
57
|
+
|
|
43
58
|
## Comments
|
|
44
59
|
|
|
45
60
|
On a plan that includes comments, people viewing a site can pin comments to its pages. `drop2run comments`
|
package/dist/index.js
CHANGED
|
@@ -190,10 +190,7 @@ async function folderFromListing(credentials, siteId, subdomain) {
|
|
|
190
190
|
}
|
|
191
191
|
async function getSite(credentials, siteId) {
|
|
192
192
|
const [site, invitedOnly] = await Promise.all([
|
|
193
|
-
call(
|
|
194
|
-
credentials,
|
|
195
|
-
`sites/${encodeURIComponent(siteId)}`
|
|
196
|
-
),
|
|
193
|
+
call(credentials, `sites/${encodeURIComponent(siteId)}`),
|
|
197
194
|
readInvitedOnly(credentials, siteId)
|
|
198
195
|
]);
|
|
199
196
|
const folderId = site.folderId !== void 0 ? site.folderId : await folderFromListing(credentials, site.siteId, site.subdomain);
|
|
@@ -215,6 +212,8 @@ async function getSite(credentials, siteId) {
|
|
|
215
212
|
expiresAt: site.expiresAt,
|
|
216
213
|
expiryAction: actionOf(site.expiresAt, site.expiryAction ?? ""),
|
|
217
214
|
scheduledExpiryAvailable: site.scheduledExpiryAvailable,
|
|
215
|
+
comments: audienceOf(site.commentAudience),
|
|
216
|
+
commentsAvailable: site.feedbackAvailable === true,
|
|
218
217
|
folderId,
|
|
219
218
|
deploys: site.deploys.map((deploy2) => ({
|
|
220
219
|
deployId: deploy2.deployId,
|
|
@@ -239,9 +238,11 @@ async function updateSiteSettings(credentials, siteId, change) {
|
|
|
239
238
|
if (change.expiryAction !== void 0) body.expiryAction = change.expiryAction;
|
|
240
239
|
if (change.formsEnabled !== void 0) body.formsEnabled = change.formsEnabled;
|
|
241
240
|
if (change.folderId !== void 0) body.folderId = change.folderId;
|
|
241
|
+
if (change.invitedOnly !== void 0) body.invitedOnly = change.invitedOnly;
|
|
242
|
+
if (change.comments !== void 0) body.commentAudience = API_AUDIENCE[change.comments];
|
|
242
243
|
if (Object.keys(body).length === 0) throw new Error("Nothing to change — name a setting.");
|
|
243
244
|
const wasInvitedOnly = change.password !== void 0 && change.password !== "" ? await readInvitedOnly(credentials, siteId) : null;
|
|
244
|
-
const settings = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
|
|
245
|
+
const { commentAudience, ...settings } = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
|
|
245
246
|
method: "PATCH",
|
|
246
247
|
body: JSON.stringify(body)
|
|
247
248
|
});
|
|
@@ -250,9 +251,19 @@ async function updateSiteSettings(credentials, siteId, change) {
|
|
|
250
251
|
...settings,
|
|
251
252
|
expiryAction: actionOf(settings.expiresAt, settings.expiryAction ?? ""),
|
|
252
253
|
invitedOnly,
|
|
254
|
+
comments: audienceOf(commentAudience),
|
|
253
255
|
replacedInviteOnly: wasInvitedOnly === true && invitedOnly === false
|
|
254
256
|
};
|
|
255
257
|
}
|
|
258
|
+
const API_AUDIENCE = {
|
|
259
|
+
off: "Off",
|
|
260
|
+
invited: "Invited",
|
|
261
|
+
anyone: "Anyone"
|
|
262
|
+
};
|
|
263
|
+
function audienceOf(value) {
|
|
264
|
+
const lower = value?.toLowerCase();
|
|
265
|
+
return lower === "off" || lower === "invited" || lower === "anyone" ? lower : null;
|
|
266
|
+
}
|
|
256
267
|
async function setSitePaused(credentials, siteId, action) {
|
|
257
268
|
const state = await call(
|
|
258
269
|
credentials,
|
|
@@ -371,6 +382,34 @@ async function setCommentResolved(credentials, siteId, threadId, resolved) {
|
|
|
371
382
|
{ method: "POST" }
|
|
372
383
|
);
|
|
373
384
|
}
|
|
385
|
+
const PEOPLE_PER_REQUEST = 50;
|
|
386
|
+
async function listPeople(credentials, siteId) {
|
|
387
|
+
const page = await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers?pageSize=${PEOPLE_PER_REQUEST}`);
|
|
388
|
+
return {
|
|
389
|
+
invitedOnly: page.invitedOnly,
|
|
390
|
+
total: page.total,
|
|
391
|
+
limit: page.limit,
|
|
392
|
+
people: page.viewers.map((row) => ({
|
|
393
|
+
email: row.email,
|
|
394
|
+
role: enumName(row.role),
|
|
395
|
+
owner: row.owner,
|
|
396
|
+
status: enumName(row.status)
|
|
397
|
+
}))
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
async function invitePeople(credentials, siteId, emails, role) {
|
|
401
|
+
return await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers`, {
|
|
402
|
+
method: "POST",
|
|
403
|
+
body: JSON.stringify({ emails, role: role === "comment" ? "Comment" : "View", notify: true })
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
async function removePerson(credentials, siteId, email) {
|
|
407
|
+
await call(
|
|
408
|
+
credentials,
|
|
409
|
+
`sites/${encodeURIComponent(siteId)}/viewers?email=${encodeURIComponent(email)}`,
|
|
410
|
+
{ method: "DELETE" }
|
|
411
|
+
);
|
|
412
|
+
}
|
|
374
413
|
const VERSIONS_LISTED = 10;
|
|
375
414
|
function servingModeOf$1(site) {
|
|
376
415
|
if (site.spaMode) return "spa";
|
|
@@ -399,6 +438,7 @@ function describeSite(site, folderPath) {
|
|
|
399
438
|
` mode ${servingModeOf$1(site)}${site.modeIsManual ? "" : " (detected on each publish)"}`,
|
|
400
439
|
` password ${site.passwordProtected ? "on" : "off"}${unavailable(site.passwordProtectionAvailable)}`,
|
|
401
440
|
` forms ${site.formsEnabled ? "on" : "off"}${unavailable(site.formsAvailable)}`,
|
|
441
|
+
` comments ${site.comments ?? "unknown"}${unavailable(site.commentsAvailable)}`,
|
|
402
442
|
` takedown ${site.expiresAt === null ? "none" : `${site.expiresAt} (${site.expiryAction})`}${unavailable(site.scheduledExpiryAvailable)}`,
|
|
403
443
|
` folder ${site.folderId === null ? "(top level)" : folderPath ?? site.folderId}`,
|
|
404
444
|
"",
|
|
@@ -874,13 +914,14 @@ const FILE_TYPES = [
|
|
|
874
914
|
reader: true,
|
|
875
915
|
editable: true
|
|
876
916
|
},
|
|
877
|
-
// Source the viewer highlights.
|
|
917
|
+
// Source the viewer highlights. Inside a documents site a link straight to one opens it in the
|
|
918
|
+
// reader; on an ordinary site, and with `?raw`, it hands over the file.
|
|
878
919
|
{
|
|
879
920
|
ext: ".css",
|
|
880
921
|
mime: "text/css; charset=utf-8",
|
|
881
922
|
kind: "code",
|
|
882
923
|
document: false,
|
|
883
|
-
reader:
|
|
924
|
+
reader: true,
|
|
884
925
|
editable: true
|
|
885
926
|
},
|
|
886
927
|
{
|
|
@@ -888,7 +929,7 @@ const FILE_TYPES = [
|
|
|
888
929
|
mime: "text/javascript; charset=utf-8",
|
|
889
930
|
kind: "code",
|
|
890
931
|
document: false,
|
|
891
|
-
reader:
|
|
932
|
+
reader: true,
|
|
892
933
|
editable: true
|
|
893
934
|
},
|
|
894
935
|
{
|
|
@@ -896,7 +937,7 @@ const FILE_TYPES = [
|
|
|
896
937
|
mime: null,
|
|
897
938
|
kind: "code",
|
|
898
939
|
document: false,
|
|
899
|
-
reader:
|
|
940
|
+
reader: true,
|
|
900
941
|
editable: false
|
|
901
942
|
},
|
|
902
943
|
{
|
|
@@ -904,7 +945,7 @@ const FILE_TYPES = [
|
|
|
904
945
|
mime: null,
|
|
905
946
|
kind: "code",
|
|
906
947
|
document: false,
|
|
907
|
-
reader:
|
|
948
|
+
reader: true,
|
|
908
949
|
editable: false
|
|
909
950
|
},
|
|
910
951
|
{
|
|
@@ -912,7 +953,7 @@ const FILE_TYPES = [
|
|
|
912
953
|
mime: null,
|
|
913
954
|
kind: "code",
|
|
914
955
|
document: false,
|
|
915
|
-
reader:
|
|
956
|
+
reader: true,
|
|
916
957
|
editable: false
|
|
917
958
|
},
|
|
918
959
|
{
|
|
@@ -920,19 +961,18 @@ const FILE_TYPES = [
|
|
|
920
961
|
mime: null,
|
|
921
962
|
kind: "code",
|
|
922
963
|
document: false,
|
|
923
|
-
reader:
|
|
964
|
+
reader: true,
|
|
924
965
|
editable: false
|
|
925
966
|
},
|
|
926
967
|
// The languages an assistant writes most, beyond the web's own. Same row as `.ts`: shown as source
|
|
927
|
-
//
|
|
928
|
-
// link names it directly, and no media type — adding one changes what a browser does with a
|
|
968
|
+
// in the reader, handed over as a file with `?raw` or on an ordinary site, and no media type — adding one changes what a browser does with a
|
|
929
969
|
// published file, which I10 makes a decision of its own rather than a side effect of this list.
|
|
930
970
|
{
|
|
931
971
|
ext: ".py",
|
|
932
972
|
mime: null,
|
|
933
973
|
kind: "code",
|
|
934
974
|
document: false,
|
|
935
|
-
reader:
|
|
975
|
+
reader: true,
|
|
936
976
|
editable: false
|
|
937
977
|
},
|
|
938
978
|
{
|
|
@@ -940,7 +980,7 @@ const FILE_TYPES = [
|
|
|
940
980
|
mime: null,
|
|
941
981
|
kind: "code",
|
|
942
982
|
document: false,
|
|
943
|
-
reader:
|
|
983
|
+
reader: true,
|
|
944
984
|
editable: false
|
|
945
985
|
},
|
|
946
986
|
{
|
|
@@ -948,7 +988,7 @@ const FILE_TYPES = [
|
|
|
948
988
|
mime: null,
|
|
949
989
|
kind: "code",
|
|
950
990
|
document: false,
|
|
951
|
-
reader:
|
|
991
|
+
reader: true,
|
|
952
992
|
editable: false
|
|
953
993
|
},
|
|
954
994
|
{
|
|
@@ -956,7 +996,7 @@ const FILE_TYPES = [
|
|
|
956
996
|
mime: null,
|
|
957
997
|
kind: "code",
|
|
958
998
|
document: false,
|
|
959
|
-
reader:
|
|
999
|
+
reader: true,
|
|
960
1000
|
editable: false
|
|
961
1001
|
},
|
|
962
1002
|
{
|
|
@@ -964,7 +1004,7 @@ const FILE_TYPES = [
|
|
|
964
1004
|
mime: null,
|
|
965
1005
|
kind: "code",
|
|
966
1006
|
document: false,
|
|
967
|
-
reader:
|
|
1007
|
+
reader: true,
|
|
968
1008
|
editable: false
|
|
969
1009
|
},
|
|
970
1010
|
{
|
|
@@ -972,7 +1012,7 @@ const FILE_TYPES = [
|
|
|
972
1012
|
mime: null,
|
|
973
1013
|
kind: "code",
|
|
974
1014
|
document: false,
|
|
975
|
-
reader:
|
|
1015
|
+
reader: true,
|
|
976
1016
|
editable: false
|
|
977
1017
|
},
|
|
978
1018
|
{
|
|
@@ -980,7 +1020,7 @@ const FILE_TYPES = [
|
|
|
980
1020
|
mime: null,
|
|
981
1021
|
kind: "code",
|
|
982
1022
|
document: false,
|
|
983
|
-
reader:
|
|
1023
|
+
reader: true,
|
|
984
1024
|
editable: false
|
|
985
1025
|
},
|
|
986
1026
|
{
|
|
@@ -988,7 +1028,7 @@ const FILE_TYPES = [
|
|
|
988
1028
|
mime: null,
|
|
989
1029
|
kind: "code",
|
|
990
1030
|
document: false,
|
|
991
|
-
reader:
|
|
1031
|
+
reader: true,
|
|
992
1032
|
editable: false
|
|
993
1033
|
},
|
|
994
1034
|
{
|
|
@@ -996,7 +1036,7 @@ const FILE_TYPES = [
|
|
|
996
1036
|
mime: null,
|
|
997
1037
|
kind: "code",
|
|
998
1038
|
document: false,
|
|
999
|
-
reader:
|
|
1039
|
+
reader: true,
|
|
1000
1040
|
editable: false
|
|
1001
1041
|
},
|
|
1002
1042
|
{
|
|
@@ -1004,7 +1044,7 @@ const FILE_TYPES = [
|
|
|
1004
1044
|
mime: null,
|
|
1005
1045
|
kind: "code",
|
|
1006
1046
|
document: false,
|
|
1007
|
-
reader:
|
|
1047
|
+
reader: true,
|
|
1008
1048
|
editable: false
|
|
1009
1049
|
},
|
|
1010
1050
|
{
|
|
@@ -1012,7 +1052,7 @@ const FILE_TYPES = [
|
|
|
1012
1052
|
mime: null,
|
|
1013
1053
|
kind: "code",
|
|
1014
1054
|
document: false,
|
|
1015
|
-
reader:
|
|
1055
|
+
reader: true,
|
|
1016
1056
|
editable: false
|
|
1017
1057
|
},
|
|
1018
1058
|
{
|
|
@@ -1020,7 +1060,7 @@ const FILE_TYPES = [
|
|
|
1020
1060
|
mime: null,
|
|
1021
1061
|
kind: "code",
|
|
1022
1062
|
document: false,
|
|
1023
|
-
reader:
|
|
1063
|
+
reader: true,
|
|
1024
1064
|
editable: false
|
|
1025
1065
|
},
|
|
1026
1066
|
{
|
|
@@ -1028,7 +1068,7 @@ const FILE_TYPES = [
|
|
|
1028
1068
|
mime: null,
|
|
1029
1069
|
kind: "code",
|
|
1030
1070
|
document: false,
|
|
1031
|
-
reader:
|
|
1071
|
+
reader: true,
|
|
1032
1072
|
editable: false
|
|
1033
1073
|
},
|
|
1034
1074
|
{
|
|
@@ -1036,7 +1076,7 @@ const FILE_TYPES = [
|
|
|
1036
1076
|
mime: null,
|
|
1037
1077
|
kind: "code",
|
|
1038
1078
|
document: false,
|
|
1039
|
-
reader:
|
|
1079
|
+
reader: true,
|
|
1040
1080
|
editable: false
|
|
1041
1081
|
},
|
|
1042
1082
|
{
|
|
@@ -1044,7 +1084,7 @@ const FILE_TYPES = [
|
|
|
1044
1084
|
mime: "application/xml",
|
|
1045
1085
|
kind: "code",
|
|
1046
1086
|
document: false,
|
|
1047
|
-
reader:
|
|
1087
|
+
reader: true,
|
|
1048
1088
|
editable: true
|
|
1049
1089
|
},
|
|
1050
1090
|
// Pictures, shown with an img element.
|
|
@@ -1115,25 +1155,25 @@ const FILE_TYPES = [
|
|
|
1115
1155
|
reader: false,
|
|
1116
1156
|
editable: false
|
|
1117
1157
|
},
|
|
1118
|
-
// A customer's own pages.
|
|
1158
|
+
// A customer's own pages. Inside a documents site a navigation opens the reader, which frames the
|
|
1159
|
+
// page; `?raw` and every ordinary site serve the page itself.
|
|
1119
1160
|
{
|
|
1120
1161
|
ext: ".html",
|
|
1121
1162
|
mime: "text/html; charset=utf-8",
|
|
1122
1163
|
kind: "html",
|
|
1123
1164
|
document: false,
|
|
1124
|
-
reader:
|
|
1165
|
+
reader: true,
|
|
1125
1166
|
editable: true
|
|
1126
1167
|
},
|
|
1127
1168
|
{
|
|
1128
|
-
//
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
// introduced as a refactor and a refactor that changes what a URL answers is not one.
|
|
1169
|
+
// An HTML page in every respect the Worker decides by extension: media type, the revalidated
|
|
1170
|
+
// cache policy invariant I7 requires, and the badge's `isHtml`. Missing from `TYPES` it used to
|
|
1171
|
+
// download, which inside the reader meant a frame that downloaded instead of showing a page.
|
|
1132
1172
|
ext: ".htm",
|
|
1133
|
-
mime:
|
|
1173
|
+
mime: "text/html; charset=utf-8",
|
|
1134
1174
|
kind: "html",
|
|
1135
1175
|
document: false,
|
|
1136
|
-
reader:
|
|
1176
|
+
reader: true,
|
|
1137
1177
|
editable: true
|
|
1138
1178
|
},
|
|
1139
1179
|
// Everything below renders as nothing — the viewer offers a download — but still needs a media
|
|
@@ -2525,7 +2565,9 @@ const SETTINGS_USAGE = [
|
|
|
2525
2565
|
" password Read from stdin, or typed at a hidden prompt",
|
|
2526
2566
|
" forms on|off Whether its forms accept submissions",
|
|
2527
2567
|
" expires <ISO 8601 instant> When it comes down; add --then delete to delete rather than pause",
|
|
2528
|
-
" folder <path|id|root> Which folder it is filed in"
|
|
2568
|
+
" folder <path|id|root> Which folder it is filed in",
|
|
2569
|
+
" invite-only on|off Whether only the people on its list can open it",
|
|
2570
|
+
" comments off|invited|anyone Who may comment on its pages"
|
|
2529
2571
|
].join("\n");
|
|
2530
2572
|
function settingChange(setting, values, then) {
|
|
2531
2573
|
const value = setting === "name" ? values.join(" ").trim() : values[0];
|
|
@@ -2573,6 +2615,14 @@ function settingChange(setting, values, then) {
|
|
|
2573
2615
|
}
|
|
2574
2616
|
case "folder":
|
|
2575
2617
|
return value ? { folder: value } : needs("Clients/Acme");
|
|
2618
|
+
case "invite-only":
|
|
2619
|
+
if (value === "on" || value === "off") return { change: { invitedOnly: value === "on" } };
|
|
2620
|
+
return { refusal: "`set invite-only` takes `on` or `off`." };
|
|
2621
|
+
case "comments":
|
|
2622
|
+
if (value === "off" || value === "invited" || value === "anyone") {
|
|
2623
|
+
return { change: { comments: value } };
|
|
2624
|
+
}
|
|
2625
|
+
return { refusal: "`set comments` takes `off`, `invited` or `anyone`." };
|
|
2576
2626
|
default:
|
|
2577
2627
|
return {
|
|
2578
2628
|
refusal: `${setting === void 0 ? "Name a setting." : `\`${setting}\` is not a setting.`} Settings:
|
|
@@ -2650,9 +2700,14 @@ async function applied(credentials, siteId, subdomain, change) {
|
|
|
2650
2700
|
` access ${accessOf(settings)}`,
|
|
2651
2701
|
` mode ${servingModeOf$1(settings)}`,
|
|
2652
2702
|
` forms ${settings.formsEnabled ? "on" : "off"}`,
|
|
2703
|
+
` comments ${settings.comments ?? "unknown"}`,
|
|
2653
2704
|
` takedown ${takedown}`,
|
|
2654
2705
|
...settings.replacedInviteOnly ? [
|
|
2655
|
-
"The password replaced invite-only: the people on its list can no longer get in without it. The list is kept
|
|
2706
|
+
"The password replaced invite-only: the people on its list can no longer get in without it. The list is kept; `drop2run set invite-only on` switches back."
|
|
2707
|
+
] : [],
|
|
2708
|
+
// The one combination that reads as on and does nothing, which the API keeps on purpose.
|
|
2709
|
+
...settings.comments === "invited" && settings.invitedOnly === false ? [
|
|
2710
|
+
"Comments are for invited people, but the site is not invite-only, so nobody can comment yet. Run `drop2run set invite-only on`, or `drop2run set comments anyone`."
|
|
2656
2711
|
] : [],
|
|
2657
2712
|
settings.live ? "Reaching every edge takes up to about a minute." : "Nothing is published yet, so this applies from the first publish."
|
|
2658
2713
|
].join("\n"),
|
|
@@ -2678,6 +2733,72 @@ async function tokens() {
|
|
|
2678
2733
|
return failure(error instanceof Error ? error.message : String(error));
|
|
2679
2734
|
}
|
|
2680
2735
|
}
|
|
2736
|
+
async function people(site) {
|
|
2737
|
+
const found = credentialsOr();
|
|
2738
|
+
if ("result" in found) return found.result;
|
|
2739
|
+
const target = siteOr(site, "drop2run people <subdomain>");
|
|
2740
|
+
if ("result" in target) return target.result;
|
|
2741
|
+
try {
|
|
2742
|
+
const summary = await findSite(found.credentials, target.named);
|
|
2743
|
+
const list2 = await listPeople(found.credentials, summary.siteId);
|
|
2744
|
+
const head = `${list2.total} of ${list2.limit} on ${summary.subdomain}'s list. ` + (list2.invitedOnly ? "Only they can open it." : "It is not invite-only, so anybody with the link can open it — `drop2run set invite-only on`.");
|
|
2745
|
+
const rows = list2.people.map(
|
|
2746
|
+
(person) => `${person.email} ${person.role}${person.owner ? ", owner" : ""} ${person.status}`
|
|
2747
|
+
);
|
|
2748
|
+
return { text: [head, ...rows].join("\n"), json: list2, code: 0 };
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
async function invite(emails, site, role) {
|
|
2754
|
+
const found = credentialsOr();
|
|
2755
|
+
if ("result" in found) return found.result;
|
|
2756
|
+
if (emails.length === 0)
|
|
2757
|
+
return failure("Name at least one address: `drop2run invite dana@example.com`.");
|
|
2758
|
+
if (role !== void 0 && role !== "view" && role !== "comment") {
|
|
2759
|
+
return failure("`--role` is `view` or `comment`.");
|
|
2760
|
+
}
|
|
2761
|
+
const target = siteOr(site, "drop2run invite <email> --site <subdomain>");
|
|
2762
|
+
if ("result" in target) return target.result;
|
|
2763
|
+
try {
|
|
2764
|
+
const summary = await findSite(found.credentials, target.named);
|
|
2765
|
+
const invited = await invitePeople(
|
|
2766
|
+
found.credentials,
|
|
2767
|
+
summary.siteId,
|
|
2768
|
+
emails,
|
|
2769
|
+
role === "comment" ? "comment" : "view"
|
|
2770
|
+
);
|
|
2771
|
+
const list2 = await listPeople(found.credentials, summary.siteId);
|
|
2772
|
+
return {
|
|
2773
|
+
text: [
|
|
2774
|
+
invited.added.length === 0 ? `Nobody new on ${summary.subdomain}'s list.` : `Invited ${invited.added.join(", ")} to ${summary.subdomain} — each was emailed.`,
|
|
2775
|
+
...invited.alreadyListed.length === 0 ? [] : [`Already on the list, not emailed again: ${invited.alreadyListed.join(", ")}.`],
|
|
2776
|
+
...list2.invitedOnly ? [] : [
|
|
2777
|
+
"The site is not invite-only yet, so the list does not decide who opens it. `drop2run set invite-only on` does."
|
|
2778
|
+
]
|
|
2779
|
+
].join("\n"),
|
|
2780
|
+
json: invited,
|
|
2781
|
+
code: 0
|
|
2782
|
+
};
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
async function uninvite(email, site) {
|
|
2788
|
+
const found = credentialsOr();
|
|
2789
|
+
if ("result" in found) return found.result;
|
|
2790
|
+
if (email === void 0)
|
|
2791
|
+
return failure("Name the address: `drop2run uninvite dana@example.com`.");
|
|
2792
|
+
const target = siteOr(site, "drop2run uninvite <email> --site <subdomain>");
|
|
2793
|
+
if ("result" in target) return target.result;
|
|
2794
|
+
try {
|
|
2795
|
+
const summary = await findSite(found.credentials, target.named);
|
|
2796
|
+
await removePerson(found.credentials, summary.siteId, email);
|
|
2797
|
+
return { text: `Removed ${email} from ${summary.subdomain}'s list.`, json: { email }, code: 0 };
|
|
2798
|
+
} catch (error) {
|
|
2799
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2681
2802
|
const COMMENTS_USAGE = [
|
|
2682
2803
|
" drop2run comments [site] [--status open|resolved|all] [--path /page]",
|
|
2683
2804
|
" drop2run comments reply <thread> <text> [--resolve] [--site X]",
|
|
@@ -2808,6 +2929,9 @@ const HELP = `drop2run — publish a static site from the command line
|
|
|
2808
2929
|
drop2run resume [site] Put a paused site back on the air
|
|
2809
2930
|
drop2run rollback <deployId> [--site X] Put an earlier version back live
|
|
2810
2931
|
drop2run rm <site> --yes Delete a site and everything on it
|
|
2932
|
+
drop2run people [site] Who the site is shared with
|
|
2933
|
+
drop2run invite <email>... [--role comment] Share it with people; each is emailed
|
|
2934
|
+
drop2run uninvite <email> Take someone off the list
|
|
2811
2935
|
drop2run comments [site] Read the comments left on a site's pages
|
|
2812
2936
|
drop2run comments reply <thread> <text> Answer a thread as the owner [--resolve]
|
|
2813
2937
|
drop2run comments resolve|reopen <thread> Resolve a thread, or open it again
|
|
@@ -2829,6 +2953,7 @@ Flags
|
|
|
2829
2953
|
--status X Which comment threads: open (the default), resolved or all
|
|
2830
2954
|
--path X Only the comments on one page, such as /pricing
|
|
2831
2955
|
--resolve Resolve the thread with the reply (\`comments reply\` only)
|
|
2956
|
+
--role X What invited people may do: view (the default) or comment
|
|
2832
2957
|
|
|
2833
2958
|
Comments
|
|
2834
2959
|
\`comments reply\`, \`resolve\` and \`reopen\` act on --site, else the site in
|
|
@@ -2847,6 +2972,11 @@ Settings
|
|
|
2847
2972
|
set forms on|off Whether its forms accept submissions
|
|
2848
2973
|
set expires <ISO 8601> Take it down then, e.g. 2026-12-31T09:00:00Z
|
|
2849
2974
|
set folder <path|id|root> Move it to a folder, or root for the top level
|
|
2975
|
+
set invite-only on|off Only the people on its list (\`invite\`) can open it;
|
|
2976
|
+
replaces a password
|
|
2977
|
+
set comments off|invited|anyone
|
|
2978
|
+
Who may comment: nobody, people invited to
|
|
2979
|
+
comment, or anybody signed in
|
|
2850
2980
|
|
|
2851
2981
|
\`set\` and \`unset\` act on --site, else the site in drop2run.json. A
|
|
2852
2982
|
setting your plan does not include is refused with the reason, and
|
|
@@ -2879,7 +3009,7 @@ function flagValue(args, flag) {
|
|
|
2879
3009
|
const value = args[at + 1];
|
|
2880
3010
|
return value === void 0 || value.startsWith("-") ? void 0 : value;
|
|
2881
3011
|
}
|
|
2882
|
-
const VALUE_FLAGS = ["--site", "--subdomain", "--folder", "--then", "--status", "--path"];
|
|
3012
|
+
const VALUE_FLAGS = ["--site", "--subdomain", "--folder", "--then", "--status", "--path", "--role"];
|
|
2883
3013
|
function positionals(argv) {
|
|
2884
3014
|
const found = [];
|
|
2885
3015
|
for (let at = 0; at < argv.length; at++) {
|
|
@@ -2928,7 +3058,7 @@ async function run(commandLine) {
|
|
|
2928
3058
|
const positional = [...positionals(argv), ...literal];
|
|
2929
3059
|
const [command, ...rest] = positional;
|
|
2930
3060
|
if (argv.includes("--version")) {
|
|
2931
|
-
const { version } = await import("./package-
|
|
3061
|
+
const { version } = await import("./package-CGI7Yt81.js").then(
|
|
2932
3062
|
(module) => module.default
|
|
2933
3063
|
);
|
|
2934
3064
|
return { text: version, json: { version }, code: 0 };
|
|
@@ -2963,11 +3093,12 @@ async function run(commandLine) {
|
|
|
2963
3093
|
status: flagValue(argv, "--status"),
|
|
2964
3094
|
path: flagValue(argv, "--path"),
|
|
2965
3095
|
resolve: argv.includes("--resolve")
|
|
2966
|
-
}
|
|
3096
|
+
},
|
|
3097
|
+
flagValue(argv, "--role")
|
|
2967
3098
|
);
|
|
2968
3099
|
return json ? { ...result, text: JSON.stringify(result.json, null, 2) } : result;
|
|
2969
3100
|
}
|
|
2970
|
-
async function dispatch(command, rest, site, json, device, confirmed, newSite = {}, then = void 0, commentOptions = {}) {
|
|
3101
|
+
async function dispatch(command, rest, site, json, device, confirmed, newSite = {}, then = void 0, commentOptions = {}, role = void 0) {
|
|
2971
3102
|
switch (command) {
|
|
2972
3103
|
case "login": {
|
|
2973
3104
|
const print = json ? () => {
|
|
@@ -3004,6 +3135,12 @@ async function dispatch(command, rest, site, json, device, confirmed, newSite =
|
|
|
3004
3135
|
return await pauseOrResume(command, rest[0] ?? site);
|
|
3005
3136
|
case "rollback":
|
|
3006
3137
|
return await rollback(rest[0], site);
|
|
3138
|
+
case "people":
|
|
3139
|
+
return await people(rest[0] ?? site);
|
|
3140
|
+
case "invite":
|
|
3141
|
+
return await invite(rest, site, role);
|
|
3142
|
+
case "uninvite":
|
|
3143
|
+
return await uninvite(rest[0], site);
|
|
3007
3144
|
case "rm":
|
|
3008
3145
|
return await remove(rest[0] ?? site, confirmed);
|
|
3009
3146
|
case "comments":
|