diff --git a/.gitignore b/.gitignore index 400506d..c94a57b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ # names it by hand — so the glob is what makes the next invented name safe. /mutants.out*/ +# herdr: the released bdi herdr/fetch.sh installs. +/bin/ + # nix result result-* diff --git a/flake.nix b/flake.nix index 3e64d88..ec6ab3e 100644 --- a/flake.nix +++ b/flake.nix @@ -1946,10 +1946,10 @@ touch $out ''; - # The version is written in three places and two of them are held to + # The version is written in four places and two of them are held to # the crate: `flake.nix` reads it out of `Cargo.toml`, and `cargo # publish --locked` refuses a `Cargo.lock` that disagrees. README's - # flake example is the third, and a bump that forgets it lands green — + # flake example is a third, and a bump that forgets it lands green — # then the release ships a page telling a reader to pin the tag before # the one being released. # @@ -2060,6 +2060,76 @@ $pinned" touch $out ''; + # The herdr plugin manifest is the fourth. Its version is the release + # herdr/fetch.sh downloads, so a bump that forgets it installs the + # previous release under the new one's name. + pluginDeclaresTheVersion = pkgs.writeShellScriptBin "plugin-declares-the-version" '' + set -u + + cd "''${1:-.}" || exit 1 + + version="$(awk -F'"' ' + /^\[/ { package = ($0 == "[package]") } + package && /^version *=/ { print $2; exit } + ' Cargo.toml)" + + declared="$(awk -F'"' ' + /^\[/ { exit } + /^version *=/ { print $2; exit } + ' herdr-plugin.toml)" + + if [ "$declared" != "$version" ]; then + echo "herdr-plugin.toml declares a version the crate does not." + echo + echo "Cargo.toml declares $version." + echo "herdr-plugin.toml declares ''${declared:-no version}." + echo + echo "herdr/fetch.sh installs the release the manifest names, so it" + echo "reads version = \"$version\"." + exit 1 + fi + ''; + + pluginDeclaresTheVersionTest = pkgs.runCommand "plugin-declares-the-version-test" + { nativeBuildInputs = [ pluginDeclaresTheVersion ]; } '' + set -u + + tree="$TMPDIR/tree" + mkdir -p "$tree" + printf '[package]\nname = "beady-eye"\nversion = "0.2.0"\n' > "$tree/Cargo.toml" + + manifest() { + printf '%s\n' "id = \"example.plugin\"" "$1" "" \ + '[[panes]]' 'id = "eye"' 'version = "0.2.0"' > "$tree/herdr-plugin.toml" + } + + fail() { echo "FAIL: $1"; echo "$output"; exit 1; } + + manifest 'version = "0.2.0"' + output="$( plugin-declares-the-version "$tree" 2>&1 )" || + fail "it refused a manifest declaring the version the crate declares:" + + # The bump that forgets the manifest, which is the whole of this. + manifest 'version = "0.1.0"' + if output="$( plugin-declares-the-version "$tree" 2>&1 )"; then + fail "it accepted a manifest declaring another version:" + fi + case "$output" in + *"declares 0.1.0"*"version = \"0.2.0\""*) ;; + *) fail "the refusal did not name both versions:" ;; + esac + + # A version under a table belongs to that entry, not the plugin, so a + # manifest whose top level has none is refused rather than read as + # matching. + manifest "" + if output="$( plugin-declares-the-version "$tree" 2>&1 )"; then + fail "it read a table's version as the plugin's:" + fi + + touch $out + ''; + # The Release body is RELEASE-NOTES/.md byte for byte, and # GitHub renders that body with its hard line break extension on — so a # newline inside a paragraph becomes a
and the published page @@ -3463,6 +3533,14 @@ and a second line" "colours-come-from-the-palette"; palette-test = coloursComeFromThePaletteTest; + # A source of its own for the same reason readme-pin has one. + plugin-version = pkgs.runCommand "plugin-version" + { nativeBuildInputs = [ pluginDeclaresTheVersion ]; } '' + plugin-declares-the-version ${sourceOf [ ./herdr-plugin.toml ./Cargo.toml ]} + touch $out + ''; + plugin-version-test = pluginDeclaresTheVersionTest; + # A source of its own, holding the two files this reads. It cannot be # a `checkOf` entry the way the scans above are: those are built from # `source`, which carries no documentation and diff --git a/herdr-plugin.toml b/herdr-plugin.toml new file mode 100644 index 0000000..8861f7e --- /dev/null +++ b/herdr-plugin.toml @@ -0,0 +1,26 @@ +id = "codeforbreakfast.beady-eye" +name = "beady-eye" +version = "0.16.0" +min_herdr_version = "0.9.1" +platforms = ["linux", "macos"] +description = "A tree of work in flight: bead graphs annotated with the live agents working them." + +# Fetches this version's released bdi into bin/, so installing needs no Rust +# toolchain. `herdr plugin link` skips it: a linked checkout runs it by hand. +[[build]] +command = ["sh", "herdr/fetch.sh"] + +# The pane starts in the focused pane's directory rather than the plugin root, +# so bdi is named by absolute path. bdi's own error is all that says why it +# refused to start, so a failed start holds the pane until the reader has read it. +[[panes]] +id = "eye" +title = "bdi" +placement = "split" +command = ["sh", "-c", "\"$HERDR_PLUGIN_ROOT/bin/bdi\" || { printf '\\nbdi exited %s. Press Enter to close.' \"$?\"; read -r _; }"] + +[[actions]] +id = "open" +title = "beady-eye: open" +contexts = ["pane", "workspace"] +command = ["sh", "herdr/open.sh"] diff --git a/herdr/fetch.sh b/herdr/fetch.sh new file mode 100644 index 0000000..f6e017d --- /dev/null +++ b/herdr/fetch.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# herdr's build step: download the released bdi for this machine into bin/, +# checked against the sha256 published beside it. The release is the tag for +# the manifest's version, so a checkout always fetches its own release. +# +# herdr runs this from the plugin checkout without the runtime environment, so +# the root is found from where the script lives. +set -eu + +root="$(cd "$(dirname "$0")/.." && pwd)" +version="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$root/herdr-plugin.toml" | head -n 1)" + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) target=x86_64-unknown-linux-musl ;; + Linux-aarch64 | Linux-arm64) target=aarch64-unknown-linux-musl ;; + Darwin-x86_64) target=x86_64-apple-darwin ;; + Darwin-arm64) target=aarch64-apple-darwin ;; + *) + echo "beady-eye: no released bdi for $(uname -s) $(uname -m)." >&2 + echo "Install it with 'cargo install beady-eye' and copy it to $root/bin/bdi." >&2 + exit 1 + ;; +esac + +asset="bdi-$target" +base="https://github.com/CodeForBreakfast/beady-eye/releases/download/v$version" + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +# GitHub's CDN can answer 404 for a few minutes after a release publishes, so a +# 404 is retried along with everything else. +fetch() { curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "$1" -o "$2"; } + +echo "beady-eye: fetching $asset from v$version" +fetch "$base/$asset" "$tmp/bdi" +fetch "$base/$asset.sha256" "$tmp/bdi.sha256" + +expected="$(awk '{ print $1 }' "$tmp/bdi.sha256")" +if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$tmp/bdi" | awk '{ print $1 }')" +else + actual="$(shasum -a 256 "$tmp/bdi" | awk '{ print $1 }')" +fi +if [ "$expected" != "$actual" ]; then + echo "beady-eye: $asset does not match its published sha256." >&2 + echo "Expected $expected, got $actual." >&2 + exit 1 +fi + +mkdir -p "$root/bin" +install -m 0755 "$tmp/bdi" "$root/bin/bdi" +echo "beady-eye: installed $root/bin/bdi" diff --git a/herdr/open.sh b/herdr/open.sh new file mode 100644 index 0000000..00dd27b --- /dev/null +++ b/herdr/open.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# herdr's open action: bdi in a split beside the focused pane, started in that +# pane's directory so it reads the project the reader is looking at. A plugin +# pane otherwise starts in the plugin root, which no tracker covers. +set -eu + +# herdr writes the context as one line of JSON. A directory whose name needs +# escaping in JSON matches neither key, and bdi then starts in the plugin root +# and says what it could not find. +context_field() { + printf '%s' "$HERDR_PLUGIN_CONTEXT_JSON" | sed -n "s/.*\"$1\":\"\([^\"\\\\]*\)\".*/\1/p" +} + +cwd="$(context_field focused_pane_cwd)" +[ -n "$cwd" ] || cwd="$(context_field workspace_cwd)" + +set -- --plugin "$HERDR_PLUGIN_ID" --entrypoint eye --focus +[ -n "$cwd" ] && set -- "$@" --cwd "$cwd" +[ -n "${HERDR_PANE_ID:-}" ] && set -- "$@" --target-pane "$HERDR_PANE_ID" + +exec "$HERDR_BIN_PATH" plugin pane open "$@"