Skip to content

feat: add CodeGen to run the codegen without the dev-server - #100

Merged
thetutlage merged 2 commits into
8.xfrom
feat/codegen
Aug 19, 2026
Merged

thetutlage merged 2 commits into
8.xfrom
feat/codegen

Conversation

@thetutlage

Copy link
Copy Markdown
Member

Summary

Adds a CodeGen class that performs the codegen the dev-server performs, without the server and without the file watcher.

Pairs with adonisjs/application#48 and the node ace codegen command in core.

Why

The dev-server, the bundler and the test runner all generate code as a side effect of doing something else, so there is no way to refresh .adonisjs on its own. That matters in CI, where you want the generated types to be current before typechecking or building, without starting a long running process.

Worth noting: only the dev-server loads the routes hooks today. The bundler loads ['init', 'buildStarting', 'buildFinished'] and the test runner ['init', 'testsStarting', 'testsFinished']. So anything hanging off routesCommitted / routesScanned — a Tuyau registry, for instance — is only regenerated while npm run dev is running.

The two stages, and why the order is forced

Static stage — run the init hooks against an IndexGenerator and write the index files. Only the file-system is needed.

Runtime stage — run the routes hooks. This needs state only a booted application knows about.

Assembler never boots an application, so the caller supplies that state. It cannot simply be passed in, because the stages are ordered: an application imports the generated barrel files from its preload files (start/routes.ts does import { controllers } from '#generated/controllers'), so it cannot be booted until the index files exist. The dev-server generates them before spawning the app for exactly this reason.

Hence the callback:

const codegen = new CodeGen(cwd, { hooks: rcFile.hooks })

await codegen.run(async () => {
  await app.boot()
  await app.warmUp()
  router.commit()
  return { routes: router.toJSON() }
})

Ordering is structural rather than documented, and there is a test pinning it. Skip the callback to generate the index files alone.

The snapshot is a keyed bag rather than a positional argument, so a second runtime producer later does not change the signature.

Shared with the dev-server

The sequence for sharing and scanning routes was identical in both, so it now lives in processRoutes / hasRoutesHooks in utils.ts. Each caller keeps its own edges: the dev-server keeps the throttle, the file read, the unlinks and the try/catch that logs and continues; CodeGen takes the routes in memory and lets errors throw, so a command can exit non-zero.

Breaking

The hook parent unions widen — CommonHooks.init to DevServer | TestRunner | Bundler | CodeGen, and the three RouterHooks to DevServer | CodeGen. A hook reading DevServer-only members without narrowing will no longer compile.

Tests

9 new tests in tests/codegen.spec.ts, including one that asserts the index files exist by the time the snapshot callback runs.

The dev-server, the bundler and the test runner all generate code as a side
effect of doing something else. CodeGen performs the same work on its own, so
the contents of the ".adonisjs" directory can be refreshed deterministically.
For example, inside a CI pipeline before typechecking or creating a build.

The class covers both the codegen stages. The static stage runs the "init"
hooks against an index generator and writes the index files. The runtime stage
runs the routes hooks against the state of a booted application.

Assembler never boots an application, hence the caller boots it from within
the "collectSnapshot" callback and returns the state we cannot collect
ourselves. The callback is invoked after the index files have been written,
because an application imports them from its preload files and therefore
cannot be booted any earlier.

The sequence for sharing and scanning the routes is now shared with the
dev-server using the "processRoutes" util, so the two cannot drift apart.
@thetutlage thetutlage self-assigned this Aug 19, 2026
@thetutlage
thetutlage merged commit 9eff650 into 8.x Aug 19, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant