Tag: deploy

A deploy that refuses to nuke your site

Petr
I ran --prune after a broken build.
Pavel
So your site is empty.
Petr
No. The deploy refused.
Pavel
…I want that.

That's the whole feature: the deploy assumes a large change is a bug until you say otherwise.

How it works

Every deploy keeps a manifest — SHA-256, size and mtime for each uploaded file. The next deploy diffs against it and only touches what actually changed. Fast, boring, correct.

The interesting part is what happens before anything uploads. If the file count dropped by more than 20% since the last deploy, the whole thing stops. This is real output from this very site — I parked half the build's folders out of sight for a minute and asked for a deploy:

❌ Stopped: public.nosync/ has 28 files, but 62 were uploaded last time.
   That's a 55% drop -- looks like a broken build.
   Check the build output. If the drop is expected (you deleted a lot of posts), run again with --force.

A drop like that almost never means "I deliberately deleted half my blog." It means a typo in a path, an empty content directory, a build that crashed halfway. Without the guard, --prune would cheerfully delete the rest of your live site and upload the wreckage.

And it's symmetric: a sharp increase trips the same wire, because a site that suddenly doubled usually means a duplicated tree or a badly merged import, not a very productive afternoon.

The escape hatches

  • --dry-run shows exactly what would upload, change or be deleted — and touches nothing. Here's today's, after editing two posts:
Deploy web -> Surfer: https://blogsh.app  [DRY-RUN]
  62 file(s) total, 10 new/changed, 52 unchanged (skipped)
  [dry] index.html (54990 B)
  [dry] posts/2026/no-gems-one-asterisk/index.html (14852 B)
  [dry] posts/2026/why-not-x/index.html (14584 B)
  [dry] rss.xml (38156 B)
  [dry] search-index.json (29072 B)
  ...

Two posts changed, so ten files upload: the posts, the pages that list them, the feed, the search index. Everything else is a checksum match and stays home.

  • --force is the "yes, I meant it" switch, for the day you really do delete a hundred posts.
  • --prune is the only destructive flag in the toolbox, and it's opt-in. (On the git backend every deploy is a snapshot anyway, so pruning is implicit — and reversible.)

One honest side effect

Do a bulk import — or publish a fourteen-post series in one evening, as this site just did — and the growth guard trips too. Again, real output, from this site's launch night:

❌ Stopped: public.nosync/ has 63 files, only 30 were uploaded last time.
   That's a 110% increase -- looks like a duplicated or broken build.
   Check the build output. If the increase is expected (a bulk import/migration), run again with --force.

That's not a bug; that's the guard doing precisely its job on the one occasion you'd forgive it for staying quiet. Check the numbers, feel briefly smug that something checked them at all, and run it again with --force.

Read more