Skip to content
This repository was archived by the owner on Jul 26, 2026. It is now read-only.

Latest commit

 

History

History
183 lines (129 loc) · 7.27 KB

File metadata and controls

183 lines (129 loc) · 7.27 KB

Contributing to Asimov

Thanks for your interest in contributing! Asimov is a small, focused project and contributions of all sizes are welcome.

Getting started

Prerequisites: macOS with Homebrew installed.

git clone https://github.com/django23/asimov.git
cd asimov
brew install bats-core shellcheck

Verify everything works:

make check

Development workflow

Command What it does
make help List available make targets with descriptions
make test Run the Bats test suite
make lint Run ShellCheck on all shell scripts
make check Run both tests and linting
make version Print asimov version
make exclusions List all paths excluded from Time Machine (requires sudo)

To run a single test by name: bats tests/behavior.bats --filter "substring of test name" or bats tests/sentinels.bats --filter "npm".

The main script supports --help, --version, --dry-run, --verbose, and --quiet; unknown options exit with an error.

Adding a new dependency pattern

This is the most common type of contribution. To add a new ecosystem or dependency directory:

  1. Add the sentinel pair to the ASIMOV_VENDOR_DIR_SENTINELS array in asimov — one 'directory sentinel' entry per pattern.
  2. Add a test in tests/sentinels.bats using create_project to build the fixture. Keep this file in sync with ASIMOV_VENDOR_DIR_SENTINELS (one test per sentinel pair).
  3. Run make check to verify your changes pass tests and linting.
  4. Add a changelog entry under the [Unreleased] section in CHANGELOG.md.

Example sentinel entry:

'.zig-cache build.zig'   # Zig build cache

This means: exclude .zig-cache/ only when build.zig exists in the same directory.

Commit conventions

This project uses Conventional Commits. Format your commits as:

type(scope): short description

Types: feat, fix, docs, test, refactor, chore, build, ci

Examples:

  • feat(sentinels): add Zig build cache exclusion
  • fix: prevent duplicate exclusions on re-run
  • test: add coverage for Go modules
  • docs: update installation instructions

Pull requests

  • Branch from main (the default branch).
  • Keep PRs focused — one feature or fix per PR.
  • Ensure make check passes before submitting.
  • Update CHANGELOG.md for user-facing changes.

Code style

  • Bash/shell: 2-space indentation, LF line endings, UTF-8.
  • Enforced via EditorConfig — most editors pick this up automatically.

Project structure

asimov                  # Main bash script
tests/
  sentinels.bats        # Tests for each dependency pattern
  behavior.bats         # Tests for edge cases and general behavior
  format.bats           # Unit tests for format_size_kb()
  plist.bats            # Tests for the LaunchAgent plist
  test_helper.bash      # Shared setup/teardown and assertions
  bin/tmutil            # Mock tmutil for testing
  bin/mdfind            # Mock mdfind for testing
scripts/
  install.sh            # Local installation script
  install-remote.sh     # Curl-based remote installer
  uninstall.sh          # Uninstallation script
Makefile                # Build targets (test, lint, check, install, uninstall)

Releasing (maintainers)

main is protected: signed commits + PR required + both CI checks (test (macos-14), test (macos-15)) must pass. The flow below respects all of that.

One-time setup

  1. Install signing keys (see SSH-signed commits below) in both the asimov clone and the homebrew-tap clone. make bump-formula commits in the tap and will fail if signing isn't configured there.

  2. Clone the tap alongside this repo:

    git clone git@github.com:django23/homebrew-tap.git ../homebrew-tap

Per-release flow

make prep-release VERSION=X.Y.Z              # branch, bump version, promote CHANGELOG, run check, commit
git push -u origin release/X.Y.Z
gh pr create --base main --fill --title "docs: release X.Y.Z"
gh pr checks <PR#> --watch --fail-fast

# After the PR is merged:
gh pr merge <PR#> --squash --delete-branch
git checkout main && git pull --ff-only

make release                                 # signed tag + push
make ship-formula                            # waits for release.yml, bumps tap formula, pushes
make verify-release                          # brew upgrade + asimov --version

If gh pr create fails with Head sha can't be blank (GraphQL indexing lag), retry once or fall back to the REST API:

gh api repos/django23/asimov/pulls -X POST \
  -f title="docs: release X.Y.Z" \
  -f head="release/X.Y.Z" -f base="main" \
  -f body="See CHANGELOG.md"

Beta releases

Skip prep-release (no CHANGELOG promotion needed). Run make release-beta from any branch — it auto-increments the -beta.N suffix and marks the GitHub release as pre-release. Skip bump-formula and ship-formula for betas.

If something goes wrong

Tag already exists (duplicate from an earlier attempt). Delete remote tag + release, prune locally, re-run:

gh release delete vX.Y.Z --yes --cleanup-tag
git fetch --prune --prune-tags origin
git tag -d vX.Y.Z 2>/dev/null || true
make release                                  # re-tags from current main

make bump-formula fails with gpg failed to sign the data: No secret key. The tap's git config doesn't have SSH signing set up. Apply the same git config block from the SSH-signed commits section inside ../homebrew-tap.

CI fails on the release PR. Fix locally, push to the PR branch, re-run gh pr checks <PR#> --watch. Don't merge until green.

You merged the PR but make release aborts with "working tree not clean". Run git status — you likely have a stray local file. git stash it and retry.

make prep-release fails with "working tree not clean" but the dirty files ARE the release. prep-release assumes the version bump comes first and the substantive changes follow. If your work is already uncommitted, do it manually: branch (git checkout -b release/X.Y.Z), commit your changes, then bump ASIMOV_VERSION in asimov and run the awk block from scripts/prep-release.sh against CHANGELOG.md to promote [Unreleased] and add the compare links. Run make check, commit, push, open the PR.

SSH-signed commits (one-time per clone)

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -N ""
gh auth refresh -s admin:ssh_signing_key
gh ssh-key add ~/.ssh/id_ed25519_signing.pub --type signing --title "$(hostname)"

git config user.email "$(gh api user --jq '.id')+$(gh api user --jq '.login')@users.noreply.github.com"
git config gpg.format ssh
git config user.signingkey ~/.ssh/id_ed25519_signing.pub
git config commit.gpgsign true
git config tag.gpgsign true

mkdir -p ~/.config/git
echo "$(git config user.email) $(awk '{print $1, $2}' ~/.ssh/id_ed25519_signing.pub)" >> ~/.config/git/allowed_signers
git config gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers

Questions?

Open an issue — happy to help.