diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 11f040c..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,187 +0,0 @@ -version: 2.1 - -elixir_current: &elixir_current "1.19.2-erlang-28.1.1-ubuntu-noble-20251001" -elixir_prev: &elixir_prev "1.18.4-erlang-27.3.4.4-ubuntu-noble-20251001" - -defaults: &defaults - working_directory: ~/repo - environment: - LC_ALL: C.UTF-8 - MIX_ENV: test - parameters: - image-tag: - description: "Docker image tag" - type: string - default: *elixir_current - -commands: - install_system_deps: - steps: - - run: | - (type -p wget >/dev/null || (apt update && apt install wget -y)) \ - && mkdir -p -m 755 /etc/apt/keyrings \ - && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - && cat $out | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ - && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ - && mkdir -p -m 755 /etc/apt/sources.list.d \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ - && apt update \ - && apt install -y gh lrzsz ca-certificates \ - && mix do local.hex --force, local.rebar --force - restore_tagged_cache: - parameters: - prefix: - description: "Cache key prefix" - type: string - steps: - - restore_cache: - keys: - - << parameters.prefix >>-{{ .Branch }}-{{ checksum "mix.lock" }} - - << parameters.prefix >>-{{ .Branch }}- - - << parameters.prefix >>- - -jobs: - build: - <<: *defaults - docker: - - image: hexpm/elixir:<< parameters.image-tag >> - steps: - - checkout - - install_system_deps - - restore_tagged_cache: - prefix: v1-mix-cache-<< parameters.image-tag >> - - run: mix do deps.get, compile --warnings-as-errors - - run: MIX_ENV=docs mix do deps.get, compile --warnings-as-errors - - save_cache: - key: v1-mix-cache-<< parameters.image-tag >>-{{ .Branch }}-{{ checksum "mix.lock" }} - paths: - - _build - - deps - - lint: - <<: *defaults - docker: - - image: hexpm/elixir:<< parameters.image-tag >> - steps: - - checkout - - install_system_deps - - restore_tagged_cache: - prefix: v1-mix-cache-<< parameters.image-tag >> - - run: mix deps.unlock --check-unused - - run: mix format --check-formatted - - run: mix credo -a - - run: mix hex.build - - run: MIX_ENV=docs mix docs --warnings-as-errors - - dialyzer: - <<: *defaults - docker: - - image: hexpm/elixir:<< parameters.image-tag >> - steps: - - checkout - - install_system_deps - - restore_tagged_cache: - prefix: v1-mix-cache-<< parameters.image-tag >> - - restore_tagged_cache: - prefix: v1-dialyzer-cache-<< parameters.image-tag >> - - run: mix dialyzer - - save_cache: - paths: - - _build/plts - key: v1-dialyzer-cache-<< parameters.image-tag >>-{{ .Branch }}-{{ checksum "mix.lock" }} - - test: - <<: *defaults - docker: - - image: hexpm/elixir:<< parameters.image-tag >> - steps: - - checkout - - install_system_deps - - restore_tagged_cache: - prefix: v1-mix-cache-<< parameters.image-tag >> - - run: - name: Run tests - command: | - circleci tests glob 'test/**/*_test.exs' | \ - circleci tests run --split-by=timings --command='xargs -n1 echo > test_file_paths.txt' - cat test_file_paths.txt | xargs mix test - - store_test_results: - path: test-junit-report.xml - - publish: - <<: *defaults - docker: - - image: hexpm/elixir:<< parameters.image-tag >> - steps: - - checkout - - install_system_deps - - restore_tagged_cache: - prefix: v1-mix-cache-<< parameters.image-tag >> - - run: - name: Create GitHub Release - command: | - awk -v ver="<< pipeline.git.tag >>" '/^#+ \[/ { if (p) { exit }; if ($2 == "["ver"]") { p=1; next} } p && NF' CHANGELOG.md > release-notes.md - gh release create << pipeline.git.tag >> --title << pipeline.git.tag >> -F release-notes.md - - run: - name: Publish Hex - command: MIX_ENV=docs HEX_API_KEY=${HEX_API_WRITE_KEY} mix hex.publish --yes - - spelling_and_markdownlint: - working_directory: ~/repo - environment: - LC_ALL: C.UTF-8 - docker: - - image: node:24-alpine - steps: - - checkout - - run: - name: Setup - command: | - npm i -g cspell markdownlint-cli2 - - run: - name: Spell check - command: cspell --quiet . - - run: - name: Lint markdown files - command: markdownlint-cli2 - -workflows: - build_test: - # max_auto_reruns: 3 - jobs: - - build: - filters: pipeline.git.tag - matrix: - parameters: - image-tag: - - *elixir_current - - *elixir_prev - - lint: - filters: pipeline.git.tag - image-tag: *elixir_current - requires: - - build - - dialyzer: - filters: pipeline.git.tag - image-tag: *elixir_current - requires: - - build - - test: - filters: pipeline.git.tag - requires: - - build - matrix: - parameters: - image-tag: - - *elixir_current - - *elixir_prev - - publish: - context: - - github-token - filters: pipeline.git.tag starts-with "v" - image-tag: *elixir_current - requires: - - lint - - dialyzer - - test - - spelling_and_markdownlint diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9093894 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,106 @@ +name: Elixir CI + +on: + push: + branches: + - main + - hotfix/[0-9]+.[0-9]+.[0-9]+ + - release/[0-9]+.[0-9]+.[0-9]+ + tags: + - "*" + pull_request: + types: [synchronize, opened, reopened] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: test (${{ matrix.elixir }}-erlang-${{ matrix.erlang }}) + runs-on: ubuntu-latest + env: + MIX_ENV: test + strategy: + fail-fast: false + matrix: + include: + - elixir: "1.19.2" + erlang: "28.1.1" + latest: true + - elixir: "1.18.4" + erlang: "27.3.4.4" + latest: false + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.erlang }} + + - name: Install lrzsz + run: sudo apt-get update && sudo apt-get install -y lrzsz + + - name: Restore deps/build cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.erlang }}-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.erlang }}- + + - name: Restore Dialyzer PLT cache (latest only) + if: matrix.latest + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: _build/plts + key: ${{ runner.os }}-plt-${{ matrix.elixir }}-${{ matrix.erlang }}-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-plt-${{ matrix.elixir }}-${{ matrix.erlang }}- + + - run: mix deps.get + + - run: mix compile --warnings-as-errors + + - run: mix test + + - name: Quality checks (latest only) + if: matrix.latest + run: | + mix deps.unlock --check-unused + mix format --check-formatted + mix credo -a + mix dialyzer + + - name: Docs + hex build (latest only) + if: matrix.latest + run: | + mix deps.get + mix docs --warnings-as-errors + mix hex.build + env: + MIX_ENV: docs + + spelling_and_markdownlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + + - name: Install cspell and markdownlint-cli2 + run: npm i -g cspell markdownlint-cli2 + + - name: Spell check + run: cspell --quiet . + + - name: Lint markdown files + run: markdownlint-cli2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..fbefdd9 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,55 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write # required for `gh release create` via the built-in GITHUB_TOKEN + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Verify matching version in CHANGELOG.md, tag, and mix.exs + run: | + VERSION=$(sed -n 's/.*@version "\([^"]*\)".*/\1/p' mix.exs) + if ! grep -q "## \[v$VERSION\]" CHANGELOG.md; then + echo "Error: Version v$VERSION not found in CHANGELOG.md" + exit 1 + fi + TAG_VERSION=${GITHUB_REF_NAME#v} + if [ "$TAG_VERSION" != "$VERSION" ]; then + echo "Error: Tag version ($TAG_VERSION) does not match mix.exs version ($VERSION)" + exit 1 + fi + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + awk -v ver="${GITHUB_REF_NAME}" '/^#+ \[/ { if (p) { exit }; if ($2 == "["ver"]") { p=1; next} } p && NF' CHANGELOG.md > release-notes.md + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + -F release-notes.md \ + --repo "${GITHUB_REPOSITORY}" + + - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + elixir-version: "1.19.2" + otp-version: "28.1.1" + + - name: Publish to Hex + env: + MIX_ENV: docs + HEX_API_KEY: ${{ secrets.HEX_API_WRITE_KEY }} + run: | + mix deps.get + mix hex.publish --yes