Packages

A Plug-based image optimization server with a declarative path API.

Current section

Files

Jump to
image_pipe mix.exs
Raw

mix.exs

defmodule ImagePipe.MixProject do
  use Mix.Project

  @version "0.1.0"
  @source_url "https://github.com/hlindset/image_pipe"
  @guide_groups [
    "Getting started": [
      "docs/index.md",
      "docs/installation.md",
      "../image_pipe_server/docs/server-getting-started.md",
      "docs/phoenix-getting-started.md",
      "docs/processing-in-elixir.md",
      "docs/elixir-api.md",
      "docs/building-server-urls.md"
    ],
    "Requesting images": [
      "docs/requesting-images.md",
      "docs/processing.md",
      "docs/processing/resize.md",
      "docs/processing/crop.md",
      "docs/processing/effects.md",
      "docs/processing/watermark.md",
      "docs/processing/output.md",
      "docs/processing/request.md"
    ],
    "Running ImagePipe": [
      "../image_pipe_server/docs/server-deployment.md",
      "docs/deployment.md",
      "docs/sources.md",
      "docs/serving-local-files.md",
      "docs/serving-from-http.md",
      "docs/serving-from-s3.md",
      "docs/caching-processed-images.md",
      "docs/serving-through-a-cdn.md",
      "docs/signing-urls.md",
      "docs/defining-presets.md",
      "docs/enabling-detection.md",
      "docs/processing-controls.md",
      "docs/choosing-an-autoquality-target.md",
      "docs/telemetry.md",
      "docs/cookbook/opentelemetry-jaeger.md",
      "docs/combined-usage.md",
      "docs/fetching-from-the-server.md"
    ],
    Concepts: [
      "docs/processing-order.md",
      "docs/caching-and-freshness.md",
      "docs/streaming-failures.md",
      "docs/urls.md",
      "docs/presets.md",
      "docs/content-aware-gravity.md",
      "docs/source-network-policy.md",
      "docs/tracing.md"
    ],
    Extending: [
      "docs/custom-sources.md",
      "docs/custom-detectors.md",
      "docs/storing-presets-in-a-database.md"
    ],
    Reference: [
      "docs/configuration.md",
      "../image_pipe_server/docs/server-configuration.md",
      "docs/shared-url-settings.md",
      "docs/cache.md",
      "docs/cdn-http-cache.md",
      "docs/errors.md",
      "docs/telemetry-events.md",
      "docs/debug_headers.md"
    ],
    Project: ["README.md", "CHANGELOG.md", "LICENSE.md"]
  ]
  @guide_paths Enum.flat_map(@guide_groups, fn {_group, paths} -> paths end)
  # Sidebar labels for pages whose H1 is too long for the sidebar.
  @guide_titles %{
    "docs/serving-from-http.md" => "Serving from an HTTP origin",
    "docs/enabling-detection.md" => "Enabling detection",
    "docs/choosing-an-autoquality-target.md" => "Choosing a quality target"
  }
  @internal_doc_references [
    "ImagePipe.Error.tag/1",
    "ImagePipe.Plug.Runner",
    "ImagePipe.Response.Sender",
    "ImagePipe.Security.verify/3",
    "ImagePipe.Source.S3.Credentials.fetch/3",
    "ImagePipe.Source.S3.Credentials.validate/1",
    "ImagePipe.Source.S3.RefreshCache",
    "ImagePipe.Telemetry.Trace.Capture"
  ]
  # ExDoc resolves remote typespecs without consulting skip_code_autolink_to.
  # These exact specs intentionally mention hidden runtime value types.
  @internal_typespec_references [
    "t:ImagePipe.Result.t/0"
  ]

  def project do
    [
      app: :image_pipe,
      version: @version,
      description: description(),
      package: package(),
      elixir: "~> 1.18",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: extra_compilers(Mix.env()) ++ Mix.compilers(),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps(),
      docs: [
        main: "overview",
        source_ref: "v#{@version}",
        source_url: @source_url,
        source_url_pattern: "#{@source_url}/blob/v#{@version}/image_pipe/%{path}#L%{line}",
        skip_code_autolink_to: @internal_doc_references,
        skip_undefined_reference_warnings_on: @internal_typespec_references,
        assets: %{"docs/assets" => "docs/assets"},
        extras:
          Enum.map(@guide_paths, fn
            "docs/index.md" -> {"docs/index.md", filename: "overview"}
            path when is_map_key(@guide_titles, path) -> {path, title: @guide_titles[path]}
            path -> path
          end),
        groups_for_extras: @guide_groups,
        groups_for_modules: [
          "Application API": [
            ImagePipe,
            ImagePipe.Config,
            ImagePipe.Result,
            ImagePipe.Plug,
            ImagePipe.Plug.StreamAbortedError,
            ImagePipe.ProcessingPool
          ],
          Sources: [
            ImagePipe.Source,
            ImagePipe.Source.File,
            ImagePipe.Source.HTTP,
            ImagePipe.Source.S3
          ],
          "Source adapter types": [
            ImagePipe.Source.Resolved,
            ImagePipe.Source.Response,
            ImagePipe.Source.Origin,
            ImagePipe.Source.CacheSettings,
            ImagePipe.Source.CachePolicy,
            ImagePipe.Source.CacheSemantics
          ],
          "S3 credentials": [~r/ImagePipe\.Source\.S3\..*/],
          Cache: [ImagePipe.Cache.FileSystem],
          "Content detection": [
            ImagePipe.Transform.Detector,
            ~r/ImagePipe\.Transform\.Detector\..*/
          ],
          Telemetry: [ImagePipe.Telemetry, ~r/ImagePipe\.Telemetry\..*/]
        ]
      ],
      test_coverage: [tool: ExCoveralls],
      dialyzer: [
        plt_core_path: "priv/plts",
        plt_local_path: "priv/plts"
      ]
    ]
  end

  def application do
    [
      mod: {ImagePipe.Application, []},
      extra_applications: [:logger]
    ]
  end

  def ex_dna_options do
    [excluded_macros: [:alias]]
  end

  def cli do
    [
      preferred_envs: [
        coveralls: :test,
        "coveralls.html": :test,
        "autoquality.bench": :test,
        "autoquality.corpus": :test,
        "autoquality.corpus.capture": :test,
        "fixtures.gen_sources": :test,
        "worktrees.clean": :test
      ]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(:dev), do: ["lib"]
  defp elixirc_paths(_env), do: ["lib"]

  defp extra_compilers(:prod), do: []
  defp extra_compilers(_env), do: [:boundary]

  defp description do
    "A Plug-based image optimization server with a declarative path API."
  end

  defp package do
    [
      files:
        Enum.reject(@guide_paths, &String.starts_with?(&1, "../")) ++
          [
            "lib",
            "priv/icc",
            "docs/assets/demo-fiddle-desktop.png",
            "mix.exs"
          ],
      licenses: ["Apache-2.0"],
      links: %{
        "GitHub" => @source_url,
        "Changelog" => "#{@source_url}/blob/main/image_pipe/CHANGELOG.md"
      },
      maintainers: ["Håvard Lindset"]
    ]
  end

  defp deps do
    base = [
      image_pipe_url_dep(),
      {:plug, "~> 1.18"},
      {:telemetry, "~> 1.0"},
      # Opt-in OpenTelemetry export. Compile against the lightweight API only
      # (optional: true, NO `only:` — the optional edge orders a host-provided
      # opentelemetry_api before image_pipe so the compile guard activates). The
      # SDK is the host's at runtime; we pull it only for our own tests.
      {:opentelemetry_api, "~> 1.5", optional: true},
      {:opentelemetry, "~> 1.7", only: :test},
      {:nimble_options, "~> 1.1"},
      {:image, "~> 0.72"},
      {:ssimulacra2, "~> 0.1.0"},
      {:vix, "~> 0.42"},
      {:req, "~> 0.8.0-rc.0"},
      {:stream_data, "~> 1.0", only: [:test, :dev]},
      {:boundary, "~> 0.10", runtime: false},
      {:excoveralls, ">= 0.0.0", only: [:test], runtime: false},
      {:ex_doc, "~> 0.35", only: :dev, runtime: false},
      {:makeup_syntect, "~> 0.1.4", only: :dev, runtime: false},
      {:makeup_eex, "~> 2.0", only: :dev, runtime: false},
      # makeup_syntect (docs) pins rustler_precompiled ~> 0.8.2, ssimulacra2 needs
      # 0.9. The override only matters where makeup_syntect is present, but its
      # `only:` applies to ssimulacra2's copy too, so it must cover those envs.
      {:rustler_precompiled, "~> 0.9", only: [:dev, :test], override: true},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:ex_slop, "~> 0.4", only: [:dev, :test], runtime: false},
      {:ex_dna, "~> 1.5", only: [:dev, :test], runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:talan, "~> 1.0"},
      {:bandit, "~> 1.5", only: [:dev, :test]}
    ]

    # Real face detection needs `image_vision` AND its optional ONNX backend
    # `ortex` (a Rust/ONNX runtime): `image_vision`'s `Image.FaceDetection` is
    # compiled only when Ortex is configured (`if ImageVision.ortex_configured?()`),
    # and the YuNet model (~340 KB) downloads on first use.
    #
    # The fiddle app owns real detection in its own deps. Here, the library pulls
    # these only into the opt-in `:test` lane (`IMAGE_VISION=1`) for its own
    # detector test (`IMAGE_VISION=1 mix test --only image_vision`).
    ml_test_deps =
      if System.get_env("IMAGE_VISION") in ["1", "true"] do
        [
          {:image_vision, "~> 0.4", only: :test},
          {:ortex, "~> 0.1", only: :test}
        ]
      else
        []
      end

    base ++ ml_test_deps
  end

  # Development and CI use the sibling project. Set IMAGE_PIPE_PUBLISH=1 when
  # publishing: Hex packages cannot carry path deps, and the two packages are
  # released in lockstep.
  defp image_pipe_url_dep do
    if System.get_env("IMAGE_PIPE_PUBLISH") in ["1", "true"] do
      {:image_pipe_url, "== #{@version}"}
    else
      {:image_pipe_url, path: "../image_pipe_url"}
    end
  end

  defp aliases do
    [
      "image_pipe.ex_dna": &run_ex_dna/1,
      setup: ["deps.get"],
      test: ["test"]
    ]
  end

  defp run_ex_dna(args) do
    options = ex_dna_options()

    excluded_macros =
      Enum.flat_map(options[:excluded_macros], &["--exclude-macro", Atom.to_string(&1)])

    Mix.Task.run("ex_dna", excluded_macros ++ args)
  end
end