Update system overhaul #1550
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: PR Size | |
| on: | |
| pull_request_target: | |
| types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] | |
| permissions: | |
| contents: read | |
| jobs: | |
| prepare-config: | |
| name: Prepare PR size config | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| labels_json: ${{ steps.config.outputs.labels_json }} | |
| steps: | |
| - id: config | |
| name: Build PR size label config | |
| uses: actions/github-script@v8 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const managedLabels = [ | |
| { | |
| name: "size:XS", | |
| color: "0e8a16", | |
| description: "0-9 effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| { | |
| name: "size:S", | |
| color: "5ebd3e", | |
| description: "10-29 effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| { | |
| name: "size:M", | |
| color: "fbca04", | |
| description: "30-99 effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| { | |
| name: "size:L", | |
| color: "fe7d37", | |
| description: "100-499 effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| { | |
| name: "size:XL", | |
| color: "d93f0b", | |
| description: "500-999 effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| { | |
| name: "size:XXL", | |
| color: "b60205", | |
| description: "1,000+ effective changed lines (test files excluded in mixed PRs).", | |
| }, | |
| ]; | |
| core.setOutput("labels_json", JSON.stringify(managedLabels)); | |
| sync-label-definitions: | |
| name: Sync PR size label definitions | |
| needs: prepare-config | |
| if: github.event_name != 'pull_request_target' | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Ensure PR size labels exist | |
| uses: actions/github-script@v8 | |
| env: | |
| PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} | |
| with: | |
| script: | | |
| const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); | |
| for (const label of managedLabels) { | |
| try { | |
| const { data: existing } = await github.rest.issues.getLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label.name, | |
| }); | |
| if ( | |
| existing.color !== label.color || | |
| (existing.description ?? "") !== label.description | |
| ) { | |
| await github.rest.issues.updateLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label.name, | |
| color: label.color, | |
| description: label.description, | |
| }); | |
| } | |
| } catch (error) { | |
| if (error.status !== 404) { | |
| throw error; | |
| } | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label.name, | |
| color: label.color, | |
| description: label.description, | |
| }); | |
| } catch (createError) { | |
| if (createError.status !== 422) { | |
| throw createError; | |
| } | |
| } | |
| } | |
| } | |
| label: | |
| name: Label PR size | |
| needs: prepare-config | |
| if: github.event_name == 'pull_request_target' | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| issues: read | |
| pull-requests: write | |
| concurrency: | |
| group: pr-size-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| steps: | |
| - name: Checkout base repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Sync PR size label | |
| uses: actions/github-script@v8 | |
| env: | |
| PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} | |
| with: | |
| script: | | |
| const { execFileSync } = require("node:child_process"); | |
| const issueNumber = context.payload.pull_request.number; | |
| const baseSha = context.payload.pull_request.base.sha; | |
| const headSha = context.payload.pull_request.head.sha; | |
| const headTrackingRef = `refs/remotes/pr-size/${issueNumber}`; | |
| const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); | |
| const managedLabelNames = new Set(managedLabels.map((label) => label.name)); | |
| // Keep this aligned with the repo's test entrypoints and test-only support files. | |
| const testExcludePathspecs = [ | |
| ":(glob,exclude)**/__tests__/**", | |
| ":(glob,exclude)**/test/**", | |
| ":(glob,exclude)**/tests/**", | |
| ":(glob,exclude)apps/server/integration/**", | |
| ":(glob,exclude)**/*.test.*", | |
| ":(glob,exclude)**/*.spec.*", | |
| ":(glob,exclude)**/*.browser.*", | |
| ":(glob,exclude)**/*.integration.*", | |
| ]; | |
| const sumNumstat = (text) => | |
| text | |
| .split("\n") | |
| .filter(Boolean) | |
| .reduce((total, line) => { | |
| const [insertionsRaw = "0", deletionsRaw = "0"] = line.split("\t"); | |
| const additions = | |
| insertionsRaw === "-" ? 0 : Number.parseInt(insertionsRaw, 10) || 0; | |
| const deletions = | |
| deletionsRaw === "-" ? 0 : Number.parseInt(deletionsRaw, 10) || 0; | |
| return total + additions + deletions; | |
| }, 0); | |
| const resolveSizeLabel = (totalChangedLines) => { | |
| if (totalChangedLines < 10) { | |
| return "size:XS"; | |
| } | |
| if (totalChangedLines < 30) { | |
| return "size:S"; | |
| } | |
| if (totalChangedLines < 100) { | |
| return "size:M"; | |
| } | |
| if (totalChangedLines < 500) { | |
| return "size:L"; | |
| } | |
| if (totalChangedLines < 1000) { | |
| return "size:XL"; | |
| } | |
| return "size:XXL"; | |
| }; | |
| execFileSync("git", ["fetch", "--no-tags", "origin", baseSha], { | |
| stdio: "inherit", | |
| }); | |
| execFileSync( | |
| "git", | |
| ["fetch", "--no-tags", "origin", `+refs/pull/${issueNumber}/head:${headTrackingRef}`], | |
| { | |
| stdio: "inherit", | |
| }, | |
| ); | |
| const resolvedHeadSha = execFileSync("git", ["rev-parse", headTrackingRef], { | |
| encoding: "utf8", | |
| }).trim(); | |
| if (resolvedHeadSha !== headSha) { | |
| core.warning( | |
| `Fetched head SHA ${resolvedHeadSha} does not match pull request head SHA ${headSha}; using fetched ref for sizing.`, | |
| ); | |
| } | |
| execFileSync("git", ["cat-file", "-e", `${baseSha}^{commit}`], { | |
| stdio: "inherit", | |
| }); | |
| const diffArgs = [ | |
| "diff", | |
| "--numstat", | |
| "--ignore-all-space", | |
| "--ignore-blank-lines", | |
| `${baseSha}...${resolvedHeadSha}`, | |
| ]; | |
| const totalChangedLines = sumNumstat( | |
| execFileSync( | |
| "git", | |
| diffArgs, | |
| { encoding: "utf8" }, | |
| ), | |
| ); | |
| const nonTestChangedLines = sumNumstat( | |
| execFileSync("git", [...diffArgs, "--", ".", ...testExcludePathspecs], { | |
| encoding: "utf8", | |
| }), | |
| ); | |
| const testChangedLines = Math.max(0, totalChangedLines - nonTestChangedLines); | |
| const changedLines = nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines; | |
| const nextLabelName = resolveSizeLabel(changedLines); | |
| const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| per_page: 100, | |
| }); | |
| for (const label of currentLabels) { | |
| if (!managedLabelNames.has(label.name) || label.name === nextLabelName) { | |
| continue; | |
| } | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| name: label.name, | |
| }); | |
| } catch (removeError) { | |
| if (removeError.status !== 404) { | |
| throw removeError; | |
| } | |
| } | |
| } | |
| if (!currentLabels.some((label) => label.name === nextLabelName)) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| labels: [nextLabelName], | |
| }); | |
| } | |
| const classification = | |
| nonTestChangedLines === 0 | |
| ? testChangedLines > 0 | |
| ? "test-only PR" | |
| : "no line changes" | |
| : testChangedLines > 0 | |
| ? "test lines excluded" | |
| : "all non-test changes"; | |
| core.info( | |
| `PR #${issueNumber}: ${nonTestChangedLines} non-test lines, ${testChangedLines} test lines, ${changedLines} effective lines -> ${nextLabelName} (${classification})`, | |
| ); |