Skip to content

Latest commit

 

History

History
237 lines (180 loc) · 13.7 KB

development.md

File metadata and controls

237 lines (180 loc) · 13.7 KB

Development

The crate uses Rust edition 2024, as declared in Cargo.toml. The repository selects the nightly Rust toolchain with the rustfmt and clippy components in rust-toolchain.toml.

For complete contribution guidelines, environment setup, and contributor expectations, see CONTRIBUTING.md.

Setup

Install rustup, just, lefthook, and actionlint separately. Install semgrep and lizard separately to run the configured quality checks.

Then install the Rust development components, coverage tool, and Git hooks:

just tools
just install-hooks

just tools installs rustfmt, clippy, and cargo-llvm-cov. just install-hooks invokes the separately installed lefthook executable. The installed pre-push hook runs Semgrep, locked Clippy, and Lizard checks sequentially, and blocks pushes when any check fails.

Common Tasks

The project uses just for task automation:

Recipe Description Underlying command
just List the available recipes just --list
just build Build the project in debug mode cargo build
just run *args Run the application with passthrough arguments cargo run -- run {{args}}
just run-all-workflows *args Run all workflows in the repository cargo run -- run --all-workflows {{args}}
just list-workflows List workflows discovered in the repository cargo run -- list-workflows
just list-actions List actions referenced across workflows cargo run -- list-actions
just test Run all default-feature test targets with aggregate line-coverage enforcement at 80%; excludes feature-gated container integration COVERAGE_THRESHOLD=80 ./scripts/check_coverage.sh
just test --crate domain Run coverage for one crate; supported names are root, domain, application, infrastructure, and presentation COVERAGE_THRESHOLD=80 ./scripts/check_coverage.sh --crate domain
just test-local Run all default-feature test targets without coverage; excludes feature-gated container integration cargo test
just lint Run the same locked, all-target Clippy check used by CI cargo clippy --all-targets --locked -- -D warnings
just lint-fix *args Preserve staged files, then run the configured Clippy fix command with optional arguments; no arguments perform no work bash scripts/lint-fix-staged.sh {{args}}
just fmt Format the entire workspace cargo fmt
just fmt-check Check formatting without modifying files cargo fmt --check
just ci Run the local Validate, Test, and Lint checks in CI order just fmt-check && just test && just lint
just tools Install rustfmt, clippy, and cargo-llvm-cov rustup component add rustfmt clippy && cargo install cargo-llvm-cov --locked --force
just clean Remove Cargo build artifacts cargo clean
just lint-workflows Lint Forgejo Actions workflows actionlint -config-file .actionlint.yaml .forgejo/workflows/*.yml
just semgrep Run the configured Semgrep rules and report findings as errors semgrep scan --config .semgrep --error .
just lizard Run Lizard with the repository's complexity, length, argument-count, and ignore thresholds lizard -C 5 -L 50 -a 5 -i 0 .
just install-hooks Install the configured lefthook Git hooks lefthook install
just install-dev Install a debug binary for local iteration cargo install --path . --debug
just install Install a release binary to ~/.cargo/bin cargo install --path .

Extending CLI Commands

CLI commands are defined with clap in crates/presentation/src/cli. When adding a command, keep parsing and execution separate:

  1. Add an argument type in its own *_args.rs module using #[derive(clap::Args)].
  2. Register the type as a variant in cli/command.rs with #[derive(clap::Subcommand)].
  3. Add the corresponding match arm in Cli::execute_command in cli/cli_app.rs.
  4. Add parser and handler tests, then document the command in README.md and docs/usage.md when it is user-facing.

For example, a new inspect command would be registered alongside run:

#[derive(clap::Subcommand)]
enum Command {
    Run(Box<RunArgs>),
    Inspect(InspectArgs), // define InspectArgs in inspect_args.rs
}

The existing run command is invoked through Cargo's argument separator:

cargo run -- run --event pull_request --workflow CI

The first -- passes arguments to ephact; run selects the application subcommand and --workflow CI is parsed by RunArgs. Follow the same pattern for a new command: register it in Command, add its arguments, and dispatch it from Cli. Noninteractive workflow runs require --event.

Running Workflows Locally

Use the existing just recipe names to run the checks that correspond to the Forgejo CI jobs. The repository names these recipes ci, lint, and test (not run-ci, run-lint, or run-test):

# Run the local Validate, Test, and Lint checks in CI order.
just ci

# Run one check independently.
just lint
just test
just fmt-check
just lint-workflows

Run just test --crate domain to limit coverage to one crate. Run just lint-workflows to validate Forgejo workflow syntax with actionlint.

Local command CI job What it checks
just fmt-check Validate Confirms the workspace is formatted with Rustfmt.
just test Test Runs the default-feature test suite with the configured 80% coverage threshold.
just lint Lint Runs locked Clippy checks for all targets with warnings denied.
just lint-workflows Workflow lint Validates Forgejo workflow syntax with actionlint.
just ci Validate, Test, Lint Runs the three local checks in the same order as CI.

Run just ci before opening a pull request when you want the local equivalent of the main CI checks. Install the tools from the Setup section first. The Verify secrets CI job is intentionally not part of just ci: it validates repository credentials and can only run reliably inside Forgejo.

Testing

Default-feature Tests

Run all default-feature test targets with aggregate line coverage enforced at 80%:

just test

Run coverage for one crate with the explicit --crate option:

just test --crate domain

Supported crate names are root, domain, application, infrastructure, and presentation.

Run the same default-feature scope without calculating coverage:

just test-local

Neither command enables the feature-gated container integration target.

Container Integration Tests

The real container integration suite requires at least one available Docker or Podman daemon on Linux and is gated behind the container-integration feature:

cargo test --features container-integration

Continuous Integration Pipeline

The CI workflow in .forgejo/workflows/ci.yml runs on pushes to main, pull requests targeting main, and manual dispatch:

  1. Validate runs cargo fmt --all -- --check.
  2. Workflow lint downloads the pinned actionlint release and validates every .forgejo/workflows/*.yml file with the repository configuration.
  3. Test runs just test, covering default-feature test targets with the 80% aggregate line threshold while excluding feature-gated container integration.
  4. Lint runs cargo clippy --all-targets --locked -- -D warnings.
  5. Verify secrets verifies repository secrets for same-repository runs.

The Validate, Test, and Lint jobs share the .forgejo/actions/setup-rust-environment composite action. It combines install-dependencies with cache-rust-deps and accepts the same cache inputs:

- uses: ./.forgejo/actions/setup-rust-environment
  with:
    cache-key-prefix: ephact
    rustc-version: stable
    include-sysroot: "false"

Workflow linting is available locally through just lint-workflows and runs in CI as the Workflow lint job.

Changelog Release Flow

Merging a pull request into main triggers .forgejo/workflows/update-changelog.yml. The workflow:

  1. Checks that the pull request was merged into main.
  2. Ignores chore/changelog/ and release/v branches to prevent recursion.
  3. Checks out the updated default branch with the built-in Forgejo token.
  4. Generates the unreleased changelog with git-cliff.
  5. Updates the stable chore/changelog/main branch with the generated commit.
  6. Creates or reuses a pull request from that branch into protected main through the Forgejo API.

Versioned releases use prepare-release.yml instead. That workflow passes a release version and target release branch to the same action, which updates the versioned changelog directly on the release branch.

Repository Secrets

Configure the following secrets in Codeberg repository settings under Settings > Actions > Secrets:

Secret Purpose Source / Value
CARGO_REGISTRY_TOKEN Crates.io production publishing Crates.io API token
GH_RELEASE_TOKEN GitHub mirror release publishing GitHub personal access token

Built-in Tokens

FORGEJO_TOKEN is automatically created and injected by Forgejo Actions for each workflow run. Do not add FORGEJO_TOKEN to repository secrets; use the built-in ${{ github.token }} context when a workflow needs the default token.

The changelog workflow passes the built-in token to both checkout and the Forgejo pull-request API call. Versioned release updates continue to push directly to their release branch.

Forgejo Workflow Capabilities

GitHub-style permissions: declarations are unsupported by Forgejo Actions and must not be placed in repository workflows. Capabilities beyond the built-in token require a dedicated repository secret or a Forgejo Authorized Integration configured in the Forgejo user settings. See the official Authorized Integrations documentation.

The integration must be narrowly restricted to the source repository, workflow file, reference, and event. The workflow then enables OIDC, requests a JWT for the generated audience, and sends that JWT as a Bearer token. The generated audience is an identifier, not a secret; grant integration capabilities only for the resources required.