1010 - " bun.lock"
1111 - " package.json"
1212 - " packages/*/package.json"
13+ - " .github/workflows/update-nix-hashes.yml"
1314 pull_request :
1415 paths :
1516 - " bun.lock"
1617 - " package.json"
1718 - " packages/*/package.json"
19+ - " .github/workflows/update-nix-hashes.yml"
1820
1921jobs :
2022 update-flake :
2527
2628 steps :
2729 - name : Checkout repository
28- uses : actions/checkout@v4
30+ uses : actions/checkout@v6
2931 with :
3032 token : ${{ secrets.GITHUB_TOKEN }}
3133 fetch-depth : 0
@@ -43,17 +45,17 @@ jobs:
4345 - name : Update ${{ env.TITLE }}
4446 run : |
4547 set -euo pipefail
46- echo "📦 Updating $TITLE..."
48+ echo "Updating $TITLE..."
4749 nix flake update
48- echo "✅ $TITLE updated successfully"
50+ echo "$TITLE updated successfully"
4951
5052 - name : Commit ${{ env.TITLE }} changes
5153 env :
5254 TARGET_BRANCH : ${{ github.head_ref || github.ref_name }}
5355 run : |
5456 set -euo pipefail
5557
56- echo "🔍 Checking for changes in tracked files..."
58+ echo "Checking for changes in tracked files..."
5759
5860 summarize() {
5961 local status="$1"
@@ -71,29 +73,29 @@ jobs:
7173 FILES=(flake.lock flake.nix)
7274 STATUS="$(git status --short -- "${FILES[@]}" || true)"
7375 if [ -z "$STATUS" ]; then
74- echo "✅ No changes detected."
76+ echo "No changes detected."
7577 summarize "no changes"
7678 exit 0
7779 fi
7880
79- echo "📝 Changes detected:"
81+ echo "Changes detected:"
8082 echo "$STATUS"
81- echo "🔗 Staging files..."
83+ echo "Staging files..."
8284 git add "${FILES[@]}"
83- echo "💾 Committing changes..."
85+ echo "Committing changes..."
8486 git commit -m "Update $TITLE"
85- echo "✅ Changes committed"
87+ echo "Changes committed"
8688
8789 BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
88- echo "🌳 Pulling latest from branch: $BRANCH"
89- git pull --rebase origin "$BRANCH"
90- echo "🚀 Pushing changes to branch: $BRANCH"
90+ echo "Pulling latest from branch: $BRANCH"
91+ git pull --rebase --autostash origin "$BRANCH"
92+ echo "Pushing changes to branch: $BRANCH"
9193 git push origin HEAD:"$BRANCH"
92- echo "✅ Changes pushed successfully"
94+ echo "Changes pushed successfully"
9395
9496 summarize "committed $(git rev-parse --short HEAD)"
9597
96- update -node-modules-hash :
98+ compute -node-modules-hash :
9799 needs : update-flake
98100 if : github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
99101 strategy :
@@ -111,11 +113,10 @@ jobs:
111113 runs-on : ${{ matrix.host }}
112114 env :
113115 SYSTEM : ${{ matrix.system }}
114- TITLE : node_modules hash (${{ matrix.system }})
115116
116117 steps :
117118 - name : Checkout repository
118- uses : actions/checkout@v4
119+ uses : actions/checkout@v6
119120 with :
120121 token : ${{ secrets.GITHUB_TOKEN }}
121122 fetch-depth : 0
@@ -125,6 +126,104 @@ jobs:
125126 - name : Setup Nix
126127 uses : nixbuild/nix-quick-install-action@v34
127128
129+ - name : Compute node_modules hash
130+ run : |
131+ set -euo pipefail
132+
133+ DUMMY="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
134+ HASH_FILE="nix/hashes.json"
135+ OUTPUT_FILE="hash-${SYSTEM}.txt"
136+
137+ export NIX_KEEP_OUTPUTS=1
138+ export NIX_KEEP_DERIVATIONS=1
139+
140+ BUILD_LOG=$(mktemp)
141+ TMP_JSON=$(mktemp)
142+ trap 'rm -f "$BUILD_LOG" "$TMP_JSON"' EXIT
143+
144+ if [ ! -f "$HASH_FILE" ]; then
145+ mkdir -p "$(dirname "$HASH_FILE")"
146+ echo '{"nodeModules":{}}' > "$HASH_FILE"
147+ fi
148+
149+ # Set dummy hash to force nix to rebuild and reveal correct hash
150+ jq --arg system "$SYSTEM" --arg value "$DUMMY" \
151+ '.nodeModules = (.nodeModules // {}) | .nodeModules[$system] = $value' "$HASH_FILE" > "$TMP_JSON"
152+ mv "$TMP_JSON" "$HASH_FILE"
153+
154+ MODULES_ATTR=".#packages.${SYSTEM}.default.node_modules"
155+ DRV_PATH="$(nix eval --raw "${MODULES_ATTR}.drvPath")"
156+
157+ echo "Building node_modules for ${SYSTEM} to discover correct hash..."
158+ echo "Attempting to realize derivation: ${DRV_PATH}"
159+ REALISE_OUT=$(nix-store --realise "$DRV_PATH" --keep-failed 2>&1 | tee "$BUILD_LOG" || true)
160+
161+ BUILD_PATH=$(echo "$REALISE_OUT" | grep "^/nix/store/" | head -n1 || true)
162+ CORRECT_HASH=""
163+
164+ if [ -n "$BUILD_PATH" ] && [ -d "$BUILD_PATH" ]; then
165+ echo "Realized node_modules output: $BUILD_PATH"
166+ CORRECT_HASH=$(nix hash path --sri "$BUILD_PATH" 2>/dev/null || true)
167+ fi
168+
169+ # Try to extract hash from build log
170+ if [ -z "$CORRECT_HASH" ]; then
171+ CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
172+ fi
173+
174+ if [ -z "$CORRECT_HASH" ]; then
175+ CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
176+ fi
177+
178+ # Try to hash from kept failed build directory
179+ if [ -z "$CORRECT_HASH" ]; then
180+ KEPT_DIR=$(grep -oE "build directory.*'[^']+'" "$BUILD_LOG" | grep -oE "'/[^']+'" | tr -d "'" | head -n1 || true)
181+ if [ -z "$KEPT_DIR" ]; then
182+ KEPT_DIR=$(grep -oE '/nix/var/nix/builds/[^ ]+' "$BUILD_LOG" | head -n1 || true)
183+ fi
184+
185+ if [ -n "$KEPT_DIR" ] && [ -d "$KEPT_DIR" ]; then
186+ HASH_PATH="$KEPT_DIR"
187+ [ -d "$KEPT_DIR/build" ] && HASH_PATH="$KEPT_DIR/build"
188+
189+ if [ -d "$HASH_PATH/node_modules" ]; then
190+ CORRECT_HASH=$(nix hash path --sri "$HASH_PATH" 2>/dev/null || true)
191+ fi
192+ fi
193+ fi
194+
195+ if [ -z "$CORRECT_HASH" ]; then
196+ echo "Failed to determine correct node_modules hash for ${SYSTEM}."
197+ cat "$BUILD_LOG"
198+ exit 1
199+ fi
200+
201+ echo "$CORRECT_HASH" > "$OUTPUT_FILE"
202+ echo "Hash for ${SYSTEM}: $CORRECT_HASH"
203+
204+ - name : Upload hash artifact
205+ uses : actions/upload-artifact@v6
206+ with :
207+ name : hash-${{ matrix.system }}
208+ path : hash-${{ matrix.system }}.txt
209+ retention-days : 1
210+
211+ commit-node-modules-hashes :
212+ needs : compute-node-modules-hash
213+ if : github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
214+ runs-on : blacksmith-4vcpu-ubuntu-2404
215+ env :
216+ TITLE : node_modules hashes
217+
218+ steps :
219+ - name : Checkout repository
220+ uses : actions/checkout@v6
221+ with :
222+ token : ${{ secrets.GITHUB_TOKEN }}
223+ fetch-depth : 0
224+ ref : ${{ github.head_ref || github.ref_name }}
225+ repository : ${{ github.event.pull_request.head.repo.full_name || github.repository }}
226+
128227 - name : Configure git
129228 run : |
130229 git config --global user.email "action@github.com"
@@ -135,22 +234,66 @@ jobs:
135234 TARGET_BRANCH : ${{ github.head_ref || github.ref_name }}
136235 run : |
137236 BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
138- git pull origin "$BRANCH"
237+ git pull --rebase --autostash origin "$BRANCH"
139238
140- - name : Update ${{ env.TITLE }}
239+ - name : Download all hash artifacts
240+ uses : actions/download-artifact@v7
241+ with :
242+ pattern : hash-*
243+ merge-multiple : true
244+
245+ - name : Merge hashes into hashes.json
141246 run : |
142247 set -euo pipefail
143- echo "🔄 Updating $TITLE..."
144- nix/scripts/update-hashes.sh
145- echo "✅ $TITLE updated successfully"
248+
249+ HASH_FILE="nix/hashes.json"
250+
251+ if [ ! -f "$HASH_FILE" ]; then
252+ mkdir -p "$(dirname "$HASH_FILE")"
253+ echo '{"nodeModules":{}}' > "$HASH_FILE"
254+ fi
255+
256+ echo "Merging hashes into ${HASH_FILE}..."
257+
258+ shopt -s nullglob
259+ files=(hash-*.txt)
260+ if [ ${#files[@]} -eq 0 ]; then
261+ echo "No hash files found, nothing to update"
262+ exit 0
263+ fi
264+
265+ EXPECTED_SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
266+ for sys in $EXPECTED_SYSTEMS; do
267+ if [ ! -f "hash-${sys}.txt" ]; then
268+ echo "WARNING: Missing hash file for $sys"
269+ fi
270+ done
271+
272+ for f in "${files[@]}"; do
273+ system="${f#hash-}"
274+ system="${system%.txt}"
275+ hash=$(cat "$f")
276+ if [ -z "$hash" ]; then
277+ echo "WARNING: Empty hash for $system, skipping"
278+ continue
279+ fi
280+ echo " $system: $hash"
281+ jq --arg sys "$system" --arg h "$hash" \
282+ '.nodeModules = (.nodeModules // {}) | .nodeModules[$sys] = $h' "$HASH_FILE" > "${HASH_FILE}.tmp"
283+ mv "${HASH_FILE}.tmp" "$HASH_FILE"
284+ done
285+
286+ echo "All hashes merged:"
287+ cat "$HASH_FILE"
146288
147289 - name : Commit ${{ env.TITLE }} changes
148290 env :
149291 TARGET_BRANCH : ${{ github.head_ref || github.ref_name }}
150292 run : |
151293 set -euo pipefail
152294
153- echo "🔍 Checking for changes in tracked files..."
295+ HASH_FILE="nix/hashes.json"
296+ echo "Checking for changes..."
154297
155298 summarize() {
156299 local status="$1"
@@ -166,27 +309,22 @@ jobs:
166309 echo "" >> "$GITHUB_STEP_SUMMARY"
167310 }
168311
169- FILES=(nix/hashes.json )
312+ FILES=("$HASH_FILE" )
170313 STATUS="$(git status --short -- "${FILES[@]}" || true)"
171314 if [ -z "$STATUS" ]; then
172- echo "✅ No changes detected."
315+ echo "No changes detected."
173316 summarize "no changes"
174317 exit 0
175318 fi
176319
177- echo "📝 Changes detected:"
320+ echo "Changes detected:"
178321 echo "$STATUS"
179- echo "🔗 Staging files..."
180322 git add "${FILES[@]}"
181- echo "💾 Committing changes..."
182323 git commit -m "Update $TITLE"
183- echo "✅ Changes committed"
184324
185325 BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
186- echo "🌳 Pulling latest from branch: $BRANCH"
187- git pull --rebase origin "$BRANCH"
188- echo "🚀 Pushing changes to branch: $BRANCH"
326+ git pull --rebase --autostash origin "$BRANCH"
189327 git push origin HEAD:"$BRANCH"
190- echo "✅ Changes pushed successfully"
328+ echo "Changes pushed successfully"
191329
192330 summarize "committed $(git rev-parse --short HEAD)"
0 commit comments