esdev create
esdev create my-app cd my-app npm install # or bun, pnpm, yarn npm run dev
What you get is a project with its esdev.json written, its entry named by the script tag in its index.html, its runtime: types installed and wired into tsconfig.json, and — for a project that runs a server — a deploy line that is already narrow:
esrun --allow-read=./dist --allow-env=PORT --allow-listen=8080 \ --allow-signals=SIGTERM,SIGINT dist/server.js
A project that deploys to a static host has no such line, because it runs nothing. Which of the two you get is the mode.
Templates
esdev create --list esdev create my-app --template=react --mode=fullstack
react | React + react-router — a static site, or an app with a server of its own |
api | A JSON API — one route, a URLPattern router. Nothing it ships depends on |
vanilla | TypeScript and the DOM — no framework |
micro-ui | Micro apps with Micro-UI — framework-free UI with a tiny reactive core |
lib | A publishable package — module tree, .d.ts, exports wired |
They are baked into the binary, so create works offline and always writes a project the esdev that wrote it can build. Nothing is downloaded.
A template is a scaffold, not a demo
What each one writes is a project that runs and one page — its name, what it was built with, the file to edit, three links — or for api, one route answering the same in JSON. That page is there to be deleted.
Nothing else is in it: no blog, no task store, no counter. What is kept is the machinery a project needs on its first day and would otherwise have to be assembled — the route table, the error boundary that renders a real 404, one render shared by the server and the static build, Fast Refresh, the security headers, the SIGTERM drain, the access log, and a permission line that is already narrow.
Both react modes are that one page on a route table, with the layout and the error boundary wired. What differs is what runs it — see Modes.
Modes
Some templates are two projects wearing one name. react is: an app with a server of its own is not the same project as a site that deploys to a static host — different files, a different esdev.json, and in one case no capabilities at all. Scaffolding the union of them and leaving you to delete half is how a starter ends up shipping a server nobody runs.
--mode=static (default) | --mode=fullstack | |
|---|---|---|
| Runs in production | Nothing | src/server.tsx, under esrun |
npm run build | Every route prerendered to dist/static/ | Server and browser bundles into dist/ |
npm run build:spa | One shell, routed in the browser | — |
| Grants | None — nothing runs | read, env, listen, signals |
| Deploy | Any static host | dist/, whole |
SSG and SPA are one mode on purpose. Which of the two a site wants is a deployment decision that moves with the content, and both come out of the same routes and components with no file edited — the switch is which script you run. Which routes are prerendered is staticPaths() in src/paths.ts, and a route left out of it is rendered in the browser instead.
A fullstack project is a file you own answering requests: a Content-Security-Policy with a per-response nonce, a SIGTERM that drains before it exits, immutable caching on hashed assets, HEAD answered with the headers and nothing after them, and one JSON log line per request.
It asks, or it writes files and stops
On a terminal it asks which template, which mode if that template has more than one, and whether to install — as a menu you arrow through:
? Which template? api A JSON API — one route, a URLPattern router, a narrow grant lib A publishable TypeScript package — module tree, .d.ts ❯ react React + react-router — a static site, or an app with a server (default) vanilla TypeScript and the DOM — no framework micro-ui Micro apps with Micro-UI — framework-free UI ↑/↓ move · 1-9 jump · enter select · esc cancel
It is drawn where the cursor already is and is replaced in place by one line naming the answer, so the scrollback is left holding a transcript of what was asked and what was chosen rather than a cleared screen. Esc at the first question cancels: nothing is written, and the exit status is zero.
Away from one — a pipe, a CI job, anything with CI set — it takes the defaults and says nothing. A prompt in a script is a script that hangs.
Every question has a flag, so nothing is only reachable by answering one:
esdev create my-app --template=react --mode=fullstack esdev create my-app --template=api --install=bun esdev create my-app --yes # defaults, no questions esdev create my-app --no-install
Only package managers this machine actually has are offered. Unattended it installs nothing: there is no lockfile yet to say which one this project uses, and guessing wrong leaves the wrong one behind — which is a reason not to guess, not a reason not to ask.
It still does no git init.
It never overwrites
A directory that already holds something is refused. --force writes among what is there and still leaves every existing file alone — it means "write here too", never "write over".
error: my-app is not empty.
Options
--template=<name> | Which template (default react) |
--mode=<name> | Which shape of it, where it has more than one: static or fullstack |
--install[=<manager>] | Install after writing: npm, bun, pnpm, yarn |
--no-install | Write the files and stop |
-y, --yes | Take every default; never ask |
--list | List the templates and exit |
--force | Write into a directory that already holds something |
esdev create weather-app writes "name": "weather-app" into package.json and <title>weather-app</title> into the document. A directory name that npm would reject — My App — is lowercased and hyphenated on the way in.