@dropthis/cli 0.19.0 → 0.21.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 CHANGED
@@ -94,6 +94,57 @@ dropthis publish ./dist \
94
94
 
95
95
  `publish` always creates a NEW drop with a new URL. To change a drop you already published, use `update-content` or `update-settings` with its `drop_…` id — never publish again.
96
96
 
97
+ ### Canonical vs raw URLs
98
+
99
+ Every drop has a canonical `url` — an always-branded human view (badge guaranteed). For a single non-HTML file (`.pdf`, `.png`, `.json`, a URL you asked the server to fetch, etc.), publish also prints a `Raw:` line: the file's exact bytes at a natural path, no wrapper. Hand the canonical `url` to people; hand the raw URL to agents or downstream tooling that needs the real bytes.
100
+
101
+ ### Publishing a bundle with remote assets (`--manifest`)
102
+
103
+ When an AI agent generates an HTML page that references external images or other files, use `--manifest` to publish everything as one drop without base64-encoding the assets. The server fetches remote files during publish so bytes never pass through your process.
104
+
105
+ ```bash
106
+ dropthis publish --manifest bundle.json
107
+ ```
108
+
109
+ `bundle.json` shape:
110
+
111
+ ```json
112
+ {
113
+ "files": [
114
+ {
115
+ "path": "index.html",
116
+ "content": "<html>...</html>"
117
+ },
118
+ {
119
+ "path": "hero.jpg",
120
+ "source_url": "https://cdn.example.com/hero.jpg"
121
+ },
122
+ {
123
+ "path": "data.json",
124
+ "content_base64": "eyJrZXkiOiJ2YWx1ZSJ9"
125
+ }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ Each file entry must have a `path` and exactly one content key:
131
+
132
+ | Key | Description |
133
+ |-----|-------------|
134
+ | `content` | UTF-8 text content (HTML, CSS, JSON, markdown, …) |
135
+ | `source_url` | Public `https://` URL — the server fetches the bytes server-side. Use this for images and other binary assets instead of base64-encoding them. |
136
+ | `content_base64` | Base64-encoded bytes (for binary files you already have in memory) |
137
+
138
+ Optional per-file keys: `content_type` (MIME type override).
139
+
140
+ The `--manifest` flag also works on `update-content`:
141
+
142
+ ```bash
143
+ dropthis update-content drop_abc123 --manifest bundle-v2.json
144
+ ```
145
+
146
+ Cannot be combined with a positional file/folder/URL argument.
147
+
97
148
  All publish flags:
98
149
 
99
150
  | Flag | Description |
@@ -105,6 +156,7 @@ All publish flags:
105
156
  | `--entry <path>` | Entry file for directories |
106
157
  | `--content-type <mime>` | MIME type (required for stdin) |
107
158
  | `--path <path>` | Filename (required for stdin) |
159
+ | `--manifest <file>` | Multi-file bundle JSON (see above) |
108
160
  | `--expires-at <datetime>` | Expiration datetime |
109
161
  | `--metadata <json>` | Metadata as JSON string |
110
162
  | `--metadata-file <path>` | Metadata from a JSON file |
@@ -133,6 +185,9 @@ Change an existing drop without creating a new URL. Content and settings are **s
133
185
  # Replace the content at the same URL (ships a new deployment)
134
186
  dropthis update-content drop_abc123 ./dist-v2
135
187
 
188
+ # Update from a manifest bundle (same --manifest format as publish)
189
+ dropthis update-content drop_abc123 --manifest bundle-v2.json
190
+
136
191
  # Change settings only — title, visibility, password, expiry, metadata
137
192
  dropthis update-settings drop_abc123 --title "New title"
138
193
 
