drop2run 0.3.0 → 0.4.1

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 CHANGED
@@ -16,6 +16,7 @@ drop2run login [--device] Sign in and store a token
16
16
  drop2run logout Remove the stored token
17
17
  drop2run init [dir] [--site <subdomain>] Tie this folder to a site
18
18
  drop2run deploy [dir] [--site <subdomain>] Publish a folder
19
+ drop2run init|deploy --subdomain <name> Create a new site under a name you pick
19
20
  drop2run ls List your sites
20
21
  drop2run open [site] Open a site in a browser
21
22
  drop2run rollback <deployId> [--site X] Put an earlier version back live
@@ -30,14 +31,26 @@ drop2run --version Print the version
30
31
 
31
32
  ## Which site a command acts on
32
33
 
33
- `--site`, then `drop2run.json`, then a new one. `init` writes that file, so this is the whole of a
34
- normal project:
34
+ `--subdomain` creates one under the name you give; otherwise `--site`, then `drop2run.json`, then a new
35
+ site with a generated name. `init` writes that file, so this is the whole of a normal project:
35
36
 
36
37
  ```bash
37
38
  drop2run init dist # creates a site, writes drop2run.json
38
39
  drop2run deploy # publishes dist to it, no arguments
39
40
  ```
40
41
 
42
+ ```bash
43
+ drop2run init dist --subdomain my-docs # same, but you choose the address
44
+ ```
45
+
46
+ Lowercase letters, digits and hyphens, not starting or ending with one. A name that is taken, reserved,
47
+ or past the number of self-picked names your plan allows is refused with the reason. It cannot be changed
48
+ later — a site is reached by the address it was created with — so a wrong name means deleting the site,
49
+ which is not undoable.
50
+
51
+ `--site` never creates anything: it finds a site you already have, and fails when nothing matches. That
52
+ is what keeps a subdomain typed one letter wrong from quietly becoming a second site.
53
+
41
54
  `deploy` never creates a second site while a `drop2run.json` sits next to it — that would leave the real
42
55
  site untouched and you looking at a URL you did not expect.
43
56
 
package/dist/index.js CHANGED
@@ -55,11 +55,17 @@ async function listSites(credentials) {
55
55
  const body = await call(credentials, "sites");
56
56
  return body.sites;
57
57
  }
58
- async function createSite(credentials) {
58
+ async function createSite(credentials, subdomain) {
59
59
  const created = await call(
60
60
  credentials,
61
61
  "sites",
62
- { method: "POST", body: JSON.stringify({}) }
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
+ }
63
69
  );
64
70
  return { ...created, name: null };
65
71
  }
@@ -369,6 +375,7 @@ const ClientErrorCode = {
369
375
  /** An upload kept failing after every retry. */
370
376
  UploadFailed: "upload_failed"
371
377
  };
378
+ const ACCOUNT_HEADER = "X-Drop2Run-Account";
372
379
  const CLAIM_TOKEN_HEADER = "X-Claim-Token";
