Files
claude-desktop-debian/scripts/verify-patches.sh
Aaddrick 9df8b88e3a verify(cowork): static-grep shipped asar for PR #555 markers (#559) (#575)
* verify(cowork): static-grep shipped asar for PR #555 markers

D6 of #559's followup plan: post-build check that greps the shipped
app.asar for 9 known cowork patch markers and exits non-zero if any
are missing. Catches the half-patched-asar failure mode from PR #555,
where two of three failed gates had no else branch and the build log
showed "Applied 10 cowork patches" instead of warning.

- scripts/cowork-patch-markers.tsv: single source of truth.
  Tab-separated name<TAB>pcre<TAB>sample. Both verify and BATS read it.
- scripts/verify-cowork-patches.sh: accepts a .js, an .asar (npx
  @electron/asar extract), or a directory containing
  app.asar.contents/.vite/build/index.js. Exits 0/1/2.
- tests/verify-cowork-patches.bats: regex-matches-sample integrity,
  positive full fixture, per-marker negative fixtures, input-shape
  coverage. 9 new BATS cases.
- .github/workflows/build-amd64.yml: runs verify against the deb
  build's asar. Pinned to deb because the patched JS is identical
  across formats.

Validated end-to-end against the pinned 1.5354.0 installer:
unpatched -> 9/9 miss; cowork.sh patched -> all 9 present.

Refs #559.

Co-Authored-By: Claude <claude@anthropic.com>

* verify(cowork): share TSV parser between verify.sh and BATS

Realises the library-mode plumbing the previous commit added but
didn't use: BATS now sources verify-cowork-patches.sh and calls
load_markers, so a TSV format change cannot desync the two consumers.
Drops the duplicate parser in tests/verify-cowork-patches.bats.

Also tightens main()'s loop (for over indexed while, drop redundant
missing counter) and the BATS index loops.

Behaviour-preserving; bats tests/verify-cowork-patches.bats still 9/9.

Co-Authored-By: Claude <claude@anthropic.com>

* rename: verify-cowork-patches → verify-patches (generic)

Rename the verify infra to make its generic intent explicit. Per
sabiut's review note on #575, the script + TSV are reusable for
non-cowork patch sets in principle — drop "cowork" from the script
and BATS filenames to reflect that, and accept an optional second
arg for the marker TSV path so other patch sets can plug their own
TSV in without forking the script.

The TSV itself stays cowork-specific (`cowork-patch-markers.tsv`)
because its contents are cowork markers; the script defaults to it
so existing CI keeps working without changes beyond the rename.

Routing implication noted by sabiut: filename now lives under
`/tests/` → @sabiut codeowner mapping (intentionally; the verify
infra is generic). Cowork-specific marker changes still touch the
TSV under `/scripts/`, which routes to @aaddrick/@RayCharlizard via
the cowork-* CODEOWNERS rule.

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-05-05 07:25:22 -04:00

