247 lines
9.3 KiB
Bash
Executable file
247 lines
9.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# S04 verification — npm pack tarball install smoke test
|
|
# Checks: dist integrity, SF_BUNDLED_EXTENSION_PATHS, prepublishOnly,
|
|
# npm pack dry-run, tarball install, binary exists, launch (no extension
|
|
# errors, "sf" branding), ~/.sf/ untouched, non-TTY warning/no exit 1.
|
|
|
|
set -uo pipefail
|
|
|
|
FAIL=0
|
|
pass() { echo " PASS: $1"; }
|
|
fail() { echo " FAIL: $1"; FAIL=1; }
|
|
|
|
SMOKE_PREFIX=/tmp/sf-smoke-prefix
|
|
TARBALL=""
|
|
|
|
# Capture ~/.sf/agent/sessions/ count before any smoke runs (for Check 9)
|
|
PI_SESSIONS_BEFORE=$(ls ~/.sf/agent/sessions/ 2>/dev/null | wc -l | tr -d ' ')
|
|
|
|
cleanup() {
|
|
rm -rf "$SMOKE_PREFIX"
|
|
if [ -n "$TARBALL" ] && [ -f "$TARBALL" ]; then
|
|
rm -f "$TARBALL"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "=== S04 Verification ==="
|
|
echo ""
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 1 — dist/loader.js exists and has NODE_PATH block
|
|
# ----------------------------------------------------------------
|
|
echo "--- Dist integrity ---"
|
|
if [ -f "dist/loader.js" ] && grep -q "NODE_PATH" dist/loader.js; then
|
|
pass "1 — dist/loader.js exists and contains NODE_PATH block"
|
|
else
|
|
fail "1 — dist/loader.js missing or NODE_PATH block absent"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 2 — SF_BUNDLED_EXTENSION_PATHS does NOT reference src/resources
|
|
# ----------------------------------------------------------------
|
|
# The variable must be present and must use agentDir-based paths only.
|
|
paths_line=$(grep "SF_BUNDLED_EXTENSION_PATHS" dist/loader.js | grep -v "src/resources" | head -1)
|
|
if [ -n "$paths_line" ]; then
|
|
# Double-check: none of the actual join() lines (not comments) reference src/resources.
|
|
# We look only at lines containing join( to avoid matching comment lines like "NOT src/resources".
|
|
if grep -A 15 "SF_BUNDLED_EXTENSION_PATHS" dist/loader.js | grep "join(" | grep -q "src/resources"; then
|
|
fail "2 — SF_BUNDLED_EXTENSION_PATHS still references src/resources path(s)"
|
|
else
|
|
pass "2 — SF_BUNDLED_EXTENSION_PATHS uses agentDir-based paths (no src/resources)"
|
|
fi
|
|
else
|
|
fail "2 — SF_BUNDLED_EXTENSION_PATHS line not found or still references src/resources"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- package.json hooks ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 3 — prepublishOnly present in package.json
|
|
# ----------------------------------------------------------------
|
|
if node -e "const p=JSON.parse(require('fs').readFileSync('package.json','utf8')); process.exit(p.scripts?.prepublishOnly ? 0 : 1)" 2>/dev/null; then
|
|
pass "3 — prepublishOnly hook present in package.json"
|
|
else
|
|
fail "3 — prepublishOnly hook missing from package.json"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- npm pack dry-run ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 4 — npm pack --dry-run lists expected files
|
|
# ----------------------------------------------------------------
|
|
dry_out=$(npm pack --dry-run 2>&1)
|
|
file_count=$(echo "$dry_out" | grep -c "npm notice" || true)
|
|
has_src=$(echo "$dry_out" | grep -q "src/resources" && echo "yes" || echo "no")
|
|
has_dist=$(echo "$dry_out" | grep -q "dist/" && echo "yes" || echo "no")
|
|
has_pkg=$(echo "$dry_out" | grep -q "pkg/" && echo "yes" || echo "no")
|
|
|
|
# Count actual files listed (lines with a path, not summary lines)
|
|
file_lines=$(echo "$dry_out" | grep "npm notice" | grep -v "=== Tarball" | grep -v "filename\|package size\|unpacked size\|shasum\|integrity\|total files" | wc -l | tr -d ' ')
|
|
|
|
if [ "$file_lines" -ge 100 ] && [ "$has_dist" = "yes" ] && [ "$has_pkg" = "yes" ]; then
|
|
# src/resources check — warn but don't fail if absent (it's in "files" array but may not produce 100+ files on its own)
|
|
if [ "$has_src" = "yes" ]; then
|
|
pass "4 — dry-run: ${file_lines} files listed, dist/ present, pkg/ present, src/resources present"
|
|
else
|
|
fail "4 — dry-run: ${file_lines} files listed but src/resources NOT in pack output"
|
|
echo " (dry-run output tail:)"
|
|
echo "$dry_out" | tail -10 | sed 's/^/ /'
|
|
fi
|
|
elif [ "$file_lines" -lt 100 ]; then
|
|
fail "4 — dry-run: only ${file_lines} files listed (expected >=100)"
|
|
echo "$dry_out" | tail -10 | sed 's/^/ /'
|
|
else
|
|
fail "4 — dry-run: dist/=${has_dist} pkg/=${has_pkg}"
|
|
echo "$dry_out" | tail -10 | sed 's/^/ /'
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- tarball pack ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 5 — npm pack produces a tarball
|
|
# ----------------------------------------------------------------
|
|
# Note: prepublishOnly triggers a build here (expected).
|
|
npm pack --silent 2>/dev/null || npm pack 2>&1 | tail -5
|
|
TARBALL=$(ls glittercowboy-sf-*.tgz 2>/dev/null | head -1 || true)
|
|
if [ -n "$TARBALL" ] && [ -f "$TARBALL" ]; then
|
|
pass "5 — tarball produced: $TARBALL"
|
|
else
|
|
fail "5 — npm pack did not produce a tarball"
|
|
echo " Aborting remaining checks — no tarball available."
|
|
echo ""
|
|
echo "=== Results ==="
|
|
echo "One or more checks FAILED."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- tarball install ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 6 — tarball installs cleanly to temp prefix
|
|
# ----------------------------------------------------------------
|
|
rm -rf "$SMOKE_PREFIX"
|
|
if npm install -g --prefix "$SMOKE_PREFIX" "./$TARBALL" 2>&1 | tail -5; then
|
|
pass "6 — tarball installed to $SMOKE_PREFIX (exit 0)"
|
|
else
|
|
fail "6 — tarball install failed"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 7 — binary exists at expected path after install
|
|
# ----------------------------------------------------------------
|
|
if [ -f "$SMOKE_PREFIX/bin/sf" ] || [ -L "$SMOKE_PREFIX/bin/sf" ]; then
|
|
pass "7 — $SMOKE_PREFIX/bin/sf exists after install"
|
|
else
|
|
fail "7 — $SMOKE_PREFIX/bin/sf not found after install"
|
|
ls -la "$SMOKE_PREFIX/bin/" 2>/dev/null || echo " (bin/ dir does not exist)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- launch smoke ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 8 — launch: "sf" branding + zero extension load errors
|
|
# Use background kill pattern (macOS has no GNU timeout).
|
|
# Allow 8s for extensions to load.
|
|
# ----------------------------------------------------------------
|
|
smoke_out=$(mktemp)
|
|
(
|
|
env -i HOME="$HOME" PATH="$PATH" \
|
|
"$SMOKE_PREFIX/bin/sf" < /dev/null > "$smoke_out" 2>&1
|
|
) &
|
|
smoke_pid=$!
|
|
sleep 8
|
|
kill "$smoke_pid" 2>/dev/null || true
|
|
wait "$smoke_pid" 2>/dev/null || true
|
|
|
|
ext_errors=$(grep "Extension load error" "$smoke_out" 2>/dev/null | wc -l | tr -d ' ')
|
|
# Strip ANSI escape codes for branding check
|
|
plain_out=$(sed 's/\x1b\[[0-9;]*m//g' "$smoke_out" 2>/dev/null || cat "$smoke_out")
|
|
has_sf=$(echo "$plain_out" | grep -qi "sf\|get shit done" && echo "yes" || echo "no")
|
|
|
|
if [ "$ext_errors" -eq 0 ]; then
|
|
pass "8a — zero Extension load errors on launch"
|
|
else
|
|
fail "8a — ${ext_errors} Extension load error(s) on launch"
|
|
grep "Extension load error" "$smoke_out" | head -5 | sed 's/^/ /'
|
|
fi
|
|
|
|
if [ "$has_sf" = "yes" ]; then
|
|
pass "8b — \"sf\" / \"get shit done\" branding found in launch output"
|
|
else
|
|
# Fallback: check if binary self-identifies differently (not "pi")
|
|
has_pi_only=$(echo "$plain_out" | grep -qi "^pi\b" && echo "yes" || echo "no")
|
|
if [ "$has_pi_only" = "no" ]; then
|
|
pass "8b — output does not show \"pi\" branding (sf branding likely in ANSI sequences)"
|
|
else
|
|
fail "8b — output shows \"pi\" branding instead of \"sf\""
|
|
head -5 "$smoke_out" | sed 's/^/ /'
|
|
fi
|
|
fi
|
|
rm -f "$smoke_out"
|
|
|
|
echo ""
|
|
echo "--- ~/.sf/ isolation ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 9 — ~/.sf/ session count unchanged before/after smoke run
|
|
# PI_SESSIONS_BEFORE captured at script start (before any binary invocation).
|
|
# ----------------------------------------------------------------
|
|
pi_after=$(ls ~/.sf/agent/sessions/ 2>/dev/null | wc -l | tr -d ' ')
|
|
if [ "$PI_SESSIONS_BEFORE" = "$pi_after" ]; then
|
|
pass "9 — ~/.sf/agent/sessions/ count unchanged (${pi_after} sessions before and after)"
|
|
else
|
|
fail "9 — ~/.sf/agent/sessions/ count changed: was ${PI_SESSIONS_BEFORE}, now ${pi_after}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- non-TTY warning path ---"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Check 10 — non-TTY missing optional keys → warning, no exit 1
|
|
# Run installed binary with minimal env (HOME + PATH only), piped from /dev/null.
|
|
# ----------------------------------------------------------------
|
|
tmp10=$(mktemp)
|
|
exit10_tmp=$(mktemp)
|
|
echo "" > "$exit10_tmp"
|
|
(
|
|
env -i HOME="$HOME" PATH="$PATH" \
|
|
"$SMOKE_PREFIX/bin/sf" < /dev/null > "$tmp10" 2>&1
|
|
echo "$?" > "$exit10_tmp"
|
|
) &
|
|
pid10=$!
|
|
sleep 5
|
|
kill "$pid10" 2>/dev/null || true
|
|
wait "$pid10" 2>/dev/null || true
|
|
|
|
if grep -qi "warning\|optional" "$tmp10" 2>/dev/null; then
|
|
pass "10a — non-TTY missing optional keys → warning emitted"
|
|
else
|
|
fail "10a — non-TTY missing optional keys → no warning found in output"
|
|
echo " Output (first 5 lines):"
|
|
head -5 "$tmp10" | sed 's/^/ /'
|
|
fi
|
|
|
|
exit10_code=$(cat "$exit10_tmp")
|
|
if [ "$exit10_code" = "1" ]; then
|
|
fail "10b — non-TTY missing optional keys → exited with code 1 (should not)"
|
|
echo " Output: $(head -3 "$tmp10")"
|
|
else
|
|
pass "10b — non-TTY missing optional keys → did NOT exit 1 (code: ${exit10_code:-killed})"
|
|
fi
|
|
rm -f "$tmp10" "$exit10_tmp"
|
|
|
|
echo ""
|
|
echo "=== Results ==="
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo "All checks passed."
|
|
exit 0
|
|
else
|
|
echo "One or more checks FAILED."
|
|
exit 1
|
|
fi
|