From b0861f4717e4233f205a99d2f3c49572af17e974 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:48:53 +0530 Subject: [PATCH 1/6] Replaced `\s` in `sed` with POSIX compliant `[[:space:]]` so that it works on both Linux and BSD based OS (incl macOS). --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index e45acb7..6933bb4 100644 --- a/install.sh +++ b/install.sh @@ -119,7 +119,7 @@ get_version() { info "Fetching latest release version..." VERSION=$(curl -sSL "https://api.github.com/repos/${REPO}/releases/latest" \ | grep '"tag_name"' \ - | sed -E 's/.*"tag_name":\s*"([^"]+)".*/\1/') + | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/') [ -n "$VERSION" ] || die "Could not determine latest version. Check https://github.com/${REPO}/releases" From f072bc5563489cd9b5c21893146f6df7ab592905 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:03:25 +0530 Subject: [PATCH 2/6] Replaced `chromium` cask with `google-chrome` for homebrew on macOS as its set to be disabled on 2026-09-01. --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 6933bb4..4da2391 100644 --- a/install.sh +++ b/install.sh @@ -86,8 +86,8 @@ ensure_chrome() { ;; macos) if command -v brew >/dev/null 2>&1; then - info "Installing Chromium via Homebrew..." - brew install --cask chromium + info "Installing Google Chrome via Homebrew..." + brew install --cask google-chrome else echo "" echo " Please install Chrome or Chromium manually:" From 3c91c3ee00834efc18a0dfee829ff3e0f8868410 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:08:50 +0530 Subject: [PATCH 3/6] Updated error messaging when Chromium/Chrome is not found and cannot be installed. --- install.sh | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/install.sh b/install.sh index 4da2391..1e210e8 100644 --- a/install.sh +++ b/install.sh @@ -15,6 +15,20 @@ need() { command -v "$1" >/dev/null 2>&1 || die "Required tool '$1' not found. Please install it and try again." } +# Print why a browser is required and how to proceed, then exit. +# $1 = reason automatic installation was not possible +browser_required() { + echo "" + echo " mmd-cli requires Chrome or Chromium to render diagrams." + echo " $1" + echo "" + echo " Please install one of the following, then run this installer again:" + echo " Google Chrome - https://www.google.com/chrome/" + echo " Chromium - https://www.chromium.org/getting-involved/download-chromium/" + echo "" + die "Chrome or Chromium is required. Install one and re-run this script." +} + # ── Detect OS and architecture ─────────────────────────────────────────────── detect_platform() { @@ -81,7 +95,7 @@ ensure_chrome() { info "Installing Chromium via yum..." sudo yum install -y chromium else - die "Could not detect a supported package manager. Please install Chrome or Chromium manually." + browser_required "No supported package manager (apt/dnf/yum) was found to install one automatically." fi ;; macos) @@ -89,12 +103,7 @@ ensure_chrome() { info "Installing Google Chrome via Homebrew..." brew install --cask google-chrome else - echo "" - echo " Please install Chrome or Chromium manually:" - echo " Google Chrome - https://www.google.com/chrome/" - echo " Chromium - https://www.chromium.org/getting-involved/download-chromium/" - echo "" - die "No supported installation method found for Chrome/Chromium on macOS." + browser_required "Homebrew is not available to install one automatically." fi ;; esac From 34aff0375599be5ec3b5ee825b219f798072acd5 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:41:45 +0530 Subject: [PATCH 4/6] Updated gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0fdd31a..81f3069 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ Thumbs.db *.png *.pdf !web/*.js -_docs +.scratchpad/ +CLAUDE.md From 57f65e0478c69b1959a011fb4edfbac32ec71fb6 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:44:51 +0530 Subject: [PATCH 5/6] Added a test suite for `install.sh` covering release version parsing, platform detection, browser detection & error messaging. Tests run in CI on both Ubuntu and macOS via a new workflow so that BSD/GNU tool differences like the one in issue #3 are caught before release. Made `install.sh` sourceable via an env guard so tests can call its functions & added a `make test-install` target to run the suite locally. --- .github/workflows/install-test.yml | 23 +++++ Makefile | 6 +- install.sh | 5 +- test/fixtures/github_release.json | 20 ++++ test/install_test.sh | 153 +++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/install-test.yml create mode 100644 test/fixtures/github_release.json create mode 100644 test/install_test.sh diff --git a/.github/workflows/install-test.yml b/.github/workflows/install-test.yml new file mode 100644 index 0000000..223a397 --- /dev/null +++ b/.github/workflows/install-test.yml @@ -0,0 +1,23 @@ +name: Install Script Tests + +on: + push: + branches: + - "**" + pull_request: + +jobs: + test-install-script: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Lint install.sh + run: shellcheck install.sh + + - name: Run install.sh tests + run: sh test/install_test.sh diff --git a/Makefile b/Makefile index 3d3cdd9..b453c55 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VERSION?=$(shell cat version) LDFLAGS=-ldflags '-s -w -X github.com/coolamit/mermaid-cli/internal/cli.Version=$(VERSION)' # Declare phony targets -.PHONY: ssh-cmd up down build clean test tidy format build-linux-x64 build-linux-arm64 build-macos-x64 build-macos-arm64 build-all docker-up docker-down docker-run docker-run-aloof docker-clean +.PHONY: ssh-cmd up down build clean test test-install tidy format build-linux-x64 build-linux-arm64 build-macos-x64 build-macos-arm64 build-all docker-up docker-down docker-run docker-run-aloof docker-clean # Common function definitions define CURRENT_HOMESTEAD_STATUS @@ -62,6 +62,10 @@ build: test: @$(call SSH_EXEC,$(GO) clean -testcache && $(GO) test ./...) +# Tests for install.sh (pure sh, runs on host — no VM needed) +test-install: + @sh test/install_test.sh + tidy: @$(call SSH_EXEC,$(GO) mod tidy) diff --git a/install.sh b/install.sh index 1e210e8..faae5f8 100644 --- a/install.sh +++ b/install.sh @@ -226,4 +226,7 @@ main() { info "Done!" } -main "$@" +# Skip execution when sourced for testing (see test/install_test.sh) +if [ -z "${INSTALL_SH_TEST:-}" ]; then + main "$@" +fi diff --git a/test/fixtures/github_release.json b/test/fixtures/github_release.json new file mode 100644 index 0000000..bab9d06 --- /dev/null +++ b/test/fixtures/github_release.json @@ -0,0 +1,20 @@ +{ + "url": "https://api.github.com/repos/coolamit/mermaid-cli/releases/200000001", + "html_url": "https://github.com/coolamit/mermaid-cli/releases/tag/v0.1.1", + "id": 200000001, + "tag_name": "v0.1.1", + "target_commitish": "master", + "name": "v0.1.1", + "draft": false, + "prerelease": false, + "created_at": "2026-02-11T00:00:00Z", + "published_at": "2026-02-11T00:30:00Z", + "assets": [ + { + "name": "mmd-cli_0.1.1_macos_arm64.tar.gz", + "content_type": "application/gzip", + "browser_download_url": "https://github.com/coolamit/mermaid-cli/releases/download/v0.1.1/mmd-cli_0.1.1_macos_arm64.tar.gz" + } + ], + "body": "Release notes" +} diff --git a/test/install_test.sh b/test/install_test.sh new file mode 100644 index 0000000..3a5464a --- /dev/null +++ b/test/install_test.sh @@ -0,0 +1,153 @@ +#!/bin/sh +# Unit tests for install.sh. +# +# Usage: sh test/install_test.sh +# +# The script under test is sourced with INSTALL_SH_TEST=1 so its functions can +# be called without running the installer. Each test runs in a subshell because +# install.sh enables `set -e` and its failure paths call exit. External +# commands (curl, uname) are shadowed with shell functions where needed. +# +# Set INSTALL_SH to test a different copy of the script (used to verify that +# these tests catch known-bad versions). + +TEST_DIR=$(cd "$(dirname "$0")" && pwd) +REPO_DIR=$(dirname "$TEST_DIR") +INSTALL_SH="${INSTALL_SH:-$REPO_DIR/install.sh}" +FIXTURE="$TEST_DIR/fixtures/github_release.json" + +PASS=0 +FAIL=0 + +pass() { PASS=$((PASS + 1)); printf 'ok - %s\n' "$1"; } +fail() { FAIL=$((FAIL + 1)); printf 'FAIL - %s\n' "$1"; } + +assert_eq() { + # $1 = test name, $2 = expected, $3 = actual + if [ "$2" = "$3" ]; then + pass "$1" + else + fail "$1 (expected '$2', got '$3')" + fi +} + +assert_contains() { + # $1 = test name, $2 = needle, $3 = haystack + case "$3" in + *"$2"*) pass "$1" ;; + *) fail "$1 (output does not contain '$2')" ;; + esac +} + +[ -f "$INSTALL_SH" ] || { echo "install.sh not found at $INSTALL_SH" >&2; exit 1; } +[ -f "$FIXTURE" ] || { echo "fixture not found at $FIXTURE" >&2; exit 1; } + +# ── Syntax check ───────────────────────────────────────────────────────────── + +if sh -n "$INSTALL_SH" 2>/dev/null; then + pass "install.sh parses (sh -n)" +else + fail "install.sh parses (sh -n)" +fi + +# ── get_version: parse latest release from GitHub API ──────────────────────── +# Regression test for issue #3: the sed pattern must work on BSD sed (macOS) +# as well as GNU sed (Linux). + +out=$( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + curl() { cat "$FIXTURE"; } + get_version >/dev/null 2>&1 + printf '%s|%s' "$VERSION" "$VERSION_NUM" +) || out="(subshell failed: $out)" +assert_eq "get_version parses tag_name from API response" "v0.1.1|0.1.1" "$out" + +# ── get_version: explicit version argument ─────────────────────────────────── + +out=$( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + get_version "v1.2.3" >/dev/null 2>&1 + printf '%s|%s' "$VERSION" "$VERSION_NUM" +) || out="(subshell failed: $out)" +assert_eq "get_version strips leading v from explicit version" "v1.2.3|1.2.3" "$out" + +# ── detect_platform: OS/arch mapping ───────────────────────────────────────── + +out=$( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + uname() { case "$1" in (-s) echo "Linux" ;; (-m) echo "x86_64" ;; esac; } + detect_platform >/dev/null 2>&1 + printf '%s|%s' "$OS" "$ARCH" +) || out="(subshell failed: $out)" +assert_eq "detect_platform maps Linux/x86_64" "linux|x64" "$out" + +out=$( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + uname() { case "$1" in (-s) echo "Darwin" ;; (-m) echo "arm64" ;; esac; } + detect_platform >/dev/null 2>&1 + printf '%s|%s' "$OS" "$ARCH" +) || out="(subshell failed: $out)" +assert_eq "detect_platform maps Darwin/arm64" "macos|arm64" "$out" + +rc=0 +( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + uname() { echo "FreeBSD"; } + detect_platform +) >/dev/null 2>&1 || rc=$? +assert_eq "detect_platform rejects unsupported OS with exit 1" "1" "$rc" + +# ── has_chrome: browser detection ──────────────────────────────────────────── + +STUB_DIR=$(mktemp -d) +trap 'rm -rf "$STUB_DIR"' EXIT + +# Negative case only works where the hardcoded macOS app bundles are absent +# (they exist on developer Macs and on GitHub's macOS runners). +if [ -d "/Applications/Google Chrome.app" ] || [ -d "/Applications/Chromium.app" ]; then + echo "skip - has_chrome returns 1 when no browser present (browser app installed on this machine)" +else + rc=0 + ( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + PATH="$STUB_DIR" + has_chrome + ) >/dev/null 2>&1 || rc=$? + assert_eq "has_chrome returns 1 when no browser present" "1" "$rc" +fi + +printf '#!/bin/sh\n' > "$STUB_DIR/chromium" +chmod +x "$STUB_DIR/chromium" +rc=0 +( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + PATH="$STUB_DIR" + has_chrome +) >/dev/null 2>&1 || rc=$? +assert_eq "has_chrome finds chromium on PATH" "0" "$rc" + +# ── browser_required: message and exit code ────────────────────────────────── + +rc=0 +out=$( + INSTALL_SH_TEST=1 + . "$INSTALL_SH" + browser_required "Test reason line." 2>&1 +) || rc=$? +assert_eq "browser_required exits 1" "1" "$rc" +assert_contains "browser_required states the requirement" "requires Chrome or Chromium" "$out" +assert_contains "browser_required echoes the reason" "Test reason line." "$out" +assert_contains "browser_required says to re-run the installer" "run this installer again" "$out" + +# ── Summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "passed: $PASS failed: $FAIL" +[ "$FAIL" -eq 0 ] || exit 1 From b165d464dac2b59802c1c30a64352ea4faeff060 Mon Sep 17 00:00:00 2001 From: Amit Gupta <1511590+coolamit@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:20:02 +0530 Subject: [PATCH 6/6] Run `shellcheck` only on linux. Its not available on macOS. --- .github/workflows/install-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/install-test.yml b/.github/workflows/install-test.yml index 223a397..ea45c42 100644 --- a/.github/workflows/install-test.yml +++ b/.github/workflows/install-test.yml @@ -17,6 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Lint install.sh + if: runner.os == 'Linux' run: shellcheck install.sh - name: Run install.sh tests