ActorSimulation.TemplateVersions.Upgrade (GenServerVirtualTime v0.6.1)

Copy Markdown View Source

Resolves the latest upstream release of everything in ActorSimulation.TemplateVersions.sources/0 and rewrites the version table.

This module is pure: it never talks to the network itself. plan/1 takes a fetch function and a table, and returns what would change. Mix.Tasks.Templates.Upgrade supplies the HTTP client; the tests supply a stub.

sources/0 describes where each version comes from, so this module only has to know how to read those upstreams:

sourceupstreamread
{:conan_center, package, transform}ConanCenter recipe config.ymlhighest version
{:crates_io, crate, transform}crates.io/api/v1/crates/<crate>max_stable_version
{:go_module, module, transform}proxy.golang.org/<module>/@latestVersion
{:maven_central, coord, transform}repo1.maven.org/.../maven-metadata.xmlhighest stable release
{:github_release, repo, transform}releases/latesttag_name
{:github_tags, repo, transform}tagshighest semver tag
{:manual, reason, :none}—nothing, reported as manual

Summary

Types

One key's outcome: its current pin, the latest we could find upstream (if any), the source it came from, and what that means.

Functions

The table the given plan would write, as a map.

True when the plan would change at least one pin.

Path of the source file that holds the version table, relative to the project root.

Narrows a single upstream version.

Asks upstream for every version and reports what would change.

Renders table as the @versions map literal, in sources/0 order, with the upstream on the line above each pin.

Rewrites the @versions table inside source with table.

The {url, parser} this source is read from, or :manual.

Extracts a version from a git tag.

Types

entry()

@type entry() :: %{
  key: atom(),
  current: String.t(),
  latest: String.t() | nil,
  status: :update | :current | :manual | :unresolved,
  source: term(),
  note: String.t() | nil
}

One key's outcome: its current pin, the latest we could find upstream (if any), the source it came from, and what that means.

Functions

apply_plan(plan)

@spec apply_plan([entry()]) :: %{required(atom()) => String.t()}

The table the given plan would write, as a map.

drift?(plan)

@spec drift?([entry()]) :: boolean()

True when the plan would change at least one pin.

module_path()

@spec module_path() :: String.t()

Path of the source file that holds the version table, relative to the project root.

narrow(value, transform)

@spec narrow(String.t() | nil, atom()) :: String.t() | nil

Narrows a single upstream version.

  • :raw — as published ("v0.0.0-20220903101357-530938a4b13d"), for values that are not releases at all
  • :tag — the tag as published ("v7.0.1")
  • :version — the version in the tag ("7.0.1")
  • :major — "v7.0.1" becomes "7", for pins that float within a major
  • :major_tag — "v7.0.1" becomes "v7", how GitHub Actions are pinned

plan(fetch)

@spec plan((String.t() -> {:ok, binary()} | {:error, term()})) :: [entry()]

Asks upstream for every version and reports what would change.

fetch is called as fetch.(url) and must return {:ok, body} or {:error, reason}.

plan(fetch, current)

@spec plan((String.t() -> {:ok, binary()} | {:error, term()}), %{
  required(atom()) => String.t()
}) :: [
  entry()
]

render_table(table)

@spec render_table(%{required(atom()) => String.t()}) :: String.t()

Renders table as the @versions map literal, in sources/0 order, with the upstream on the line above each pin.

The layout is exactly what mix format produces, so an upgrade is a one-line-per-changed-pin diff and never a reformat.

rewrite(source, table)

@spec rewrite(String.t(), %{required(atom()) => String.t()}) :: String.t()

Rewrites the @versions table inside source with table.

Everything outside the versions:begin/versions:end markers is left byte for byte alone, so the module's documentation and functions are never touched.

upstream(arg)

@spec upstream(term()) ::
  {:ok, String.t(), (binary(), String.t() -> String.t() | nil)} | :manual

The {url, parser} this source is read from, or :manual.

The parser is a fun(body, current_pin) that returns the newest version the upstream offers, or nil when the body holds none. It receives the current pin because :latest_within_major uses it as a ceiling: a major upgrade of a generated project's dependencies is a decision, not something a script should make overnight.

version_from_tag(tag)

@spec version_from_tag(String.t() | nil) :: String.t() | nil

Extracts a version from a git tag.

Tags come in flavours like v7.0.1, omnetpp-6.4.0 or 1.29.0; the first semver-looking run of digits is the version.