defmodule NPM.Package.Manifest do
alias NPM.Resolution.Exports
@moduledoc """
Generate a complete package manifest from `package.json`.
Aggregates data from the package.json file into a structured
manifest used by publishing, auditing, and analysis tools.
"""
@type t :: %{
name: String.t() | nil,
version: String.t() | nil,
license: String.t() | nil,
module_type: :esm | :cjs,
dependencies: %{String.t() => String.t()},
dev_dependencies: %{String.t() => String.t()},
optional_dependencies: %{String.t() => String.t()},
scripts: %{String.t() => String.t()},
engines: %{String.t() => String.t()},
exports: map() | nil,
files: [String.t()] | nil
}
@doc """
Build a manifest from a `package.json` file.
"""
@spec from_file(String.t()) :: {:ok, t()} | {:error, term()}
def from_file(path \\ "package.json") do
case File.read(path) do
{:ok, content} -> {:ok, from_json(content)}
{:error, reason} -> {:error, reason}
end
end
@doc """
Build a manifest from a JSON string.
"""
@spec from_json(String.t()) :: t()
def from_json(json) do
data = NPM.JSON.decode!(json)
%{
name: Map.get(data, "name"),
version: Map.get(data, "version"),
license: Map.get(data, "license"),
module_type: Exports.module_type(data),
dependencies: Map.get(data, "dependencies", %{}),
dev_dependencies: Map.get(data, "devDependencies", %{}),
optional_dependencies: Map.get(data, "optionalDependencies", %{}),
scripts: Map.get(data, "scripts", %{}),
engines: Map.get(data, "engines", %{}),
exports: Exports.parse(data),
files: Map.get(data, "files")
}
end
@doc """
Count total dependency count across all types.
"""
@spec dep_count(t()) :: non_neg_integer()
def dep_count(manifest) do
map_size(manifest.dependencies) +
map_size(manifest.dev_dependencies) +
map_size(manifest.optional_dependencies)
end
@doc """
Check if the manifest has any scripts defined.
"""
@spec has_scripts?(t()) :: boolean()
def has_scripts?(manifest), do: map_size(manifest.scripts) > 0
@doc """
Get all dependency names across all types.
"""
@spec all_dep_names(t()) :: [String.t()]
def all_dep_names(manifest) do
[manifest.dependencies, manifest.dev_dependencies, manifest.optional_dependencies]
|> Enum.flat_map(&Map.keys/1)
|> Enum.uniq()
|> Enum.sort()
end
end