@dropthis/cli 0.20.0 → 0.22.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.20.0"],
919
+ ["Version", "0.22.0"],
920
920
  ["Auth", authSource],
921
921
  ["Storage", storageBackend]
922
922
  ];
923
923
  writeKv(deps, pairs, {
924
924
  ok: true,
925
- version: "0.20.0",
925
+ version: "0.22.0",
926
926
  auth: { source: authSource },
927
927
  storage: { backend: storageBackend }
928
928
  });
@@ -1615,6 +1615,7 @@ async function runLogout(input, deps) {
1615
1615
 
1616
1616
  // src/drop-operations.ts
1617
1617
  var import_node_crypto = require("crypto");
1618
+ var import_promises = require("fs/promises");
1618
1619
  var MAX_BUNDLE_FILE_COUNT = 200;
1619
1620
  function isFileSystemError(error2) {
1620
1621
  if (!(error2 instanceof Error)) return false;
@@ -1634,6 +1635,70 @@ function validateManifestFileCount(count) {
1634
1635
  );
1635
1636
  }
1636
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
+ }
1637
1702
  function formatDryRunManifest(prepared, extra) {
1638
1703
  if (prepared.kind === "source") {
1639
1704
  return {
@@ -1646,8 +1711,11 @@ function formatDryRunManifest(prepared, extra) {
1646
1711
  ...prepared.metadata ? { metadata: prepared.metadata } : {}
1647
1712
  };
1648
1713
  }
1714
+ const remoteFileCount = prepared.manifest.files.filter(
1715
+ (f) => typeof f.sizeBytes !== "number"
1716
+ ).length;
1649
1717
  const totalBytes = prepared.manifest.files.reduce(
1650
- (sum, f) => sum + f.sizeBytes,
1718
+ (sum, f) => sum + (typeof f.sizeBytes === "number" ? f.sizeBytes : 0),
1651
1719
  0
1652
1720
  );
1653
1721
  return {
@@ -1661,12 +1729,15 @@ function formatDryRunManifest(prepared, extra) {
1661
1729
  },
1662
1730
  options: prepared.options,
1663
1731
  ...prepared.metadata ? { metadata: prepared.metadata } : {},
1664
- 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 } : {}
1665
1736
  };
1666
1737
  }
1667
1738
 
1668
1739
  // src/options.ts
1669
- var import_promises = require("fs/promises");
1740
+ var import_promises2 = require("fs/promises");
1670
1741
  var import_node = require("@dropthis/node");
