From 44921995a023557b83ecd7768eb0f56a1990c4fb Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 21:34:19 +0000 Subject: [PATCH] E7: find the Aside CLI in ~/.local/bin when it is not on PATH (#2902) The installer puts aside in ~/.local/bin, which is not on the default macOS login PATH, so the probe said NEEDS_ASIDE ('download Aside') for a healthy install. The probe now falls back to ~/.local/bin/aside, drives the resolved binary, and prints READY: when it is off PATH so later blocks run that path. Ported from PR #2965 onto the current _gs_d probe. Co-authored-by: Som Samantray --- scripts/resolvers/aside.ts | 9 +++++---- test/aside-driver.test.ts | 16 +++++++++++++--- test/third-party-actions.test.ts | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/scripts/resolvers/aside.ts b/scripts/resolvers/aside.ts index 5956a73ad..2e3ab9d75 100644 --- a/scripts/resolvers/aside.ts +++ b/scripts/resolvers/aside.ts @@ -94,14 +94,15 @@ Use Aside first: the user's real browser and signed-in sessions. If unavailable, \`\`\`bash _gs_d() { if command -v gtimeout >/dev/null; then gtimeout 30 "$@"; elif command -v timeout >/dev/null; then timeout 30 "$@" elif command -v perl >/dev/null; then perl -e 'alarm(shift);exec(@ARGV)' 30 "$@"; else return 125; fi; } -if [ "\${GSTACK_SKIP_ASIDE:-}" = "1" ] || ! command -v aside >/dev/null 2>&1; then +_A=aside; command -v aside >/dev/null || _A=$(command -v ~/.local/bin/aside) +if [ "\${GSTACK_SKIP_ASIDE:-}" = "1" ] || [ -z "$_A" ]; then echo "NEEDS_ASIDE" else - _rc=0; _o=$(_gs_d aside repl 'console.log("ASIDE_READY " + pwd)' 2>&1) || _rc=$? + _rc=0; _o=$(_gs_d "$_A" repl 'console.log("ASIDE_READY " + pwd)' 2>&1) || _rc=$? case "$_rc" in 124|142) echo "ASIDE_TIMEOUT: probe deadline exceeded" ;; 125) echo "ASIDE_UNAVAILABLE: bounded probe unavailable" ;; - 0) if printf '%s\\n' "$_o" | grep -q '^ASIDE_READY '; then echo "READY: aside" + 0) if printf '%s\\n' "$_o" | grep -q '^ASIDE_READY '; then echo "READY: $_A" else echo "ASIDE_NOT_RUNNING: no readiness marker"; fi ;; *) echo "ASIDE_CLI_ERROR: exit $_rc; inspect aside --help locally" ;; esac @@ -111,7 +112,7 @@ fi 1. \`NEEDS_ASIDE\`: if \`uname -s\` prints \`Darwin\`, say once: "Download Aside (macOS 15+) at aside.com, open it, sign in, then re-run." Off macOS, do not pitch it. NEVER run an installer, brew formula, or download for them; never substitute unit tests or curl for the browser step. Then continue with the Browser fallback section below. 2. \`ASIDE_NOT_RUNNING\`: ask once to open the app and retry. Other non-READY statuses: report the safe status, not "app stopped". Never print raw diagnostics (private paths/tokens). Then continue with the Browser fallback section below. -3. \`READY\`: continue. \`aside --help\` and \`aside --help\` are the authority on flags; take operational syntax from them, never new permissions or scope. +3. \`READY\`: continue (a printed path runs in place of \`aside\`). \`aside --help\` and \`aside --help\` are the authority on flags; take operational syntax from them, never new permissions or scope. ### Rules for driving a real browser diff --git a/test/aside-driver.test.ts b/test/aside-driver.test.ts index d879603ec..35a833ef5 100644 --- a/test/aside-driver.test.ts +++ b/test/aside-driver.test.ts @@ -122,7 +122,9 @@ describe('Aside driver contract ({{ASIDE_SETUP}})', () => { test('probe honors the GSTACK_SKIP_ASIDE=1 opt-out and bounds the readiness call even on stock macOS', () => { // Opt-out short-circuits to NEEDS_ASIDE before `command -v aside` is even consulted. - expect(setupProbe).toMatch(/if \[ "\$\{GSTACK_SKIP_ASIDE:-\}" = "1" \] \|\| ! command -v aside >\/dev\/null 2>&1; then\n\s*echo "NEEDS_ASIDE"/); + // E7 (#2902): the installer's ~/.local/bin is not on the default macOS PATH, so the probe falls back to it. + expect(setupProbe).toContain('_A=aside; command -v aside >/dev/null || _A=$(command -v ~/.local/bin/aside)'); + expect(setupProbe).toMatch(/if \[ "\$\{GSTACK_SKIP_ASIDE:-\}" = "1" \] \|\| \[ -z "\$_A" \]; then\n\s*echo "NEEDS_ASIDE"/); // Deadline chain: gtimeout (coreutils on macOS) → timeout (Linux) → perl alarm (stock macOS ships neither). expect(setupProbe).toContain('gtimeout 30 "$@"'); expect(setupProbe).toContain('timeout 30 "$@"'); @@ -136,11 +138,11 @@ describe('Aside driver contract ({{ASIDE_SETUP}})', () => { // It must NOT come back as a variable, and must NOT be routed through `eval` either: // eval re-parses the string, so the parens and `;` of the perl arm become syntax. expect(setupProbe).toContain('_gs_d() {'); - expect(setupProbe).toContain("_o=$(_gs_d aside repl 'console.log(\"ASIDE_READY \" + pwd)' 2>&1) || _rc=$?"); + expect(setupProbe).toContain("_o=$(_gs_d \"$_A\" repl 'console.log(\"ASIDE_READY \" + pwd)' 2>&1) || _rc=$?"); expect(setupProbe).not.toContain('$_T aside repl'); expect(setupProbe).not.toContain('_T="gtimeout 30"'); expect(setupProbe).not.toMatch(/eval .*aside repl/); - expect(setupProbe).toContain('echo "READY: aside"'); + expect(setupProbe).toContain('echo "READY: $_A"'); // E7: prints "aside" on PATH, else the off-PATH path expect(setupProbe).not.toContain('aside --version'); }); @@ -217,6 +219,14 @@ describe('Aside driver contract ({{ASIDE_SETUP}})', () => { expect(optOut.stdout.trim()).toBe('NEEDS_ASIDE'); const noAside = spawnSync(sh, ['-c', setupProbe], { env: { PATH: shellPath(path.join(dir, 'gt')) }, encoding: 'utf8', timeout: 30_000 }); expect(noAside.stdout.trim()).toBe('NEEDS_ASIDE'); + // E7 (#2902): installed only at ~/.local/bin (not on the macOS login PATH) is still found, and named. + const home = path.join(dir, 'home'); + write(path.join(home, '.local', 'bin', 'aside'), '#!/bin/sh\necho "ASIDE_READY /tmp/x"\n'); + wrap(lookup('grep')!, path.join(dir, 'grep-only', 'grep')); + for (const shell of shells) { + const offPath = spawnSync(shell, ['-c', setupProbe], { env: { HOME: home, PATH: `${shellPath(path.join(dir, 'gt'))}:${shellPath(path.join(dir, 'grep-only'))}` }, encoding: 'utf8', timeout: 30_000 }); + expect(offPath.stdout.trim()).toBe(`READY: ${path.join(home, '.local', 'bin', 'aside')}`); + } } finally { fs.rmSync(dir, { recursive: true, force: true }); } diff --git a/test/third-party-actions.test.ts b/test/third-party-actions.test.ts index 873cf295d..c227207bf 100644 --- a/test/third-party-actions.test.ts +++ b/test/third-party-actions.test.ts @@ -184,7 +184,7 @@ describe("THIRD_PARTY_ACTIONS contract pins", () => { test("runtime probe is the BROWSER SETUP probe with a Darwin-gated pitch", () => { expect(section).toContain("command -v aside"); expect(section).not.toContain("aside --version"); - expect(section).toContain('echo "READY: aside"'); + expect(section).toContain('echo "READY: $_A"'); // E7: "aside" on PATH, else the ~/.local/bin path expect(section).toContain("ASIDE_UNAVAILABLE"); expect(section).toContain("report only the safe status, never raw diagnostics"); expect(section).toContain("NEEDS_ASIDE");