diff --git a/scripts/install.sh b/scripts/install.sh index 836b8f1..599c02a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -202,7 +202,7 @@ main() { fi ok "✅ Node $(node --version)" - local meta tarball extract_dir version_dir launcher + local meta tarball extract_dir version_dir launcher command_hint CLEANUP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/clawfix-install.XXXXXX")" tmp="$CLEANUP_DIR" trap 'rm -rf "${CLEANUP_DIR:-}"' EXIT @@ -259,8 +259,13 @@ EOF log " Binary: $launcher" log "" + command_hint="$launcher" case ":${PATH}:" in - *":${BIN_DIR}:"*) ;; + *":${BIN_DIR}:"*) + if [ "$(command -v clawfix 2>/dev/null || true)" = "$launcher" ]; then + command_hint="clawfix" + fi + ;; *) warn "⚠️ ${BIN_DIR} is not on PATH" log " Add this to your shell profile:" @@ -269,18 +274,13 @@ EOF ;; esac - if command -v clawfix >/dev/null 2>&1; then - ok "✅ Ready: clawfix --version" - clawfix --version || true - else - ok "✅ Ready: ${launcher} --version" - "$launcher" --version || true - fi + ok "✅ Verifying installed binary: ${launcher} --version" + "$launcher" --version || die "Installed ClawFix failed its version check" log "" info "Next:" - log " clawfix --dry-run" - log " clawfix" + log " ${command_hint} --dry-run" + log " ${command_hint}" log "" } diff --git a/src/landing.js b/src/landing.js index 6b592dc..65f7c0f 100644 --- a/src/landing.js +++ b/src/landing.js @@ -509,11 +509,11 @@ const LANDING_HTML = `
$ - curl -fsSL https://clawfix.dev/install -o install.sh - + curl -fsSL https://clawfix.dev/install -o install.sh && bash install.sh +

- Recommended Verify with shasum -a 256 install.sh against the published hash, then run bash install.sh. No global npm. + Recommended Downloads and runs the installer. To inspect first, omit && bash install.sh, verify with shasum -a 256 install.sh against the published hash, then run it. No global npm.

Prefer npm? npx clawfix@0.12.0 · Dry run: npx clawfix@0.12.0 --dry-run · macOS, Linux, WSL · Node.js 22+ @@ -870,14 +870,53 @@ gateway-not-running repair. Want me to? `; diff --git a/test/install-script.test.js b/test/install-script.test.js index f034a6e..f3455b7 100644 --- a/test/install-script.test.js +++ b/test/install-script.test.js @@ -90,8 +90,16 @@ test('install script installs a pinned package into a local prefix without npm i const packDir = join(packRoot, 'package'); const prefix = join(work, 'prefix'); const binDir = join(work, 'bin'); + const oldBinDir = join(work, 'old-bin'); + let server; try { - await run('mkdir', ['-p', packDir, join(registryRoot, 'clawfix'), prefix, binDir]); + await run('mkdir', ['-p', packDir, join(registryRoot, 'clawfix'), prefix, binDir, oldBinDir]); + + await writeFile( + join(oldBinDir, 'clawfix'), + '#!/usr/bin/env bash\necho "clawfix v0.9.0-old"\n', + ); + await chmod(join(oldBinDir, 'clawfix'), 0o755); // Minimal package matching published clawfix bin layout const pkg = { @@ -129,7 +137,7 @@ test('install script installs a pinned package into a local prefix without npm i // Fake registry HTTP server const tarballBytes = await readFile(tarball); - const server = createServer((req, res) => { + server = createServer((req, res) => { if (req.url === '/clawfix/0.10.0') { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ @@ -161,11 +169,14 @@ test('install script installs a pinned package into a local prefix without npm i CLAWFIX_PREFIX: prefix, CLAWFIX_BIN_DIR: binDir, CLAWFIX_REGISTRY: registry, - PATH: `${binDir}:${process.env.PATH}`, + PATH: `${oldBinDir}:${binDir}:${process.env.PATH}`, HOME: work, }); assert.equal(result.code, 0, result.stdout + result.stderr); + assert.match(result.stdout, /clawfix v0\.10\.0-test/); + assert.doesNotMatch(result.stdout, /clawfix v0\.9\.0-old/); + assert.match(result.stdout, new RegExp(`${join(binDir, 'clawfix')} --dry-run`)); await access(join(binDir, 'clawfix')); await access(join(prefix, 'versions/0.10.0/bin/clawfix.js')); @@ -173,8 +184,12 @@ test('install script installs a pinned package into a local prefix without npm i assert.equal(version.code, 0, version.stderr); assert.match(version.stdout, /clawfix v0\.10\.0-test/); - server.close(); } finally { + if (server?.listening) { + await new Promise((resolve, reject) => { + server.close(error => error ? reject(error) : resolve()); + }); + } await rm(work, { recursive: true, force: true }); } }); diff --git a/test/landing-install.test.js b/test/landing-install.test.js new file mode 100644 index 0000000..8952d9d --- /dev/null +++ b/test/landing-install.test.js @@ -0,0 +1,164 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import vm from 'node:vm'; + +const landingPath = new URL('../src/landing.js', import.meta.url); + +async function readLandingSource() { + return readFile(landingPath, 'utf8'); +} + +function installCommandFrom(source) { + const match = source.match(/([\s\S]*?)<\/code>/); + assert.ok(match, 'install command exists'); + return match[1] + .replace(/<[^>]+>/g, '') + .replaceAll('&', '&') + .replace(/\s+/g, ' ') + .trim(); +} + +function copyScriptFrom(source) { + const scripts = [...source.matchAll(/