diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 11a6d970d557..1e855e49533b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,10 +2,11 @@ // https://github.com/devcontainers/images/blob/v0.4.19/src/typescript-node/.devcontainer/devcontainer.json { "name": "Node.js & TypeScript", - "image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm", + "image": "mcr.microsoft.com/devcontainers/typescript-node:24-trixie", "features": { "ghcr.io/devcontainers/features/docker-in-docker": { - "version": "latest" + "version": "latest", + "moby": false }, "ghcr.io/devcontainers/features/github-cli": { "version": "latest" @@ -40,7 +41,7 @@ } }, - "onCreateCommand": "sudo apt-get update && export DEBIAN_FRONTEND=noninteractive && sudo apt-get -y install --no-install-recommends ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libexpat1 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 wget xdg-utils redis-server default-jre-headless && sudo apt-get autoremove -y && sudo apt-get clean -y && sudo rm -rf /var/lib/apt/lists/*", + "onCreateCommand": "sudo apt-get update && export DEBIAN_FRONTEND=noninteractive && sudo apt-get -y install --no-install-recommends ca-certificates fonts-liberation libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 libcairo2 libcups2t64 libdbus-1-3 libexpat1 libgbm1 libglib2.0-0t64 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 wget xdg-utils redis-server default-jre-headless && sudo apt-get autoremove -y && sudo apt-get clean -y && sudo rm -rf /var/lib/apt/lists/*", "updateContentCommand": "export JAVA_HOME=/usr/lib/jvm/default-java && pnpm config set store-dir ~/.local/share/pnpm/store && pnpm i && pnpm rb && pnpx rebrowser-puppeteer browsers install chrome", diff --git a/.dockerignore b/.dockerignore index 8354e45cdd62..7c2ba2290604 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ # folders .devcontainer .github -.husky .idea .idx .vscode diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 280c46e6ae89..4fa5e4986100 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -7,6 +7,7 @@ # - Optional platform prefix: platform:username (e.g., github:user). # - Denounce with minus prefix: -username or -platform:username. # - Optional details after a space following the handle. +-an-lee impersonate as enpitsulin and DIYgod in #22205, 3a07e45a99c5300f47dadb2e91b40334626759d5. 2nd offense in #22929, b19ede356a535b36db10cbba3e8798045dc1a12e. 3rd offense in #22931, 41c4b80f7b57bbe13b2cc80eb98a54f4c6282668. -betterandbetterii impersonate as dvorak0, goestav, Kjasn, Loongphy, TonyRL, ttttmr and xbot. https://github.com/DIYgod/RSSHub/commit/9e6f84df79bc9efcfabb0ca0768d7fded6775a1a. diygod henryqw @@ -14,4 +15,5 @@ hyoban neverbehave pseudoyu tonyrl +-weixshaw impersonate as magazian in #23333. 23f8a7a8509f6620a79503a45370a7339a157bcb zhenlonghe diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 073aae6491d2..b6aaf6ad09d0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,15 +10,21 @@ updates: - dependencies ignore: # pin to version before it is sold to potential suspicious party - # https://github.com/goofychris/art-template/issues/660 the issue created from original author stating v4.13.3 + # https://github.com/goofychris/art-template/issues/660 the issue created from original author stating v4.13.3 (March, 2025) # contains suspicious code and related issues (#658, #659) were deleted # related: # https://github.com/fastify/point-of-view/issues/463 https://github.com/fastify/point-of-view/pull/461#issuecomment-2718888986 # https://github.com/cnpm/bug-versions/pull/266 https://github.com/cnpm/cnpmcore/issues/777 # https://github.com/yoimiya-kokomi/Miao-Yunzai/pull/515 https://github.com/zhangfisher/flex-tools/commit/09b565dfe6e2932bb829613ddbe09f6d0acbccd4 + # v4.13.5, v4.13.6 (May, 2026) + # https://web.archive.org/web/20260521024725/https://github.com/goofychris/art-template/issues/665 + # https://safedep.io/art-template-npm-supply-chain-compromise/ https://github.com/ossf/malicious-packages/pull/1265 - dependency-name: art-template versions: ['>=4.13.3'] groups: + bbob: + patterns: + - '@bbob/*' cloudflare: patterns: - '@cloudflare/*' @@ -27,12 +33,23 @@ updates: patterns: - 'eslint' - '@eslint/*' + hono: + patterns: + - '@hono/*' + - '@scalar/hono-api-reference' + - 'hono' + msw: + patterns: + - '@mswjs/*' + - 'msw' opentelemetry: patterns: - '@opentelemetry/*' oxc: patterns: + - '@oxc-parser/*' - '@oxlint/*' + - 'oxc-parser' - 'oxfmt' - 'oxlint' - 'oxlint-plugin-eslint' @@ -58,6 +75,10 @@ updates: open-pull-requests-limit: 100 labels: - dependencies + groups: + nix: + patterns: + - '*' - package-ecosystem: 'github-actions' directory: '/' diff --git a/.github/labeler.yml b/.github/labeler.yml index 4b6c2602e5e0..48db57424d54 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,7 @@ 'route: deprecated': - changed-files: - any-glob-to-any-file: ['lib/router.js'] - - all-globs-to-any-file: ['lib/routes-deprecated/**/*.js', '!lib/routes-deprecated/index.js'] + - all-globs-to-any-file: ['lib/routes-deprecated/**'] 'Route': - changed-files: @@ -10,7 +10,7 @@ core enhancement: - changed-files: - any-glob-to-any-file: ['lib/routes/index.ts'] - - all-globs-to-any-file: ['lib/**', '!lib/config.ts', '!lib/router.js', '!lib/routes/**', '!lib/routes-deprecated/**'] + - all-globs-to-any-file: ['lib/**', '!lib/config.ts', '!lib/routes/**'] dependencies: - changed-files: diff --git a/.github/prompts/pr_review_rules.md b/.github/prompts/pr_review_rules.md index a162ccec99df..433fb1b1f602 100644 --- a/.github/prompts/pr_review_rules.md +++ b/.github/prompts/pr_review_rules.md @@ -47,4 +47,3 @@ Only report **clear and actionable** violations in changed lines/files. Do not r - Report only violated rules. - Each bullet should include: file path, problem, and concrete fix. - Group repeated issues across files into one concise bullet when possible. -- If no rule is clearly violated, do not comment. diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml index 44e45a852264..13cab5abd336 100644 --- a/.github/workflows/build-assets.yml +++ b/.github/workflows/build-assets.yml @@ -18,15 +18,15 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - name: Use Node.js Active LTS - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' - - name: Install dependencies (yarn) + - name: Install dependencies run: pnpm i # assets @@ -44,14 +44,14 @@ jobs: # docs - name: Build docs - run: pnpm build:docs + run: pnpm exec tsx scripts/workflow/build-docs.ts - id: check-docs-env env: DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }} if: ${{ env.DOCS_API_TOKEN != '' }} run: echo "defined=true" >> $GITHUB_OUTPUT - name: Checkout docs - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: steps.check-docs-env.outputs.defined == 'true' with: repository: 'RSSNext/rsshub-docs' diff --git a/.github/workflows/cleanup-pr-cache.yml b/.github/workflows/cleanup-pr-cache.yml new file mode 100644 index 000000000000..747db1f93654 --- /dev/null +++ b/.github/workflows/cleanup-pr-cache.yml @@ -0,0 +1,34 @@ +name: Clean up PR cache + +on: + pull_request_target: + types: + - closed + workflow_dispatch: + inputs: + pr_number: + description: 'Pull request number' + required: true + type: number + +jobs: + cleanup: + runs-on: ubuntu-slim + timeout-minutes: 5 + permissions: + actions: write + steps: + - name: Delete caches of the PR + run: gh cache delete --all --repo "$REPO" --ref "$REF" --succeed-on-no-caches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + REF: refs/pull/${{ github.event.pull_request.number || inputs.pr_number }}/merge + + - name: Delete caches of the PR branch + if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name == github.repository + run: gh cache delete --all --repo "$REPO" --ref "$REF" --succeed-on-no-caches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + REF: refs/heads/${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a870789efa99..3edda4f7d8b7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Initializes the CodeQL tools for scanning. # TODO: use hash pinning when https://github.com/dependabot/dependabot-core/pull/13007 pass diff --git a/.github/workflows/comment-on-issue.yml b/.github/workflows/comment-on-issue.yml index 06d6825c0088..de6a197096c5 100644 --- a/.github/workflows/comment-on-issue.yml +++ b/.github/workflows/comment-on-issue.yml @@ -26,9 +26,9 @@ jobs: outputs: closed: ${{ steps.check.outputs.closed }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' @@ -60,9 +60,9 @@ jobs: (needs.checkIssue.result == 'success' || needs.checkIssue.result == 'skipped') && needs.checkIssue.outputs.closed != 'true' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' diff --git a/.github/workflows/dependabot-fork.yml b/.github/workflows/dependabot-fork.yml index 40494f4e0b40..ab0c6354d717 100644 --- a/.github/workflows/dependabot-fork.yml +++ b/.github/workflows/dependabot-fork.yml @@ -10,7 +10,7 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Comment Dependabot PR uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 9c542fad47ad..1bbf8afbc885 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -39,21 +39,25 @@ jobs: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 - runner: ubuntu-24.04-arm + runner: ubuntu-26.04-arm permissions: packages: write id-token: write attestations: write steps: - name: Enable ZRAM + id: zram # Reduce memory pressure # PERCENT=100 is safe: https://fedoraproject.org/wiki/Changes/Scale_ZRAM_to_full_memory_size run: | sudo apt-get update -yq - sudo apt-get install -yq "linux-modules-extra-$(uname -r)" zram-tools + sudo apt-get install -yq zram-tools + # TODO: remove when runner is 26.0 + modinfo zram > /dev/null 2>&1 || sudo apt-get install -yq "linux-modules-extra-$(uname -r)" echo -e 'ALGO=zstd\nPERCENT=100' | sudo tee -a /etc/default/zramswap sudo systemctl restart zramswap swapon + background: true - name: Prepare run: | @@ -61,7 +65,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Extract repository name id: repo-name @@ -71,16 +75,16 @@ jobs: echo "repo-name=$REPO_NAME_LOWER" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ vars.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -88,7 +92,7 @@ jobs: - name: Extract Docker metadata (ordinary version) id: meta-ordinary - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -105,9 +109,12 @@ jobs: tags=$(jq -r '.target["docker-metadata-action"].args.DOCKER_META_IMAGES' "$DOCKER_METADATA_OUTPUT_BAKE_FILE_TAGS") echo "tags=$tags" >> "$GITHUB_OUTPUT" + - name: Wait for ZRAM + wait: zram + - name: Build and push Docker image (ordinary version) id: build-and-push - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . tags: ${{ steps.image-name-ordinary.outputs.tags }} @@ -118,7 +125,7 @@ jobs: outputs: type=image,compression=zstd,force-compression=true,push-by-digest=true,name-canonical=true,push=true - name: Attest (ordinary version) - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -141,7 +148,7 @@ jobs: - name: Extract Docker metadata (Chromium-bundled version) id: meta-chromium-bundled - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -160,7 +167,7 @@ jobs: - name: Build and push Docker image (Chromium-bundled version) id: build-and-push-chromium - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . build-args: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 @@ -173,7 +180,7 @@ jobs: outputs: type=image,compression=zstd,force-compression=true,push-by-digest=true,name-canonical=true,push=true - name: Attest (Chromium-bundled version) - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -204,16 +211,16 @@ jobs: id-token: write steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ vars.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -228,7 +235,7 @@ jobs: - name: Extract Docker metadata (ordinary version) id: meta-ordinary-merge - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ needs.release.outputs.repo-name }} @@ -254,7 +261,7 @@ jobs: - name: Extract Docker metadata (Chromium-bundled version) id: meta-chromium-bundled-merge - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ needs.release.outputs.repo-name }} @@ -271,13 +278,48 @@ jobs: docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ vars.DOCKER_USERNAME }}/${{ needs.release.outputs.repo-name }}@sha256:%s ' *) + github-release: + runs-on: ubuntu-slim + needs: [release, merge] + timeout-minutes: 5 + permissions: + contents: write + steps: + - name: Prepare release notes + id: notes + env: + IMAGE_REPOSITORY: ${{ needs.release.outputs.repo-name }} + DOCKER_USERNAME: ${{ vars.DOCKER_USERNAME }} + run: | + echo "tag=v$(date -u +%Y.%m.%d)-${GITHUB_RUN_NUMBER}.${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT" + image_repository="${GITHUB_REPOSITORY,,}" + cat > "$RUNNER_TEMP/release-notes.md" < core.warning(error)) - name: Print Docker container logs if: ${{ always() }} @@ -191,6 +207,12 @@ jobs: issue_number: ${{ steps.source-run-info.outputs.number }}, labels: ['auto: DO NOT merge'], }) + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ steps.source-run-info.outputs.number }}, + name: 'auto: ready to review', + }).catch((error) => core.warning(error)) - name: Close broken PR if: ${{ steps.source-run-info.outputs.author_association != 'OWNER' && steps.source-run-info.outputs.author_association != 'COLLABORATOR' && steps.source-run-info.outputs.author_association != 'MEMBER' }} diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 80e317c45364..479137998d09 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -27,20 +27,20 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Docker Buildx # needed by `cache-from` - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0 - name: Extract Docker metadata id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: rsshub flavor: latest=true - name: Build Docker image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . build-args: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 # also test bundling Chromium @@ -65,6 +65,12 @@ jobs: issue_number: ${{ github.event.pull_request.number }}, labels: ['auto: DO NOT merge'], }) + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.pull_request.number }}, + name: 'auto: ready to review', + }).catch((error) => core.warning(error)) - name: Test Docker image run: bash scripts/docker/test-docker.sh @@ -75,6 +81,6 @@ jobs: - name: Upload Docker image uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: docker-image + archive: false path: rsshub.tar.zst retention-days: 1 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 776717f4fc1c..c6edde4a9d67 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -14,9 +14,9 @@ jobs: timeout-minutes: 15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' diff --git a/.github/workflows/ghcr-retention.yml b/.github/workflows/ghcr-retention.yml index ec98d38df410..eea0f5fc9258 100644 --- a/.github/workflows/ghcr-retention.yml +++ b/.github/workflows/ghcr-retention.yml @@ -13,7 +13,7 @@ jobs: contents: read steps: - name: Delete old container versions (30+ days) - uses: dataaxiom/ghcr-cleanup-action@f092b48ba3b604b2a83690dc4b2bbb3392e1045f # v1.2.1 + uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # v1.2.2 with: dry-run: false token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-command.yml b/.github/workflows/issue-command.yml index 87e7edd967d4..6bd4b95b7405 100644 --- a/.github/workflows/issue-command.yml +++ b/.github/workflows/issue-command.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: Checkout the latest code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Automatic Rebase @@ -49,10 +49,10 @@ jobs: group: vouch-manage cancel-in-progress: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - id: vouch - uses: mitchellh/vouch/action/manage-by-issue@c6d80ead49839655b61b422700b7a3bc9d0804a9 # v1.4.2 + uses: mitchellh/vouch/action/manage-by-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0 with: issue-id: ${{ github.event.issue.number }} comment-id: ${{ github.event.comment.id }} @@ -70,7 +70,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, - body: 'This pull request has been automatically closed.', + body: 'This pull request has been automatically closed for denounced users by [vouch](https://github.com/mitchellh/vouch).' }); await github.rest.pulls.update({ owner: context.repo.owner, @@ -102,25 +102,25 @@ jobs: - name: Checkout if: ${{ !github.event.issue.pull_request }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Checkout PR if: github.event.issue.pull_request - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ fromJson(steps.pr-data.outputs.data).head.ref }} - name: Install pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - name: Use Node.js Active LTS - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' - name: Install dependencies (pnpm) - run: pnpm i && pnpm rb && pnpm exec playwright install chromium + run: pnpm i && pnpm rb && pnpm exec patchright install chromium - name: Fetch affected routes id: fetch-route @@ -141,8 +141,10 @@ jobs: run: pnpm build - name: Start RSSHub + id: rsshub if: env.TEST_CONTINUE - run: pnpm start & + run: pnpm start + background: true env: ALLOW_USER_HOTLINK_TEMPLATE: true ALLOW_USER_SUPPLY_UNSAFE_DOMAIN: true @@ -167,6 +169,9 @@ jobs: const { default: test } = await import('${{ github.workspace }}/scripts/workflow/test-route/test.mjs') await test({ github, context, core }, link, routes, number) + - name: Stop RSSHub + cancel: rsshub + - name: Print logs if: env.TEST_CONTINUE run: cat ${{ github.workspace }}/logs/combined.log diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c269b1497144..c47f07cf48c0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,28 +20,32 @@ jobs: permissions: security-events: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' - run: pnpm i - name: Install oxlint to SARIF converter run: pnpm i -g oxlint-json-to-sarif - - name: Lint - run: pnpm exec oxlint --type-aware --config=.oxlintrc.ci.json - --format=json | oxlint-json-to-sarif > oxlint-results.sarif - continue-on-error: true - - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: oxlint-results.sarif - wait-for-processing: true - - name: Upload Artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - path: oxlint-results.sarif + - parallel: + - name: Typecheck + run: pnpm typecheck + - name: Lint + run: pnpm exec oxlint --type-aware --config=.oxlintrc.ci.json + --format=json | oxlint-json-to-sarif > oxlint-results.sarif + continue-on-error: true + - parallel: + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: oxlint-results.sarif + wait-for-processing: true + - name: Upload Artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: oxlint-results.sarif # https://github.com/amannn/action-semantic-pull-request title-lint: @@ -65,9 +69,10 @@ jobs: runs-on: ubuntu-slim timeout-minutes: 5 steps: - - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 + - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + sync-labels: true vouch-check-pr: name: Vouch check PR @@ -79,12 +84,63 @@ jobs: runs-on: ubuntu-slim timeout-minutes: 5 steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check if PR author is denounced - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: vouch + uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0 + with: + pr-number: ${{ github.event.pull_request.number }} + auto-close: true + require-vouch: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Label denounced PR as spam + if: ${{ steps.vouch.outputs.status == 'closed' }} + run: gh pr edit ${{ github.event.pull_request.number }} --add-label spam + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + + agentscan: + name: AgentScan + needs: labeler + if: ${{ github.event_name == 'pull_request_target' && github.event.action == 'opened' && github.repository == 'DIYgod/RSSHub' }} + permissions: + contents: read + issues: write + pull-requests: write + runs-on: ubuntu-slim + timeout-minutes: 10 + steps: + - name: AgentScan + uses: MatteoGabriele/agentscan-action@8112fb79b33fafb8506159df20129a34209ac410 # v2.5.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + trusted-author-associations: 'collaborator,member,owner' + cache-path: .agentscan-cache + label-community-flagged: spam + label-mixed: spam + label-automation: spam + auto-close: true + auto-close-classifications: automation,mixed + + anti-slop: + name: Anti Slop + needs: labeler + if: ${{ github.event_name == 'pull_request_target' && github.event.action == 'opened' && github.repository == 'DIYgod/RSSHub' }} + runs-on: ubuntu-slim + permissions: + contents: read + issues: read + pull-requests: write + steps: + - name: Anti Slop + uses: peakoss/anti-slop@57858eead489d08b255fab2af45a506c2ca6eab2 # v0.3.0 with: - script: | - const { default: checkPr } = await import('${{ github.workspace }}/scripts/workflow/vouch/check-pr.mjs') - await checkPr({ github, context, core }) + close-pr: true + exempt-label: exempt + failure-add-pr-labels: spam + failure-pr-message: | + Thanks for the contribution. This PR was automatically closed because it matched multiple low-quality/spam [signals](https://github.com/peakoss/anti-slop). + lock-pr: false + max-changed-lines: 1000 + max-description-length: 2000 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 5dcd201dc749..c0d964990a1f 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -19,12 +19,10 @@ jobs: if: github.repository == 'DIYgod/RSSHub' runs-on: ubuntu-slim timeout-minutes: 10 - env: - HUSKY: 0 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index 512b7b734d51..728c7c0b8c60 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # https://github.com/orgs/community/discussions/25220#discussioncomment-11316244 - name: Search the PR that triggered this workflow @@ -68,108 +68,85 @@ jobs: Please fix the Docker build test issues first." - - name: Set up Bun - if: github.event_name == 'workflow_dispatch' || (steps.check-pr-author.outputs.skip != 'true' && github.event.workflow_run.conclusion != 'failure') - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - - - name: Install opencode - if: github.event_name == 'workflow_dispatch' || (steps.check-pr-author.outputs.skip != 'true' && github.event.workflow_run.conclusion != 'failure') - run: curl -fsSL https://opencode.ai/install | bash - - - name: Review PR with rules + - name: Check PR number if: github.event_name == 'workflow_dispatch' || (steps.check-pr-author.outputs.skip != 'true' && github.event.workflow_run.conclusion != 'failure') env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ steps.source-run-info.outputs.number || inputs.pr_number }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "base64*": "allow", - "cat*": "allow", - "echo*": "allow", - "gh api*": "allow", - "gh auth*": "allow", - "gh pr comment*": "allow", - "gh pr diff*": "allow", - "gh pr view*": "allow", - "gh repo view*": "allow", - "gh search*": "allow", - "git diff*": "allow", - "git log*": "allow", - "git show*": "allow", - "git status*": "allow", - "grep*": "allow", - "head*": "allow", - "ls*": "allow", - "sed*": "allow", - "tail*": "allow", - "wc*": "allow" - }, - "webfetch": "deny" - } run: | if [ -z "$PR_NUMBER" ]; then echo "pr_number is required" exit 1 fi - RULES=$(cat .github/prompts/pr_review_rules.md) - - opencode run -m ${{ vars.OPENCODE_MODEL }} "A pull request has been created or updated in this repository. - - Pull request number: - $PR_NUMBER - - Repository: - $GITHUB_REPOSITORY - - Your task: - 1. Use GitHub CLI commands to inspect this PR's metadata and code changes. - 2. Review only based on the following rules. - 3. Report only clear and actionable violations from changed files. - 4. If no clear violations are found, do not comment. - - Review rules: - $RULES - - Required behavior: - - Keep feedback concise and grouped by rule. - - Include file path and a concrete fix suggestion for each issue. - - Ignore uncertain or low-confidence findings. - - Avoid duplicate comments. - - Comment protocol: - - Use marker: - - Check existing comments in issue comments for this marker. - - If a marker comment exists, update it with latest findings. - - Otherwise create a new PR comment. - - If there are no findings and marker comment exists, edit marker comment to a short pass status. - - gh command reference (PR_NUMBER and GITHUB_REPOSITORY are available as env vars): - - Create a new PR comment: - gh pr comment \"\$PR_NUMBER\" --repo \"\$GITHUB_REPOSITORY\" --body \" - ## Auto Review - ...\" - - List existing marker comments to find the one to update: - gh pr view \"\$PR_NUMBER\" --repo \"\$GITHUB_REPOSITORY\" --json comments \\ - --jq '.comments[] | select(.body | startswith(\"\")) | {id: .id, body: .body}' - - Update an existing marker comment (use the numeric id from the URL, not the node id): - gh api \"repos/\$GITHUB_REPOSITORY/issues/comments/\" -X PATCH -f body=\" + - name: Review PR with rules + if: github.event_name == 'workflow_dispatch' || (steps.check-pr-author.outputs.skip != 'true' && github.event.workflow_run.conclusion != 'failure') + uses: anthropics/claude-code-action@v1 + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ steps.source-run-info.outputs.number || inputs.pr_number }} + CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + display_report: 'true' + allowed_non_write_users: '*' + claude_args: | + --model ${{ vars.OPENCODE_MODEL }} + ${{ vars.CLAUDE_EFFORT && format('--effort {0}', vars.CLAUDE_EFFORT) || '' }} + --allowedTools "Bash(base64:*),Bash(cat:*),Bash(echo:*),Bash(gh api:*),Bash(gh auth:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh repo view:*),Bash(gh search:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git status:*),Bash(grep:*),Bash(head:*),Bash(ls:*),Bash(rg:*),Bash(sed:*),Bash(tail:*),Bash(wc:*),${{ github.event_name == 'workflow_dispatch' && 'WebFetch,WebSearch' || 'WebFetch(domain:docs.rsshub.app)' }}" + ${{ github.event_name != 'workflow_dispatch' && '--disallowedTools "WebSearch"' || '' }} + prompt: | + A pull request has been created or updated in this repository. + + Pull request number: + ${{ steps.source-run-info.outputs.number || inputs.pr_number }} + + Repository: + ${{ github.repository }} + + Your task: + 1. Use GitHub CLI commands to inspect this PR's metadata and code changes. + 2. Review only based on the following rules. + 3. Report only clear and actionable violations from changed files. + + Review rules: + Read them from .github/prompts/pr_review_rules.md and AGENTS.md in the checked-out repository. + + Required behavior: + - Keep feedback concise and grouped by rule. + - Include file path and a concrete fix suggestion for each issue. + - Ignore uncertain or low-confidence findings. + - Avoid duplicate comments. + + Comment protocol: + - Use marker: + - Check existing comments in issue comments for this marker. + - If a marker comment exists, update it with latest findings. + - Otherwise create a new PR comment. + - If there are no findings: when a marker comment exists, edit it to the short pass status below; when none exists, do not comment. + + gh command reference (PR_NUMBER and GITHUB_REPOSITORY are available as env vars): + - Create a new PR comment: + gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body " + ## Auto Review + ..." + - List existing marker comments to find the one to update: + gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json comments \ + --jq '.comments[] | select(.body | startswith("")) | {id: .id, body: .body}' + - Update an existing marker comment (use the numeric id from the URL, not the node id): + gh api "repos/$GITHUB_REPOSITORY/issues/comments/" -X PATCH -f body=" + ## Auto Review + ..." + - Prefer --body-file - with a heredoc when the body contains quotes or many lines. + + Suggested comment format: + ## Auto Review - ...\" - - Prefer --body-file - with a heredoc when the body contains quotes or many lines. - - Suggested comment format: - - ## Auto Review - - [Rule] file: issue + suggestion + - [Rule] file: issue + suggestion - For no findings: - - ## Auto Review - No clear rule violations found in the current diff. + For no findings: + + ## Auto Review + No clear rule violations found in the current diff. - Use only gh commands and repository data." + Use only gh commands and repository data. diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index b7bda03ddc4f..f20c0407ebe7 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -22,7 +22,7 @@ jobs: permissions: security-events: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: semgrep ci --sarif > semgrep.sarif env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} diff --git a/.github/workflows/similar-issues.yml b/.github/workflows/similar-issues.yml index 95372a91e63f..d981778074f3 100644 --- a/.github/workflows/similar-issues.yml +++ b/.github/workflows/similar-issues.yml @@ -18,54 +18,46 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check for duplicate issues + uses: anthropics/claude-code-action@v1 env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } - run: | - if [ -z "$ISSUE_NUMBER" ]; then - echo "issue_number is required" - exit 1 - fi - - opencode run -m ${{ vars.OPENCODE_MODEL }} "A new issue has been created:' - - Issue number: - $ISSUE_NUMBER - - Lookup this issue and search through existing issues (excluding #$ISSUE_NUMBER) in this repository (DIYgod/RSSHub) to find any potential duplicates of this new issue. - Consider: - 1. Similar titles or descriptions - 2. Same error messages or symptoms - 3. Related functionality or components - 4. Similar feature requests - - If you find any potential duplicates, please comment on the new issue with: - - A brief explanation of why it might be a duplicate - - Links to the potentially duplicate issues - - A suggestion to check those issues first - - Use this format for the comment: - 'This issue might be a duplicate of existing issues. Please check: - - #[issue_number]: [brief description of similarity] - - Feel free to ignore if none of these address your specific case.' - - If no clear duplicates are found, do not comment." + CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: 0 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + display_report: 'true' + allowed_non_write_users: '*' + claude_args: | + --model ${{ vars.OPENCODE_MODEL }} + ${{ vars.CLAUDE_EFFORT && format('--effort {0}', vars.CLAUDE_EFFORT) || '' }} + --allowedTools "Bash(gh issue:*),Bash(gh search:*)" + --disallowedTools "WebFetch,WebSearch" + prompt: | + A new issue has been created: + + Issue number: + ${{ github.event.issue.number || inputs.issue_number }} + + Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number || inputs.issue_number }}) in this repository (${{ github.repository }}) to find any potential duplicates of this new issue. + Consider: + 1. Similar titles or descriptions + 2. Same error messages or symptoms + 3. Related functionality or components + 4. Similar feature requests + + If you find any potential duplicates, please comment on the new issue with: + - A brief explanation of why it might be a duplicate + - Links to the potentially duplicate issues + - A suggestion to check those issues first + + Use this format for the comment: + "This issue might be a duplicate of existing issues. Please check: + - #[issue_number]: [brief description of similarity] + + Feel free to ignore if none of these address your specific case." + + If no clear duplicates are found, do not comment. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 83efc8d6bad8..088d6cea4235 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: stale: runs-on: ubuntu-slim steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: # Don't stale issues days-before-issue-stale: -1 @@ -22,13 +22,13 @@ jobs: stale-pr-message: > This PR is stale because it has been opened for more than 3 weeks with no activity. Comment or this will be closed in 7 days. close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' - close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 30 days with no activity.' exempt-issue-labels: 'wait for upstream' exempt-pr-labels: 'wait for upstream' any-of-issue-labels: 'more data required' - name: Close Broken PRs - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-issue-stale: -1 days-before-issue-close: -1 diff --git a/.github/workflows/test-full-routes.yml b/.github/workflows/test-full-routes.yml index bf306839795d..b925872af453 100644 --- a/.github/workflows/test-full-routes.yml +++ b/.github/workflows/test-full-routes.yml @@ -3,7 +3,7 @@ name: Build assets (Full Routes Test Result) on: workflow_dispatch: schedule: - - cron: '0 0 * * *' + - cron: '0 0 * * 0' jobs: build: @@ -14,15 +14,15 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - name: Use Node.js Active LTS - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: lts/* cache: 'pnpm' - - name: Install dependencies (yarn) + - name: Install dependencies run: pnpm i - name: Build assets run: pnpm build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aedd27b554ed..cb9553afc9ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ on: permissions: checks: write + code-quality: write jobs: vitest: @@ -27,35 +28,44 @@ jobs: strategy: fail-fast: false matrix: - # playwright install hangs in node v26.1.0, https://github.com/microsoft/playwright/issues/40724 - node-version: [26.0.0, lts/*, lts/-1] + node-version: [latest, lts/*] name: Vitest on Node ${{ matrix.node-version }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' - name: Install dependencies (pnpm) run: pnpm i - name: Run postinstall script for dependencies - run: pnpm rb && pnpm exec playwright install chromium - - name: Build routes - run: pnpm build - - name: Build worker routes - run: WORKER_BUILD=true pnpm build:routes - - name: Build worker - run: pnpm worker-build + run: pnpm rb && pnpm exec patchright install chromium + - parallel: + - name: Build routes + run: pnpm build + - name: Build worker + run: pnpm worker-build - name: Test all and generate coverage run: pnpm run vitest:coverage --reporter=github-actions env: REDIS_URL: redis://localhost:${{ job.services.redis.ports['6379'] }}/ - - name: Upload coverage to Codecov - if: ${{ matrix.node-version == 'lts/*' }} - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos as documented, but seems broken + - name: Test worker runtime (workerd) + run: pnpm run vitest:workerd + - parallel: + - name: Upload coverage to Codecov + if: ${{ matrix.node-version == 'lts/*' }} + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos as documented, but seems broken + - name: Upload coverage to GitHub + if: ${{ matrix.node-version == 'lts/*' }} + uses: actions/upload-code-coverage@2b21a77928be8d5168c2b9581a67f2adbebacc52 # v1.4.4 + with: + file: coverage/cobertura-coverage.xml + language: TypeScript + label: code-coverage/vitest + fail-on-error: false playwright: runs-on: ubuntu-latest @@ -63,7 +73,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [26.0.0, lts/*, lts/-1] + node-version: [latest, lts/*] chromium: - name: bundled Chromium dependency: '' @@ -76,9 +86,9 @@ jobs: environment: '{ "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1" }' name: Vitest Playwright on Node ${{ matrix.node-version }} with ${{ matrix.chromium.name }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -87,25 +97,26 @@ jobs: env: ${{ fromJSON(matrix.chromium.environment) }} - name: Run postinstall script for dependencies run: pnpm rb - - name: Build routes - run: pnpm build - - name: Install bundled Chromium - if: ${{ matrix.chromium.dependency == '' }} - run: pnpm exec playwright install chromium - - name: Install Chromium - if: ${{ matrix.chromium.dependency != '' }} - # 'chromium-browser' from Ubuntu APT repo is a dummy package. Its version (85.0.4183.83) means - # nothing since it calls Snap (disgusting!) to install Chromium, which should be up-to-date. - # That's not really a problem since the Chromium-bundled Docker image is based on Debian bookworm, - # which provides up-to-date native packages. - run: | - set -eux - curl -s "https://dl.google.com/linux/linux_signing_key.pub" | gpg --dearmor | - sudo tee /etc/apt/trusted.gpg.d/google-chrome.gpg > /dev/null - echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" | - sudo tee /etc/apt/sources.list.d/google-chrome.list > /dev/null - sudo apt-get update - sudo apt-get install -yq --no-install-recommends ${{ matrix.chromium.dependency }} + - parallel: + - name: Build routes + run: pnpm build + - name: Install bundled Chromium + if: ${{ matrix.chromium.dependency == '' }} + run: pnpm exec patchright install chromium + - name: Install Chromium + if: ${{ matrix.chromium.dependency != '' }} + # 'chromium-browser' from Ubuntu APT repo is a dummy package. Its version (85.0.4183.83) means + # nothing since it calls Snap (disgusting!) to install Chromium, which should be up-to-date. + # That's not really a problem since the Chromium-bundled Docker image is based on Debian trixie, + # which provides up-to-date native packages. + run: | + set -eux + curl -s "https://dl.google.com/linux/linux_signing_key.pub" | gpg --dearmor | + sudo tee /etc/apt/trusted.gpg.d/google-chrome.gpg > /dev/null + echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" | + sudo tee /etc/apt/sources.list.d/google-chrome.list > /dev/null + sudo apt-get update + sudo apt-get install -yq --no-install-recommends ${{ matrix.chromium.dependency }} - name: Test Playwright run: | set -eux @@ -122,12 +133,12 @@ jobs: strategy: fail-fast: false matrix: - node-version: [26, 24, 22] + node-version: [26, 24] name: Build radar and maintainer on Node ${{ matrix.node-version }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -148,7 +159,7 @@ jobs: pull-requests: write contents: write steps: - - uses: fastify/github-action-merge-dependabot@30c3f8f14a4f7b315ba38dbc1b793d27128fef82 # v3.12.0 + - uses: fastify/github-action-merge-dependabot@73ec4cbb5e56df5591eae286972d5b2201ffe90f # v3.15.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} target: patch diff --git a/.github/workflows/update-nix-hash.yml b/.github/workflows/update-nix-hash.yml index 51f1f97812c6..6fc91e28417b 100644 --- a/.github/workflows/update-nix-hash.yml +++ b/.github/workflows/update-nix-hash.yml @@ -18,13 +18,13 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Nix - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 + uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 with: nix_path: nixpkgs=channel:nixos-unstable - name: Cache Nix store - uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # v14 + uses: DeterminateSystems/magic-nix-cache-action@84c0677f58dcedf3b91f8223ce36a9ea5b3c84b7 # v15 - name: Update Nix flake hash id: update-hash diff --git a/.gitignore b/.gitignore index 4d6ad18b583e..c91e2604f62f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ .env .eslintcache .idea -.pre-commit-config.yaml .log .now .roomodes @@ -27,8 +26,10 @@ memory-bank/ node_modules tmp dist +dist-container dist-lib dist-worker +.vitest .wrangler Session.vim @@ -40,5 +41,3 @@ package-lock.json # pnpm-lock.yaml yarn.lock yarn-error.log - -dist-container diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index c27d8893a994..000000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1 +0,0 @@ -lint-staged diff --git a/.idx/dev.nix b/.idx/dev.nix deleted file mode 100644 index f5fb581a466d..000000000000 --- a/.idx/dev.nix +++ /dev/null @@ -1,63 +0,0 @@ -# To learn more about how to use Nix to configure your environment -# see: https://firebase.google.com/docs/studio/customize-workspace -{ pkgs, ... }: { - # Which nixpkgs channel to use. - channel = "unstable"; # or "unstable" - - # Use https://search.nixos.org/packages to find packages - packages = [ - # pkgs.go - # pkgs.python311 - # pkgs.python311Packages.pip - pkgs.nodejs_22 - pkgs.pnpm_9 - # pkgs.nodePackages.nodemon - pkgs.cacert - pkgs.valkey - ]; - - # Sets environment variables in the workspace - env = {}; - idx = { - # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" - extensions = [ - "cweijan.vscode-database-client2" - "dbaeumer.vscode-eslint" - "eamodio.gitlens" - "EditorConfig.EditorConfig" - "oxc.oxc-vscode" - "sonarsource.sonarlint-vscode" - ]; - - # Enable previews - previews = { - enable = true; - previews = { - # web = { - # # Example: run "npm run dev" with PORT set to IDX's defined port for previews, - # # and show it in IDX's web preview panel - # command = ["npm" "run" "dev"]; - # manager = "web"; - # env = { - # # Environment variables to set for your server - # PORT = "$PORT"; - # }; - # }; - }; - }; - - # Workspace lifecycle hooks - workspace = { - # Runs when a workspace is first created - onCreate = { - # Example: install JS dependencies from NPM - pnpm-install = "pnpm i && pnpm rb && pnpx rebrowser-puppeteer browsers install chrome"; - }; - # Runs when the workspace is (re)started - onStart = { - # Example: start a background task to watch and re-build backend code - # watch-backend = "npm run watch-backend"; - }; - }; - }; -} diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 0e90a74943fe..ca8be44404d8 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -5,7 +5,7 @@ "singleQuote": true, "trailingComma": "es5", "arrowParens": "always", - "ignorePatterns": ["lib/routes-deprecated", "lib/router.js", "babel.config.js", "scripts/docker/minify-docker.js", "dist", "pnpm-lock.yaml"], + "ignorePatterns": ["scripts/docker/minify-docker.js", "dist", "pnpm-lock.yaml"], "sortPackageJson": { "sortScripts": true }, diff --git a/.oxlintrc.ci.json b/.oxlintrc.ci.json index 7a7509dc2253..c756f51ec387 100644 --- a/.oxlintrc.ci.json +++ b/.oxlintrc.ci.json @@ -1,9 +1,35 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", "extends": ["./.oxlintrc.json"], - // extends doesn't extend ignorePatterns, oxc-project/oxc#10223, oxc-project/oxc#16079 - "ignorePatterns": ["**/coverage", "**/.vscode", "**/docker-compose.yml", "!.github", "assets/build", "lib/routes-deprecated", "lib/router.js", "dist", "dist-lib", "dist-worker"], + // extends doesn't extend ignorePatterns and env, oxc-project/oxc#10223, oxc-project/oxc#16079 + "ignorePatterns": ["**/coverage", "**/.vscode", "**/docker-compose.yml", "!.github", "assets/build", "eslint-plugins/anti-slop/**", "dist", "dist-lib", "dist-worker"], + "env": { + "builtin": true, + "browser": true, + "es2026": true, + "node": true + }, "options": { "respectEslintDisableDirectives": false + }, + "rules": { + // "anti-slop/no-array-filter-map": "error", + // "anti-slop/no-chained-type-assertions": "error", + "anti-slop/no-conditional-empty-object-spread": "error", + "anti-slop/no-known-value-widening": "error", + // "anti-slop/no-module-mocking": "error", + "anti-slop/no-object-parameters": "error", + "anti-slop/no-reduce-accumulator-copy": "error", + "anti-slop/no-reflect-apply": "error", + "anti-slop/no-reflect-get": "error", + // "anti-slop/no-runtime-typeof": ["error", { "allowInTypeGuards": true }], + "anti-slop/no-shape-in-symbol-names": "error", + "anti-slop/no-unknown-parameters": "error", + "anti-slop/no-unknown-returns": "error", + "anti-slop/no-unknown-type-aliases": "error", + "anti-slop/no-unsafe-dictionary-type": "error", + "anti-slop/no-widen-then-assert": "error" + // "anti-slop/require-readable-spacing": "error", + // "anti-slop/require-safety-comment-for-type-assertion": "error" } } diff --git a/.oxlintrc.json b/.oxlintrc.json index 85967256d352..c6fd167b69e6 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -3,22 +3,23 @@ "categories": { "correctness": "off" }, - "ignorePatterns": ["**/coverage", "**/.vscode", "**/docker-compose.yml", "!.github", "assets/build", "lib/routes-deprecated", "lib/router.js", "dist", "dist-lib", "dist-worker"], + "ignorePatterns": ["**/coverage", "**/.vscode", "**/docker-compose.yml", "!.github", "assets/build", "eslint-plugins/anti-slop/**", "dist", "dist-lib", "dist-worker"], "env": { "builtin": true, "browser": true, "es2026": true, "node": true }, - "plugins": ["eslint", "typescript", "node", "unicorn", "import"], + "plugins": ["eslint", "typescript", "node", "unicorn", "import", "oxc"], "jsPlugins": [ { "name": "n", "specifier": "eslint-plugin-n" }, { "name": "unicorn-js", "specifier": "eslint-plugin-unicorn" }, + { "name": "regexp", "specifier": "eslint-plugin-regexp" }, "@stylistic/eslint-plugin", "eslint-plugin-simple-import-sort", - "oxlint-plugin-eslint", - "./eslint-plugins/no-then.js", - "./eslint-plugins/nsfw-flag.js" + { "name": "eslint-js", "specifier": "oxlint-plugin-eslint" }, + "./eslint-plugins/nsfw-flag.js", + { "name": "anti-slop", "specifier": "./eslint-plugins/anti-slop/index.ts" } ], "rules": { // #region --- ESLint js/recommended possible problems --- @@ -32,26 +33,26 @@ "no-const-assign": "error", "no-constant-binary-expression": "error", "no-constant-condition": "error", - // "no-control-regex": "error", -> off + "no-control-regex": "error", "no-debugger": "error", "no-dupe-class-members": "error", "no-dupe-else-if": "error", "no-dupe-keys": "error", "no-duplicate-case": "error", - "no-empty-character-class": "error", + // "no-empty-character-class": "error", off, handled by eslint-plugin-regexp "no-empty-pattern": "error", "no-ex-assign": "error", "no-fallthrough": "error", "no-func-assign": "error", "no-import-assign": "error", - "no-invalid-regexp": "error", + // "no-invalid-regexp": "error", off, handled by eslint-plugin-regexp "no-irregular-whitespace": "error", "no-loss-of-precision": "error", "no-misleading-character-class": "error", "no-new-native-nonconstructor": "error", "no-nonoctal-decimal-escape": "error", "no-obj-calls": "error", - // "no-prototype-builtins": "error", -> off + // "no-prototype-builtins": "error", off "no-self-assign": "error", "no-setter-return": "error", "no-sparse-arrays": "error", @@ -64,8 +65,8 @@ "no-unsafe-negation": "error", "no-unsafe-optional-chaining": "error", "no-unused-private-class-members": "error", - // "no-unused-vars": "error", -> off for @typescript-eslint/no-unused-vars - "no-useless-backreference": "error", + // "no-unused-vars": "error", off, handled by @typescript-eslint/no-unused-vars + // "no-useless-backreference": "error", off, handled by eslint-plugin-regexp "use-isnan": "error", "valid-typeof": "error", // #endregion @@ -148,45 +149,113 @@ // #endregion // #region --- unicorn recommended --- + // "unicorn-js/better-dom-traversing": "error", false positive on cheerio "unicorn/catch-error-name": "error", + "unicorn-js/class-reference-in-static-methods": "error", "unicorn/consistent-assert": "error", + // "unicorn-js/consistent-boolean-name": "error", // unopinionated + "unicorn-js/consistent-class-member-order": "error", + "unicorn-js/consistent-compound-words": "error", + "unicorn-js/consistent-conditional-object-spread": "error", "unicorn/consistent-date-clone": "error", "unicorn/consistent-empty-array-spread": "error", "unicorn/consistent-existence-index-check": "error", - // "unicorn/consistent-function-scoping": "error", + "unicorn-js/consistent-export-decorator-position": "error", + // "unicorn/consistent-function-scoping": "error", warn + "unicorn-js/consistent-json-file-read": "error", + "unicorn-js/consistent-optional-chaining": "error", "unicorn/consistent-template-literal-escape": "error", + "unicorn-js/consistent-tuple-labels": "error", + "unicorn-js/default-export-style": "error", "unicorn/empty-brace-spaces": "error", "unicorn/error-message": "error", "unicorn/escape-case": "error", // "unicorn/expiring-todo-comments": "error", // not yet implemented // "unicorn/explicit-length-check": "error", + "unicorn/explicit-timer-delay": "error", // "unicorn/filename-case": "error", "unicorn/import-style": "error", - "unicorn-js/isolated-functions": "error", // use jsPlugins + "unicorn-js/isolated-functions": ["error", { "overrideGlobals": { "document": "readonly", "window": "readonly", "fetch": "readonly", "XMLHttpRequest": "readonly" } }], + "unicorn-js/logical-assignment-operators": "error", + "unicorn/max-nested-calls": "error", + // "unicorn-js/name-replacements": "error", // unopinionated, rebranded prevent-abbreviations "unicorn/new-for-builtins": "error", "unicorn/no-abusive-eslint-disable": "error", "unicorn/no-accessor-recursion": "error", + // "unicorn-js/no-accidental-bitwise-operator": "error", off, handled by oxc/bad-bitwise-operator "unicorn/no-anonymous-default-export": "error", // "unicorn/no-array-callback-reference": "error", + "unicorn-js/no-array-concat-in-loop": "error", + "unicorn/no-array-fill-with-reference-type": "error", "unicorn/no-array-for-each": "error", + "unicorn-js/no-array-from-fill": "error", "unicorn/no-array-method-this-argument": "error", // "unicorn/no-array-reduce": "error", - "unicorn/no-array-reverse": "error", + // "unicorn/no-array-reverse": "error", // "unicorn/no-array-sort": "error", + "unicorn-js/no-array-sort-for-min-max": "error", + "unicorn-js/no-array-splice": "error", + "unicorn-js/no-async-promise-finally": "error", + "unicorn-js/no-async-iterator-callback": "error", // "unicorn/no-await-expression-member": "error", "unicorn/no-await-in-promise-methods": "error", + // "unicorn-js/no-blob-to-file": "error", // unused + // "unicorn-js/no-boolean-sort-comparator": "error", off, handled by tsc + // "unicorn-js/no-break-in-nested-loop": "error", // unopinionated + // "unicorn-js/no-canvas-to-image": "error", // unused + // "unicorn-js/no-chained-comparison": "error", off, handled by oxc/bad-comparison-sequence + // "unicorn-js/no-collection-bracket-access": "error", off, handled by tsc + "unicorn-js/no-computed-property-existence-check": "error", + "unicorn-js/no-conflicting-constraints": "error", + "unicorn-js/no-confusing-array-splice": "error", + "unicorn/no-confusing-array-with": "error", "unicorn/no-console-spaces": "error", + // "unicorn-js/no-constant-zero-expression": "error", off, handled by oxc/erasing-op + // "unicorn-js/no-declarations-before-early-exit": "error", // unopinionated "unicorn/no-document-cookie": "error", + // "unicorn-js/no-double-comparison": "error", off, handled by oxc/double-comparisons + "unicorn-js/no-duplicate-if-branches": "error", + "unicorn-js/no-duplicate-logical-operands": "error", + "unicorn-js/no-duplicate-loops": "error", + "unicorn-js/no-duplicate-set-values": "error", // "unicorn/no-empty-file": "error", + "unicorn-js/no-error-property-assignment": "error", + "unicorn-js/no-exports-in-scripts": "error", // "unicorn/no-for-loop": "error", // won't be implemented - // "unicorn/no-hex-escape": "error", + "unicorn-js/no-global-object-property-assignment": "error", "unicorn/no-immediate-mutation": "error", + "unicorn-js/no-impossible-length-comparison": "error", + "unicorn-js/no-incomplete-accessor-override": "error", + "unicorn-js/no-incorrect-query-selector": "error", + // "unicorn-js/no-incorrect-template-string-interpolation": "error", // unopinionated, too many false positives in v68 + "unicorn-js/no-ineffective-csp-directives": "error", "unicorn/no-instanceof-builtins": "error", + // "unicorn-js/no-invalid-argument-count": "error", off, handled by tsc + "unicorn-js/no-invalid-boolean-attribute-value": "error", + // "unicorn-js/no-invalid-character-comparison": "error", off, handled by oxc/bad-char-at-comparison + "unicorn-js/no-invalid-dom-token": "error", "unicorn/no-invalid-fetch-options": "error", + "unicorn-js/no-invalid-integrity": "error", + "unicorn-js/no-invalid-intl-options": "error", + "unicorn-js/no-invalid-property-descriptor": "error", "unicorn/no-invalid-remove-event-listener": "error", + "unicorn-js/no-invalid-response-options": "error", + "unicorn-js/no-invalid-style-set-property": "error", + "unicorn-js/no-invalid-temporal-arithmetic": "error", + "unicorn-js/no-invalid-url-protocol-comparison": "error", + "unicorn-js/no-invalid-well-known-symbol-methods": "error", + "unicorn-js/no-late-current-target-access": "error", + "unicorn-js/no-late-event-control": "error", + "unicorn-js/no-leading-empty-lines": "error", "unicorn/no-lonely-if": "error", + "unicorn-js/no-loop-iterable-mutation": "error", "unicorn/no-magic-array-flat-depth": "error", - // "unicorn/no-named-default": "error", -> use import/no-named-default + "unicorn-js/no-mismatched-map-key": "error", + // "unicorn-js/no-misrefactored-assignment": "error", off, handled by oxc/misrefactored-assign-op + "unicorn-js/no-multiple-promise-resolver-calls": "error", + // "unicorn/no-named-default": "error", use import/no-named-default + "unicorn-js/no-negated-array-predicate": "error", + "unicorn-js/no-negated-comparison": "error", "no-negated-condition": "off", "unicorn/no-negated-condition": "error", "unicorn/no-negation-in-equality-check": "error", @@ -194,107 +263,304 @@ // "unicorn/no-nested-ternary": "error", "unicorn/no-new-array": "error", "unicorn/no-new-buffer": "error", + "unicorn-js/no-non-function-verb-prefix": "error", + // "unicorn-js/no-nonstandard-builtin-properties": "error", off, handled by tsc // "unicorn/no-null": "error", // "unicorn/no-object-as-default-parameter": "error", + "unicorn-js/no-object-methods-with-collections": "error", + "unicorn-js/no-optional-chaining-on-undeclared-variable": "error", + "unicorn-js/no-prevent-default-in-passive-listener": "error", // "unicorn/no-process-exit": "error", + "unicorn-js/no-redundant-comparison": "error", + "unicorn-js/no-return-array-push": "error", + "unicorn-js/no-selector-as-dom-name": "error", + // "unicorn-js/no-shorthand-property-overrides": "error", // css "unicorn/no-single-promise-in-promise-methods": "error", "unicorn/no-static-only-class": "error", + "unicorn-js/no-subtraction-comparison": "error", "unicorn/no-thenable": "error", "unicorn/no-this-assignment": "error", + "unicorn-js/no-this-outside-of-class": "error", + // "unicorn-js/no-transition-all": "error", // css + // "unicorn-js/no-top-level-assignment-in-function": "error", // unopinionated + // "unicorn-js/no-top-level-side-effects": "error", // off, allows dayjs.extend() "unicorn/no-typeof-undefined": "error", + "unicorn-js/no-uncalled-method": "error", + // "unicorn-js/no-undeclared-class-members": "error", off, handled by tsc "unicorn/no-unnecessary-array-flat-depth": "error", + "unicorn-js/no-unnecessary-array-flat-map": "error", "unicorn/no-unnecessary-array-splice-count": "error", "unicorn/no-unnecessary-await": "error", - "unicorn-js/no-unnecessary-polyfills": "error", // use jsPlugins + // "unicorn-js/no-unnecessary-boolean-comparison": "error", off, handled by @typescript-eslint/no-unnecessary-boolean-literal-compare + "unicorn-js/no-unnecessary-fetch-options": "error", + "unicorn-js/no-unnecessary-global-this": "error", + "unicorn-js/no-unnecessary-nested-ternary": "error", + // "unicorn-js/no-unnecessary-parameters": "error", warn + "unicorn-js/no-unnecessary-polyfills": "error", "unicorn/no-unnecessary-slice-end": "error", + "unicorn-js/no-unnecessary-splice": "error", + "unicorn-js/no-unnecessary-string-trim": "error", "unicorn/no-unreadable-array-destructuring": "error", + "unicorn-js/no-unreadable-for-of-expression": "error", "unicorn/no-unreadable-iife": "error", + "unicorn-js/no-unreadable-object-destructuring": "error", + "unicorn-js/no-unsafe-buffer-conversion": "error", + "unicorn-js/no-unsafe-json-serialization": "error", + // "unicorn-js/no-unsafe-promise-all-settled-values": "error", off, handled by tsc + "unicorn-js/no-unsafe-property-key": "error", + // "unicorn-js/no-unsafe-sqlite-interpolation": "error", // unused + "unicorn-js/no-unsafe-string-replacement": "error", + "unicorn-js/no-unused-builtin-method-return": "error", + "unicorn-js/no-unused-iterator-helper": "error", + "unicorn-js/no-url-in-search-params": "error", + "unicorn-js/no-useless-boolean-cast": "error", + // "unicorn-js/no-useless-coercion": "error", off, handled by @typescript-eslint/no-unnecessary-type-conversion "unicorn/no-useless-collection-argument": "error", + "unicorn-js/no-useless-compound-assignment": "error", + // "unicorn-js/no-useless-concat": "error", off, handled by no-useless-concat + "unicorn-js/no-useless-continue": "error", + "unicorn-js/no-useless-delete-check": "error", + "unicorn-js/no-useless-else": "error", "unicorn/no-useless-error-capture-stack-trace": "error", "unicorn/no-useless-fallback-in-spread": "error", // "unicorn/no-useless-iterator-to-array": "error", "unicorn/no-useless-length-check": "error", + "unicorn-js/no-useless-logical-operand": "error", + "unicorn-js/no-useless-override": "error", "unicorn/no-useless-promise-resolve-reject": "error", + "unicorn-js/no-useless-re-export": "error", + // "unicorn-js/no-useless-recursion": "error", unopinionated "unicorn/no-useless-spread": "error", + "unicorn-js/no-useless-set-construction": "error", // "unicorn/no-useless-switch-case": "error", + // "unicorn-js/no-useless-template-literals": "error", off, handled by @typescript-eslint/no-unnecessary-template-expression and no-implicit-coercion // "unicorn/no-useless-undefined": "error", + "unicorn-js/no-using-resource-escape": "error", + "unicorn-js/no-xor-as-exponentiation": "error", "unicorn/no-zero-fractions": "error", // "unicorn/number-literal-case": "error", // "unicorn/numeric-separators-style": "error", + "unicorn-js/operator-assignment": "error", + "unicorn-js/prefer-abort-signal-any": "error", + "unicorn-js/prefer-abort-signal-timeout": "error", "unicorn/prefer-add-event-listener": "error", + "unicorn-js/prefer-add-event-listener-options": "error", + "unicorn-js/prefer-aggregate-error": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-array-flat": "error", "unicorn/prefer-array-flat-map": "error", + "unicorn-js/prefer-array-from-async": "error", + "unicorn-js/prefer-array-from-map": "error", + "unicorn-js/prefer-array-from-range": "error", "unicorn/prefer-array-index-of": "error", + "unicorn-js/prefer-array-iterable-methods": "error", + "unicorn-js/prefer-array-last-methods": "error", + "unicorn-js/prefer-array-slice": "error", "unicorn/prefer-array-some": "error", "unicorn/prefer-at": "error", + "unicorn-js/prefer-await": "error", "unicorn/prefer-bigint-literals": "error", - "unicorn/prefer-blob-reading-methods": "error", + // "unicorn/prefer-blob-reading-methods": "error", // unused + "unicorn-js/prefer-block-statement-over-iife": "error", + "unicorn-js/prefer-boolean-return": "error", "unicorn/prefer-class-fields": "error", - "unicorn/prefer-classlist-toggle": "error", + "unicorn-js/prefer-combined-guards": "error", + // "unicorn/prefer-classlist-toggle": "error", // unused // "unicorn/prefer-code-point": "error", + "unicorn-js/prefer-continue": "error", "unicorn/prefer-date-now": "error", "unicorn/prefer-default-parameters": "error", + "unicorn-js/prefer-direct-iteration": "error", "unicorn/prefer-dom-node-append": "error", "unicorn/prefer-dom-node-dataset": "error", + "unicorn-js/prefer-dom-node-html-methods": "error", "unicorn/prefer-dom-node-remove": "error", + "unicorn-js/prefer-dom-node-replace-children": "error", "unicorn/prefer-dom-node-text-content": "error", + "unicorn-js/prefer-early-return": "error", + "unicorn-js/prefer-else-if": "error", + "unicorn-js/prefer-escaped-irregular-whitespace": "error", "unicorn/prefer-event-target": "error", - "unicorn-js/prefer-export-from": "error", // use jsPlugins + "unicorn/prefer-export-from": "error", + "unicorn-js/prefer-flat-math-min-max": "error", + "unicorn-js/prefer-get-or-insert-computed": "error", + "unicorn-js/prefer-global-number-constants": "error", // "unicorn/prefer-global-this": "error", + "unicorn-js/prefer-group-by": "error", + "unicorn-js/prefer-has-check": "error", + "unicorn-js/prefer-hoisting-branch-code": "error", + // "unicorn-js/prefer-https": "error", + "unicorn-js/prefer-identifier-import-export-specifiers": "error", "unicorn/prefer-includes": "error", - "unicorn/prefer-keyboard-event-key": "error", + "unicorn-js/prefer-includes-over-repeated-comparisons": "error", + "unicorn-js/prefer-iterable-in-constructor": "error", + "unicorn-js/prefer-iterator-helpers": "error", + "unicorn-js/prefer-iterator-to-array": "error", + "unicorn-js/prefer-iterator-zip": "error", + // "unicorn-js/prefer-iterator-to-array-at-end": "error", + // "unicorn/prefer-keyboard-event-key": "error", // unused + "unicorn-js/prefer-literal-ascii": "error", + "unicorn-js/prefer-location-assign": "error", "unicorn/prefer-logical-operator-over-ternary": "error", + "unicorn-js/prefer-map-from-entries": "error", + "unicorn-js/prefer-math-abs": "error", + // "unicorn-js/prefer-math-constants": "error", off, handled by oxc/approx-constant "unicorn/prefer-math-min-max": "error", "unicorn/prefer-math-trunc": "error", + // "unicorn-js/prefer-minimal-ternary": "error", // unopinionated, fixes have less readability "unicorn/prefer-modern-dom-apis": "error", "unicorn/prefer-modern-math-apis": "error", // "unicorn/prefer-module": "error", "unicorn/prefer-native-coercion-functions": "error", "unicorn/prefer-negative-index": "error", "unicorn/prefer-node-protocol": "error", - // "unicorn/prefer-number-properties": "error", + "unicorn/prefer-number-coercion": "error", + "unicorn-js/prefer-number-is-safe-integer": "error", + // "unicorn/prefer-number-properties": "error", // checkNaN: false + "unicorn-js/prefer-object-define-properties": "error", + "unicorn-js/prefer-object-destructuring-defaults": "error", "unicorn/prefer-object-from-entries": "error", + "unicorn-js/prefer-object-iterable-methods": "error", + "unicorn-js/prefer-observer-apis": "error", "unicorn/prefer-optional-catch-binding": "error", + // "unicorn-js/prefer-path2d": "error", // unused + "unicorn-js/prefer-private-class-fields": "error", + "unicorn-js/prefer-promise-static-methods": "error", + "unicorn-js/prefer-promise-try": "error", + "unicorn-js/prefer-promise-with-resolvers": "error", "unicorn/prefer-prototype-methods": "error", "unicorn/prefer-query-selector": "error", + "unicorn-js/prefer-queue-microtask": "error", "unicorn/prefer-reflect-apply": "error", "unicorn/prefer-regexp-test": "error", "unicorn/prefer-response-static-json": "error", + "unicorn-js/prefer-scoped-selector": "error", "unicorn/prefer-set-has": "error", + "unicorn-js/prefer-set-methods": "error", "unicorn/prefer-set-size": "error", - "unicorn-js/prefer-simple-condition-first": "error", // use jsPlugins - "unicorn-js/prefer-single-call": "error", // use jsPlugins + "unicorn-js/prefer-short-arrow-method": "warn", + "unicorn-js/prefer-short-escape-sequences": "error", + // "unicorn-js/prefer-simple-condition-first": "error", // unopinionated + "unicorn-js/prefer-simple-sort-comparator": "error", + "unicorn-js/prefer-simplified-conditions": "error", + "unicorn-js/prefer-single-array-predicate": "error", + "unicorn/prefer-single-call": "error", + "unicorn-js/prefer-single-object-destructuring": "error", + "unicorn-js/prefer-single-replace": "error", + "unicorn-js/prefer-smaller-scope": "error", + "unicorn-js/prefer-split-limit": "error", // "unicorn/prefer-spread": "error", + "unicorn-js/prefer-string-match-all": "error", + "unicorn-js/prefer-string-pad-start-end": "error", "unicorn/prefer-string-raw": "error", + "unicorn-js/prefer-string-repeat": "error", "unicorn/prefer-string-replace-all": "error", // "unicorn/prefer-string-slice": "error", - "unicorn/prefer-string-starts-ends-with": "error", + // "unicorn/prefer-string-starts-ends-with": "error", use typescript/prefer-string-starts-ends-with "unicorn/prefer-string-trim-start-end": "error", "unicorn/prefer-structured-clone": "error", - // "unicorn/prefer-switch": "error", // use jsPlugins + "unicorn-js/prefer-temporal-conversion": "error", + // "unicorn/prefer-switch": "error", "unicorn/prefer-ternary": "error", + "unicorn-js/prefer-then-catch": "error", + "unicorn-js/prefer-toggle-attribute": "error", // "unicorn/prefer-top-level-await": "error", "unicorn/prefer-type-error": "error", + "unicorn-js/prefer-type-literal-last": "error", + // "unicorn-js/prefer-uint8array-base64": "error", re-evaluate when node > 25 + "unicorn-js/prefer-unary-minus": "error", + "unicorn-js/prefer-unicode-code-point-escapes": "error", + "unicorn-js/prefer-url-can-parse": "error", + "unicorn-js/prefer-url-href": "error", + "unicorn-js/prefer-url-search-parameters": "error", + "unicorn-js/prefer-while-loop-condition": "error", // "unicorn/prevent-abbreviations": "error", "unicorn/relative-url-style": "error", "unicorn/require-array-join-separator": "error", + // "unicorn-js/require-array-sort-compare": "error", off, handled by @typescript-eslint/require-array-sort-compare + "unicorn-js/require-css-escape": "error", "unicorn/require-module-attributes": "error", "unicorn/require-module-specifiers": "error", "unicorn/require-number-to-fixed-digits-argument": "error", + "unicorn-js/require-passive-events": "error", + // "unicorn-js/require-proxy-trap-boolean-return": "error", off, handled by tsc + "unicorn-js/require-text-decoder-streaming": "error", + // "unicorn-js/single-line-block-comment-style": "error", // "unicorn/switch-case-braces": "error", - "unicorn-js/switch-case-break-position": "error", // use jsPlugins - "unicorn-js/template-indent": "error", // use jsPlugins + "unicorn-js/switch-case-break-position": "error", + "unicorn-js/template-indent": "error", // "unicorn/text-encoding-identifier-case": "error", "unicorn/throw-new-error": "error", // #endregion + // #region --- regexp recommended --- + "regexp/confusing-quantifier": "warn", + "regexp/control-character-escape": "error", + "regexp/match-any": "error", + "regexp/negation": "error", + "regexp/no-contradiction-with-assertion": "error", + "regexp/no-dupe-characters-character-class": "error", + "regexp/no-dupe-disjunctions": "error", + "regexp/no-empty-alternative": "warn", + "regexp/no-empty-capturing-group": "error", + "regexp/no-empty-character-class": "error", + "regexp/no-empty-group": "error", + "regexp/no-empty-lookarounds-assertion": "error", + "regexp/no-empty-string-literal": "error", + "regexp/no-escape-backspace": "error", + "regexp/no-extra-lookaround-assertions": "error", + "regexp/no-invalid-regexp": "error", + "regexp/no-invisible-character": "error", + "regexp/no-lazy-ends": "warn", + "regexp/no-legacy-features": "error", + "regexp/no-misleading-capturing-group": "error", + "regexp/no-misleading-unicode-character": "error", + "regexp/no-missing-g-flag": "error", + "regexp/no-non-standard-flag": "error", + "regexp/no-obscure-range": "error", + "regexp/no-optional-assertion": "error", + "regexp/no-potentially-useless-backreference": "warn", + "regexp/no-super-linear-backtracking": "error", + "regexp/no-trivially-nested-assertion": "error", + "regexp/no-trivially-nested-quantifier": "error", + "regexp/no-unused-capturing-group": "error", + "regexp/no-useless-assertions": "error", + "regexp/no-useless-backreference": "error", + "regexp/no-useless-character-class": "error", + "regexp/no-useless-dollar-replacements": "error", + "regexp/no-useless-escape": "error", + "regexp/no-useless-flag": "warn", + "regexp/no-useless-lazy": "error", + "regexp/no-useless-non-capturing-group": "error", + "regexp/no-useless-quantifier": "error", + "regexp/no-useless-range": "error", + "regexp/no-useless-set-operand": "error", + "regexp/no-useless-string-literal": "error", + "regexp/no-useless-two-nums-quantifier": "error", + "regexp/no-zero-quantifier": "error", + "regexp/optimal-lookaround-quantifier": "warn", + "regexp/optimal-quantifier-concatenation": "error", + "regexp/prefer-character-class": "error", + "regexp/prefer-d": "error", + "regexp/prefer-plus-quantifier": "error", + "regexp/prefer-predefined-assertion": "error", + "regexp/prefer-question-quantifier": "error", + "regexp/prefer-range": "error", + "regexp/prefer-set-operation": "error", + "regexp/prefer-star-quantifier": "error", + "regexp/prefer-unicode-codepoint-escapes": "error", + "regexp/prefer-w": "error", + "regexp/simplify-set-operations": "error", + "regexp/sort-flags": "error", + "regexp/strict": "error", + "regexp/use-ignore-case": "error", + // #endregion + // --- custom rules --- // #region --- possible problems --- "array-callback-return": ["error", { "allowImplicit": true }], "no-await-in-loop": "error", - "no-control-regex": "off", "no-prototype-builtins": "off", "no-undef": "off", // typescript/eslint-recommended, ts(2552) // #endregion @@ -303,7 +569,7 @@ "arrow-body-style": "error", "block-scoped-var": "error", "curly": "error", - // "dot-notation": "error", -> use @typescript-eslint/dot-notation + // "dot-notation": "error", use @typescript-eslint/dot-notation "eqeqeq": "error", "default-case": ["warn", { "commentPattern": "^no default$" }], @@ -324,46 +590,34 @@ } ], - "eslint-js/no-implicit-globals": "error", // use jsPlugins + "eslint-js/no-implicit-globals": "error", "no-labels": "error", "no-lonely-if": "error", "no-multi-str": "error", "no-new-func": "error", "eslint-js/no-restricted-syntax": [ - "error", // use jsPlugins + "error", { "selector": "CallExpression[callee.property.name='get'][arguments.length=0]", - "message": "Please use .toArray() instead." - }, - { - "selector": "CallExpression[callee.property.name='toArray'] MemberExpression[object.callee.property.name='map']", - "message": "Please use .toArray() before .map()." + "message": "Use .toArray() instead." }, { - "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0][body.value=null]", - "message": "Usage of .catch(() => null) is not allowed. Please handle the error appropriately." - }, - { - "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0][body.type=\"Identifier\"][body.name=\"undefined\"]", - "message": "Usage of .catch(() => undefined) is not allowed. Please handle the error appropriately." + "selector": "CallExpression[callee.property.name='clone'][arguments.length=0]:not([callee.object.name=/^(request|response|req|res)$/])", + "message": "Usage of .clone() in cheerio is not allowed." }, { - "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0] > ArrayExpression[elements.length=0]", - "message": "Usage of .catch(() => []) is not allowed. Please handle the error appropriately." + "selector": "CallExpression[callee.property.name='empty'][arguments.length=0]", + "message": "Usage of .empty() in cheerio is not allowed." }, { - "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0] > BlockStatement[body.length=0]", - "message": "Usage of .catch(() => {}) is not allowed. Please handle the error appropriately." + "selector": "CallExpression[callee.property.name='toArray'] MemberExpression[object.callee.property.name='map']", + "message": "Use .toArray() before .map()." }, { "selector": "CallExpression[callee.name=\"load\"] AwaitExpression > CallExpression", "message": "Do not use await in call expressions. Extract the result into a variable first." }, - { - "selector": "Identifier[name=\"_ctx\"]", - "message": "Usage of `_ctx` is not allowed." - }, { "selector": "CallExpression[callee.property.name=\"each\"] > FunctionExpression", "message": "Use an arrow function instead." @@ -375,6 +629,34 @@ { "selector": "CallExpression[callee.property.name=\"map\"] > FunctionExpression", "message": "Use an arrow function instead." + }, + { + "selector": "TSTypeReference[typeName.name='ReturnType'] TSTypeQuery[exprName.name='load']", + "message": "Usage of ReturnType is not allowed. Use appropriate types from cheerio instead." + }, + { + "selector": "TSTypeReference[typeName.name='ReturnType'] TSTypeReference[typeName.name='CheerioAPI']", + "message": "Usage of ReturnType is not allowed. Use appropriate types from cheerio and domhandler instead." + }, + { + "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0][body.value=null]", + "message": "Usage of .catch(() => null) is not allowed. Handle the error appropriately." + }, + { + "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0][body.type=\"Identifier\"][body.name=\"undefined\"]", + "message": "Usage of .catch(() => undefined) is not allowed. Handle the error appropriately." + }, + { + "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0] > ArrayExpression[elements.length=0]", + "message": "Usage of .catch(() => []) is not allowed. Handle the error appropriately." + }, + { + "selector": "CallExpression[callee.property.name=\"catch\"] > ArrowFunctionExpression[params.length=0] > BlockStatement[body.length=0]", + "message": "Usage of .catch(() => {}) is not allowed. Handle the error appropriately." + }, + { + "selector": "Identifier[name=\"_ctx\"]", + "message": "Usage of `_ctx` is not allowed." } ], @@ -383,7 +665,7 @@ "no-useless-concat": "warn", "no-useless-rename": "error", "no-var": "error", - "eslint-js/object-shorthand": "error", // use jsPlugins + "eslint-js/object-shorthand": "error", "prefer-arrow-callback": "error", "prefer-const": "error", "prefer-object-has-own": "error", @@ -399,7 +681,17 @@ // #region --- TypeScript --- "@typescript-eslint/array-type": ["error", { "default": "array-simple" }], - "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-comment": [ + "warn", + { + "ts-check": false, + "ts-expect-error": "allow-with-description", + "ts-ignore": "allow-with-description", + "ts-nocheck": "allow-with-description", + "minimumDescriptionLength": 3 + } + ], + "@typescript-eslint/consistent-indexed-object-style": "off", // stylistic "@typescript-eslint/consistent-type-definitions": "off", // stylistic "@typescript-eslint/dot-notation": "error", // type-aware @@ -408,11 +700,16 @@ "@typescript-eslint/no-inferrable-types": ["error", { "ignoreParameters": true, "ignoreProperties": true }], + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", // type-aware "@typescript-eslint/no-unnecessary-template-expression": "error", // type-aware + "@typescript-eslint/no-unnecessary-type-conversion": "error", // type-aware "@typescript-eslint/no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], "@typescript-eslint/no-unused-vars": ["error", { "args": "after-used", "argsIgnorePattern": "^_" }], + "@typescript-eslint/prefer-string-starts-ends-with": "error", // type-aware + "@typescript-eslint/require-array-sort-compare": "error", // type-aware + // type-aware // "@typescript-eslint/await-thenable": "off", // "@typescript-eslint/no-base-to-string": "off", @@ -437,15 +734,17 @@ "unicorn/no-array-callback-reference": "warn", "unicorn/no-array-reduce": "warn", - "unicorn/no-array-sort": "warn", + "unicorn/no-array-reverse": ["error", { "allowExpressionStatement": false }], + "unicorn/no-array-sort": ["warn", { "allowAfterSpread": true }], + "unicorn/no-await-expression-member": "off", "unicorn/no-empty-file": "warn", // "unicorn/no-for-loop": "off", // won't be implemented - "unicorn/no-hex-escape": "warn", "unicorn/no-nested-ternary": "off", "unicorn/no-null": "off", "unicorn/no-object-as-default-parameter": "warn", "unicorn/no-process-exit": "off", + "unicorn-js/no-unnecessary-parameters": "warn", "unicorn/no-useless-iterator-to-array": "off", "unicorn/no-useless-switch-case": "off", @@ -495,10 +794,32 @@ "unicorn/prefer-top-level-await": "off", // "unicorn/prevent-abbreviations": "off", + "unicorn-js/single-line-block-comment-style": ["error", "single-line"], "unicorn/switch-case-braces": ["error", "avoid"], "unicorn/text-encoding-identifier-case": "off", // #endregion + // #region --- oxc --- + "oxc/approx-constant": "error", + "oxc/bad-array-method-on-arguments": "error", + "oxc/bad-bitwise-operator": "error", + "oxc/bad-char-at-comparison": "error", + "oxc/bad-comparison-sequence": "error", + "oxc/bad-match-all-arg": "error", + "oxc/bad-min-max-func": "error", + "oxc/bad-object-literal-comparison": "error", + "oxc/bad-replace-all-arg": "error", + "oxc/const-comparisons": "error", + "oxc/double-comparisons": "error", + "oxc/erasing-op": "error", + "oxc/misrefactored-assign-op": "error", + "oxc/missing-throw": "error", + "oxc/no-accumulating-spread": "error", + "oxc/number-arg-out-of-range": "error", + "oxc/only-used-in-recursion": "error", + "oxc/uninvoked-array-callback": "error", + // #endregion + // #region --- stylistic --- "@stylistic/arrow-parens": "error", "@stylistic/arrow-spacing": "error", @@ -559,9 +880,6 @@ ], // #endregion - // github - "github/no-then": "warn", - // rsshub "@rsshub/nsfw-flag/add-nsfw-flag": "error" }, diff --git a/.pre-commit-config.yml b/.pre-commit-config.yml new file mode 100644 index 000000000000..0dc0e72fd00a --- /dev/null +++ b/.pre-commit-config.yml @@ -0,0 +1,28 @@ +repos: + - repo: local + hooks: + - id: format-description + name: format-description + entry: pnpm exec tsx scripts/workflow/format-description.ts + language: system + files: \.(ts|tsx|js|cjs|mjs)$ + - id: oxlint + name: oxlint + entry: pnpm exec oxlint --type-aware --fix + language: system + files: \.(ts|tsx|js|cjs|mjs)$ + - id: eslint + name: eslint + entry: pnpm exec eslint --cache --fix --concurrency auto + language: system + files: \.(ts|tsx|js|cjs|mjs|ya?ml)$ + - id: oxfmt + name: oxfmt + entry: pnpm exec oxfmt --no-error-on-unmatched-pattern + language: system + - id: typecheck + name: typecheck + entry: pnpm typecheck + language: system + pass_filenames: false + always_run: true diff --git a/AGENTS.md b/AGENTS.md index 4576664659db..e908c693c047 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,124 +4,116 @@ 1. **Example Format**: The `example` field must start with `/` and be a working RSSHub route path (e.g., `/example/route`), not a full URL or source website URL. -2. **Route Name**: Do NOT repeat the namespace name in the route name. The namespace is already defined in `namespace.ts`. +2. **Route Name**: Do not repeat the namespace name in the route name. The namespace is already defined in `namespace.ts`. -3. **Radar Source Format**: Use relative paths without `https://` prefix in `radar[]. source`. Example: `source: ['www.example.com/path']` instead of `source: ['https://www.example.com/path']`. +3. **Radar Source Format**: Use relative paths without `https://` prefix in `radar[].source`. Example: `source: ['www.example.com/path']` instead of `source: ['https://www.example.com/path']`. -4. **Radar Target**: The `radar[].target` must match the route path. If the source URL does not contain a path parameter, do not include it in the target. +4. **Radar Target**: The `radar[].target` may be empty. If present, it must match the route path and its parameters. -5. **Namespace URL**: In `namespace.ts`, the `url` field should NOT include the `https://` protocol prefix. +5. **Namespace URL**: In `namespace.ts`, the `url` field should not include the `https://` protocol prefix. -6. **Single Category**: Provide only ONE category in the `categories` array, not multiple. +6. **Single Category**: Provide only one category in the `categories` array, not multiple. -7. **Unnecessary Files**: Do NOT create separate `README.md` or `radar.ts` files. Put descriptions in `Route['description']` and radar rules in `Route['radar']`. +7. **Unnecessary Files**: Do not create separate `README.md` or `radar.ts` files. Put descriptions in `Route['description']` and radar rules in `Route['radar']`. -8. **Legacy Router**: Do NOT add routes to `lib/router.js` - this file is deprecated. +8. **Features Accuracy**: Set `requirePuppeteer: true` only if your route actually uses browser automation (`@/utils/playwright`). Do not mismatch feature flags. -9. **Features Accuracy**: Set `requirePuppeteer: true` only if your route actually uses Puppeteer. Do not mismatch feature flags. - -10. **Maintainer GitHub ID**: The `maintainers` field must contain valid GitHub usernames. Verify that the username exists before adding it. +9. **Maintainer GitHub ID**: The `maintainers` field must contain valid GitHub usernames. Verify that the username exists before adding it. ### Code Style -11. **Naming Convention**: Use `camelCase` for variable names in JavaScript/TypeScript. Avoid `snake_case` (e.g., use `videoUrl` instead of `video_url`). - -12. **Type Imports**: Use `import type { ... }` for type-only imports instead of `import { ... }`. - -13. **Import Sorting**: Keep imports sorted. Run autofix if linter reports import order issues. +10. **Naming Convention**: Use `camelCase` for variable names in JavaScript/TypeScript. Avoid `snake_case` (e.g., use `videoUrl` instead of `video_url`). -14. **Unnecessary Template Literals**: Do not use template literals when simple strings suffice (e.g., use `'plain string'` instead of `` `plain string` ``). +11. **Import Sorting**: Keep imports sorted. Run autofix if linter reports import order issues. -15. **Avoid Loading HTML Twice**: Do not call `load()` from cheerio multiple times on the same content. Reuse the initial `$` object. +12. **Avoid Loading HTML Twice**: Do not call `load()` from cheerio multiple times on the same content. Reuse the initial `$` object. -16. **Async/Await in Close**: When closing Puppeteer pages/browsers, use `await page.close()` and `await browser.close()` instead of non-awaited calls. +13. **Async/Await in Close**: When closing a Playwright page or context, use `await page.close()`, `await context.close()`, or `await destroy()` instead of non-awaited calls. -17. **No Explicit Null**: No need to explicitly set a property to `null` if it does not exist - just omit it. +14. **No Explicit Null**: No need to explicitly set a property to `null` if it does not exist - just omit it. -18. **Valid Item Properties**: Only use properties defined in [lib/types. ts](https://github.com/DIYgod/RSSHub/blob/master/lib/types.ts). Custom properties like `avatar`, `bio` will be ignored by RSSHub. +15. **Valid Item Properties**: Only use properties defined in [lib/types.ts](https://github.com/DIYgod/RSSHub/blob/master/lib/types.ts). Custom properties like `avatar`, `bio` will be ignored by RSSHub. -19. **String Methods**: Use `startsWith()` instead of `includes()` when checking if a string begins with a specific prefix. - -20. **Simplify Code**: Combine multiple conditional assignments into single expressions using `||` or `??` operators when appropriate. +16. **String Methods**: Use `startsWith()` instead of `includes()` when checking if a string begins with a specific prefix. ### Data Handling -21. **Use Cache**: Always [cache](https://docs.rsshub.app/joinus/advanced/use-cache) the returned results when fetching article details in a loop using `cache.tryGet()`. +17. **Use Cache**: Always [cache](https://docs.rsshub.app/joinus/advanced/use-cache) the returned results when fetching article details in a loop using `cache.tryGet()`. -22. **Description Content**: The `description` field should contain ONLY the main article content. Do NOT include `title`, `author`, `pubDate`, or tags in `description` - they have their own dedicated fields. +18. **Description Content**: The `description` field should contain only the main article content. Do not include `title`, `author`, `pubDate`, or tags in `description` - they have their own dedicated fields. -23. **Category Field**: Extract tags/categories from articles and place them in the `category` field, not in `description`. +19. **Category Field**: Extract tags/categories from articles and place them in the `category` field, not in `description`. -24. **pubDate Field**: Always include `pubDate` when the source provides date/time information. Use the `parseDate` utility function. +20. **pubDate Field**: Always include `pubDate` when the source provides date/time information. Use the `parseDate` utility function. -25. **No Fake Dates**: Do NOT use `new Date()` as a fallback for `pubDate`. If no date is available, leave it undefined. See [No Date documentation](https://docs.rsshub.app/joinus/advanced/pub-date#no-date). +21. **No Fake Dates**: Do not use `new Date()` as a fallback for `pubDate`. If no date is available, leave it undefined. See [No Date documentation](https://docs.rsshub.app/joinus/advanced/pub-date#no-date). -26. **No Title Trimming**: Do not manually trim or truncate titles. RSSHub core handles title processing automatically. +22. **No Title Trimming**: Do not manually trim or truncate titles. RSSHub core handles title processing automatically. -27. **Unique Links**: Ensure each item's `link` is unique as it will be used as `guid`. Avoid fallback URLs that could cause duplicate `guid` values. +23. **Unique Links**: Ensure each item's `link` is unique as it will be used as `guid`. Avoid fallback URLs that could cause duplicate `guid` values. -28. **Human-Readable Links**: The feed `link` field should point to a human-readable webpage URL, NOT an API endpoint URL. +24. **Human-Readable Links**: The feed `link` field should point to a human-readable webpage URL, not an API endpoint URL. ### API and Data Fetching -29. **Prefer APIs Over Scraping**: When the target website has an API (often found by scrolling pages or checking network requests), use the API endpoint instead of HTML scraping. - -30. **JSON Parsing**: When using `ofetch`, `JSON.parse` is automatically applied. Do not manually decode JSON escape sequences like `\u003C`. +25. **Prefer APIs Over Scraping**: When the target website has an API (often found by scrolling pages or checking network requests), use the API endpoint instead of HTML scraping. -31. **No Page Turning**: RSS feeds should only request the first page of content. Do not implement pagination parameters for users. +26. **JSON Parsing**: When using `ofetch`, `JSON.parse` is automatically applied. Do not manually decode JSON escape sequences like `\u003C`. -32. **Use Common Parameters**: Use RSSHub's built-in common parameters like [`limit`](https://docs.rsshub.app/guide/parameters#limit-entries) instead of implementing custom query parameters for limiting entries. +27. **No Page Turning**: RSS feeds should only request the first page of content. Do not implement pagination parameters for users. -33. **No Custom Query Parameters**: Avoid using querystring parameters for route configuration. Use path parameters (`:param`) instead. +28. **Use Common Parameters**: Use RSSHub's built-in common parameters like [`limit`](https://docs.rsshub.app/guide/parameters#limit-entries) instead of implementing custom query parameters for limiting entries. -34. **No Custom Filtering**: Do not implement custom tag/category filtering in routes. Users can apply filtering using [common parameters](https://docs.rsshub.app/guide/parameters). +29. **No Custom Query Parameters**: Avoid using querystring parameters for route configuration. Use path parameters (`:param`) instead. -35. **Avoid Dynamic Hashes**: If an API requires a hash that changes across builds, extract it dynamically from the webpage rather than hardcoding it. +30. **No Custom Filtering**: Do not implement custom tag/category filtering in routes. Users can apply filtering using [common parameters](https://docs.rsshub.app/guide/parameters). -36. **User-Agent**: Use RSSHub's built-in [User-Agent](https://github.com/DIYgod/RSSHub/blob/master/lib/config.ts#L494) (`config.trueUA`) when making requests that need realistic browser headers. +31. **Avoid Dynamic Hashes**: If an API requires a hash that changes across builds, extract it dynamically from the webpage rather than hardcoding it. ### Media and Enclosures -37. **Valid MIME Types**: The `enclosure_type` must be a valid MIME type as defined in RFC specifications. For example, `video/youtube` is NOT valid - use actual video file URLs with proper types like `video/mp4`. +32. **Valid MIME Types**: The `enclosure_type` must be a valid MIME type as defined in RFC specifications. For example, `video/youtube` is not valid - use actual video file URLs with proper types like `video/mp4`. -38. **Direct Media URLs**: `enclosure_url` must point directly to downloadable media files (e.g., `.mp4`, `.mp3`), not to web pages containing media. +33. **Direct Media URLs**: `enclosure_url` must point directly to downloadable media files (e.g., `.mp4`, `.mp3`), not to web pages containing media. -39. **Video Poster**: Use the HTML5 `