From dcf4aa7c6c6cf3754c12dc241e1fd525cc254271 Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Tue, 11 Aug 2026 16:06:59 -0400 Subject: [PATCH 1/3] chore!: graduate to 1.0.0 - disable pre-major version bumping Manually bumps the manifest/.version.txt/mix.exs to 1.0.0 and flips bump-minor-pre-major/bump-patch-for-minor-pre-major off in .release-please-config.json - release-please would otherwise keep bumping minor/patch under 0.x forever, even off a breaking-change commit. From here, release-please's next PR picks up normal semver bumping (major/minor/patch) from this 1.0.0 baseline. Unblocks CRY-37 (homebrew tap), which is waiting on a real tagged release to exist. --- .release-please-config.json | 4 ++-- .release-please-manifest.json | 2 +- .version.txt | 2 +- app/mix.exs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.release-please-config.json b/.release-please-config.json index 5b6fc0d..7d924c8 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -3,8 +3,8 @@ ".": { "changelog-path": "CHANGELOG.md", "release-type": "simple", - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": true, + "bump-minor-pre-major": false, + "bump-patch-for-minor-pre-major": false, "draft": false, "prerelease": false, "version-file": ".version.txt", diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b894e70..37fcefa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.8.5" + ".": "1.0.0" } diff --git a/.version.txt b/.version.txt index 7ada0d3..3eefcb9 100644 --- a/.version.txt +++ b/.version.txt @@ -1 +1 @@ -0.8.5 +1.0.0 diff --git a/app/mix.exs b/app/mix.exs index 5f0438e..0532d0a 100644 --- a/app/mix.exs +++ b/app/mix.exs @@ -5,7 +5,7 @@ defmodule LinearCli.MixProject do [ app: :linear_cli, # x-release-please-start-version - version: "0.8.5", + version: "1.0.0", # x-release-please-end elixir: "~> 1.20", start_permanent: Mix.env() == :prod, From 7301dbd9f20ef5b61e6d12cd5f23a081859012cd Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Wed, 12 Aug 2026 08:16:45 -0400 Subject: [PATCH 2/3] feat(cli): vendor rubyists/homebrew-tap as a git submodule --- .gitmodules | 3 +++ vendor/rubyists-homebrew-tap | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/rubyists-homebrew-tap diff --git a/.gitmodules b/.gitmodules index bc4d906..96fb69f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "vendor/owl"] path = vendor/owl url = git@github.com:fuelen/owl +[submodule "vendor/rubyists-homebrew-tap"] + path = vendor/rubyists-homebrew-tap + url = git@github.com:rubyists/homebrew-tap diff --git a/vendor/rubyists-homebrew-tap b/vendor/rubyists-homebrew-tap new file mode 160000 index 0000000..098d41c --- /dev/null +++ b/vendor/rubyists-homebrew-tap @@ -0,0 +1 @@ +Subproject commit 098d41c0e911e839aa3d16b99a9eceba22351892 From d21981aaf6fceba4e3e1333521905a2a8fb596f7 Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Wed, 12 Aug 2026 08:17:39 -0400 Subject: [PATCH 3/3] fix: make install.sh curl-pipeable - fetch a release binary instead of building from source Previously required a local repo checkout (used BASH_SOURCE[0] to find app/) and a full Elixir/Erlang/Zig toolchain to build from source - unusable as a curl | bash one-liner despite its own comment claiming to be the Homebrew-less fallback. Now downloads the release tarball for the detected platform (already bundles the wrapper scripts), verifies it against the release's SHA256SUMS, and installs - no toolchain needed. Adds LC_VERSION to pin a specific release tag. Also fixes a real bug found while testing: a checksum-verification failure didn't actually stop the script (missing `|| exit 1` on the fetch_lc call), so it fell through into an unrelated second error instead of failing clean. Also updates the README: fixes the stale "Homebrew tap is planned but not yet available" note now that rubyists/homebrew-tap exists, and documents install.sh/uninstall.sh's new curl-pipeable one-liners. --- Readme.adoc | 35 +++++++++++----- install.sh | 112 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 108 insertions(+), 39 deletions(-) diff --git a/Readme.adoc b/Readme.adoc index 8d94d67..c5464ec 100644 --- a/Readme.adoc +++ b/Readme.adoc @@ -72,8 +72,6 @@ built with. The container image has its own separate SBOM (its OS packages - irrelevant if you're not using the container), published as a workflow artifact on the release's build rather than a release asset. -NOTE: A Homebrew tap is planned but not yet available. - On macOS, Gatekeeper blocks `lc` itself (the wrapper scripts are plain shell, so they're unaffected) since it isn't signed/notarized yet: @@ -82,18 +80,37 @@ so they're unaffected) since it isn't signed/notarized yet: $ xattr -d com.apple.quarantine /usr/local/bin/lc ---- -=== install.sh (builds from source, for machines without Homebrew) +=== Homebrew (macOS Apple Silicon, Linux x86_64) [source,sh] ---- -$ git clone https://github.com/rubyists/linear-cli-ex.git -$ cd linear-cli-ex -$ ./install.sh +$ brew tap rubyists/tap +$ brew install lc +---- + +See https://github.com/rubyists/homebrew-tap for the formula itself. + +=== install.sh (fetches a release binary, for machines without Homebrew) + +[source,sh] +---- +$ curl -fsSL https://raw.githubusercontent.com/rubyists/linear-cli-ex/main/install.sh | bash +---- + +Detects your platform, downloads and checksum-verifies the matching release +tarball (the same one the "Download a release binary" section above uses), +and installs `lc` plus the `bin/` wrapper scripts onto a directory already on +your `$PATH` - no Erlang/Elixir/Zig toolchain required. `LC_VERSION` pins a +specific release tag instead of the latest one; `LC_INSTALL_DIR` pins a +specific install directory. + +[source,sh] +---- +$ curl -fsSL https://raw.githubusercontent.com/rubyists/linear-cli-ex/main/uninstall.sh | bash ---- -Builds a native release via https://mise.jdx.dev[mise] and installs `lc` plus -the `bin/` wrapper scripts onto a directory already on your `$PATH`. Run -`./uninstall.sh` to remove exactly what it installed. +Removes exactly what `install.sh` installed, by replaying the manifest it +wrote - not a guess at where things ended up. === From Source (You are obviously a developer) diff --git a/install.sh b/install.sh index 4baad09..2ad245b 100755 --- a/install.sh +++ b/install.sh @@ -1,15 +1,20 @@ #!/usr/bin/env bash -# Builds a native Burrito release of `lc` for the current machine and -# installs it, plus the bin/ wrapper scripts (lcreate, lcls, lclose, -# lcomment, lproj), onto a directory already on $PATH. Fallback path for -# machines without Homebrew - see rubyists/homebrew-tap once it exists. +# Downloads a precompiled Burrito release of `lc` for the current machine +# and installs it, plus the bin/ wrapper scripts (lcreate, lcls, lclose, +# lcomment, lproj) already bundled in the same release tarball, onto a +# directory already on $PATH. Fallback path for machines without +# Homebrew - see rubyists/homebrew-tap for that. +# +# LC_VERSION pins a specific release tag (e.g. "v1.0.0") instead of the +# latest one. LC_INSTALL_DIR pins a specific install directory instead of +# the first writable $PATH entry found. # # Records exactly what it installed and where in a manifest file, so # uninstall.sh can remove precisely those files even if $PATH changes # between install and uninstall. -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -app_dir="$repo_root/app" +repo=rubyists/linear-cli-ex +version=${LC_VERSION:-latest} state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/linear-cli-ex" manifest="$state_dir/manifest" @@ -64,44 +69,85 @@ pick_install_dir() { printf '%s\n' "$HOME/.local/bin" } -build_lc() { - local target="$1" - - if ! cd "$app_dir" +# GNU coreutils' sha256sum vs. macOS's shasum -a 256 - both read the same +# " " format via `-c -` on stdin. +checksum_cmd() { + if command -v sha256sum >/dev/null 2>&1 then - printf 'error: could not cd into %s\n' "$app_dir" >&2 + printf 'sha256sum\n' + elif command -v shasum >/dev/null 2>&1 + then + printf 'shasum -a 256\n' + else + printf 'error: need sha256sum or shasum on $PATH to verify the download\n' >&2 exit 1 fi +} - rm -rf _build/prod - - have_mise=0 - if command -v mise >/dev/null 2>&1 +release_url() { + local asset="$1" + if [ "$version" = "latest" ] then - have_mise=1 + printf 'https://github.com/%s/releases/latest/download/%s\n' "$repo" "$asset" else - printf 'warning: mise not found on PATH; building with whatever Erlang/Elixir are active\n' >&2 + printf 'https://github.com/%s/releases/download/%s/%s\n' "$repo" "$version" "$asset" + fi +} + +# Downloads lc_.tar.gz + SHA256SUMS, verifies the tarball's +# checksum against just its own line (SHA256SUMS covers every target, +# not only the one being installed here), and extracts it. Prints the +# directory it extracted into. +fetch_lc() { + local target="$1" tarball tmp_dir sum_tool + tarball="lc_${target}.tar.gz" + + tmp_dir=$(mktemp -d) || { + printf 'error: could not create a temp directory\n' >&2 + exit 1 + } + trap 'rm -rf "$tmp_dir"' EXIT + + printf 'Downloading %s (%s)...\n' "$tarball" "$version" >&2 + + if ! curl -fsSL "$(release_url "$tarball")" -o "$tmp_dir/$tarball" + then + printf 'error: failed to download %s\n' "$tarball" >&2 + exit 1 fi - if [ "$have_mise" -eq 1 ] + if ! curl -fsSL "$(release_url "SHA256SUMS")" -o "$tmp_dir/SHA256SUMS" then - MIX_ENV=prod BURRITO_TARGET="$target" mise exec -- mix release lc --overwrite - else - MIX_ENV=prod BURRITO_TARGET="$target" mix release lc --overwrite + printf 'error: failed to download SHA256SUMS\n' >&2 + exit 1 + fi + + sum_tool=$(checksum_cmd) + + if ! grep -- " $tarball\$" "$tmp_dir/SHA256SUMS" | (cd "$tmp_dir" && $sum_tool -c -) >/dev/null + then + printf 'error: checksum verification failed for %s\n' "$tarball" >&2 + exit 1 fi - if [ "$?" -ne 0 ] + if ! tar -xzf "$tmp_dir/$tarball" -C "$tmp_dir" then - printf 'error: build failed (mix release lc, target %s)\n' "$target" >&2 + printf 'error: failed to extract %s\n' "$tarball" >&2 exit 1 fi + + printf '%s\n' "$tmp_dir" } +if ! command -v curl >/dev/null 2>&1 +then + printf 'error: curl is required\n' >&2 + exit 1 +fi + target=$(detect_target) || exit 1 install_dir=$(pick_install_dir) - -printf 'Building lc (%s)...\n' "$target" -build_lc "$target" +extracted_dir=$(fetch_lc "$target") || exit 1 if ! mkdir -p "$install_dir" then @@ -118,10 +164,7 @@ fi : > "$manifest" for name in lc lcreate lcls lclose lcomment lproj do - src="$app_dir/burrito_out/lc_${target}" - [ "$name" = lc ] || src="$repo_root/bin/$name" - - if ! install -m 755 "$src" "$install_dir/$name" + if ! install -m 755 "$extracted_dir/$name" "$install_dir/$name" then printf 'error: failed to install %s to %s\n' "$name" "$install_dir" >&2 exit 1 @@ -130,6 +173,15 @@ do printf '%s\n' "$install_dir/$name" >> "$manifest" done +# Gatekeeper blocks lc itself (a real Mach-O binary) on macOS since it +# isn't signed/notarized - the wrapper scripts are plain shell, so they're +# unaffected. Best-effort: xattr not existing/failing shouldn't fail the +# install, since the user can still do this by hand (see the README). +if [ "$(uname -s)" = Darwin ] +then + xattr -d com.apple.quarantine "$install_dir/lc" 2>/dev/null || true +fi + printf 'Installed lc, lcreate, lcls, lclose, lcomment, lproj to %s\n' "$install_dir" printf '(uninstall.sh will remove exactly these files - manifest at %s)\n' "$manifest"