diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 9a35d62ea4..84bfdd410c 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -25,7 +25,7 @@ jobs: - name: Checkout base revision uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ github.event.pull_request.base.sha }} + ref: ${{ github.sha }} persist-credentials: false - name: Auto-rename GitHub revert title to Conventional Commit @@ -63,6 +63,8 @@ jobs: core.info(`PR title OK: ${title}`) const type = match[3] + if (!/^(?:feat|fix|perf|docs)$/.test(type)) return + const changedFiles = await github.paginate(github.rest.pulls.listFiles, { ...context.repo, pull_number: pullRequest.number, @@ -71,13 +73,17 @@ jobs: const files = [] const { appendChangedPaths, isInternalOnly } = require('./scripts/release/changelog') appendChangedPaths(files, changedFiles) - if (/^(?:feat|fix|perf|docs)$/.test(type) && isInternalOnly(files)) { + if (isInternalOnly(files)) { core.setFailed(`PR title type "${type}" is public, but every changed file is internal. ` + 'Use test, bench, ci, or chore.') } - name: Sync labels with PR title - if: steps.rename.outputs.renamed != 'true' + if: >- + steps.rename.outputs.renamed != 'true' && + (github.event.action == 'opened' || + github.event.action == 'reopened' || + (github.event.action == 'edited' && github.event.changes.title != null)) uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | diff --git a/scripts/pr-title.spec.mjs b/scripts/pr-title.spec.mjs new file mode 100644 index 0000000000..b444eab16a --- /dev/null +++ b/scripts/pr-title.spec.mjs @@ -0,0 +1,81 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import vm from 'node:vm' + +import { describe, it } from 'mocha' +import YAML from 'yaml' + +const workflow = YAML.parse(fs.readFileSync(new URL('../.github/workflows/pr-title.yml', import.meta.url), 'utf8')) +const job = workflow.jobs['conventional-commit'] +const steps = new Map() +for (const step of job.steps) steps.set(step.name, step) + +const checkout = steps.get('Checkout base revision') +const validation = steps.get('Validate PR title and release-note context') +const labelSync = steps.get('Sync labels with PR title') +const validateTitle = vm.runInNewContext( + `(async function validateTitle (context, core, github, process, require) {\n${validation.with.script}\n})` +) +const releaseHelpers = { + appendChangedPaths: Function.prototype, + isInternalOnly: () => false, +} +const loadReleaseHelpers = () => releaseHelpers + +describe('PR title workflow', () => { + it('lists changed files only for public title types', async () => { + const expectedCallsByType = new Map([ + ['feat', 1], + ['fix', 1], + ['perf', 1], + ['docs', 1], + ['style', 0], + ['refactor', 0], + ['test', 0], + ['bench', 0], + ['build', 0], + ['ci', 0], + ['chore', 0], + ['revert', 0], + ]) + const validations = [] + + for (const [type, expectedCalls] of expectedCallsByType) { + let listFilesCalls = 0 + const context = { + repo: { owner: 'DataDog', repo: 'dd-trace-js' }, + payload: { pull_request: { number: 1, title: `${type}: change` } }, + } + const core = { + info: Function.prototype, + setFailed: assert.fail, + } + const github = { + paginate: () => { + listFilesCalls++ + return [] + }, + rest: { pulls: { listFiles: Function.prototype } }, + } + const process = { env: { PR_TITLE_PATTERN: job.env.PR_TITLE_PATTERN } } + + validations.push(validateTitle(context, core, github, process, loadReleaseHelpers)) + + assert.strictEqual(listFilesCalls, expectedCalls, type) + } + + await Promise.all(validations) + }) + + it('syncs labels only for events that can require reconciliation', () => { + assert.strictEqual(labelSync.if.replaceAll(/\s+/g, ' '), + "steps.rename.outputs.renamed != 'true' && " + + "(github.event.action == 'opened' || " + + "github.event.action == 'reopened' || " + + "(github.event.action == 'edited' && github.event.changes.title != null))") + }) + + it('checks out the workflow revision', () => { + assert.strictEqual(checkout.with.ref, '$' + '{{ github.sha }}') + }) +})