This is an automated email from the ASF dual-hosted git repository.
piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 559a330ad feat(repo): introduce pre-commit hooks and refactor CI
scripts (#2383)
559a330ad is described below
commit 559a330ad7289c90ed15a5291c315ba1075b5cc3
Author: Hubert Gruszecki <[email protected]>
AuthorDate: Fri Nov 21 11:26:07 2025 +0100
feat(repo): introduce pre-commit hooks and refactor CI scripts (#2383)
Adds .pre-commit-config.yaml with prek-based hooks and extracts inline bash
from GitHub Actions workflows into reusable scripts with --check/--fix
modes.
---
.github/workflows/_common.yml | 253 ++++--------------------------
.github/workflows/dependabot-licenses.yml | 2 +-
.markdownlint.yml | 24 +++
.pre-commit-config.yaml | 120 ++++++++++++++
CONTRIBUTING.md | 34 ++++
foreign/cpp/.pre-commit-config.yaml | 107 -------------
justfile | 8 +-
scripts/ci/license-headers.sh | 136 ++++++++++++++++
scripts/ci/licenses-list.sh | 100 ++++++++++++
scripts/ci/markdownlint.sh | 63 ++++++++
scripts/ci/shellcheck.sh | 111 +++++++++++++
scripts/{ => ci}/sync-rust-version.sh | 4 +-
scripts/ci/trailing-newline.sh | 188 ++++++++++++++++++++++
scripts/ci/trailing-whitespace.sh | 187 ++++++++++++++++++++++
scripts/copy-latest-from-master.sh | 4 +-
scripts/licenses-list.sh | 110 -------------
16 files changed, 1000 insertions(+), 451 deletions(-)
diff --git a/.github/workflows/_common.yml b/.github/workflows/_common.yml
index f4de395e3..1595affc2 100644
--- a/.github/workflows/_common.yml
+++ b/.github/workflows/_common.yml
@@ -39,18 +39,7 @@ jobs:
- uses: actions/checkout@v4
- name: Check Rust versions are synchronized
- run: |
- # Use the sync-rust-version.sh script in check mode
- if ! bash scripts/sync-rust-version.sh --check; then
- echo ""
- echo "โ Rust versions are not synchronized!"
- echo ""
- echo "To fix this issue, run:"
- echo " ./scripts/sync-rust-version.sh --fix"
- echo ""
- echo "This script will automatically update all Dockerfiles to
match rust-toolchain.toml"
- exit 1
- fi
+ run: ./scripts/ci/sync-rust-version.sh --check
pr-title:
name: Check PR Title
@@ -121,36 +110,7 @@ jobs:
- uses: actions/checkout@v4
- name: Check Apache license headers
- run: |
- echo "๐ Checking license headers..."
-
- # Pull the addlicense image
- docker pull ghcr.io/google/addlicense:latest
-
- # Run the check
- if docker run --rm -v ${{ github.workspace }}:/src -w /src \
- ghcr.io/google/addlicense:latest \
- -check -f ASF_LICENSE.txt . > missing_files.txt 2>&1; then
- echo "โ
All files have proper license headers"
- else
- file_count=$(wc -l < missing_files.txt)
- echo "โ Found $file_count files missing license headers:"
- echo ""
- cat missing_files.txt | sed 's/^/ โข /'
- echo ""
- echo "๐ก Run 'addlicense -f ASF_LICENSE.txt .' to fix automatically"
-
- # Add to summary
- echo "## โ License Headers Missing" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "The following files are missing Apache license headers:" >>
$GITHUB_STEP_SUMMARY
- echo '```' >> $GITHUB_STEP_SUMMARY
- cat missing_files.txt >> $GITHUB_STEP_SUMMARY
- echo '```' >> $GITHUB_STEP_SUMMARY
- echo 'Please call `just licenses-fix` to fix automatically.' >>
$GITHUB_STEP_SUMMARY
-
- exit 1
- fi
+ run: ./scripts/ci/license-headers.sh --check
license-list:
name: Check licenses list
@@ -163,9 +123,12 @@ jobs:
- name: Setup Rust toolchain
uses: ./.github/actions/utils/setup-rust-with-cache
with:
- enabled: "false" # Don't need cache for just checking licenses
+ enabled: "false" # Don't need cache for just checking licenses
- - run: scripts/licenses-list.sh --check
+ - name: Install cargo-license
+ run: cargo install cargo-license
+
+ - run: ./scripts/ci/licenses-list.sh --check
markdown:
name: Markdown lint
@@ -178,37 +141,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '23'
+ node-version: "23"
- name: Install markdownlint-cli
run: npm install -g markdownlint-cli
- name: Run markdownlint
- run: |
- echo "๐ Checking markdown files..."
-
- # Create config if it doesn't exist
- if [ ! -f ".markdownlint.yml" ]; then
- cat > .markdownlint.yml << 'EOF'
- # Markdown lint configuration
- default: true
- MD013:
- line_length: 120
- tables: false
- MD033:
- allowed_elements: [details, summary, img]
- MD041: false # First line in file should be a top level heading
- EOF
- fi
-
- # Run the linter
- if markdownlint '**/*.md' --ignore-path .gitignore; then
- echo "โ
All markdown files are properly formatted"
- else
- echo "โ Markdown linting failed"
- echo "๐ก Run 'markdownlint **/*.md --fix' to auto-fix issues"
- exit 1
- fi
+ run: ./scripts/ci/markdownlint.sh --check
shellcheck:
name: Shell scripts lint
@@ -221,26 +160,12 @@ jobs:
- name: Install shellcheck
run: |
- sudo apt-get update --yes && sudo apt-get install --yes shellcheck
+ SHELLCHECK_VERSION="0.11.0"
+ wget -qO-
"https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz"
| tar -xJv
+ sudo cp "shellcheck-v${SHELLCHECK_VERSION}/shellcheck"
/usr/local/bin/
- name: Check shell scripts
- run: |
- echo "๐ Checking shell scripts..."
-
- # Find all shell scripts excluding certain directories
- if find . -type f -name "*.sh" \
- -not -path "./target/*" \
- -not -path "./node_modules/*" \
- -not -path "./.git/*" \
- -not -path "./foreign/node/node_modules/*" \
- -not -path "./foreign/python/.venv/*" \
- -exec shellcheck -S warning {} +; then
- echo "โ
All shell scripts passed shellcheck"
- else
- echo "โ Shellcheck found issues in shell scripts"
- echo "๐ก Fix the issues reported above"
- exit 1
- fi
+ run: ./scripts/ci/shellcheck.sh --check
trailing-whitespace:
name: Check trailing whitespace
@@ -251,76 +176,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
with:
- fetch-depth: 0 # Need full history to get diff
+ fetch-depth: 0 # Need full history to get diff
- name: Check for trailing whitespace in changed files
- run: |
- echo "๐ Checking for trailing whitespace in changed files..."
-
- # Get list of changed files in PR
- if [ "${{ github.event_name }}" = "pull_request" ]; then
- git fetch --no-tags --depth=1 origin ${{
github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
|| true
- BASE_SHA="${{ github.event.pull_request.base.sha }}"
- CHANGED_FILES=$(git diff --name-only --diff-filter=ACM
"$BASE_SHA"...HEAD || true)
- else
- CHANGED_FILES=$(git diff --name-only --diff-filter=ACM HEAD~1)
- fi
-
- if [ -z "$CHANGED_FILES" ]; then
- echo "No files changed to check"
- exit 0
- fi
-
- echo "Files to check:"
- echo "$CHANGED_FILES" | sed 's/^/ โข /'
- echo ""
-
- # Check each changed file for trailing whitespace
- FILES_WITH_TRAILING=""
- for file in $CHANGED_FILES; do
- # Skip if file doesn't exist (might be deleted)
- if [ ! -f "$file" ]; then
- continue
- fi
-
- # Skip binary files
- if file "$file" | grep -qE "binary|data|executable|compressed";
then
- continue
- fi
-
- # Check for trailing whitespace
- if grep -q '[[:space:]]$' "$file" 2>/dev/null; then
- FILES_WITH_TRAILING="$FILES_WITH_TRAILING $file"
- fi
- done
-
- if [ -z "$FILES_WITH_TRAILING" ]; then
- echo "โ
No trailing whitespace found in changed files"
- else
- echo "โ Found trailing whitespace in the following changed files:"
- echo ""
- for file in $FILES_WITH_TRAILING; do
- echo " โข $file"
- # Show lines with trailing whitespace (limit to first 5
occurrences per file)
- grep -n '[[:space:]]$' "$file" | head -5 | while IFS=: read -r
line_num content; do
- # Show the line with visible whitespace markers
- visible_content=$(echo "$content" | sed 's/ /ยท/g; s/\t/โ/g')
- echo " Line $line_num: '${visible_content}'"
- done
- TOTAL_LINES=$(grep -c '[[:space:]]$' "$file")
- if [ "$TOTAL_LINES" -gt 5 ]; then
- echo " ... and $((TOTAL_LINES - 5)) more lines"
- fi
- echo ""
- done
-
- echo "๐ก To fix trailing whitespace in these files:"
- echo " โข VSCode: Enable 'files.trimTrailingWhitespace' setting"
- echo " โข Fix specific file: sed -i 's/[[:space:]]*$//'
<filename>"
- echo " โข Fix all changed files:"
- echo " for f in$FILES_WITH_TRAILING; do sed -i
's/[[:space:]]*$//' \$f; done"
- exit 1
- fi
+ run: ./scripts/ci/trailing-whitespace.sh --check --ci
trailing-newline:
name: Check trailing newline
@@ -331,80 +190,24 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
with:
- fetch-depth: 0 # Need full history to get diff
+ fetch-depth: 0 # Need full history to get diff
- name: Check for trailing newline in changed text files
- run: |
- echo "๐ Checking for trailing newline in changed text files..."
-
- # Get list of changed files in PR
- if [ "${{ github.event_name }}" = "pull_request" ]; then
- git fetch --no-tags --depth=1 origin ${{
github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
|| true
- BASE_SHA="${{ github.event.pull_request.base.sha }}"
- CHANGED_FILES=$(git diff --name-only --diff-filter=ACM
"$BASE_SHA"...HEAD || true)
- else
- CHANGED_FILES=$(git diff --name-only --diff-filter=ACM HEAD~1)
- fi
-
- if [ -z "$CHANGED_FILES" ]; then
- echo "No files changed to check"
- exit 0
- fi
-
- echo "Files to check:"
- echo "$CHANGED_FILES" | sed 's/^/ โข /'
- echo ""
-
- # Check each changed file for missing trailing newline
- FILES_WITHOUT_NEWLINE=""
- for file in $CHANGED_FILES; do
- # Skip if file doesn't exist (might be deleted)
- if [ ! -f "$file" ]; then
- continue
- fi
-
- # Skip binary files
- if file "$file" | grep -qE "binary|data|executable|compressed";
then
- continue
- fi
-
- # Skip empty files
- if [ ! -s "$file" ]; then
- continue
- fi
-
- # Check if file ends with a newline
- # Use tail to get last byte and od to check if it's a newline
(0x0a)
- if [ -n "$(tail -c 1 "$file" | od -An -tx1 | grep -v '0a')" ]; then
- FILES_WITHOUT_NEWLINE="$FILES_WITHOUT_NEWLINE $file"
- fi
- done
-
- if [ -z "$FILES_WITHOUT_NEWLINE" ]; then
- echo "โ
All changed text files have trailing newlines"
- else
- echo "โ Found text files without trailing newline:"
- echo ""
- for file in $FILES_WITHOUT_NEWLINE; do
- echo " โข $file"
- # Show last few characters of the file for context
- echo -n " Last characters: '"
- tail -c 20 "$file" | tr '\n' 'โต' | sed 's/\t/โ/g'
- echo "'"
- echo ""
- done
-
- echo "๐ก To add trailing newlines to these files:"
- echo " โข VSCode: Enable 'files.insertFinalNewline' setting"
- echo " โข Fix specific file: echo >> <filename>"
- echo " โข Fix all files:"
- echo " for f in$FILES_WITHOUT_NEWLINE; do [ -n \"\$(tail -c 1
\"\$f\")\" ] && echo >> \"\$f\"; done"
- exit 1
- fi
+ run: ./scripts/ci/trailing-newline.sh --check --ci
summary:
name: Common checks summary
- needs: [rust-versions, pr-title, license-headers, license-list, markdown,
shellcheck, trailing-whitespace, trailing-newline]
+ needs:
+ [
+ rust-versions,
+ pr-title,
+ license-headers,
+ license-list,
+ markdown,
+ shellcheck,
+ trailing-whitespace,
+ trailing-newline,
+ ]
if: always()
runs-on: ubuntu-latest
env:
diff --git a/.github/workflows/dependabot-licenses.yml
b/.github/workflows/dependabot-licenses.yml
index d58f91aeb..8b433feb4 100644
--- a/.github/workflows/dependabot-licenses.yml
+++ b/.github/workflows/dependabot-licenses.yml
@@ -50,7 +50,7 @@ jobs:
run: cargo install cargo-license
- name: Update DEPENDENCIES.md
- run: ./scripts/licenses-list.sh --update
+ run: ./scripts/ci/licenses-list.sh --update
- name: Commit changes
run: |
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 000000000..88d9d9b03
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+default: true
+MD013:
+ line_length: 120
+ tables: false
+MD033:
+ allowed_elements: [details, summary, img]
+MD041: false # First line in file should be a top level heading
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..e0919768b
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,120 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+default_stages: [pre-commit]
+default_install_hook_types: [pre-commit, pre-push]
+
+repos:
+ # Standard pre-commit hooks
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v6.0.0
+ hooks:
+ - id: check-yaml
+ - id: mixed-line-ending
+
+ # CI scripts
+ - repo: local
+ hooks:
+ - id: markdownlint
+ name: markdownlint
+ entry: ./scripts/ci/markdownlint.sh
+ args: ["--fix", "--staged"]
+ language: system
+ types: [markdown]
+ pass_filenames: false
+
+ - id: shellcheck
+ name: shellcheck
+ entry: ./scripts/ci/shellcheck.sh
+ args: ["--fix", "--staged"]
+ language: system
+ types: [shell]
+ pass_filenames: false
+
+ - id: license-headers
+ name: license headers
+ entry: ./scripts/ci/license-headers.sh
+ args: ["--fix"]
+ language: system
+ pass_filenames: false
+
+ - id: licenses-list
+ name: licenses list
+ entry: ./scripts/ci/licenses-list.sh
+ args: ["--fix"]
+ language: system
+ files: ^Cargo\.lock$
+ pass_filenames: false
+
+ - id: rust-version-sync
+ name: rust version sync
+ entry: ./scripts/ci/sync-rust-version.sh
+ args: ["--fix"]
+ language: system
+ files: ^rust-toolchain\.toml$
+ pass_filenames: false
+
+ - id: trailing-whitespace
+ name: trailing whitespace
+ entry: ./scripts/ci/trailing-whitespace.sh
+ args: ["--fix", "--staged"]
+ language: system
+ types: [text]
+ pass_filenames: false
+
+ - id: trailing-newline
+ name: trailing newline
+ entry: ./scripts/ci/trailing-newline.sh
+ args: ["--fix", "--staged"]
+ language: system
+ types: [text]
+ pass_filenames: false
+
+ # Rust
+ - repo: local
+ hooks:
+ - id: cargo-fmt
+ name: cargo fmt
+ entry: cargo fmt --all
+ language: system
+ types: [rust]
+ pass_filenames: false
+
+ - id: cargo-clippy
+ name: cargo clippy
+ entry: cargo clippy
+ args:
+ [
+ "--all-features",
+ "--all-targets",
+ "--manifest-path",
+ "Cargo.toml",
+ "--",
+ "-D",
+ "warnings",
+ ]
+ language: system
+ types: [rust]
+ pass_filenames: false
+ stages: [pre-push]
+
+ # TODO(hubcio): add fast checks, linters, formatters for other
languages
+ # - python (maturin, pytest, ruff, black)
+ # - java
+ # - go
+ # - csharp
+ # - js
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 31696169a..8964a23e9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,6 +72,40 @@ $ cargo version
cargo 1.86.0 (adf9b6ad1 2025-02-28)
```
+### Pre-commit Hooks (Recommended)
+
+Iggy uses [prek](https://github.com/9999years/prek) for pre-commit hooks to
ensure code quality before commits. Setting up hooks is optional but strongly
recommended to catch issues early.
+
+#### Setup
+
+```shell
+cargo install prek
+prek install
+```
+
+This will install git hooks that automatically run on `pre-commit` and
`pre-push` events.
+
+#### Manual hook execution
+
+You can manually run specific hooks:
+
+```shell
+# Run all pre-commit hooks
+prek run
+
+# Run specific hook
+prek run cargo-fmt
+prek run cargo-clippy
+```
+
+#### Skip hooks (when necessary)
+
+If you need to skip hooks for a specific commit:
+
+```shell
+git commit --no-verify -m "your message"
+```
+
## How to build
See [Quick
Start](https://github.com/apache/iggy?tab=readme-ov-file#quick-start)
diff --git a/foreign/cpp/.pre-commit-config.yaml
b/foreign/cpp/.pre-commit-config.yaml
deleted file mode 100644
index e3a87f017..000000000
--- a/foreign/cpp/.pre-commit-config.yaml
+++ /dev/null
@@ -1,107 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
----
-repos:
- - repo: https://github.com/adrienverge/yamllint.git
- rev: v1.21.0
- hooks:
- - id: yamllint
- args: [--format, parsable, --strict]
- - repo: https://github.com/cmake-lint/cmake-lint
- rev: 1.4.2
- hooks:
- - id: cmakelint
- args: [--filter, -linelength]
- - repo: https://github.com/cpplint/cpplint
- rev: 1.6.1
- hooks:
- - id: cpplint
- - repo: https://github.com/gitleaks/gitleaks
- rev: v8.16.1
- hooks:
- - id: gitleaks
- - repo: https://github.com/hadolint/hadolint
- rev: v2.12.0
- hooks:
- - id: hadolint
- args: [--config, .github/linters/.hadolint.yaml]
- - repo: https://github.com/igorshubovych/markdownlint-cli
- rev: v0.39.0
- hooks:
- - id: markdownlint
- args: [--config, .github/linters/.markdown-lint.yml]
- - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
- rev: 0.2.3
- hooks:
- - id: yamlfmt
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.3.0
- hooks:
- - id: check-added-large-files
- - id: check-json
- - id: check-merge-conflict
- - id: check-yaml
- - id: end-of-file-fixer
- - id: trailing-whitespace
- - repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v18.1.0
- hooks:
- - id: clang-format
- - repo: https://github.com/scop/pre-commit-shfmt
- rev: v3.8.0-1
- hooks:
- - id: shfmt
- - repo: local
- hooks:
- - id: cppcheck
- name: cppcheck
- entry: cppcheck --enable=style,performance,warning --inline-suppr
--std=c++20 -x c++
- language: system
- files: \.(cc|h)$
- pass_filenames: true
- always_run: false
- - id: flawfinder
- name: flawfinder
- entry: flawfinder --quiet -S -D --error-level=1
- language: system
- files: \.(cc|h)$
- pass_filenames: true
- always_run: false
- - id: lizard
- name: Check code cyclomatic complexity
- entry: lizard -C 15 -i 0 -w -l c,cc,cpp,h,hpp
- language: system
- pass_filenames: true
- always_run: false
- - id: build
- name: Build
- entry: cmake --build build
- language: system
- pass_filenames: false
- always_run: true
- - id: run-unit-tests
- name: Run Unit Tests
- entry: ./build/tests/Debug/iggy_cpp_test
- language: system
- pass_filenames: false
- always_run: true
- - id: e2e-tests
- name: Run E2E Tests
- entry: ./build/tests/Debug/iggy_e2e_test
- language: system
- pass_filenames: false
- always_run: true
diff --git a/justfile b/justfile
index 7b7a14b9e..578d0e4dd 100644
--- a/justfile
+++ b/justfile
@@ -67,16 +67,16 @@ profile-io-client:
./scripts/profile.sh iggy-bench io
licenses-fix:
- docker run --rm -v $(pwd):/src -w /src ghcr.io/google/addlicense:latest -f
ASF_LICENSE.txt .
+ ./scripts/ci/license-headers.sh --fix
licenses-check:
- docker run --rm -v $(pwd):/src -w /src ghcr.io/google/addlicense:latest
-check -f ASF_LICENSE.txt .
+ ./scripts/ci/license-headers.sh --check
licenses-list-check:
- ./scripts/licenses-list.sh --check
+ ./scripts/ci/licenses-list.sh --check
licenses-list-fix:
- ./scripts/licenses-list.sh --update
+ ./scripts/ci/licenses-list.sh --fix
markdownlint:
markdownlint '**/*.md' --ignore-path .gitignore
diff --git a/scripts/ci/license-headers.sh b/scripts/ci/license-headers.sh
new file mode 100755
index 000000000..7a002d5b8
--- /dev/null
+++ b/scripts/ci/license-headers.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Parse arguments
+MODE="check"
+if [ $# -gt 0 ]; then
+ case "$1" in
+ --check)
+ MODE="check"
+ ;;
+ --fix)
+ MODE="fix"
+ ;;
+ *)
+ echo "Usage: $0 [--check|--fix]"
+ echo " --check Check files for Apache license headers (default)"
+ echo " --fix Add Apache license headers to files missing them"
+ exit 1
+ ;;
+ esac
+fi
+
+# Check if docker is available
+if ! command -v docker &> /dev/null; then
+ echo "โ docker command not found"
+ echo "๐ก Install Docker to run license header checks"
+ exit 1
+fi
+
+# Get the repository root
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+cd "$REPO_ROOT"
+
+# Check if ASF_LICENSE.txt exists
+if [ ! -f "ASF_LICENSE.txt" ]; then
+ echo "โ ASF_LICENSE.txt not found in repository root"
+ exit 1
+fi
+
+# Pull the addlicense image
+echo "Pulling addlicense Docker image..."
+docker pull ghcr.io/google/addlicense:latest >/dev/null 2>&1
+
+# Common patterns to ignore (build artifacts, dependencies, IDE files)
+IGNORE_PATTERNS=(
+ "**/target/**"
+ "target/**"
+ "**/node_modules/**"
+ "node_modules/**"
+ "**/.venv/**"
+ ".venv/**"
+ "**/venv/**"
+ "venv/**"
+ "**/dist/**"
+ "dist/**"
+ "**/build/**"
+ "build/**"
+ "**/.idea/**"
+ ".idea/**"
+ "**/.vscode/**"
+ ".vscode/**"
+ "**/.gradle/**"
+ ".gradle/**"
+ "**/bin/**"
+ "**/obj/**"
+ "**/local_data*/**"
+ "**/performance_results*/**"
+)
+
+# Build ignore flags for addlicense
+IGNORE_FLAGS=()
+for pattern in "${IGNORE_PATTERNS[@]}"; do
+ IGNORE_FLAGS+=("-ignore" "$pattern")
+done
+
+if [ "$MODE" = "fix" ]; then
+ echo "๐ง Adding license headers to files..."
+
+ # Run addlicense without -check to fix files
+ docker run --rm -v "$REPO_ROOT:/src" -w /src \
+ ghcr.io/google/addlicense:latest \
+ -f ASF_LICENSE.txt "${IGNORE_FLAGS[@]}" .
+
+ echo "โ
License headers have been added to files"
+else
+ echo "๐ Checking license headers..."
+
+ # Run the check and capture output
+ TEMP_FILE=$(mktemp)
+ trap 'rm -f "$TEMP_FILE"' EXIT
+
+ if docker run --rm -v "$REPO_ROOT:/src" -w /src \
+ ghcr.io/google/addlicense:latest \
+ -check -f ASF_LICENSE.txt "${IGNORE_FLAGS[@]}" . > "$TEMP_FILE" 2>&1; then
+ echo "โ
All files have proper license headers"
+ else
+ file_count=$(wc -l < "$TEMP_FILE")
+ echo "โ Found $file_count files missing license headers:"
+ echo ""
+ cat "$TEMP_FILE" | sed 's/^/ โข /'
+ echo ""
+ echo "๐ก Run '$0 --fix' to add license headers automatically"
+
+ # Add to GitHub Actions summary if running in CI
+ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+ {
+ echo "## โ License Headers Missing"
+ echo ""
+ echo "The following files are missing Apache license headers:"
+ echo '```'
+ cat "$TEMP_FILE"
+ echo '```'
+ echo "Please run \`./scripts/ci/license-headers.sh --fix\` to fix
automatically."
+ } >> "$GITHUB_STEP_SUMMARY"
+ fi
+
+ exit 1
+ fi
+fi
diff --git a/scripts/ci/licenses-list.sh b/scripts/ci/licenses-list.sh
new file mode 100755
index 000000000..2372ff3b3
--- /dev/null
+++ b/scripts/ci/licenses-list.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Default mode
+MODE="check"
+
+# Parse arguments
+if [ $# -gt 0 ]; then
+ case "$1" in
+ --check)
+ MODE="check"
+ ;;
+ --update|--fix)
+ MODE="update"
+ ;;
+ *)
+ echo "Usage: $0 [--check|--update|--fix]"
+ echo " --check Check if DEPENDENCIES.md is up to date (default)"
+ echo " --update Update DEPENDENCIES.md with current dependencies"
+ echo " --fix Alias for --update"
+ exit 1
+ ;;
+ esac
+fi
+
+# Check if cargo-license is installed
+if ! command -v cargo-license &>/dev/null; then
+ echo "โ cargo-license command not found"
+ echo "๐ก Install it using: cargo install cargo-license"
+ exit 1
+fi
+
+# Check if DEPENDENCIES.md exists
+if [ ! -f "DEPENDENCIES.md" ] && [ "$MODE" = "check" ]; then
+ echo "โ DEPENDENCIES.md does not exist"
+ echo "๐ก Run '$0 --fix' to create it"
+ exit 1
+fi
+
+# Generate current dependencies
+TEMP_FILE=$(mktemp)
+trap 'rm -f "$TEMP_FILE"' EXIT
+
+echo "Generating current dependencies list..."
+cargo license --color never --do-not-bundle --all-features >"$TEMP_FILE"
+
+# Update mode
+if [ "$MODE" = "update" ]; then
+ echo "๐ง Updating DEPENDENCIES.md..."
+ {
+ echo "# Dependencies"
+ echo ""
+ cat "$TEMP_FILE"
+ } >DEPENDENCIES.md
+ echo "โ
DEPENDENCIES.md has been updated"
+ exit 0
+fi
+
+# Check mode
+if [ "$MODE" = "check" ]; then
+ echo "๐ Checking if DEPENDENCIES.md is up to date..."
+ # Create expected format for comparison
+ EXPECTED_FILE=$(mktemp)
+ trap 'rm -f "$TEMP_FILE" "$EXPECTED_FILE"' EXIT
+ {
+ echo "# Dependencies"
+ echo ""
+ cat "$TEMP_FILE"
+ } >"$EXPECTED_FILE"
+
+ if ! diff -q "$EXPECTED_FILE" DEPENDENCIES.md >/dev/null; then
+ echo "โ DEPENDENCIES.md is out of date"
+ echo ""
+ echo "Diff:"
+ diff -u DEPENDENCIES.md "$EXPECTED_FILE" || true
+ echo ""
+ echo "๐ก Run '$0 --fix' to update it"
+ exit 1
+ else
+ echo "โ
DEPENDENCIES.md is up to date"
+ fi
+fi
diff --git a/scripts/ci/markdownlint.sh b/scripts/ci/markdownlint.sh
new file mode 100755
index 000000000..61ba62467
--- /dev/null
+++ b/scripts/ci/markdownlint.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Parse arguments
+MODE="check"
+if [ $# -gt 0 ]; then
+ case "$1" in
+ --check)
+ MODE="check"
+ ;;
+ --fix)
+ MODE="fix"
+ ;;
+ *)
+ echo "Usage: $0 [--check|--fix]"
+ echo " --check Check markdown files for issues (default)"
+ echo " --fix Automatically fix markdown issues"
+ exit 1
+ ;;
+ esac
+fi
+
+# Check if markdownlint is installed
+if ! command -v markdownlint &> /dev/null; then
+ echo "โ markdownlint command not found"
+ echo "๐ก Install it using: npm install -g markdownlint-cli"
+ exit 1
+fi
+
+# Files to ignore (in addition to .gitignore)
+IGNORE_FILES="CLAUDE.md"
+
+if [ "$MODE" = "fix" ]; then
+ echo "๐ง Fixing markdown files..."
+ markdownlint '**/*.md' --ignore-path .gitignore --ignore "$IGNORE_FILES"
--fix
+ echo "โ
Markdown files have been fixed"
+else
+ echo "๐ Checking markdown files..."
+ if markdownlint '**/*.md' --ignore-path .gitignore --ignore "$IGNORE_FILES";
then
+ echo "โ
All markdown files are properly formatted"
+ else
+ echo "โ Markdown linting failed"
+ echo "๐ก Run '$0 --fix' to auto-fix issues"
+ exit 1
+ fi
+fi
diff --git a/scripts/ci/shellcheck.sh b/scripts/ci/shellcheck.sh
new file mode 100755
index 000000000..e4b1e0f32
--- /dev/null
+++ b/scripts/ci/shellcheck.sh
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Parse arguments
+MODE="check"
+if [ $# -gt 0 ]; then
+ case "$1" in
+ --check)
+ MODE="check"
+ ;;
+ --fix)
+ MODE="fix"
+ ;;
+ *)
+ echo "Usage: $0 [--check|--fix]"
+ echo " --check Check shell scripts for issues (default)"
+ echo " --fix Show detailed suggestions for fixes"
+ exit 1
+ ;;
+ esac
+fi
+
+# Check if shellcheck is installed
+if ! command -v shellcheck &> /dev/null; then
+ echo "โ shellcheck command not found"
+ echo "๐ก Install it using:"
+ echo " โข Ubuntu/Debian: sudo apt-get install shellcheck"
+ echo " โข macOS: brew install shellcheck"
+ echo " โข Or visit: https://www.shellcheck.net/"
+ exit 1
+fi
+
+echo "shellcheck version: $(shellcheck --version)"
+
+# Get repository root
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+cd "$REPO_ROOT"
+
+# Directories to exclude
+EXCLUDE_PATHS=(
+ "./target/*"
+ "./node_modules/*"
+ "./.git/*"
+ "./foreign/node/node_modules/*"
+ "./foreign/python/.venv/*"
+ "./.venv/*"
+ "./venv/*"
+ "./build/*"
+ "./dist/*"
+)
+
+# Build find exclusion arguments
+FIND_EXCLUDE_ARGS=()
+for path in "${EXCLUDE_PATHS[@]}"; do
+ FIND_EXCLUDE_ARGS+=("-not" "-path" "$path")
+done
+
+if [ "$MODE" = "fix" ]; then
+ echo "๐ง Running shellcheck with detailed suggestions..."
+ echo ""
+ echo "Note: shellcheck does not support automatic fixing."
+ echo "Please review the suggestions below and fix issues manually."
+ echo ""
+
+ # Run with detailed format
+ FAILED=0
+ while IFS= read -r -d '' script; do
+ echo "Checking: $script"
+ if ! shellcheck -f gcc "$script"; then
+ FAILED=1
+ fi
+ echo ""
+ done < <(find . -type f -name "*.sh" "${FIND_EXCLUDE_ARGS[@]}" -print0)
+
+ if [ $FAILED -eq 1 ]; then
+ echo "โ Found issues in shell scripts"
+ echo "๐ก Fix the issues reported above manually"
+ exit 1
+ else
+ echo "โ
All shell scripts passed shellcheck"
+ fi
+else
+ echo "๐ Checking shell scripts..."
+
+ # Run shellcheck on all shell scripts (checks all severities: error,
warning, info, style)
+ if find . -type f -name "*.sh" "${FIND_EXCLUDE_ARGS[@]}" -exec shellcheck {}
+; then
+ echo "โ
All shell scripts passed shellcheck"
+ else
+ echo ""
+ echo "โ Shellcheck found issues in shell scripts"
+ echo "๐ก Run '$0 --fix' to see detailed suggestions"
+ exit 1
+ fi
+fi
diff --git a/scripts/sync-rust-version.sh b/scripts/ci/sync-rust-version.sh
similarity index 97%
rename from scripts/sync-rust-version.sh
rename to scripts/ci/sync-rust-version.sh
index ccaa8012f..45b056f7f 100755
--- a/scripts/sync-rust-version.sh
+++ b/scripts/ci/sync-rust-version.sh
@@ -64,8 +64,8 @@ if [ -z "$MODE" ]; then
exit 1
fi
-# Get the repository root (parent of scripts directory)
-REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+# Get the repository root (two levels up from scripts/ci/)
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$REPO_ROOT"
# Extract Rust version from rust-toolchain.toml
diff --git a/scripts/ci/trailing-newline.sh b/scripts/ci/trailing-newline.sh
new file mode 100755
index 000000000..bbab2e564
--- /dev/null
+++ b/scripts/ci/trailing-newline.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Default values
+MODE="check"
+FILE_MODE="staged"
+FILES=()
+
+# Parse arguments
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --check)
+ MODE="check"
+ shift
+ ;;
+ --fix)
+ MODE="fix"
+ shift
+ ;;
+ --staged)
+ FILE_MODE="staged"
+ shift
+ ;;
+ --ci)
+ FILE_MODE="ci"
+ shift
+ ;;
+ --all)
+ FILE_MODE="all"
+ shift
+ ;;
+ --help|-h)
+ echo "Usage: $0 [--check|--fix] [--staged|--ci|--all] [files...]"
+ echo ""
+ echo "Modes:"
+ echo " --check Check for missing trailing newlines (default)"
+ echo " --fix Add trailing newlines to files"
+ echo ""
+ echo "File selection:"
+ echo " --staged Check staged files (default, for git hooks)"
+ echo " --ci Check files changed in PR (for CI)"
+ echo " --all Check all tracked files"
+ echo " [files] Check specific files"
+ exit 0
+ ;;
+ -*)
+ echo "Unknown option: $1"
+ echo "Use --help for usage information"
+ exit 1
+ ;;
+ *)
+ # Treat as file argument
+ FILES+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# Get files to check based on mode
+get_files() {
+ case "$FILE_MODE" in
+ staged)
+ # Get staged files for git hooks
+ git diff --cached --name-only --diff-filter=ACM
+ ;;
+ ci)
+ # Get files changed in PR for CI
+ if [ -n "${GITHUB_BASE_REF:-}" ]; then
+ # GitHub Actions PR context
+ git fetch --no-tags --depth=1 origin
"${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" 2>/dev/null || true
+ git diff --name-only --diff-filter=ACM "${GITHUB_BASE_REF}...HEAD"
+ elif [ -n "${CI:-}" ]; then
+ # Generic CI - compare with HEAD~1
+ git diff --name-only --diff-filter=ACM HEAD~1
+ else
+ # Fallback to staged files
+ git diff --cached --name-only --diff-filter=ACM
+ fi
+ ;;
+ all)
+ # Get all tracked files
+ git ls-files
+ ;;
+ esac
+}
+
+# If files were provided as arguments, use them
+if [ ${#FILES[@]} -gt 0 ]; then
+ CHANGED_FILES=("${FILES[@]}")
+else
+ # Get files based on mode
+ CHANGED_FILES=()
+ while IFS= read -r file; do
+ CHANGED_FILES+=("$file")
+ done < <(get_files)
+fi
+
+# Exit early if no files to check
+if [ ${#CHANGED_FILES[@]} -eq 0 ]; then
+ echo "No files to check"
+ exit 0
+fi
+
+echo "Checking ${#CHANGED_FILES[@]} file(s) for trailing newlines..."
+
+# Track files with issues
+FILES_WITHOUT_NEWLINE=()
+
+# Check each file
+for file in "${CHANGED_FILES[@]}"; do
+ # Skip if file doesn't exist (might be deleted)
+ if [ ! -f "$file" ]; then
+ continue
+ fi
+
+ # Skip binary files
+ if file "$file" | grep -qE "binary|data|executable|compressed"; then
+ continue
+ fi
+
+ # Skip empty files
+ if [ ! -s "$file" ]; then
+ continue
+ fi
+
+ # Check if file ends with a newline
+ # Use tail to get last byte and od to check if it's a newline (0x0a)
+ if ! tail -c 1 "$file" | od -An -tx1 | grep -q '0a'; then
+ FILES_WITHOUT_NEWLINE+=("$file")
+ fi
+done
+
+# Fix mode
+if [ "$MODE" = "fix" ]; then
+ if [ ${#FILES_WITHOUT_NEWLINE[@]} -eq 0 ]; then
+ echo "โ
All files have trailing newlines"
+ exit 0
+ fi
+
+ echo "๐ง Adding trailing newlines to ${#FILES_WITHOUT_NEWLINE[@]} file(s)..."
+ for file in "${FILES_WITHOUT_NEWLINE[@]}"; do
+ # Add newline if file doesn't end with one
+ if [ -n "$(tail -c 1 "$file")" ]; then
+ echo >> "$file"
+ echo " Fixed: $file"
+ fi
+ done
+ echo "โ
Trailing newlines added to ${#FILES_WITHOUT_NEWLINE[@]} file(s)"
+ exit 0
+fi
+
+# Check mode
+if [ ${#FILES_WITHOUT_NEWLINE[@]} -eq 0 ]; then
+ echo "โ
All text files have trailing newlines"
+ exit 0
+fi
+
+echo "โ Found ${#FILES_WITHOUT_NEWLINE[@]} file(s) without trailing newline:"
+echo ""
+
+for file in "${FILES_WITHOUT_NEWLINE[@]}"; do
+ echo " โข $file"
+ # Show last few characters of the file for context
+ echo -n " Last characters: '"
+ tail -c 20 "$file" | tr '\n' 'โต' | sed 's/\t/โ/g'
+ echo "'"
+ echo ""
+done
+
+echo "๐ก Run '$0 --fix' to add trailing newlines automatically"
+exit 1
diff --git a/scripts/ci/trailing-whitespace.sh
b/scripts/ci/trailing-whitespace.sh
new file mode 100755
index 000000000..b2e066021
--- /dev/null
+++ b/scripts/ci/trailing-whitespace.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+# Default values
+MODE="check"
+FILE_MODE="staged"
+FILES=()
+
+# Parse arguments
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --check)
+ MODE="check"
+ shift
+ ;;
+ --fix)
+ MODE="fix"
+ shift
+ ;;
+ --staged)
+ FILE_MODE="staged"
+ shift
+ ;;
+ --ci)
+ FILE_MODE="ci"
+ shift
+ ;;
+ --all)
+ FILE_MODE="all"
+ shift
+ ;;
+ --help|-h)
+ echo "Usage: $0 [--check|--fix] [--staged|--ci|--all] [files...]"
+ echo ""
+ echo "Modes:"
+ echo " --check Check for trailing whitespace (default)"
+ echo " --fix Remove trailing whitespace"
+ echo ""
+ echo "File selection:"
+ echo " --staged Check staged files (default, for git hooks)"
+ echo " --ci Check files changed in PR (for CI)"
+ echo " --all Check all tracked files"
+ echo " [files] Check specific files"
+ exit 0
+ ;;
+ -*)
+ echo "Unknown option: $1"
+ echo "Use --help for usage information"
+ exit 1
+ ;;
+ *)
+ # Treat as file argument
+ FILES+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# Get files to check based on mode
+get_files() {
+ case "$FILE_MODE" in
+ staged)
+ # Get staged files for git hooks
+ git diff --cached --name-only --diff-filter=ACM
+ ;;
+ ci)
+ # Get files changed in PR for CI
+ if [ -n "${GITHUB_BASE_REF:-}" ]; then
+ # GitHub Actions PR context
+ git fetch --no-tags --depth=1 origin
"${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" 2>/dev/null || true
+ git diff --name-only --diff-filter=ACM "${GITHUB_BASE_REF}...HEAD"
+ elif [ -n "${CI:-}" ]; then
+ # Generic CI - compare with HEAD~1
+ git diff --name-only --diff-filter=ACM HEAD~1
+ else
+ # Fallback to staged files
+ git diff --cached --name-only --diff-filter=ACM
+ fi
+ ;;
+ all)
+ # Get all tracked files
+ git ls-files
+ ;;
+ esac
+}
+
+# If files were provided as arguments, use them
+if [ ${#FILES[@]} -gt 0 ]; then
+ CHANGED_FILES=("${FILES[@]}")
+else
+ # Get files based on mode
+ CHANGED_FILES=()
+ while IFS= read -r file; do
+ CHANGED_FILES+=("$file")
+ done < <(get_files)
+fi
+
+# Exit early if no files to check
+if [ ${#CHANGED_FILES[@]} -eq 0 ]; then
+ echo "No files to check"
+ exit 0
+fi
+
+echo "Checking ${#CHANGED_FILES[@]} file(s) for trailing whitespace..."
+
+# Track files with issues
+FILES_WITH_TRAILING=()
+
+# Check each file
+for file in "${CHANGED_FILES[@]}"; do
+ # Skip if file doesn't exist (might be deleted)
+ if [ ! -f "$file" ]; then
+ continue
+ fi
+
+ # Skip binary files
+ if file "$file" | grep -qE "binary|data|executable|compressed"; then
+ continue
+ fi
+
+ # Check for trailing whitespace
+ if grep -q '[[:space:]]$' "$file" 2>/dev/null; then
+ FILES_WITH_TRAILING+=("$file")
+ fi
+done
+
+# Fix mode
+if [ "$MODE" = "fix" ]; then
+ if [ ${#FILES_WITH_TRAILING[@]} -eq 0 ]; then
+ echo "โ
No trailing whitespace found"
+ exit 0
+ fi
+
+ echo "๐ง Removing trailing whitespace from ${#FILES_WITH_TRAILING[@]}
file(s)..."
+ for file in "${FILES_WITH_TRAILING[@]}"; do
+ # Remove trailing whitespace (in-place)
+ sed -i 's/[[:space:]]*$//' "$file"
+ echo " Fixed: $file"
+ done
+ echo "โ
Trailing whitespace removed from ${#FILES_WITH_TRAILING[@]} file(s)"
+ exit 0
+fi
+
+# Check mode
+if [ ${#FILES_WITH_TRAILING[@]} -eq 0 ]; then
+ echo "โ
No trailing whitespace found"
+ exit 0
+fi
+
+echo "โ Found trailing whitespace in ${#FILES_WITH_TRAILING[@]} file(s):"
+echo ""
+
+for file in "${FILES_WITH_TRAILING[@]}"; do
+ echo " โข $file"
+ # Show lines with trailing whitespace (limit to first 3 occurrences per file)
+ grep -n '[[:space:]]$' "$file" | head -3 | while IFS=: read -r line_num
content; do
+ # Show the line with visible whitespace markers
+ visible_content=$(echo "$content" | sed 's/ /ยท/g; s/\t/โ/g')
+ echo " Line $line_num: '${visible_content}'"
+ done
+
+ TOTAL_LINES=$(grep -c '[[:space:]]$' "$file")
+ if [ "$TOTAL_LINES" -gt 3 ]; then
+ echo " ... and $((TOTAL_LINES - 3)) more lines"
+ fi
+ echo ""
+done
+
+echo "๐ก Run '$0 --fix' to remove trailing whitespace automatically"
+exit 1
diff --git a/scripts/copy-latest-from-master.sh
b/scripts/copy-latest-from-master.sh
index a6c2fa6c9..35665cf58 100755
--- a/scripts/copy-latest-from-master.sh
+++ b/scripts/copy-latest-from-master.sh
@@ -107,7 +107,7 @@ case "$COMMAND" in
# Iterate through all saved files
find "$TMP_SAVED" -type f | while read -r saved_file; do
# Get relative path by removing the tmp prefix
- rel_path="${saved_file#$TMP_SAVED/}"
+ rel_path="${saved_file#"$TMP_SAVED"/}"
dest_dir=$(dirname "$rel_path")
# Create destination directory if it doesn't exist
@@ -136,4 +136,4 @@ case "$COMMAND" in
echo "Valid commands: save, apply"
exit 1
;;
-esac
\ No newline at end of file
+esac
diff --git a/scripts/licenses-list.sh b/scripts/licenses-list.sh
deleted file mode 100755
index 8739383d9..000000000
--- a/scripts/licenses-list.sh
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/bin/bash
-
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# This script is used to generate Cross.toml file for user which executes
-# this script. This is needed since Cross.toml build.dockerfile.build-args
-# section requires statically defined Docker build arguments and parameters
-# like current UID or GID must be entered (cannot be generated or fetched
-# during cross execution time).
-
-set -euo pipefail
-
-# Default mode
-MODE="help"
-
-# Parse arguments
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --update)
- MODE="update"
- shift
- ;;
- --check)
- MODE="check"
- shift
- ;;
- *)
- echo "Unknown option: $1"
- MODE="help"
- shift
- ;;
- esac
-done
-
-# Display usage if no valid arguments provided
-if [ "$MODE" = "help" ]; then
- echo "Usage: $0 [OPTIONS]"
- echo "Options:"
- echo " --check Check if DEPENDENCIES.md is up to date"
- echo " --update Update DEPENDENCIES.md with current dependencies"
- exit 0
-fi
-
-# Check if cargo-license is installed
-if ! command -v cargo-license &>/dev/null; then
- echo "Installing cargo-license..."
- cargo install cargo-license
-fi
-
-# Check if DEPENDENCIES.md exists
-if [ ! -f "DEPENDENCIES.md" ] && [ "$MODE" = "check" ]; then
- echo "Error: DEPENDENCIES.md does not exist."
- exit 1
-fi
-
-# Generate current dependencies
-TEMP_FILE=$(mktemp)
-trap 'rm -f "$TEMP_FILE"' EXIT
-
-echo "Generating current dependencies list..."
-cargo license --color never --do-not-bundle --all-features >"$TEMP_FILE"
-
-# Update mode
-if [ "$MODE" = "update" ]; then
- echo "Updating DEPENDENCIES.md..."
- {
- echo "# Dependencies"
- echo ""
- cat "$TEMP_FILE"
- } >DEPENDENCIES.md
- echo "DEPENDENCIES.md has been updated."
- exit 0
-fi
-
-# Check mode
-if [ "$MODE" = "check" ]; then
- echo "Checking if DEPENDENCIES.md is up to date..."
- # Create expected format for comparison
- EXPECTED_FILE=$(mktemp)
- trap 'rm -f "$TEMP_FILE" "$EXPECTED_FILE"' EXIT
- {
- echo "# Dependencies"
- echo ""
- cat "$TEMP_FILE"
- } >"$EXPECTED_FILE"
-
- if ! diff -q "$EXPECTED_FILE" DEPENDENCIES.md >/dev/null; then
- echo "Error: DEPENDENCIES.md is out of date. Please run '$0 --update'
to update it."
- echo "Diff:"
- diff -u DEPENDENCIES.md "$EXPECTED_FILE"
- exit 1
- else
- echo "DEPENDENCIES.md is up to date."
- fi
-fi