Skip to content
Merged
33 changes: 16 additions & 17 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

jobs:
test:
name: Unit tests
name: Test · Format · Lint · Dead code
runs-on: ubuntu-latest
permissions:
pull-requests: write
Expand Down Expand Up @@ -36,22 +36,6 @@
lcov-file: coverage/lcov.info
continue-on-error: true

quality:
name: Format · Lint · Dead code
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Check formatting (oxfmt)
run: bun run format:check

Expand All @@ -60,3 +44,18 @@

- name: Dead code detection (knip)
run: bun run knip

test-bats:
name: Shell tests (bats)
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install bats-core
# bats is not pre-installed on GitHub-hosted Ubuntu runners.
run: npm install -g bats

- name: Run install.sh shell tests
run: bats install.test.bats
Comment thread Fixed
Comment thread Fixed
37 changes: 37 additions & 0 deletions github-code-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { buildOutput } from "./src/output.ts";
import { groupByTeamPrefix, flattenTeamSections } from "./src/group.ts";
import { checkForUpdate } from "./src/upgrade.ts";
import { runInteractive } from "./src/tui.ts";
import { generateCompletion, detectShell } from "./src/completions.ts";
import type { OutputFormat, OutputType } from "./src/types.ts";

// Version + build metadata injected at compile time via --define (see build.ts).
Expand Down Expand Up @@ -349,6 +350,42 @@ program
process.stdout.write(`error: ${e instanceof Error ? e.message : String(e)}\n`);
process.exit(1);
}
const { refreshCompletions } = await import("./src/upgrade.ts");
const refreshedPath = await refreshCompletions(detectShell(), undefined, opts.debug);
if (refreshedPath) {
process.stdout.write(`✓ Shell completions refreshed at ${refreshedPath}\n`);
Comment thread
shouze marked this conversation as resolved.
Outdated
}
process.exit(0);
});

// `completions` subcommand — print a shell completion script to stdout
program
.command("completions")
.description("Print a shell completion script for bash, zsh or fish")
.configureHelp(helpFormatConfig)
.addHelpText(
"after",
helpSection("Documentation:", "https://fulll.github.io/github-code-search/usage/upgrade"),
Comment thread
shouze marked this conversation as resolved.
)
Comment thread
shouze marked this conversation as resolved.
.option(
"--shell <shell>",
["Target shell: bash, zsh or fish.", "Auto-detected from $SHELL when omitted."].join("\n"),
)
.action((opts: { shell?: string }) => {
const shell = opts.shell ?? detectShell();
if (!shell) {
writeFileSync(
2,
`error: could not detect shell. Use --shell bash|zsh|fish to specify it explicitly.\n`,
);
process.exit(1);
}
try {
writeFileSync(1, generateCompletion(shell) + "\n");
} catch (e: unknown) {
writeFileSync(2, `error: ${e instanceof Error ? e.message : String(e)}\n`);
process.exit(1);
}
process.exit(0);
});

Expand Down
173 changes: 114 additions & 59 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,69 +10,124 @@

set -euo pipefail

REPO="fulll/github-code-search"
BINARY_NAME="github-code-search"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
GITHUB_API="https://api.github.com"

# ─── Detect OS ────────────────────────────────────────────────────────────────

case "$(uname -s)" in
Linux*) OS="linux" ;;
Darwin*) OS="macos" ;;
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
*)
echo "error: unsupported OS: $(uname -s)" >&2
exit 1
;;
esac

# ─── Detect architecture ──────────────────────────────────────────────────────

MACHINE="$(uname -m)"
case "$MACHINE" in
x86_64|amd64) ARCH="x64" ;;
arm64|aarch64) ARCH="arm64" ;;
*)
echo "error: unsupported architecture: $MACHINE" >&2
exit 1
;;
esac

ARTIFACT="${BINARY_NAME}-${OS}-${ARCH}"
[ "$OS" = "windows" ] && ARTIFACT="${ARTIFACT}.exe"

# ─── Resolve version ──────────────────────────────────────────────────────────

