From 7b6301423a357b7a176ed4dedc6cf396d83546c6 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 21:36:37 +0700 Subject: [PATCH 01/10] chore(jusfile): upgrade build command --- justfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 2330e0e..89735f3 100644 --- a/justfile +++ b/justfile @@ -1,9 +1,9 @@ # build binary file [group('dev')] build: - @go build -o iris main.go + @go build -o iris main.go optimized-build: - @GOAMD64=v3 go build -pgo=auto -ldflags="-s -w" -trimpath -o iris main.go + @GOAMD64=v4 go build -ldflags="-s -w" -trimpath -o iris main.go # run iris [group('dev')] @@ -14,6 +14,7 @@ run: [group('dev')] [linux, macos] reload: + # @GOAMD64=v3 go build -pgo=auto -ldflags="-s -w" -trimpath -o iris main.go @go build -o iris main.go @if [ -n "${IRIS_PID:-}" ]; then kill -USR1 $IRIS_PID; fi @if [ -z "${IRIS_FD:-}" ]; then ./iris; fi @@ -44,7 +45,7 @@ alias ana := analyze analyze: @go run scripts/test_analyzer.go -# run linter +# run linter [group('dev')] lint: @golangci-lint run ./... From bb9a750a344020e2be3be2099f8baa81998e8713 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:00:02 +0700 Subject: [PATCH 02/10] feat(justfile): debug installer --- justfile | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 89735f3..2864034 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,12 @@ +# NOTE: RUN "just -l" TO QUICKLY VIEW ALL COMMANDS + # build binary file -[group('dev')] +[group('build')] build: @go build -o iris main.go + +# build with optimized binary file +[group('build')] optimized-build: @GOAMD64=v4 go build -ldflags="-s -w" -trimpath -o iris main.go @@ -24,8 +29,8 @@ reload: pkg: @go mod tidy -# debugger -[group('dev')] +# iris debugger +[group('debug')] debug: @./iris --debug @@ -76,3 +81,162 @@ debug-notify version="v1.99.0": [group('debug')] build-release version: @GOAMD64=v3 go build -pgo=auto -ldflags="-s -w -X github.com/versenilvis/iris/root.Version={{version}}" -trimpath -o iris main.go + +# test the install script locally +# usage: just debug-install v1.0.0 +[group('debug')] +debug-install version="v1.0.0": + #!/bin/sh + PORT=19998 + TMP=$(mktemp -d) + PASS=0 + FAIL=0 + + # always cleanup server + tmp, even if a test crashes + trap "kill \$SERVER_PID 2>/dev/null; rm -rf $TMP" EXIT + + ok() { PASS=$((PASS+1)); printf " \033[32m✓\033[0m %s\n" "$1"; } + fail() { FAIL=$((FAIL+1)); printf " \033[31m✗\033[0m %s\n" "$1"; } + + # --- detect actual arch (not hardcoded) --- + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + MACH=$(uname -m) + case "$MACH" in + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "unsupported arch: $MACH"; exit 1 ;; + esac + ARCHIVE_NAME="iris_${OS}_${ARCH}.tar.gz" + + echo "building iris ({{version}}, ${OS}_${ARCH})..." + go build -ldflags="-X github.com/versenilvis/iris/root.Version={{version}}" -o "$TMP/iris" main.go + + echo "packaging archive ($ARCHIVE_NAME)..." + tar -czf "$TMP/$ARCHIVE_NAME" -C "$TMP" iris + + # --- start mock server --- + python3 -c 'import sys, textwrap; exec(textwrap.dedent(r""" + import os, sys + from http.server import HTTPServer, BaseHTTPRequestHandler + + port = int(sys.argv[1]) + tmp_dir = sys.argv[2] + version = sys.argv[3] + os_name = sys.argv[4] + arch = sys.argv[5] + + class mock_handler(BaseHTTPRequestHandler): + def do_GET(self): + if "/repos/versenilvis/iris/releases/latest" in self.path: + if self.path.startswith("/404"): + self.send_response(404) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(b"{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest\"}") + elif self.path.startswith("/ratelimit"): + self.send_response(403) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(b"{\"message\":\"API rate limit exceeded for ...\",\"documentation_url\":\"https://docs.github.com/rest/overview/rate-limits-for-the-rest-api\"}") + else: + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + download_url = "http://localhost:" + str(port) + "/iris_" + os_name + "_" + arch + ".tar.gz" + response = "{\n \"tag_name\": \"" + version + "\",\n \"assets\": [\n {\n \"browser_download_url\": \"" + download_url + "\"\n }\n ]\n}\n" + self.wfile.write(response.encode("utf-8")) + elif self.path.endswith(".tar.gz"): + filename = os.path.basename(self.path) + filepath = os.path.join(tmp_dir, filename) + if os.path.exists(filepath): + self.send_response(200) + self.send_header("Content-Type", "application/octet-stream") + self.send_header("Content-Length", str(os.path.getsize(filepath))) + self.end_headers() + with open(filepath, "rb") as f: + self.wfile.write(f.read()) + else: + self.send_response(404) + self.end_headers() + else: + self.send_response(200) + self.end_headers() + + def log_message(self, *args): + pass + + HTTPServer(("", port), mock_handler).serve_forever() + """))' "$PORT" "$TMP" "{{version}}" "$OS" "$ARCH" 2>/dev/null & + SERVER_PID=$! + + # retry loop instead of blind sleep - wait up to 3s for server to be ready + i=0 + until curl -sf "http://localhost:${PORT}/" >/dev/null 2>&1; do + i=$((i+1)) + [ $i -ge 30 ] && echo "mock server failed to start" && exit 1 + sleep 0.1 + done + + echo "" + echo "--- running test cases ---" + + # --- test 1: happy path --- + printf "test 1: happy path install... " + OUT=$(BIN_DIR="$TMP/out1" IRIS_API_URL="http://localhost:${PORT}/happy" sh scripts/install.sh 2>&1) && STATUS=0 || STATUS=$? + if [ $STATUS -eq 0 ] && echo "$OUT" | grep -q "Installation verified"; then + ok "binary installed and verified" + else + fail "happy path failed\n$OUT" + fi + + # --- test 2: version output check --- + printf "test 2: version string matches... " + BIN="$TMP/out1/iris" + if [ -x "$BIN" ]; then + VER=$("$BIN" version 2>&1) + if echo "$VER" | grep -q "{{version}}"; then + ok "got '$VER'" + else + fail "expected {{version}}, got '$VER'" + fi + else + fail "binary not found at $BIN" + fi + + # --- test 3: 404 (no release published) --- + printf "test 3: 404 no release error... " + OUT=$(BIN_DIR="$TMP/out3" IRIS_API_URL="http://localhost:${PORT}/404" sh scripts/install.sh 2>&1) && STATUS=0 || STATUS=1 + if [ $STATUS -ne 0 ] && echo "$OUT" | grep -qi "no releases found\|not have published"; then + ok "correct error message for 404" + else + fail "expected 404 error, got: $OUT" + fi + + # --- test 4: 403 rate limit --- + printf "test 4: rate limit error... " + OUT=$(BIN_DIR="$TMP/out4" IRIS_API_URL="http://localhost:${PORT}/ratelimit" sh scripts/install.sh 2>&1) && STATUS=0 || STATUS=1 + if [ $STATUS -ne 0 ] && echo "$OUT" | grep -qi "rate limit\|rate limited"; then + ok "correct error message for rate limit" + else + fail "expected rate limit error, got: $OUT" + fi + + # --- test 5: wget code path (call wget directly, same as install script would) --- + printf "test 5: wget code path... " + if ! command -v wget >/dev/null 2>&1; then + ok "skipped (wget not installed)" + else + # directly invoke wget the same way install.sh does and check it parses correctly + RELEASES=$(wget -qO- "http://localhost:${PORT}/happy/repos/versenilvis/iris/releases/latest" 2>&1) && WS=0 || WS=$? + URL=$(echo "$RELEASES" | grep "browser_download_url" | grep "${OS}_${ARCH}" | head -1 | cut -d '"' -f 4) + if [ -n "$URL" ]; then + ok "wget parses download URL correctly ($URL)" + else + fail "wget could not parse URL from response: $RELEASES" + fi + fi + + # --- summary --- + echo "" + printf "results: \033[32m%d passed\033[0m, \033[31m%d failed\033[0m\n" "$PASS" "$FAIL" + [ $FAIL -eq 0 ] || exit 1 \ No newline at end of file From 6026e6ab0325b384ef1a8c59eb9ab2b3fb46a5c8 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:00:07 +0700 Subject: [PATCH 03/10] fix(installer): installer and tests --- scripts/install.sh | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index f57414b..642fd08 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -6,6 +6,8 @@ set -e REPO="versenilvis/iris" BIN_DIR="${BIN_DIR:-/usr/local/bin}" +# allow overriding the GitHub API base URL for local testing +IRIS_API_URL="${IRIS_API_URL:-https://api.github.com}" main() { echo "Installing iris..." @@ -55,7 +57,7 @@ main() { echo "Installed iris to ${BIN_DIR}/iris" echo "" - if "${BIN_DIR}/iris" --version >/dev/null 2>&1; then + if "${BIN_DIR}/iris" version >/dev/null 2>&1; then echo "Installation verified." else echo "Warning: could not verify binary" @@ -89,24 +91,31 @@ get_download_url() { arch="$1" if command -v curl >/dev/null 2>&1; then - releases=$(curl -sL \ + http_response=$(curl -sL -w "\n%{http_code}" \ ${GITHUB_TOKEN:+-H "Authorization: Bearer ${GITHUB_TOKEN}"} \ - "https://api.github.com/repos/${REPO}/releases/latest") + "${IRIS_API_URL}/repos/${REPO}/releases/latest") + http_code=$(echo "${http_response}" | tail -1) + releases=$(echo "${http_response}" | head -n -1) elif command -v wget >/dev/null 2>&1; then releases=$(wget -qO- \ ${GITHUB_TOKEN:+--header "Authorization: Bearer ${GITHUB_TOKEN}"} \ - "https://api.github.com/repos/${REPO}/releases/latest") + "${IRIS_API_URL}/repos/${REPO}/releases/latest") + http_code="200" else err "curl or wget is required" fi - if echo "${releases}" | grep -q "rate limit"; then - err "GitHub API rate limited. Try again later or set GITHUB_TOKEN env variable" + if [ "${http_code}" = "404" ]; then + err "no releases found for ${REPO}. the project may not have published a release yet" fi - if echo "${releases}" | grep -q '"message"'; then + if [ "${http_code}" = "403" ] || echo "${releases}" | grep -q "rate limit"; then + err "GitHub API rate limited. try again later or set GITHUB_TOKEN env variable" + fi + + if [ "${http_code}" != "200" ]; then msg=$(echo "${releases}" | grep '"message"' | head -1 | cut -d '"' -f 4) - err "GitHub API error: ${msg}" + err "GitHub API error (HTTP ${http_code}): ${msg}" fi url=$(echo "${releases}" | grep "browser_download_url" | grep "${arch}" | head -1 | cut -d '"' -f 4) From d8013077caa534640700897ece0d613dd78ac283 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:05:44 +0700 Subject: [PATCH 04/10] fix(justfile): reload crash --- justfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/justfile b/justfile index 2864034..e28760d 100644 --- a/justfile +++ b/justfile @@ -19,9 +19,8 @@ run: [group('dev')] [linux, macos] reload: - # @GOAMD64=v3 go build -pgo=auto -ldflags="-s -w" -trimpath -o iris main.go @go build -o iris main.go - @if [ -n "${IRIS_PID:-}" ]; then kill -USR1 $IRIS_PID; fi + @if [ -n "${IRIS_PID:-}" ]; then kill -USR1 $IRIS_PID 2>/dev/null || true; fi @if [ -z "${IRIS_FD:-}" ]; then ./iris; fi # update pkg From 8ad579c1b0600854945c887d205121b1469edcd1 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:06:11 +0700 Subject: [PATCH 05/10] fix(lookup): cocurrent map writes --- commands/core/lookup.go | 26 +++++++++++++++++++------- commands/core/system.go | 10 ++++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/commands/core/lookup.go b/commands/core/lookup.go index 10853d8..69cef2c 100644 --- a/commands/core/lookup.go +++ b/commands/core/lookup.go @@ -2,13 +2,19 @@ package core import ( "strings" + "sync" "github.com/versenilvis/iris/integration/shell" ) -var ShellAliases = map[string]string{} +var ( + ShellAliases = map[string]string{} + shellAliasesMu sync.RWMutex +) func GetAlias(name string) (string, bool) { + shellAliasesMu.RLock() + defer shellAliasesMu.RUnlock() val, ok := ShellAliases[name] return val, ok } @@ -19,9 +25,16 @@ func GetAlias(name string) (string, bool) { // e.g. Lookup("git checkout ") -> suggests branch names via generator func Lookup(input string) []Suggestion { if shell.Current != nil { - ShellAliases = shell.Current.ScanAliases() + aliases := shell.Current.ScanAliases() + shellAliasesMu.Lock() + ShellAliases = aliases + shellAliasesMu.Unlock() } + shellAliasesMu.RLock() + aliases := ShellAliases + shellAliasesMu.RUnlock() + if input == "" { return nil } @@ -35,13 +48,12 @@ func Lookup(input string) []Suggestion { // in the future, maybe we will write unit test or using Lookup in another module // it will be safer because we maybe forget to check it in that module - pathCmds = make(map[string]bool) scanExternalCommands() // if you have an alias in your shell config like: alias gca="git commit -a" // if the first word match it, IRIS will suggest "git commit -a" if len(tokens) > 1 { - if target, ok := ShellAliases[tokens[0]]; ok { + if target, ok := aliases[tokens[0]]; ok { aliasTokens := Tokenize(target) if len(aliasTokens) > 0 && aliasTokens[len(aliasTokens)-1] == "" { aliasTokens = aliasTokens[:len(aliasTokens)-1] @@ -52,7 +64,7 @@ func Lookup(input string) []Suggestion { if len(tokens) == 1 { query := tokens[0] - results := topLevelSuggestions(query) + results := topLevelSuggestions(query, aliases) if spec, exists := Registry[query]; exists { hasTrailingSpace := query != "" && query[len(query)-1] == ' ' @@ -244,10 +256,10 @@ func Lookup(input string) []Suggestion { return results } -func topLevelSuggestions(query string) []Suggestion { +func topLevelSuggestions(query string, aliases map[string]string) []Suggestion { results, seen := []Suggestion{}, make(map[string]bool) - for name, target := range ShellAliases { + for name, target := range aliases { if !seen[name] && (query == "" || HasPrefix(name, query)) { results = append(results, Suggestion{ Cmd: target, Desc: "alias: " + name, Icon: "root", diff --git a/commands/core/system.go b/commands/core/system.go index ca847d9..b7e67de 100644 --- a/commands/core/system.go +++ b/commands/core/system.go @@ -3,13 +3,19 @@ package core import ( "os" "path/filepath" + "sync" ) -var pathCmds = make(map[string]bool) +var ( + pathCmds = make(map[string]bool) + pathCmdsOnce sync.Once +) // scanExternalCommands populates pathCmds with system executables func scanExternalCommands() { - scanPath() + pathCmdsOnce.Do(func() { + scanPath() + }) } // scanPath populates pathCmds with all executable files found in path environment variable From 370a3a2d9ff1d123083fe95e4a7302252889d59d Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:13:56 +0700 Subject: [PATCH 06/10] feat(test): test lookup concurrent --- tests/core/lookup_test.go | 41 ++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/tests/core/lookup_test.go b/tests/core/lookup_test.go index aa94526..80c0d8a 100644 --- a/tests/core/lookup_test.go +++ b/tests/core/lookup_test.go @@ -2,6 +2,7 @@ package tests import ( "strings" + "sync" "testing" "github.com/versenilvis/iris/commands/core" @@ -31,21 +32,13 @@ func TestLookup(t *testing.T) { minResults int mustContain string }{ - // REQUIREMENT: Token 1, no trailing space -> top-level suggestions {"Top-level", "gi", 1, "git"}, - // REQUIREMENT: Token 1, with trailing space -> subcommand suggestions {"Subcommand", "git ", 1, "git commit"}, - // REQUIREMENT: Alias expansion (gca -> git commit -a) {"Alias expansion", "gca", 1, "git commit -a"}, - // REQUIREMENT: Alias value with space (ta -> tmux a -t) {"Alias with space", "ta", 1, "tmux a -t"}, - // REQUIREMENT: Subcommand depth 2+ (git remote add) {"Deep subcommand", "git remote ", 1, "git remote add"}, - // REQUIREMENT: Option dedup (do not suggest --verbose if already typed) - {"Option dedup", "git --verbose -", 0, ""}, - // REQUIREMENT: --flag=value does not count into argCount + {"Option dedup", "git --verbose -", 0, ""}, {"Flag with value ignore", "git --output=json ", 2, "git --output=json commit"}, - // REQUIREMENT: Unknown root command -> nil {"Unknown root command", "unknowncmd ", 0, ""}, } @@ -70,3 +63,33 @@ func TestLookup(t *testing.T) { }) } } + +func TestLookupConcurrent(t *testing.T) { + core.Registry = make(map[string]*core.Spec) + core.Register(&core.Spec{ + Name: "git", + Subcommands: []core.Subcommand{ + {Name: "commit", Options: []core.Option{{Name: "--message"}}, MaxArgs: 1}, + }, + }) + + core.ShellAliases = map[string]string{ + "gca": "git commit -a", + } + + var wg sync.WaitGroup + const goroutines = 10 + const iterations = 50 + + for range goroutines { + wg.Add(1) + go func() { + defer wg.Done() + for range iterations { + _ = core.Lookup("gca") + _ = core.Lookup("git ") + } + }() + } + wg.Wait() +} From 3ba87b24822d7ac311d9ca5d4623af5aaf0ab495 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 22:14:05 +0700 Subject: [PATCH 07/10] chore(test): remove unneeded comments --- tests/core/filegen_test.go | 13 ++++++------- tests/core/utils_test.go | 11 ----------- tests/fs/zoxide_test.go | 6 +++--- tests/root/suggestions_test.go | 4 ++-- tests/shell/adapter_test.go | 6 +++--- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/tests/core/filegen_test.go b/tests/core/filegen_test.go index b9b238d..6ff2087 100644 --- a/tests/core/filegen_test.go +++ b/tests/core/filegen_test.go @@ -22,7 +22,7 @@ func TestFileGenerator(t *testing.T) { _ = os.Chdir(tmp) defer func() { _ = os.Chdir(oldWd) }() - // REQUIREMENT: dirOnly shows only dirs + t.Run("dirOnly shows only dirs", func(t *testing.T) { gen := core.FileGenerator("/") results := gen([]string{"cd", ""}, "cd ", "") @@ -33,7 +33,7 @@ func TestFileGenerator(t *testing.T) { } }) - // REQUIREMENT: Filter extension shows only matching files + t.Run("Filter extension", func(t *testing.T) { gen := core.FileGenerator(".go") results := gen([]string{"ls", ""}, "ls ", "") @@ -51,7 +51,7 @@ func TestFileGenerator(t *testing.T) { } }) - // REQUIREMENT: Nested path (src/mai -> correct dir + prefix) + t.Run("Nested path", func(t *testing.T) { gen := core.FileGenerator() results := gen([]string{"ls", "src/u"}, "ls src/u", "src/u") @@ -66,7 +66,7 @@ func TestFileGenerator(t *testing.T) { } }) - // REQUIREMENT: Deep scan 1 level finds files in subdir + t.Run("Deep scan 1 level", func(t *testing.T) { gen := core.FileGenerator() results := gen([]string{"ls", "src/"}, "ls src/", "src/") @@ -81,10 +81,9 @@ func TestFileGenerator(t *testing.T) { } }) - // REQUIREMENT: Deep scan does not go deeper than 1 level - // (This is implicitly tested by the logic in FileGenerator) - // REQUIREMENT: Hidden files are skipped + + t.Run("Hidden files are skipped", func(t *testing.T) { gen := core.FileGenerator() results := gen([]string{"ls", ""}, "ls ", "") diff --git a/tests/core/utils_test.go b/tests/core/utils_test.go index 90a6ebb..98e0def 100644 --- a/tests/core/utils_test.go +++ b/tests/core/utils_test.go @@ -13,19 +13,12 @@ func TestTokenize(t *testing.T) { input string expected []string }{ - // REQUIREMENT: Empty input {"Empty input", "", []string{""}}, - // REQUIREMENT: Trailing space("git " -> 2 tokens, the last token is "") {"Trailing space", "git ", []string{"git", ""}}, - // REQUIREMENT: Multi-space("git add") {"Multi-space", "git add", []string{"git", "add"}}, - // REQUIREMENT: Quoted string("git commit -m \"hello world\"") {"Quoted string", "git commit -m \"hello world\"", []string{"git", "commit", "-m", "hello world"}}, - // REQUIREMENT: Quote not closed {"Quote not closed", "git commit -m \"hello", []string{"git", "commit", "-m", "hello"}}, - // REQUIREMENT: Single quote vs double quote {"Single quote", "git commit -m 'hello world'", []string{"git", "commit", "-m", "hello world"}}, - // REQUIREMENT: Backslash escape {"Backslash escape", "git commit -m \"hello\\ world\"", []string{"git", "commit", "-m", "hello world"}}, } @@ -46,13 +39,9 @@ func TestHasPrefix(t *testing.T) { prefix string want bool }{ - // REQUIREMENT: Case insensitive match {"Case insensitive", "Hello", "hel", true}, - // REQUIREMENT: Unicode support (Vietnamese) {"Unicode support", "Thử nghiệm", "thử", true}, - // REQUIREMENT: Prefix longer than string -> false {"Prefix longer", "Iris", "Iris-Longer", false}, - // REQUIREMENT: Empty prefix -> true {"Empty prefix", "Iris", "", true}, } diff --git a/tests/fs/zoxide_test.go b/tests/fs/zoxide_test.go index 7ea781f..2e2fd75 100644 --- a/tests/fs/zoxide_test.go +++ b/tests/fs/zoxide_test.go @@ -25,7 +25,7 @@ func TestZoxideGenerator(t *testing.T) { gen := fs.ZoxideGenerator() - // REQUIREMENT: Query returns the correct result when partial = "" + t.Run("Query returns correct result when partial is empty", func(t *testing.T) { results := gen([]string{"z", ""}, "z ", "") if len(results) == 0 { @@ -33,7 +33,7 @@ func TestZoxideGenerator(t *testing.T) { } }) - // REQUIREMENT: Path replaces home dir with ~ + t.Run("Path replaces home dir with ~", func(t *testing.T) { home, _ := os.UserHomeDir() results := gen([]string{"z", ""}, "z ", "") @@ -49,7 +49,7 @@ func TestZoxideGenerator(t *testing.T) { } }) - // REQUIREMENT: Sort by descending score + t.Run("Sort by descending score", func(t *testing.T) { results := gen([]string{"z", "i"}, "z ", "i") if len(results) >= 1 { diff --git a/tests/root/suggestions_test.go b/tests/root/suggestions_test.go index 6fb73ba..c7b15fb 100644 --- a/tests/root/suggestions_test.go +++ b/tests/root/suggestions_test.go @@ -7,7 +7,7 @@ import ( ) func TestMergeResults(t *testing.T) { - // REQUIREMENT: Dedup exact match + t.Run("Dedup exact match", func(t *testing.T) { // Mock history items that might conflict with specs res := root.MergeResults("git", "spec") @@ -20,7 +20,7 @@ func TestMergeResults(t *testing.T) { } }) - // REQUIREMENT: Limit 100 + t.Run("Limit 100", func(t *testing.T) { res := root.MergeResults("a", "history") if len(res) > 100 { diff --git a/tests/shell/adapter_test.go b/tests/shell/adapter_test.go index 2ab3ab7..933ca53 100644 --- a/tests/shell/adapter_test.go +++ b/tests/shell/adapter_test.go @@ -8,7 +8,7 @@ import ( ) func TestScanPosixAliases(t *testing.T) { - // REQUIREMENT: Parse single alias, multi alias, comments, value with space + input := ` alias gca='git commit -a' alias ta="tmux a -t" # this is a comment @@ -34,10 +34,10 @@ func TestSplitAliasTokens(t *testing.T) { input string expected []string }{ - // REQUIREMENT: Parse multi alias on one line + {"Single", "a='b'", []string{"a='b'"}}, {"Multi", "a='b' c=\"d\"", []string{"a='b'", "c=\"d\""}}, - // REQUIREMENT: Value with space in quote + {"With Space", "ta='tmux a -t' l='ls -l'", []string{"ta='tmux a -t'", "l='ls -l'"}}, } From c727b7f46f4ab8bbb756e5075580f0648d7bf4f9 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 23:17:48 +0700 Subject: [PATCH 08/10] chore(gitignore): personal task list --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 13f159c..e961b81 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ iris iris.log autocomplete temp -docs/guide.md \ No newline at end of file +docs/guide.md +task \ No newline at end of file From 07a16afa1958c5113e794ab8ec13a07c774fae78 Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 23:18:23 +0700 Subject: [PATCH 09/10] fix(installer): ensure portability across different Unix-like systems --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 642fd08..b72e0f9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -95,7 +95,7 @@ get_download_url() { ${GITHUB_TOKEN:+-H "Authorization: Bearer ${GITHUB_TOKEN}"} \ "${IRIS_API_URL}/repos/${REPO}/releases/latest") http_code=$(echo "${http_response}" | tail -1) - releases=$(echo "${http_response}" | head -n -1) + releases=$(echo "${http_response}" | sed '$d') elif command -v wget >/dev/null 2>&1; then releases=$(wget -qO- \ ${GITHUB_TOKEN:+--header "Authorization: Bearer ${GITHUB_TOKEN}"} \ From a58d95917cdbd87f9be9b19fc2860ac8ed8fcf6e Mon Sep 17 00:00:00 2001 From: verse91 Date: Sat, 23 May 2026 23:20:39 +0700 Subject: [PATCH 10/10] fix(installer): handle wget errors and extract HTTP status code --- scripts/install.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index b72e0f9..6840619 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -97,10 +97,13 @@ get_download_url() { http_code=$(echo "${http_response}" | tail -1) releases=$(echo "${http_response}" | sed '$d') elif command -v wget >/dev/null 2>&1; then - releases=$(wget -qO- \ + tmp_headers=$(mktemp) + releases=$(wget -S -qO- \ ${GITHUB_TOKEN:+--header "Authorization: Bearer ${GITHUB_TOKEN}"} \ - "${IRIS_API_URL}/repos/${REPO}/releases/latest") - http_code="200" + "${IRIS_API_URL}/repos/${REPO}/releases/latest" 2>"$tmp_headers" || true) + http_code=$(grep "HTTP/" "$tmp_headers" | tail -1 | sed -e 's/^[[:space:]]*//' | cut -d' ' -f2) + [ -z "${http_code}" ] && http_code="000" + rm -f "$tmp_headers" else err "curl or wget is required" fi