Contributing to apex-mutation-testing

August 18, 2026 · View on GitHub

We encourage the developer community to contribute to this repository. This guide has instructions to install, build, test and contribute to the framework.

Requirements

  • Node >= 22.22 (supported: 22, 24, 26)
  • npm >= 10.9.0

Installation

1) Download the repository

git clone git@github.com:scolladon/apex-mutation-testing.git

2) Install Dependencies

This will install all the tools needed to contribute

npm install

3) Build application

npm pack

Rebuild every time you made a change in the source and you need to test locally

Environment secrets

Secrets are read from the environment, never hardcoded and never committed to sfdx-project.json. Prefer tools that read a secret from the environment over ones that take it as a command-line argument, since arguments are visible to any process on the machine via ps. Where a third-party tool requires an argument anyway, pass the variable ("$MY_SECRET") rather than the literal value, so the secret stays out of shell history and out of this repository.

Locally, put them in a .env file at the repository root. It is git-ignored. Use the same variable names as the repository secrets, so a local run and a CI run resolve identically:

# .env — never commit this file
AER_LICENSE_KEY=

Nothing loads .env automatically yet, so export it into your shell for now (set -a; . ./.env; set +a, or a tool such as direnv).

VariableRepository secretNeeded for
AER_LICENSE_KEY✅ same nameCarrying the aer licence key into a run (see below)

Note for CI: pull requests from forks cannot read repository secrets, so any job requiring a key must be restricted to non-fork branches or stay within the free tier.

The aer licence

The local Apex runtime (aer) reads AER_LICENSE_KEY from the environment, which is the recommended way to supply it in CI. It can also be registered once and stored on disk with aer license register <key> — prefer the environment variable, since a command-line argument is visible in ps and in CI logs.

What actually needs a licence:

  • aer test has a free tier — 100 test methods per iteration, with an unlimited number of iterations. Ordinary mutation runs stay inside it, because each mutant executes only the test methods covering the mutated line. Only a baseline run over a large test perimeter can exceed it.
  • aer server requires a licence. Without one it shuts down after five minutes.

The repository's key is for CI only. It is an open-source project licence, and aer validates that it is being used from a GitHub Action on a public repository — so it does not work on a developer machine, by design. Running aer server locally needs a developer licence; a trial is available by registering an email address at octoberswimmer.com.

Testing

Unit Testing

When developing, use vitest unit testing to provide test coverage for new functionality. To run the vitest tests use the following command from the root directory:

# just run test
npm run test:unit

To execute a particular test file or run tests matching a pattern, use the following commands:

# run a single test file
npx vitest run test/unit/path/to/file.test.ts

# run tests matching a pattern
npx vitest run -t "pattern"

NUT Testing

When developing, use Vitest NUT tests to provide command-level functional coverage with a mocked org. To run the NUT tests use the following command from the root directory:

# run test
npm run test:nut

E2E Testing

E2E tests run the full mutation testing pipeline against a real Salesforce org, produce an HTML report, and validate the output against a committed snapshot (test/e2e/index.html).

# Run locally (uses node ./bin/run.js + apex-mutation-testing org)
npm run test:e2e:run:local

# Validate snapshot matches
npm run test:e2e:validate

The validation step uses git diff --quiet test/e2e/ — any difference from the committed snapshot fails the check.

Known Flaky Mutant

The MemberVariableMutator mutation on String label = '' (line 2 of Mutation.cls) is non-deterministic. The Salesforce org sometimes returns CompileError and sometimes Survived for this mutation. The committed snapshot uses Survived as the stable baseline. If the e2e CI job fails only on this entry, re-run the job.

Editor Configurations

Configure your editor to use our lint and code style rules.

Code formatting

Biome Format, lint, and more in a fraction of a second.

Code linting

Biome Format, lint, and more in a fraction of a second.

Commit linting

This repository uses Commitlint to check our commit convention. Pre-commit git hook using husky and pull request check both the commit convention for each commit in a branch.

You can use an interactive command line to help you create supported commit message

npm run commit

Engine linting

This repository uses ls-engines to verify the running Node version is within the supported range and that engines.node stays consistent with the dependency graph. It runs as a blocking pre-push git hook and as a pull request check.

Dependency policy

