diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 014b7236..ef2baa76 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -48,44 +48,58 @@ jobs: # 3. Fetch and update code git fetch origin --prune - # Clear bits that can pin host-local stale copies over origin/main. - git ls-files -v | awk '/^[a-zS]/ {print substr($0,3)}' | while IFS= read -r f; do - [ -n "$f" ] || continue + + # Clear EVERY skip-worktree / assume-unchanged bit. Those bits make + # `git reset --hard` and `git diff` lie: the working tree can keep + # ancient file contents while git reports a clean checkout. + STICKY=0 + while IFS= read -r -d '' f; do + if git ls-files -v -- "$f" | grep -qE '^[a-zS]'; then + STICKY=$((STICKY + 1)) + fi git update-index --no-skip-worktree --no-assume-unchanged -- "$f" 2>/dev/null || true - done + done < <(git ls-files -z) + echo "Cleared skip-worktree/assume-unchanged bits (saw ${STICKY} sticky before clear)" + git reset --hard origin/main - # Drop stray untracked sources left on the host (keep secrets/env). - git clean -fd -e .env -e .env.local -e .env.production -e .env*.local -- src + + # Nuclear: delete src/ on disk, then restore ONLY from git objects. + # This is the only reliable way to drop host-local ghosts that survive + # reset/checkout when index flags or permissions pin old bytes. + rm -rf src git checkout -f HEAD -- src - # Working tree under src/ must match HEAD exactly (catches sticky host edits). - if ! git diff --exit-code -- src >/dev/null; then - echo "ERROR: src/ still differs from HEAD after reset/checkout:" >&2 - git diff --stat -- src >&2 || true - git checkout -f HEAD -- src - git diff --exit-code -- src - fi + # Drop other stray untracked junk under the app root (keep secrets/env). + git clean -fd -e .env -e .env.local -e .env.production -e .env*.local - # Prove critical sources match the git object (not just index timestamps). - NITRO_SRC="src/app/admin/import/furni/nitro-editor-dialog.tsx" - NITRO_EXPECTED="$(git rev-parse "HEAD:${NITRO_SRC}")" - NITRO_ACTUAL="$(git hash-object "${NITRO_SRC}")" - echo "nitro-editor-dialog blob expected=${NITRO_EXPECTED} actual=${NITRO_ACTUAL}" - if [ "${NITRO_EXPECTED}" != "${NITRO_ACTUAL}" ]; then - echo "ERROR: ${NITRO_SRC} content hash mismatch after checkout" >&2 - exit 1 - fi - if grep -nE '(^|[[:space:]])type Json\b|:\s*Json\b' "${NITRO_SRC}"; then - echo "ERROR: ${NITRO_SRC} still has a Json type annotation after checkout" >&2 + # Prove EVERY tracked file under src/ matches the HEAD blob (content hash). + # `git diff` alone is not enough when skip-worktree was previously set. + MISMATCH=0 + while IFS= read -r -d '' f; do + case "$f" in + src/*) ;; + *) continue ;; + esac + expected="$(git rev-parse "HEAD:${f}")" + actual="$(git hash-object "${f}")" + if [ "${expected}" != "${actual}" ]; then + echo "ERROR: content hash mismatch: ${f}" >&2 + echo " expected=${expected}" >&2 + echo " actual=${actual}" >&2 + MISMATCH=1 + fi + done < <(git ls-files -z) + if [ "${MISMATCH}" -ne 0 ]; then + echo "ERROR: src/ working tree does not match HEAD after nuclear checkout" >&2 exit 1 fi + echo "Verified all tracked src/ blobs match HEAD" # Drop incremental TS caches that can hide real type errors. rm -f tsconfig.tsbuildinfo .tsbuildinfo find . -maxdepth 3 -name '*.tsbuildinfo' -delete 2>/dev/null || true - # Wipe .next entirely — partial cache has caused next build TS to disagree - # with a clean `tsc` on the same sources (stale nitro/rooms typings). + # Wipe .next entirely — partial cache has disagreed with clean sources. rm -rf .output dist .next # Release tag for Sentry / logs (short git sha) diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts index afb9ac2f..fda4a6b2 100644 --- a/src/lib/deploy-workflow-contract.test.ts +++ b/src/lib/deploy-workflow-contract.test.ts @@ -24,11 +24,13 @@ describe("production deploy workflow", () => { expect(resetAt).toBeGreaterThan(reclaimAt); }); - it("verifies src/ and nitro blob match HEAD before typecheck", () => { - expect(workflow).toContain("git diff --exit-code -- src"); + it("nuclear-replaces src/ and verifies every tracked blob hash", () => { + expect(workflow).toContain("rm -rf src"); + expect(workflow).toContain("git checkout -f HEAD -- src"); expect(workflow).toContain("git hash-object"); - expect(workflow).toContain("nitro-editor-dialog.tsx"); - expect(workflow).toContain("*.tsbuildinfo"); + expect(workflow).toContain("no-skip-worktree"); + expect(workflow).toContain("no-assume-unchanged"); + expect(workflow).toContain("Verified all tracked src/ blobs match HEAD"); expect(workflow).toContain("pnpm typecheck"); });