defmodule NPM.Gitignore do @moduledoc """ Manages .gitignore entries for npm projects. """ @essential_patterns ["node_modules/", ".npm/"] @recommended_patterns ["node_modules/", ".npm/", "*.tgz", ".env", ".env.local"] @doc """ Returns essential gitignore patterns for npm projects. """ @spec essential :: [String.t()] def essential, do: @essential_patterns @doc """ Returns recommended gitignore patterns. """ @spec recommended :: [String.t()] def recommended, do: @recommended_patterns @doc """ Checks if a .gitignore file covers node_modules. """ @spec covers_node_modules?(String.t()) :: boolean() def covers_node_modules?(content) do content |> String.split("\n") |> Enum.any?(fn line -> trimmed = String.trim(line) trimmed == "node_modules" or trimmed == "node_modules/" end) end @doc """ Returns missing essential patterns from a .gitignore file. """ @spec missing(String.t()) :: [String.t()] def missing(content) do lines = content |> String.split("\n") |> Enum.map(&String.trim/1) |> MapSet.new() Enum.reject(@essential_patterns, fn pattern -> bare = String.trim_trailing(pattern, "/") MapSet.member?(lines, pattern) or MapSet.member?(lines, bare) end) end @doc """ Generates a .gitignore content for an npm project. """ @spec generate(keyword()) :: String.t() def generate(opts \\ []) do patterns = if Keyword.get(opts, :recommended, true), do: @recommended_patterns, else: @essential_patterns extra = Keyword.get(opts, :extra, []) (patterns ++ extra) |> Enum.join("\n") end @doc """ Checks a .gitignore file on disk. """ @spec check(String.t()) :: {:ok, [String.t()]} | {:error, :not_found} def check(project_dir) do path = Path.join(project_dir, ".gitignore") case File.read(path) do {:ok, content} -> {:ok, missing(content)} _ -> {:error, :not_found} end end end