Skip to content

Commit 3f52dd8

Browse files
committed
feat(validate-perps): introduce perps validation script for multiproject checks
- Added `scripts/perps-validate.sh` to automate the validation process for Core changes in client checkouts (Mobile and Extension). - Enhanced documentation in `skill.md` with detailed usage instructions and error handling guidelines. - Emphasized the importance of pre-state awareness and yalc resolution in the validation workflow.
1 parent 8867048 commit 3f52dd8

2 files changed

Lines changed: 402 additions & 50 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
#!/usr/bin/env bash
2+
#
3+
# perps-validate.sh — deterministic helper for validating a Core
4+
# @metamask/perps-controller change inside a client checkout (Mobile or
5+
# Extension) via yalc.
6+
#
7+
# Direction is always: a CLIENT validates a CORE controller change.
8+
# owner = the Core checkout that holds the perps-controller change
9+
# client = the Mobile/Extension checkout that consumes it
10+
#
11+
# Run subcommands in this order:
12+
# 1. prestate <client-dir> [client-dir...] # snapshot before touching anything
13+
# 2. build <core-dir> [--full] # build the package (freshness gate)
14+
# 3. push <core-dir> <client-dir> [...] # yalc publish + push into clients
15+
# 4. verify <client-dir> # confirm version + new symbols landed
16+
# 5. restore <client-dir> [client-dir...] # put the client back to its snapshot
17+
#
18+
# Helper subcommands:
19+
# resolve-yalc # print the resolved yalc invocation
20+
# doctor <core-dir> <client-dir> # quick environment sanity check
21+
#
22+
# Design goals:
23+
# - No assumption about the Node manager (asdf / nvm / volta / brew / none).
24+
# - No hardcoded paths. Everything is derived or passed in.
25+
# - Pre-state aware: if a client was ALREADY on a yalc link, restore brings
26+
# that exact link back instead of nuking the user's dev setup.
27+
#
28+
# Env overrides:
29+
# YALC_BIN explicit yalc invocation (e.g. "/opt/homebrew/bin/yalc"
30+
# or "node /path/to/yalc/src/yalc.js"). Skips auto-resolution.
31+
# PKG package name (default: @metamask/perps-controller)
32+
# STATE_DIR where snapshots live (default: <client>/tmp/.perps-validate)
33+
34+
set -euo pipefail
35+
36+
PKG="${PKG:-@metamask/perps-controller}"
37+
# perps-controller lives at packages/<leaf> in the Core monorepo.
38+
PKG_LEAF="${PKG##*/}" # perps-controller
39+
PKG_SCOPE="${PKG%/*}" # @metamask
40+
41+
# ---------------------------------------------------------------------------
42+
# yalc resolution — the single most fragile thing across machines.
43+
#
44+
# A version-manager shim (notably asdf) can SUCCEED with exit 0 yet do nothing,
45+
# printing "No version is set for command yalc". So we never trust exit code
46+
# alone: a working yalc must print a semver to stdout. If the shim is broken we
47+
# fall back to locating yalc's own yalc.js and running it through a real node.
48+
# ---------------------------------------------------------------------------
49+
_looks_like_version() { printf '%s' "$1" | grep -Eq '^[0-9]+\.[0-9]+'; }
50+
51+
resolve_yalc() {
52+
if [ -n "${YALC_BIN:-}" ]; then printf '%s' "$YALC_BIN"; return 0; fi
53+
54+
# 1. plain yalc on PATH, but only if it actually reports a version.
55+
# Use `command yalc` so we hit the real binary, never the run_yalc wrapper.
56+
if command -v yalc >/dev/null 2>&1; then
57+
local v; v="$(command yalc --version 2>/dev/null || true)"
58+
if _looks_like_version "$v"; then printf 'yalc'; return 0; fi
59+
fi
60+
61+
# 2. a real node to run yalc.js with (any working node is fine).
62+
local node_bin; node_bin="$(command -v node || true)"
63+
[ -z "$node_bin" ] && { echo "ERROR: no node on PATH to run yalc" >&2; return 1; }
64+
65+
# 3. hunt for yalc's entrypoint across the common install layouts.
66+
local cand
67+
for cand in \
68+
"$(npm root -g 2>/dev/null)/yalc/src/yalc.js" \
69+
"$(npm root -g 2>/dev/null)/yalc/yalc.js" \
70+
"$HOME"/.asdf/installs/nodejs/*/lib/node_modules/yalc/src/yalc.js \
71+
"$HOME"/.nvm/versions/node/*/lib/node_modules/yalc/src/yalc.js \
72+
"$HOME"/.volta/tools/image/packages/yalc/lib/node_modules/yalc/src/yalc.js \
73+
/opt/homebrew/lib/node_modules/yalc/src/yalc.js \
74+
/usr/local/lib/node_modules/yalc/src/yalc.js ; do
75+
[ -f "$cand" ] && { printf '%s %s' "$node_bin" "$cand"; return 0; }
76+
done
77+
78+
echo "ERROR: could not resolve yalc. Install it (npm i -g yalc) or set YALC_BIN." >&2
79+
return 1
80+
}
81+
82+
# Run yalc regardless of how it resolved (binary or "node yalc.js").
83+
# NOT named `yalc` on purpose — a function named `yalc` would shadow the real
84+
# binary and make resolve_yalc recurse forever.
85+
run_yalc() { local y; y="$(resolve_yalc)" || return 1; eval "$y \"\$@\""; }
86+
87+
# ---------------------------------------------------------------------------
88+
state_dir() { printf '%s/tmp/.perps-validate' "$1"; }
89+
90+
# ===========================================================================
91+
cmd_resolve_yalc() {
92+
local y; y="$(resolve_yalc)" || exit 1
93+
echo "yalc => $y"
94+
eval "$y --version"
95+
}
96+
97+
# ---------------------------------------------------------------------------
98+
cmd_prestate() {
99+
[ "$#" -ge 1 ] || { echo "usage: prestate <client-dir> [client-dir...]" >&2; exit 2; }
100+
for client in "$@"; do
101+
client="$(cd "$client" && pwd)"
102+
local sd; sd="$(state_dir "$client")"; mkdir -p "$sd"
103+
echo "=== prestate: $client ==="
104+
git -C "$client" status --short --branch | tee "$sd/git-status.txt" >/dev/null
105+
cp "$client/package.json" "$sd/package.json.bak" 2>/dev/null || true
106+
cp "$client/yalc.lock" "$sd/yalc.lock.bak" 2>/dev/null || true
107+
108+
local linkdir="$client/.yalc/$PKG"
109+
if [ -d "$linkdir" ]; then
110+
# Client was ALREADY on a yalc link — back it up byte-for-byte so restore
111+
# reproduces the exact pre-existing dev setup, not a clean registry state.
112+
echo "mode=PREEXISTING_YALC" > "$sd/mode"
113+
tar -czf "$sd/yalc-pkg.tgz" -C "$client/.yalc/$PKG_SCOPE" "$PKG_LEAF"
114+
cat "$linkdir/yalc.sig" 2>/dev/null > "$sd/yalc.sig" || true
115+
echo " was already yalc-linked: version=$(node -p "require('$linkdir/package.json').version" 2>/dev/null) sig=$(cat "$sd/yalc.sig" 2>/dev/null)"
116+
else
117+
echo "mode=REGISTRY" > "$sd/mode"
118+
echo " no prior yalc link (registry baseline)"
119+
fi
120+
echo " snapshot -> $sd"
121+
done
122+
}
123+
124+
# ---------------------------------------------------------------------------
125+
# Build the controller package. The package CANNOT build standalone in a fresh
126+
# Core checkout: its referenced packages have no dist yet and tsc fails with
127+
# TS6305. That is expected — the supported fix is a full monorepo build first.
128+
cmd_build() {
129+
local core="${1:?usage: build <core-dir> [--full]}"; shift || true
130+
local full=0; [ "${1:-}" = "--full" ] && full=1
131+
core="$(cd "$core" && pwd)"
132+
local log="$core/tmp/perps-build.log"; mkdir -p "$core/tmp"
133+
134+
if [ "$full" -eq 1 ]; then
135+
echo "=== full monorepo build (nice) — builds all referenced dists ==="
136+
( cd "$core" && nice -n 10 yarn build ) 2>&1 | tee "$log"
137+
else
138+
echo "=== package build: yarn workspace $PKG build ==="
139+
if ! ( cd "$core" && yarn workspace "$PKG" build ) 2>&1 | tee "$log"; then
140+
:
141+
fi
142+
if grep -q "TS6305" "$log"; then
143+
echo ""
144+
echo "BLOCKED: TS6305 — referenced package dists are missing (fresh checkout)."
145+
echo "Do NOT use 'workspaces foreach -R' (cycle can delete dist)."
146+
echo "Re-run with --full to build the whole monorepo first:"
147+
echo " perps-validate.sh build $core --full"
148+
exit 3
149+
fi
150+
fi
151+
152+
# Freshness gate: prove the built dist actually carries this change.
153+
local dist="$core/packages/$PKG_LEAF/dist"
154+
[ -f "$dist/index.cjs" ] || { echo "ERROR: no dist/index.cjs produced" >&2; exit 3; }
155+
echo ""
156+
echo "built version: $(node -p "require('$core/packages/$PKG_LEAF/package.json').version")"
157+
echo "dist OK -> $dist"
158+
}
159+
160+
# ---------------------------------------------------------------------------
161+
cmd_push() {
162+
local core="${1:?usage: push <core-dir> <client-dir> [client-dir...]}"; shift
163+
[ "$#" -ge 1 ] || { echo "need at least one client dir" >&2; exit 2; }
164+
core="$(cd "$core" && pwd)"
165+
local pkgdir="$core/packages/$PKG_LEAF"
166+
167+
echo "=== yalc publish $PKG from $pkgdir ==="
168+
( cd "$pkgdir" && run_yalc publish --private )
169+
170+
for client in "$@"; do
171+
client="$(cd "$client" && pwd)"
172+
echo "=== push into client: $client ==="
173+
if [ -d "$client/.yalc/$PKG" ] || grep -q "$PKG" "$client/yalc.lock" 2>/dev/null; then
174+
( cd "$client" && run_yalc update "$PKG" ) # advance an existing link
175+
else
176+
( cd "$client" && run_yalc add "$PKG" && yarn install --mode=skip-build )
177+
fi
178+
done
179+
}
180+
181+
# ---------------------------------------------------------------------------
182+
cmd_verify() {
183+
local client="${1:?usage: verify <client-dir>}"
184+
client="$(cd "$client" && pwd)"
185+
local inst="$client/node_modules/$PKG"
186+
echo "=== verify $PKG in $client ==="
187+
echo "installed version: $(node -p "require('$inst/package.json').version" 2>/dev/null || echo MISSING)"
188+
echo "yalc link version: $(node -p "require('$client/.yalc/$PKG/package.json').version" 2>/dev/null || echo none)"
189+
echo "yalc sig: $(cat "$client/.yalc/$PKG/yalc.sig" 2>/dev/null || echo none)"
190+
echo "--- next: run the client's own proof (type-check + the affected tests) ---"
191+
}
192+
193+
# ---------------------------------------------------------------------------
194+
# Restore is pre-state aware. PREEXISTING_YALC clients get their exact link
195+
# back; REGISTRY clients are fully un-yalc'd.
196+
cmd_restore() {
197+
[ "$#" -ge 1 ] || { echo "usage: restore <client-dir> [client-dir...]" >&2; exit 2; }
198+
for client in "$@"; do
199+
client="$(cd "$client" && pwd)"
200+
local sd; sd="$(state_dir "$client")"
201+
local mode; mode="$(cat "$sd/mode" 2>/dev/null | cut -d= -f2 || echo UNKNOWN)"
202+
echo "=== restore: $client (mode=$mode) ==="
203+
case "$mode" in
204+
PREEXISTING_YALC)
205+
rm -rf "$client/.yalc/$PKG"
206+
tar -xzf "$sd/yalc-pkg.tgz" -C "$client/.yalc/$PKG_SCOPE"
207+
cp "$sd/yalc.lock.bak" "$client/yalc.lock" 2>/dev/null || true
208+
cp "$sd/package.json.bak" "$client/package.json" 2>/dev/null || true
209+
echo " restored prior link: sig now=$(cat "$client/.yalc/$PKG/yalc.sig" 2>/dev/null) expected=$(cat "$sd/yalc.sig" 2>/dev/null)"
210+
echo " run 'yarn install --mode=skip-build' if node_modules needs to match"
211+
;;
212+
REGISTRY)
213+
( cd "$client" && run_yalc remove "$PKG" || true )
214+
git -C "$client" checkout -- package.json yarn.lock 2>/dev/null || true
215+
rm -rf "$client/.yalc" "$client/yalc.lock"
216+
echo " un-yalc'd; package.json/yarn.lock reverted"
217+
;;
218+
*)
219+
echo " no snapshot found ($sd) — nothing to restore. Run prestate next time." ;;
220+
esac
221+
git -C "$client" status --short --branch | sed 's/^/ /'
222+
done
223+
}
224+
225+
# ---------------------------------------------------------------------------
226+
cmd_doctor() {
227+
local core="${1:?usage: doctor <core-dir> <client-dir>}"; local client="${2:?need client dir}"
228+
echo "=== doctor ==="
229+
echo "node: $(command -v node) $(node -v 2>/dev/null)"
230+
echo -n "yalc: "; cmd_resolve_yalc || true
231+
echo "core: $core ($(git -C "$core" rev-parse --abbrev-ref HEAD 2>/dev/null))"
232+
echo "client: $client ($(git -C "$client" rev-parse --abbrev-ref HEAD 2>/dev/null))"
233+
echo "package: $PKG ($core/packages/$PKG_LEAF $( [ -d "$core/packages/$PKG_LEAF" ] && echo found || echo MISSING ))"
234+
echo "client links pkg already: $( [ -d "$client/.yalc/$PKG" ] && echo yes || echo no )"
235+
}
236+
237+
# ===========================================================================
238+
sub="${1:-}"; shift || true
239+
case "$sub" in
240+
resolve-yalc) cmd_resolve_yalc "$@" ;;
241+
prestate) cmd_prestate "$@" ;;
242+
build) cmd_build "$@" ;;
243+
push) cmd_push "$@" ;;
244+
verify) cmd_verify "$@" ;;
245+
restore) cmd_restore "$@" ;;
246+
doctor) cmd_doctor "$@" ;;
247+
*) cat >&2 <<EOF
248+
perps-validate.sh — validate a Core perps-controller change in a client via yalc
249+
250+
Usage:
251+
perps-validate.sh prestate <client-dir> [client-dir...]
252+
perps-validate.sh build <core-dir> [--full]
253+
perps-validate.sh push <core-dir> <client-dir> [client-dir...]
254+
perps-validate.sh verify <client-dir>
255+
perps-validate.sh restore <client-dir> [client-dir...]
256+
perps-validate.sh resolve-yalc
257+
perps-validate.sh doctor <core-dir> <client-dir>
258+
259+
Env: YALC_BIN, PKG (default @metamask/perps-controller), STATE_DIR
260+
EOF
261+
exit 2 ;;
262+
esac

0 commit comments

Comments
 (0)