!! NOT INTENDED FOR PUBLIC USE. THIS IS MY PERSONAL PROJECT USED ONLY BY ME !!
Drop a folder or a .zip of static files, get a live site on a subdomain.
Files live in Vercel Blob; a Next.js proxy maps <name>.site.pakhale.com onto
sites/<name>/ and streams the assets back.
Turborepo with bun workspaces:
apps/web— the Next.js app: drop page,/sites, API routes, proxy.apps/raycast— the Raycast extension.packages/core— the shared protocol: prepare, publish, keys, mime, subdomain rules. TypeScript source, no build step; the web app transpiles it, the CLI and the extension bundle it.packages/cli— thesitedropnpm package. Bundled to a single dependency-free file that runs on node >= 20.
turbo dev, turbo build, turbo typecheck from the root fan out.
Outside the workspaces, skills/sitedrop/ is an agent skill that documents the CLI
for coding agents; npx skills add pratikpakhale/sitedrop -g -s sitedrop installs it.
bun install- Create a Vercel project with Root Directory
apps/web, attach a Blob store (setsBLOB_READ_WRITE_TOKEN). - Set
NEXT_PUBLIC_ROOT_DOMAINandDROP_PASSWORDin the project's env vars. - Add both
site.pakhale.comand*.site.pakhale.comas domains on the project. The wildcard requires the domain to use Vercel's nameservers — Vercel needs DNS write access to answer the ACME DNS-01 challenge for the wildcard cert.
cp apps/web/.env.example apps/web/.env.local for local work. Subdomains
resolve under *.localhost in Chrome and Safari, so foo.localhost:3000 just
works.
/ is the drop page. /sites manages what is already live — previews, rename,
delete. The password is checked against /api/sites once and then kept in
localStorage.
From the terminal (npx sitedrop, or bun run drop inside the repo):
sitedrop ./dist # folder, random subdomain
sitedrop ./dist -n my-demo # explicit subdomain
sitedrop ./site.zip # a zip
sitedrop report.html chart.png # loose files
sitedrop ./assets --force # publish without an index.htmlFrom Raycast, the Deploy command publishes a path, the Finder selection, or files
picked from a dialog, in that order of preference. The path may be a file, a folder, or
a zip, with ~ expanded; leave it blank and the Finder selection is used, and with
nothing selected there a file picker opens. The second argument names the subdomain.
Publishing something without an index.html fails with a Publish Anyway action on the
toast. The link is copied and the site opens in a browser. Set the endpoint and password
in the extension's preferences.
bun run --cwd apps/raycast dev registers the extension with Raycast and watches for
changes; it must run once, against a logged-in ray CLI, before Raycast knows the
extension exists. package rebuilds it in place, minified, without the watcher.
The endpoint and password come from --endpoint/--password or the
SITEDROP_ENDPOINT/SITEDROP_PASSWORD environment variables. Redeploying the same
subdomain replaces the site and prunes files that are no longer part of it.
Publishing to npm is tagged: push a v* tag and .github/workflows/publish.yml
builds and publishes packages/cli with provenance (needs the NPM_TOKEN
repo secret).
- Every file type is accepted. Known extensions get a real content type; the rest
are served as
application/octet-stream, which browsers download rather than render. Nothing is ever sniffed. - A lone
.htmlfile at the root is renamed toindex.html. Two root-level pages are ambiguous, and a nested page would have its relative links broken by the move, so neither is promoted. - Without a root
index.htmlthe site root 404s. Publishing that way needs an explicit opt-in: the checkbox in the UI,--forceon the CLI,force: trueon/api/commit. - A wrapper folder (
dist/index.html) is stripped automatically, in both folders and archives. /aboutresolves toabout.html, thenabout/index.html, then a literalabout. A root404.htmlis served for misses, with a 404 status.- Dot-files are never uploaded, so a stray
.envor.git/in a dragged folder cannot leak. Dot-directories are allowed, which keeps.well-known/usable. - Reserved subdomains (
www,api, …) are rejected; seepackages/core/src/config.ts.
Every rule the UI enforces is the same rule the CLI enforces, because both
call prepare() from @sitedrop/core.