feat: model-level provider priority lists with runtime fallback #130186
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: contrib | |
| on: | |
| issue_comment: | |
| types: [created, edited] | |
| # zizmor: ignore[dangerous-triggers] We explicitly want to run on pull_request_target. | |
| pull_request_target: | |
| types: | |
| - opened | |
| - closed | |
| - synchronize | |
| - labeled | |
| - unlabeled | |
| - reopened | |
| - edited | |
| # For jobs that don't run on draft PRs. | |
| - ready_for_review | |
| permissions: | |
| contents: read | |
| # Only run one instance per PR to ensure in-order execution. | |
| concurrency: pr-${{ github.ref }} | |
| jobs: | |
| community-label: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| if: >- | |
| ${{ | |
| github.event_name == 'pull_request_target' && | |
| github.event.action == 'opened' && | |
| github.event.pull_request.author_association != 'MEMBER' && | |
| github.event.pull_request.author_association != 'COLLABORATOR' && | |
| github.event.pull_request.author_association != 'OWNER' | |
| }} | |
| steps: | |
| - name: Add community label | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const params = { | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| } | |
| const labels = context.payload.pull_request.labels.map((label) => label.name) | |
| if (labels.includes("community")) { | |
| console.log('PR already has "community" label.') | |
| return | |
| } | |
| console.log( | |
| 'Adding "community" label for author association "%s".', | |
| context.payload.pull_request.author_association, | |
| ) | |
| await github.rest.issues.addLabels({ | |
| ...params, | |
| labels: ["community"], | |
| }) | |
| cla: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: cla | |
| if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' | |
| uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # the below token should have repo scope and must be manually added by you in the repository's secret | |
| PERSONAL_ACCESS_TOKEN: ${{ secrets.CDRCI2_GITHUB_TOKEN }} | |
| with: | |
| remote-organization-name: "coder" | |
| remote-repository-name: "cla" | |
| path-to-signatures: "v2022-09-04/signatures.json" | |
| path-to-document: "https://github.com/coder/cla/blob/main/README.md" | |
| # branch should not be protected | |
| branch: "main" | |
| # Some users have signed a corporate CLA with Coder so are exempt from signing our community one. | |
| allowlist: "coryb,aaronlehmann,dependabot*,blink-so*,blinkagent*" | |
| title: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'pull_request_target' }} | |
| steps: | |
| - name: Validate PR title | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const { pull_request } = context.payload; | |
| const title = pull_request.title; | |
| const repo = { owner: context.repo.owner, repo: context.repo.repo }; | |
| const allowedTypes = [ | |
| "feat", "fix", "docs", "style", "refactor", | |
| "perf", "test", "build", "ci", "chore", "revert", | |
| ]; | |
| const expectedFormat = `"type(scope): description" or "type: description"`; | |
| const guidelinesLink = `See: https://github.com/coder/coder/blob/main/docs/about/contributing/CONTRIBUTING.md#commit-messages`; | |
| const scopeHint = (type) => | |
| `Use a broader scope or no scope (e.g., "${type}: ...") for cross-cutting changes.\n` + | |
| guidelinesLink; | |
| console.log("Title: %s", title); | |
| // Parse conventional commit format: type(scope)!: description | |
| const match = title.match(/^(\w+)(\(([^)]*)\))?(!)?\s*:\s*.+/); | |
| if (!match) { | |
| core.setFailed( | |
| `PR title does not match conventional commit format.\n` + | |
| `Expected: ${expectedFormat}\n` + | |
| `Allowed types: ${allowedTypes.join(", ")}\n` + | |
| guidelinesLink | |
| ); | |
| return; | |
| } | |
| const type = match[1]; | |
| const scope = match[3]; // undefined if no parentheses | |
| // Validate type. | |
| if (!allowedTypes.includes(type)) { | |
| core.setFailed( | |
| `PR title has invalid type "${type}".\n` + | |
| `Expected: ${expectedFormat}\n` + | |
| `Allowed types: ${allowedTypes.join(", ")}\n` + | |
| guidelinesLink | |
| ); | |
| return; | |
| } | |
| // If no scope, we're done. | |
| if (!scope) { | |
| console.log("No scope provided, title is valid."); | |
| return; | |
| } | |
| console.log("Scope: %s", scope); | |
| // Fetch changed files. | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| ...repo, | |
| pull_number: pull_request.number, | |
| per_page: 100, | |
| }); | |
| const changedPaths = files.map(f => f.filename); | |
| console.log("Changed files: %d", changedPaths.length); | |
| // Derive scope type from the changed files. The diff is the | |
| // source of truth: if files exist under the scope, the path | |
| // exists on the PR branch. No need for Contents API calls. | |
| const isDir = changedPaths.some(f => f.startsWith(scope + "/")); | |
| const isFile = changedPaths.some(f => f === scope); | |
| const isStem = changedPaths.some(f => f.startsWith(scope + ".")); | |
| if (!isDir && !isFile && !isStem) { | |
| core.setFailed( | |
| `PR title scope "${scope}" does not match any files changed in this PR.\n` + | |
| `Scopes must reference a path (directory or file stem) that contains changed files.\n` + | |
| scopeHint(type) | |
| ); | |
| return; | |
| } | |
| // Verify all changed files fall under the scope. | |
| const outsideFiles = changedPaths.filter(f => { | |
| if (isDir && f.startsWith(scope + "/")) return false; | |
| if (f === scope) return false; | |
| if (isStem && f.startsWith(scope + ".")) return false; | |
| return true; | |
| }); | |
| if (outsideFiles.length > 0) { | |
| const listed = outsideFiles.map(f => " - " + f).join("\n"); | |
| core.setFailed( | |
| `PR title scope "${scope}" does not contain all changed files.\n` + | |
| `Files outside scope:\n${listed}\n\n` + | |
| scopeHint(type) | |
| ); | |
| return; | |
| } | |
| console.log("PR title is valid."); | |
| release-labels: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| # Skip tagging for draft PRs. | |
| if: ${{ github.event_name == 'pull_request_target' && !github.event.pull_request.draft }} | |
| steps: | |
| - name: release-labels | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| # This script ensures PR title and labels are in sync: | |
| # | |
| # When release/breaking label is: | |
| # - Added, rename PR title to include ! (e.g. feat!:) | |
| # - Removed, rename PR title to strip ! (e.g. feat:) | |
| # | |
| # When title is: | |
| # - Renamed (+!), add the release/breaking label | |
| # - Renamed (-!), remove the release/breaking label | |
| script: | | |
| const releaseLabels = { | |
| breaking: "release/breaking", | |
| } | |
| const { action, changes, label, pull_request } = context.payload | |
| const { title } = pull_request | |
| const labels = pull_request.labels.map((label) => label.name) | |
| const isBreakingTitle = isBreaking(title) | |
| // Debug information. | |
| console.log("Action: %s", action) | |
| console.log("Title: %s", title) | |
| console.log("Labels: %s", labels.join(", ")) | |
| const params = { | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| } | |
| if (action === "opened" || action === "reopened" || action === "ready_for_review") { | |
| if (isBreakingTitle && !labels.includes(releaseLabels.breaking)) { | |
| console.log('Add "%s" label', releaseLabels.breaking) | |
| await github.rest.issues.addLabels({ | |
| ...params, | |
| labels: [releaseLabels.breaking], | |
| }) | |
| } | |
| } | |
| if (action === "edited" && changes.title) { | |
| if (isBreakingTitle && !labels.includes(releaseLabels.breaking)) { | |
| console.log('Add "%s" label', releaseLabels.breaking) | |
| await github.rest.issues.addLabels({ | |
| ...params, | |
| labels: [releaseLabels.breaking], | |
| }) | |
| } | |
| if (!isBreakingTitle && labels.includes(releaseLabels.breaking)) { | |
| const wasBreakingTitle = isBreaking(changes.title.from) | |
| if (wasBreakingTitle) { | |
| console.log('Remove "%s" label', releaseLabels.breaking) | |
| await github.rest.issues.removeLabel({ | |
| ...params, | |
| name: releaseLabels.breaking, | |
| }) | |
| } else { | |
| console.log('Rename title from "%s" to "%s"', title, toBreaking(title)) | |
| await github.rest.issues.update({ | |
| ...params, | |
| title: toBreaking(title), | |
| }) | |
| } | |
| } | |
| } | |
| if (action === "labeled") { | |
| if (label.name === releaseLabels.breaking && !isBreakingTitle) { | |
| console.log('Rename title from "%s" to "%s"', title, toBreaking(title)) | |
| await github.rest.issues.update({ | |
| ...params, | |
| title: toBreaking(title), | |
| }) | |
| } | |
| } | |
| if (action === "unlabeled") { | |
| if (label.name === releaseLabels.breaking && isBreakingTitle) { | |
| console.log('Rename title from "%s" to "%s"', title, fromBreaking(title)) | |
| await github.rest.issues.update({ | |
| ...params, | |
| title: fromBreaking(title), | |
| }) | |
| } | |
| } | |
| function isBreaking(t) { | |
| return t.split(" ")[0].endsWith("!:") | |
| } | |
| function toBreaking(t) { | |
| const parts = t.split(" ") | |
| return [parts[0].replace(/:$/, "!:"), ...parts.slice(1)].join(" ") | |
| } | |
| function fromBreaking(t) { | |
| const parts = t.split(" ") | |
| return [parts[0].replace(/!:$/, ":"), ...parts.slice(1)].join(" ") | |
| } |