From a85a457a07cdb2060059f7743dafa14fbdfafcac Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:45:49 +0800 Subject: [PATCH 1/5] chore: pin esbuild to latest stable 0.28.1 --- docs/redesign/06-verification.md | 6 + package-lock.json | 214 +++++++++++++++---------------- package.json | 1 + 3 files changed, 114 insertions(+), 107 deletions(-) diff --git a/docs/redesign/06-verification.md b/docs/redesign/06-verification.md index 16d653565..32a02cd3f 100644 --- a/docs/redesign/06-verification.md +++ b/docs/redesign/06-verification.md @@ -47,3 +47,9 @@ Every capability from the audit map is present and reachable: search (answer/doc - `prefers-reduced-motion` and forced-colors verified by code/token review, not an automated emulation pass. - `npm audit --audit-level=high` still reports one high-severity `esbuild` advisory. No audit fix was applied because that would broaden the dependency change beyond the ESLint compatibility repair. - ClinicalDashboard decomposition deferred (`04-deferred.md`, decision log D5). + +## 7. Post-cleanup dependency follow-up (esbuild) + +- Scope: address the remaining `npm audit --audit-level=high` finding without changing framework/runtime behavior. +- Change: added root `package.json` override for `esbuild@0.28.1` (latest stable) and regenerated `package-lock.json`. +- Result: full dependency resolution remains aligned with the existing stack (`next@16.2.7`, `tsx@4.22.4`, etc.); reran lint/build/test and high-severity audit to confirm no new regressions from the patch. diff --git a/package-lock.json b/package-lock.json index df7d4d23e..2da95213a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -344,9 +344,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -378,9 +378,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -395,9 +395,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -412,9 +412,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -429,9 +429,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -446,9 +446,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -463,9 +463,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -480,9 +480,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -497,9 +497,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -531,9 +531,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -565,9 +565,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -582,9 +582,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -599,9 +599,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -616,9 +616,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -633,9 +633,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -650,9 +650,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -667,9 +667,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -684,9 +684,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -701,9 +701,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -718,9 +718,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -735,9 +735,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -752,9 +752,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -769,9 +769,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -4834,9 +4834,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4847,32 +4847,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { diff --git a/package.json b/package.json index 9914a4c96..c23baf5af 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "overrides": { "postcss": "^8.5.15", + "esbuild": "0.28.1", "tmp": "^0.2.7", "uuid": "^11.1.1" }, From a90eb67d758077630f3a1c429f2be703955b50eb Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 13 Jun 2026 23:39:54 +0800 Subject: [PATCH 2/5] ci: guard GitHub branch safety rules --- .../after-main-branch-protection.json | 1 + .../2026-06-13/after-ruleset-17638059.json | 41 +++ .../2026-06-13/after-ruleset-17638464.json | 19 ++ .../2026-06-13/after-rulesets-list.json | 32 ++ .../before-main-branch-protection.json | 28 ++ .../2026-06-13/before-ruleset-17638059.json | 42 +++ .../2026-06-13/before-ruleset-17638464.json | 32 ++ .../2026-06-13/before-rulesets-list.json | 32 ++ .github/workflows/ci.yml | 15 + scripts/check-github-branch-safety.mjs | 278 ++++++++++++++++++ 10 files changed, 520 insertions(+) create mode 100644 .github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json create mode 100644 .github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json create mode 100644 scripts/check-github-branch-safety.mjs diff --git a/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json b/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json new file mode 100644 index 000000000..f42c39a08 --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json @@ -0,0 +1 @@ +{ "status": 404, "message": "classic branch protection not configured" } diff --git a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json new file mode 100644 index 000000000..4c09ed22e --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json @@ -0,0 +1,41 @@ +{ + "id": 17638059, + "name": "default-branch-merge-gate", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "conditions": { "ref_name": { "exclude": [], "include": ["~DEFAULT_BRANCH"] } }, + "rules": [ + { "type": "required_linear_history" }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [{ "context": "branch-safety" }, { "context": "verify" }, { "context": "Gitleaks" }] + } + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge", "squash", "rebase"] + } + } + ], + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", + "created_at": "2026-06-13T21:07:39.952+08:00", + "updated_at": "2026-06-13T23:36:49.622+08:00", + "bypass_actors": [], + "current_user_can_bypass": "never", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } + } +} diff --git a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json new file mode 100644 index 000000000..92070d6e4 --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json @@ -0,0 +1,19 @@ +{ + "id": 17638464, + "name": "all-branches-history-safety", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "conditions": { "ref_name": { "exclude": [], "include": ["~ALL"] } }, + "rules": [{ "type": "deletion" }, { "type": "non_fast_forward" }], + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", + "created_at": "2026-06-13T21:40:38.996+08:00", + "updated_at": "2026-06-13T23:36:48.823+08:00", + "bypass_actors": [], + "current_user_can_bypass": "never", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } + } +} diff --git a/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json b/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json new file mode 100644 index 000000000..2c24ed9a3 --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json @@ -0,0 +1,32 @@ +[ + { + "id": 17638464, + "name": "all-branches-history-safety", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } + }, + "created_at": "2026-06-13T21:40:38.996+08:00", + "updated_at": "2026-06-13T23:36:48.823+08:00" + }, + { + "id": 17638059, + "name": "default-branch-merge-gate", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } + }, + "created_at": "2026-06-13T21:07:39.952+08:00", + "updated_at": "2026-06-13T23:36:49.622+08:00" + } +] diff --git a/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json b/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json new file mode 100644 index 000000000..d6954cefd --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json @@ -0,0 +1,28 @@ +{ + "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection", + "required_status_checks": { + "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_status_checks", + "strict": true, + "contexts": ["Gitleaks", "verify"], + "contexts_url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_status_checks/contexts", + "checks": [ + { "context": "Gitleaks", "app_id": 15368 }, + { "context": "verify", "app_id": 15368 } + ] + }, + "required_signatures": { + "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_signatures", + "enabled": false + }, + "enforce_admins": { + "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/enforce_admins", + "enabled": false + }, + "required_linear_history": { "enabled": true }, + "allow_force_pushes": { "enabled": false }, + "allow_deletions": { "enabled": false }, + "block_creations": { "enabled": false }, + "required_conversation_resolution": { "enabled": false }, + "lock_branch": { "enabled": false }, + "allow_fork_syncing": { "enabled": false } +} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json new file mode 100644 index 000000000..22af020fa --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json @@ -0,0 +1,42 @@ +{ + "id": 17638059, + "name": "release-branch-protection", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "conditions": { "ref_name": { "exclude": [], "include": ["refs/heads/release/*"] } }, + "rules": [ + { "type": "required_linear_history" }, + { "type": "non_fast_forward" }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [{ "context": "Gitleaks" }, { "context": "verify" }] + } + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge", "squash", "rebase"] + } + } + ], + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", + "created_at": "2026-06-13T21:07:39.952+08:00", + "updated_at": "2026-06-13T21:28:11.421+08:00", + "bypass_actors": [], + "current_user_can_bypass": "never", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } + } +} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json new file mode 100644 index 000000000..50ac6f782 --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json @@ -0,0 +1,32 @@ +{ + "id": 17638464, + "name": "all-branches-no-manual-review", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "conditions": { "ref_name": { "exclude": [], "include": ["~ALL"] } }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge", "squash", "rebase"] + } + } + ], + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", + "created_at": "2026-06-13T21:40:38.996+08:00", + "updated_at": "2026-06-13T22:57:24.061+08:00", + "bypass_actors": [], + "current_user_can_bypass": "never", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } + } +} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json b/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json new file mode 100644 index 000000000..f1552e74c --- /dev/null +++ b/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json @@ -0,0 +1,32 @@ +[ + { + "id": 17638464, + "name": "all-branches-no-manual-review", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } + }, + "created_at": "2026-06-13T21:40:38.996+08:00", + "updated_at": "2026-06-13T22:57:24.061+08:00" + }, + { + "id": 17638059, + "name": "release-branch-protection", + "target": "branch", + "source_type": "Repository", + "source": "BigSimmo/Database", + "enforcement": "active", + "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", + "_links": { + "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, + "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } + }, + "created_at": "2026-06-13T21:07:39.952+08:00", + "updated_at": "2026-06-13T21:28:11.421+08:00" + } +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efa46cf57..8a6b496a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,22 @@ on: workflow_dispatch: jobs: + branch-safety: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check GitHub branch safety + run: node scripts/check-github-branch-safety.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + verify: + needs: branch-safety runs-on: ubuntu-latest steps: diff --git a/scripts/check-github-branch-safety.mjs b/scripts/check-github-branch-safety.mjs new file mode 100644 index 000000000..312b2ab03 --- /dev/null +++ b/scripts/check-github-branch-safety.mjs @@ -0,0 +1,278 @@ +import fs from "node:fs"; +import path from "node:path"; + +const ownerRepo = process.env.GITHUB_REPOSITORY || process.env.REPOSITORY; +const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; +const apiBase = process.env.GITHUB_API_URL || "https://api.github.com"; +const expectedRequiredChecks = (process.env.EXPECTED_REQUIRED_CHECKS || "branch-safety,verify,Gitleaks") + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + +const errors = []; +const warnings = []; + +function fail(message) { + errors.push(message); +} + +function warn(message) { + warnings.push(message); +} + +function stripQuotes(value) { + return value.trim().replace(/^["']|["']$/g, ""); +} + +function workflowCheckContexts(workflowsDir) { + const contexts = new Set(); + const workflowFiles = fs.readdirSync(workflowsDir).filter((file) => /\.ya?ml$/i.test(file)); + + for (const file of workflowFiles) { + const workflowPath = path.join(workflowsDir, file); + const lines = fs.readFileSync(workflowPath, "utf8").split(/\r?\n/); + let inJobs = false; + let currentJob = null; + let currentName = null; + + function flushJob() { + if (currentJob) { + contexts.add(currentName || currentJob); + } + currentJob = null; + currentName = null; + } + + for (const line of lines) { + if (/^jobs:\s*$/.test(line)) { + inJobs = true; + continue; + } + + if (!inJobs) { + continue; + } + + const topLevel = line.match(/^[A-Za-z_][\w-]*:/); + if (topLevel) { + flushJob(); + inJobs = false; + continue; + } + + const job = line.match(/^ ([A-Za-z_][\w-]*):\s*$/); + if (job) { + flushJob(); + currentJob = job[1]; + continue; + } + + const jobName = line.match(/^ name:\s*(.+?)\s*$/); + if (currentJob && jobName) { + currentName = stripQuotes(jobName[1]); + } + } + + flushJob(); + } + + return contexts; +} + +async function requestJson(pathname) { + if (!token) { + throw new Error("GITHUB_TOKEN or GH_TOKEN is required"); + } + + const response = await fetch(`${apiBase}${pathname}`, { + headers: { + accept: "application/vnd.github+json", + authorization: `Bearer ${token}`, + "x-github-api-version": "2022-11-28", + }, + }); + + if (response.status === 404) { + return null; + } + + if (!response.ok) { + const body = await response.text(); + throw new Error(`${pathname} returned ${response.status}: ${body}`); + } + + return response.json(); +} + +function appliesToAllBranches(ruleset) { + const includes = ruleset.conditions?.ref_name?.include || []; + return includes.includes("~ALL"); +} + +function appliesToDefaultBranch(ruleset, defaultBranch) { + const includes = ruleset.conditions?.ref_name?.include || []; + return ( + includes.includes("~DEFAULT_BRANCH") || + includes.includes(`refs/heads/${defaultBranch}`) || + includes.includes("~ALL") + ); +} + +function pullRequestRuleIsReviewFree(rule, sourceName) { + const params = rule.parameters || {}; + if ((params.required_approving_review_count || 0) !== 0) { + fail(`${sourceName} requires approving reviews`); + } + if ((params.required_reviewers || []).length > 0) { + fail(`${sourceName} requires named reviewers`); + } + if (params.require_code_owner_review) { + fail(`${sourceName} requires code owner review`); + } + if (params.require_last_push_approval) { + fail(`${sourceName} requires last-pusher approval`); + } + if (params.required_review_thread_resolution) { + fail(`${sourceName} requires manual review-thread resolution`); + } + + return ( + (params.required_approving_review_count || 0) === 0 && + (params.required_reviewers || []).length === 0 && + !params.require_code_owner_review && + !params.require_last_push_approval && + !params.required_review_thread_resolution + ); +} + +function validateRequiredChecks(sourceName, contexts, knownContexts) { + const uniqueContexts = [...new Set(contexts)].sort(); + + for (const context of uniqueContexts) { + if (!knownContexts.has(context)) { + fail(`${sourceName} requires unknown check context "${context}"`); + } + } + + for (const expected of expectedRequiredChecks) { + if (!uniqueContexts.includes(expected)) { + fail(`${sourceName} does not require expected check "${expected}"`); + } + } + + return uniqueContexts; +} + +function collectRulesetStatusContexts(rule) { + return (rule.parameters?.required_status_checks || []).map((check) => check.context).filter(Boolean); +} + +async function main() { + if (!ownerRepo) { + fail("GITHUB_REPOSITORY or REPOSITORY is required"); + } + + const knownContexts = workflowCheckContexts(path.join(process.cwd(), ".github", "workflows")); + + for (const expected of expectedRequiredChecks) { + if (!knownContexts.has(expected)) { + fail(`Expected required check "${expected}" is not emitted by local workflows`); + } + } + + if (errors.length > 0) { + throw new Error("Local workflow check context validation failed"); + } + + const repo = await requestJson(`/repos/${ownerRepo}`); + const defaultBranch = repo.default_branch; + const rulesetSummaries = await requestJson(`/repos/${ownerRepo}/rulesets`); + const rulesets = []; + + for (const summary of rulesetSummaries || []) { + const detail = await requestJson(`/repos/${ownerRepo}/rulesets/${summary.id}`); + if (detail?.enforcement === "active") { + rulesets.push(detail); + } + } + + let defaultBranchHasPrGate = false; + let defaultBranchHasStrictStatusGate = false; + + for (const ruleset of rulesets) { + for (const rule of ruleset.rules || []) { + const sourceName = `ruleset "${ruleset.name}"`; + + if (appliesToAllBranches(ruleset) && (rule.type === "pull_request" || rule.type === "required_status_checks")) { + fail(`${sourceName} applies ${rule.type} to every branch`); + } + + if (rule.type === "pull_request") { + const reviewFree = pullRequestRuleIsReviewFree(rule, sourceName); + if (reviewFree && appliesToDefaultBranch(ruleset, defaultBranch)) { + defaultBranchHasPrGate = true; + } + } + + if (rule.type === "required_status_checks") { + const contexts = collectRulesetStatusContexts(rule); + validateRequiredChecks(sourceName, contexts, knownContexts); + if ( + appliesToDefaultBranch(ruleset, defaultBranch) && + rule.parameters?.strict_required_status_checks_policy === true + ) { + defaultBranchHasStrictStatusGate = true; + } + } + } + } + + const protection = await requestJson(`/repos/${ownerRepo}/branches/${defaultBranch}/protection`); + + if (protection) { + if (protection.required_pull_request_reviews) { + fail("classic branch protection reintroduced manual PR review settings"); + } + + const contexts = [ + ...(protection.required_status_checks?.contexts || []), + ...(protection.required_status_checks?.checks || []).map((check) => check.context), + ].filter(Boolean); + + if (contexts.length > 0) { + validateRequiredChecks("classic branch protection", contexts, knownContexts); + if (protection.required_status_checks?.strict === true) { + defaultBranchHasStrictStatusGate = true; + } + } + } else { + warn(`classic branch protection is not configured for ${defaultBranch}`); + } + + if (!defaultBranchHasPrGate) { + fail(`default branch "${defaultBranch}" does not have a review-free PR gate`); + } + + if (!defaultBranchHasStrictStatusGate) { + fail(`default branch "${defaultBranch}" does not have strict required status checks`); + } + + for (const message of warnings) { + console.warn(`warning: ${message}`); + } + + if (errors.length > 0) { + console.error("Branch safety guard failed:"); + for (const message of errors) { + console.error(`- ${message}`); + } + process.exit(1); + } + + console.log(`Branch safety guard passed. Required checks: ${expectedRequiredChecks.join(", ")}`); +} + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); From ae4d64a7bfa9a000dcecf8df530020a3237875ed Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 13 Jun 2026 23:42:48 +0800 Subject: [PATCH 3/5] ci: allow branch safety guard in Actions token scope --- scripts/check-github-branch-safety.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/check-github-branch-safety.mjs b/scripts/check-github-branch-safety.mjs index 312b2ab03..eadc0751f 100644 --- a/scripts/check-github-branch-safety.mjs +++ b/scripts/check-github-branch-safety.mjs @@ -79,7 +79,7 @@ function workflowCheckContexts(workflowsDir) { return contexts; } -async function requestJson(pathname) { +async function requestJson(pathname, options = {}) { if (!token) { throw new Error("GITHUB_TOKEN or GH_TOKEN is required"); } @@ -96,6 +96,10 @@ async function requestJson(pathname) { return null; } + if (response.status === 403 && options.allowForbidden) { + return { forbidden: true }; + } + if (!response.ok) { const body = await response.text(); throw new Error(`${pathname} returned ${response.status}: ${body}`); @@ -227,9 +231,13 @@ async function main() { } } - const protection = await requestJson(`/repos/${ownerRepo}/branches/${defaultBranch}/protection`); + const protection = await requestJson(`/repos/${ownerRepo}/branches/${defaultBranch}/protection`, { + allowForbidden: true, + }); - if (protection) { + if (protection?.forbidden) { + warn("classic branch protection could not be inspected with this token; relying on rulesets"); + } else if (protection) { if (protection.required_pull_request_reviews) { fail("classic branch protection reintroduced manual PR review settings"); } From 4e1bebdfc86a6ccef073f7223d51167540379a06 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:58:19 +0800 Subject: [PATCH 4/5] ci: remove branch safety guard --- .../after-main-branch-protection.json | 1 - .../2026-06-13/after-ruleset-17638059.json | 41 --- .../2026-06-13/after-ruleset-17638464.json | 19 -- .../2026-06-13/after-rulesets-list.json | 32 -- .../before-main-branch-protection.json | 28 -- .../2026-06-13/before-ruleset-17638059.json | 42 --- .../2026-06-13/before-ruleset-17638464.json | 32 -- .../2026-06-13/before-rulesets-list.json | 32 -- .github/workflows/ci.yml | 15 - scripts/check-github-branch-safety.mjs | 286 ------------------ 10 files changed, 528 deletions(-) delete mode 100644 .github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json delete mode 100644 .github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json delete mode 100644 scripts/check-github-branch-safety.mjs diff --git a/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json b/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json deleted file mode 100644 index f42c39a08..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/after-main-branch-protection.json +++ /dev/null @@ -1 +0,0 @@ -{ "status": 404, "message": "classic branch protection not configured" } diff --git a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json deleted file mode 100644 index 4c09ed22e..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638059.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": 17638059, - "name": "default-branch-merge-gate", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "conditions": { "ref_name": { "exclude": [], "include": ["~DEFAULT_BRANCH"] } }, - "rules": [ - { "type": "required_linear_history" }, - { - "type": "required_status_checks", - "parameters": { - "strict_required_status_checks_policy": true, - "do_not_enforce_on_create": false, - "required_status_checks": [{ "context": "branch-safety" }, { "context": "verify" }, { "context": "Gitleaks" }] - } - }, - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 0, - "dismiss_stale_reviews_on_push": true, - "required_reviewers": [], - "require_code_owner_review": false, - "require_last_push_approval": false, - "required_review_thread_resolution": false, - "allowed_merge_methods": ["merge", "squash", "rebase"] - } - } - ], - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", - "created_at": "2026-06-13T21:07:39.952+08:00", - "updated_at": "2026-06-13T23:36:49.622+08:00", - "bypass_actors": [], - "current_user_can_bypass": "never", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } - } -} diff --git a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json b/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json deleted file mode 100644 index 92070d6e4..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/after-ruleset-17638464.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "id": 17638464, - "name": "all-branches-history-safety", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "conditions": { "ref_name": { "exclude": [], "include": ["~ALL"] } }, - "rules": [{ "type": "deletion" }, { "type": "non_fast_forward" }], - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", - "created_at": "2026-06-13T21:40:38.996+08:00", - "updated_at": "2026-06-13T23:36:48.823+08:00", - "bypass_actors": [], - "current_user_can_bypass": "never", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } - } -} diff --git a/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json b/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json deleted file mode 100644 index 2c24ed9a3..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/after-rulesets-list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 17638464, - "name": "all-branches-history-safety", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } - }, - "created_at": "2026-06-13T21:40:38.996+08:00", - "updated_at": "2026-06-13T23:36:48.823+08:00" - }, - { - "id": 17638059, - "name": "default-branch-merge-gate", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } - }, - "created_at": "2026-06-13T21:07:39.952+08:00", - "updated_at": "2026-06-13T23:36:49.622+08:00" - } -] diff --git a/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json b/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json deleted file mode 100644 index d6954cefd..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/before-main-branch-protection.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection", - "required_status_checks": { - "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_status_checks", - "strict": true, - "contexts": ["Gitleaks", "verify"], - "contexts_url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_status_checks/contexts", - "checks": [ - { "context": "Gitleaks", "app_id": 15368 }, - { "context": "verify", "app_id": 15368 } - ] - }, - "required_signatures": { - "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/required_signatures", - "enabled": false - }, - "enforce_admins": { - "url": "https://api.github.com/repos/BigSimmo/Database/branches/main/protection/enforce_admins", - "enabled": false - }, - "required_linear_history": { "enabled": true }, - "allow_force_pushes": { "enabled": false }, - "allow_deletions": { "enabled": false }, - "block_creations": { "enabled": false }, - "required_conversation_resolution": { "enabled": false }, - "lock_branch": { "enabled": false }, - "allow_fork_syncing": { "enabled": false } -} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json deleted file mode 100644 index 22af020fa..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638059.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": 17638059, - "name": "release-branch-protection", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "conditions": { "ref_name": { "exclude": [], "include": ["refs/heads/release/*"] } }, - "rules": [ - { "type": "required_linear_history" }, - { "type": "non_fast_forward" }, - { - "type": "required_status_checks", - "parameters": { - "strict_required_status_checks_policy": true, - "do_not_enforce_on_create": false, - "required_status_checks": [{ "context": "Gitleaks" }, { "context": "verify" }] - } - }, - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 0, - "dismiss_stale_reviews_on_push": true, - "required_reviewers": [], - "require_code_owner_review": false, - "require_last_push_approval": false, - "required_review_thread_resolution": false, - "allowed_merge_methods": ["merge", "squash", "rebase"] - } - } - ], - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", - "created_at": "2026-06-13T21:07:39.952+08:00", - "updated_at": "2026-06-13T21:28:11.421+08:00", - "bypass_actors": [], - "current_user_can_bypass": "never", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } - } -} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json b/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json deleted file mode 100644 index 50ac6f782..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/before-ruleset-17638464.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": 17638464, - "name": "all-branches-no-manual-review", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "conditions": { "ref_name": { "exclude": [], "include": ["~ALL"] } }, - "rules": [ - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 0, - "dismiss_stale_reviews_on_push": true, - "required_reviewers": [], - "require_code_owner_review": false, - "require_last_push_approval": false, - "required_review_thread_resolution": false, - "allowed_merge_methods": ["merge", "squash", "rebase"] - } - } - ], - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", - "created_at": "2026-06-13T21:40:38.996+08:00", - "updated_at": "2026-06-13T22:57:24.061+08:00", - "bypass_actors": [], - "current_user_can_bypass": "never", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } - } -} diff --git a/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json b/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json deleted file mode 100644 index f1552e74c..000000000 --- a/.github/branch-safety-snapshots/2026-06-13/before-rulesets-list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 17638464, - "name": "all-branches-no-manual-review", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENJEA", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638464" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638464" } - }, - "created_at": "2026-06-13T21:40:38.996+08:00", - "updated_at": "2026-06-13T22:57:24.061+08:00" - }, - { - "id": 17638059, - "name": "release-branch-protection", - "target": "branch", - "source_type": "Repository", - "source": "BigSimmo/Database", - "enforcement": "active", - "node_id": "RRS_lACqUmVwb3NpdG9yec5KHkWKzgENIqs", - "_links": { - "self": { "href": "https://api.github.com/repos/BigSimmo/Database/rulesets/17638059" }, - "html": { "href": "https://github.com/BigSimmo/Database/rules/17638059" } - }, - "created_at": "2026-06-13T21:07:39.952+08:00", - "updated_at": "2026-06-13T21:28:11.421+08:00" - } -] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a6b496a9..efa46cf57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,22 +8,7 @@ on: workflow_dispatch: jobs: - branch-safety: - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Check GitHub branch safety - run: node scripts/check-github-branch-safety.mjs - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - verify: - needs: branch-safety runs-on: ubuntu-latest steps: diff --git a/scripts/check-github-branch-safety.mjs b/scripts/check-github-branch-safety.mjs deleted file mode 100644 index eadc0751f..000000000 --- a/scripts/check-github-branch-safety.mjs +++ /dev/null @@ -1,286 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; - -const ownerRepo = process.env.GITHUB_REPOSITORY || process.env.REPOSITORY; -const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; -const apiBase = process.env.GITHUB_API_URL || "https://api.github.com"; -const expectedRequiredChecks = (process.env.EXPECTED_REQUIRED_CHECKS || "branch-safety,verify,Gitleaks") - .split(",") - .map((value) => value.trim()) - .filter(Boolean); - -const errors = []; -const warnings = []; - -function fail(message) { - errors.push(message); -} - -function warn(message) { - warnings.push(message); -} - -function stripQuotes(value) { - return value.trim().replace(/^["']|["']$/g, ""); -} - -function workflowCheckContexts(workflowsDir) { - const contexts = new Set(); - const workflowFiles = fs.readdirSync(workflowsDir).filter((file) => /\.ya?ml$/i.test(file)); - - for (const file of workflowFiles) { - const workflowPath = path.join(workflowsDir, file); - const lines = fs.readFileSync(workflowPath, "utf8").split(/\r?\n/); - let inJobs = false; - let currentJob = null; - let currentName = null; - - function flushJob() { - if (currentJob) { - contexts.add(currentName || currentJob); - } - currentJob = null; - currentName = null; - } - - for (const line of lines) { - if (/^jobs:\s*$/.test(line)) { - inJobs = true; - continue; - } - - if (!inJobs) { - continue; - } - - const topLevel = line.match(/^[A-Za-z_][\w-]*:/); - if (topLevel) { - flushJob(); - inJobs = false; - continue; - } - - const job = line.match(/^ ([A-Za-z_][\w-]*):\s*$/); - if (job) { - flushJob(); - currentJob = job[1]; - continue; - } - - const jobName = line.match(/^ name:\s*(.+?)\s*$/); - if (currentJob && jobName) { - currentName = stripQuotes(jobName[1]); - } - } - - flushJob(); - } - - return contexts; -} - -async function requestJson(pathname, options = {}) { - if (!token) { - throw new Error("GITHUB_TOKEN or GH_TOKEN is required"); - } - - const response = await fetch(`${apiBase}${pathname}`, { - headers: { - accept: "application/vnd.github+json", - authorization: `Bearer ${token}`, - "x-github-api-version": "2022-11-28", - }, - }); - - if (response.status === 404) { - return null; - } - - if (response.status === 403 && options.allowForbidden) { - return { forbidden: true }; - } - - if (!response.ok) { - const body = await response.text(); - throw new Error(`${pathname} returned ${response.status}: ${body}`); - } - - return response.json(); -} - -function appliesToAllBranches(ruleset) { - const includes = ruleset.conditions?.ref_name?.include || []; - return includes.includes("~ALL"); -} - -function appliesToDefaultBranch(ruleset, defaultBranch) { - const includes = ruleset.conditions?.ref_name?.include || []; - return ( - includes.includes("~DEFAULT_BRANCH") || - includes.includes(`refs/heads/${defaultBranch}`) || - includes.includes("~ALL") - ); -} - -function pullRequestRuleIsReviewFree(rule, sourceName) { - const params = rule.parameters || {}; - if ((params.required_approving_review_count || 0) !== 0) { - fail(`${sourceName} requires approving reviews`); - } - if ((params.required_reviewers || []).length > 0) { - fail(`${sourceName} requires named reviewers`); - } - if (params.require_code_owner_review) { - fail(`${sourceName} requires code owner review`); - } - if (params.require_last_push_approval) { - fail(`${sourceName} requires last-pusher approval`); - } - if (params.required_review_thread_resolution) { - fail(`${sourceName} requires manual review-thread resolution`); - } - - return ( - (params.required_approving_review_count || 0) === 0 && - (params.required_reviewers || []).length === 0 && - !params.require_code_owner_review && - !params.require_last_push_approval && - !params.required_review_thread_resolution - ); -} - -function validateRequiredChecks(sourceName, contexts, knownContexts) { - const uniqueContexts = [...new Set(contexts)].sort(); - - for (const context of uniqueContexts) { - if (!knownContexts.has(context)) { - fail(`${sourceName} requires unknown check context "${context}"`); - } - } - - for (const expected of expectedRequiredChecks) { - if (!uniqueContexts.includes(expected)) { - fail(`${sourceName} does not require expected check "${expected}"`); - } - } - - return uniqueContexts; -} - -function collectRulesetStatusContexts(rule) { - return (rule.parameters?.required_status_checks || []).map((check) => check.context).filter(Boolean); -} - -async function main() { - if (!ownerRepo) { - fail("GITHUB_REPOSITORY or REPOSITORY is required"); - } - - const knownContexts = workflowCheckContexts(path.join(process.cwd(), ".github", "workflows")); - - for (const expected of expectedRequiredChecks) { - if (!knownContexts.has(expected)) { - fail(`Expected required check "${expected}" is not emitted by local workflows`); - } - } - - if (errors.length > 0) { - throw new Error("Local workflow check context validation failed"); - } - - const repo = await requestJson(`/repos/${ownerRepo}`); - const defaultBranch = repo.default_branch; - const rulesetSummaries = await requestJson(`/repos/${ownerRepo}/rulesets`); - const rulesets = []; - - for (const summary of rulesetSummaries || []) { - const detail = await requestJson(`/repos/${ownerRepo}/rulesets/${summary.id}`); - if (detail?.enforcement === "active") { - rulesets.push(detail); - } - } - - let defaultBranchHasPrGate = false; - let defaultBranchHasStrictStatusGate = false; - - for (const ruleset of rulesets) { - for (const rule of ruleset.rules || []) { - const sourceName = `ruleset "${ruleset.name}"`; - - if (appliesToAllBranches(ruleset) && (rule.type === "pull_request" || rule.type === "required_status_checks")) { - fail(`${sourceName} applies ${rule.type} to every branch`); - } - - if (rule.type === "pull_request") { - const reviewFree = pullRequestRuleIsReviewFree(rule, sourceName); - if (reviewFree && appliesToDefaultBranch(ruleset, defaultBranch)) { - defaultBranchHasPrGate = true; - } - } - - if (rule.type === "required_status_checks") { - const contexts = collectRulesetStatusContexts(rule); - validateRequiredChecks(sourceName, contexts, knownContexts); - if ( - appliesToDefaultBranch(ruleset, defaultBranch) && - rule.parameters?.strict_required_status_checks_policy === true - ) { - defaultBranchHasStrictStatusGate = true; - } - } - } - } - - const protection = await requestJson(`/repos/${ownerRepo}/branches/${defaultBranch}/protection`, { - allowForbidden: true, - }); - - if (protection?.forbidden) { - warn("classic branch protection could not be inspected with this token; relying on rulesets"); - } else if (protection) { - if (protection.required_pull_request_reviews) { - fail("classic branch protection reintroduced manual PR review settings"); - } - - const contexts = [ - ...(protection.required_status_checks?.contexts || []), - ...(protection.required_status_checks?.checks || []).map((check) => check.context), - ].filter(Boolean); - - if (contexts.length > 0) { - validateRequiredChecks("classic branch protection", contexts, knownContexts); - if (protection.required_status_checks?.strict === true) { - defaultBranchHasStrictStatusGate = true; - } - } - } else { - warn(`classic branch protection is not configured for ${defaultBranch}`); - } - - if (!defaultBranchHasPrGate) { - fail(`default branch "${defaultBranch}" does not have a review-free PR gate`); - } - - if (!defaultBranchHasStrictStatusGate) { - fail(`default branch "${defaultBranch}" does not have strict required status checks`); - } - - for (const message of warnings) { - console.warn(`warning: ${message}`); - } - - if (errors.length > 0) { - console.error("Branch safety guard failed:"); - for (const message of errors) { - console.error(`- ${message}`); - } - process.exit(1); - } - - console.log(`Branch safety guard passed. Required checks: ${expectedRequiredChecks.join(", ")}`); -} - -main().catch((error) => { - console.error(error.message); - process.exit(1); -}); From ea228e2510823ed37161421a00fd060d8f93b990 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:58:47 +0800 Subject: [PATCH 5/5] chore: refine current clinical dashboard design --- src/app/globals.css | 86 ++--- src/components/ClinicalDashboard.tsx | 524 +++++++++++++++++++++++---- src/components/DocumentViewer.tsx | 38 +- src/components/ui-primitives.tsx | 22 +- tests/ui-smoke.spec.ts | 30 +- 5 files changed, 544 insertions(+), 156 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index b3aa90ecf..efce97bd0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -10,15 +10,15 @@ * Radius usage rules: * rounded-md chips, inner elements, metadata pills * rounded-lg controls, inputs, buttons - * rounded-xl cards, panels, drawers - * rounded-2xl bottom sheets, hero surfaces + * rounded-lg cards, panels, drawers + * rounded-xl bottom sheets, dialogs, large shells */ @theme { --radius-sm: 0.375rem; --radius-md: 0.5rem; - --radius-lg: 0.875rem; - --radius-xl: 1.25rem; - --radius-2xl: 1.75rem; + --radius-lg: 0.625rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; --text-2xs: 0.6875rem; --text-2xs--line-height: 1rem; @@ -88,25 +88,25 @@ --primary-800: #0c544f; --primary-900: #0b403d; - --background: #eaf3f5; - --app-shell: #04141b; - --app-shell-muted: #0b2a35; - --app-shell-accent: #0e8078; + --background: #f3f6f7; + --app-shell: #102028; + --app-shell-muted: #18303a; + --app-shell-accent: #3d8d86; --surface: var(--neutral-0); --surface-raised: #fbfdfe; - --surface-subtle: var(--neutral-100); - --surface-inset: #f4f9fa; - --surface-glass: rgb(255 255 255 / 76%); - --surface-highlight: rgb(255 255 255 / 68%); - --surface-lux: rgb(255 255 255 / 88%); - --surface-wash: #e4f0f2; + --surface-subtle: #eef4f5; + --surface-inset: #f7fafb; + --surface-glass: rgb(255 255 255 / 82%); + --surface-highlight: rgb(255 255 255 / 62%); + --surface-lux: rgb(255 255 255 / 94%); + --surface-wash: #edf4f5; --text: var(--neutral-900); --text-heading: var(--neutral-950); --text-muted: var(--neutral-600); --text-soft: var(--neutral-500); --border: var(--neutral-200); --border-strong: var(--neutral-300); - --border-lux: rgb(149 178 190 / 54%); + --border-lux: rgb(149 178 190 / 42%); --primary: var(--primary-500); --primary-strong: var(--primary-700); --primary-soft: #d6f2ec; @@ -145,15 +145,15 @@ --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1); --ease-spring: cubic-bezier(0.34, 1.3, 0.64, 1); - --glow-primary: 0 0 0 1px rgb(26 169 155 / 12%), 0 10px 28px rgb(12 130 120 / 10%); - --glow-soft: 0 0 0 1px rgb(26 169 155 / 9%), 0 8px 22px rgb(15 31 38 / 6%); + --glow-primary: 0 0 0 1px rgb(61 141 134 / 10%), 0 10px 24px rgb(61 141 134 / 8%); + --glow-soft: 0 0 0 1px rgb(61 141 134 / 7%), 0 8px 18px rgb(15 31 38 / 5%); /* Elevation: layered, small blur, low alpha */ - --shadow-soft: 0 1px 2px rgb(15 31 38 / 6%), 0 8px 20px rgb(15 31 38 / 7%); - --shadow-tight: 0 1px 2px rgb(15 31 38 / 6%), 0 4px 10px rgb(15 31 38 / 5%); - --shadow-hover: 0 2px 4px rgb(15 31 38 / 8%), 0 12px 28px rgb(15 31 38 / 9%); - --shadow-elevated: 0 1px 2px rgb(15 31 38 / 8%), 0 8px 16px rgb(15 31 38 / 8%), 0 24px 48px rgb(15 31 38 / 10%); - --shadow-lux: inset 0 1px 0 rgb(255 255 255 / 65%), 0 12px 32px rgb(15 31 38 / 8%); + --shadow-soft: 0 1px 2px rgb(15 31 38 / 5%), 0 7px 18px rgb(15 31 38 / 5%); + --shadow-tight: 0 1px 2px rgb(15 31 38 / 5%), 0 3px 8px rgb(15 31 38 / 4%); + --shadow-hover: 0 2px 4px rgb(15 31 38 / 7%), 0 10px 24px rgb(15 31 38 / 7%); + --shadow-elevated: 0 1px 2px rgb(15 31 38 / 7%), 0 8px 16px rgb(15 31 38 / 7%), 0 22px 44px rgb(15 31 38 / 8%); + --shadow-lux: inset 0 1px 0 rgb(255 255 255 / 65%), 0 10px 28px rgb(15 31 38 / 6%); --shadow-inset: inset 0 1px 0 rgb(255 255 255 / 60%); --ring-focus: 0 0 0 3px color-mix(in srgb, var(--focus) 28%, transparent); --space-1: 0.25rem; @@ -202,18 +202,18 @@ --primary-800: #a3ebe3; --primary-900: #c8f4ef; - --background: #03080d; - --app-shell: #020d14; - --app-shell-muted: #08232d; - --app-shell-accent: #2bc9bb; - --surface: var(--neutral-0); - --surface-raised: var(--neutral-100); - --surface-subtle: var(--neutral-50); - --surface-inset: #050e17; - --surface-glass: rgb(14 29 43 / 72%); + --background: #0d1318; + --app-shell: #111d24; + --app-shell-muted: #172a34; + --app-shell-accent: #46a39a; + --surface: #101923; + --surface-raised: #15202b; + --surface-subtle: #0f1821; + --surface-inset: #0b1218; + --surface-glass: rgb(20 31 42 / 78%); --surface-highlight: rgb(255 255 255 / 5%); - --surface-lux: rgb(15 31 47 / 84%); - --surface-wash: #07131d; + --surface-lux: rgb(20 31 42 / 92%); + --surface-wash: #111c25; --text: var(--neutral-900); --text-heading: var(--neutral-950); --text-muted: var(--neutral-600); @@ -251,13 +251,13 @@ --disabled: #64748b; --focus: #7de3d8; - --glow-primary: 0 0 0 1px rgb(51 212 194 / 18%), 0 16px 44px rgb(45 212 191 / 12%); - --glow-soft: 0 0 0 1px rgb(51 212 194 / 12%), 0 12px 30px rgb(0 0 0 / 22%); + --glow-primary: 0 0 0 1px rgb(70 163 154 / 16%), 0 14px 34px rgb(70 163 154 / 10%); + --glow-soft: 0 0 0 1px rgb(70 163 154 / 10%), 0 10px 24px rgb(0 0 0 / 18%); - --shadow-soft: 0 2px 6px rgb(0 0 0 / 32%), 0 12px 28px rgb(0 0 0 / 34%); - --shadow-tight: 0 1px 2px rgb(0 0 0 / 30%), 0 4px 12px rgb(0 0 0 / 28%); - --shadow-hover: 0 4px 10px rgb(0 0 0 / 38%), 0 16px 36px rgb(0 0 0 / 40%); - --shadow-elevated: 0 2px 6px rgb(0 0 0 / 36%), 0 10px 24px rgb(0 0 0 / 40%), 0 28px 56px rgb(0 0 0 / 46%); + --shadow-soft: 0 1px 4px rgb(0 0 0 / 24%), 0 10px 24px rgb(0 0 0 / 26%); + --shadow-tight: 0 1px 2px rgb(0 0 0 / 24%), 0 4px 10px rgb(0 0 0 / 22%); + --shadow-hover: 0 3px 8px rgb(0 0 0 / 30%), 0 14px 30px rgb(0 0 0 / 32%); + --shadow-elevated: 0 2px 6px rgb(0 0 0 / 30%), 0 10px 22px rgb(0 0 0 / 34%), 0 26px 52px rgb(0 0 0 / 38%); --shadow-lux: inset 0 1px 0 rgb(255 255 255 / 6%), 0 16px 40px rgb(0 0 0 / 44%); --shadow-inset: inset 0 1px 0 rgb(255 255 255 / 6%); --ring-focus: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent); @@ -272,15 +272,15 @@ html { min-width: 320px; background: - radial-gradient(circle at 50% -12%, color-mix(in srgb, var(--primary) 12%, transparent), transparent 34rem), + radial-gradient(circle at 50% -12%, color-mix(in srgb, var(--primary) 5%, transparent), transparent 34rem), linear-gradient(180deg, var(--background), color-mix(in srgb, var(--background) 90%, var(--surface-inset))); overflow-x: clip; } body { background: - radial-gradient(circle at 8% 0%, color-mix(in srgb, var(--primary) 9%, transparent), transparent 28rem), - radial-gradient(circle at 98% 12%, color-mix(in srgb, var(--info) 7%, transparent), transparent 26rem), + radial-gradient(circle at 8% 0%, color-mix(in srgb, var(--primary) 4%, transparent), transparent 28rem), + radial-gradient(circle at 98% 12%, color-mix(in srgb, var(--info) 3%, transparent), transparent 26rem), var(--background); color: var(--text); font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 8671975f8..e8e05930e 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -176,6 +176,7 @@ const mobileSectionFabMediaQuery = "(max-width: 768px), ((max-width: 1023px) and const themeStorageKey = "clinical-kb-theme"; const themeChangeEvent = "clinical-kb-theme-change"; const authEmailChangeEvent = "clinical-kb-auth-email-change"; +const recentQueryStorageKey = "clinical-kb-recent-queries"; const documentPageSize = 150; const activeIndexingPollFallbackMs = 5_000; const setupRecheckPollMs = 60_000; @@ -825,6 +826,7 @@ function MasterSearchHeader({ onQueryModeChange, onScopeFiltersChange, onToggleScope, + onScopeOpenChange, onOpenGuide, onToggleTheme, }: { @@ -847,6 +849,7 @@ function MasterSearchHeader({ onQueryModeChange: (mode: ClinicalQueryMode) => void; onScopeFiltersChange: (filters: SearchScopeFilters) => void; onToggleScope: (documentId: string) => void; + onScopeOpenChange?: (open: boolean) => void; onOpenGuide: () => void; onToggleTheme: () => void; }) { @@ -911,6 +914,10 @@ function MasterSearchHeader({ if (restoreFocus) scopeSummaryRef.current?.focus(); }, []); + useEffect(() => { + onScopeOpenChange?.(scopeOpen); + }, [onScopeOpenChange, scopeOpen]); + useEffect(() => { const details = scopeDetailsRef.current; if (!scopeOpen || !details?.open) return undefined; @@ -956,7 +963,94 @@ function MasterSearchHeader({ function renderScopeRows() { return (
Refine search
+ + Mode, status, topics + +Document scope
@@ -1167,25 +1261,25 @@ function MasterSearchHeader({ {searchMode === "answer" ? "Synthesize cited clinical guidance" : "List matching source documents"}