Add default autosave restore UX #1020
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto-label PRs | |
| # PR labeling has two parts: | |
| # | |
| # 1. `paths` job — runs actions/labeler against .github/labeler.yml to | |
| # apply [Aspect], [Focus], [Feature], and [Type] Documentation | |
| # labels. These have no count limit and a wide refactor can match | |
| # many of them legitimately. | |
| # | |
| # 2. `package-and-type` job — applies the `[Package][...]` labels and | |
| # a single inferred `[Type]` label. This one needs custom logic: | |
| # | |
| # - [Package]: ranks packages by total lines changed | |
| # (additions + deletions, file count as tiebreaker) and applies | |
| # at most the top 3. Without this cap, a cross-cutting change | |
| # ends up with a wall of package labels and the signal is lost. | |
| # | |
| # - [Type]: parses the PR title's conventional-commit prefix | |
| # (fix:/feat:/perf:) and applies one matching label only when | |
| # the signal is unambiguous. We skip refactor:/chore:/test: | |
| # because they have no clean target label. | |
| # | |
| # History note: an earlier version used GitHub Models (actions/ai-inference) | |
| # to suggest labels from PR title + body + paths. That returned 403 | |
| # because GitHub Models access in Actions is gated by an org-level toggle | |
| # we can't flip. Path + PR-title heuristics cover the structured labels | |
| # without any external service. | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| # Disable permissions for all available scopes by default. | |
| # Any needed permissions should be configured at the job level. | |
| permissions: {} | |
| jobs: | |
| paths: | |
| if: github.repository == 'WordPress/wordpress-playground' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read # Required for actions/labeler to read the configuration file. | |
| pull-requests: write # Required to apply labels to pull requests. | |
| steps: | |
| # Pinned to a commit SHA, not a tag: this job runs with | |
| # pull-requests:write, so a moved tag would be a supply-chain | |
| # foothold. Bump deliberately when upgrading. | |
| - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 | |
| with: | |
| configuration-path: .github/labeler.yml | |
| sync-labels: false | |
| package-and-type: | |
| if: github.repository == 'WordPress/wordpress-playground' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| pull-requests: write # Required to apply labels to pull requests. | |
| steps: | |
| - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| with: | |
| script: | | |
| // Path prefix → [Package][...] label. Order matters: the | |
| // first matching prefix wins, so longer / more specific | |
| // prefixes must come before shorter ones (e.g. `web-service-worker` | |
| // before `web`). | |
| const PACKAGE_RULES = [ | |
| ['packages/php-wasm/cli-util/', '[Package][@php-wasm] CLI-Util'], | |
| ['packages/php-wasm/cli/', '[Package][@php-wasm] CLI'], | |
| ['packages/php-wasm/compile/', '[Package][@php-wasm] Compile'], | |
| ['packages/php-wasm/fs-journal/', '[Package][@php-wasm] FS Journal'], | |
| ['packages/php-wasm/logger/', '[Package][@php-wasm] Logger'], | |
| ['packages/php-wasm/node/', '[Package][@php-wasm] Node'], | |
| ['packages/php-wasm/progress/', '[Package][@php-wasm] Progress'], | |
| ['packages/php-wasm/scopes/', '[Package][@php-wasm] Scopes'], | |
| ['packages/php-wasm/stream-compression/','[Package][@php-wasm] Stream Compression'], | |
| ['packages/php-wasm/universal/', '[Package][@php-wasm] Universal'], | |
| ['packages/php-wasm/util/', '[Package][@php-wasm] Util'], | |
| ['packages/php-wasm/web-service-worker/','[Package][@php-wasm] Web'], | |
| ['packages/php-wasm/web/', '[Package][@php-wasm] Web'], | |
| ['packages/php-wasm/xdebug-bridge/', '[Package][@php-wasm] Xdebug Bridge'], | |
| ['packages/playground/blueprints/', '[Package][@wp-playground] Blueprints'], | |
| ['packages/playground/cli/', '[Package][@wp-playground] CLI'], | |
| ['packages/playground/client/', '[Package][@wp-playground] Client'], | |
| ['packages/playground/common/', '[Package][@wp-playground] Common'], | |
| ['packages/playground/components/', '[Package][@wp-playground] Components'], | |
| ['packages/playground/php-cors-proxy/', '[Package][@wp-playground] CORS Proxy'], | |
| ['packages/playground/remote/', '[Package][@wp-playground] Remote'], | |
| ['packages/playground/storage/', '[Package][@wp-playground] Storage'], | |
| ['packages/playground/sync/', '[Package][@wp-playground] Sync'], | |
| ['packages/playground/website-extras/', '[Package][@wp-playground] Website'], | |
| ['packages/playground/website/', '[Package][@wp-playground] Website'], | |
| ['packages/playground/wordpress-builds/','[Package][@wp-playground] WordPress Builds'], | |
| ['packages/playground/wordpress/', '[Package][@wp-playground] WordPress'], | |
| ]; | |
| // Conventional-commit prefix → [Type] label. Only entries | |
| // with an unambiguous target label are listed; refactor / | |
| // chore / test / build / ci are intentionally absent. | |
| const TYPE_RULES = [ | |
| [/^fix(\(|:)/i, '[Type] Bug'], | |
| [/^feat(ure)?(\(|:)/i, '[Type] Enhancement'], | |
| [/^perf(\(|:)/i, '[Type] Performance'], | |
| [/^docs(\(|:)/i, '[Type] Documentation'], | |
| ]; | |
| const pr = context.payload.pull_request; | |
| const files = await github.paginate( | |
| github.rest.pulls.listFiles, | |
| { owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, per_page: 100 } | |
| ); | |
| // Tally lines + file count per package label. | |
| const stats = new Map(); | |
| for (const f of files) { | |
| const rule = PACKAGE_RULES.find(([prefix]) => f.filename.startsWith(prefix)); | |
| if (!rule) continue; | |
| const label = rule[1]; | |
| const lines = (f.additions || 0) + (f.deletions || 0); | |
| const cur = stats.get(label) || { lines: 0, files: 0 }; | |
| stats.set(label, { lines: cur.lines + lines, files: cur.files + 1 }); | |
| } | |
| // Rank: lines desc, then file count desc, then label name | |
| // for stable output. | |
| const topPackages = [...stats.entries()] | |
| .sort(([a, sa], [b, sb]) => | |
| sb.lines - sa.lines || | |
| sb.files - sa.files || | |
| a.localeCompare(b) | |
| ) | |
| .slice(0, 3) | |
| .map(([label]) => label); | |
| const labels = [...topPackages]; | |
| const titleMatch = TYPE_RULES.find(([re]) => re.test(pr.title)); | |
| if (titleMatch) labels.push(titleMatch[1]); | |
| core.info(`PR title: ${pr.title}`); | |
| core.info(`Package stats: ${JSON.stringify([...stats])}`); | |
| core.info(`Applying labels: ${JSON.stringify(labels)}`); | |
| if (labels.length === 0) return; | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels, | |
| }); |