feat: add minimal per-language starters, verified in CI - #12
Merged
Conversation
Adds 26 single-purpose starters — one per language or runtime — as a
counterpart to the existing production-shaped examples. Each is the
smallest thing that builds and answers an HTTP request, listens on $PORT
and binds 0.0.0.0.
Nine of these did not build at all before this commit:
astro, nuxt, sveltekit no package.json
dotnet/web no .csproj
elixir/phoenix no mix.exs, and an endpoint with duplicate
at:/from: keys referencing a non-existent app
nextjs/app-router no app/layout.tsx
nodejs/nestjs `node dist/main` with no build script
php/laravel no artisan, bootstrap/app.php or public/index.php
ruby/rails config/application.rb contained JSON, not Ruby
swift/vapor Package.swift written in YAML, bound to 127.0.0.1
Also fixed while verifying: Laravel served / as 404 (Laravel 11+ prefixes
API routes with /api), and SvelteKit needed src/app.html for adapter-node.
Django now reads SECRET_KEY from the environment rather than carrying a
committed placeholder.
Adds .github/workflows/starters.yml, which builds each starter with
nixpacks and waits for a real HTTP response, on push, on PRs, and weekly.
The matrix is split: 13 starters nixpacks can build are blocking, and 13
that it cannot are non-blocking with the reason documented — those are
correct applications that fail because the Nix package set lags upstream
(dotnet-sdk 6.0.413 against net9.0, npm older than astro requires, Go
older than the `go 1.24` in go.mod, no Swift toolchain, no bun).
Every starter was built and confirmed to return a response before being
committed.
continue-on-error stops a job blocking a merge but does not stop GitHub painting it red, so a PR that passed showed 13 failing checks. That trains people to ignore red, which defeats the point of having CI at all. The 13 known-gap starters are now probed by a single job that always exits zero and writes a table to the run summary. It also calls out any starter that has started building, since nixpacks catching up is the news worth acting on — that was invisible when the jobs were permanently red.
dviejokfs
added a commit
to gotempsh/autopack
that referenced
this pull request
Aug 1, 2026
The README mentioned the crates but never answered "how would a platform actually call this", which is the question that matters for embedding it in Temps. Adds: - a runnable example (cargo run --example embed) covering the whole surface: analyze, metadata, resolved versions and their sources, plan JSON round-trip, lowering, the lock, and typed error handling - docs/embedding.md, with a worked adapter for a host whose interface is "give me a Dockerfile and build args" — the shape Temps already has — plus the requirements a host must meet (BuildKit, secrets by name, the separate .dockerignore) - a README section that leads with the Cargo dependencies and is linked from the top, since the previous one was buried below Configuration Also adds .github/workflows/starters.yml, which builds the language starters from the public gotempsh/temps-examples with autopack. The examples in this repository were written to exercise autopack and are a weak judge of it; that corpus was written by someone else for a different builder and is what the platform ships to users. It runs from this side because temps-examples is public and this repository is not: a workflow there would need a PAT in a public repo, and secrets do not reach fork pull requests anyway. Cloning a public repo from a private one needs no credentials. The job skips with a notice until gotempsh/temps-examples#12 lands the corpus, so it does not sit red waiting on another repository.
…t commands
Each of these pinned a start command that only ever worked because of how
Nixpacks happened to lay out its image, and breaks under any other builder:
ruby/rails `rails server` with no `bundle exec` — `rails` is not on PATH,
so the container exits with `exec: rails: not found`
python/django `manage.py migrate` against a project with no database
configured — `settings.DATABASES is improperly configured`
php/laravel `php artisan serve`, a development server; also pins
`php8.3-pgsql`, which does not resolve on the runtime base
dotnet/web `dotnet run`, which needs the SDK — runtime images carry only
the ASP.NET runtime, so it fails with "command not found"
All four start and serve correctly once the file is gone: the builder's own
provider already emits `bundle exec rails server`, gunicorn, FrankenPHP and
`dotnet <assembly>.dll`.
The three remaining files (flask, fastapi, php/vanilla) pin commands that are
correct, and are kept deliberately — they are the CI coverage for reading a
nixpacks.toml at all.
dviejokfs
added a commit
to gotempsh/temps
that referenced
this pull request
Aug 3, 2026
Every job in the matrix failed in 0.03s: `examples/starters` does not exist on temps-examples' default branch yet — it arrives with gotempsh/temps-examples#12 — so the checkout produced a tree without it and the test panicked before building anything. Two changes: * the checkout pins `ref: feat/language-starters`, marked TEMPORARY. It must come out when #12 merges; a branch ref that outlives its PR is a job that quietly stops testing what main ships. * the panic now distinguishes "the variable is unset" from "the directory is not there". They read identically and want completely different fixes, and the second one is what just cost a full CI run to diagnose.
dviejokfs
added a commit
to gotempsh/temps
that referenced
this pull request
Aug 3, 2026
gotempsh/temps-examples#12 is merged and the branch is deleted, so the `ref:` now points at nothing. The job reads `examples/starters` from the default branch, which is what it should have been testing all along.
dviejokfs
added a commit
to gotempsh/temps
that referenced
this pull request
Aug 4, 2026
* feat(presets): replace the nixpacks build engine with autopack
Nixpacks is gone as a dependency. The slugs it owned stay, and now build
through autopack.
Why now: nixpacks 1.41.0 is the latest published version and pulls two
advisories with no upstream fix available — RUSTSEC-2023-0018 (remove_dir_all
0.5.3, via tempdir 0.3.7) and GHSA-8wf9-4rjw-8j9r (serde_with 2.3.3). Both
were carried as documented, reachability-analysed exceptions because there was
nothing to upgrade to. Removing the dependency is the only thing that clears
them, and `cargo audit` now reports neither.
Compatibility is the constraint, not a nice-to-have. Existing projects have
`preset = 'nixpacks'` and a NixpacksConfig persisted against them, so:
* every `nixpacks*` slug still resolves, with the same stored_preset and the
same config shape;
* a persisted `nixpacks_config` TOML is still honoured — autopack reads the
Nixpacks schema in compatibility mode, and reports what it could not
translate instead of dropping it;
* a stored provider still forces that language, so a polyglot repository
pinned to `python` does not start building as `node` because detection
order differs.
Labels move from "Nixpacks (Python)" to "Autopack (Python)"; slugs do not.
Two behaviour changes worth knowing about:
* a project autopack cannot plan now produces a Dockerfile that exits 1 with
the reason. The previous fallback emitted `FROM alpine` + `COPY . .` and no
CMD, which builds and deploys cleanly into a container that exits at once;
* autopack's Dockerfiles need BuildKit for cache and secret mounts. The
deployment pipeline already enables it; a build that does not is refused by
name rather than failing later on a line the user never wrote.
Also adds tests/starters.rs, which builds every temps-examples starter through
the real preset, runs it, and requests a page — plus the two properties that
are invisible until they bite: that it answers on $PORT, and that it stops on
SIGTERM instead of waiting out the kill timeout.
* fix(ci): point the starters job at the branch that carries them
Every job in the matrix failed in 0.03s: `examples/starters` does not exist on
temps-examples' default branch yet — it arrives with gotempsh/temps-examples#12
— so the checkout produced a tree without it and the test panicked before
building anything.
Two changes:
* the checkout pins `ref: feat/language-starters`, marked TEMPORARY. It must
come out when #12 merges; a branch ref that outlives its PR is a job that
quietly stops testing what main ships.
* the panic now distinguishes "the variable is unset" from "the directory is
not there". They read identically and want completely different fixes, and
the second one is what just cost a full CI run to diagnose.
* test(ci): gate on rust/actix, which passes
It was parked in the report-only job on the strength of a local failure: on
macOS it ignored SIGTERM and `docker stop` had to SIGKILL it. On Linux CI it
passes cleanly in 104s, SIGTERM check included — so the failure was an artefact
of Docker Desktop, not the starter.
Leaving a passing starter in the report-only bucket means it is not actually
gated, and a real regression in it would go unnoticed. Linux CI is the
environment the gate is for.
java/spring-boot stays behind: it still fails, for the same reason, every run.
* ci: drop the temporary starters branch pin
gotempsh/temps-examples#12 is merged and the branch is deleted, so the `ref:`
now points at nothing. The job reads `examples/starters` from the default
branch, which is what it should have been testing all along.
* fix(presets): stop the Nixpacks TOML validation error echoing the config
`test_nixpacks_invalid_inline_toml_is_rejected_during_create` caught this: the
new validation interpolated `toml`'s error Display, which renders the offending
*source line*. The test feeds `secret_token = ["do-not-echo"` and asserts the
value does not come back — it did.
That message reaches an API response body and the logs, and a nixpacks_config
can hold secrets, so this was a real leak rather than untidy output.
Only the position crosses the boundary now: "failed to parse Nixpacks TOML at
line 1, column 30; verify its syntax and supported fields". The position is
derived from the error's span rather than its rendering, because both of the
library's own accessors can carry content — Display embeds the source line, and
`.message()` names the offending key on an unknown-field error. The full error
goes to the debug log server-side, where it is useful and not exposed.
Adds the same assertion as a unit test. The existing guard is behind
`docker_available()`, so it only runs in the Docker integration job — a leak
this cheap to reintroduce should fail in `cargo test -p temps-presets`.
* feat(web): add the autopack preset icon
`AutopackPreset::icon_url` and the auto-detect Nixpacks variant both point at
/presets/autopack.svg, which did not exist. The console falls back to
custom.svg on error, so this was not a broken image — the new preset simply
showed the generic mark, which is worse in a picker whose whole job is telling
builders apart at a glance.
The mark is an isometric box with its lid lifted. Not a triangle (Vercel's,
and temps competes with them) and not a letter "A" (Astro's, and it sits three
tiles away in this very picker). Teal because the picker already has purple,
blue, green and orange.
* refactor(web): open the autopack icon's lid into a chevron
Matches gotempsh/autopack. The closed cube was the generic registry package
mark; the open lid reads as a box mid-pack and doubles as an up arrow.
Teal rather than green is deliberate and checked against the real picker:
Node.js owns green there, and autopack builds 24 ecosystems, so reading as a
Node tool is misleading. Sky blue collides with Docker and Nixpacks, amber with
HTML5. Teal is the only gap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds 26 minimal per-language starters under
examples/starters/— one per language or runtime — as a counterpart to the existing production-shaped examples. Each is the smallest thing that builds and answers an HTTP request, listens on$PORT, and binds0.0.0.0.They live under
starters/rather than at the top level becauseexamples/astroandexamples/sveltekitalready exist as richer examples; this keeps both.These were broken
Nine did not build with any builder before this PR:
astro,nuxt,sveltekitpackage.jsondotnet/web.csprojelixir/phoenixmix.exs; endpoint had duplicateat:/from:keys and referenced a non-existent:nextjsappnextjs/app-routerapp/layout.tsx—next buildrefusesnodejs/nestjsnode dist/mainwith no build script to producedist/php/laravelartisan,bootstrap/app.phporpublic/index.phpruby/railsconfig/application.rbcontained JSON, not Rubyswift/vaporPackage.swiftwritten in YAML, and bound to127.0.0.1Two more surfaced only by running them:
php/laravelserved/as 404 — Laravel 11+ prefixes API routes with/api. Cleared to match the others.sveltekitneededsrc/app.html, required byadapter-node.Django now reads
SECRET_KEYfrom the environment instead of carrying a committed placeholder.The CI
.github/workflows/starters.ymlbuilds each starter with nixpacks — the builder Temps uses — then runs it withPORTinjected and polls for a real HTTP response, failing fast with logs if the container exits. Runs on push, PRs, and weekly, since base images and registries move on their own.The matrix is deliberately split:
supported— 13 starters nixpacks can build. Blocking.nixpacks-gaps— 13 that it cannot. Non-blocking, each with its reason inline.The second group is not broken code. All 26 build and serve with a current toolchain; these fail because nixpacks resolves runtimes from the Nix package set, which lags upstream:
go/net-http,go/gingo.moddeclaresgo 1.24; Nix Go is older and the toolchain auto-download failsdotnet/webnet9.0projectastro,nuxtnpm >=9.6.5these requireswift/vaporswift: command not founddenomain.tsjava/spring-bootbuild.gradlewithout a wrapperbun/*,nodejs/honobun: not foundpython/djangoKeeping them visible rather than deleting them means CI tells us the day nixpacks catches up. It is also, read plainly, a list of stacks Temps cannot currently deploy.
Verification
Every starter was built and confirmed to return a response before committing. The 13 supported ones are confirmed again by CI on amd64.
Only git-tracked files were migrated (a plain copy pulled in 196MB of
rust/actix/targetbuild artifacts), and the diff was scanned for credentials before pushing.