mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 00:26:21 +03:00
* 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>
This commit is contained in:
23
.github/workflows/build-amd64.yml
vendored
23
.github/workflows/build-amd64.yml
vendored
@@ -49,6 +49,29 @@ jobs:
|
||||
fi
|
||||
./build.sh ${{ inputs.build_flags }} $TAG_FLAG
|
||||
|
||||
# Static-grep the shipped asar for the cowork patch markers
|
||||
# defined in scripts/cowork-patch-markers.tsv (issue #559 D6,
|
||||
# PR #555). Pinned to amd64-deb because the patched JS is
|
||||
# identical across formats, so one verification per CI run is
|
||||
# sufficient — no need to duplicate across the matrix.
|
||||
- name: Verify cowork patches in shipped asar
|
||||
if: inputs.artifact_suffix == 'deb'
|
||||
run: |
|
||||
deb_file=$(find . -maxdepth 1 -name 'claude-desktop_*amd64.deb' \
|
||||
-print -quit)
|
||||
if [[ -z "$deb_file" ]]; then
|
||||
echo "verify-patches: no .deb artifact found" >&2
|
||||
exit 1
|
||||
fi
|
||||
extract_dir=$(mktemp -d)
|
||||
dpkg-deb -x "$deb_file" "$extract_dir"
|
||||
asar_path=$(find "$extract_dir" -name app.asar -print -quit)
|
||||
if [[ -z "$asar_path" ]]; then
|
||||
echo "verify-patches: app.asar not found in deb" >&2
|
||||
exit 1
|
||||
fi
|
||||
./scripts/verify-patches.sh "$asar_path"
|
||||
|
||||
- name: Upload AMD64 Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
|
||||
@@ -269,8 +269,11 @@ Four layers: build log, syntactic validity, asar markers, runtime.
|
||||
```
|
||||
|
||||
3. Static-grep the shipped asar for the 9 cowork markers from PR
|
||||
#555. A `verify-cowork-patches.sh` helper that automates this is
|
||||
planned (issue #559 D6); prefer it once it lands.
|
||||
#555. `scripts/verify-patches.sh` automates this (issue #559 D6)
|
||||
and runs in CI on every `amd64-deb` build via the
|
||||
`Verify cowork patches in shipped asar` step in
|
||||
`.github/workflows/build-amd64.yml`. Reusable for non-cowork patch
|
||||
sets — pass any same-shape TSV as the second arg.
|
||||
|
||||
4. Launch the AppImage and check runtime state:
|
||||
|
||||
|
||||
29
scripts/cowork-patch-markers.tsv
Normal file
29
scripts/cowork-patch-markers.tsv
Normal file
@@ -0,0 +1,29 @@
|
||||
# Cowork patch markers — single source of truth.
|
||||
#
|
||||
# Format:
|
||||
# <name><TAB><pcre_pattern><TAB><sample>
|
||||
# Lines starting with '#' and blank lines are ignored.
|
||||
#
|
||||
# Each row names a post-patch fingerprint of patch_cowork_linux() in
|
||||
# scripts/patches/cowork.sh. Both verify-patches.sh and
|
||||
# tests/verify-patches.bats consume this file, so adding a marker
|
||||
# here adds it to the runtime check and the test matrix at the same
|
||||
# time.
|
||||
#
|
||||
# Columns:
|
||||
# name — kebab-case id; surfaces in verify output and BATS names.
|
||||
# pattern — PCRE matched against the shipped index.js by `grep -P`.
|
||||
# sample — concrete string the pattern matches; BATS uses it to
|
||||
# build positive and per-marker negative fixtures.
|
||||
#
|
||||
# The 9 markers below correspond 1:1 with the smoke-test set defined
|
||||
# in issue #559 (PR #555 retrofit, deliverable D6).
|
||||
vmclient-log-gate process\.platform==="linux"\)\s*\?\s*"vmClient \(TypeScript\)" (F||process.platform==="linux")?"vmClient (TypeScript)"
|
||||
vm-assignment-linux-gate process\.platform==="linux"\)\?\(?[\w$]+=\{vm:[\w$]+\} (F||process.platform==="linux")?N={vm:M}
|
||||
unix-socket-path process\.platform==="linux"\?\(process\.env\.XDG_RUNTIME_DIR\|\|"/tmp"\)\+"/cowork-vm-service\.sock" process.platform==="linux"?(process.env.XDG_RUNTIME_DIR||"/tmp")+"/cowork-vm-service.sock"
|
||||
empty-linux-bundle-manifest linux:\{x64:\[\],arm64:\[\]\} ,linux:{x64:[],arm64:[]}
|
||||
getdownloadstatus-suppression getDownloadStatus\(\)\{return process\.platform==="linux"\?[\w$]+\.NotDownloaded getDownloadStatus(){return process.platform==="linux"?Z.NotDownloaded
|
||||
econnrefused-on-linux process\.platform==="linux"&&[\w$]+\.code==="ECONNREFUSED" (n.code==="ENOENT"||process.platform==="linux"&&n.code==="ECONNREFUSED")
|
||||
cowork-daemon-pid global\.__coworkDaemonPid global.__coworkDaemonPid=_c.pid
|
||||
cowork-linux-daemon-shutdown cowork-linux-daemon-shutdown name:"cowork-linux-daemon-shutdown"
|
||||
sharedcwdpath-threadthrough sharedCwdPath:this\.sessions\.get\( sharedCwdPath:this.sessions.get(t)?.userSelectedFolders?.[0]
|
||||
|
Can't render this file because it contains an unexpected character in line 21 and column 39.
|
200
scripts/verify-patches.sh
Executable file
200
scripts/verify-patches.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/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
|
||||
163
tests/verify-patches.bats
Normal file
163
tests/verify-patches.bats
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# verify-patches.bats
|
||||
# Tests for scripts/verify-patches.sh — the post-build static grep
|
||||
# that confirms patch markers (default: cowork, issue #559 D6 / PR
|
||||
# #555) are present in the shipped index.js.
|
||||
#
|
||||
# Both these tests and the verify script consume the marker list from
|
||||
# scripts/cowork-patch-markers.tsv, so adding a marker there
|
||||
# automatically expands the test matrix below.
|
||||
#
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)"
|
||||
VERIFY_SH="$SCRIPT_DIR/../scripts/verify-patches.sh"
|
||||
|
||||
setup() {
|
||||
TEST_TMP=$(mktemp -d)
|
||||
export TEST_TMP
|
||||
|
||||
# Source the verify script in library mode and reuse its
|
||||
# parser, so a TSV format change can't desync the two consumers.
|
||||
# shellcheck source-path=SCRIPTDIR/.. source=scripts/verify-patches.sh
|
||||
source "$VERIFY_SH"
|
||||
load_markers
|
||||
}
|
||||
|
||||
teardown() {
|
||||
if [[ -n "${TEST_TMP:-}" && -d "$TEST_TMP" ]]; then
|
||||
rm -rf "$TEST_TMP"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build a fixture index.js containing every sample. If $1 is given,
|
||||
# the marker with that name is omitted (used to drive the missing-
|
||||
# marker negative tests).
|
||||
write_fixture() {
|
||||
local omit="${1:-}"
|
||||
local fixture="$TEST_TMP/index.js"
|
||||
: > "$fixture"
|
||||
local i
|
||||
for i in "${!marker_names[@]}"; do
|
||||
if [[ ${marker_names[$i]} != "$omit" ]]; then
|
||||
printf '%s\n' "${marker_samples[$i]}" >> "$fixture"
|
||||
fi
|
||||
done
|
||||
printf '%s\n' "$fixture"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Marker file integrity
|
||||
# =============================================================================
|
||||
|
||||
@test "markers file: every regex matches its sample" {
|
||||
local i
|
||||
for i in "${!marker_names[@]}"; do
|
||||
run grep -qP -- "${marker_patterns[$i]}" \
|
||||
<(printf '%s\n' "${marker_samples[$i]}")
|
||||
[[ "$status" -eq 0 ]] || {
|
||||
echo "regex did not match own sample: ${marker_names[$i]}"
|
||||
echo "pattern: ${marker_patterns[$i]}"
|
||||
echo "sample: ${marker_samples[$i]}"
|
||||
return 1
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
@test "markers file: at least 9 markers loaded" {
|
||||
[[ "${#marker_names[@]}" -ge 9 ]] || {
|
||||
echo "expected >= 9 markers, got ${#marker_names[@]}"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Positive path: full fixture passes
|
||||
# =============================================================================
|
||||
|
||||
@test "verify: exits 0 when every marker present" {
|
||||
local fixture
|
||||
fixture="$(write_fixture)"
|
||||
|
||||
run "$VERIFY_SH" "$fixture"
|
||||
[[ "$status" -eq 0 ]] || {
|
||||
echo 'verify rejected a fully-marked fixture'
|
||||
echo "$output"
|
||||
return 1
|
||||
}
|
||||
|
||||
run grep -c 'OK ' <<< "$output"
|
||||
[[ "$output" -eq "${#marker_names[@]}" ]] || {
|
||||
echo "expected ${#marker_names[@]} OK lines, got: $output"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Negative path: per-marker missing fixture
|
||||
# =============================================================================
|
||||
|
||||
@test "verify: exits 2 and names the missing marker (each)" {
|
||||
local name fixture failures=0
|
||||
for name in "${marker_names[@]}"; do
|
||||
fixture="$(write_fixture "$name")"
|
||||
|
||||
run "$VERIFY_SH" "$fixture"
|
||||
if [[ "$status" -ne 2 ]]; then
|
||||
echo "missing $name should exit 2, got $status"
|
||||
echo "$output"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
if ! grep -q "$name" <<< "$output"; then
|
||||
echo "missing $name not named in output"
|
||||
echo "$output"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
[[ "$failures" -eq 0 ]]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Input shapes
|
||||
# =============================================================================
|
||||
|
||||
@test "verify: accepts a directory containing the asar layout" {
|
||||
local layout="$TEST_TMP/staging/app.asar.contents/.vite/build"
|
||||
mkdir -p "$layout"
|
||||
: > "$layout/index.js"
|
||||
local sample
|
||||
for sample in "${marker_samples[@]}"; do
|
||||
printf '%s\n' "$sample" >> "$layout/index.js"
|
||||
done
|
||||
|
||||
run "$VERIFY_SH" "$TEST_TMP/staging"
|
||||
[[ "$status" -eq 0 ]] || {
|
||||
echo 'verify rejected directory-shaped input'
|
||||
echo "$output"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@test "verify: rejects missing path with exit 1" {
|
||||
run "$VERIFY_SH" "$TEST_TMP/does-not-exist.js"
|
||||
[[ "$status" -eq 1 ]]
|
||||
[[ "$output" == *'not found'* ]]
|
||||
}
|
||||
|
||||
@test "verify: rejects directory without expected layout" {
|
||||
mkdir -p "$TEST_TMP/empty"
|
||||
run "$VERIFY_SH" "$TEST_TMP/empty"
|
||||
[[ "$status" -eq 1 ]]
|
||||
}
|
||||
|
||||
@test "verify: prints usage on no args and exits 1" {
|
||||
run "$VERIFY_SH"
|
||||
[[ "$status" -eq 1 ]]
|
||||
[[ "$output" == *'Usage:'* ]]
|
||||
}
|
||||
|
||||
@test "verify: --help prints usage and exits 0" {
|
||||
run "$VERIFY_SH" --help
|
||||
[[ "$status" -eq 0 ]]
|
||||
[[ "$output" == *'Usage:'* ]]
|
||||
}
|
||||
Reference in New Issue
Block a user