feat(cli): add supabase workers new - #6261
Draft
johnstonmatt wants to merge 3 commits into
Draft
Conversation
This was referenced Aug 19, 2026
The two pieces every `supabase workers` command rests on, landed on their own because they are the subtle ones and deserve their own diff. Both live in `shared/`, so neither carries a shell prefix and neither is specific to one command tree. `worker-paths.ts` resolves the project layout: `supabase/<root>/<name>/`, mirroring `supabase/functions/<slug>/`, with `[workers] root` moving the grouping directory and `[workers.<name>] source` moving one worker's code anywhere in the project. Both are validated rather than joined blindly. `resolveWorkerSource` is load-bearing for safety, not tidiness: the path it returns is the directory `workers new --force` deletes outright, so a value naming the project root, `supabase/`, `functions/`, `migrations/`, or anywhere outside the project is refused before anything is removed. `toml-section.ts` edits one `[section]` of a TOML file textually rather than round-tripping it. `config.toml` belongs to the whole CLI — users hand-edit, comment and commit it — and reserialising preserves the data while discarding every comment and normalising the formatting they chose. Existing keys are rewritten in place, keeping any comment trailing the value; new keys are appended; everything else is left byte for byte. A value spanning several lines cannot be swapped one line at a time, so it is reported as unsupported and the file is left untouched instead of stranded half-rewritten. This follows the approach `legacy-pgdelta.write.ts` already takes for `[db.migrations] schema_paths`, generalised from one hard-coded key.
Scaffolds `supabase/workers/<name>/` from a runtime's starter files and records the choice in `config.toml`. Entirely local disk — nothing is deployed and no network is involved, which is why it lands before the API seam. The runtime and instance size are resolved before anything is written, so cancelling either prompt leaves nothing behind — including the name, which is only generated once both questions are answered. A worker already described in `config.toml` reuses those values instead of re-asking, and says so only when something recorded actually answers for an omitted flag. Two closed sets, both narrow on purpose. The runtimes are the catalog images plus `dockerfile`; the sizes are the alpha envelope's two, each implying its own vCPU count. `root` is refused as a worker name: `[workers] root` is the scalar key in the same table, so `[workers.root]` would stop the whole config parsing. Every runtime's starter runs as scaffolded, `dockerfile` included — its Dockerfile's `CMD` names a `server.js`, so that file is scaffolded too, alongside a `package.json` whose `"type": "module"` is what makes it ESM rather than Node's syntax detection. This also brings the command family's shell wiring, which is where the conventions here differ from a command tree's usual shape: - The project directory is `LegacyCliConfig.workdir`, so `--workdir` and `SUPABASE_WORKDIR` select the project exactly as they do for every sibling command, rather than an ancestor walk of the process's own directory. - Output goes through `output.raw` as plain text with no `intro`/`outro` framing, and tables through `renderGlamourTable`, so `workers` reads like `functions` and `projects` rather than like a second CLI. - `-o`/`--output` is honoured (`workers.output.ts`). It is a global flag 33 of this shell's 37 command families answer to, so ignoring it would print human text to a stdout the user asked to be machine-readable. What workers does not inherit is the Go-parity obligation behind the struct encoders: there is no Go counterpart to be byte-identical to, so the payload is serialised through the generic encoders instead. `-o env` is refused for a payload containing a list, because `encodeEnv` reproduces `godotenv.Marshal`, whose flattening does not descend into slices. - Telemetry state is flushed in `Effect.ensuring`, matching Go's `PersistentPostRun`. Two shell-wide registries have to move in step with the command appearing, and both are enforced by tests rather than convention: `LEGACY_DOCS_TAGS`, without which the generated CLI reference refuses to build, and `VALUE_CONSUMING_LONG_FLAGS`, without which the telemetry argv scan treats `--runtime`'s value as a flag and can fabricate one Go never recorded. `supabase workers` has no Go equivalent, so it is recorded in `docs/go-cli-divergences.md` as TS-only.
…acro
The starter files `workers new` writes lived as string literals with their
newlines and `${}` escaped. Hold them as ordinary files under
`shared/workers/stacks/<runtime>/` instead, authored in the language they are
written in, and narrow the offered runtimes to the three that have starters.
A shipped binary has no `stacks/` directory to read, so the directory is
expanded through a Bun macro: it runs while `worker-stacks.ts` is transpiled
and its return value is inlined as a literal, which means the content is
carried with nothing to pass at a build site and no directory to find at
runtime. Bun expands macros in the runtime transpiler too, so running from
source behaves the same; Vitest does not implement them and degrades to
calling the function against the source tree, which is why the path comes
from `import.meta.url` rather than Bun's `import.meta.dir`.
Discovery stays directory-driven — a new runtime is a new directory plus its
`WORKER_RUNTIMES` entry — and a completeness check inside the macro fails the
build rather than the binary when the two drift. Bun reports a throwing macro
as one it could not coerce to AST, so the reason is logged first to keep the
diagnostic legible.
Nothing imports the starters, which is what keeps them out of the type
program: a `deno` starter is not valid under this workspace's Bun types, and
`tsconfig.json` excludes the directory.
johnstonmatt
force-pushed
the
FUNC-753/workers-new
branch
from
August 19, 2026 03:11
ca0599d to
0d433d5
Compare
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.
Summary
Adds
supabase workers new, plus the project layout andconfig.tomlediting thewhole command family builds on:
shared/workers/— worker path resolution,config.tomlsection reading andpatching (
toml-section.tspreserves surrounding formatting), the runtime/sizeenvelope, and the starter files.
shared/workers/stacks/<runtime>/rather than string literals, and are embedded into the compiled binary through a
Bun macro — the directory is expanded at transpile time and inlined.
A completeness check inside the macro fails the build if
WORKER_RUNTIMESandthe directory drift apart.
Stack 2 of 4, on top of the config schema (#6260).
Reviewer note: the third commit is where the embedding mechanism is explained; the
starters are deliberately kept out of the type program (a
denostarter is notvalid under this workspace's Bun types), which is why
tsconfig.jsonexcludes thedirectory and nothing imports the files.
Linked issue
FUNC-753 (Linear). Supabase maintainer, exempt from the
open-for-contributionflow.Checklist