201 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# verify-patches.sh
#
# Static-greps a patched index.js for the patch markers defined in
# a TSV (defaults to scripts/cowork-patch-markers.tsv). Exits non-zero
# on any miss and names the missing markers in the output.
#
# Defends against silent half-patched asars (issue #559 D6, PR #555).
# Reusable for non-cowork patch sets — pass any TSV of the same shape
# via the second arg.
#
# Usage:
# verify-patches.sh <path> [markers-tsv]
#
# <path> may be:
# * a JavaScript file (the index.js itself)
# * an .asar archive (extracted on the fly via npx @electron/asar)
# * a directory containing app.asar.contents/.vite/build/index.js
#
# Exit codes:
# 0 — every marker present.
# 1 — usage error or input not found.
# 2 — one or more markers missing (named on stderr).
#
set -u
IFS=$'\n\t'
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
default_markers_tsv="$script_dir/cowork-patch-markers.tsv"
markers_tsv="$default_markers_tsv"
usage() {
cat <<-EOF >&2
Usage: $(basename "$0") <path> [markers-tsv]
<path> may be a .js file, an .asar archive, or a directory
containing app.asar.contents/.vite/build/index.js. The script
greps for patch markers (default: cowork, PR #555 / issue #559
D6) and exits non-zero if any are missing.
[markers-tsv] overrides the default TSV so the same script can
verify other patch sets.
EOF
}
# Parse the marker TSV into three parallel arrays. Skips comments
# and blank lines. Used by both the verify path here and by the
# BATS test, which sources this script (see _is_sourced below) to
# share parsing and avoid drift between the two consumers.
load_markers() {
marker_names=()
marker_patterns=()
marker_samples=()
if [[ ! -f $markers_tsv ]]; then
echo "verify-patches: marker file not found:" \
"$markers_tsv" >&2
return 1
fi
local name pattern sample
while IFS=$'\t' read -r name pattern sample; do
[[ -z $name || $name == '#'* ]] && continue
if [[ -z ${pattern:-} || -z ${sample:-} ]]; then
echo "verify-patches: malformed row '$name'" \
'in markers file' >&2
return 1
fi
marker_names+=("$name")
marker_patterns+=("$pattern")
marker_samples+=("$sample")
done < "$markers_tsv"
if [[ ${#marker_names[@]} -eq 0 ]]; then
echo 'verify-patches: no markers loaded' >&2
return 1
fi
}
# Resolve the input path to an actual index.js. For .asar inputs,
# extracts to a temp dir and echoes the inner index.js path. The
# caller cleans up via cleanup_tmp.
tmp_extract_dir=''
cleanup_tmp() {
if [[ -n $tmp_extract_dir && -d $tmp_extract_dir ]]; then
rm -rf "$tmp_extract_dir"
fi
}
trap cleanup_tmp EXIT
resolve_index_js() {
local input="$1"
if [[ ! -e $input ]]; then
echo "verify-patches: not found: $input" >&2
return 1
fi
if [[ -d $input ]]; then
local candidate="$input/app.asar.contents/.vite/build/index.js"
if [[ -f $candidate ]]; then
printf '%s\n' "$candidate"
return 0
fi
echo "verify-patches: directory does not contain" \
"app.asar.contents/.vite/build/index.js: $input" >&2
return 1
fi
if [[ $input == *.asar ]]; then
if ! command -v npx > /dev/null 2>&1; then
echo 'verify-patches: npx not found; install Node.js' \
'or pre-extract the asar' >&2
return 1
fi
tmp_extract_dir="$(mktemp -d)"
if ! npx --yes @electron/asar extract "$input" \
"$tmp_extract_dir" > /dev/null 2>&1; then
echo "verify-patches: asar extraction failed:" \
"$input" >&2
return 1
fi
local extracted="$tmp_extract_dir/.vite/build/index.js"
if [[ ! -f $extracted ]]; then
echo 'verify-patches: extracted asar lacks' \
'.vite/build/index.js' >&2
return 1
fi
printf '%s\n' "$extracted"
return 0
fi
# Treat as a JS file (.js or any other extension) — let grep
# decide whether the contents are sensible.
printf '%s\n' "$input"
}
main() {
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage
return 1
fi
case "$1" in
-h | --help)
usage
return 0
;;
esac
if [[ $# -eq 2 ]]; then
markers_tsv="$2"
fi
local index_js
if ! index_js="$(resolve_index_js "$1")"; then
return 1
fi
if ! load_markers; then
return 1
fi
echo "Verifying patch markers in: $index_js"
echo "Marker source: $markers_tsv"
local i missing_names=()
for i in "${!marker_names[@]}"; do
if grep -qP -- "${marker_patterns[$i]}" "$index_js"; then
printf ' OK %s\n' "${marker_names[$i]}"
else
printf ' MISS %s\n' "${marker_names[$i]}" >&2
missing_names+=("${marker_names[$i]}")
fi
done
if [[ ${#missing_names[@]} -gt 0 ]]; then
local joined
joined="$(IFS=','; printf '%s' "${missing_names[*]}")"
printf '\nverify-patches: %d/%d markers missing: %s\n' \
"${#missing_names[@]}" "${#marker_names[@]}" "$joined" >&2
return 2
fi
printf '\nAll %d patch markers present.\n' \
"${#marker_names[@]}"
return 0
}
# Library mode: when sourced (BATS test), expose load_markers and
# the markers_tsv path without running main.
_is_sourced() {
[[ ${BASH_SOURCE[0]} != "${0}" ]]
}
if ! _is_sourced; then
main "$@"
fi