-
Notifications
You must be signed in to change notification settings - Fork 0
chore(hooks): restore the canonical git hooks #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
824923c
8be786f
442b2ca
b61351b
35d5a97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,62 +1,55 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| # | ||
| # Copyright 2026 ResQ | ||
| # | ||
| # Licensed 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. | ||
| # | ||
| # commit-msg | ||
| # | ||
| # Validates the commit message format. | ||
| # Ensures the subject line follows the Conventional Commits specification. | ||
| # | ||
| # Usage: | ||
| # .git-hooks/commit-msg COMMIT_MSG_FILE | ||
| # | ||
| # Arguments: | ||
| # $1 - Path to the file containing the commit message. | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Exit codes: | ||
| # 0 Commit message is valid. | ||
| # 1 Commit message is invalid. | ||
| # Canonical ResQ commit-msg shim — source: resq-software/dev. | ||
| # Enforces Conventional Commits; blocks fixup/squash/WIP on main/master. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| [ -n "${GIT_HOOKS_SKIP:-}" ] && exit 0 | ||
|
|
||
| # INPUT_FILE stores the path to the commit message file. | ||
| INPUT_FILE=${1:-} | ||
| # PATTERN is the regular expression for a valid Conventional Commit message. | ||
| INPUT_FILE="${1:-}" | ||
| # git always passes the message file, but the `${1:-}` default means a hook run | ||
| # by hand, or by a tool that forgets the argument, reaches `head -1 ""` instead — | ||
| # and `set -e` turns that into a bare tool error naming neither the hook nor the | ||
| # cause. Fail here, where the message can say what was expected. | ||
| if [ -z "$INPUT_FILE" ] || [ ! -f "$INPUT_FILE" ]; then | ||
| echo "❌ commit-msg: expected a commit-message file as \$1, got '$INPUT_FILE'." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+$" | ||
|
|
||
| # Validate only the first line (subject). Strip an optional [TICKET-123] prefix | ||
| # inserted by prepare-commit-msg so the two hooks don't conflict. | ||
| FIRST_LINE=$(head -1 "$INPUT_FILE") | ||
| SUBJECT=$(echo "$FIRST_LINE" | sed 's/^\[[A-Z][A-Z]*-[0-9]*\] //') | ||
| # Ticket-prefix regex matches what prepare-commit-msg prepends ({2,} chars). | ||
| SUBJECT=$(sed -E 's/^\[[A-Z]{2,}-[0-9]+\][[:space:]]*//' <<<"$FIRST_LINE") | ||
|
|
||
| # WIP / fixup! / squash! guard runs *before* the format check so users get | ||
| # a branch-specific error message on main/master instead of the generic | ||
| # "Invalid commit message format". | ||
| BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "") | ||
| case "$BRANCH" in | ||
| main|master) | ||
| if grep -qiE "^(\[[A-Z]{2,}-[0-9]+\] )?(fixup!|squash!|wip[: ])" <<<"$FIRST_LINE"; then | ||
| echo "Error: fixup!/squash!/WIP commits are not allowed on $BRANCH." | ||
| echo "Create a feature branch instead." | ||
| exit 1 | ||
| fi | ||
| ;; | ||
| esac | ||
|
|
||
| if ! echo "$SUBJECT" | grep -qE "$PATTERN"; then | ||
| if ! grep -qE "$PATTERN" <<<"$SUBJECT"; then | ||
| echo "Error: Invalid commit message format." | ||
| echo "Expected format: type(scope): subject" | ||
| echo "Expected: type(scope)(!): subject" | ||
| echo "Examples:" | ||
| echo " feat(core): add new feature" | ||
| echo " feat!: remove deprecated API (breaking change marker)" | ||
| echo " fix(ui): fix button color" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Block fixup!/WIP commits on main | ||
| BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "") | ||
| if [ "$BRANCH" = "main" ]; then | ||
| if echo "$FIRST_LINE" | grep -qiE "^(\[[A-Z]+-[0-9]+\] )?(fixup!|squash!|wip[: ])"; then | ||
| echo "Error: fixup!/squash!/WIP commits are not allowed on main." | ||
| echo "Create a feature branch instead." | ||
| exit 1 | ||
| fi | ||
| LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-commit-msg" | ||
| if [ -x "$LOCAL_HOOK" ]; then | ||
| exec "$LOCAL_HOOK" "$@" | ||
| fi | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,85 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Copyright 2026 ResQ Systems, Inc. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Licensed 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. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Repo-specific pre-push — runs after the canonical ResQ pre-push hook, which | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # execs this file when it exists. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The canonical hook is language-agnostic on purpose: it guards branch naming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # and force-pushes to main, and delegates anything needing a toolchain to here. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Rust/Anchor validation therefore lives in this file rather than in the shared | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # template, which is also what `.git-hooks/README.md` promises for `pre-push`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Skip with SKIP_CARGO_CHECK=1 for a push that cannot affect compilation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ "${SKIP_CARGO_CHECK:-0}" = "1" ] && { echo " Rust: skipped (SKIP_CARGO_CHECK=1)"; exit 0; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! command -v cargo >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Rust: cargo not found, skipping workspace check" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ZERO_SHA="0000000000000000000000000000000000000000" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The canonical hook forwards git's ref records, so read them rather than | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # assuming the push is of the checked-out branch: `git push origin feat/rust` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # from elsewhere, or `git push --all`, sends refs this hook would never see by | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # looking at HEAD alone. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Clippy compiles the *working tree*, so it can only ever speak for what is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # checked out. Rather than pretend otherwise, scope conservatively: lint | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # whenever a pushed ref is something other than HEAD, and use the cheap | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # file-diff skip only when the push is exactly the branch in hand. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run_check=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scoped=1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ ! -t 0 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while read -r _local_ref local_sha _remote_ref _remote_sha; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -z "$local_sha" ] && continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ "$local_sha" = "$ZERO_SHA" ] && continue # deletion: nothing to compile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$local_sha" != "$HEAD_SHA" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scoped=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run_check=1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$scoped" = "1" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| REMOTE="${1:-origin}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Compare against the tracking branch when there is one; a brand-new branch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # has no upstream yet, so fall back to the remote's main. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| REMOTE_BRANCH=$(git rev-parse --abbrev-ref "@{upstream}" 2>/dev/null || echo "$REMOTE/main") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if CHANGED_RS=$(git diff --name-only "$REMOTE_BRANCH"...HEAD -- '*.rs' 'Cargo.toml' 'Cargo.lock' 2>/dev/null); then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Skip validation for tag-only pushes. Lines 50-65 treat tag refs as branch refs. An annotated-tag push always has a Process only Proposed fix run_check=0
scoped=1
+saw_branch_ref=0
if [ ! -t 0 ]; then
- while read -r _local_ref local_sha _remote_ref _remote_sha; do
+ while read -r local_ref local_sha _remote_ref _remote_sha; do
[ -z "$local_sha" ] && continue
[ "$local_sha" = "$ZERO_SHA" ] && continue # deletion: nothing to compile
+ case "$local_ref" in
+ refs/heads/*) saw_branch_ref=1 ;;
+ *) continue ;;
+ esac
if [ "$local_sha" != "$HEAD_SHA" ]; then
scoped=0
run_check=1
fi
done
fi
-if [ "$scoped" = "1" ]; then
+if [ "$saw_branch_ref" = "1" ] && [ "$scoped" = "1" ]; then📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -n "$CHANGED_RS" ] && run_check=1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The range would not resolve — shallow clone, missing upstream. Fail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # toward running the check rather than silently skipping it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run_check=1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$run_check" = "1" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Clippy, not `cargo check`: AGENTS.md names | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `cargo clippy --workspace -- -D warnings` as this repo's lint gate, and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `cargo check` passes happily on the warnings clippy exists to reject. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Rust: cargo clippy --workspace -- -D warnings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! cargo clippy --workspace --quiet -- -D warnings; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "❌ cargo clippy failed. Fix the warnings before pushing." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Override with: git push --no-verify" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "✅ Rust workspace OK" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,71 +1,35 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| # | ||
| # Copyright 2026 ResQ | ||
| # | ||
| # Licensed 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. | ||
| # | ||
| # post-checkout | ||
| # | ||
| # Automatically installs dependencies when lock files change during branch checkout. | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Usage: | ||
| # .git-hooks/post-checkout PREV_HEAD NEW_HEAD IS_BRANCH_CHECKOUT | ||
| # | ||
| # Arguments: | ||
| # $1 - Previous HEAD commit hash. | ||
| # $2 - New HEAD commit hash. | ||
| # $3 - Flag indicating if it's a branch checkout (1) or file checkout (0). | ||
| # | ||
| # Exit codes: | ||
| # 0 Always. | ||
| # Canonical ResQ post-checkout shim — source: resq-software/dev. | ||
| # Notifies when lock files change so devs know to resync dependencies. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| [ -n "${GIT_HOOKS_SKIP:-}" ] && exit 0 | ||
|
|
||
| # PREV_HEAD stores the commit hash before checkout. | ||
| PREV_HEAD="${1:-}" | ||
| # NEW_HEAD stores the commit hash after checkout. | ||
| NEW_HEAD="${2:-}" | ||
| # IS_BRANCH_CHECKOUT is "1" if a branch was checked out, "0" otherwise. | ||
| IS_BRANCH_CHECKOUT="${3:-}" | ||
|
|
||
| # Only run on branch checkouts, not file checkouts | ||
| if [ "$IS_BRANCH_CHECKOUT" != "1" ]; then | ||
| exit 0 | ||
| fi | ||
| if [ "$IS_BRANCH_CHECKOUT" = "1" ] && [ "$PREV_HEAD" != "$NEW_HEAD" ]; then | ||
| CHANGED=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null || true) | ||
|
|
||
| # Skip if both heads are the same (no actual branch change) | ||
| if [ "$PREV_HEAD" = "$NEW_HEAD" ]; then | ||
| exit 0 | ||
| # Matched by basename rather than anchored to the repository root: a monorepo | ||
| # keeps lockfiles in nested workspaces (`libs/dotnet/flake.lock`, | ||
| # `programs/Cargo.lock`), and a root-anchored pattern ignores them all. | ||
| grep -qE "(^|/)Cargo\.lock$" <<<"$CHANGED" && echo "📦 Cargo.lock changed — run: cargo build" | ||
| # `\?` is a GNU BRE extension; BSD grep on macOS reads it as a literal `?`, | ||
| # so the optional-`b` form needs ERE to match on every developer's box. | ||
| grep -qE "(^|/)bun\.lockb?$" <<<"$CHANGED" && echo "📦 bun.lock changed — run: bun install" | ||
| grep -qE "(^|/)uv\.lock$" <<<"$CHANGED" && echo "📦 uv.lock changed — run: uv sync" | ||
| grep -qE "(^|/)flake\.lock$" <<<"$CHANGED" && echo "📦 flake.lock changed — exit and re-enter: nix develop" | ||
| fi | ||
|
WomB0ComB0 marked this conversation as resolved.
|
||
|
|
||
| # CHANGED stores the list of files that differ between the two heads. | ||
| CHANGED=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null) | ||
|
|
||
| # Check if Cargo.lock changed | ||
| if echo "$CHANGED" | grep -q "^Cargo\.lock$"; then | ||
| echo "📦 Cargo.lock changed — dependencies will update on next build" | ||
| LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-post-checkout" | ||
| if [ -x "$LOCAL_HOOK" ]; then | ||
| exec "$LOCAL_HOOK" "$@" | ||
| fi | ||
|
|
||
| # Check if bun.lockb changed | ||
| if echo "$CHANGED" | grep -q "^bun\.lockb$"; then | ||
| if [ -n "${SKIP_BUN_INSTALL:-}" ]; then | ||
| echo "📦 bun.lockb changed — skipping install (SKIP_BUN_INSTALL set)" | ||
| elif command -v bun >/dev/null 2>&1; then | ||
| echo "📦 bun.lockb changed — running bun install..." | ||
| bun install --frozen-lockfile 2>/dev/null || bun install | ||
| echo "✅ JS dependencies updated" | ||
| else | ||
| echo "⚠️ bun not found — run 'bun install' manually" | ||
| fi | ||
| fi | ||
| exit 0 | ||
Uh oh!
There was an error while loading. Please reload this page.