diff --git a/CHANGELOG.md b/CHANGELOG.md index 753bbbd..5f8ba1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries for unreleased work are not written here directly. Each issue drops a -fragment in [`changelog.d/`](changelog.d/README.md); the fragments are assembled +fragment in [`changelog.d/`](https://github.com/riddler/statifier-ui/blob/v0.10.1/changelog.d/README.md); the fragments are assembled into a version section at release. See that README for the format and for when a change warrants an entry at all. diff --git a/README.md b/README.md index 700baf6..baa99a2 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ The vocabulary that stream can carry is closed and published: string the format defines. It is the handle to hold and diff across upgrades - a consumer still ignores types it does not know, but a diff of that list is how it notices a new one. See the note on pinning the vocabulary in -[ADR-0005](docs/adr/0005-language-neutral-trace-wire-format.md). +[ADR-0005](https://github.com/riddler/statifier-ui/blob/v0.10.1/docs/adr/0005-language-neutral-trace-wire-format.md). ## Checking expressions diff --git a/mix.exs b/mix.exs index c781d9f..baf4ff5 100644 --- a/mix.exs +++ b/mix.exs @@ -36,9 +36,9 @@ defmodule StatifierUI.MixProject do defp elixirc_paths(_env), do: ["lib"] # Hexdocs configuration. These paths are read off the publisher's disk at - # `mix docs` time and need no entry in package()'s files: list - the docs - # tarball hexdocs hosts is built separately from the package tarball - # `mix deps.get` fetches. + # `mix docs` time - the docs tarball hexdocs hosts is built separately from + # the package tarball `mix deps.get` fetches. A guide the README links to + # relatively is also in package()'s files: list, for hex.pm's README page. defp docs do [ name: "StatifierUI", @@ -69,8 +69,24 @@ defmodule StatifierUI.MixProject do # `assets` is here because ADR-0009 makes it public API: the JavaScript # ships as source and the host's bundler compiles it, so a tarball without # it is a package whose documented hook cannot be imported. - # `test/packaging_test.exs` holds this list against the ADRs. - files: ~w(lib assets mix.exs README.md LICENSE CHANGELOG.md), + # The docs/ guides are here because hex.pm renders the README from this + # tarball, so a README relative link answers 404 there unless its target + # ships; each is also an extra, so the same link works on HexDocs. + # `test/packaging_test.exs` holds this list against the ADRs and the + # README's relative links. + files: ~w( + lib + assets + mix.exs + README.md + LICENSE + CHANGELOG.md + docs/architecture.md + docs/ops-embedding.md + docs/fixture-bundles.md + docs/wire-format.md + docs/telemetry.md + ), links: %{ "GitHub" => @source_url, "Changelog" => "#{@source_url}/blob/main/CHANGELOG.md" diff --git a/test/packaging_test.exs b/test/packaging_test.exs index 79cb44a..169a700 100644 --- a/test/packaging_test.exs +++ b/test/packaging_test.exs @@ -55,4 +55,41 @@ defmodule StatifierUI.PackagingTest do assert {"assets", "ADR-0009 (JavaScript ships as source)"} in @published_by_adr end end + + describe "README relative links" do + # hex.pm renders the README from the package tarball, so a relative link + # there resolves inside the tarball and answers 404 unless the target + # ships; HexDocs rewrites a relative link only when the target is an + # extra. One relative link works on GitHub, HexDocs and hex.pm alike only + # when its target is in both lists. A file that is in neither (an ADR, + # for one) is linked by absolute GitHub URL instead. + test "every relative link target is in package files: and in extras" do + config = Mix.Project.config() + files = config[:package][:files] + extras = Enum.map(config[:docs][:extras], &extra_path/1) + + targets = readme_relative_targets() + assert targets != [], "found no relative links in README.md; the scan is broken" + + for target <- targets do + assert target in extras, + "README.md links #{target} relatively, but it is not in docs() extras: #{inspect(extras)}" + + assert Enum.any?(files, &(target == &1 or String.starts_with?(target, &1 <> "/"))), + "README.md links #{target} relatively, but package() files: does not ship it: #{inspect(files)}" + end + end + end + + defp readme_relative_targets do + ~r/\]\(([^)\s]+)\)/ + |> Regex.scan(File.read!("README.md"), capture: :all_but_first) + |> List.flatten() + |> Enum.reject(&String.match?(&1, ~r{^(https?:|mailto:|#)})) + |> Enum.map(&(&1 |> String.split("#") |> hd())) + |> Enum.uniq() + end + + defp extra_path({path, _opts}), do: to_string(path) + defp extra_path(path), do: to_string(path) end