if [ -n "${VERSION:-}" ]; then
TAG="$VERSION"
else
echo "Detecting latest release…"
TAG=$(curl -fsSL "${GITHUB_API}/repos/${REPO}/releases/latest" \
| grep '"tag_name"' \
| sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')
fi

echo "Installing ${BINARY_NAME} ${TAG} (${OS}/${ARCH})…"
# ─── Shell completions ────────────────────────────────────────────────────────
# Defined early so it can be sourced and tested independently (INSTALL_SH_TEST=1).

# ─── Download ─────────────────────────────────────────────────────────────────
install_completions() {
local bin="$1"
local shell_name
shell_name="$(basename "${SHELL:-}")"

DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${ARTIFACT}"
TMP="$(mktemp)"
curl -fsSL --progress-bar -o "$TMP" "$DOWNLOAD_URL"
chmod +x "$TMP"
case "$shell_name" in
fish)
local comp_dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish/completions"
local comp_file="${comp_dir}/github-code-search.fish"
mkdir -p "$comp_dir"
"$bin" completions --shell fish > "$comp_file"
echo ""
echo "✓ Fish completions installed at ${comp_file}"
;;
zsh)
local comp_dir="${ZDOTDIR:-$HOME}/.zfunc"
local comp_file="${comp_dir}/_github-code-search"
mkdir -p "$comp_dir"
"$bin" completions --shell zsh > "$comp_file"
echo ""
echo "✓ Zsh completions installed at ${comp_file}"
echo " Make sure ${comp_dir} is on your fpath. Add to ~/.zshrc if needed:"
echo " fpath=(${comp_dir} \$fpath)"
echo " autoload -Uz compinit && compinit"
;;
bash)
local comp_dir="${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions"
local comp_file="${comp_dir}/github-code-search"
mkdir -p "$comp_dir"
"$bin" completions --shell bash > "$comp_file"
echo ""
echo "✓ Bash completions installed at ${comp_file}"
;;
*)
echo ""
echo " Shell completions are available for bash, zsh and fish."
echo " Run the following to generate your completion script:"
echo " ${BINARY_NAME} completions --shell <bash|zsh|fish>"
;;
esac
}

# ─── Install ──────────────────────────────────────────────────────────────────
# ─── Main installation ────────────────────────────────────────────────────────
# Skipped when sourced for testing (export INSTALL_SH_TEST=1 before sourcing).

DEST="${INSTALL_DIR}/${BINARY_NAME}"
if [ -w "$INSTALL_DIR" ]; then
mv "$TMP" "$DEST"
else
echo " (sudo required for ${INSTALL_DIR})"
sudo mv "$TMP" "$DEST"
fi
if [ -z "${INSTALL_SH_TEST:-}" ]; then

REPO="fulll/github-code-search"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
GITHUB_API="https://api.github.com"

# ─── Detect OS ──────────────────────────────────────────────────────────────

case "$(uname -s)" in
Linux*) OS="linux" ;;
Darwin*) OS="macos" ;;
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
*)
echo "error: unsupported OS: $(uname -s)" >&2
exit 1
;;
esac

# ─── Detect architecture ────────────────────────────────────────────────────

MACHINE="$(uname -m)"
case "$MACHINE" in
x86_64|amd64) ARCH="x64" ;;
arm64|aarch64) ARCH="arm64" ;;
*)
echo "error: unsupported architecture: $MACHINE" >&2
exit 1
;;
esac

ARTIFACT="${BINARY_NAME}-${OS}-${ARCH}"
[ "$OS" = "windows" ] && ARTIFACT="${ARTIFACT}.exe"

# ─── Resolve version ────────────────────────────────────────────────────────

echo "✓ ${BINARY_NAME} ${TAG} installed at ${DEST}"
echo ""
echo " Remember to export your GitHub token:"
echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx"
if [ -n "${VERSION:-}" ]; then
TAG="$VERSION"
else
echo "Detecting latest release…"
TAG=$(curl -fsSL "${GITHUB_API}/repos/${REPO}/releases/latest" \
| grep '"tag_name"' \
| sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')
fi

echo "Installing ${BINARY_NAME} ${TAG} (${OS}/${ARCH})…"

# ─── Download ───────────────────────────────────────────────────────────────

DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${ARTIFACT}"
TMP="$(mktemp)"
curl -fsSL --progress-bar -o "$TMP" "$DOWNLOAD_URL"
chmod +x "$TMP"

# ─── Install ────────────────────────────────────────────────────────────────

DEST="${INSTALL_DIR}/${BINARY_NAME}"
if [ -w "$INSTALL_DIR" ]; then
mv "$TMP" "$DEST"
else
echo " (sudo required for ${INSTALL_DIR})"
sudo mv "$TMP" "$DEST"
fi

echo "✓ ${BINARY_NAME} ${TAG} installed at ${DEST}"
echo ""
echo " Remember to export your GitHub token:"
echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx"

install_completions "$DEST"

Comment thread
shouze marked this conversation as resolved.
fi
Loading
Loading