The search extension is compiled, and the stage that compiles it checked for npm and skipped quietly when it found none. On this machine it always found one -- but only because Claude Desktop depends on nodejs and dragged a system npm in. Nowhere else would. Node moved to nvm when the shell config turned out to have been assuming it for months, and nvm is a shell function in a file only an interactive shell sources; a stage is not one. So a fresh install would have set up the launcher, printed one line about extensions not being built, and left somebody wondering why typing in it suggested nothing. The stage sources nvm before looking, and the contract pins that it does -- checking for npm is not the same as being able to find it. Claude-Session: https://claude.ai/code/session_01Q84axqUE5inJhf5Jz9CFy1
128 lines
6.4 KiB
Bash
Executable File
128 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Searching the web from the launcher, in both the forms Panama ships it.
|
|
#
|
|
# There are two, deliberately: a script command that needs nothing but bash, and
|
|
# an extension that adds live suggestions but has to be compiled. That is also
|
|
# the risk this pins -- the search engine is written down twice, and two copies
|
|
# of a URL drift.
|
|
#
|
|
# `vicinae script check` is the validator for the first, and it exits 0 even when
|
|
# it rejects a file. Checking its exit status would pass on a script Vicinae
|
|
# refuses to load, so its output is what counts.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
script="$repo_dir/config/local/share/vicinae/scripts/search-web"
|
|
extension="$repo_dir/config/local/share/vicinae/extensions/panama-search"
|
|
stage="$repo_dir/setup/scripts/link-vicinae-scripts"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
# ── The script command ───────────────────────────────────────────────────────
|
|
|
|
if [[ ! -x "$script" ]]; then
|
|
note 'search-web is missing or not executable, so Vicinae will not load it'
|
|
else
|
|
if command -v vicinae >/dev/null 2>&1; then
|
|
output="$(vicinae script check "$script" 2>&1)"
|
|
[[ "$output" == *Error* ]] && note "Vicinae rejects search-web: $output"
|
|
fi
|
|
|
|
# Exactly one argument. Vicinae only offers a no-view command as a fallback
|
|
# -- type anything, get "Search" at the bottom -- when it takes a single
|
|
# text argument, which is the whole point of shipping this one.
|
|
arguments="$(grep -c '@vicinae.argument' "$script" || true)"
|
|
(( arguments == 1 )) \
|
|
|| note "search-web declares $arguments arguments; a fallback command takes exactly 1"
|
|
|
|
grep -q '"percentEncoded": true' "$script" \
|
|
|| note 'the argument is not percent-encoded, so a query containing & or # is truncated'
|
|
|
|
grep -qE '@vicinae\.mode silent' "$script" \
|
|
|| note 'search-web is not a silent command, so it would render a view it has nothing to put in'
|
|
|
|
# The default browser is a setting this desktop already owns. Naming a
|
|
# browser here would quietly outrank the Applications page.
|
|
grep -q 'xdg-open' "$script" \
|
|
|| note 'search-web does not open through xdg-open, so it ignores the default browser'
|
|
grep -qE '\b(helium|firefox|chromium|google-chrome)\b' "$script" \
|
|
&& note 'search-web names a specific browser instead of following the default'
|
|
fi
|
|
|
|
# ── The extension ────────────────────────────────────────────────────────────
|
|
|
|
manifest="$extension/package.json"
|
|
if [[ ! -f "$manifest" ]]; then
|
|
note 'the panama-search extension has no manifest'
|
|
else
|
|
if command -v jq >/dev/null 2>&1; then
|
|
jq -e . "$manifest" >/dev/null 2>&1 || note 'the extension manifest is not valid JSON'
|
|
|
|
for field in name title description commands; do
|
|
jq -e "has(\"$field\")" "$manifest" >/dev/null 2>&1 \
|
|
|| note "the extension manifest has no \"$field\", which Vicinae requires"
|
|
done
|
|
|
|
# A command's name is its source file. Getting this wrong builds an
|
|
# extension with a command that cannot be opened.
|
|
while read -r command_name; do
|
|
[[ -n "$command_name" ]] || continue
|
|
[[ -f "$extension/src/$command_name.tsx" || -f "$extension/src/$command_name.ts" ]] \
|
|
|| note "the manifest declares command \"$command_name\" with no matching file in src/"
|
|
done < <(jq -r '.commands[]?.name // empty' "$manifest" 2>/dev/null)
|
|
fi
|
|
fi
|
|
|
|
# ── One engine, written twice ────────────────────────────────────────────────
|
|
#
|
|
# The script command and the extension both have to know where a search goes.
|
|
# Nothing makes them agree, so this does: searching from the fallback and
|
|
# searching from the suggestions list must not reach different places.
|
|
|
|
engine_in_script="$(grep -oE 'https://[^"]+\?q=' "$script" 2>/dev/null | head -1)"
|
|
engine_in_extension="$(grep -oE 'https://[^"]+\?q=' "$extension/src/search.tsx" 2>/dev/null | head -1)"
|
|
|
|
if [[ -z "$engine_in_script" ]]; then
|
|
note 'no search engine URL found in search-web'
|
|
elif [[ -z "$engine_in_extension" ]]; then
|
|
note 'no search engine URL found in the extension'
|
|
elif [[ "$engine_in_script" != "$engine_in_extension" ]]; then
|
|
note "the script searches $engine_in_script but the extension searches $engine_in_extension"
|
|
fi
|
|
|
|
# ── Provisioning ─────────────────────────────────────────────────────────────
|
|
#
|
|
# An extension is compiled, so unlike a script command it cannot simply be
|
|
# linked. If nothing builds it, it ships as source nobody can run.
|
|
|
|
grep -q 'npm run build' "$stage" \
|
|
|| note 'no stage builds the Vicinae extensions, so they never reach the launcher'
|
|
grep -q 'command -v npm' "$stage" \
|
|
|| note 'the extension build does not check for npm, so a machine without it fails the stage'
|
|
|
|
# Checking for npm is not enough. Node comes from nvm, whose script only an
|
|
# interactive shell sources -- and a stage is not one, so npm is absent unless
|
|
# the stage goes and gets it. This machine hid that: a system npm existed only
|
|
# because Claude Desktop depends on nodejs. Without this line, a fresh install
|
|
# builds no extension and says so in one line nobody reads.
|
|
grep -q '/etc/profile.d/nvm.sh' "$stage" \
|
|
|| note 'the extension build never sources nvm, so npm is missing on any machine without a system node'
|
|
|
|
# node_modules is a dependency tree, not configuration.
|
|
git -C "$repo_dir" check-ignore -q "$extension/node_modules" 2>/dev/null \
|
|
|| note 'the extension node_modules is not gitignored'
|
|
|
|
# ── Report ───────────────────────────────────────────────────────────────────
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
|
printf 'launcher search contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'launcher search contract: PASS\n'
|