towerops/mix.exs
Graham McIntire 3bf21c746f refactor: upgrade to Elixir 1.20, extract GraphLive.Polling, async subscriber impact
- Bump elixir requirement from ~> 1.19 to ~> 1.20 for gradual typing
- Extract SNMP polling functions from graph_live/show.ex (1492→1118 lines)
  into new GraphLive.Polling module (363 lines) — 374 lines net reduction
- Defer subscriber impact API call via start_async/handle_async in
  device_live/show.ex, avoiding synchronous Gaiia API call in handle_params
- Add terminate/2 to graph_live/show.ex for live_poll timer cleanup
- Fix .tool-versions to match installed Erlang 27.3.4.2
- Relax AGENTS.md dialyzer policy: allow ignore file for deps/vendored code
2026-07-22 11:01:00 -05:00

208 lines
7.3 KiB
Elixir

defmodule Towerops.MixProject do
use Mix.Project
def project do
[
app: :towerops,
version: "0.1.0",
elixir: "~> 1.20",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
# NIF compiled by `make -C c_src` in dev shell (nix/shell.nix) and
# pre-built by `towerops-nif` Nix package in CI/prod. Not an auto-compiler
# because Mix.Tasks.Compile.ToweropsNif is part of the project source and
# isn't available on the code path when Mix first resolves the compilers
# list in a fresh build. Run `mix compile.towerops_nif` manually when
# changing C source between `mix compile` runs in dev.
compilers: [:phoenix_live_view] ++ Mix.compilers(),
prune_code_paths: false,
listeners: [Phoenix.CodeReloader],
dialyzer: dialyzer(),
test_coverage: [ignore_modules: ignore_modules_for_coverage()]
]
end
# Vendored SnmpKit library, generated protobuf modules, and purely
# declarative Phoenix/Absinthe type modules are excluded from coverage
# calculations since they are third-party, generated, or data-only code.
defp ignore_modules_for_coverage do
[
~r/^SnmpKit/,
~r/^Towerops\.Agent\./,
~r/^Inspect\./,
~r/^ToweropsWeb\.GraphQL\.Types\./,
~r/HTML$/,
# Test helpers (compiled into :test env so they show up in coverage)
ToweropsWeb.LiveViewTestHelpers,
ToweropsWeb.ConnCase,
Towerops.DataCase,
Towerops.Lidar.Test.FakeParser,
Towerops.Test.StubTerrain,
# Release-only DB migrate/rollback task — only meaningful in a deployed
# release where Mix isn't loaded; can't be exercised under ExUnit.
Towerops.Release,
# Mix compile task that shells out to `make` to build the C NIF — tested
# implicitly by every other test (the NIF must build for the suite to load).
Mix.Tasks.Compile.ToweropsNif,
# Postgrex types macro emits a defmodule but no testable code paths.
Towerops.PostgrexTypes,
# Absinthe schema DSL — every `field/arg/middleware` line registers
# metadata at compile time. Resolvers are tested via GraphQL integration
# tests; the schema declarations themselves carry no runtime behaviour.
ToweropsWeb.GraphQL.Schema,
# OTP boot — runs once at app start; covered implicitly by every test
# (the app must boot for ExUnit to run) but the boot-time conditionals
# for prod/dev paths can't be exercised under :test.
Towerops.Application
]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {Towerops.Application, []},
extra_applications: [:logger, :runtime_tools, :os_mon]
]
end
def cli do
[
preferred_envs: [precommit: :test]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:argon2_elixir, "~> 4.0"},
{:nimble_totp, "~> 1.0"},
{:eqrcode, "~> 0.2.1"},
{:phoenix, "~> 1.8.3"},
{:phoenix_ecto, "~> 4.5"},
{:ecto_sql, "~> 3.13"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 4.1"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 1.2"},
{:lazy_html, ">= 0.1.0", only: :test},
{:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
{:heroicons,
github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1},
{:swoosh, "~> 1.24"},
{:gen_smtp, "~> 1.0"},
{:req, "~> 0.5"},
{:gaiia, path: "vendor/gaiia"},
{:castore, "~> 1.0"},
{:sweet_xml, "~> 0.7"},
{:yaml_elixir, "~> 2.9"},
{:telemetry_metrics, "~> 1.0"},
{:telemetry_poller, "~> 1.0"},
{:prom_ex, "~> 1.11"},
{:gettext, "~> 1.0"},
{:jason, "~> 1.2"},
{:dns_cluster, "~> 0.2.0"},
{:libcluster, "~> 3.4"},
{:oban_pro, "~> 1.7", path: "vendor/oban_pro"},
{:oban_met, "~> 1.0", path: "vendor/oban_met", override: true},
{:oban_web, "~> 2.11", path: "vendor/oban_web"},
{:bandit, "~> 1.5"},
{:phoenix_pubsub_redis, "~> 3.0"},
{:ecto_psql_extras, "~> 0.6"},
{:mox, "~> 1.0", only: :test},
{:absinthe, "~> 1.7"},
{:absinthe_plug, "~> 1.5"},
{:absinthe_phoenix, "~> 2.0"},
{:error_tracker, "~> 0.7"},
{:error_tracker_notifier, "~> 0.2"},
{:stream_data, "~> 1.1", only: :test},
{:tzdata, "~> 1.1"},
{:styler, "~> 1.10", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:jump_credo_checks, "~> 0.4", only: [:dev, :test], runtime: false},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:inet_cidr, "~> 1.0"},
{:cloak_ecto, "~> 1.3"},
{:geo_postgis, "~> 3.7"},
{:logger_file_backend, "~> 0.0.13", only: :dev},
{:logger_backends, "~> 1.0", only: :dev},
# Force hackney 4.x to fix CVEs (GHSA-gp9c-pm5m-5cxr, GHSA-j9wq-vxxc-94wf, etc.)
{:hackney, "~> 4.6", override: true},
{:httpoison, "~> 3.0", override: true},
# Force mint 1.9.3+ to fix CVE-2026-59249 (HTTP/1 chunk-size parser)
{:mint, "~> 1.9", override: true}
]
end
# Dialyzer configuration for static analysis
defp dialyzer do
[
plt_file: {:no_warn, "priv/plts/dialyzer.plt"},
plt_add_deps: :apps_direct,
plt_add_apps: [
:mix,
:ex_unit,
:ecto,
:plug,
:plug_crypto,
:phoenix,
:phoenix_pubsub,
:phoenix_live_view,
:phoenix_template,
:oban,
:decimal,
:redix,
:telemetry,
:ssl,
:public_key,
:geo,
:geo_postgis,
:db_connection,
:tools
],
flags: [:unmatched_returns, :error_handling, :underspecs, :unknown],
ignore_warnings: ".dialyzer_ignore.exs"
]
end
# Aliases are shortcuts or tasks specific to the current project.
# For example, to install project dependencies and perform other setup tasks, run:
#
# $ mix setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.load --quiet --skip-if-loaded", "ecto.migrate --quiet", "test"],
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
"assets.build": ["compile", "tailwind towerops", "esbuild towerops"],
"assets.deploy": [
"tailwind towerops --minify",
"esbuild towerops --minify",
"phx.digest"
],
precommit: [
"compile",
"deps.unlock --unused",
"format",
"test"
]
]
end
end