package/dist/cli.cjs CHANGED
@@ -916,13 +916,13 @@ async function runDoctor(_input, deps) {
916
916
  const authSource = credential?.source ?? "missing";
917
917
  const storageBackend = stored?.storage ?? "none";
918
918
  const pairs = [
919
- ["Version", "0.19.0"],
919
+ ["Version", "0.21.0"],
920
920
  ["Auth", authSource],
921
921
  ["Storage", storageBackend]
922
922
  ];
923
923
  writeKv(deps, pairs, {
924
924
  ok: true,
925
- version: "0.19.0",
925
+ version: "0.21.0",
926
926
  auth: { source: authSource },
927
927
  storage: { backend: storageBackend }
928
928
  });
@@ -997,6 +997,14 @@ function printNextHints(deps, hints, hostname, dns) {
997
997
  `);
998
998
  }
999
999
  }
1000
+ function printConsoleHandoff(deps, domain) {
1001
+ if (domain.status === "live" || !domain.consoleUrl) return;
1002
+ deps.stdout(
1003
+ `
1004
+ ${hint(`Or finish it in the console: ${domain.consoleUrl}`)}
1005
+ `
1006
+ );
1007
+ }
1000
1008
  function computeSleepMs(records, elapsedMs, timeoutMs) {
1001
1009
  const maxRetryAfter = records.reduce(
1002
1010
  (max, r) => Math.max(max, r.retryAfter ?? 0),
@@ -1031,6 +1039,7 @@ async function runDomainsConnect(input, deps) {
1031
1039
  `);
1032
1040
  }
1033
1041
  printNextHints(deps, domain.next, domain.hostname, domain.dns);
1042
+ printConsoleHandoff(deps, domain);
1034
1043
  } else {
1035
1044
  writeJson(deps, { ok: true, domain });
1036
1045
  }
@@ -1111,6 +1120,7 @@ async function runDomainsStatus(idOrHostname, _input, deps) {
1111
1120
  `);
1112
1121
  }
1113
1122
  printNextHints(deps, domain.next, domain.hostname, domain.dns);
1123
+ printConsoleHandoff(deps, domain);
1114
1124
  } else {
1115
1125
  writeJson(deps, { ok: true, domain });
1116
1126
  }
@@ -1144,6 +1154,7 @@ async function runDomainsVerify(idOrHostname, input, deps, sleep = defaultSleep)
1144
1154
  `);
1145
1155
  }
1146
1156
  printNextHints(deps, domain.next, domain.hostname, domain.dns);
1157
+ printConsoleHandoff(deps, domain);
1147
1158
  } else {
1148
1159
  writeJson(deps, { ok: true, domain });
1149
1160
  }
@@ -1604,6 +1615,7 @@ async function runLogout(input, deps) {
1604
1615
 
1605
1616
  // src/drop-operations.ts
1606
1617
  var import_node_crypto = require("crypto");
1618
+ var import_promises = require("fs/promises");
1607
1619
  var MAX_BUNDLE_FILE_COUNT = 200;
1608
1620
  function isFileSystemError(error2) {
1609
1621
  if (!(error2 instanceof Error)) return false;
@@ -1623,6 +1635,70 @@ function validateManifestFileCount(count) {
1623
1635
  );
1624
1636
  }
1625
1637
  }
