---
# System prepended metadata

title: Bevy CLI and Linter Upstreaming Document
tags: [CLI]

---

# Bevy CLI and Linter Upstreaming

The [Bevy CLI and linter](https://github.com/TheBevyFlock/bevy_cli) have grown more mature in the past year. The CLI has had two releases, and the linter has had four, all received positively. This document intends to lay out a plan for upstreaming the CLI and linter so that they become official Bevy projects.

- [**Repository**](https://github.com/TheBevyFlock/bevy_cli)
- [**Docs**](https://thebevyflock.github.io/bevy_cli/)
- [**Working Group**](https://discord.com/channels/691052431525675048/1278871953721262090)
- [**MVP Doc**](https://hackmd.io/cCHAfbtaSviU_MDnbNHKxg)
- [**Original Issue**](https://github.com/bevyengine/bevy/issues/436)

## Why upstream now?

I feel now is a good time to begin upstreaming the CLI and linter. For one, the code and infrastructure has stabilized significantly since the project's inception. Many core design decisions, such as how the projects deal with configuration, how the CLI handles binary dependencies like `wasm-opt`, and how the linter registers lint passes, have been solved.

Secondly, the projects have grown a non-trivial user base. 41 different people[^issue-openers] have opened issues in the repository, and 122 repositories on Github use the CLI, linter, or both, including[^dependency-graph]:

- `janhohenheim/foxtrot`
- `TheBevyFlock/bevy_new_2d`
- `ickshonpe/bevy_ui_text_input`
- `benfrankel/pyri_tooltip`
- `benfrankel/pyri_state`

And while not a direct statistic, interactions with the release announcements of the project has proved quite favorable. There are quite a few people are who are interested in using the CLI and linter, and are excited when a new release is made:

![Bevy CLI and Linter Interactions](https://hackmd.io/_uploads/rJvZfekTge.png)

*You can view the raw data and additional notes [here](https://docs.google.com/spreadsheets/d/e/2PACX-1vSOT4M6_t96-levSECwywCDtM-fq75NanYhHgVyVg70Swiu8lJQGTFwpJln0TkJRuKSBwaFCmK6p2tA/pubhtml).[^non-unique-people]*

Finally, there is future work that could massively benefit from the CLI and linter being upstreamed. For example, CLI-driven asset preprocessing has been desired since [the MVP document was created](https://hackmd.io/cCHAfbtaSviU_MDnbNHKxg?view#So-what-might-it-do). Our oldest open feature requests are for [cubemap](https://github.com/TheBevyFlock/bevy_cli/issues/6), [meshlet](https://github.com/TheBevyFlock/bevy_cli/issues/11), and [mipmap](https://github.com/TheBevyFlock/bevy_cli/issues/12) generation. Bevy's asset preprocessing story is still lacking, as noted by [Cart](https://discord.com/channels/691052431525675048/695741366520512563/1412570003357564958) and [Jan](https://discord.com/channels/691052431525675048/1404237363550486751/1420943486009479273), but upstreaming the CLI makes it a good candidate for improving that fact. Additionally, upstreaming the linter paves way for integrating it into the Bevy engine's CI. Making it official reduces security concerns for running the linter in Github Actions, and leads way to better support for running it on Bevy's `main` branch.

In summary, now is a good time to upstream the CLI and linter because:

- The project's features and architecture have stabilized into a working local minimum
- It's been adopted by a decent-sized user base
- Further high-benefit work on the project is hindered by its unofficial status[^stalled-work]

[^issue-openers]: Found by running `gh issue list --state all --json author --jq '.[].author.login' --limit 400 | sort | uniq | wc -l`. This command fetches the authors of all issues, sorts their names, removes duplicates, and counts how many there are.
[^dependency-graph]: <https://github.com/TheBevyFlock/bevy_cli/network/dependents>
[^non-unique-people]: Do not be fooled in thinking that interactions = unique people. A person can interact with a release post multiple times, and on multiple platforms. I do still think that interaction correlates with excitement for the project, however, so it's still a useful statistic.
[^stalled-work]: There has also been a recent lapse in contributor activity, as we were worried about introducing new features that would make the CLI and linter harder to review during the upstream. (And life got a bit hectic IRL as well!) Don't mistake a lack of progress as a lack of interest.

## Where should the code go?

From my perspective, there are two potential locations to place the code (and accompanying issues) of the CLI and linter: a new repository named `bevy_cli` under the `bevyengine` org, or `bevyengine/bevy` itself.

### `bevyengine/bevy_cli`

We can create a new repository under the Bevy Organization named `bevy_cli`, and place the CLI and linter code there. This approach is nice because it:

- Lets the CLI and linter be easily released separate from the engine. (The CLI isn't tied to a specific Bevy version.)
- Opens up the possibility of transferring the repository.

This is my preferred approach. It's easy to do and maintains all of the current infrastructure built up around the project.

### `bevyengine/bevy`

The alternative, however, is a mono-repo approach where the CLI and linter are moved to the main `bevyengine/bevy` repository. This approach is beneficial because:

- It increases the visibility and "officialness" of the CLI and linter.
- It lets the linter be easily run in CI while guaranteeing it works with Bevy's `main` branch.
- It can help engine contributors get more involved with the project's development.

The linter may cause some complications with the workspace setup due to its nightly toolchain requirement, but there certainly exists a solution (if somewhat inelegant).

### Conclusion

I personally prefer transferring the project to `bevyengine/bevy_cli`. I think being able to release outside of Bevy's normal release cycle is very important, but I'm not against either approach.

## Where should the docs go?

There are currently 4 categories of documentation related to the CLI and linter:

- The user guides
- The contributing guides
- The lint catalog
- The Rust API documentation

Each category differs slightly in how it should be approached in the upstream process. For each, I hope to cover:

- How should it be built?
- Where should it be stored?
- Where should it be accessed?
- How should it be updated?

### The User Guides

The user guides are the primary user-facing documentation detailing how to use the tools. They are currently built with `mdbook`, stored alongside the code in the `docs` folder, accessible at <https://thebevyflock.github.io/bevy_cli/>, and are updated every time a new commit is pushed to the `main` branch.

I propose that the user guides be migrated to the Bevy website (under <https://bevy.org/learn/cli> and <https://bevy.org/learn/linter>), built using Zola.

However, I believe a copy of the user guides should still live alongside the code, so that PRs can update code and docs all at once. (Although `mdbook` can be removed, leaving the `docs` folder as a plain collection of Markdown files.) During the release process, the `docs` folder can be copied to the Bevy website, similar to how release notes and migration guides are moved from the engine to the website. This avoids issues where unreleased features are mentioned in public documentation, which the CLI and linter's current setup suffers from.

### The Contributing Guides

The contributing guides are a collection of documents targeted to contributors to the CLI and linter. They contain information on the release process, coding conventions, explanations, and recommended readings. The contributing guides currently live alongside the user guide. They are built with `mdbook`, stored alongside the code in the `docs` folder, accessible at <https://thebevyflock.github.io/bevy_cli/>, and are updated every time a new commit is pushed to the `main` branch.

Even more so than the user guides, I believe it is _vital_ that the contributing guides live alongside the code, as they must have information up-to-date with the `main` branch. As such, I don't think we should try to synchronize the CLI and linter contributing guides with <https://bevy.org/learn/contribute/> until some form of automated synchronization makes it easier. (Such as using [JOSH](https://github.com/josh-project/josh), like [what the Rust project is doing](https://github.com/rust-lang/josh-sync).)

### Lint catalog

The lint catalog contains a list of all lints supported by the Bevy linter. It is built using Rustdoc on the `bevy_lint` crate, and is currently available at <https://thebevyflock.github.io/bevy_cli/api/bevy_lint/lints/index.html>.

Once upstreamed and published on crates.io, the lint catalog will be automatically published to docs.rs. I think this works well enough in the short-term, however in the future I'd like to build a better interface available at bevy.org (inspired by [Clippy's lint catalog](https://rust-lang.github.io/rust-clippy/stable/index.html)).

### Rust API docs

The Rust API docs are built using Rustdoc and are accessible at <https://thebevyflock.github.io/bevy_cli/api/>. The primary purpose is to make the CLI Rust interface visible so that the Bevy launcher and editor may potentially use it.

Once the CLI gets published to crates.io, it will automatically benefit from docs.rs building and hosting the API docs. I feel this is sufficient, unless there is a demand for unstable docs (similar to <https://dev-docs.bevy.org>).

## How should the code be transferred?

When transferring the code for the CLI and linter, I have several goals:

1. It should be complete. It should transfer all code and issues, and ideally also releases, tags, branches, and projects.
2. It should be relatively easy to do the transfer. No major redesigns, painstaking 13,000 line code reviews, or manual issue recreation.
3. It should maintain attribution. We've had 23 unique contributors, I want to give them credit for their help!
4. It should maintain history. A person digging through `git blame` should be able to figure out why a piece of code is the way it is, even if they have to jump through a few hoops.

With this in mind, I see 2 potential strategies for transferring the code.

### Transfer Repository Ownership

The easiest way to check all 4 goals is to transfer the repository ownership from `TheBevyFlock` to `bevyengine`. All of the existing code gets moved, it's super easy to do (there's a button at the bottom of the settings page!), it maintains attribution for all past contributors, and preserves the history (every single PR, issue, and commit).

There are three follies to this strategy, however.

First, all code review must be done at once. If maintainers wish to review the codebase before transferring, they must do it all at once before the transfer can be done. This means that any changes requested will block the entire process, similar to when reviewing one giant pull request. It also means maintainers are much more likely to say "LGTM 👍" without thoroughly reviewing the entire codebase, missing potential issues.

Second, this strategy only works when moving the code to `bevyengine/bevy_cli`, not `bevyengine/bevy`. If we want to go with the monorepo approach, transferring ownership will not work.

Third, Github Pages are not transferred. We will need to update the docs at <https://thebevyflock.github.io/bevy_cli> to say they are outdated, and point to the new location, before the transfer is done.

### Upstream Piece-by-Piece in Small PRs

This approach is more difficult, but may be more rewarding if done correctly. In this approach, the code is split up into several smaller segments. Each segment is then upstreamed separately, in small reviewable PRs. In order to maintain history and attribution, the PR descriptions will contain:

1. A link to the original commit the code is extracted from (e.g. <https://github.com/TheBevyFlock/bevy_cli/tree/770dffa22c4a512375d13343269fa94dd1163e99>).
2. A list of co-authors at the bottom, using [Github's format](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors) and `git shortlog --summary --numbered --email -- ./path/to/file`.

The issues can then be transferred with the [Github CLI (`gh issue transfer`)](https://cli.github.com/manual/gh_issue_transfer) and the [projects can be copied](https://docs.github.com/en/issues/planning-and-tracking-with-projects/creating-projects/copying-an-existing-project?versionId=free-pro-team%40latest&productId=pull-requests&restPage=committing-changes-to-your-project%2Ccreating-and-editing-commits%2Ccreating-a-commit-with-multiple-authors) to the repository.

The strength of this approach is that it makes reviewing the project significantly easier, and lets us fix poor design decisions made during development of the prototype. However, the process of splitting up the project into smaller segments is challenging and time consuming. It may also be a bit overwhelming, depending on how many different segments there are and how long the process takes.

## Release Process

The current release process for the CLI and linter can be found [here](https://thebevyflock.github.io/bevy_cli/contribute/cli/release.html) and [here](https://thebevyflock.github.io/bevy_cli/contribute/linter/how-to/release.html). Once upstreamed, new releases will follow the same process with a few exceptions:

- The CLI will no longer be in alpha, and will have its first v0.1.0 official release
- Releases will be published to crates.io, rather than just tagged in Git
- Major updates will be announced on bevy.org
- Releases will be published by Bevy engine maintainers (or the project lead)
    - I (BD103) have handled releases in the past, and I'd be happy to walk a maintainer through the release process!

### Releasing Past Versions

While by no means necessary, I think it would be very nice to upload the past releases of the CLI and linter to crates.io, as it keeps the old versions accessible for inspection and comparison. This is especially relevant for the linter, as it lets the [lint catalog](https://thebevyflock.github.io/bevy_cli/api/bevy_lint/index.html) for past releases to be easily accessed.[^bevy-lint-docs].

[^bevy-lint-docs]: Although I don't think the lint documentation will stay on docs.rs forever. I think eventually it would be better to migrate the lint docs to bevy.org, but work on that can wait until after the upstream.

## Communications

With the upstreaming of the CLI and linter, the CLI working group should be retired. However, there is still much benefit in having a shared space where contributors can communicate about the project.

I propose a new channel, named either `#cli-dev` or `#tooling-dev`, be created in the Bevy Discord for CLI and linter development. While conversation could move to the existing `#editor-dev`, I feel like the projects are distinct enough that they would benefit from separate channels.

## Maintainership

Currently, there are three people with write permissions on the `bevy_cli` repo:

- BD103 (Administrator access, mainly works on the linter)
- TimJentzsch (Write access, mainly works on the CLI)
- DaAlbrecht (Write access, works on both the CLI and linter)

All three maintainers were driving forces in the working group, and have proved their expertise and trustworthiness. I propose that they be designated as Subject Matter Experts so that other contributors know to reach out to them for guidance or questions. (Either SME-Tooling for all three, or SME-CLI for TimJentzsch and DaAlbrecht and SME-Linter for BD103 and DaAlbrecht.)

However, I do not believe these three should keep write permissions after the upstream, as doing so would reduce how involved the Bevy maintainers are in the project. The Bevy maintainers and project lead should take ownership of the CLI and linter, and a good way to do so is to give them responsibility for merging pull requests.

## Review Process

Currently, pull requests to the CLI and linter require 1 approval before they can be merged. The Bevy engine, however, requires 2 approvals before PRs can be merged.

I propose that the review requirement for the CLI and linter be increased to 2, as it:

- Requires more contributors get involved with the project
- Generally improves the quality of pull requests
- Matches the behavior of existing Bevy repositories

## FAQ

### Why not rewrite the project from scratch?

Because that's the [easiest way to kill a project](https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/). The CLI and linter are not demo software that can be easily torn down and redone. Although appealing to those unfamiliar with the code base, the effort of recreating the projects from the ground up surpasses that of incrementally refactoring the existing code.

### Why not split the CLI and linter into two separate projects?

The Bevy CLI working group has grown a tight-knit community that, although small, has been one of the highlights of my open-source career. I worry that splitting the CLI and linter into two separate repositories will fracture that community, and hurt both projects in the process.

For example, each project individually does not have enough contributors yet for sustained development. However, when development is done in a shared repository, there are enough eyes for consistent PR reviews and issue triage.

Additionally, keeping the two programs in the same repo has lead to beneficial integration between the two. For example, `bevy lint install` improves the user experience of installing the linter significantly. Features like that are less likely to arise in the future if the two are kept separate.

In sum, I believe keeping the CLI and linter together leads to a healthier contributor environment while also encouraging better integration between the two projects.

### What makes the linter particularly difficult to maintain?

Ignoring the domain-specific knowledge required for working on the linter, there are 3 main reasons why the linter is more difficult to maintain than a normal Rust project.

First, the linter relies on [`rustc`'s internal crates](https://doc.rust-lang.org/nightly/nightly-rustc/) using [`#![feature(rustc_private)]`](https://doc.rust-lang.org/nightly/unstable-book/language-features/rustc-private.html) to do the brunt of parsing, path resolving, and type checking so that the linter can focus on static analysis. While this is invaluable in simplifying the linter, the Rust compiler devs offer zero support for this process. The `rustc_*` crates have no changelog, frequent breaking changes, and poor / outdated documentation. This makes the process of updating `bevy_lint` to a newer Rust version a unique challenge that relies heavily on tests, commit logs, `git blame`, and Rust's fantastic error messages.

Second, the linter must release at least once every 18 weeks or the nightly version that it uses will not support the features available in the latest stable Rust. This was an issue in the past where [`let`-chains were stabilized but the most recent version of the linter didn't support them](https://github.com/TheBevyFlock/bevy_cli/issues/528). This has resulted in the linter "Rustup"-ing every 6 weeks, matching the same nightly toolchain used by [`clippy_utils`](https://crates.io/crates/clippy_utils). This 4-month release cadence aligns with Bevy's, but unlike the engine it is not adjustable without potentially breaking users.

Finally, the linter hard-codes the full paths of all traits and types that it checks for, even if these paths are not public. For example, in Bevy 0.14 the `Event` trait [lived in `bevy_ecs::event::Event`](https://github.com/bevyengine/bevy/blob/5c759a1be800209f537bea31d32b8ba7e966b0c1/crates/bevy_ecs/src/event.rs#L51). In 0.15 the trait [was moved to `bevy_ecs::event::base::Event`](https://github.com/bevyengine/bevy/blob/75f04a743bc3da77d3d1fac9d9322920d56ed05b/crates/bevy_ecs/src/event/base.rs#L37) as part of [#13801](https://github.com/bevyengine/bevy/pull/13801). This was not a user-facing breaking change, but it [broke the linter anyways while migrating it to Bevy 0.15](https://github.com/TheBevyFlock/bevy_cli/pull/191/commits/138fa469e997516524014dbebf790f2da37c8c68). While the linter could be made independent of paths using [diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html), that requires making changes to the Bevy engine source code, which means the linter needs to be upstreamed first.

### How should `bevy_new_2d` and `bevy_new_minimal` be handled?

The `bevy new` command has special support for the [`bevy_new_minimal`](https://github.com/TheBevyFlock/bevy_new_minimal) and [`bevy_new_2d`](https://github.com/TheBevyFlock/bevy_new_2d) templates. Upstreaming the CLI could transitively imply that these templates are official as well. This may be an issue, as the templates are opinionated and not fully vetted by Bevy's maintainers.

I don't think we should try upstreaming the templates now, as I fear it will result in too much bikeshedding and slow down the rest of the process. Instead, I propose that we document that these templates are unofficial in the user guide or remove support for the `--template 2d` shorthand.