373
380
  async function prepareDeploy(siteId, files, options, signal) {
374
381
  const manifest = files.map((file) => ({
@@ -405,6 +412,7 @@ async function request(path, body, options, signal) {
405
412
  if (body !== void 0) headers["Content-Type"] = "application/json";
406
413
  if (options.claimToken) headers[CLAIM_TOKEN_HEADER] = options.claimToken;
407
414
  if (options.token) headers.Authorization = `Bearer ${options.token}`;
415
+ if (options.accountId) headers[ACCOUNT_HEADER] = options.accountId;
408
416
  const response = await doFetch(`${base}/${path}`, {
409
417
  method: "POST",
410
418
  headers,
@@ -865,7 +873,15 @@ function escapesRoot(root, candidate) {
865
873
  const inside = relative(root, candidate);
866
874
  return inside === "" || inside.startsWith("..");
867
875
  }
868
- async function resolveSite(credentials, site) {
876
+ async function resolveSite(credentials, site, subdomain) {
877
+ if (subdomain !== void 0) {
878
+ if (site !== void 0) {
879
+ throw new Error(
880
+ "Give either an existing site or a subdomain for a new one, not both: publishing over a site and creating one are different requests."
881
+ );
882
+ }
883
+ return await createSite(credentials, subdomain);
884
+ }
869
885
  if (site === void 0) return await createSite(credentials);
870
886
  const wanted = site.trim().toLowerCase();
871
887
  const found = (await listSites(credentials)).find(
@@ -878,8 +894,8 @@ async function resolveSite(credentials, site) {
878
894
  }
879
895
  return found;
880
896
  }
881
- async function publish(credentials, source, site, onProgress) {
882
- const target = await resolveSite(credentials, site);
897
+ async function publish(credentials, source, site, onProgress, subdomain) {
898
+ const target = await resolveSite(credentials, site, subdomain);
883
899
  let files = 0;
884
900
  let url = target.url;
885
901
  let unchanged = false;
@@ -910,8 +926,11 @@ async function publish(credentials, source, site, onProgress) {
910
926
  });
911
927
  return { url, siteId: target.siteId, subdomain: target.subdomain, files, unchanged };
912
928
  }
913
- function publishDirectory(credentials, directory, site, onProgress) {
914
- return publish(credentials, directorySource(directory), site, onProgress);
929
+ async function publishDirectory(credentials, directory, site, onProgress, subdomain) {
930
+ const found = await stat(directory).catch(() => null);
931
+ if (found === null) throw new Error(`There is no folder at ${directory}.`);
932
+ if (!found.isDirectory()) throw new Error(`${directory} is a file, not a folder to publish.`);
933
+ return await publish(credentials, directorySource(directory), site, onProgress, subdomain);
915
934
  }
916
935
  const silentProgress = Object.assign(() => {
917
936
  }, { done: () => {
@@ -1122,14 +1141,20 @@ async function list() {
1122
1141
  code: 0
1123
1142
  };
1124
1143
  }
1125
- async function deployCommand(directory, site, report = silentProgress) {
1144
+ async function deployCommand(directory, site, report = silentProgress, subdomain) {
1126
1145
  const found = credentialsOr();
1127
1146
  if ("result" in found) return found.result;
1128
1147
  const project = readProject();
1129
- const target = site ?? project?.siteId;
1130
1148
  const folder = directory ?? project?.dir ?? ".";
1149
+ const target = subdomain === void 0 ? site ?? project?.siteId : void 0;
1131
1150
  try {
1132
- const result = await publishDirectory(found.credentials, resolve(folder), target, report);
1151
+ const result = await publishDirectory(
1152
+ found.credentials,
1153
+ resolve(folder),
1154
+ target,
1155
+ report,
1156
+ subdomain
1157
+ );
1133
1158
  const what = result.unchanged ? "Already up to date — nothing needed publishing." : `Published ${result.files.toLocaleString()} ${result.files === 1 ? "file" : "files"}.`;
1134
1159
  return {
1135
1160
  text: `${what}
@@ -1143,7 +1168,7 @@ ${result.url}`,
1143
1168
  report.done();
1144
1169
  }
1145
1170
  }
1146
- async function init(directory, site) {
1171
+ async function init(directory, site, subdomain) {
1147
1172
  const found = credentialsOr();
1148
1173
  if ("result" in found) return found.result;
1149
1174
  if (readProject() !== null) {
@@ -1152,7 +1177,7 @@ async function init(directory, site) {
1152
1177
  );
1153
1178
  }
1154
1179
  try {
1155
- const target = site === void 0 ? await createSite(found.credentials) : await findSite(found.credentials, site);
1180
+ const target = site === void 0 ? await createSite(found.credentials, subdomain) : await findSite(found.credentials, site);
1156
1181
  const project = {
1157
1182
  siteId: target.siteId,
1158
1183
  subdomain: target.subdomain,
@@ -1290,6 +1315,7 @@ const HELP = `drop2run — publish a static site from the command line
1290
1315
  drop2run logout Remove the stored token
1291
1316
  drop2run init [dir] [--site <subdomain>] Tie this folder to a site (writes drop2run.json)
1292
1317
  drop2run deploy [dir] [--site <subdomain>] Publish a folder (default: . or drop2run.json)
1318
+ drop2run init|deploy --subdomain <name> Create a new site under a name you pick
1293
1319
  drop2run ls List your sites
1294
1320
  drop2run open [site] Open a site in a browser
1295
1321
  drop2run rollback <deployId> [--site X] Put an earlier version back live
@@ -1300,14 +1326,19 @@ const HELP = `drop2run — publish a static site from the command line
1300
1326
  drop2run --version Print the version
1301
1327
 
1302
1328
  Flags
1303
- --json Print machine-readable output instead of text
1304
- --site X Act on an existing site rather than the one in drop2run.json
1305
- --device Sign in by approving a code on another machine
1306
- --yes Confirm a deletion, which cannot be undone
1329
+ --json Print machine-readable output instead of text
1330
+ --site X Act on an existing site rather than the one in drop2run.json
1331
+ --subdomain X Create a new site under this name (init and deploy only)
1332
+ --device Sign in by approving a code on another machine
1333
+ --yes Confirm a deletion, which cannot be undone
1307
1334
 
1308
1335
  Where a site comes from
1309
- --site, then drop2run.json, then a new one. \`init\` writes that file, so
1310
- \`deploy\` in the same folder needs no arguments at all.
1336
+ --subdomain creates one under the name you give. Otherwise: --site, then
1337
+ drop2run.json, then a new site with a generated name. \`init\` writes that
1338
+ file, so \`deploy\` in the same folder needs no arguments at all.
1339
+
1340
+ --site only ever finds a site you already have; it never creates one, so a
1341
+ subdomain typed wrong fails instead of quietly becoming a second site.
1311
1342
 
1312
1343
  Signing in
1313
1344
  \`login\` opens a browser and listens on 127.0.0.1, so it needs both on this
@@ -1324,12 +1355,40 @@ function flagValue(args, flag) {
1324
1355
  const value = args[at + 1];
1325
1356
  return value === void 0 || value.startsWith("-") ? void 0 : value;
1326
1357
  }
1358
+ const VALUE_FLAGS = ["--site", "--subdomain"];
1359
+ function positionals(argv) {
1360
+ const found = [];
1361
+ for (let at = 0; at < argv.length; at++) {
1362
+ const argument = argv[at];
1363
+ if (argument === void 0) continue;
1364
+ if (VALUE_FLAGS.includes(argument)) {
1365
+ const value = argv[at + 1];
1366
+ if (value !== void 0 && !value.startsWith("-")) at++;
1367
+ continue;
1368
+ }
1369
+ if (!argument.startsWith("-")) found.push(argument);
1370
+ }
1371
+ return found;
1372
+ }
1373
+ function subdomainFlagProblem(argv, command, site, subdomain) {
1374
+ if (!argv.includes("--subdomain")) return void 0;
1375
+ if (subdomain === void 0) {
1376
+ return "`--subdomain` needs a name after it, for example `--subdomain my-docs`.";
1377
+ }
1378
+ if (site !== void 0) {
1379
+ return "`--site` and `--subdomain` cannot both be given: `--site` publishes to a site you already have, and `--subdomain` creates a new one. Drop whichever is not what you meant.";
1380
+ }
1381
+ if (command !== "init" && command !== "deploy") {
1382
+ return `\`--subdomain\` only applies to \`init\` and \`deploy\`, which can create a site. Use \`--site\` to tell \`${command}\` which of your sites to act on.`;
1383
+ }
1384
+ return void 0;
1385
+ }
1327
1386
  async function run(argv) {
1328
1387
  const json = argv.includes("--json");
1329
- const positional = argv.filter((argument) => !argument.startsWith("-"));
1388
+ const positional = positionals(argv);
1330
1389
  const [command, ...rest] = positional;
1331
1390
  if (argv.includes("--version")) {
1332
- const { version } = await import("./package-B5PgBI_i.js").then(
1391
+ const { version } = await import("./package-sLisZctX.js").then(
1333
1392
  (module) => module.default
1334
1393
  );
1335
1394
  return { text: version, json: { version }, code: 0 };
@@ -1338,17 +1397,24 @@ async function run(argv) {
1338
1397
  if (askedForHelp) return { text: HELP, json: { help: HELP }, code: 0 };
1339
1398
  if (command === void 0) return { text: HELP, json: { help: HELP }, code: 1 };
1340
1399
  const site = flagValue(argv, "--site");
1400
+ const subdomain = flagValue(argv, "--subdomain");
1401
+ const refusal = subdomainFlagProblem(argv, command, site, subdomain);
1402
+ if (refusal !== void 0) {
1403
+ const failed = { text: refusal, json: { error: refusal }, code: 1 };
1404
+ return json ? { ...failed, text: JSON.stringify(failed.json, null, 2) } : failed;
1405
+ }
1341
1406
  const result = await dispatch(
1342
1407
  command,
1343
1408
  rest,
1344
1409
  site,
1345
1410
  json,
1346
1411
  argv.includes("--device"),
1347
- argv.includes("--yes")
1412
+ argv.includes("--yes"),
1413
+ subdomain
1348
1414
  );
1349
1415
  return json ? { ...result, text: JSON.stringify(result.json, null, 2) } : result;
1350
1416
  }
1351
- async function dispatch(command, rest, site, json, device, confirmed) {
1417
+ async function dispatch(command, rest, site, json, device, confirmed, subdomain) {
1352
1418
  switch (command) {
1353
1419
  case "login": {
1354
1420
  const print = json ? () => {
@@ -1358,14 +1424,14 @@ async function dispatch(command, rest, site, json, device, confirmed) {
1358
1424
  case "logout":
1359
1425
  return logout();
1360
1426
  case "init":
1361
- return await init(rest[0] ?? ".", site);
1427
+ return await init(rest[0] ?? ".", site, subdomain);
1362
1428
  case "deploy": {
1363
1429
  const report = json ? silentProgress : progressWriter(
1364
1430
  (text) => process.stderr.write(text),
1365
1431
  process.stderr.isTTY === true,
1366
1432
  process.stderr.columns ?? 80
1367
1433
  );
1368
- return await deployCommand(rest[0], site, report);
1434
+ return await deployCommand(rest[0], site, report, subdomain);
1369
1435
  }
1370
1436
  case "ls":
1371
1437
  case "list":
@@ -1,5 +1,5 @@
1
1
  const name = "drop2run";
2
- const version = "0.3.0";
2
+ const version = "0.4.1";
3
3
  const description = "Publish a static site to Drop2Run from the command line.";
4
4
  const license = "MIT";
5
5
  const type = "module";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drop2run",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Publish a static site to Drop2Run from the command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",