diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a18486..dbdbb5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,46 +2,54 @@ name: CI on: push: - branches: [master] + branches: [main] pull_request: env: MIX_ENV: test + CI_ELIXIR_VERSION: "1.20" + CI_OTP_VERSION: "28.5" + CI_BEAM_VERSION: "1.20-28.5" jobs: test: - name: Unit tests + name: Unit Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) strategy: - fail-fast: true + fail-fast: false matrix: - elixir: - - 1.14.2 - - 1.13.4 - erlang: - - 25.2 - - 24.3 - - runs-on: ubuntu-latest + include: + # https://hexdocs.pm/elixir/compatibility-and-deprecations.html + # compatibility between Elixir and Erlang/OTP + - elixir: "1.17" + otp: "27.3" + - elixir: "1.18" + otp: "27.3" + - elixir: "1.19" + otp: "28.5" + - elixir: "1.20" + otp: "28.5" + + runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.3 - name: Dependency cache - uses: actions/cache@v3 + uses: actions/cache@v5.0.5 with: path: | deps _build - key: mix-test-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + key: test-${{ matrix.elixir }}-${{ hashFiles('mix.exs') }} restore-keys: | - mix-test-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + test-${{ matrix.elixir }}- mix-test-${{ runner.os }}- mix-test- - name: Setup Elixir uses: erlef/setup-beam@v1 with: - otp-version: ${{ matrix.erlang }} + otp-version: ${{ matrix.otp }} elixir-version: ${{ matrix.elixir }} - name: Install dependencies @@ -59,28 +67,28 @@ jobs: formatter: name: Formatter - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.3 - name: Dependency cache - uses: actions/cache@v3 + uses: actions/cache@v5.0.5 with: path: | deps _build - key: mix-fmt-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + key: fmt-${{ env.CI_BEAM_VERSION }}-${{ hashFiles('mix.exs') }} restore-keys: | - mix-fmt-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + fmt-${{ env.CI_BEAM_VERSION }}- mix-fmt-${{ runner.os }}- mix-fmt- - name: Setup Elixir uses: erlef/setup-beam@v1 with: - otp-version: "25.1.2" - elixir-version: "1.14.2" + otp-version: ${{ env.CI_OTP_VERSION }} + elixir-version: ${{ env.CI_ELIXIR_VERSION }} - name: Install dependencies run: mix deps.get @@ -90,28 +98,28 @@ jobs: linter: name: Linter - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.3 - name: Dependency cache - uses: actions/cache@v3 + uses: actions/cache@v5.0.5 with: path: | deps _build - key: mix-lint-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + key: lint-${{ env.CI_BEAM_VERSION }}-${{ hashFiles('mix.exs') }} restore-keys: | - mix-lint-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + lint-${{ env.CI_BEAM_VERSION }}- mix-lint-${{ runner.os }}- mix-lint- - name: Setup Elixir uses: erlef/setup-beam@v1 with: - otp-version: "25.1.2" - elixir-version: "1.14.2" + otp-version: ${{ env.CI_OTP_VERSION }} + elixir-version: ${{ env.CI_ELIXIR_VERSION }} - name: Install dependencies run: | @@ -123,28 +131,28 @@ jobs: dialyzer: name: Dialyzer - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6.0.3 - name: Dependency cache - uses: actions/cache@v3 + uses: actions/cache@v5.0.5 with: path: | deps _build - key: mix-dev-dialyzer-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + key: dialyzer-${{ env.CI_BEAM_VERSION }}-${{ hashFiles('mix.exs') }} restore-keys: | - mix-dev-dialyzer-${{ runner.os }}-${{ hashFiles('**/mix.lock') }} + dialyzer-${{ env.CI_BEAM_VERSION }}- mix-dev-dialyzer-${{ runner.os }}- mix-dev-dialyzer- - name: Setup Elixir uses: erlef/setup-beam@v1 with: - otp-version: "25.1.2" - elixir-version: "1.14.2" + otp-version: ${{ env.CI_OTP_VERSION }} + elixir-version: ${{ env.CI_ELIXIR_VERSION }} - name: Install dependencies run: | diff --git a/.github/workflows/convention.yml b/.github/workflows/convention.yml new file mode 100644 index 0000000..deda0cb --- /dev/null +++ b/.github/workflows/convention.yml @@ -0,0 +1,21 @@ +--- +name: Conventional Commits + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +permissions: + contents: read + pull-requests: write + +jobs: + validate-pr-title: + name: Validate title + runs-on: ubuntu-latest + steps: + - name: PR Conventional Commit Validation + uses: ytanikin/pr-conventional-commits@1.5.2 + with: + task_types: >- + ["feat","fix","doc","docs","test","ci","refactor","perf","chore","revert","style","security"] diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000..a93176a --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,302 @@ +--- +name: Auto-create Release PR + +"on": + push: + branches: + - main + paths-ignore: + - 'CHANGELOG.md' + - 'mix.exs' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-pr-main + cancel-in-progress: true + +jobs: + preflight: + runs-on: ubuntu-latest + outputs: + release_token_available: ${{ steps.release-token.outputs.available }} + steps: + - name: Check release token + id: release-token + env: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + run: | + if [ -n "${RELEASE_APP_ID}" ] && + [ -n "${RELEASE_APP_PRIVATE_KEY}" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "::error title=Release secrets missing::Configure secrets." + exit 1 + fi + + echo "::notice title=Release skipped::Configure release app secrets." + echo "::notice title=Release skipped::GITHUB_TOKEN cannot be used." + echo "available=false" >> "$GITHUB_OUTPUT" + + create-release-pr: + needs: preflight + if: needs.preflight.outputs.release_token_available == 'true' + runs-on: ubuntu-latest + steps: + - name: Create release app token + id: release-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Checkout + uses: actions/checkout@v6.0.3 + with: + fetch-depth: 0 + token: ${{ steps.release-token.outputs.token }} + + - name: Skip release commits + id: skip + run: | + release_commit_regex='^chore\(release\): v[0-9]+\.[0-9]+\.[0-9]+$' + + if git log -1 --pretty=%s | grep -Eq "${release_commit_regex}"; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Check for unreleased commits + if: steps.skip.outputs.skip == 'false' + id: changes + run: | + latest_tag="$( + git describe --tags --abbrev=0 \ + --match 'v*' \ + --match '[0-9]*' \ + 2>/dev/null || true + )" + head_sha="$(git rev-parse HEAD)" + head_short_sha="$(git rev-parse --short HEAD)" + + if [ -n "${latest_tag}" ]; then + ahead="$(git rev-list --count "${latest_tag}"..HEAD)" + compare_url="${{ github.server_url }}" + compare_url="${compare_url}/${{ github.repository }}" + compare_url="${compare_url}/compare/${latest_tag}...${head_sha}" + compare_label="${latest_tag}...${head_short_sha}" + else + ahead="$(git rev-list --count HEAD)" + compare_url="" + compare_label="" + fi + + echo "latest_tag=${latest_tag}" >> "$GITHUB_OUTPUT" + echo "ahead=${ahead}" >> "$GITHUB_OUTPUT" + echo "compare_url=${compare_url}" >> "$GITHUB_OUTPUT" + echo "compare_label=${compare_label}" >> "$GITHUB_OUTPUT" + + if [ "${ahead}" -eq 0 ]; then + echo "No unreleased commits found" + else + base="${latest_tag:-repo start}" + echo "Found ${ahead} unreleased commits since ${base}" + fi + + - name: Compute next version + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' + id: next-version + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --bumped-version + env: + GITHUB_REPO: ${{ github.repository }} + + - name: Normalize next tag + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' + id: next-tag + env: + NEXT_VERSION: ${{ steps.next-version.outputs.content }} + run: | + next_version="${NEXT_VERSION#v}" + echo "tag=v${next_version}" >> "$GITHUB_OUTPUT" + + - name: Skip if no version bump is needed + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' + id: version-check + env: + LATEST_TAG: ${{ steps.changes.outputs.latest_tag }} + NEXT_TAG: ${{ steps.next-tag.outputs.tag }} + run: | + latest_version="${LATEST_TAG#v}" + next_version="${NEXT_TAG#v}" + + if [ -n "${latest_version}" ] && + [ "${latest_version}" = "${next_version}" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Prepare release files + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + env: + RELEASE_TAG: ${{ steps.next-tag.outputs.tag }} + run: | + version="${RELEASE_TAG#v}" + sed -i.bak -E \ + "s/^ @version \".*\"/ @version \"${version}\"/" \ + mix.exs + rm -f mix.exs.bak + + - name: Generate changelog + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --tag ${{ steps.next-tag.outputs.tag }} + env: + OUTPUT: CHANGELOG.md + GITHUB_REPO: ${{ github.repository }} + + - name: Generate release notes preview + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: >- + --unreleased --tag ${{ steps.next-tag.outputs.tag }} + --strip header + env: + OUTPUT: RELEASE_NOTES.md + GITHUB_REPO: ${{ github.repository }} + + - name: Create or update release branch + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + env: + RELEASE_TAG: ${{ steps.next-tag.outputs.tag }} + run: | + branch="release/next" + + git checkout -B "${branch}" + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git add CHANGELOG.md mix.exs + + if git diff --cached --quiet; then + echo "Release branch already matches generated files" + else + git commit -m "chore(release): ${RELEASE_TAG}" + fi + + git push --force-with-lease origin "${branch}" + + - name: Create PR body + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + env: + RELEASE_TAG: ${{ steps.next-tag.outputs.tag }} + AHEAD: ${{ steps.changes.outputs.ahead }} + COMPARE_URL: ${{ steps.changes.outputs.compare_url }} + COMPARE_LABEL: ${{ steps.changes.outputs.compare_label }} + run: | + if [ -n "${COMPARE_URL}" ]; then + compare_line="- Diff: [\`${COMPARE_LABEL}\`](${COMPARE_URL})" + else + compare_line="- Diff: unavailable until the first tag exists" + fi + + cat > /tmp/release-pr-body.md < + ## Release + + This PR prepares \`${RELEASE_TAG}\`. + + - Commits since last tag: ${AHEAD} + ${compare_line} + - Source branch: \`release/next\` + - Base branch: \`${{ github.event.repository.default_branch }}\` + + ## Notes Preview + + $(cat RELEASE_NOTES.md) + EOF + + - name: Find existing release PR + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' + id: existing-pr + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + run: | + pr_number="$(gh pr list \ + --base "${{ github.event.repository.default_branch }}" \ + --head "release/next" \ + --state open \ + --json number \ + --jq '.[0].number // empty')" + + echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" + + - name: Create release PR + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' && + steps.existing-pr.outputs.pr_number == '' + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + RELEASE_TAG: ${{ steps.next-tag.outputs.tag }} + run: | + gh pr create \ + --base "${{ github.event.repository.default_branch }}" \ + --head "release/next" \ + --title "chore(release): ${RELEASE_TAG}" \ + --body-file /tmp/release-pr-body.md + + - name: Update existing release PR + if: >- + steps.skip.outputs.skip == 'false' && + steps.changes.outputs.ahead != '0' && + steps.version-check.outputs.skip == 'false' && + steps.existing-pr.outputs.pr_number != '' + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + RELEASE_TAG: ${{ steps.next-tag.outputs.tag }} + run: | + gh pr edit "${{ steps.existing-pr.outputs.pr_number }}" \ + --title "chore(release): ${RELEASE_TAG}" \ + --body-file /tmp/release-pr-body.md diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 0000000..9717140 --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,73 @@ +--- +name: Auto-tag Release Commits + +"on": + pull_request_target: + types: + - closed + +permissions: + contents: write + +concurrency: + group: release-tag-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + tag-release: + if: >- + github.event.pull_request.merged == true && + github.event.pull_request.head.ref == 'release/next' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Create release app token + id: release-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Checkout + uses: actions/checkout@v6.0.3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.merge_commit_sha }} + token: ${{ steps.release-token.outputs.token }} + + - name: Determine release tag + id: release + run: | + git fetch --tags origin + pr_title='${{ github.event.pull_request.title }}' + release_title_regex='^chore\(release\): v[0-9]+\.[0-9]+\.[0-9]+$' + + if ! printf '%s\n' "${pr_title}" | + grep -Eq "${release_title_regex}"; then + echo "Release PR title is not a valid release title: ${pr_title}" + exit 1 + fi + + tag_name="${pr_title#chore(release): }" + package_version="$( + sed -n 's/^ @version "\(.*\)"/\1/p' mix.exs | head -n 1 + )" + + if [ "${tag_name#v}" != "${package_version}" ]; then + echo "Tag ${tag_name} does not match mix.exs ${package_version}" + exit 1 + fi + + if git rev-parse "${tag_name}" >/dev/null 2>&1; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "tag_name=${tag_name}" >> "$GITHUB_OUTPUT" + + - name: Create and push tag + if: steps.release.outputs.skip == 'false' + run: | + git tag "${{ steps.release.outputs.tag_name }}" + git push origin "${{ steps.release.outputs.tag_name }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fc0b600 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,102 @@ +--- +name: Release + +"on": + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + name: Publish package and GitHub release + runs-on: ubuntu-latest + steps: + - name: Checkout tagged commit + uses: actions/checkout@v6.0.3 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + + - uses: DeterminateSystems/nix-installer-action@v22 + + - name: Initialize Nix dev shell + run: nix develop -c true + + - name: Validate tag matches package version + id: version + run: | + tag_version="${GITHUB_REF_NAME#v}" + package_version="$( + sed -n 's/^ @version "\(.*\)"/\1/p' mix.exs | head -n 1 + )" + + if [ -z "${package_version}" ]; then + echo "Failed to read version from mix.exs" + exit 1 + fi + + if [ "${tag_version}" != "${package_version}" ]; then + echo \ + "Tag ${GITHUB_REF_NAME} does not match mix.exs ${package_version}" + exit 1 + fi + + echo "package_version=${package_version}" >> "$GITHUB_OUTPUT" + + - name: Check whether package version already exists on Hex.pm + id: hex + env: + PACKAGE_VERSION: ${{ steps.version.outputs.package_version }} + run: | + hex_release_url="https://hex.pm/api/packages/ecto_ulid/releases/${PACKAGE_VERSION}" + status="$( + curl -fsS \ + -o /tmp/ecto-ulid-hex-release.json \ + -w '%{http_code}' \ + "${hex_release_url}" || true + )" + + if [ "${status}" = "200" ]; then + echo "skip_publish=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${status}" = "404" ]; then + echo "skip_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Unexpected Hex.pm response for ${PACKAGE_VERSION}: ${status}" + exit 1 + + - name: Publish package to Hex.pm + if: steps.hex.outputs.skip_publish != 'true' + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + run: | + nix develop -c mix local.hex --force + nix develop -c mix deps.get + nix develop -c mix hex.publish --yes + + - name: Generate release notes + uses: orhun/git-cliff-action@v4 + id: release-notes + with: + config: cliff.toml + args: --latest --strip header + env: + OUTPUT: RELEASE_NOTES.md + GITHUB_REPO: ${{ github.repository }} + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + body: ${{ steps.release-notes.outputs.content }} diff --git a/.gitignore b/.gitignore index fa4fa86..b2c8c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez + +/.direnv/ +/.nix/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a2d885 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +.PHONY: deps +deps: + mix deps.get + +.PHONY: build +build: + mix compile + +.PHONY: test +test: + MIX_ENV=test mix test + +.PHONY: fmt +fmt: + mix fmt + +.PHONY: lint +lint: + mix credo + +.PHONY: dialyzer +dialyzer: + mix dialyzer + +.PHONY: clean +clean: + mix deps.clean --unlock --unused + +.PHONY: docs +docs: + mix docs --open + +.PHONY: changelog +changelog: + git cliff -o CHANGELOG.md + +.PHONY: bump +bump: + git cliff --bump -o CHANGELOG.md diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..479b8f0 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,86 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# CHANGELOG\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://tera.netlify.app/docs +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespace from the template +trim = true +# changelog footer +footer = """ + +""" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^doc", group = "Documentation" }, + { message = "^docs", group = "Documentation" }, + { message = "^ci", group = "Continuous Integration" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(release\\): v", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "Miscellaneous Tasks" }, + { message = "^revert", group = "Reverts" }, + { message = "^security", group = "Security" }, + { body = ".*security", group = "Security" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for matching git tags +tag_pattern = "v?[0-9].*" +# regex for skipping tags +skip_tags = "" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +# limit_commits = 42 +recurse_submodules = false diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1f0da5e --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1783279667, + "narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..52a8405 --- /dev/null +++ b/flake.nix @@ -0,0 +1,58 @@ +{ + description = "Textbin - paste and share snippets"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + + in + { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + # Elixir + beam.packages.erlang_28.elixir_1_18 + beam.packages.erlang_28.rebar3 + erlang_28 + + # LSPs + beamPackages.expert + + # Tools + watchman + docker-compose + yamllint + shfmt + shellcheck + git-cliff + yaml-language-server + postgresql + ]; + + shellHook = '' + export MIX_HOME="$PWD/.nix/mix" + export HEX_HOME="$PWD/.nix/hex" + export REBAR_CACHE_DIR="$PWD/.nix/rebar3" + export ERL_AFLAGS="-kernel shell_history enabled" + + mkdir -p "$MIX_HOME" "$HEX_HOME" "$REBAR_CACHE_DIR" + ''; + }; + } + ); +} diff --git a/mix.exs b/mix.exs index fb235c1..1f3c602 100644 --- a/mix.exs +++ b/mix.exs @@ -1,10 +1,12 @@ defmodule Ecto.ULID.Mixfile do use Mix.Project + @version "0.4.0" + def project do [ app: :ecto_ulid, - version: "0.4.0", + version: @version, elixir: "~> 1.13", start_permanent: Mix.env() == :prod, deps: deps(),