mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
ci: cut PR runner waste and harden workflow permissions (#1226)
Pipeline audit follow-up: reduce wasted runner time on PRs and tighten CI security, without reducing what actually gets validated. Runner-time waste: - Concurrency with PR-only cancel-in-progress on CI, E2E, and docker-build, so a new push cancels the previous commit's still-running checks. Non-PR runs use the unique run_id as the group, because GitHub keeps at most one pending run per group even with cancel-in-progress: false — a shared ref group could silently drop a queued master run. - paths-ignore for docs-only changes (Markdown, docs/, .plans/, .codex/, .claude/) on the Electron build matrix and the E2E suites; E2E also skips apps/website/**. The build workflow keeps apps/website/** because its Linux job builds the website to verify AppStream assets. Tag pushes are unaffected: GitHub does not evaluate paths filters for tags. - PRs lint affected projects only; master pushes keep the full run-many. Lint-global inputs (eslint.config.mjs, tools/eslint/**) now mark all 41 lint projects affected, including the run-commands targets database and packaging, so the max-lines baseline cannot be widened without lint. Hardening: - Explicit least-privilege permissions on CI, E2E, and build-and-make; the create-release job keeps its job-level contents: write. The repository default workflow token was switched to read-only. - New actionlint job (image pinned by digest, shellcheck at warning+), with the shared-anchor false positive suppressed in .github/actionlint.yaml. Fixed one real finding: unquoted $GITHUB_OUTPUT. - .github/dependabot.yml: weekly cadence, minor+patch grouped per ecosystem (npm, GitHub Actions, Docker), majors stay individual PRs. Docs updated: CLAUDE.md, docs/architecture/nx-workspace-boundaries.md, and docs/architecture/validation-map.md now describe affected-lint on PRs and the E2E path-filter exceptions.
This commit is contained in:
1 parent
e24da447c1
commit
cfa602d5b1
12 files changed
+197
-28
No files matched your search
@@ -0,0 +1,10 @@
|
||||
paths:
|
||||
# build-cross-platform and build-linux share their steps via a YAML anchor
|
||||
# (steps: *electron-build-steps). actionlint type-checks the anchored steps
|
||||
# against each job's own matrix, so matrix.linux_profile — defined only in
|
||||
# build-linux — is reported as unknown when the same steps are checked
|
||||
# against the build-cross-platform matrix. The steps guard every use with
|
||||
# `matrix.os == 'linux'` or a `|| ''` fallback, so this is a false positive.
|
||||
.github/workflows/build-and-make.yaml:
|
||||
ignore:
|
||||
- 'property "linux_profile" is not defined in object type'
|
||||
@@ -0,0 +1,39 @@
|
||||
# Every Dependabot PR triggers the full pipeline (~15 jobs), so version
|
||||
# updates are batched: weekly cadence, minor+patch bumps grouped into one PR
|
||||
# per ecosystem, majors as individual PRs so CI gates them one by one.
|
||||
# Security updates are separate and are not limited by this schedule.
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: '06:00'
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
npm-minor-patch:
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: '06:00'
|
||||
groups:
|
||||
actions-minor-patch:
|
||||
patterns:
|
||||
- '*'
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /docker
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: '06:00'
|
||||
@@ -1,16 +1,46 @@
|
||||
name: Build and Make Electron App
|
||||
|
||||
# Docs-only changes never affect the packaged app, so they skip the build
|
||||
# matrix. apps/website/** is intentionally NOT ignored: the Linux build job
|
||||
# builds the website to verify AppStream screenshot assets. Tag pushes are
|
||||
# unaffected — GitHub does not evaluate paths filters for tags, so v* release
|
||||
# builds always run.
|
||||
#
|
||||
# Known accepted edge case: if a PR built a test-pr-<n> draft and later
|
||||
# reverts its code changes so the remaining diff is docs-only, new pushes
|
||||
# skip this workflow and the draft keeps assets from the older commit. The
|
||||
# draft's title/body name the exact commit they were built from, and
|
||||
# cleanup-pr-draft.yml deletes the draft when the PR closes, so the stale
|
||||
# window is visible and bounded; refreshing drafts on skipped runs is not
|
||||
# worth a separate workflow.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.plans/**'
|
||||
- '.codex/**'
|
||||
- '.claude/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.plans/**'
|
||||
- '.codex/**'
|
||||
- '.claude/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Build jobs only read the repo; the create-release job raises itself to
|
||||
# contents: write at the job level to manage the rolling draft release.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux-embedded-mpv-runtime:
|
||||
name: Build pinned Linux Embedded MPV runtime
|
||||
@@ -1389,7 +1419,7 @@ jobs:
|
||||
|
||||
- name: Get version from package.json
|
||||
id: package-version
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Test drafts (PR/master) get a self-describing title plus a context
|
||||
# header linking the PR, real head commit, and workflow run. A stable
|
||||
|
||||
@@ -9,7 +9,39 @@ on:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
# Superseded PR pushes cancel their still-running checks. Non-PR runs get a
|
||||
# unique group (run_id) because GitHub keeps at most one pending run per
|
||||
# group even with cancel-in-progress: false — a shared ref group would let a
|
||||
# rapid master push silently replace a queued sibling and leave a merged
|
||||
# commit without a lint/test record.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
name: Workflow lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Image pinned by digest (tag 1.7.12). False positives are
|
||||
# suppressed in .github/actionlint.yaml; shellcheck runs at
|
||||
# warning+ severity so style/info notes in long release scripts
|
||||
# don't fail CI while real quoting/logic bugs still do.
|
||||
- name: Run actionlint
|
||||
uses: docker://rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
|
||||
with:
|
||||
args: -color
|
||||
env:
|
||||
SHELLCHECK_OPTS: --severity=warning
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
@@ -18,6 +50,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# nx affected needs the merge-base with the PR target branch.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -31,7 +66,18 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Lint all projects
|
||||
# PRs lint only affected projects for faster feedback; root config
|
||||
# or lockfile changes make every project affected, so the
|
||||
# module-boundary and max-lines rules cannot be dodged this way.
|
||||
- name: Lint affected projects (PR)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: pnpm nx affected --target=lint --base=origin/${{ github.base_ref }} --head=HEAD --parallel=3 --output-style=static
|
||||
env:
|
||||
CI: true
|
||||
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
||||
|
||||
- name: Lint all projects (master)
|
||||
if: github.event_name != 'pull_request'
|
||||
run: pnpm nx run-many --target=lint --all --parallel=3 --output-style=static
|
||||
env:
|
||||
CI: true
|
||||
|
||||
@@ -40,31 +40,17 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
# Initializes the CodeQL tools for scanning. PR runs analyze the merge
|
||||
# commit checked out above (the modern default); JavaScript is
|
||||
# interpreted, so no build step is needed before analysis.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -26,6 +26,14 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Superseded PR pushes cancel their still-running image build. Master/tag/
|
||||
# manual runs get a unique group (run_id): GitHub keeps at most one pending
|
||||
# run per group even with cancel-in-progress: false, so a shared ref group
|
||||
# could silently drop a queued publish between two rapid master pushes.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker image
|
||||
|
||||
@@ -4,11 +4,37 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.plans/**'
|
||||
- '.codex/**'
|
||||
- '.claude/**'
|
||||
- 'apps/website/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.plans/**'
|
||||
- '.codex/**'
|
||||
- '.claude/**'
|
||||
- 'apps/website/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Superseded PR pushes cancel their still-running E2E matrix (the most
|
||||
# expensive per-PR runner time). Non-PR runs get a unique group (run_id):
|
||||
# GitHub keeps at most one pending run per group even with
|
||||
# cancel-in-progress: false, so a shared ref group could silently drop a
|
||||
# queued master run between two rapid pushes.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
electron-e2e-tests:
|
||||
name: Electron E2E on ${{ matrix.os }}
|
||||
|
||||
@@ -193,7 +193,7 @@ Before finishing behavior changes or bug fixes, follow `Regression Prevention An
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Lint all projects (what CI enforces on every PR)
|
||||
# Lint all projects (CI runs this on master; PRs lint affected projects)
|
||||
pnpm run lint
|
||||
|
||||
# Lint a single project
|
||||
@@ -201,7 +201,8 @@ nx lint web
|
||||
nx lint electron-backend
|
||||
```
|
||||
|
||||
CI runs lint for every project (`.github/workflows/ci.yml`). This enforces the
|
||||
CI lints affected projects on PRs (`nx affected`) and every project on master
|
||||
pushes (`.github/workflows/ci.yml`). This enforces the
|
||||
Nx module-boundary tags, the legacy bare-alias ban, and a `max-lines` ESLint
|
||||
rule (hard maximum 400 lines per TypeScript file). Pre-existing oversized files
|
||||
are baselined in `tools/eslint/max-lines-baseline.mjs`; regenerate the baseline
|
||||
|
||||
@@ -92,6 +92,9 @@ pulling the lazy-loaded workspace shell feature bundle into the initial chunk.
|
||||
## CI Enforcement
|
||||
|
||||
The `Lint` job in `.github/workflows/ci.yml` runs
|
||||
`pnpm nx run-many --target=lint --all` on every PR, so
|
||||
`pnpm nx affected --target=lint` on PRs and
|
||||
`pnpm nx run-many --target=lint --all` on master pushes, so
|
||||
`@nx/enforce-module-boundaries` violations, legacy bare-alias imports, and
|
||||
`max-lines` violations fail CI. Run `pnpm run lint` locally before pushing.
|
||||
`max-lines` violations fail CI. Root config or lockfile changes mark every
|
||||
project affected, so the boundary rules cannot be dodged on PRs. Run
|
||||
`pnpm run lint` locally before pushing.
|
||||
@@ -30,8 +30,9 @@ pnpm run lint # nx run-many --target=lint --all
|
||||
pnpm nx lint <project> # single project
|
||||
```
|
||||
|
||||
The CI workflow (`.github/workflows/ci.yml`) runs lint for every project on
|
||||
each PR. This enforces `@nx/enforce-module-boundaries` (scope/domain/type tag
|
||||
The CI workflow (`.github/workflows/ci.yml`) lints affected projects on PRs
|
||||
(`nx affected`) and every project on master pushes.
|
||||
This enforces `@nx/enforce-module-boundaries` (scope/domain/type tag
|
||||
constraints), the legacy bare-alias ban, and the `max-lines` file-size rule
|
||||
(hard maximum 400 lines per TypeScript file). Files that predate the
|
||||
`max-lines` rule are baselined in `tools/eslint/max-lines-baseline.mjs`; after
|
||||
@@ -48,7 +49,11 @@ project is missing from the policy, a listed project no longer exists, or a
|
||||
Tier A entry has no test target. CI runs Tier A with coverage (uploaded to
|
||||
Codecov) and each Tier B/C project's `validationCommand` (falling back to
|
||||
`nx test`) without coverage; projects with an `e2e` target are skipped there
|
||||
because the E2E workflow already runs them on every PR.
|
||||
because the E2E workflow already runs them on every PR that touches app code.
|
||||
Docs-only changes (Markdown, `docs/`, `.plans/`, `.codex/`, `.claude/`) and
|
||||
`apps/website/**` changes skip the E2E workflow via `paths-ignore` — for those
|
||||
PRs no E2E validation runs in CI, which is intentional: they cannot affect app
|
||||
behavior.
|
||||
|
||||
| Tier | Rule | Validation |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -28,7 +28,19 @@
|
||||
"{workspaceRoot}/.eslintrc.json",
|
||||
"{workspaceRoot}/.eslintignore",
|
||||
"{workspaceRoot}/eslint.config.mjs",
|
||||
"{workspaceRoot}/tools/eslint-rules/**/*"
|
||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||
"{workspaceRoot}/tools/eslint/**/*"
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
"inputs": [
|
||||
"default",
|
||||
"{workspaceRoot}/.eslintrc.json",
|
||||
"{workspaceRoot}/.eslintignore",
|
||||
"{workspaceRoot}/eslint.config.mjs",
|
||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||
"{workspaceRoot}/tools/eslint/**/*"
|
||||
]
|
||||
},
|
||||
"@nx/jest:jest": {
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
"lint": {
|
||||
"inputs": [
|
||||
"default",
|
||||
"{workspaceRoot}/eslint.config.mjs",
|
||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||
"{workspaceRoot}/tools/eslint/**/*",
|
||||
"{workspaceRoot}/.github/workflows/build-and-make.yaml",
|
||||
"{workspaceRoot}/.github/workflows/publish-snap.yaml",
|
||||
"{workspaceRoot}/tools/packaging/prepare-linux-runtime-source-snapshot.cjs",
|
||||
|
||||
Reference in new issue
Block a user