IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.

Contributing

Local development setup, verification workflow, and a guide to adding a new build-tool adapter.

This page describes everything you need to go from a fresh checkout to a green make check run, and then explains how to add a new build-tool adapter.

For the contribution process and pull-request expectations, read CONTRIBUTING.md. The repository’s CODE_OF_CONDUCT.md defines the conduct policy and private reporting route.


Prerequisites

Tool Minimum version Notes
Node.js as specified in .nvmrc Use nvm: nvm install && nvm use
npm as specified in package.json packageManager field Provision this exact version after selecting Node.js
Java 21+ Required only for local integration tests; any distribution works
Maven (mvn) 3.9+ Required only for the Maven distributed-reuse integration test

No project tooling needs to be installed globally. The setup below only updates the npm CLI in the selected nvm-managed Node.js installation; all project dependencies are installed locally via npm ci.


Local setup

 1# 1. Select the correct Node.js version
 2nvm install   # installs if not present, selects the version from .nvmrc
 3nvm use
 4
 5# 2. Provision the npm version selected by package.json
 6npm_version="$(node scripts/resolve-npm-version.mjs)"
 7if [[ "$(npm --version)" != "$npm_version" ]]; then
 8  npm install --global --ignore-scripts --no-audit --no-fund "npm@$npm_version"
 9fi
10
11# 3. Install dependencies (clean install from package-lock.json)
12make build    # runs npm ci + tsc + esbuild in one step

Running unit tests

1make test           # build (incremental) then run all unit tests
2npm run test        # run tests only (assumes build is current)

Unit tests live in test/ and are discovered by Vitest. They run fast (~1 s) and do not require Java, Maven, or network access.


Running integration tests

Integration tests exercise the full prepare → build → finalize pipeline against real Gradle and Maven builds, using a local file-based artifact store instead of the CI artifact service. They take several minutes because Gradle and Maven download dependencies the first time.

1# All local integration tests (requires Java 21+; Maven also required for the Maven test)
2make integration-test
3
4# Individual tests
5make integration-test-build-reporting           # multi-build Gradle flow with build-reporting
6make integration-test-gradle-distributed-reuse  # worker-A → worker-B → aggregator (Gradle)
7make integration-test-maven-distributed-reuse   # worker-A → worker-B → aggregator (Maven)

To keep the staged workspace on disk after a run (useful for debugging):

1BUILDISH_MAMMOTH_CACHE_KEEP_LOCAL_IT=1 make integration-test-build-reporting

The staged root path is printed at the end of each run.


Code quality gate

make check is the mandatory gate before committing. It runs everything from a clean slate:

1make check

Internally this runs in order: clean-allbuildtestlint-checkrelease-legal-incompatible-license-checkrat-check.

Individual steps:

Command What it does
make lint-check ESLint + Prettier format check
make lint-fix Auto-fix ESLint issues and rewrite Prettier formatting
make rat-check Apache RAT license-header verification (requires Java 21+)
make release-legal-check Verify legal/github/LICENSE and legal/github/NOTICE
make smoke-test End-to-end smoke test of the bundled GitHub Action
make zizmor-check GitHub Actions security analysis (requires zizmor on PATH)

Adding a new build-tool adapter

Mammoth Cache is designed so that all build-tool-specific logic is isolated behind the BuildToolAdapter interface (src/build-tool/types.ts). The shared prepare and finalize phases call adapter methods at well-defined lifecycle points and never import tool-specific modules.

To add a new adapter (for example, for sbt), you need to:

1. Implement BuildToolAdapter

Create src/build-tool/sbt/adapter.ts and implement every method of the interface:

  • getName() — human-readable name shown in job summaries (e.g. "sbt").
  • getBuildToolId() — stable lowercase machine-readable id baked into every cache manifest (e.g. "sbt"). This can never be changed without invalidating all existing caches.
  • getCacheRoot() — absolute path to the directory that should be cached (e.g. $HOME/.sbt or $HOME/.ivy2/cache). Read from the normalized action config.
  • getBuiltInPartitionPresets() — return the ordered list of cache partition presets relevant to your tool. Model them on the Gradle or Maven presets for reference.
  • getHardCacheExcludeGlobs() — globs for files that must never be cached (lock files, daemon sockets, build-tool-internal state that is OS-specific or references absolute paths).
  • provision(options) — download, verify, and install the tool if needed. For tools with a wrapper script (like Gradle), verify the JAR; for tools installed via PATH, just check the version and log it.
  • installBuildHooks(context) — install any hooks that capture per-invocation build metadata (e.g. an init script). If your tool has no hook mechanism, return immediately.
  • collectBuildReport(context) — read the hook output, clean up installed hooks, and return a BuildReport with Markdown summary lines, plain-text log lines, and per-build metadata.

2. Add a config module

Create src/build-tool/sbt/config.ts following the pattern of gradle/config.ts or maven/config.ts. The config module is responsible for:

  • Defining RawSbtActionInputs and NormalizedSbtConfig by extending the shared types in src/config/types.ts.
  • Adding the tool and its inputs to src/config/public-contract.ts, including each allowed config-file value representation, and to the typed tool/input mapping in src/config/inputs.ts.
  • Delegating raw reads and config-file overlay to src/config/inputs.ts and shared normalization to src/config/normalize.ts.
  • Normalizing only sbt-specific values, such as its cache root. Shared code owns cache namespace, family, lineage, generation, cleanup, and distributed-job settings.

3. Create CI entry-point files

Create src/ci/github/sbt/main.ts and src/ci/github/sbt/post.ts following the Gradle or Maven entry-point files. These are thin wrappers that call executePrepareAction and executeFinalizeAction with an sbt-specific buildToolAdapterFactory.

Add the two new bundles to package.json’s bundle:github:sbt:main and bundle:github:sbt:post scripts, and wire them in bundle:github:sbt and bundle:github.

4. Add an action.yml

Create actions/github/sbt/action.yml following the Gradle or Maven action definition. Declare all inputs (shared ones plus any sbt-specific ones) and the two entry-point using: node24 steps.

5. Add unit and integration tests

  • Unit tests: test/build-tool/sbt/adapter.test.ts, test/build-tool/sbt/config.test.ts.
  • Integration test: test/integration/sbt-distributed-reuse.test.ts (if applicable).
  • Wire the integration test into the Vitest integration project and add a make integration-test-sbt-distributed-reuse target.