From 3281e5d8142f803fb314471869ac89fb0a419bca Mon Sep 17 00:00:00 2001 From: Matt Sutton Date: Mon, 29 Jun 2026 19:17:30 -0400 Subject: [PATCH 1/3] Add CI workflows: OTP/Elixir test matrix and OSV scanner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci.yaml: test matrix over OTP 27/28/29 × Elixir 1.18/1.19/1.20 with incompatible pairs excluded (6 jobs), fail-fast disabled; runs compile/format/test with --warnings-as-errors via setup-beam. - osv-scanner.yaml: scheduled + PR dependency vulnerability scanning, uploading SARIF to the Security tab. - All actions pinned to full commit SHAs with version comments: checkout v7.0.0, setup-beam v1.24.1, osv-scanner-action v2.3.8, codeql-action upload-sarif v4.36.2. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yaml | 53 ++++++++++++++++++++++++++++++ .github/workflows/osv-scanner.yaml | 42 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/osv-scanner.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..389ed25 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,53 @@ +name: CI + +on: + pull_request: + push: + branches: ["main"] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + name: Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }} + strategy: + fail-fast: false + matrix: + otp: ["27", "28", "29"] + elixir: ["1.18", "1.19", "1.20"] + exclude: + # Elixir 1.18 supports OTP 25-27 + - elixir: "1.18" + otp: "28" + - elixir: "1.18" + otp: "29" + # Elixir 1.19 supports OTP 26-28 + - elixir: "1.19" + otp: "29" + env: + MIX_ENV: test + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + + - name: Install dependencies + run: mix deps.get + + - name: Compile (warnings as errors) + run: mix compile --warnings-as-errors + + - name: Check formatting + run: mix format --check-formatted + + - name: Run tests + run: mix test --warnings-as-errors diff --git a/.github/workflows/osv-scanner.yaml b/.github/workflows/osv-scanner.yaml new file mode 100644 index 0000000..c85e396 --- /dev/null +++ b/.github/workflows/osv-scanner.yaml @@ -0,0 +1,42 @@ +# OSV-Scanner — scans dependency manifests against the OSV vulnerability DB. +# https://github.com/google/osv-scanner + +name: OSV-Scanner + +on: + pull_request: + branches: ["master"] + schedule: + - cron: "42 20 * * 3" + push: + branches: ["master"] + +permissions: + contents: read + +jobs: + sca-scan: + permissions: + contents: read + security-events: write # upload SARIF to Security tab + actions: read # required by upload-sarif on private repos + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Run OSV-Scanner + continue-on-error: true + uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8 + with: + scan-args: |- + --format=sarif + --output-file=osv-scanner.sarif + -r + ./ + + - name: Upload SARIF + if: ${{ !cancelled() }} + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + sarif_file: osv-scanner.sarif From c5d2835ba56b1d7fd157a2ec1c520006aaf6e813 Mon Sep 17 00:00:00 2001 From: Matt Sutton Date: Mon, 29 Jun 2026 19:23:43 -0400 Subject: [PATCH 2/3] Add .formatter.exs so the CI format check can run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mix format --check-formatted failed because no .formatter.exs existed — and .gitignore was actively ignoring it. Track the file with the standard config/lib/test inputs, stop ignoring it, and apply the formatting it surfaces in sender_test.exs (multi-line anonymous functions). Co-Authored-By: Claude Opus 4.8 (1M context) --- .formatter.exs | 3 +++ .gitignore | 3 +-- test/segmentry/sender_test.exs | 24 ++++++++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 .formatter.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d304ff3 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore index 9a6ebb3..b062252 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ erl_crash.dump segment-*.tar .DS_Store -.formatter.exs .vscode/ .elixir_ls/ -.dexter +.dexter/ diff --git a/test/segmentry/sender_test.exs b/test/segmentry/sender_test.exs index b89ba9e..dbaa070 100644 --- a/test/segmentry/sender_test.exs +++ b/test/segmentry/sender_test.exs @@ -16,7 +16,11 @@ defmodule Segmentry.Analytics.SenderTest do test "starts and registers under the module name" do parent = self() - adapter = fn req -> send(parent, {:req, req}); {req, %Req.Response{status: 200}} end + + adapter = fn req -> + send(parent, {:req, req}) + {req, %Req.Response{status: 200}} + end {:ok, pid} = Sender.start_link("k", adapter: adapter) assert Process.whereis(Sender) == pid @@ -24,7 +28,11 @@ defmodule Segmentry.Analytics.SenderTest do test "call/1 sends each event immediately and asynchronously" do parent = self() - adapter = fn req -> send(parent, {:req, req}); {req, %Req.Response{status: 200}} end + + adapter = fn req -> + send(parent, {:req, req}) + {req, %Req.Response{status: 200}} + end {:ok, _pid} = Sender.start_link("k", adapter: adapter) @@ -37,7 +45,11 @@ defmodule Segmentry.Analytics.SenderTest do test "single-event call uses /track endpoint, not /batch" do parent = self() - adapter = fn req -> send(parent, {:req, req}); {req, %Req.Response{status: 200}} end + + adapter = fn req -> + send(parent, {:req, req}) + {req, %Req.Response{status: 200}} + end {:ok, _pid} = Sender.start_link("k", adapter: adapter) :ok = Sender.call(%Track{userId: "u", event: "one"}) @@ -48,7 +60,11 @@ defmodule Segmentry.Analytics.SenderTest do test "start_link/1 uses default Req options" do parent = self() - adapter = fn req -> send(parent, {:req, req}); {req, %Req.Response{status: 200}} end + + adapter = fn req -> + send(parent, {:req, req}) + {req, %Req.Response{status: 200}} + end Application.put_env(:segmentry, :req_options, adapter: adapter) From 22d57dc1664988cda3f7a99c80ae8c4caddc51b6 Mon Sep 17 00:00:00 2001 From: Matt Sutton Date: Mon, 29 Jun 2026 19:28:31 -0400 Subject: [PATCH 3/3] Add config/dev.exs to fix dev-env config loading config.exs imports "#{config_env()}.exs" unconditionally, but no dev.exs existed, so every mix task in the default dev env crashed reading a missing file. Add a minimal dev config that defaults :send_to_http to false, so local development uses the no-op adapter instead of hitting Segment. Co-Authored-By: Claude Opus 4.8 (1M context) --- config/dev.exs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 config/dev.exs diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..c4cdf38 --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,7 @@ +import Config + +# In development, don't make real calls to Segment. With `:send_to_http` false, +# the no-op adapter (`Segmentry.Http.Noop`) logs each request at `:debug` and +# replies `200` instead of hitting the network. Flip to `true` to exercise the +# real HTTP path locally. +config :segmentry, send_to_http: false