From 07a72e1cf46023dd52c7d7dbc8b2774de956e8dc Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:56:20 +0300 Subject: [PATCH 1/3] ci: run lint-staged on changed files instead of full checks Add a dedicated lint-staged workflow that lints/formats only the files changed in a PR (or push) via 'lint-staged --diff', and fail if any fix is needed. Remove the whole-tree Lint and Format steps from ci.yml. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 6 --- .github/workflows/lint-staged.yml | 65 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/lint-staged.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69cda01..12de159 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,12 +55,6 @@ jobs: echo "::error::npm audit signatures failed after 3 attempts" exit 1 - - name: 'Lint' - run: npm run lint:check - - - name: 'Format' - run: npm run format:check - - name: 'Build' run: npm run build diff --git a/.github/workflows/lint-staged.yml b/.github/workflows/lint-staged.yml new file mode 100644 index 0000000..54aa1f7 --- /dev/null +++ b/.github/workflows/lint-staged.yml @@ -0,0 +1,65 @@ +--- +name: '🧹 Lint (staged)' + +on: + workflow_dispatch: + pull_request: + push: + branches: ['main', 'beta'] + +concurrency: + group: '${{ github.workflow }}-${{ github.ref }}' + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint-staged: + runs-on: 'ubuntu-latest' + timeout-minutes: 10 + permissions: + contents: 'read' # to check out the repository + steps: + - name: 'Checkout' + uses: actions/checkout@v6.0.3 + # Full history so the diff base commit is available locally. + with: { fetch-depth: '0', ref: '${{ github.event.pull_request.head.sha }}' } + + - name: 'Setup Node.js' + uses: actions/setup-node@v6.4.0 + with: { node-version-file: '.nvmrc', cache: 'npm' } + + - name: 'Install' + run: npm ci --no-fund --no-audit + + - name: 'Resolve diff range' + id: 'range' + # Only lint files that changed in this PR (or push), mirroring the local + # pre-commit hook instead of re-checking the whole tree. + run: | + if [ "${{ github.event_name }}" = 'pull_request' ]; then + base='${{ github.event.pull_request.base.ref }}' + git fetch --no-tags --depth=1 origin "$base" + echo "value=origin/${base}...HEAD" >> "$GITHUB_OUTPUT" + else + before='${{ github.event.before }}' + if [ -z "$before" ] \ + || [ "$before" = '0000000000000000000000000000000000000000' ] \ + || ! git cat-file -e "${before}^{commit}" 2>/dev/null; then + before="$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)" + fi + echo "value=${before}...HEAD" >> "$GITHUB_OUTPUT" + fi + + - name: 'Run lint-staged on changed files' + # lint-staged applies eslint/prettier fixes and stages them; it exits + # non-zero on unfixable lint errors. + run: npx lint-staged --diff="${{ steps.range.outputs.value }}" + + - name: 'Verify no fixes were required' + # If lint-staged staged any fix, the changed files were not properly + # linted/formatted before being committed. Surface the diff and fail. + run: | + if ! git diff --quiet HEAD; then + echo '::error::lint-staged applied fixes to changed files. Run "npx lint-staged" locally and commit the result.' + git --no-pager diff HEAD + exit 1 + fi From 763726283fccedacc72de783a60115e0b1bdc3f1 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:58:09 +0300 Subject: [PATCH 2/3] ci: fetch PR base at full depth for lint-staged diff The shallow (--depth=1) fetch of the base branch left no merge-base for the 'origin/...HEAD' range, so lint-staged failed to resolve changed files. Fetch the base ref to full depth instead. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/lint-staged.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-staged.yml b/.github/workflows/lint-staged.yml index 54aa1f7..a8b5d51 100644 --- a/.github/workflows/lint-staged.yml +++ b/.github/workflows/lint-staged.yml @@ -37,7 +37,7 @@ jobs: run: | if [ "${{ github.event_name }}" = 'pull_request' ]; then base='${{ github.event.pull_request.base.ref }}' - git fetch --no-tags --depth=1 origin "$base" + git fetch --no-tags origin "+refs/heads/${base}:refs/remotes/origin/${base}" echo "value=origin/${base}...HEAD" >> "$GITHUB_OUTPUT" else before='${{ github.event.before }}' From eaed853b6da33ec198e52b85fb8eda1b88bcec74 Mon Sep 17 00:00:00 2001 From: titan-ron <30556071+titan-ron@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:11:59 +0300 Subject: [PATCH 3/3] ci: move lint-staged into ci.yml as the sca job Fold the standalone lint-staged workflow into ci.yml as a job named 'sca' and gate 'release' on it, restoring lint/format as a release prerequisite. Remove the now-redundant lint-staged.yml. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++- .github/workflows/lint-staged.yml | 65 ------------------------------- 2 files changed, 53 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/lint-staged.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12de159..09bddff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,58 @@ jobs: path: '*.tgz' if-no-files-found: 'error' + sca: + runs-on: 'ubuntu-latest' + timeout-minutes: 10 + permissions: + contents: 'read' # to check out the repository + steps: + - name: 'Checkout' + uses: actions/checkout@v6.0.3 + # Full history so the diff base commit is available locally. + with: { fetch-depth: '0', ref: '${{ github.event.pull_request.head.sha }}' } + + - name: 'Setup Node.js' + uses: actions/setup-node@v6.4.0 + with: { node-version-file: '.nvmrc', cache: 'npm' } + + - name: 'Install' + run: npm ci --no-fund --no-audit + + - name: 'Resolve diff range' + id: 'range' + # Only lint files that changed in this PR (or push), mirroring the local + # pre-commit hook instead of re-checking the whole tree. + run: | + if [ "${{ github.event_name }}" = 'pull_request' ]; then + base='${{ github.event.pull_request.base.ref }}' + git fetch --no-tags origin "+refs/heads/${base}:refs/remotes/origin/${base}" + echo "value=origin/${base}...HEAD" >> "$GITHUB_OUTPUT" + else + before='${{ github.event.before }}' + if [ -z "$before" ] \ + || [ "$before" = '0000000000000000000000000000000000000000' ] \ + || ! git cat-file -e "${before}^{commit}" 2>/dev/null; then + before="$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)" + fi + echo "value=${before}...HEAD" >> "$GITHUB_OUTPUT" + fi + + - name: 'Run lint-staged on changed files' + # lint-staged applies eslint/prettier fixes and stages them; it exits + # non-zero on unfixable lint errors. + run: npx lint-staged --diff="${{ steps.range.outputs.value }}" + + - name: 'Verify no fixes were required' + # If lint-staged staged any fix, the changed files were not properly + # linted/formatted before being committed. Surface the diff and fail. + run: | + if ! git diff --quiet HEAD; then + echo '::error::lint-staged applied fixes to changed files. Run "npx lint-staged" locally and commit the result.' + git --no-pager diff HEAD + exit 1 + fi + upload-coverage: needs: 'ci' runs-on: 'ubuntu-latest' @@ -188,7 +240,7 @@ jobs: run: docker compose down --volumes release: - needs: ['ci', 'smoke', 'e2e'] + needs: ['ci', 'sca', 'smoke', 'e2e'] runs-on: 'ubuntu-latest' # Only enter the protected release environment for real releases; pull # request dry runs run without it (its branch policy blocks PR refs). diff --git a/.github/workflows/lint-staged.yml b/.github/workflows/lint-staged.yml deleted file mode 100644 index a8b5d51..0000000 --- a/.github/workflows/lint-staged.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: '🧹 Lint (staged)' - -on: - workflow_dispatch: - pull_request: - push: - branches: ['main', 'beta'] - -concurrency: - group: '${{ github.workflow }}-${{ github.ref }}' - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - lint-staged: - runs-on: 'ubuntu-latest' - timeout-minutes: 10 - permissions: - contents: 'read' # to check out the repository - steps: - - name: 'Checkout' - uses: actions/checkout@v6.0.3 - # Full history so the diff base commit is available locally. - with: { fetch-depth: '0', ref: '${{ github.event.pull_request.head.sha }}' } - - - name: 'Setup Node.js' - uses: actions/setup-node@v6.4.0 - with: { node-version-file: '.nvmrc', cache: 'npm' } - - - name: 'Install' - run: npm ci --no-fund --no-audit - - - name: 'Resolve diff range' - id: 'range' - # Only lint files that changed in this PR (or push), mirroring the local - # pre-commit hook instead of re-checking the whole tree. - run: | - if [ "${{ github.event_name }}" = 'pull_request' ]; then - base='${{ github.event.pull_request.base.ref }}' - git fetch --no-tags origin "+refs/heads/${base}:refs/remotes/origin/${base}" - echo "value=origin/${base}...HEAD" >> "$GITHUB_OUTPUT" - else - before='${{ github.event.before }}' - if [ -z "$before" ] \ - || [ "$before" = '0000000000000000000000000000000000000000' ] \ - || ! git cat-file -e "${before}^{commit}" 2>/dev/null; then - before="$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)" - fi - echo "value=${before}...HEAD" >> "$GITHUB_OUTPUT" - fi - - - name: 'Run lint-staged on changed files' - # lint-staged applies eslint/prettier fixes and stages them; it exits - # non-zero on unfixable lint errors. - run: npx lint-staged --diff="${{ steps.range.outputs.value }}" - - - name: 'Verify no fixes were required' - # If lint-staged staged any fix, the changed files were not properly - # linted/formatted before being committed. Surface the diff and fail. - run: | - if ! git diff --quiet HEAD; then - echo '::error::lint-staged applied fixes to changed files. Run "npx lint-staged" locally and commit the result.' - git --no-pager diff HEAD - exit 1 - fi