Skip to content
Merged
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ iris
iris.log
autocomplete
temp
docs/guide.md
docs/guide.md
task
26 changes: 19 additions & 7 deletions commands/core/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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]
Expand All @@ -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] == ' '
Expand Down Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions commands/core/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
178 changes: 171 additions & 7 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# NOTE: RUN "just -l" TO QUICKLY VIEW ALL COMMANDS

# build binary file
[group('dev')]
[group('build')]
build:
@go build -o iris main.go
@go build -o iris main.go

# build with optimized binary file
[group('build')]
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Targeting GOAMD64=v4 for the 'optimized' build significantly limits hardware compatibility, as it requires AVX-512 support which is absent in many modern consumer CPUs. This may lead to SIGILL (Illegal Instruction) crashes for users running the binary on older or mid-range hardware. v3 (AVX2) is generally considered the standard target for optimized x86_64 builds as it covers most CPUs from the last decade.

    @GOAMD64=v3 go build -ldflags="-s -w" -trimpath -o iris main.go


# run iris
[group('dev')]
Expand All @@ -15,16 +20,16 @@ run:
[linux, macos]
reload:
@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
[group('dev')]
pkg:
@go mod tidy

# debugger
[group('dev')]
# iris debugger
[group('debug')]
debug:
@./iris --debug

Expand All @@ -44,7 +49,7 @@ alias ana := analyze
analyze:
@go run scripts/test_analyzer.go

# run linter
# run linter
[group('dev')]
lint:
@golangci-lint run ./...
Expand Down Expand Up @@ -75,3 +80,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
30 changes: 21 additions & 9 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -89,24 +91,34 @@ 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}" | 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}"} \
"https://api.github.com/repos/${REPO}/releases/latest")
"${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

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)
Expand Down
Loading
Loading