diff --git a/.github/scripts/issue-fields.cjs b/.github/scripts/issue-fields.cjs index b73e42d5..db44bb24 100644 --- a/.github/scripts/issue-fields.cjs +++ b/.github/scripts/issue-fields.cjs @@ -40,7 +40,14 @@ function componentLabels(body) { function releaseIssues(body, repository) { const result = new Set(); - let text = String(body || '').replace(/https:\/\/github\.com\/([^\s/]+\/[^\s/]+)\/(issues|pull)\/(\d+)[^\s)]*/g, (_all, repo, kind, number) => { + // Consume the whole Markdown link so a foreign #number in its label cannot + // accidentally refer to an unrelated issue in this repository. + let text = String(body || '').replace(/\[[^\]]*\]\((https?:\/\/[^\s)]+)\)/g, (_all, url) => { + const match = url.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)(?:[/?#]|$)/i); + if (match && match[1].toLowerCase() === repository.toLowerCase()) result.add(Number(match[2])); + return ''; + }); + text = text.replace(/https:\/\/github\.com\/([^\s/]+\/[^\s/]+)\/(issues|pull)\/(\d+)[^\s)]*/g, (_all, repo, kind, number) => { if (repo.toLowerCase() === repository.toLowerCase() && kind === 'issues') result.add(Number(number)); return ''; }); diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000..22679cbc --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,24 @@ +name: Dependency audit +on: + workflow_call: + inputs: + checkout_ref: + type: string + default: '' +permissions: + contents: read +jobs: + audit: + runs-on: ubuntu-latest + timeout-minutes: 8 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + ref: ${{ inputs.checkout_ref || (github.event.inputs.pull_request && format('refs/pull/{0}/merge', github.event.inputs.pull_request)) || github.sha }} + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "22" + cache: npm + - run: npm ci --no-audit --no-fund + - run: npm run test:audit diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml new file mode 100644 index 00000000..af65c6c4 --- /dev/null +++ b/.github/workflows/maintenance.yml @@ -0,0 +1,85 @@ +name: Weekly maintenance +on: + schedule: + - cron: '23 5 * * 1' + workflow_dispatch: +permissions: + contents: read +concurrency: + group: weekly-maintenance + cancel-in-progress: false +jobs: + audit: + uses: ./.github/workflows/audit.yml + with: + checkout_ref: dev + compatibility: + name: Latest stable HA on dev + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: dev + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.14' + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: '22' + - run: pip install --upgrade pytest-homeassistant-custom-component + - name: Record tested versions + run: pip show homeassistant pytest-homeassistant-custom-component + - run: python scripts/run_ci_tests.py + - run: python scripts/test_installed_release.py + - name: Measure backend performance + if: always() + run: python -m pytest -q custom_components/f1_sensor/tests/test_phase_5_quality.py -m performance --durations=0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: always() + with: + name: weekly-python + path: | + test-results/python-*.xml + coverage.json + retention-days: 14 + reviews: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: dev + persist-credentials: false + - run: python3 scripts/check_source_reviews.py quality/data-sources.json + - run: python3 scripts/check_token_helper_review.py quality/token-helper-review.json + browser: + runs-on: ubuntu-latest + timeout-minutes: 12 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: dev + persist-credentials: false + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: '22' + cache: npm + - run: npm ci --no-audit --no-fund + - run: npx playwright install --with-deps chromium webkit + - name: Chromium performance + env: + F1_MAINTENANCE: 'true' + run: npx playwright test --grep '@performance' + - name: WebKit lifecycle, locale, keyboard and destructive-action smoke + if: always() + env: + F1_BROWSER: webkit + run: npx playwright test --grep 'mounts and unmounts|keyboard actions|timezone wins|clear always' + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: failure() + with: + name: weekly-browser + path: test-results/playwright + retention-days: 14 diff --git a/ci_tests/issues.test.cjs b/ci_tests/issues.test.cjs index 47efd01f..598bddbd 100644 --- a/ci_tests/issues.test.cjs +++ b/ci_tests/issues.test.cjs @@ -45,3 +45,7 @@ test('published beta and stable instructions retain user guidance', () => { release.prerelease = false; assert.match(releaseComment(issue, release), /latest stable release/); }); + +test('foreign Markdown link labels cannot become local issue references',()=>{ + assert.deepEqual(releaseIssues('[#42](https://github.com/another/project/issues/42) and [#43](https://github.com/Nicxe/f1_sensor/pull/43) and [#44](https://github.com/Nicxe/f1_sensor/issues/44)', 'Nicxe/f1_sensor'), [44]); +});