defmodule NPM.Import do
@moduledoc """
Imports lockfiles from other package managers.
Converts yarn.lock, pnpm-lock.yaml metadata into npm_ex format
for migration from other tools.
"""
@doc """
Detects which package manager lockfiles exist in the project.
"""
@spec detect(String.t()) :: [atom()]
def detect(project_dir \\ ".") do
checks = [
{:npm, "package-lock.json"},
{:yarn, "yarn.lock"},
{:pnpm, "pnpm-lock.yaml"},
{:bun, "bun.lockb"},
{:npm_ex, "npm.lock"}
]
Enum.filter(checks, fn {_manager, file} ->
File.exists?(Path.join(project_dir, file))
end)
|> Enum.map(fn {manager, _file} -> manager end)
end
@doc """
Reads dependencies from a package-lock.json file.
"""
@spec from_package_lock(String.t()) :: {:ok, map()} | {:error, term()}
def from_package_lock(path) do
case File.read(path) do
{:ok, content} ->
data = NPM.JSON.decode!(content)
packages = extract_npm_lock_packages(data)
{:ok, packages}
error ->
error
end
end
@doc """
Checks if migration is needed (other lockfile exists but no npm.lock).
"""
@spec migration_needed?(String.t()) :: boolean()
def migration_needed?(project_dir \\ ".") do
managers = detect(project_dir)
other_exists = Enum.any?(managers, &(&1 != :npm_ex))
no_npm_ex = :npm_ex not in managers
other_exists and no_npm_ex
end
@doc """
Returns the primary package manager detected.
"""
@spec primary_manager(String.t()) :: atom() | nil
def primary_manager(project_dir \\ ".") do
detect(project_dir) |> List.first()
end
defp extract_npm_lock_packages(%{"packages" => packages}) when is_map(packages) do
packages
|> Enum.reject(fn {key, _} -> key == "" end)
|> Enum.flat_map(fn {path, info} ->
name = path |> String.replace("node_modules/", "")
if String.contains?(name, "node_modules/") do
[]
else
[
{name,
%{
version: info["version"] || "",
integrity: info["integrity"] || "",
resolved: info["resolved"] || "",
dependencies: info["dependencies"] || %{}
}}
]
end
end)
|> Map.new()
end
defp extract_npm_lock_packages(%{"dependencies" => deps}) when is_map(deps) do
Map.new(deps, fn {name, info} ->
{name,
%{
version: info["version"] || "",
integrity: info["integrity"] || "",
resolved: info["resolved"] || "",
dependencies: info["requires"] || %{}
}}
end)
end
defp extract_npm_lock_packages(_), do: %{}
end