diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9178b34..aa6a853 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,6 +14,12 @@ on: permissions: contents: read +# A new push to a pull request cancels the run on the commit it replaces. Each master push +# gets its own group, so master runs are never cancelled. +concurrency: + group: validate-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: validate: name: Repository validation diff --git a/AGENTS.md b/AGENTS.md index d3fb317..e018596 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,8 @@ nub run format Open a pull request as a draft. Keep it a draft while you work. GitHub Actions does not run jobs on a draft pull request. +`nub install` installs a lefthook pre-push hook (`lefthook.yml`) that runs `nub run typecheck`, `nub run lint`, and `nub run format`. + When the work is done, mark the pull request ready for review. ## Release management diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..056f972 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,12 @@ +# Runs the fast repository checks before a push, so a lint, type or format error fails +# here and not in CI. Skip once with `git push --no-verify` or `LEFTHOOK=0 git push`. +# `nub install` installs the hook (the `prepare` script). +pre-push: + parallel: true + jobs: + - name: typecheck + run: nub run typecheck + - name: lint + run: nub run lint + - name: format + run: nub run format diff --git a/nub.lock b/nub.lock index cce06eb..1bf9165 100644 --- a/nub.lock +++ b/nub.lock @@ -15,6 +15,9 @@ importers: '@types/node': specifier: 24.19.2 version: 24.19.2 + lefthook: + specifier: ^2.2.2 + version: 2.2.2 oxfmt: specifier: 0.74.0 version: 0.74.0 @@ -479,6 +482,65 @@ packages: picomatch: optional: true + lefthook-android-arm64@2.2.2: + resolution: {integrity: sha512-wb9rQQFLX7u1dJLxvMrq9VUfMYi5kxM7tJQ6Y4mWEOwaU+SmIm2wjoT/nromKq27Y8fh2Qd7FgCD3G1wfW8CnQ==} + cpu: [arm64] + os: [android] + + lefthook-darwin-arm64@2.2.2: + resolution: {integrity: sha512-6V8D3bLnD/60Xhqn0o2EQD6H3Fe/U9LGkt4WF1O/3eBHbux+5ZJqoH0hfrC+Bi9Dy1Ve1AWxiAY7U2asYl8qVA==} + cpu: [arm64] + os: [darwin] + + lefthook-darwin-x64@2.2.2: + resolution: {integrity: sha512-fH/kArZ9ZZ65wCFtvBMgGcq25qwQpkQ5jp93ba+8wpjgxxzjczYGluwHvM/NQDJWLOFXuKXW0yFs76SramQkyQ==} + cpu: [x64] + os: [darwin] + + lefthook-freebsd-arm64@2.2.2: + resolution: {integrity: sha512-MCRsU01ju989QQffOfS3gCeFfyVDXs5B/FzR65fmrNvqcxb98fldvfzzoAbnK2q6SBJlRT3QT1tNEMCxddCwlA==} + cpu: [arm64] + os: [freebsd] + + lefthook-freebsd-x64@2.2.2: + resolution: {integrity: sha512-LkKuJU0S2oLimeoHwPTy1TLWB6GcN0t5lLxiXNlsBKUkr6R47QRcvPMZMoXmu3IetQtd+WliXMLE8qTwi2fqBg==} + cpu: [x64] + os: [freebsd] + + lefthook-linux-arm64@2.2.2: + resolution: {integrity: sha512-d+HFtS2DEKaOnD7vRU0tUz8pNgHwsUYmQ22+1dyXpOrXGyzNXR0AqX5KsIRF36JGhVvO5X5Anw5QYgR3mxVSuA==} + cpu: [arm64] + os: [linux] + + lefthook-linux-x64@2.2.2: + resolution: {integrity: sha512-N/OlQyAhcU6RAXjrwFxLsh70sn96rBoi+XkuLJ8CVBkk9Ci3VDXQayDZTNz5B1xML8RwoMcV8Wblniy1xFOmSA==} + cpu: [x64] + os: [linux] + + lefthook-openbsd-arm64@2.2.2: + resolution: {integrity: sha512-6G/gKXcpIwC+XcODg7ELZYP/hIrVIHQxhy2GoRdVyecmpGMFj+BZo3iqcUmeWusQMuL+UdNexbkrUaeJalIkNw==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@2.2.2: + resolution: {integrity: sha512-eMJGi3/tqKEqu7XCZlqwQWRtK5JlL/9diaqqgRlEdlRD7GxwzHZN21z6awbITum9emea3lALvXFe6V8odQLGOA==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@2.2.2: + resolution: {integrity: sha512-4P1EaQb5xG1zhYD1RLJ6TEGdFkmHkB+Slqx6yRDPKuBDNxMiD0+kTAAdUY2E8xPuRR35xHpVPR+b3/HtzPrjQQ==} + cpu: [arm64] + os: [win32] + + lefthook-windows-x64@2.2.2: + resolution: {integrity: sha512-9Ck4zancQilBfXT3ZeRipm7Lcq4RQfxyMpy8SAVINw0t/BrbjsT25V46ZuziERcwdajnE4myfMzC33GoCBwr7g==} + cpu: [x64] + os: [win32] + + lefthook@2.2.2: + resolution: {integrity: sha512-37fYHAhL12r1twaEV0PA5TwoIT491eTGGYjhhXxlYNMq3DSkiGZBQ++Jfk5maXj0vq52NRauLtMikBKtbNJs3Q==} + hasBin: true + oxfmt@0.74.0: resolution: {integrity: sha512-G4OP7FgwGYZR5ttRpCOOfJdyRsSyvtn34vSXnjDq6Oqed5Dufg5mB44mank+51nAdHOKuTJ0lj0mksY7L+N61A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -784,6 +846,53 @@ snapshots: dependencies: picomatch: 4.0.7 + lefthook-android-arm64@2.2.2: + optional: true + + lefthook-darwin-arm64@2.2.2: + optional: true + + lefthook-darwin-x64@2.2.2: + optional: true + + lefthook-freebsd-arm64@2.2.2: + optional: true + + lefthook-freebsd-x64@2.2.2: + optional: true + + lefthook-linux-arm64@2.2.2: + optional: true + + lefthook-linux-x64@2.2.2: + optional: true + + lefthook-openbsd-arm64@2.2.2: + optional: true + + lefthook-openbsd-x64@2.2.2: + optional: true + + lefthook-windows-arm64@2.2.2: + optional: true + + lefthook-windows-x64@2.2.2: + optional: true + + lefthook@2.2.2: + optionalDependencies: + lefthook-android-arm64: 2.2.2 + lefthook-darwin-arm64: 2.2.2 + lefthook-darwin-x64: 2.2.2 + lefthook-freebsd-arm64: 2.2.2 + lefthook-freebsd-x64: 2.2.2 + lefthook-linux-arm64: 2.2.2 + lefthook-linux-x64: 2.2.2 + lefthook-openbsd-arm64: 2.2.2 + lefthook-openbsd-x64: 2.2.2 + lefthook-windows-arm64: 2.2.2 + lefthook-windows-x64: 2.2.2 + oxfmt@0.74.0: dependencies: tinypool: 2.2.0 diff --git a/package.json b/package.json index 5afff8e..e000065 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "scripts": { "build": "tsc -p tsconfig.build.json", + "prepare": "lefthook install", "typecheck": "tsrs --noEmit", "test": "node --test \"tests/**/*.test.ts\"", "lint": "nub run build && oxlint --deny-warnings", @@ -56,6 +57,7 @@ }, "devDependencies": { "@types/node": "24.19.2", + "lefthook": "^2.2.2", "oxfmt": "0.74.0", "oxlint": "1.89.0", "tegami": "1.5.0",