Working on an app
The commands you use while writing an app:
melee new,dev,check,push,logs,env.
All of these read melee.toml in the app directory (name, server, url) and take --dir <path> if you
are not standing in it. melee new is the exception: it writes that file.
melee new
melee new notes && cd notes
melee dev
Creates notes/ with a working two-route app: melee.toml, spin.toml, app.rb, views/layout.erb and
views/index.erb, public/app.css, a first migration, a .meleeignore and a README. The name must be
lowercase letters, digits and dashes, not starting with a dash, at most 40 characters — the same rule the
server applies. It refuses a directory that exists and is not empty.
melee dev
ADMIN_SECRET=letmein melee dev # http://127.0.0.1:4567
Compiles the templates and migrations, then runs the app under CRuby with a small development server. It prints one line per request:
melee dev: prepared 5 templates, 1 migrations
melee dev: http://127.0.0.1:4567 (Ctrl-C to stop)
GET /admin -> 200 (4.2 ms)
GET /admin.css -> 200 (public/)
- Port from
PORT, default 4567. One request at a time, no keep-alive, no TLS. - Files under
public/are served straight from disk, as the real server does. - The database is
.melee/app.sqliteinside the app directory. Delete it to start fresh. - Environment variables come from your shell, standing in for
melee env. - The session secret is
MELEE_SESSION_SECRET, defaulting to a fixed development value. - No reload. Changing
app.rb, a template or a migration means stopping and starting it again.
The error page
Under melee dev only, a 500 renders a page with the exception class and message, the request, the params,
the session and a CRuby backtrace. It exists to tell you what happened while you are writing the app.
Production has no backtrace at all (see logging.md), so do not build a habit on it.
melee check
melee check
The build without the deploy: the same template and migration steps, then a full Spinel compile of the app and the stdlib. Nothing is sent anywhere. It is the fastest way to find out whether the code compiles.
Every diagnostic is one line, file:line: message, and template errors are mapped back from the generated
Ruby to the .erb you wrote:
views/broken.erb:2: unexpected end-of-input, assuming it is closing the parent top level context
db/migrations/002_bad.sql:1: near "TABEL": syntax error
app.rb:30: views/login.erb requires `error`
What is caught here: Ruby syntax in app.rb and lib/**/*.rb (reported against the file you edited, before
Spinel runs), template syntax, every render/partial call against the template’s declared locals,
migration SQL (each file is run against a scratch database), unknown requires, eval, thread primitives, a
rescue clause that reaches its class through a constant alias, the other things dialect.md forbids, and a
call to a top-level helper that does not exist. What is not caught: wrong arity, calling a method on
nil, and any undefined method reached through an explicit receiver — db.frist(...), "x".nope and
Household.get("home").refrsh all compile and raise NoMethodError when the line runs. So click through the
app under melee dev before pushing.
melee push
melee push
Tars the app directory and sends it to the server named in melee.toml; the server builds it, and on success
activates the new release and prints the URL. build/, .git/, .melee* and spin.lock are never sent, and
a .meleeignore file (one path or name per line) excludes more.
A failed build answers with the same file:line: message diagnostics and deploys nothing; the previous
release keeps serving.
melee logs, env, restart
melee logs # last 50 lines
melee logs -f # follow
melee logs --tail 200
melee env ADMIN_SECRET letmein # set a variable; the app restarts on its next request
melee restart # stop the warm process; the next request starts it again
melee apps # what is deployed on this server
melee open # print and open the app's URL
melee env is the only place secrets belong. They arrive in the app as ENV["ADMIN_SECRET"].
Generated files
The build step writes three files. They are regenerated on every dev, check and push; never edit them
and do not commit them.
| File | From |
|---|---|
.melee/views.rb | views/*.erb — one method per template plus the render/partial dispatch |
.melee/migrations.rb | db/migrations/*.sql embedded as Melee.migrate([[name, sql], ...]) |
bin/<name>.rb | the entry point: requires the two above, then app.rb, then starts the runtime |
The same thing by hand
melee new writes these; this is what they are.
melee.toml:
name = "hello"
server = "http://127.0.0.1:7070"
url = "http://hello.localhost:8080"
The control API wants a token on every request. melee looks for it in MELEE_TOKEN, else a token_file
path named here — which has to be outside the app directory, because melee push uploads everything in it.
The development server writes one for you, so the local loop needs nothing here.
spin.toml:
[package]
name = "hello"
version = "0.1.0"
[dependencies]
melee = { path = "../melee/stdlib" }
app.rb:
# frozen_string_literal: true
title "Hello"
get "/" do
"<h1>Hello from #{app_title}</h1>"
end
Then melee dev and open http://127.0.0.1:4567.