drop2run 0.5.0 → 0.6.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 +13 -0
- package/dist/index.js +240 -103
- package/dist/{package-DXmy47q4.js → package-bzaCMW_E.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ drop2run logout Remove the stored token
|
|
|
18
18
|
drop2run init [dir] [--site <subdomain>] Tie this folder to a site
|
|
19
19
|
drop2run deploy [dir] [--site <subdomain>] Publish a folder
|
|
20
20
|
drop2run init|deploy --subdomain <name> Create a new site under a name you pick
|
|
21
|
+
drop2run init|deploy --folder <path> File the new site in one of your folders
|
|
21
22
|
drop2run ls List your sites
|
|
23
|
+
drop2run folders List your folders, as --folder takes them
|
|
22
24
|
drop2run open [site] Open a site in a browser
|
|
23
25
|
drop2run rollback <deployId> [--site X] Put an earlier version back live
|
|
24
26
|
drop2run rm <site> --yes Delete a site and everything on it
|
|
@@ -52,6 +54,17 @@ which is not undoable.
|
|
|
52
54
|
`--site` never creates anything: it finds a site you already have, and fails when nothing matches. That
|
|
53
55
|
is what keeps a subdomain typed one letter wrong from quietly becoming a second site.
|
|
54
56
|
|
|
57
|
+
`--folder` files the site being created in one of your dashboard folders — by path, as `drop2run folders`
|
|
58
|
+
prints it, or by folder id:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
drop2run init dist --folder Clients/Acme
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Case does not matter. It never creates a folder and never moves an existing site, so it cannot be given
|
|
65
|
+
with `--site`, or to `deploy` in a project whose `drop2run.json` already names a site; a folder that does
|
|
66
|
+
not exist is refused with the list of those that do, before any site is made.
|
|
67
|
+
|
|
55
68
|
`deploy` never creates a second site while a `drop2run.json` sits next to it — that would leave the real
|
|
56
69
|
site untouched and you looking at a URL you did not expect.
|
|
57
70
|
|
package/dist/index.js
CHANGED
|
@@ -5,70 +5,6 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
import { randomBytes, createHash } from "node:crypto";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import { realpath, readdir, stat, readFile } from "node:fs/promises";
|
|
8
|
-
async function call(credentials, path, init2 = {}) {
|
|
9
|
-
const response = await fetch(`${credentials.apiBaseUrl}/${path}`, {
|
|
10
|
-
...init2,
|
|
11
|
-
headers: {
|
|
12
|
-
...init2.headers,
|
|
13
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
14
|
-
...init2.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
const problem = await response.json().catch(() => null);
|
|
19
|
-
throw new Error(problem?.detail ?? `The API answered ${response.status}.`);
|
|
20
|
-
}
|
|
21
|
-
return await response.json();
|
|
22
|
-
}
|
|
23
|
-
async function findSite(credentials, site) {
|
|
24
|
-
const wanted = site.trim().toLowerCase();
|
|
25
|
-
const found = (await listSites(credentials)).find(
|
|
26
|
-
(candidate) => candidate.siteId.toLowerCase() === wanted || candidate.subdomain.toLowerCase() === wanted
|
|
27
|
-
);
|
|
28
|
-
if (found === void 0) {
|
|
29
|
-
throw new Error(`No site of yours is called "${site}". \`drop2run ls\` shows what exists.`);
|
|
30
|
-
}
|
|
31
|
-
return found;
|
|
32
|
-
}
|
|
33
|
-
async function deleteSite(credentials, siteId) {
|
|
34
|
-
const response = await fetch(`${credentials.apiBaseUrl}/sites/${encodeURIComponent(siteId)}`, {
|
|
35
|
-
method: "DELETE",
|
|
36
|
-
headers: { Authorization: `Bearer ${credentials.token}` }
|
|
37
|
-
});
|
|
38
|
-
if (!response.ok) {
|
|
39
|
-
const problem = await response.json().catch(() => null);
|
|
40
|
-
throw new Error(problem?.detail ?? `The API answered ${response.status}.`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
async function promoteDeploy(credentials, siteId, deployId) {
|
|
44
|
-
return await call(
|
|
45
|
-
credentials,
|
|
46
|
-
`sites/${encodeURIComponent(siteId)}/deploys/${encodeURIComponent(deployId)}/promote`,
|
|
47
|
-
{ method: "POST" }
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
async function listTokens(credentials) {
|
|
51
|
-
const body = await call(credentials, "tokens");
|
|
52
|
-
return body.tokens;
|
|
53
|
-
}
|
|
54
|
-
async function listSites(credentials) {
|
|
55
|
-
const body = await call(credentials, "sites");
|
|
56
|
-
return body.sites;
|
|
57
|
-
}
|
|
58
|
-
async function createSite(credentials, subdomain) {
|
|
59
|
-
const created = await call(
|
|
60
|
-
credentials,
|
|
61
|
-
"sites",
|
|
62
|
-
{
|
|
63
|
-
method: "POST",
|
|
64
|
-
// An absent field rather than a null one, because the API reads null, empty and missing the
|
|
65
|
-
// same way and a body carrying only what was asked for is the one that reads correctly in a
|
|
66
|
-
// log.
|
|
67
|
-
body: JSON.stringify(subdomain === void 0 ? {} : { subdomain })
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
return { ...created, name: null };
|
|
71
|
-
}
|
|
72
8
|
const TOKEN_VARIABLE = "DROP2RUN_TOKEN";
|
|
73
9
|
function configPath() {
|
|
74
10
|
return join(homedir(), ".config", "drop2run", "config.json");
|
|
@@ -161,6 +97,142 @@ function missingCredentialsMessage(surface = "cli") {
|
|
|
161
97
|
"The token is shown once when it is created and cannot be recovered afterwards."
|
|
162
98
|
].join("\n");
|
|
163
99
|
}
|
|
100
|
+
async function call(credentials, path, init2 = {}) {
|
|
101
|
+
const response = await fetch(`${credentials.apiBaseUrl}/${path}`, {
|
|
102
|
+
...init2,
|
|
103
|
+
headers: {
|
|
104
|
+
...init2.headers,
|
|
105
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
106
|
+
...init2.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const problem = await response.json().catch(() => null);
|
|
111
|
+
throw new Error(problem?.detail ?? `The API answered ${response.status}.`);
|
|
112
|
+
}
|
|
113
|
+
return await response.json();
|
|
114
|
+
}
|
|
115
|
+
async function findSite(credentials, site) {
|
|
116
|
+
const found = await lookUpSite(credentials, site);
|
|
117
|
+
if (found === void 0) {
|
|
118
|
+
throw new Error(`No site of yours is called "${site}". \`drop2run ls\` shows what exists.`);
|
|
119
|
+
}
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
const SITE_ID_SHAPE = /^[0-9a-z]{26}$/i;
|
|
123
|
+
async function lookUpSite(credentials, site) {
|
|
124
|
+
const wanted = site.trim().toLowerCase();
|
|
125
|
+
if (wanted === "") return void 0;
|
|
126
|
+
const { sites } = await call(
|
|
127
|
+
credentials,
|
|
128
|
+
`sites?pageSize=${SITES_PER_REQUEST}&q=${encodeURIComponent(wanted)}`
|
|
129
|
+
);
|
|
130
|
+
const bySubdomain = sites.find((candidate) => candidate.subdomain.toLowerCase() === wanted);
|
|
131
|
+
if (bySubdomain !== void 0) return bySubdomain;
|
|
132
|
+
if (!SITE_ID_SHAPE.test(wanted)) return void 0;
|
|
133
|
+
const response = await fetch(
|
|
134
|
+
`${credentials.apiBaseUrl}/sites/${encodeURIComponent(wanted.toUpperCase())}`,
|
|
135
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
136
|
+
);
|
|
137
|
+
if (response.status === 404) return void 0;
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const problem = await response.json().catch(() => null);
|
|
140
|
+
throw new Error(problem?.detail ?? `The API answered ${response.status}.`);
|
|
141
|
+
}
|
|
142
|
+
const detail = await response.json();
|
|
143
|
+
return { siteId: detail.siteId, subdomain: detail.subdomain, url: detail.url, name: detail.name };
|
|
144
|
+
}
|
|
145
|
+
async function deleteSite(credentials, siteId) {
|
|
146
|
+
const response = await fetch(`${credentials.apiBaseUrl}/sites/${encodeURIComponent(siteId)}`, {
|
|
147
|
+
method: "DELETE",
|
|
148
|
+
headers: { Authorization: `Bearer ${credentials.token}` }
|
|
149
|
+
});
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const problem = await response.json().catch(() => null);
|
|
152
|
+
throw new Error(problem?.detail ?? `The API answered ${response.status}.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function promoteDeploy(credentials, siteId, deployId) {
|
|
156
|
+
return await call(
|
|
157
|
+
credentials,
|
|
158
|
+
`sites/${encodeURIComponent(siteId)}/deploys/${encodeURIComponent(deployId)}/promote`,
|
|
159
|
+
{ method: "POST" }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
async function listTokens(credentials) {
|
|
163
|
+
const body = await call(credentials, "tokens");
|
|
164
|
+
return body.tokens;
|
|
165
|
+
}
|
|
166
|
+
const SITES_PER_REQUEST = 100;
|
|
167
|
+
async function listSites(credentials) {
|
|
168
|
+
const body = await call(
|
|
169
|
+
credentials,
|
|
170
|
+
`sites?pageSize=${SITES_PER_REQUEST}`
|
|
171
|
+
);
|
|
172
|
+
return { sites: body.sites, total: body.total };
|
|
173
|
+
}
|
|
174
|
+
async function createSite(credentials, subdomain, folderId) {
|
|
175
|
+
const created = await call(
|
|
176
|
+
credentials,
|
|
177
|
+
"sites",
|
|
178
|
+
{
|
|
179
|
+
method: "POST",
|
|
180
|
+
// Absent fields rather than null ones, because the API reads null, empty and missing the same
|
|
181
|
+
// way and a body carrying only what was asked for is the one that reads correctly in a log.
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
...subdomain === void 0 ? {} : { subdomain },
|
|
184
|
+
...folderId === void 0 ? {} : { folderId }
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
return { ...created, name: null };
|
|
189
|
+
}
|
|
190
|
+
async function listFolders(credentials) {
|
|
191
|
+
const body = await call(credentials, "site-folders");
|
|
192
|
+
const byId = new Map(body.folders.map((folder) => [folder.folderId, folder]));
|
|
193
|
+
const pathOf = (folderId) => {
|
|
194
|
+
const names = [];
|
|
195
|
+
let cursor = byId.get(folderId);
|
|
196
|
+
while (cursor !== void 0 && names.length <= byId.size) {
|
|
197
|
+
names.unshift(cursor.name);
|
|
198
|
+
cursor = cursor.parentId === null ? void 0 : byId.get(cursor.parentId);
|
|
199
|
+
}
|
|
200
|
+
return names.join("/");
|
|
201
|
+
};
|
|
202
|
+
return body.folders.map((folder) => ({ folderId: folder.folderId, path: pathOf(folder.folderId) })).sort((a, b) => a.path.localeCompare(b.path));
|
|
203
|
+
}
|
|
204
|
+
function comparablePath(path) {
|
|
205
|
+
return path.split("/").map((name) => name.trim()).filter((name) => name !== "").join("/").toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
const FOLDERS_LISTED_ON_MISS = 20;
|
|
208
|
+
async function resolveFolder(credentials, folder) {
|
|
209
|
+
const wanted = comparablePath(folder);
|
|
210
|
+
if (wanted === "") {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`"${folder}" does not name a folder. Leave the folder out to put the site at the top level.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const folders2 = await listFolders(credentials);
|
|
216
|
+
const byId = folders2.find((candidate) => candidate.folderId.toLowerCase() === wanted);
|
|
217
|
+
if (byId !== void 0) return byId.folderId;
|
|
218
|
+
const matches = folders2.filter((candidate) => comparablePath(candidate.path) === wanted);
|
|
219
|
+
if (matches.length === 1 && matches[0] !== void 0) return matches[0].folderId;
|
|
220
|
+
if (matches.length > 1) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`More than one folder is at "${folder}", because a folder name can contain "/". Name it by id instead: ${matches.map((match) => match.folderId).join(", ")}.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (folders2.length === 0) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`No folder of yours is called "${folder}" — this account has no folders yet. Make one in the dashboard at ${dashboardUrlFor(credentials.apiBaseUrl)}, or leave the folder out.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const listed = folders2.slice(0, FOLDERS_LISTED_ON_MISS).map((candidate) => candidate.path);
|
|
231
|
+
const more = folders2.length - listed.length;
|
|
232
|
+
throw new Error(
|
|
233
|
+
`No folder of yours is called "${folder}". Your folders: ${listed.join(", ")}${more > 0 ? `, and ${more} more` : ""}.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
164
236
|
const TIMEOUT_MS = 5 * 60 * 1e3;
|
|
165
237
|
function newAttempt() {
|
|
166
238
|
const verifier = randomBytes(32).toString("base64url");
|
|
@@ -1334,20 +1406,23 @@ function escapesRoot(root, candidate) {
|
|
|
1334
1406
|
const inside = relative(root, candidate);
|
|
1335
1407
|
return inside === "" || inside.startsWith("..");
|
|
1336
1408
|
}
|
|
1337
|
-
async function resolveSite(credentials, site,
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
}
|
|
1344
|
-
return await createSite(credentials, subdomain);
|
|
1409
|
+
async function resolveSite(credentials, site, newSite) {
|
|
1410
|
+
const { subdomain, folder } = newSite;
|
|
1411
|
+
if (site !== void 0 && subdomain !== void 0) {
|
|
1412
|
+
throw new Error(
|
|
1413
|
+
"Give either an existing site or a subdomain for a new one, not both: publishing over a site and creating one are different requests."
|
|
1414
|
+
);
|
|
1345
1415
|
}
|
|
1346
|
-
if (site
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1416
|
+
if (site !== void 0 && folder !== void 0) {
|
|
1417
|
+
throw new Error(
|
|
1418
|
+
"A folder only says where a new site goes, so it cannot be given with an existing site. Leave the site out to create one in that folder, or move the existing site from the dashboard."
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
if (site === void 0) {
|
|
1422
|
+
const folderId = folder === void 0 ? void 0 : await resolveFolder(credentials, folder);
|
|
1423
|
+
return await createSite(credentials, subdomain, folderId);
|
|
1424
|
+
}
|
|
1425
|
+
const found = await lookUpSite(credentials, site);
|
|
1351
1426
|
if (found === void 0) {
|
|
1352
1427
|
throw new Error(
|
|
1353
1428
|
`No site of yours is called "${site}". Leave the site out to publish to a new one, or list your sites to see what exists.`
|
|
@@ -1355,8 +1430,8 @@ async function resolveSite(credentials, site, subdomain) {
|
|
|
1355
1430
|
}
|
|
1356
1431
|
return found;
|
|
1357
1432
|
}
|
|
1358
|
-
async function publish(credentials, source, site, onProgress,
|
|
1359
|
-
const target = await resolveSite(credentials, site,
|
|
1433
|
+
async function publish(credentials, source, site, onProgress, newSite = {}) {
|
|
1434
|
+
const target = await resolveSite(credentials, site, newSite);
|
|
1360
1435
|
let files = 0;
|
|
1361
1436
|
let url = target.url;
|
|
1362
1437
|
let unchanged = false;
|
|
@@ -1387,11 +1462,11 @@ async function publish(credentials, source, site, onProgress, subdomain) {
|
|
|
1387
1462
|
});
|
|
1388
1463
|
return { url, siteId: target.siteId, subdomain: target.subdomain, files, unchanged };
|
|
1389
1464
|
}
|
|
1390
|
-
async function publishDirectory(credentials, directory, site, onProgress,
|
|
1465
|
+
async function publishDirectory(credentials, directory, site, onProgress, newSite = {}) {
|
|
1391
1466
|
const found = await stat(directory).catch(() => null);
|
|
1392
1467
|
if (found === null) throw new Error(`There is no folder at ${directory}.`);
|
|
1393
1468
|
if (!found.isDirectory()) throw new Error(`${directory} is a file, not a folder to publish.`);
|
|
1394
|
-
return await publish(credentials, directorySource(directory), site, onProgress,
|
|
1469
|
+
return await publish(credentials, directorySource(directory), site, onProgress, newSite);
|
|
1395
1470
|
}
|
|
1396
1471
|
const silentProgress = Object.assign(() => {
|
|
1397
1472
|
}, { done: () => {
|
|
@@ -1588,33 +1663,67 @@ API: ${found.credentials.apiBaseUrl}`,
|
|
|
1588
1663
|
async function list() {
|
|
1589
1664
|
const found = credentialsOr();
|
|
1590
1665
|
if ("result" in found) return found.result;
|
|
1591
|
-
const sites = await listSites(found.credentials);
|
|
1666
|
+
const { sites, total } = await listSites(found.credentials);
|
|
1592
1667
|
if (sites.length === 0) {
|
|
1593
1668
|
return {
|
|
1594
1669
|
text: "No sites yet. `drop2run deploy` publishes one.",
|
|
1595
|
-
json: { sites: [] },
|
|
1670
|
+
json: { sites: [], total: 0 },
|
|
1596
1671
|
code: 0
|
|
1597
1672
|
};
|
|
1598
1673
|
}
|
|
1674
|
+
const lines = sites.map(
|
|
1675
|
+
(site) => `${site.subdomain} ${site.url}${site.name ? ` ${site.name}` : ""}`
|
|
1676
|
+
);
|
|
1677
|
+
if (total > sites.length) {
|
|
1678
|
+
lines.push(
|
|
1679
|
+
`… and ${(total - sites.length).toLocaleString()} more — this lists the newest ${sites.length.toLocaleString()} of ${total.toLocaleString()}.`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1599
1682
|
return {
|
|
1600
|
-
text:
|
|
1601
|
-
json: { sites },
|
|
1683
|
+
text: lines.join("\n"),
|
|
1684
|
+
json: { sites, total },
|
|
1602
1685
|
code: 0
|
|
1603
1686
|
};
|
|
1604
1687
|
}
|
|
1605
|
-
async function
|
|
1688
|
+
async function folders() {
|
|
1689
|
+
const found = credentialsOr();
|
|
1690
|
+
if ("result" in found) return found.result;
|
|
1691
|
+
try {
|
|
1692
|
+
const all = await listFolders(found.credentials);
|
|
1693
|
+
if (all.length === 0) {
|
|
1694
|
+
return {
|
|
1695
|
+
text: `No folders yet. Make one in the dashboard at ${dashboardUrlFor(found.credentials.apiBaseUrl)}, then file a new site in it with \`--folder\`.`,
|
|
1696
|
+
json: { folders: [] },
|
|
1697
|
+
code: 0
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
return {
|
|
1701
|
+
text: all.map((folder) => `${folder.path} ${folder.folderId}`).join("\n"),
|
|
1702
|
+
json: { folders: all },
|
|
1703
|
+
code: 0
|
|
1704
|
+
};
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
async function deployCommand(directory, site, report = silentProgress, newSite = {}) {
|
|
1606
1710
|
const found = credentialsOr();
|
|
1607
1711
|
if ("result" in found) return found.result;
|
|
1608
1712
|
const project = readProject();
|
|
1609
1713
|
const folder = directory ?? project?.dir ?? ".";
|
|
1610
|
-
const target = subdomain === void 0 ? site ?? project?.siteId : void 0;
|
|
1714
|
+
const target = newSite.subdomain === void 0 ? site ?? project?.siteId : void 0;
|
|
1715
|
+
if (newSite.folder !== void 0 && target !== void 0 && project !== null) {
|
|
1716
|
+
return failure(
|
|
1717
|
+
`This folder's ${PROJECT_FILE} already publishes to ${project.subdomain || project.siteId}, and \`--folder\` only files a new site. Move that site from the dashboard, or add \`--subdomain\` to create a new one in the folder.`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1611
1720
|
try {
|
|
1612
1721
|
const result = await publishDirectory(
|
|
1613
1722
|
found.credentials,
|
|
1614
1723
|
resolve(folder),
|
|
1615
1724
|
target,
|
|
1616
1725
|
report,
|
|
1617
|
-
|
|
1726
|
+
newSite
|
|
1618
1727
|
);
|
|
1619
1728
|
const what = result.unchanged ? "Already up to date — nothing needed publishing." : `Published ${result.files.toLocaleString()} ${result.files === 1 ? "file" : "files"}.`;
|
|
1620
1729
|
return {
|
|
@@ -1629,7 +1738,7 @@ ${result.url}`,
|
|
|
1629
1738
|
report.done();
|
|
1630
1739
|
}
|
|
1631
1740
|
}
|
|
1632
|
-
async function init(directory, site,
|
|
1741
|
+
async function init(directory, site, newSite = {}) {
|
|
1633
1742
|
const found = credentialsOr();
|
|
1634
1743
|
if ("result" in found) return found.result;
|
|
1635
1744
|
if (readProject() !== null) {
|
|
@@ -1638,7 +1747,11 @@ async function init(directory, site, subdomain) {
|
|
|
1638
1747
|
);
|
|
1639
1748
|
}
|
|
1640
1749
|
try {
|
|
1641
|
-
const target = site === void 0 ? await createSite(
|
|
1750
|
+
const target = site === void 0 ? await createSite(
|
|
1751
|
+
found.credentials,
|
|
1752
|
+
newSite.subdomain,
|
|
1753
|
+
newSite.folder === void 0 ? void 0 : await resolveFolder(found.credentials, newSite.folder)
|
|
1754
|
+
) : await findSite(found.credentials, site);
|
|
1642
1755
|
const project = {
|
|
1643
1756
|
siteId: target.siteId,
|
|
1644
1757
|
subdomain: target.subdomain,
|
|
@@ -1777,7 +1890,9 @@ const HELP = `drop2run — publish a static site from the command line
|
|
|
1777
1890
|
drop2run init [dir] [--site <subdomain>] Tie this folder to a site (writes drop2run.json)
|
|
1778
1891
|
drop2run deploy [dir] [--site <subdomain>] Publish a folder (default: . or drop2run.json)
|
|
1779
1892
|
drop2run init|deploy --subdomain <name> Create a new site under a name you pick
|
|
1893
|
+
drop2run init|deploy --folder <path> File the new site in one of your folders
|
|
1780
1894
|
drop2run ls List your sites
|
|
1895
|
+
drop2run folders List your folders, as --folder takes them
|
|
1781
1896
|
drop2run open [site] Open a site in a browser
|
|
1782
1897
|
drop2run rollback <deployId> [--site X] Put an earlier version back live
|
|
1783
1898
|
drop2run rm <site> --yes Delete a site and everything on it
|
|
@@ -1790,6 +1905,8 @@ Flags
|
|
|
1790
1905
|
--json Print machine-readable output instead of text
|
|
1791
1906
|
--site X Act on an existing site rather than the one in drop2run.json
|
|
1792
1907
|
--subdomain X Create a new site under this name (init and deploy only)
|
|
1908
|
+
--folder X File the new site in this folder: a path such as Clients/Acme,
|
|
1909
|
+
or a folder id (init and deploy only)
|
|
1793
1910
|
--device Sign in by approving a code on another machine
|
|
1794
1911
|
--yes Confirm a deletion, which cannot be undone
|
|
1795
1912
|
|
|
@@ -1801,6 +1918,10 @@ Where a site comes from
|
|
|
1801
1918
|
--site only ever finds a site you already have; it never creates one, so a
|
|
1802
1919
|
subdomain typed wrong fails instead of quietly becoming a second site.
|
|
1803
1920
|
|
|
1921
|
+
--folder only applies to a site being created. It never creates a folder
|
|
1922
|
+
and never moves an existing site; make and rearrange folders in the
|
|
1923
|
+
dashboard.
|
|
1924
|
+
|
|
1804
1925
|
Signing in
|
|
1805
1926
|
\`login\` opens a browser and listens on 127.0.0.1, so it needs both on this
|
|
1806
1927
|
machine. Over SSH, in a container or under WSL, use \`login --device\`: it
|
|
@@ -1816,7 +1937,7 @@ function flagValue(args, flag) {
|
|
|
1816
1937
|
const value = args[at + 1];
|
|
1817
1938
|
return value === void 0 || value.startsWith("-") ? void 0 : value;
|
|
1818
1939
|
}
|
|
1819
|
-
const VALUE_FLAGS = ["--site", "--subdomain"];
|
|
1940
|
+
const VALUE_FLAGS = ["--site", "--subdomain", "--folder"];
|
|
1820
1941
|
function positionals(argv) {
|
|
1821
1942
|
const found = [];
|
|
1822
1943
|
for (let at = 0; at < argv.length; at++) {
|
|
@@ -1831,16 +1952,29 @@ function positionals(argv) {
|
|
|
1831
1952
|
}
|
|
1832
1953
|
return found;
|
|
1833
1954
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1955
|
+
const SUBDOMAIN_FLAG = {
|
|
1956
|
+
flag: "--subdomain",
|
|
1957
|
+
example: "my-docs",
|
|
1958
|
+
effect: "`--subdomain` creates a new one",
|
|
1959
|
+
elsewhere: (command) => ` Use \`--site\` to tell \`${command}\` which of your sites to act on.`
|
|
1960
|
+
};
|
|
1961
|
+
const FOLDER_FLAG = {
|
|
1962
|
+
flag: "--folder",
|
|
1963
|
+
example: "Clients/Acme",
|
|
1964
|
+
effect: "`--folder` only files a new one — move an existing site from the dashboard",
|
|
1965
|
+
elsewhere: (command) => command === "ls" || command === "list" ? " `drop2run folders` lists them." : ""
|
|
1966
|
+
};
|
|
1967
|
+
function newSiteFlagProblem(argv, command, site, value, described) {
|
|
1968
|
+
const { flag, example, effect, elsewhere } = described;
|
|
1969
|
+
if (!argv.includes(flag)) return void 0;
|
|
1970
|
+
if (value === void 0) {
|
|
1971
|
+
return `\`${flag}\` needs a value after it, for example \`${flag} ${example}\`.`;
|
|
1838
1972
|
}
|
|
1839
1973
|
if (site !== void 0) {
|
|
1840
|
-
return
|
|
1974
|
+
return `\`--site\` and \`${flag}\` cannot both be given: \`--site\` publishes to a site you already have, and ${effect}. Drop whichever is not what you meant.`;
|
|
1841
1975
|
}
|
|
1842
1976
|
if (command !== "init" && command !== "deploy") {
|
|
1843
|
-
return
|
|
1977
|
+
return `\`${flag}\` only applies to \`init\` and \`deploy\`, which can create a site.` + elsewhere(command);
|
|
1844
1978
|
}
|
|
1845
1979
|
return void 0;
|
|
1846
1980
|
}
|
|
@@ -1849,7 +1983,7 @@ async function run(argv) {
|
|
|
1849
1983
|
const positional = positionals(argv);
|
|
1850
1984
|
const [command, ...rest] = positional;
|
|
1851
1985
|
if (argv.includes("--version")) {
|
|
1852
|
-
const { version } = await import("./package-
|
|
1986
|
+
const { version } = await import("./package-bzaCMW_E.js").then(
|
|
1853
1987
|
(module) => module.default
|
|
1854
1988
|
);
|
|
1855
1989
|
return { text: version, json: { version }, code: 0 };
|
|
@@ -1859,7 +1993,8 @@ async function run(argv) {
|
|
|
1859
1993
|
if (command === void 0) return { text: HELP, json: { help: HELP }, code: 1 };
|
|
1860
1994
|
const site = flagValue(argv, "--site");
|
|
1861
1995
|
const subdomain = flagValue(argv, "--subdomain");
|
|
1862
|
-
const
|
|
1996
|
+
const folder = flagValue(argv, "--folder");
|
|
1997
|
+
const refusal = newSiteFlagProblem(argv, command, site, subdomain, SUBDOMAIN_FLAG) ?? newSiteFlagProblem(argv, command, site, folder, FOLDER_FLAG);
|
|
1863
1998
|
if (refusal !== void 0) {
|
|
1864
1999
|
const failed = { text: refusal, json: { error: refusal }, code: 1 };
|
|
1865
2000
|
return json ? { ...failed, text: JSON.stringify(failed.json, null, 2) } : failed;
|
|
@@ -1871,11 +2006,11 @@ async function run(argv) {
|
|
|
1871
2006
|
json,
|
|
1872
2007
|
argv.includes("--device"),
|
|
1873
2008
|
argv.includes("--yes"),
|
|
1874
|
-
subdomain
|
|
2009
|
+
{ subdomain, folder }
|
|
1875
2010
|
);
|
|
1876
2011
|
return json ? { ...result, text: JSON.stringify(result.json, null, 2) } : result;
|
|
1877
2012
|
}
|
|
1878
|
-
async function dispatch(command, rest, site, json, device, confirmed,
|
|
2013
|
+
async function dispatch(command, rest, site, json, device, confirmed, newSite = {}) {
|
|
1879
2014
|
switch (command) {
|
|
1880
2015
|
case "login": {
|
|
1881
2016
|
const print = json ? () => {
|
|
@@ -1885,18 +2020,20 @@ async function dispatch(command, rest, site, json, device, confirmed, subdomain)
|
|
|
1885
2020
|
case "logout":
|
|
1886
2021
|
return logout();
|
|
1887
2022
|
case "init":
|
|
1888
|
-
return await init(rest[0] ?? ".", site,
|
|
2023
|
+
return await init(rest[0] ?? ".", site, newSite);
|
|
1889
2024
|
case "deploy": {
|
|
1890
2025
|
const report = json ? silentProgress : progressWriter(
|
|
1891
2026
|
(text) => process.stderr.write(text),
|
|
1892
2027
|
process.stderr.isTTY === true,
|
|
1893
2028
|
process.stderr.columns ?? 80
|
|
1894
2029
|
);
|
|
1895
|
-
return await deployCommand(rest[0], site, report,
|
|
2030
|
+
return await deployCommand(rest[0], site, report, newSite);
|
|
1896
2031
|
}
|
|
1897
2032
|
case "ls":
|
|
1898
2033
|
case "list":
|
|
1899
2034
|
return await list();
|
|
2035
|
+
case "folders":
|
|
2036
|
+
return await folders();
|
|
1900
2037
|
case "open":
|
|
1901
2038
|
return await open(rest[0] ?? site);
|
|
1902
2039
|
case "rollback":
|