diff --git a/.github/workflows/test-formulae.yml b/.github/workflows/test-formulae.yml new file mode 100644 index 0000000..d6f13b2 --- /dev/null +++ b/.github/workflows/test-formulae.yml @@ -0,0 +1,115 @@ +name: Test formulae + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Install and test changed formulae + runs-on: macos-latest + env: + HOMEBREW_NO_AUTO_UPDATE: "1" + HOMEBREW_NO_INSTALL_CLEANUP: "1" + HOMEBREW_NO_ANALYTICS: "1" + HOMEBREW_NO_ENV_HINTS: "1" + steps: + - uses: actions/checkout@v7 + with: + # Need the base commit to diff against. + fetch-depth: 0 + + # Only the formulae touched by this change are installed. Testing every + # formula on every push would fail on unrelated pre-existing issues and + # would say nothing about the change under review. + - name: Determine changed formulae + id: changed + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="origin/${{ github.base_ref }}" + git fetch -q origin "${{ github.base_ref }}" + else + base="${{ github.event.before }}" + # First push to a branch has no usable before-commit. + if ! git cat-file -e "$base^{commit}" 2>/dev/null; then + base="HEAD~1" + fi + fi + + formulae=$(git diff --name-only --diff-filter=d "$base" HEAD -- '*.rb' | tr '\n' ' ') + echo "formulae=${formulae}" >> "$GITHUB_OUTPUT" + + if [ -z "$formulae" ]; then + echo "No formula changes in this diff." + else + echo "Changed formulae: ${formulae}" + fi + + # brew audit, install and test all take a formula name rather than a + # path: "brew audit [path ...]" is disabled outright. Register this + # checkout as the tap so the formulae can be referred to by name. + - name: Register the checkout as a tap + if: steps.changed.outputs.formulae != '' + run: | + tap_dir="$(brew --repository)/Library/Taps/clouddrove/homebrew-tap" + mkdir -p "$(dirname "$tap_dir")" + rm -rf "$tap_dir" + ln -s "$GITHUB_WORKSPACE" "$tap_dir" + brew tap | grep clouddrove + + - name: Audit + if: steps.changed.outputs.formulae != '' + run: | + # --online also checks that the homepage and download URL resolve + # and that the checksum matches what is published. + for f in ${{ steps.changed.outputs.formulae }}; do + name="clouddrove/tap/$(basename "$f" .rb)" + echo "::group::brew audit $name" + brew audit --strict --online "$name" || { + echo "::error file=$f::brew audit failed" + exit 1 + } + echo "::endgroup::" + done + + - name: Install from source + if: steps.changed.outputs.formulae != '' + run: | + for f in ${{ steps.changed.outputs.formulae }}; do + name="clouddrove/tap/$(basename "$f" .rb)" + echo "::group::brew install $name" + brew install --build-from-source --verbose "$name" || { + echo "::error file=$f::brew install failed" + exit 1 + } + echo "::endgroup::" + done + + - name: Run the formula test block + if: steps.changed.outputs.formulae != '' + run: | + for f in ${{ steps.changed.outputs.formulae }}; do + name="clouddrove/tap/$(basename "$f" .rb)" + echo "::group::brew test $name" + brew test --verbose "$name" || { + echo "::error file=$f::brew test failed" + exit 1 + } + echo "::endgroup::" + done + + - name: List what was installed + if: steps.changed.outputs.formulae != '' + run: | + for f in ${{ steps.changed.outputs.formulae }}; do + name=$(basename "$f" .rb) + echo "::group::brew list $name" + brew list --verbose "$name" + echo "::endgroup::" + done diff --git a/Formula/noddy.rb b/Formula/noddy.rb new file mode 100644 index 0000000..b5a85b7 --- /dev/null +++ b/Formula/noddy.rb @@ -0,0 +1,41 @@ +class Noddy < Formula + desc "macOS command-line tools for DevOps" + homepage "https://github.com/clouddrove/noddy" + url "https://github.com/clouddrove/noddy/archive/refs/tags/v1.0.0.tar.gz" + sha256 "7824218f528fa0de6d25d064a8a9def1ead8bdd4dfcd88edf43173bea2754463" + license "Apache-2.0" + + depends_on :macos + + def install + # Completions first: libexec.install moves toyland/ rather than copying + # it, so these paths stop existing once it has run. + bash_completion.install "toyland/completion/noddy.bash" => "noddy" + zsh_completion.install "toyland/completion/_noddy" + fish_completion.install "toyland/completion/noddy.fish" + + # noddy sources its plugins from toyland/ beside the script, so the two + # travel together into libexec and only the entry point is linked onto + # PATH. The script resolves symlinks before locating toyland/. + libexec.install "noddy", "toyland" + bin.install_symlink libexec/"noddy" + end + + def caveats + <<~EOS + Optional extras some commands use: + brew install pv # progress bar for tar:compress + brew install fdupes # find:duplicated + brew install glances # system + + Commands that drive the UI, such as lock and eject-all, need your + terminal granted under System Settings > Privacy & Security. + EOS + end + + test do + assert_match "noddy", shell_output("#{bin}/noddy help") + assert_match "git", shell_output("#{bin}/noddy categories") + system bin/"noddy", "info" + end +end