@within-7/jetr
v0.11.1
Published
CLI for Jetr static site hosting
Readme
@within-7/jetr
One-command static site hosting on Cloudflare R2 + Workers.
- Static sites, SPAs, file packs — same command for all three
- Auto-detected SPA fallback — Vue/React/Svelte routes just work
- Pretty URLs,
_redirects,_headers— Netlify/Pages compatible syntax - Atomic deploys + preview URLs — every deploy is reachable forever, rollback is one SQL UPDATE
- Smart cache tiers — HTML stays fresh, hashed assets get
immutable - Custom domains, password protection, share tokens — out of the box
Install
npm i -g @within-7/jetrOr use directly without installing:
npx @within-7/jetr ./dist my-siteQuick Start (zero-config)
# 1. Login (or auto-detected from AITER_TOKEN env)
jetr login
# 2. Build your app, then deploy the output directory
npm run build
jetr ./dist my-blog
# ✔ Deployed!
# Site: my-blog
# URL: https://my-blog.jetr.within-7.comThat's it. For a Vue/React/Svelte SPA, jetr automatically detects
the SPA shape and asks if you want client-side routes to work — answer
"yes" and /about, /dashboard/orders, etc. all serve the SPA shell
correctly.
What jetr detects
When you run jetr ./dist, the CLI classifies your output as one of:
| Shape | Signal | What jetr does |
|---|---|---|
| SPA | index.html + ≤ 2 HTML files | Prompts to add SPA fallback so /any/route serves index.html |
| Static site | index.html + 3+ HTML files | Deploys as-is; pretty URLs (/about → /about.html) work automatically |
| File pack | No root index.html | Serves a clickable directory browser + in-browser viewer for markdown/csv/code |
Override the prompt with flags:
jetr ./dist --ssg # write _redirects without asking (good for CI)
jetr ./dist --no-ssg # never modify source, deploy bytes as-isSSG / SPA configuration
Jetr understands two Netlify/Pages-style config files in your deploy directory. Either drop them in by hand or run:
jetr init --ssg ./dist…which scaffolds .jetrignore, _redirects, and _headers with
commented templates you can edit.
_redirects — routing rules
# SPA fallback (catch-all rewrite)
/* /index.html 200
# Legacy URL migration
/old-blog/* /posts/:splat 301
# API rewrite (serve /backend/x.json as /api/x.json without exposing the path)
/api/* /backend/:splat 200
# External redirect
/docs https://docs.example.com 302Supported: exact paths, * splat, :splat substitution, status 200
(rewrite, URL unchanged) and 30x (HTTP redirect). External 200 rewrites
are refused (jetr never proxies third-party content); external 30x
pass through.
_headers — per-path HTTP headers
# Global headers (every response)
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
# API CORS
/api/*
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST
# Long cache for fonts
/fonts/*
Cache-Control: public, max-age=31536000, immutableLater rules override earlier ones on the same header key.
Pretty URLs
/about automatically serves /about.html when the latter exists and
the requested path has no file extension. Works for nested paths:
/blog/post-1 → /blog/post-1.html. This matches the Hugo / Jekyll /
Astro convention out of the box — no config needed.
The directory-redirect rule (/docs → /docs/) still fires when a
matching .html doesn't exist but a directory does.
Smart caching
Jetr picks Cache-Control based on file type and name:
| File | Header |
|---|---|
| *.html, *.json, *.xml | max-age=300 (5 min) |
| Vite/Webpack/Rollup hashed assets (app.a1b2c3d4.js) | max-age=31536000, immutable |
| Other assets (logo.png, styles.css) | max-age=3600 (1 hour) |
Hashed-asset detection requires an asset extension AND an 8+ char
basename segment containing at least one digit. Human-named files
(my-component-page.js, theme-dark.css) are never tagged immutable
by accident.
Atomic deploys + preview URLs + rollback
Every jetr invocation produces a unique deployId. All files for
that deploy go to a dedicated R2 prefix; the production URL only
serves the latest deploy's bytes once finalize commits an atomic
pointer swap. Old deploys remain reachable for preview / rollback.
jetr deploys my-blog
# Active: 7Jb3pK 2026-06-03 14:21 23 files, 1.2 MB
# qF2nLm 2026-06-03 11:08 23 files, 1.2 MB
# h8WqEr 2026-06-02 17:55 21 files, 1.0 MB
# Preview a historical deploy
jetr preview my-blog qF2nLm
# https://my-blog--qF2nLm.jetr.within-7.com
# Roll back to it
jetr rollback my-blog qF2nLm
# ✔ Active deploy: qF2nLmPreview URLs are validated against (siteId, deployId, status='complete')
so unrelated deploy IDs can't be probed across sites.
Preview URLs work on the default
*.jetr.within-7.comorigin only. Custom domains always serve the active deploy. This matches Vercel and Netlify behaviour.
Custom domains
jetr ./dist blog.example.com
# ✔ Deployed!
# Domain: blog.example.com
# DNS: Set blog.example.com CNAME to within-7-jetr.comThe CLI shows the DNS instruction on every deploy (not just the first) so you don't lose it.
Root domains (example.com with no subdomain) are rejected because
CNAMEs at the apex are forbidden by DNS — use www.example.com or
app.example.com instead.
Password protection + share tokens
# Lock a site
jetr password my-draft hunter2
# Generate a share link that bypasses the password
jetr token create my-draft --note "QA team" --expires 86400
# URL: https://my-draft.jetr.within-7.com/?token=...
# Revoke a token by ID
jetr info my-draft # lists tokens
jetr token revoke my-draft <token-id>File-package mode (no index.html)
Drop a folder of mixed files and jetr serves a clickable directory browser with in-browser viewers for:
- Markdown (rendered HTML)
- CSV (tabular HTML)
- Plain text / source code (syntax-highlighted)
- Images (inline)
Useful for quick file dumps, datasheet shares, asset distribution.
Append ?raw to any URL to bypass the viewer and download the
underlying bytes.
Command reference
jetr [paths...] Deploy directory or files
jetr login Authenticate
jetr whoami Show current user
jetr logout Clear credentials
jetr init [dir] [--ssg] Scaffold .jetrignore (+ _redirects, _headers)
jetr create <name> [-p PW] [-e SECS] Create empty site without deploying
jetr list List your sites
jetr info <name> Show site details (files, tokens, domains)
jetr delete <name> [-y] Delete a site permanently (prompts unless -y)
jetr password <name> [pw] Set / remove visitor password
jetr token create <site> [-n NOTE] [-e SECS] Generate share token
jetr token revoke <site> <id> Revoke a share token
jetr deploys <site> List deploy history
jetr rollback <site> <deployId> [-y] Flip active pointer to a past deploy (prompts unless -y)
jetr preview <site> <deployId> Print preview URLDeploy flags
--ssg Force-write _redirects for SPA-shaped sites
--no-ssg Skip SSG detection
--concurrency <n> Max parallel uploads (default: JETR_CONCURRENCY env or 10)Exit codes
Scripts and CI can check $? to branch on the failure reason:
| Code | Meaning | Typical trigger |
|---|---|---|
| 0 | Success | — |
| 1 | Generic error | Unexpected failure |
| 2 | Usage / argument error | Root domain, missing required arg |
| 3 | Auth error | Not logged in, token expired (jetr login) |
| 4 | Network error | Cannot reach API (check internet connection) |
| 5 | Not found / forbidden | Site 404, deploy doesn't exist, site belongs to another user |
| 6 | Conflict | Deploy invalid / superseded, file not in upload set, domain already in use |
Example:
jetr ./dist my-site
case $? in
0) echo "deployed" ;;
3) echo "need to run jetr login" ;;
4) echo "network issue — retry?" ;;
6) echo "conflict — may need to retry deploy" ;;
*) echo "deploy failed" ;;
esacEnvironment variables
| Variable | Effect |
|---|---|
| AITER_TOKEN | Auth token (preferred over JETR_TOKEN and ~/.jetr/config.json) |
| JETR_TOKEN | Fallback auth token |
| JETR_CONCURRENCY | Default upload concurrency (overridden by --concurrency flag, default 10) |
| JETR_MAX_RETRIES | Max HTTP retry attempts for transient errors (default 3) |
Files jetr writes / reads in your deploy dir
| File | Purpose |
|---|---|
| .jetrignore | Patterns to skip during upload (created by jetr init) |
| .jetrkeep | Force-include files that would otherwise be ignored |
| .jetrrc | Site-name memory (created on first deploy) |
| .jetr/hash-cache.json | SHA-256 hash cache keyed by path+size+mtime — speeds up unchanged-file detection across deploys |
| _redirects | Routing rules — compiled to .jetr/meta.json on the server, raw file NOT uploaded |
| _headers | Per-path HTTP headers — also compiled to .jetr/meta.json, raw file NOT uploaded |
License
MIT