1638
+ async function loadManifestFile(manifestPath) {
1639
+ let raw;
1640
+ try {
1641
+ raw = JSON.parse(await (0, import_promises.readFile)(manifestPath, "utf8"));
1642
+ } catch (err) {
1643
+ const msg = err instanceof Error ? err.message : "Could not read manifest file.";
1644
+ throw new Error(`Failed to read manifest file: ${msg}`);
1645
+ }
1646
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1647
+ throw new Error("Manifest must be a JSON object with a 'files' array.");
1648
+ }
1649
+ const obj = raw;
1650
+ if (!Array.isArray(obj.files)) {
1651
+ throw new Error("Manifest must have a 'files' array.");
1652
+ }
1653
+ const files = obj.files.map(
1654
+ (entry, i) => {
1655
+ if (!entry || typeof entry !== "object") {
1656
+ throw new Error(`Manifest files[${i}] must be an object.`);
1657
+ }
1658
+ const {
1659
+ path,
1660
+ content,
1661
+ content_base64,
1662
+ source_url,
1663
+ content_type,
1664
+ size_bytes,
1665
+ checksum_sha256
1666
+ } = entry;
1667
+ if (!path || typeof path !== "string") {
1668
+ throw new Error(`Manifest files[${i}] must have a 'path' string.`);
1669
+ }
1670
+ const contentKeys = [content, content_base64, source_url].filter(
1671
+ (v) => v !== void 0
1672
+ );
1673
+ if (contentKeys.length === 0) {
1674
+ throw new Error(
1675
+ `Manifest files[${i}] (path: "${path}") must have exactly one of: content, content_base64, source_url.`
1676
+ );
1677
+ }
1678
+ if (contentKeys.length > 1) {
1679
+ throw new Error(
1680
+ `Manifest files[${i}] (path: "${path}") must have only one of: content, content_base64, source_url.`
1681
+ );
1682
+ }
1683
+ if (source_url !== void 0 && !source_url.startsWith("http://") && !source_url.startsWith("https://")) {
1684
+ throw new Error(
1685
+ `Manifest files[${i}] (path: "${path}") has an invalid source_url "${source_url}": source_url must be an http(s) URL.`
1686
+ );
1687
+ }
1688
+ const file = { path };
1689
+ if (content !== void 0) file.content = content;
1690
+ if (content_base64 !== void 0) file.contentBase64 = content_base64;
1691
+ if (source_url !== void 0) file.sourceUrl = source_url;
1692
+ if (content_type !== void 0) file.contentType = content_type;
1693
+ if (size_bytes !== void 0)
1694
+ file.sizeBytes = size_bytes;
1695
+ if (checksum_sha256 !== void 0)
1696
+ file.checksumSha256 = checksum_sha256;
1697
+ return file;
1698
+ }
1699
+ );
1700
+ return { kind: "files", files };
1701
+ }
1626
1702
  function formatDryRunManifest(prepared, extra) {
1627
1703
  if (prepared.kind === "source") {
1628
1704
  return {
@@ -1635,8 +1711,11 @@ function formatDryRunManifest(prepared, extra) {
1635
1711
  ...prepared.metadata ? { metadata: prepared.metadata } : {}
1636
1712
  };
1637
1713
  }
1714
+ const remoteFileCount = prepared.manifest.files.filter(
1715
+ (f) => typeof f.sizeBytes !== "number"
1716
+ ).length;
1638
1717
  const totalBytes = prepared.manifest.files.reduce(
1639
- (sum, f) => sum + f.sizeBytes,
1718
+ (sum, f) => sum + (typeof f.sizeBytes === "number" ? f.sizeBytes : 0),
1640
1719
  0
1641
1720
  );
1642
1721
  return {
@@ -1650,12 +1729,15 @@ function formatDryRunManifest(prepared, extra) {
1650
1729
  },
1651
1730
  options: prepared.options,
1652
1731
  ...prepared.metadata ? { metadata: prepared.metadata } : {},
1653
- totalBytes
1732
+ totalBytes,
1733
+ // Only include remoteFileCount when there are remote files, to preserve
1734
+ // the existing output shape for all-inline bundles.
1735
+ ...remoteFileCount > 0 ? { remoteFileCount } : {}
1654
1736
  };
1655
1737
  }
1656
1738
 
1657
1739
  // src/options.ts
1658
- var import_promises = require("fs/promises");
1740
+ var import_promises2 = require("fs/promises");
1659
1741
  var import_node = require("@dropthis/node");
1660
1742
  async function parseDropOptions(raw) {
1661
1743
  if (raw.metadata && raw.metadataFile) {
@@ -1684,7 +1766,7 @@ async function parseDropOptions(raw) {
1684
1766
  }
1685
1767
  }
1686
1768
  const metadata = raw.metadata ? parseJsonObject(raw.metadata, "--metadata") : raw.metadataFile ? parseJsonObject(
1687
- await (0, import_promises.readFile)(raw.metadataFile, "utf8"),
1769
+ await (0, import_promises2.readFile)(raw.metadataFile, "utf8"),
1688
1770
  "--metadata-file"
1689
1771
  ) : void 0;
1690
1772
  return {
@@ -1766,6 +1848,25 @@ async function runPublish(input, raw, deps) {
1766
1848
  if (didInlineAuth && deps.createClient) {
1767
1849
  client = deps.createClient(credential.apiKey);
1768
1850
  }
1851
+ if (raw.manifest) {
1852
+ const hasInput2 = Array.isArray(input) ? input.length > 0 : input !== void 0;
1853
+ if (hasInput2) {
1854
+ return writeError(
1855
+ deps,
1856
+ "invalid_usage",
1857
+ "--manifest cannot be combined with a positional input.",
1858
+ "Remove the positional argument, or omit --manifest and pass your files directly."
1859
+ );
1860
+ }
1861
+ let manifestInput;
1862
+ try {
1863
+ manifestInput = await loadManifestFile(raw.manifest);
1864
+ } catch (err) {
1865
+ const msg = err instanceof Error ? err.message : "Invalid manifest.";
1866
+ return writeError(deps, "invalid_usage", msg);
1867
+ }
1868
+ input = manifestInput;
1869
+ }
1769
1870
  if (raw.dryRun) {
1770
1871
  return handlePublishDryRun(input, raw, deps, client);
1771
1872
  }
@@ -1834,7 +1935,8 @@ async function handlePublishDryRun(input, raw, deps, client) {
1834
1935
  );
1835
1936
  }
1836
1937
  try {
1837
- if (!input || Array.isArray(input) && input.length === 0) {
1938
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
1939
+ if (!hasInput) {
1838
1940
  throw new Error("Publish requires <input>.");
1839
1941
  }
1840
1942
  const options = await parseDropOptions(raw);
@@ -1857,7 +1959,7 @@ async function handlePublishDryRun(input, raw, deps, client) {
1857
1959
  }
1858
1960
 
1859
1961
  // src/commands/pull.ts
1860
- var import_promises2 = require("fs/promises");
1962
+ var import_promises3 = require("fs/promises");
1861
1963
  var import_node_path = require("path");
1862
1964
  async function runPull(target, input, deps) {
1863
1965
  try {
@@ -1897,7 +1999,7 @@ async function runPull(target, input, deps) {
1897
1999
  return writeError(deps, "local_input_error", message);
1898
2000
  }
1899
2001
  try {
1900
- await (0, import_promises2.mkdir)(outDir, { recursive: true });
2002
+ await (0, import_promises3.mkdir)(outDir, { recursive: true });
1901
2003
  for (const { path, dest } of destinations) {
1902
2004
  const fileResult = await deps.client.drops.getContent(dropId, { path });
1903
2005
  if (fileResult.error) {
@@ -1908,8 +2010,8 @@ async function runPull(target, input, deps) {
1908
2010
  });
1909
2011
  }
1910
2012
  const file = fileResult.data;
1911
- await (0, import_promises2.mkdir)((0, import_node_path.dirname)(dest), { recursive: true });
1912
- await (0, import_promises2.writeFile)(dest, file.bytes);
2013
+ await (0, import_promises3.mkdir)((0, import_node_path.dirname)(dest), { recursive: true });
2014
+ await (0, import_promises3.writeFile)(dest, file.bytes);
1913
2015
  }
1914
2016
  } catch (error2) {
1915
2017
  spin?.fail();
@@ -1975,6 +2077,23 @@ async function runUpdateContent(target, input, raw, deps) {
1975
2077
  const resolved = await resolveDropTarget(target, resolveDeps);
1976
2078
  if (!resolved.ok) return { exitCode: resolved.exitCode };
1977
2079
  const dropId = resolved.dropId;
2080
+ if (raw.manifest) {
2081
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
2082
+ if (hasInput) {
2083
+ return writeError(
2084
+ deps,
2085
+ "invalid_usage",
2086
+ "--manifest cannot be combined with a positional input.",
2087
+ "Remove the positional argument, or omit --manifest and pass your files directly."
2088
+ );
2089
+ }
2090
+ try {
2091
+ input = await loadManifestFile(raw.manifest);
2092
+ } catch (err) {
2093
+ const msg = err instanceof Error ? err.message : "Invalid manifest.";
2094
+ return writeError(deps, "invalid_usage", msg);
2095
+ }
2096
+ }
1978
2097
  if (raw.dryRun) {
1979
2098
  return handleDryRun(dropId, input, raw, deps);
1980
2099
  }
@@ -2467,7 +2586,7 @@ function createContext(input) {
2467
2586
  }
2468
2587
 
2469
2588
  // src/storage.ts
2470
- var import_promises3 = require("fs/promises");
2589
+ var import_promises4 = require("fs/promises");
2471
2590
  var import_node_os = require("os");
2472
2591
  var import_node_path2 = require("path");
2473
2592
  var import_keyring = require("@napi-rs/keyring");
@@ -2501,7 +2620,7 @@ var InsecureFileCredentialStore = class {
2501
2620
  });
2502
2621
  }
2503
2622
  async clear() {
2504
- await (0, import_promises3.rm)(this.filePath, { force: true });
2623
+ await (0, import_promises4.rm)(this.filePath, { force: true });
2505
2624
  }
2506
2625
  };
2507
2626
  var KeyringCredentialStore = class {
@@ -2528,7 +2647,7 @@ var KeyringCredentialStore = class {
2528
2647
  }
2529
2648
  async clear() {
2530
2649
  await this.keyring.deletePassword();
2531
- await (0, import_promises3.rm)(this.filePath, { force: true });
2650
+ await (0, import_promises4.rm)(this.filePath, { force: true });
2532
2651
  }
2533
2652
  };
2534
2653
  function createCredentialStore(options = {}) {
@@ -2553,7 +2672,7 @@ function credentialPath(configDir) {
2553
2672
  }
2554
2673
  async function readJsonFile(path) {
2555
2674
  try {
2556
- return JSON.parse(await (0, import_promises3.readFile)(path, "utf8"));
2675
+ return JSON.parse(await (0, import_promises4.readFile)(path, "utf8"));
2557
2676
  } catch (error2) {
2558
2677
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
2559
2678
  return null;
@@ -2562,18 +2681,18 @@ async function readJsonFile(path) {
2562
2681
  }
2563
2682
  }
2564
2683
  async function writeCredentialFile(path, credential) {
2565
- await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
2684
+ await (0, import_promises4.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
2566
2685
  const tmpPath = `${path}.${process.pid}.tmp`;
2567
- await (0, import_promises3.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
2686
+ await (0, import_promises4.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
2568
2687
  `, {
2569
2688
  mode: 384
2570
2689
  });
2571
- await (0, import_promises3.chmod)(tmpPath, 384);
2572
- await (0, import_promises3.rename)(tmpPath, path);
2690
+ await (0, import_promises4.chmod)(tmpPath, 384);
2691
+ await (0, import_promises4.rename)(tmpPath, path);
2573
2692
  }
2574
2693
 
2575
2694
  // src/typo-guard.ts
2576
- var import_promises4 = require("fs/promises");
2695
+ var import_promises5 = require("fs/promises");
2577
2696
  function levenshtein(a, b) {
2578
2697
  if (a === b) return 0;
2579
2698
  if (a.length === 0) return b.length;
@@ -2614,7 +2733,7 @@ function isBareWordToken(token) {
2614
2733
  }
2615
2734
  async function pathExists(path) {
2616
2735
  try {
2617
- await (0, import_promises4.stat)(path);
2736
+ await (0, import_promises5.stat)(path);
2618
2737
  return true;
2619
2738
  } catch {
2620
2739
  return false;
@@ -2648,7 +2767,7 @@ function buildProgram(options = {}) {
2648
2767
  ` ${import_picocolors6.default.cyan("dropthis login")}`,
2649
2768
  ` ${import_picocolors6.default.cyan("dropthis publish ./dist")}`
2650
2769
  ].join("\n")
2651
- ).version("0.19.0").configureHelp({
2770
+ ).version("0.21.0").configureHelp({
2652
2771
  subcommandTerm(cmd) {
2653
2772
  const args = cmd.registeredArguments.map((a) => {
2654
2773
  const name = a.name();
@@ -2699,6 +2818,9 @@ ${lines.map((l) => ` ${l}`).join("\n")}
2699
2818
  ).option(
2700
2819
  "--slug <vanity-slug>",
2701
2820
  "Vanity slug for path-mode custom domains (1\u201363 lowercase letters/digits/hyphens). Auto-suffixed if taken. Only valid with --domain on a path-mode domain."
2821
+ ).option(
2822
+ "--manifest <file>",
2823
+ "Publish a multi-file bundle defined in a JSON file (path, content/source_url/content_base64 per file). Cannot combine with a positional input."
2702
2824
  ).option("--url", "Print only the published URL (no JSON envelope)").option(
2703
2825
  "--dry-run",
2704
2826
  "Show what would be published without publishing (JSON; cannot combine with --url)"
@@ -2729,7 +2851,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2729
2851
  ].join("\n")
2730
2852
  ).action(async (inputs, commandOptions) => {
2731
2853
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
2732
- if (inputs.length === 0 && stdinIsTTY) {
2854
+ if (inputs.length === 0 && stdinIsTTY && !commandOptions.manifest) {
2733
2855
  program.outputHelp();
2734
2856
  return;
2735
2857
  }
@@ -2741,16 +2863,22 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2741
2863
  stdinIsTTY
2742
2864
  );
2743
2865
  let publishInput;
2744
- try {
2745
- publishInput = await resolvePublishInputs(
2746
- inputs,
2747
- options.stdin,
2748
- stdinIsTTY
2749
- );
2750
- } catch (err) {
2751
- const msg = err instanceof Error ? err.message : "Invalid input.";
2752
- process.exitCode = writeError(deps, "local_input_error", msg).exitCode;
2753
- return;
2866
+ if (inputs.length > 0 || !commandOptions.manifest) {
2867
+ try {
2868
+ publishInput = await resolvePublishInputs(
2869
+ inputs,
2870
+ options.stdin,
2871
+ stdinIsTTY
2872
+ );
2873
+ } catch (err) {
2874
+ const msg = err instanceof Error ? err.message : "Invalid input.";
2875
+ process.exitCode = writeError(
2876
+ deps,
2877
+ "local_input_error",
2878
+ msg
2879
+ ).exitCode;
2880
+ return;
2881
+ }
2754
2882
  }
2755
2883
  if (deps.outputMode === "human" && deps.interactive && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
2756
2884
  const suggestion = suggestCommand(
@@ -2795,6 +2923,9 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2795
2923
  "--content-type <mime>",
2796
2924
  "Override MIME type (auto-detected from extension)"
2797
2925
  ).option("--path <name>", "Set filename for stdin content").option(
2926
+ "--manifest <file>",
2927
+ "Update content from a multi-file bundle defined in a JSON file (path, content/source_url/content_base64 per file). Cannot combine with a positional input."
2928
+ ).option(
2798
2929
  "--if-revision <n>",
2799
2930
  "Fail if current revision doesn't match (optimistic lock)",
2800
2931
  parseInteger
@@ -2809,20 +2940,22 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2809
2940
  const deps = await commandDeps(program, options, store, commandOptions);
2810
2941
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
2811
2942
  let updateInput;
2812
- try {
2813
- updateInput = await resolvePublishInputs(
2814
- inputs,
2815
- options.stdin,
2816
- stdinIsTTY
2817
- );
2818
- } catch (err) {
2819
- const msg = err instanceof Error ? err.message : "Invalid input.";
2820
- process.exitCode = writeError(
2821
- deps,
2822
- "local_input_error",
2823
- msg
2824
- ).exitCode;
2825
- return;
2943
+ if (inputs.length > 0 || !commandOptions.manifest) {
2944
+ try {
2945
+ updateInput = await resolvePublishInputs(
2946
+ inputs,
2947
+ options.stdin,
2948
+ stdinIsTTY
2949
+ );
2950
+ } catch (err) {
2951
+ const msg = err instanceof Error ? err.message : "Invalid input.";
2952
+ process.exitCode = writeError(
2953
+ deps,
2954
+ "local_input_error",
2955
+ msg
2956
+ ).exitCode;
2957
+ return;
2958
+ }
2826
2959
  }
2827
2960
  let dropOpts;
2828
2961
  try {
@@ -3221,7 +3354,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3221
3354
  );
3222
3355
  const result = await runUpgrade(commandOptions, {
3223
3356
  ...deps,
3224
- currentVersion: "0.19.0"
3357
+ currentVersion: "0.21.0"
3225
3358
  });
3226
3359
  process.exitCode = result.exitCode;
3227
3360
  });
@@ -3331,6 +3464,7 @@ function toDropOptions(options) {
3331
3464
  ...options.domain ? { domain: options.domain } : {},
3332
3465
  ...options.shared ? { shared: true } : {},
3333
3466
  ...options.slug ? { slug: options.slug } : {},
3467
+ ...options.manifest ? { manifest: options.manifest } : {},
3334
3468
  ...options.json !== void 0 ? { json: options.json } : {},
3335
3469
  ...options.quiet !== void 0 ? { quiet: options.quiet } : {},
3336
3470
  ...options.url !== void 0 ? { url: options.url } : {},
@@ -3465,7 +3599,7 @@ var showNotice = shouldShowNotice({
3465
3599
  if (showNotice) {
3466
3600
  const notice = noticeFromCache({
3467
3601
  cache: updateCache,
3468
- currentVersion: "0.19.0"
3602
+ currentVersion: "0.21.0"
3469
3603
  });
3470
3604
  if (notice) {
3471
3605
  process.stderr.write(`