Docs
/

@leumas/hosting-upload

What gets deployed and what never does — the upload rules shared by Studio's drag-and-drop uploader and the agent lane, plus the Node walker and pooled uploader.

@leumas/hosting-upload

What gets deployed, and what never does — decided once.

The same decisions have to be made in two places that share no runtime: Studio's drag-and-drop uploader (a browser holding File handles) and the agent lane (a Node process on somebody's laptop holding paths). Both must strip the same wrapper folder, skip the same directories and refuse the same files.

The consequence of them disagreeing is not cosmetic. .env is on the skip list because a bundle directory is served to the public internet — a second implementation that forgets it publishes a secret, and does it silently. So the rules live here once, in a module that imports nothing.

The one contract that made it sharable

An entry is { rel, size, file?, path? }not { rel, file }. The planner reads size and carries everything else through untouched, so a browser passes { file, size: file.size, rel } and the Node walker passes { path, size, rel }. Neither runtime gets to decide policy.

Exports

ImportWhat
@leumas/hosting-uploadthe rules. Pure, no node: imports, safe in a browser bundle
@leumas/hosting-upload/nodewalkDir, uploadOne, uploadPlan — real files, real bytes

planUpload(entries){ files, skipped, stripped, buildDir, oversized, bytes, count }. rerootToBuildDir(plan) accepts the buildDir offer. looksLikeSpa(files) answers the one question that actually breaks deploys in the field.

The barrel is the rules only, deliberately./node imports node:fs, and a browser bundling the barrel would pull it in.

Why looksLikeSpa exists

target.spa rewrites unknown paths to index.html. Getting it wrong is the failure this feature has in production: the home page is perfect and every deep link 404s, so nothing catches it — not a smoke test, not a glance at the deployed site. It answers yes only to the shape a single-page app actually has (one .html, at the root), because a multi-page static site with SPA on turns every 404 into the home page — the opposite failure, and equally silent.

Why one file per request

The upload route takes a single file as a raw stream; express.json() is deliberately not mounted on it, which is the only reason a build can travel at all. A base64 tarball through a tool argument is capped by the /mcp body limit and inflated 4/3 by the encoding on top — this repo has already paid for that once, at 78 MB against a 2 MB limit.

uploadPlan therefore runs a bounded pool and reports ok only when every file landed. A caller must not point target.dir at a partially uploaded build: it serves a broken site with no error anywhere. Two refusals stop the run early rather than repeating — storage_full and a rejected credential both make every remaining request fail identically, and a hundred identical errors buries the one line that says what to do.

Limits it respects

  • 25 MB per file (MAX_UPLOAD_BYTES, mirrors the server, enforced there twice)
  • Per site: the owner's imperium.siteStorageBytes — 50 MB free, 250 MB on the plan
  • DENY_EXT / DENY_NAME are a mirror of the server's list, checked client-side to fail fast

with a better message. Never the guard.

What it exports

16 exports: 6 components, 0 hooks, 10 helpers. Import from @leumas/hosting-upload.

BUILD_DIRSDENY_EXTDENY_NAMEMAX_UPLOAD_BYTESSKIP_DIRSSKIP_FILEcleanRelPathdeniedReasonlooksLikeSpanewBuildIdplanUploadrerootToBuildDirskipReasonuploadOneuploadPlanwalkDir
Source shared/packages/hosting-upload/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt