defmodule Mix.Tasks.Npm.Shrinkwrap do @shortdoc "Create npm-shrinkwrap.json from lockfile" @moduledoc """ Create an `npm-shrinkwrap.json` from the current `package-lock.json`. mix npm.shrinkwrap The shrinkwrap file locks exact versions for publishing. Unlike `package-lock.json` (which is project-only), `npm-shrinkwrap.json` is included when the package is published to the registry. """ use Mix.Task @impl true def run([]) do Application.ensure_all_started(:req) case NPM.Lockfile.read() do {:ok, lockfile} when lockfile == %{} -> Mix.shell().error("No package-lock.json found. Run `mix npm.install` first.") {:ok, lockfile} -> shrinkwrap = build_shrinkwrap(lockfile) content = NPM.JSON.encode_pretty(shrinkwrap) File.write!("npm-shrinkwrap.json", content) Mix.shell().info("Created npm-shrinkwrap.json (#{map_size(lockfile)} packages)") {:error, reason} -> Mix.shell().error("Failed to read lockfile: #{inspect(reason)}") end end def run(_) do Mix.shell().error("Usage: mix npm.shrinkwrap") end defp build_shrinkwrap(lockfile) do deps = Map.new(lockfile, fn {name, entry} -> {name, %{ "version" => entry.version, "integrity" => entry.integrity, "resolved" => entry.tarball }} end) %{ "lockfileVersion" => 1, "dependencies" => deps } end end