Skip to main content

lib/mix/tasks/rulestead.code_refs.ex

defmodule Mix.Tasks.Rulestead.CodeRefs do
  @moduledoc false
  # Scans application code for Rulestead flag references and pushes them to the Rulestead API.

  # ## Examples

  #     mix rulestead.code_refs --token secret_token
  #     mix rulestead.code_refs --dir lib --url http://example.com/api/webhooks/rulestead/code_refs

  use Mix.Task

  alias Rulestead.CodeRefs.Scanner

  @shortdoc "Scans application code for Rulestead flag references and pushes them to the Rulestead API"

  @switches [dir: :string, url: :string, token: :string]

  @impl Mix.Task
  def run(args) do
    # Ensure dependencies are available (like Jason and Req/HTTPoison/inets)
    Application.ensure_all_started(:inets)
    Application.ensure_all_started(:ssl)

    {opts, _argv, _invalid} = OptionParser.parse(args, strict: @switches)

    dir = Keyword.get(opts, :dir, "lib")

    url =
      Keyword.get(opts, :url) || System.get_env("RULESTEAD_API_URL") ||
        "http://localhost:4000/api/webhooks/rulestead/code_refs"

    token = Keyword.get(opts, :token) || System.get_env("RULESTEAD_CI_TOKEN")

    if is_nil(token) do
      Mix.raise(
        "A CI token is required. Pass --token <token> or set RULESTEAD_CI_TOKEN environment variable."
      )
    end

    shell = Mix.shell()
    shell.info("Scanning #{dir} for Rulestead flag references...")

    references = Scanner.scan_dir(dir)

    shell.info("Found #{length(references)} references. Pushing to #{url}...")

    payload = Jason.encode!(%{references: references})

    headers = [
      {~c"content-type", ~c"application/json"},
      {~c"authorization", String.to_charlist("Bearer #{token}")}
    ]

    case :httpc.request(
           :post,
           {String.to_charlist(url), headers, ~c"application/json", payload},
           [],
           []
         ) do
      {:ok, {{_, status_code, _}, _, _response_body}} when status_code in 200..299 ->
        shell.info("Successfully pushed references to Rulestead!")

      {:ok, {{_, status_code, _}, _, response_body}} ->
        Mix.raise(
          "Failed to push references. Server returned status #{status_code}: #{response_body}"
        )

      {:error, reason} ->
        Mix.raise("Failed to connect to Rulestead API: #{inspect(reason)}")
    end
  end
end