Current section

Files

Jump to
backpex mix.exs
Raw

mix.exs

defmodule Backpex.MixProject do
  use Mix.Project

  @version "0.24.1"

  @source_url "https://github.com/naymspace/backpex"
  @changelog_url "https://github.com/naymspace/backpex/releases"
  @website_url "https://backpex.live"

  # The integration tests in `integration_test` run the LiveResources of a test app against PostgreSQL and in a browser.
  # Their dependencies need Elixir 1.19, so older versions of Elixir only run the unit tests in `test`.
  # `INTEGRATION_TESTS=false` leaves them out on any version, with their dependencies and support code. CI runs them
  # once, on the latest Elixir, and sets it for the other versions.
  @integration_tests Version.match?(System.version(), ">= 1.19.0") and System.get_env("INTEGRATION_TESTS") != "false"

  def project do
    [
      app: :backpex,
      version: @version,
      elixir: "~> 1.16",
      elixirc_paths: elixirc_paths(Mix.env()),
      test_paths: test_paths(),
      compilers: [:phoenix_live_view] ++ Mix.compilers(),
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      aliases: aliases(),
      gettext: gettext(),
      test_coverage: test_coverage(),
      usage_rules: usage_rules(),

      # Hex.pm
      hex: [cooldown: "3d"],
      package: package(),
      description: "Highly customizable administration panel for Phoenix LiveView applications.",

      # Docs
      name: "Backpex",
      source_url: @source_url,
      homepage_url: @website_url,
      docs: docs()
    ]
  end

  defp test_paths do
    if @integration_tests, do: ["test", "integration_test"], else: ["test"]
  end

  defp elixirc_paths(:test) do
    if @integration_tests, do: ["lib", "test/support", "integration_test/support"], else: ["lib", "test/support"]
  end

  defp elixirc_paths(_), do: ["lib"]

  # The threshold is the floor of the merged coverage of the unit and browser tests in CI. Raise it when coverage grows.
  defp test_coverage do
    [
      summary: [threshold: 90],
      ignore_modules: [~r/^Backpex\.(Test\.|ConnCase|DataCase|E2ECase)/]
    ]
  end

  # `mix usage_rules.sync` links the rules of these packages at the end of CODING_STANDARDS.md. It only sees dev
  # dependencies, so CODING_STANDARDS.md links the rules of the test-only `phoenix_test_playwright` by hand.
  defp usage_rules do
    [
      file: "CODING_STANDARDS.md",
      usage_rules: [
        {:phoenix, link: :markdown, except: [:elixir]},
        {:igniter, link: :markdown},
        {:sobelow, link: :markdown}
      ]
    ]
  end

  defp deps do
    [
      # development
      {:ex_doc, "~> 0.40", only: [:dev, :test], runtime: false},
      {:makeup_eex, "~> 2.0", only: [:dev, :test], runtime: false},
      {:makeup_syntect, "~> 0.1.3", only: [:dev, :test], runtime: false},
      {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false},
      {:mix_audit, "~> 2.0", only: [:dev, :test], runtime: false},
      {:sobelow, ">= 0.0.0", only: [:dev, :test]},
      {:lazy_html, ">= 0.0.0", only: :test},
      {:phx_new, "~> 1.8", only: :test, runtime: false},
      {:esbuild, "~> 0.10", only: :dev},
      {:quokka, "~> 2.9", only: [:dev, :test], runtime: false},
      {:usage_rules, "~> 1.3", only: [:dev]},

      # core
      {:nimble_options, "~> 1.1"},
      {:gettext, ">= 0.26.0"},

      # phoenix
      {:phoenix, ">= 1.7.6 and < 1.9.0"},
      {:phoenix_html, "~> 4.1"},
      {:phoenix_html_helpers, "~> 1.0"},
      {:phoenix_live_view, "~> 1.1"},

      # adapters
      {:ecto, "~> 3.6"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_ecto, "~> 4.4"},

      # generators
      {:igniter, "~> 0.8", optional: true},
      {:igniter_js, "~> 0.5", optional: true}
    ] ++ integration_test_deps()
  end

  defp integration_test_deps do
    if @integration_tests do
      [
        {:ecto_sql, "~> 3.10", only: :test},
        {:bandit, "~> 1.14", only: :test},
        {:phoenix_test, "~> 0.13.0", only: :test, runtime: false},
        {:phoenix_test_playwright, "~> 0.19.0", only: :test, runtime: false},
        {:a11y_audit, "~> 0.5.0", only: :test},
        {:ex_machina, "~> 2.8", only: :test},
        {:money, "~> 1.14", only: :test}
      ]
    else
      []
    end
  end

  defp package do
    [
      files: ~w(assets/js package.json lib priv .formatter.exs mix.exs README.md LICENSE.md usage-rules.md),
      maintainers: ["Florian Arens", "Phil-Bastian Berndt", "Simon Hansen"],
      licenses: ["MIT"],
      links: %{
        Changelog: @changelog_url,
        GitHub: @source_url,
        Website: @website_url
      }
    ]
  end

  defp aliases do
    [
      lint: lint(),
      ci: ci(),
      "assets.build": [
        "esbuild module",
        "esbuild main"
      ],
      "assets.watch": "esbuild module --watch",
      "assets.check": ["assets.build", "cmd ./scripts/check_assets.sh"]
    ]
  end

  # Without the integration tests, the lock still holds their dependencies, which `deps.unlock --unused` would remove.
  defp lint do
    unlock_unused = if @integration_tests, do: ["deps.unlock --unused"], else: []

    ["compile --warnings-as-errors"] ++ unlock_unused ++ ["format", "credo", "test"]
  end

  # The checks of the CI job `test` that `lint` leaves out, cheap ones first. `assets.check` needs `esbuild`, which only
  # the `dev` environment has, so it runs in a `dev` subprocess; the first run compiles the dependencies for `dev`.
  defp ci do
    [
      "lint",
      "cmd bun run lint:standard",
      "cmd bun run test:js",
      "gettext.extract --check-up-to-date",
      "sobelow --config",
      "docs --warnings-as-errors",
      "cmd env MIX_ENV=dev mix assets.check"
    ]
  end

  defp docs do
    [
      main: "readme",
      logo: "priv/static/images/logo.svg",
      extras: extras(),
      extra_section: "GUIDES",
      groups_for_extras: groups_for_extras(),
      groups_for_modules: groups_for_modules(),
      groups_for_docs: [
        Components: &(&1[:type] == :component)
      ],
      source_ref: @version,
      source_url: @source_url,
      before_closing_head_tag: fn type ->
        if type == :html do
          """
          <script>
            if (location.hostname === "hexdocs.pm") {
              const script = document.createElement("script");
              script.src = "https://plausible.io/js/script.js";
              script.defer = true;
              script.setAttribute("data-domain", "hexdocs.pm/backpex");
              document.head.appendChild(script);
            }
          </script>
          """
        end
      end,
      skip_code_autolink_to: [
        "Ecto.Query.DynamicExpr",
        "Backpex.Router.cookie_path/1"
      ]
    ]
  end

  defp extras do
    [
      {"README.md", title: "Introduction"},

      # About
      "guides/about/what-is-backpex.md",
      "guides/about/why-we-built-backpex.md",
      "guides/about/contribute-to-backpex.md",

      # Get Started
      "guides/get_started/installation.md",

      # AI Assisted Development
      "guides/ai_development/ai-development.md",

      # Live Resource
      "guides/live_resource/what-is-a-live-resource.md",
      "guides/live_resource/templates.md",
      "guides/live_resource/item-query.md",
      "guides/live_resource/ordering.md",
      "guides/live_resource/pagination.md",
      "guides/live_resource/on_mount-hook.md",
      "guides/live_resource/hooks.md",
      "guides/live_resource/navigation.md",
      "guides/live_resource/panels.md",
      "guides/live_resource/metrics.md",
      "guides/live_resource/fluid-layout.md",
      "guides/live_resource/listen-to-pubsub-events.md",
      "guides/live_resource/additional-classes-for-index-table-rows.md",
      "guides/live_resource/user-preferences.md",
      "guides/live_resource/context-assigns.md",

      # Fields
      "guides/fields/what-is-a-field.md",
      "guides/fields/custom-fields.md",
      "guides/fields/alignment.md",
      "guides/fields/visibility.md",
      "guides/fields/defaults.md",
      "guides/fields/readonly.md",
      "guides/fields/custom-alias.md",
      "guides/fields/placeholder.md",
      "guides/fields/debounce-and-throttle.md",
      "guides/fields/index-edit.md",
      "guides/fields/error-customization.md",
      "guides/fields/computed-fields.md",

      # Filter
      "guides/filter/what-is-a-filter.md",
      "guides/filter/how-to-add-a-filter.md",
      "guides/filter/filter-presets.md",
      "guides/filter/filter-validation.md",
      "guides/filter/custom-filter.md",
      "guides/filter/visibility-and-authorization.md",

      # Actions
      "guides/actions/item-actions.md",
      "guides/actions/resource-actions.md",

      # Authorization
      "guides/authorization/live-resource-authorization.md",
      "guides/authorization/field-authorization.md",

      # Searching
      "guides/searching/basic-search.md",
      "guides/searching/full-text-search.md",

      # Translations
      "guides/translations/translations.md",

      # Upgrade Guides
      "guides/upgrading/v0.24.md",
      "guides/upgrading/v0.23.md",
      "guides/upgrading/v0.22.md",
      "guides/upgrading/v0.21.md",
      "guides/upgrading/v0.20.md",
      "guides/upgrading/v0.19.md",
      "guides/upgrading/v0.18.md",
      "guides/upgrading/v0.17.md",
      "guides/upgrading/v0.16.md",
      "guides/upgrading/v0.15.md",
      "guides/upgrading/v0.14.md",
      "guides/upgrading/v0.13.md",
      "guides/upgrading/v0.12.md",
      "guides/upgrading/v0.11.md",
      "guides/upgrading/v0.10.md",
      "guides/upgrading/v0.9.md",
      "guides/upgrading/v0.8.md",
      "guides/upgrading/v0.7.md",
      "guides/upgrading/v0.6.md",
      "guides/upgrading/v0.5.md",
      "guides/upgrading/v0.3.md",
      "guides/upgrading/v0.2.md"
    ]
  end

  defp groups_for_extras do
    [
      Introduction: ~r/README/,
      About: ~r/guides\/about\/.?/,
      "Get Started": ~r/guides\/get_started\/.?/,
      "AI Assisted Development": ~r/guides\/ai_development\/.?/,
      "Live Resource": ~r/guides\/live_resource\/.?/,
      Fields: ~r/guides\/fields\/.?/,
      Filter: ~r/guides\/filter\/.?/,
      Actions: ~r/guides\/actions\/.?/,
      Authorization: ~r/guides\/authorization\/.?/,
      Searching: ~r/guides\/searching\/.?/,
      Translations: ~r/guides\/translations\/.?/,
      "Upgrade Guides": ~r{guides/upgrading/.*}
    ]
  end

  defp groups_for_modules do
    [
      Adapters: ~r/Backpex\.Adapter.?/,
      Components: ~r/Backpex\.HTML.?/,
      Fields: ~r/Backpex\.Field.?/,
      Actions: ~r/Backpex\.(ItemAction|ResourceAction).?/,
      Filters: ~r/Backpex\.Filter.?/,
      Metrics: ~r/Backpex\.Metric.?/,
      Preferences: ~r/Backpex\.Preferences.*/,
      Testing: ~r/Backpex\.Testing/
    ]
  end

  defp gettext do
    [
      write_reference_comments: false,
      sort_by_msgid: :case_insensitive
    ]
  end
end