This repository is kept aligned with its three sibling plugins (sfdx-git-delta, apex-mutation-testing, sf-git-merge-driver, dataset-loader), so the rules below are identical in all four.

  • Every dependency is pinned exactly — runtime and dev alike. No ^, no ~, no ranges. A range in a runtime dependency becomes non-determinism for consumers, and a range in a dev dependency becomes drift between the four repositories.
  • .npmrc sets save-exact=true, so npm install <package> records an exact version by default. This is the only mechanism enforcing the rule — keep the file. save-exact cannot be expressed in package.json: npm reads it from .npmrc or the npm_config_save_exact environment variable, and publishConfig applies at publish time only.
  • Pins track current latest. Dependabot moves them; its versioning-strategy: increase raises a pinned requirement in place rather than widening it, so grouped updates stay exact.
  • npm 12 is required (engines.npm: ">=12"), and no shrinkwrap is shipped. npm 12 excludes npm-shrinkwrap.json from npm pack even when it is listed in files, silently and with exit 0, so the mechanism is inert rather than merely unused.
  • There is deliberately no lint for this. npm outdated runs as a blocking check in CI and catches a pin that has fallen behind latest, but it cannot see a range that still resolves to latest. Adding a hand-edited range is caught in review, not by tooling.

What the pinning does and does not buy: it caps only the direct dependencies a consumer resolves. The transitive majority still floats, and capping those would mean declaring the whole chain directly.

PR linting

When a PR is ready for merge we use the PR name to create the squash and merge commit message. We use the commit convention to auto-generate the content and the type of each release It needs to follow our commit lint convention and it will be check at the PR level

Git Workflow

The process of submitting a pull request is straightforward and generally follows the same pattern each time:

  1. Fork the repo
  2. Create a feature branch
  3. Make your changes
  4. Rebase
  5. Check your submission
  6. Create a pull request
  7. Update the pull request

Fork the repo

Fork the scolladon/apex-mutation-testing repo. Clone your fork in your local workspace and configure your remote repository settings.

git clone git@github.com:<YOUR-USERNAME>/apex-mutation-testing.git
cd apex-mutation-testing
git remote add upstream git@github.com:scolladon/apex-mutation-testing.git

Create a feature branch

git checkout main
git pull origin main
git checkout -b feature/<name-of-the-feature>

Make your changes

Change the files, build, test, lint and commit your code using the following command:

git add <path/to/file/to/commit>
git commit ...
git push origin feature/<name-of-the-feature>

Commit your changes using a descriptive commit message

The above commands will commit the files into your feature branch. You can keep pushing new changes into the same branch until you are ready to create a pull request.

Rebase

Sometimes your feature branch will get stale on the main branch, and it will must a rebase. Do not use the github UI rebase to keep your commits signed. The following steps can help:

git checkout main
git pull upstream main
git checkout feature/<name-of-the-feature>
git rebase upstream/main

note: If no conflicts arise, these commands will apply your changes on top of the main branch. Resolve any conflicts.

Check your submission

Lint your changes

npm run lint

The above command may display lint issues not related to your changes. The recommended way to avoid lint issues is to configure your editor to warn you in real time as you edit the file.

the plugin lint all those things :

  • typescript files
  • folder structure
  • plugin parameters
  • plugin output
  • dependencies
  • dead code / configuration

Fixing all existing lint issues is a tedious task so please pitch in by fixing the ones related to the files you make changes to!

Run tests

Test your change by running the unit tests and integration tests. See the testing instructions.

Create a pull request

If you've never created a pull request before, follow these instructions. See pull request samples

Update the pull request

git fetch origin
git rebase origin/${base_branch}

# Then force push it
git push origin ${feature_branch} --force-with-lease

note: If your pull request needs more changes, keep working on your feature branch as described above.

CI validates prettifying, linting and tests

Collaborate on the pull request

We use Conventional Comments to ensure every comment expresses the intention and is easy to understand. Pull Request comments are not enforced, it is more a way to help the reviewers and contributors to collaborate on the pull request.

CLI parameters convention

The plugins uses sf cli parameters convention to define parameters for the CLI.

Testing the plugin from a pull request

Every pull request — including one opened from a fork — publishes a preview build of the plugin to pkg.pr.new. A bot comment on the pull request carries the exact install command:

sf plugins install https://pkg.pr.new/apex-mutation-testing@<commit>

Previews are addressed by commit, not by pull request: each push produces a new URL, so read the install command again rather than re-running an earlier one. Preview builds are removed once they have gone a month without a download, and after six months in any case — reinstall from a fresh comment if an old link stops resolving.

A preview from a fork is unreviewed contributor code, and installing a plugin executes arbitrary code with your Salesforce org credentials in reach. Install those only in a disposable environment. Fork pull requests get no comment — their token is read-only — so the URL must be read from the preview job's log.

Previews publish only once the pkg-pr-new GitHub App is installed on the repository. Without it the preview job fails with a self-describing 404.

To go back to the released plugin:

sf plugins uninstall apex-mutation-testing
sf plugins install apex-mutation-testing@latest-rc