diff --git a/.vscode/settings.json b/.vscode/settings.json index cac0e10e..eec4fcb7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "editor.formatOnSave": true -} \ No newline at end of file + "editor.formatOnSave": true, + "createTests.defaultLocationForTestFiles": "project root", + "createTests.testDirectoryName": "__tests__" +} diff --git a/README.md b/README.md index f5c2a642..15d8e43d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: TimonVS/pr-labeler-action@v3 + with: + configuration-path: .github/pr-labeler.yml # optional, .github/pr-labeler.yml is the default value env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` @@ -43,6 +45,16 @@ Then if a pull request is opened with the branch name `feature/218-add-emoji-sup You can use `*` as a wildcard for matching multiple branch names. See https://www.npmjs.com/package/matcher for more information about wildcard options. +### Default configuration + +When no configuration is provided, the following defaults will be used: + +```yml +feature: ['feature/*', 'feat/*'], +fix: 'fix/*', +chore: 'chore/*' +``` + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/__tests__/action.test.ts b/__tests__/action.test.ts index ff97ec02..904a1de8 100644 --- a/__tests__/action.test.ts +++ b/__tests__/action.test.ts @@ -6,6 +6,12 @@ import action from '../src/action' nock.disableNetConnect() describe('pr-labeler-action', () => { + beforeEach(() => { + // configuration-path parameter is required + // parameters are exposed as environment variables: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswith + process.env['INPUT_CONFIGURATION-PATH'] = '.github/pr-labeler.yml' + }) + it('adds the "fix" label for "fix/510-logging" branch', async () => { nock('https://api.github.com') .get('/repos/Codertocat/Hello-World/contents/.github/pr-labeler.yml?ref=fix%2F510-logging') @@ -30,7 +36,7 @@ describe('pr-labeler-action', () => { .reply(200, configFixture()) .post('/repos/Codertocat/Hello-World/issues/1/labels', body => { expect(body).toMatchObject({ - labels: ['feature'] + labels: ['🎉 feature'] }) return true }) @@ -42,6 +48,24 @@ describe('pr-labeler-action', () => { expect.assertions(1) }) + it('adds the "release" label for "release/2.0" branch', async () => { + nock('https://api.github.com') + .get('/repos/Codertocat/Hello-World/contents/.github/pr-labeler.yml?ref=release%2F2.0') + .reply(200, configFixture()) + .post('/repos/Codertocat/Hello-World/issues/1/labels', body => { + expect(body).toMatchObject({ + labels: ['release'] + }) + return true + }) + .reply(200) + + await action({ + payload: pullRequestOpenedFixture({ ref: 'release/2.0' }) + }) + expect.assertions(1) + }) + it('uses the default config when no config was provided', async () => { nock('https://api.github.com') .get('/repos/Codertocat/Hello-World/contents/.github/pr-labeler.yml?ref=fix%2F510-logging') diff --git a/__tests__/fixtures/config.yml b/__tests__/fixtures/config.yml index 1d506383..04574e6b 100644 --- a/__tests__/fixtures/config.yml +++ b/__tests__/fixtures/config.yml @@ -1,3 +1,4 @@ -feature: ['feature/*', 'feat/*'] +'🎉 feature': ['feature/*', 'feat/*'] fix: fix/* chore: chore/* +release: release/* diff --git a/__tests__/utils/config.test.ts b/__tests__/utils/config.test.ts new file mode 100644 index 00000000..cd6f2151 --- /dev/null +++ b/__tests__/utils/config.test.ts @@ -0,0 +1,31 @@ +import getConfig from '../../src/utils/config' + +describe('getConfig', () => { + it('returns default config when GitHub returns a 404 for given path', async () => { + const defaultConfig = { + foo: 'bar' + } + + const githubMock = { + repos: { + getContents() { + throw new HTTPError(404) + } + } + } + + const config = await getConfig( + githubMock as any, + 'path/to/config', + { owner: 'repo-owner', repo: 'repo-name' }, + 'ref', + defaultConfig + ) + + expect(config).toBe(defaultConfig) + }) +}) + +class HTTPError { + constructor(public status: number) {} +} diff --git a/action.yml b/action.yml index ceb0a177..21f7f089 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,10 @@ name: 'PR Labeler' description: 'Automatically labels your PRs based on branch name patterns like feature/* or fix/*.' author: 'Timon van Spronsen' +inputs: + configuration-path: + description: 'The path for the label configurations' + default: '.github/pr-labeler.yml' branding: icon: 'tag' color: 'white' diff --git a/package-lock.json b/package-lock.json index e5fee65b..475df675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3381,9 +3381,9 @@ } }, "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "lodash.get": { diff --git a/src/action.ts b/src/action.ts index e0a0f7a1..458c68e4 100644 --- a/src/action.ts +++ b/src/action.ts @@ -5,8 +5,7 @@ import matcher from 'matcher' import getConfig from './utils/config' import { RepoInfo } from './utils/config' -const CONFIG_FILENAME = 'pr-labeler.yml' -const defaults = { +const defaultConfig = { feature: ['feature/*', 'feat/*'], fix: 'fix/*', chore: 'chore/*' @@ -20,6 +19,7 @@ async function action(context: Pick = github.context) { owner: context.payload.repository!.owner.login, repo: context.payload.repository!.name } + const configPath = core.getInput('configuration-path', { required: true }) if (!context.payload.pull_request) { throw new Error( @@ -28,22 +28,22 @@ async function action(context: Pick = github.context) { } const ref: string = context.payload.pull_request.head.ref - const config = { - ...defaults, - ...(await getConfig(octokit, CONFIG_FILENAME, repoInfo, ref)) - } + const config = await getConfig(octokit, configPath, repoInfo, ref, defaultConfig) - const labelsToAdd = Object.entries(config).reduce((labels: string[], [label, patterns]) => { - if ( - Array.isArray(patterns) - ? patterns.some(pattern => matcher.isMatch(ref, pattern)) - : matcher.isMatch(ref, patterns) - ) { - labels.push(label) - } + const labelsToAdd = Object.entries(config).reduce( + (labels, [label, patterns]) => { + if ( + Array.isArray(patterns) + ? patterns.some(pattern => matcher.isMatch(ref, pattern)) + : matcher.isMatch(ref, patterns) + ) { + labels.push(label) + } - return labels - }, []) + return labels + }, + [] as string[] + ) if (labelsToAdd.length > 0) { await octokit.issues.addLabels({ diff --git a/src/utils/config.ts b/src/utils/config.ts index 81ae9bc0..78455af7 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,26 +1,34 @@ -import path from 'path' import yaml from 'js-yaml' import { GitHub } from '@actions/github' -const CONFIG_PATH = '.github' export interface RepoInfo { owner: string repo: string } -export default async function getConfig(github: GitHub, fileName: string, { owner, repo }: RepoInfo, ref: string) { +interface Config { + [k: string]: string | string[] +} + +export default async function getConfig( + github: GitHub, + path: string, + { owner, repo }: RepoInfo, + ref: string, + defaultConfig: Config +): Promise { try { const response = await github.repos.getContents({ owner, repo, - path: path.posix.join(CONFIG_PATH, fileName), + path, ref }) return parseConfig(response.data.content) } catch (error) { if (error.status === 404) { - return null + return defaultConfig } throw error