ci: Add shellcheck for scripts (#20631)

Peter Tripp created

Fixes shellcheck errors in script/*
Adds a couple trailing newlines.
Adds `script/shellcheck-scripts` and associated CI machinery.
Current set ultra-conservative, does not output warnings, only errors.

Change summary

.github/workflows/script_checks.yml    | 21 +++++++++++++++++++++
script/analyze_highlights.py           |  1 +
script/bundle-linux                    |  8 +++++---
script/clear-target-dir-if-larger-than |  2 +-
script/deploy-postgrest                |  2 +-
script/get-crate-version               |  4 ++--
script/kube-shell                      |  4 ++--
script/metal-debug                     |  2 +-
script/shellcheck-scripts              | 12 ++++++++++++
script/upload-nightly                  |  4 ++--
10 files changed, 48 insertions(+), 12 deletions(-)

Detailed changes

.github/workflows/script_checks.yml 🔗

@@ -0,0 +1,21 @@
+name: Script
+
+on:
+  pull_request:
+    paths:
+      - "script/**"
+  push:
+    branches:
+      - main
+
+jobs:
+  shellcheck:
+    name: "ShellCheck Scripts"
+    if: github.repository_owner == 'zed-industries'
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+      - name: Shellcheck ./scripts
+        run: |
+          ./script/shellcheck-scripts error

script/analyze_highlights.py 🔗

@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
 """
 This script analyzes all the highlight.scm files in our embedded languages and extensions.
 It counts the number of unique instances of @{name} and the languages in which they are used.

script/bundle-linux 🔗

@@ -69,7 +69,9 @@ strip --strip-debug "${target_dir}/${remote_server_triple}/release/remote_server
 
 
 # Ensure that remote_server does not depend on libssl nor libcrypto, as we got rid of these deps.
-! ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl'
+if ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl'; then
+    echo "Error: remote_server still depends on libssl or libcrypto" && exit 1
+fi
 
 suffix=""
 if [ "$channel" != "stable" ]; then
@@ -89,8 +91,8 @@ cp "${target_dir}/${target_triple}/release/cli" "${zed_dir}/bin/zed"
 # Libs
 find_libs() {
     ldd ${target_dir}/${target_triple}/release/zed |\
-    cut -d' ' -f3 |\
-    grep -v '\<\(libstdc++.so\|libc.so\|libgcc_s.so\|libm.so\|libpthread.so\|libdl.so\)'
+        cut -d' ' -f3 |\
+        grep -v '\<\(libstdc++.so\|libc.so\|libgcc_s.so\|libm.so\|libpthread.so\|libdl.so\)'
 }
 
 mkdir -p "${zed_dir}/lib"

script/deploy-postgrest 🔗

@@ -3,7 +3,7 @@
 set -eu
 source script/lib/deploy-helpers.sh
 
-if [[ $# < 1 ]]; then
+if [[ $# != 1 ]]; then
   echo "Usage: $0 <production|staging> (postgrest not needed on preview or nightly)"
   exit 1
 fi

script/get-crate-version 🔗

@@ -2,7 +2,7 @@
 
 set -eu
 
-if [[ $# < 1 ]]; then
+if [[ $# -ne 1 ]]; then
   echo "Usage: $0 <crate_name>" >&2
   exit 1
 fi
@@ -14,4 +14,4 @@ cargo metadata \
     --format-version=1 \
     | jq \
         --raw-output \
-        ".packages[] | select(.name == \"${CRATE_NAME}\") | .version"
+        ".packages[] | select(.name == \"${CRATE_NAME}\") | .version"

script/kube-shell 🔗

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-if [[ $# < 1 ]]; then
+if [[ $# -ne 1 ]]; then
   echo "Usage: $0 [production|staging|...]"
   exit 1
 fi
@@ -8,4 +8,4 @@ fi
 export ZED_KUBE_NAMESPACE=$1
 
 pod=$(kubectl --namespace=${ZED_KUBE_NAMESPACE} get pods --selector=app=zed --output=jsonpath='{.items[*].metadata.name}')
-exec kubectl --namespace $ZED_KUBE_NAMESPACE exec --tty --stdin $pod -- /bin/bash
+exec kubectl --namespace $ZED_KUBE_NAMESPACE exec --tty --stdin $pod -- /bin/bash

script/metal-debug 🔗

@@ -10,4 +10,4 @@ export GPUProfilerEnabled="YES"
 export METAL_DEBUG_ERROR_MODE=0
 export LD_LIBRARY_PATH="/Applications/Xcode.app/Contents/Developer/../SharedFrameworks/"
 
-cargo run $@
+cargo run "$@"

script/shellcheck-scripts 🔗

@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+mode=${1:-error}
+[[ "$mode" =~ ^(error|warning)$ ]] || { echo "Usage: $0 [error|warning]"; exit 1; }
+
+cd "$(dirname "$0")/.." || exit 1
+
+find script -maxdepth 1 -type f -print0 |
+  xargs -0 grep -l -E '^#!(/bin/|/usr/bin/env )(sh|bash|dash)' |
+  xargs -r shellcheck -x -S "$mode" -C

script/upload-nightly 🔗

@@ -19,12 +19,12 @@ if [[ -n "${1:-}" ]]; then
         target="$1"
     else
         echo "Error: Target '$1' is not allowed"
-        echo "Usage: $0 [${allowed_targets[@]}]"
+        echo "Usage: $0 [${allowed_targets[*]}]"
         exit 1
     fi
 else
 echo "Error: Target is not specified"
-echo "Usage: $0 [${allowed_targets[@]}]"
+echo "Usage: $0 [${allowed_targets[*]}]"
 exit 1
 fi
 echo "Uploading nightly for target: $target"