Skip to content

Re-vendor the Greptile CLI at 3.5.2 (#7) #15

Re-vendor the Greptile CLI at 3.5.2 (#7)

Re-vendor the Greptile CLI at 3.5.2 (#7) #15

Workflow file for this run

name: CLI Check
# The plugin vendors the Greptile CLI at plugins/greptile/scripts/greptile.mjs.
# Vendoring is a manual step, so the two things that can silently go wrong are
# shipping a bundle that is not the release it claims to be, and shipping one
# that is not what npm publishes at all. Both are checked by running the file
# and by comparing it byte-for-byte against the published tarball.
#
# A local rebuild of the CLI is NOT byte-identical to the npm artifact for the
# same version, so the vendored file must always be extracted from `npm pack`,
# never from a local `bun scripts/bundle.ts`.
#
# No secrets. As in mcp-check.yml, repo-controlled values are read from disk
# and never interpolated into shell source with `${{ }}` — a fork pull request
# controls these files.
on:
push:
branches: [main]
paths:
- 'plugins/greptile/scripts/**'
- 'plugins/greptile/commands/**'
- '.github/workflows/cli-check.yml'
pull_request:
paths:
- 'plugins/greptile/scripts/**'
- 'plugins/greptile/commands/**'
- '.github/workflows/cli-check.yml'
schedule:
- cron: '41 8 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Recorded version is well formed
run: |
set -euo pipefail
version=$(tr -d '[:space:]' < plugins/greptile/scripts/greptile.version)
if ! printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::plugins/greptile/scripts/greptile.version is not a plain semver string."
exit 1
fi
printf '%s' "$version" > "$RUNNER_TEMP/version.txt"
echo "Recorded version $version"
- name: Bundle runs and reports the recorded version
run: |
set -euo pipefail
version=$(cat "$RUNNER_TEMP/version.txt")
reported=$(node plugins/greptile/scripts/greptile.mjs --version | tr -d '[:space:]')
if [ "$reported" != "$version" ]; then
echo "::error::Vendored bundle reports $reported but greptile.version records $version."
exit 1
fi
echo "Bundle reports $reported."
- name: Bundle is byte-identical to the published npm release
run: |
set -euo pipefail
version=$(cat "$RUNNER_TEMP/version.txt")
cd "$RUNNER_TEMP"
npm pack "greptile@$version" >/dev/null
tar -xzf "greptile-$version.tgz"
cd "$GITHUB_WORKSPACE"
published=$(shasum -a 256 "$RUNNER_TEMP/package/dist/greptile.js" | cut -d' ' -f1)
vendored=$(shasum -a 256 plugins/greptile/scripts/greptile.mjs | cut -d' ' -f1)
if [ "$published" != "$vendored" ]; then
echo "::error::Vendored bundle does not match npm greptile@$version. published=$published vendored=$vendored. Re-vendor with: npm pack greptile@$version && tar -xzf greptile-$version.tgz && cp package/dist/greptile.js plugins/greptile/scripts/greptile.mjs"
exit 1
fi
echo "Vendored bundle matches npm greptile@$version ($vendored)."
- name: Commands invoke the vendored bundle, not a fetched one
run: |
set -euo pipefail
# Assert against the fenced command block only. Matching whole files
# would fail on a prose mention of npx, and — worse — would accept a
# command that dropped GREPTILE_NO_UPDATE_CHECK as long as the
# paragraph explaining it survived.
for f in plugins/greptile/commands/review.md plugins/greptile/commands/login.md; do
block=$(awk '/^```/{fence = !fence; next} fence' "$f")
if [ -z "$block" ]; then
echo "::error::$f has no fenced command block to validate."
exit 1
fi
if grep -q 'npx' <<<"$block"; then
echo "::error::$f still fetches the CLI with npx. The plugin vendors it; invoke \${CLAUDE_PLUGIN_ROOT}/scripts/greptile.mjs instead."
exit 1
fi
if ! grep -qF 'node "${CLAUDE_PLUGIN_ROOT}/scripts/greptile.mjs"' <<<"$block"; then
echo "::error::$f does not invoke the vendored bundle at \${CLAUDE_PLUGIN_ROOT}/scripts/greptile.mjs."
exit 1
fi
if ! grep -qF 'GREPTILE_NO_UPDATE_CHECK=1 node "${CLAUDE_PLUGIN_ROOT}/scripts/greptile.mjs"' <<<"$block"; then
echo "::error::$f invokes the vendored bundle without GREPTILE_NO_UPDATE_CHECK=1. CLI versions that predate plugin-install detection read this path as a standalone install and name an installer that cannot update the plugin's copy. --agent already suppresses the notice, so this is a second line of defence for invocations that drop it."
exit 1
fi
done
echo "Commands invoke the vendored bundle with the update check disabled."
- name: Review command suppresses the renderer download
run: |
set -euo pipefail
# The CLI fetches the mmdr binary from a third-party GitHub release
# the first time it renders a Mermaid diagram. That is fine in a
# terminal; inside Claude Code there is no TTY to show the image on,
# so it would stall a review to produce a link. README.md documents
# the download and states that this command suppresses it — keep the
# two in step.
block=$(awk '/^```/{fence = !fence; next} fence' plugins/greptile/commands/review.md)
if ! grep -qF 'GREPTILE_NO_AUTO_INSTALL=1 GREPTILE_NO_UPDATE_CHECK=1 node "${CLAUDE_PLUGIN_ROOT}/scripts/greptile.mjs"' <<<"$block"; then
echo "::error::plugins/greptile/commands/review.md must invoke the bundle with GREPTILE_NO_AUTO_INSTALL=1 ahead of GREPTILE_NO_UPDATE_CHECK=1. README.md tells users the plugin never downloads the mmdr renderer; this is what makes that true."
exit 1
fi
echo "Review command suppresses the renderer download."