defmodule ExcellentMigrations.Runner do @moduledoc """ This module finds migration files in a project and detects potentially dangerous database operations in them. """ alias ExcellentMigrations.{ DangersDetector, FilesFinder } @type danger_type :: :column_added_with_default | :column_removed | :column_renamed | :column_type_changed | :index_not_concurrently | :many_columns_index | :not_null_added | :operation_delete | :operation_insert | :operation_update | :raw_sql_executed | :table_dropped | :table_renamed @type danger :: %{ type: danger_type, path: String.t(), line: integer } @doc """ Detects potentially dangerous database operations in database migration files. ## Options * `:migrations_paths` - optional list of file paths to be checked. ## Scope of analysis * If `migrations_paths` are specified, the analysis will be narrowed down to these files only. * If not and application env `:excellent_migrations, :start_after` is set, only migrations with timestamp older than the provided one will be chosen. * If none of the above, all migration files in a project will be analyzed. """ @spec check_migrations(migrations_paths: [String.t()]) :: :safe | {:dangerous, [danger]} def check_migrations(opts \\ []) do opts |> get_migrations_paths() |> Task.async_stream(fn path -> source_code = File.read!(path) ast = Code.string_to_quoted!(source_code) dangers = DangersDetector.detect_dangers(ast, source_code) build_result(dangers, path) end) |> Stream.flat_map(fn {:ok, items} -> items end) |> Enum.to_list() |> close() end defp get_migrations_paths(opts) do opts |> Keyword.get_lazy(:migrations_paths, &FilesFinder.get_migrations_paths/0) |> Enum.sort() end defp build_result(dangers, path) do Enum.map(dangers, fn {type, line} -> %{ type: type, path: path, line: line } end) end defp close([]), do: :safe defp close(dangers), do: {:dangerous, dangers} end