1671
1742
  async function parseDropOptions(raw) {
1672
1743
  if (raw.metadata && raw.metadataFile) {
@@ -1695,7 +1766,7 @@ async function parseDropOptions(raw) {
1695
1766
  }
1696
1767
  }
1697
1768
  const metadata = raw.metadata ? parseJsonObject(raw.metadata, "--metadata") : raw.metadataFile ? parseJsonObject(
1698
- await (0, import_promises.readFile)(raw.metadataFile, "utf8"),
1769
+ await (0, import_promises2.readFile)(raw.metadataFile, "utf8"),
1699
1770
  "--metadata-file"
1700
1771
  ) : void 0;
1701
1772
  return {
@@ -1777,6 +1848,25 @@ async function runPublish(input, raw, deps) {
1777
1848
  if (didInlineAuth && deps.createClient) {
1778
1849
  client = deps.createClient(credential.apiKey);
1779
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
+ }
1780
1870
  if (raw.dryRun) {
1781
1871
  return handlePublishDryRun(input, raw, deps, client);
1782
1872
  }
@@ -1845,7 +1935,8 @@ async function handlePublishDryRun(input, raw, deps, client) {
1845
1935
  );
1846
1936
  }
1847
1937
  try {
1848
- if (!input || Array.isArray(input) && input.length === 0) {
1938
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
1939
+ if (!hasInput) {
1849
1940
  throw new Error("Publish requires <input>.");
1850
1941
  }
1851
1942
  const options = await parseDropOptions(raw);
@@ -1868,7 +1959,7 @@ async function handlePublishDryRun(input, raw, deps, client) {
1868
1959
  }
1869
1960
 
1870
1961
  // src/commands/pull.ts
1871
- var import_promises2 = require("fs/promises");
1962
+ var import_promises3 = require("fs/promises");
1872
1963
  var import_node_path = require("path");
1873
1964
  async function runPull(target, input, deps) {
1874
1965
  try {
@@ -1908,7 +1999,7 @@ async function runPull(target, input, deps) {
1908
1999
  return writeError(deps, "local_input_error", message);
1909
2000
  }
1910
2001
  try {
1911
- await (0, import_promises2.mkdir)(outDir, { recursive: true });
2002
+ await (0, import_promises3.mkdir)(outDir, { recursive: true });
1912
2003
  for (const { path, dest } of destinations) {
1913
2004
  const fileResult = await deps.client.drops.getContent(dropId, { path });
1914
2005
  if (fileResult.error) {
@@ -1919,8 +2010,8 @@ async function runPull(target, input, deps) {
1919
2010
  });
1920
2011
  }
1921
2012
  const file = fileResult.data;
1922
- await (0, import_promises2.mkdir)((0, import_node_path.dirname)(dest), { recursive: true });
1923
- 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);
1924
2015
  }
1925
2016
  } catch (error2) {
1926
2017
  spin?.fail();
@@ -1968,6 +2059,15 @@ function contentOptions(options) {
1968
2059
  if (options.path !== void 0) out.path = options.path;
1969
2060
  return out;
1970
2061
  }
2062
+ function partialOptions(raw) {
2063
+ const out = {};
2064
+ if (raw.replace) out.mode = "replace";
2065
+ else if (raw.mode !== void 0) out.mode = raw.mode;
2066
+ if (raw.deletePaths !== void 0 && raw.deletePaths.length > 0) {
2067
+ out.deletePaths = raw.deletePaths;
2068
+ }
2069
+ return out;
2070
+ }
1971
2071
  async function runUpdateContent(target, input, raw, deps) {
1972
2072
  const apiKey = raw.apiKey ?? deps.apiKey;
1973
2073
  try {
@@ -1986,6 +2086,23 @@ async function runUpdateContent(target, input, raw, deps) {
1986
2086
  const resolved = await resolveDropTarget(target, resolveDeps);
1987
2087
  if (!resolved.ok) return { exitCode: resolved.exitCode };
1988
2088
  const dropId = resolved.dropId;
2089
+ if (raw.manifest) {
2090
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
2091
+ if (hasInput) {
2092
+ return writeError(
2093
+ deps,
2094
+ "invalid_usage",
2095
+ "--manifest cannot be combined with a positional input.",
2096
+ "Remove the positional argument, or omit --manifest and pass your files directly."
2097
+ );
2098
+ }
2099
+ try {
2100
+ input = await loadManifestFile(raw.manifest);
2101
+ } catch (err) {
2102
+ const msg = err instanceof Error ? err.message : "Invalid manifest.";
2103
+ return writeError(deps, "invalid_usage", msg);
2104
+ }
2105
+ }
1989
2106
  if (raw.dryRun) {
1990
2107
  return handleDryRun(dropId, input, raw, deps);
1991
2108
  }
@@ -2008,7 +2125,11 @@ async function runUpdateContent(target, input, raw, deps) {
2008
2125
  dropId,
2009
2126
  input,
2010
2127
  withIdempotencyKey(
2011
- { ...contentOptions(parsed), ...revisionOptions },
2128
+ {
2129
+ ...contentOptions(parsed),
2130
+ ...partialOptions(raw),
2131
+ ...revisionOptions
2132
+ },
2012
2133
  "upd"
2013
2134
  )
2014
2135
  );
@@ -2478,7 +2599,7 @@ function createContext(input) {
2478
2599
  }
2479
2600
 
2480
2601
  // src/storage.ts
2481
- var import_promises3 = require("fs/promises");
2602
+ var import_promises4 = require("fs/promises");
2482
2603
  var import_node_os = require("os");
2483
2604
  var import_node_path2 = require("path");
2484
2605
  var import_keyring = require("@napi-rs/keyring");
@@ -2512,7 +2633,7 @@ var InsecureFileCredentialStore = class {
2512
2633
  });
2513
2634
  }
2514
2635
  async clear() {
2515
- await (0, import_promises3.rm)(this.filePath, { force: true });
2636
+ await (0, import_promises4.rm)(this.filePath, { force: true });
2516
2637
  }
2517
2638
  };
2518
2639
  var KeyringCredentialStore = class {
@@ -2539,7 +2660,7 @@ var KeyringCredentialStore = class {
2539
2660
  }
2540
2661
  async clear() {
2541
2662
  await this.keyring.deletePassword();
2542
- await (0, import_promises3.rm)(this.filePath, { force: true });
2663
+ await (0, import_promises4.rm)(this.filePath, { force: true });
2543
2664
  }
2544
2665
  };
2545
2666
  function createCredentialStore(options = {}) {
@@ -2564,7 +2685,7 @@ function credentialPath(configDir) {
2564
2685
  }
2565
2686
  async function readJsonFile(path) {
2566
2687
  try {
2567
- return JSON.parse(await (0, import_promises3.readFile)(path, "utf8"));
2688
+ return JSON.parse(await (0, import_promises4.readFile)(path, "utf8"));
2568
2689
  } catch (error2) {
2569
2690
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
2570
2691
  return null;
@@ -2573,18 +2694,18 @@ async function readJsonFile(path) {
2573
2694
  }
2574
2695
  }
2575
2696
  async function writeCredentialFile(path, credential) {
2576
- await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
2697
+ await (0, import_promises4.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
2577
2698
  const tmpPath = `${path}.${process.pid}.tmp`;
2578
- await (0, import_promises3.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
2699
+ await (0, import_promises4.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
2579
2700
  `, {
2580
2701
  mode: 384
2581
2702
  });
2582
- await (0, import_promises3.chmod)(tmpPath, 384);
2583
- await (0, import_promises3.rename)(tmpPath, path);
2703
+ await (0, import_promises4.chmod)(tmpPath, 384);
2704
+ await (0, import_promises4.rename)(tmpPath, path);
2584
2705
  }
2585
2706
 
2586
2707
  // src/typo-guard.ts
2587
- var import_promises4 = require("fs/promises");
2708
+ var import_promises5 = require("fs/promises");
2588
2709
  function levenshtein(a, b) {
2589
2710
  if (a === b) return 0;
2590
2711
  if (a.length === 0) return b.length;
@@ -2625,7 +2746,7 @@ function isBareWordToken(token) {
2625
2746
  }
2626
2747
  async function pathExists(path) {
2627
2748
  try {
2628
- await (0, import_promises4.stat)(path);
2749
+ await (0, import_promises5.stat)(path);
2629
2750
  return true;
2630
2751
  } catch {
2631
2752
  return false;
@@ -2659,7 +2780,7 @@ function buildProgram(options = {}) {
2659
2780
  ` ${import_picocolors6.default.cyan("dropthis login")}`,
2660
2781
  ` ${import_picocolors6.default.cyan("dropthis publish ./dist")}`
2661
2782
  ].join("\n")
2662
- ).version("0.20.0").configureHelp({
2783
+ ).version("0.22.0").configureHelp({
2663
2784
  subcommandTerm(cmd) {
2664
2785
  const args = cmd.registeredArguments.map((a) => {
2665
2786
  const name = a.name();
@@ -2710,6 +2831,9 @@ ${lines.map((l) => ` ${l}`).join("\n")}
2710
2831
  ).option(
2711
2832
  "--slug <vanity-slug>",
2712
2833
  "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."
2834
+ ).option(
2835
+ "--manifest <file>",
2836
+ "Publish a multi-file bundle defined in a JSON file (path, content/source_url/content_base64 per file). Cannot combine with a positional input."
2713
2837
  ).option("--url", "Print only the published URL (no JSON envelope)").option(
2714
2838
  "--dry-run",
2715
2839
  "Show what would be published without publishing (JSON; cannot combine with --url)"
@@ -2740,7 +2864,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2740
2864
  ].join("\n")
2741
2865
  ).action(async (inputs, commandOptions) => {
2742
2866
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
2743
- if (inputs.length === 0 && stdinIsTTY) {
2867
+ if (inputs.length === 0 && stdinIsTTY && !commandOptions.manifest) {
2744
2868
  program.outputHelp();
2745
2869
  return;
2746
2870
  }
@@ -2752,16 +2876,22 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2752
2876
  stdinIsTTY
2753
2877
  );
2754
2878
  let publishInput;
2755
- try {
2756
- publishInput = await resolvePublishInputs(
2757
- inputs,
2758
- options.stdin,
2759
- stdinIsTTY
2760
- );
2761
- } catch (err) {
2762
- const msg = err instanceof Error ? err.message : "Invalid input.";
2763
- process.exitCode = writeError(deps, "local_input_error", msg).exitCode;
2764
- return;
2879
+ if (inputs.length > 0 || !commandOptions.manifest) {
2880
+ try {
2881
+ publishInput = await resolvePublishInputs(
2882
+ inputs,
2883
+ options.stdin,
2884
+ stdinIsTTY
2885
+ );
2886
+ } catch (err) {
2887
+ const msg = err instanceof Error ? err.message : "Invalid input.";
2888
+ process.exitCode = writeError(
2889
+ deps,
2890
+ "local_input_error",
2891
+ msg
2892
+ ).exitCode;
2893
+ return;
2894
+ }
2765
2895
  }
2766
2896
  if (deps.outputMode === "human" && deps.interactive && inputs.length === 1 && typeof publishInput === "string" && isBareWordToken(publishInput) && !await pathExists(publishInput)) {
2767
2897
  const suggestion = suggestCommand(
@@ -2806,6 +2936,18 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2806
2936
  "--content-type <mime>",
2807
2937
  "Override MIME type (auto-detected from extension)"
2808
2938
  ).option("--path <name>", "Set filename for stdin content").option(
2939
+ "--manifest <file>",
2940
+ "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."
2941
+ ).option(
2942
+ "--mode <mode>",
2943
+ "patch (default: provided files upsert, the rest carry forward) or replace (provided files become the whole content set)",
2944
+ parseMode
2945
+ ).option("--replace", "Shortcut for --mode replace (full content swap)").option(
2946
+ "--delete-path <path>",
2947
+ "Remove a file by path (patch-mode only; repeatable). Invalid with --replace.",
2948
+ collect,
2949
+ []
2950
+ ).option(
2809
2951
  "--if-revision <n>",
2810
2952
  "Fail if current revision doesn't match (optimistic lock)",
2811
2953
  parseInteger
@@ -2820,20 +2962,22 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2820
2962
  const deps = await commandDeps(program, options, store, commandOptions);
2821
2963
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
2822
2964
  let updateInput;
2823
- try {
2824
- updateInput = await resolvePublishInputs(
2825
- inputs,
2826
- options.stdin,
2827
- stdinIsTTY
2828
- );
2829
- } catch (err) {
2830
- const msg = err instanceof Error ? err.message : "Invalid input.";
2831
- process.exitCode = writeError(
2832
- deps,
2833
- "local_input_error",
2834
- msg
2835
- ).exitCode;
2836
- return;
2965
+ if (inputs.length > 0 || !commandOptions.manifest) {
2966
+ try {
2967
+ updateInput = await resolvePublishInputs(
2968
+ inputs,
2969
+ options.stdin,
2970
+ stdinIsTTY
2971
+ );
2972
+ } catch (err) {
2973
+ const msg = err instanceof Error ? err.message : "Invalid input.";
2974
+ process.exitCode = writeError(
2975
+ deps,
2976
+ "local_input_error",
2977
+ msg
2978
+ ).exitCode;
2979
+ return;
2980
+ }
2837
2981
  }
2838
2982
  let dropOpts;
2839
2983
  try {
@@ -2843,10 +2987,16 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
2843
2987
  process.exitCode = writeError(deps, "invalid_usage", msg).exitCode;
2844
2988
  return;
2845
2989
  }
2990
+ const deletePaths = commandOptions.deletePath ?? [];
2991
+ const updateOpts = {
2992
+ ...dropOpts,
2993
+ ...commandOptions.replace ? { mode: "replace" } : commandOptions.mode !== void 0 ? { mode: commandOptions.mode } : {},
2994
+ ...deletePaths.length > 0 ? { deletePaths } : {}
2995
+ };
2846
2996
  const result = await runUpdateContent(
2847
2997
  dropId,
2848
2998
  updateInput,
2849
- dropOpts,
2999
+ updateOpts,
2850
3000
  deps
2851
3001
  );
2852
3002
  process.exitCode = result.exitCode;
@@ -3232,7 +3382,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3232
3382
  );
3233
3383
  const result = await runUpgrade(commandOptions, {
3234
3384
  ...deps,
3235
- currentVersion: "0.20.0"
3385
+ currentVersion: "0.22.0"
3236
3386
  });
3237
3387
  process.exitCode = result.exitCode;
3238
3388
  });
@@ -3342,6 +3492,7 @@ function toDropOptions(options) {
3342
3492
  ...options.domain ? { domain: options.domain } : {},
3343
3493
  ...options.shared ? { shared: true } : {},
3344
3494
  ...options.slug ? { slug: options.slug } : {},
3495
+ ...options.manifest ? { manifest: options.manifest } : {},
3345
3496
  ...options.json !== void 0 ? { json: options.json } : {},
3346
3497
  ...options.quiet !== void 0 ? { quiet: options.quiet } : {},
3347
3498
  ...options.url !== void 0 ? { url: options.url } : {},
@@ -3354,6 +3505,15 @@ function parseInteger(value) {
3354
3505
  if (Number.isNaN(n)) throw new import_commander.InvalidArgumentError("Expected an integer.");
3355
3506
  return n;
3356
3507
  }
3508
+ function parseMode(value) {
3509
+ if (value !== "patch" && value !== "replace") {
3510
+ throw new import_commander.InvalidArgumentError('Use "patch" or "replace".');
3511
+ }
3512
+ return value;
3513
+ }
3514
+ function collect(value, previous) {
3515
+ return [...previous, value];
3516
+ }
3357
3517
  async function resolvePublishInputs(inputs, stdin, stdinIsTTY) {
3358
3518
  if (inputs.length === 0) {
3359
3519
  if (stdinIsTTY) return void 0;
@@ -3476,7 +3636,7 @@ var showNotice = shouldShowNotice({
3476
3636
  if (showNotice) {
3477
3637
  const notice = noticeFromCache({
3478
3638
  cache: updateCache,
3479
- currentVersion: "0.20.0"
3639
+ currentVersion: "0.22.0"
3480
3640
  });
3481
3641
  if (notice) {
3482
3642
  process.stderr.write(`