Current section
Files
Jump to
Current section
Files
mix.exs
defmodule Exgit.MixProject do
use Mix.Project
@version "0.1.0"
@source_url "https://github.com/ivarvong/exgit"
def project do
[
app: :exgit,
version: @version,
elixir: "~> 1.17",
# The library doesn't use any post-1.17 features; pinning the
# lower bound at 1.17 keeps it installable for older consumers.
# CI matrix tests 1.20/OTP 29 (primary, all quality gates),
# 1.19/OTP 28, and 1.17/OTP 27 (minimum-supported).
start_permanent: Mix.env() == :prod,
deps: deps(),
elixirc_paths: elixirc_paths(Mix.env()),
dialyzer: dialyzer(),
# Don't consolidate protocols in test so test-only implementations
# (e.g. fake transports) load without a clean rebuild.
consolidate_protocols: Mix.env() != :test,
description: description(),
package: package(),
source_url: @source_url,
docs: docs()
]
end
# Mix CLI configuration. Lives in its own callback (per Mix 1.14+)
# rather than in `def project` so that `:preferred_envs` doesn't
# trigger a deprecation warning under Mix 1.19.
def cli do
[
preferred_envs: [
dialyzer: :dev,
"bench.run": :test
]
]
end
defp description do
"""
Pure-Elixir git: clone, fetch, push over smart HTTP v2 with lazy
partial-clone support and a path-oriented FS API for agents.
No `git` binary, no libgit2, no shelling out.
"""
end
defp package do
[
licenses: ["MIT"],
links: %{
"GitHub" => @source_url
},
files:
~w(lib docs/PERFORMANCE.md .formatter.exs mix.exs README.md LICENSE CHANGELOG.md SECURITY.md)
]
end
defp docs do
[
main: "readme",
extras: ["README.md", "CHANGELOG.md", "SECURITY.md", "docs/PERFORMANCE.md"],
source_url: @source_url,
source_ref: "v#{@version}"
]
end
def application do
# Do NOT add :vfs (or any other optional integration) here. vfs is a
# compile-time-optional integration: we use its protocol module via
# `defimpl VFS.Mountable, for: Exgit.Workspace` when it is present,
# but exgit must NOT declare :vfs as a runtime application — vfs
# itself depends on exgit (in dev/test), and listing it here would
# deadlock `:application_controller` at boot for any consumer that
# bundles both libs. The corresponding `runtime: false` flag on the
# :vfs entry in `deps/0` is what keeps Mix from auto-injecting it.
[
extra_applications: [:logger, :crypto],
mod: {Exgit.Application, []}
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
{:req, "~> 0.5"},
# Telemetry: BEAM-wide standard for instrumentation. Emits events
# consumers can attach to. Zero cost when no handler is attached.
{:telemetry, "~> 1.0"},
# Optional vfs integration: `Exgit.Workspace` ships a
# `VFS.Mountable` defimpl when `:vfs` is loaded.
#
# `optional: true` means downstream consumers don't have to install
# :vfs to use exgit; if they DO add :vfs, Mix orders our build after
# vfs's so the defimpl compiles in and protocol consolidation picks
# it up.
#
# `runtime: false` is load-bearing. Without it, Mix auto-includes
# :vfs in exgit's generated `applications` list (optional deps are
# still added as application edges; `optional_applications` only
# tells `:application_controller` it's OK if the app is absent — it
# does NOT break the dependency edge when present). vfs in turn
# depends on exgit (for its own integration tests), so any build
# that bundles both — e.g. an agent app pulling in both libs —
# creates a cycle in `:application_controller` that deadlocks at
# boot. We use vfs's protocol module at COMPILE time (to define
# `defimpl VFS.Mountable, for: Exgit.Workspace`); there is no vfs
# supervision tree we depend on at runtime, so `runtime: false` is
# not a workaround — it's an accurate description of the
# relationship. Same shape as Phoenix and its optional templating
# adapters.
#
# We deliberately do NOT scope this to :dev/:test only — that would
# remove vfs from our dep graph in :prod, breaking compile-ordering
# guarantees in downstream consumer builds. Requires Elixir ~> 1.18;
# the 1.17 CI tier skips the integration via `Code.ensure_loaded?`.
{:vfs, "~> 0.1.0", optional: true, runtime: false},
{:stream_data, "~> 1.0", only: [:test, :dev]},
# Test-only: localhost HTTP server for stubbing the Cloudflare
# Artifacts REST API in `test/exgit/cloudflare_artifacts_test.exs`.
{:bypass, "~> 2.1", only: :test},
# Optional dev-only OpenTelemetry bridge: auto-converts :telemetry
# events into OTel spans. Only loaded in dev/test; production users
# can wire their own handlers.
{:opentelemetry, "~> 1.5", only: [:dev, :test]},
{:opentelemetry_api, "~> 1.4", only: [:dev, :test]},
{:opentelemetry_telemetry, "~> 1.1", only: [:dev, :test]},
{:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]},
# Dev-only static analysis. Not runtime deps.
{:dialyxir, "~> 1.4", only: [:dev], runtime: false},
{:credo, "~> 1.7", only: [:dev], runtime: false},
# Doc generation for HexDocs (`mix docs` / `mix hex.publish`).
{:ex_doc, "~> 0.34", only: [:dev], runtime: false}
]
end
defp dialyzer do
[
plt_core_path: "priv/plts/core",
plt_local_path: "priv/plts/project",
# `:vfs` is `runtime: false` in deps/0 so it stays out of our
# generated `applications` list (see comment on the :vfs dep for
# why). Side effect: Dialyxir's default PLT closure follows
# `applications`, so without this hint vfs's beams are absent
# from the PLT and `lib/exgit/workspace/vfs.ex` lights up with
# spurious `unknown_function` warnings for `VFS.Error.new/2`,
# `VFS.Path.normalize/1`, etc. Adding it here adds the beams to
# the PLT only — it does not reintroduce a runtime application
# edge.
plt_add_apps: [:vfs],
flags: [:unmatched_returns, :error_handling, :extra_return, :missing_return]
]
end
end