defmodule NPM.Init do @moduledoc """ Generates package.json files with sensible defaults. Implements the `npm init` functionality — creates a new package.json with project metadata derived from the current directory or Mix project. """ @doc """ Generates a default package.json map from the given options. """ @spec generate(keyword()) :: map() def generate(opts \\ []) do %{ "name" => Keyword.get(opts, :name, default_name()), "version" => Keyword.get(opts, :version, "1.0.0"), "description" => Keyword.get(opts, :description, ""), "main" => Keyword.get(opts, :main, "index.js"), "scripts" => Keyword.get(opts, :scripts, default_scripts()), "keywords" => Keyword.get(opts, :keywords, []), "author" => Keyword.get(opts, :author, ""), "license" => Keyword.get(opts, :license, "ISC"), "dependencies" => %{}, "devDependencies" => %{} } end @doc """ Writes a package.json to disk. """ @spec write(String.t(), keyword()) :: :ok | {:error, term()} def write(dir \\ ".", opts \\ []) do path = Path.join(dir, "package.json") if File.exists?(path) do {:error, :already_exists} else data = generate(opts) File.write(path, NPM.JSON.encode_pretty(data)) end end @doc """ Checks if a package.json already exists in the directory. """ @spec exists?(String.t()) :: boolean() def exists?(dir \\ "."), do: dir |> Path.join("package.json") |> File.exists?() @doc """ Infers project name from the current directory. """ @spec default_name :: String.t() def default_name do File.cwd!() |> Path.basename() |> String.downcase() |> String.replace(~r/[^a-z0-9\-_]/, "-") end @doc """ Returns default scripts for a new package. """ @spec default_scripts :: map() def default_scripts do %{"test" => "echo \"Error: no test specified\" && exit 1"} end @doc """ Generates a minimal package.json (name and version only). """ @spec generate_minimal(String.t(), String.t()) :: map() def generate_minimal(name, version \\ "1.0.0") do %{"name" => name, "version" => version} end @doc """ Detects if this is an Elixir/Mix project and adjusts defaults. """ @spec from_mix_project(keyword()) :: map() def from_mix_project(mix_config \\ []) do name = Keyword.get(mix_config, :app, :unnamed) |> Atom.to_string() version = Keyword.get(mix_config, :version, "0.1.0") description = Keyword.get(mix_config, :description, "") generate( name: name, version: version, description: if(is_binary(description), do: description, else: "") ) end end