diff --git a/README.md b/README.md index b4a5931ba..dd9e27c86 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The match object allows control over the matching options. You can specify the l The base match object is defined as: ```yml -- changed-files: +- changed-files: - any-glob-to-any-file: ['list', 'of', 'globs'] - any-glob-to-all-files: ['list', 'of', 'globs'] - all-globs-to-any-file: ['list', 'of', 'globs'] @@ -87,7 +87,35 @@ Documentation: - any-glob-to-any-file: 'docs/*' ``` - If path globs are combined with `!` negation, you can write complex matching rules. See the examples below for more information. +If path globs are combined with `!` negation, you can write complex matching rules. See the examples below for more information. + +Furthermore, if a top-level key is omitted, or is `any` then a `changed-files` key without any further options will default to `any-glob-to-any-file`, though if an `all` key is provided it will default to `all-globs-to-all-files`. + +For example the following would be the same: +```yml +Documentation: +- changed-files: 'docs/*' +``` +and +```yml +Documentation: +- any + - changed-files: + - any-glob-to-any-file: 'docs/*' +``` +along with +```yml +Documentation: +- all: + - changed-files: 'docs/*' +``` +and +```yml +Documentation: +- all: + - changed-files: + - all-globs-to-all-files: 'docs/*' +``` #### Basic Examples @@ -125,7 +153,7 @@ Documentation: - changed-files: - any-glob-to-any-file: ['docs/*', 'guides/*'] -# Add 'Documentation' label to any change to .md files within the entire repository +# Add 'Documentation' label to any change to .md files within the entire repository Documentation: - changed-files: - any-glob-to-any-file: '**/*.md' @@ -206,7 +234,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - + # Label PRs 1, 2, and 3 - uses: actions/labeler@v6 with: @@ -218,9 +246,9 @@ jobs: **Note:** in normal usage the `pr-number` input is not required as the action will detect the PR number from the workflow context. -#### Outputs +#### Outputs -Labeler provides the following outputs: +Labeler provides the following outputs: | Name | Description | |--------------|-----------------------------------------------------------| @@ -248,7 +276,7 @@ jobs: run: | echo "Running frontend tests..." # Put your commands for running frontend tests here - + - id: run-backend-tests if: contains(steps.label-the-PR.outputs.all-labels, 'backend') run: | diff --git a/__tests__/changedFiles.test.ts b/__tests__/changedFiles.test.ts index 07e0b7575..2851cb7cf 100644 --- a/__tests__/changedFiles.test.ts +++ b/__tests__/changedFiles.test.ts @@ -100,12 +100,20 @@ describe('toChangedFilesMatchConfig', () => { describe('but the glob pattern config key is not provided', () => { const config = {'changed-files': ['bar']}; - it('throws the error', () => { - expect(() => { - toChangedFilesMatchConfig(config); - }).toThrow( - `The "changed-files" section must have a valid config structure. Please read the action documentation for more information` - ); + it('defaults to anyGlobToAnyFile', () => { + const result = toChangedFilesMatchConfig(config); + expect(result).toEqual({ + changedFiles: [{anyGlobToAnyFile: ['bar']}] + }); + }); + + describe('and the defaultToAll option is passed', () => { + it('defaults to allGlobToAllFiles', () => { + const result = toChangedFilesMatchConfig(config, true); + expect(result).toEqual({ + changedFiles: [{allGlobsToAllFiles: ['bar']}] + }); + }); }); }); diff --git a/__tests__/fixtures/default_any_and_all.yml b/__tests__/fixtures/default_any_and_all.yml new file mode 100644 index 000000000..16a6d01c4 --- /dev/null +++ b/__tests__/fixtures/default_any_and_all.yml @@ -0,0 +1,6 @@ +default_any: + - changed-files: ['glob'] + +default_all: + - all: + - changed-files: ['glob'] diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index b780c82ff..ae07fab17 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -24,37 +24,55 @@ const loadYaml = (filepath: string) => { }; describe('getLabelConfigMapFromObject', () => { - const yamlObject = loadYaml('__tests__/fixtures/all_options.yml'); - const expected = new Map(); - expected.set('label1', [ - { - any: [ - {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, - {baseBranch: undefined, headBranch: ['regexp']}, - {baseBranch: ['regexp'], headBranch: undefined} - ] - }, - { - all: [ - {changedFiles: [{allGlobsToAllFiles: ['glob']}]}, - {baseBranch: undefined, headBranch: ['regexp']}, - {baseBranch: ['regexp'], headBranch: undefined} - ] - } - ]); - expected.set('label2', [ - { - any: [ - {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, - {baseBranch: undefined, headBranch: ['regexp']}, - {baseBranch: ['regexp'], headBranch: undefined} - ] - } - ]); - - it('returns a MatchConfig', () => { - const result = getLabelConfigMapFromObject(yamlObject); - expect(result).toEqual(expected); + describe('when all options are present', () => { + const yamlObject = loadYaml('__tests__/fixtures/all_options.yml'); + const expected = new Map(); + expected.set('label1', [ + { + any: [ + {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, + {baseBranch: undefined, headBranch: ['regexp']}, + {baseBranch: ['regexp'], headBranch: undefined} + ] + }, + { + all: [ + {changedFiles: [{allGlobsToAllFiles: ['glob']}]}, + {baseBranch: undefined, headBranch: ['regexp']}, + {baseBranch: ['regexp'], headBranch: undefined} + ] + } + ]); + expected.set('label2', [ + { + any: [ + {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, + {baseBranch: undefined, headBranch: ['regexp']}, + {baseBranch: ['regexp'], headBranch: undefined} + ] + } + ]); + + it('returns a MatchConfig', () => { + const result = getLabelConfigMapFromObject(yamlObject); + expect(result).toEqual(expected); + }); + }); + + describe('when no any or all key are present', () => { + const yamlObject = loadYaml('__tests__/fixtures/default_any_and_all.yml'); + const expected = new Map(); + expected.set('default_any', [ + {any: [{changedFiles: [{anyGlobToAnyFile: ['glob']}]}]} + ]); + expected.set('default_all', [ + {all: [{changedFiles: [{allGlobsToAllFiles: ['glob']}]}]} + ]); + + it('returns a MatchConfig', () => { + const result = getLabelConfigMapFromObject(yamlObject); + expect(result).toEqual(expected); + }); }); }); diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 0490f7953..ee8d33ef2 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -37,7 +37,10 @@ const yamlFixtures = { 'branches.yml': fs.readFileSync('__tests__/fixtures/branches.yml'), 'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'), 'not_supported.yml': fs.readFileSync('__tests__/fixtures/not_supported.yml'), - 'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml') + 'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml'), + 'default_any_and_all.yml': fs.readFileSync( + '__tests__/fixtures/default_any_and_all.yml' + ) }; const configureInput = ( diff --git a/dist/index.js b/dist/index.js index f720b3c7d..327e2cb01 100644 --- a/dist/index.js +++ b/dist/index.js @@ -320,7 +320,7 @@ function getLabelConfigMapFromObject(configObject) { // our config objects. if (key === 'any' || key === 'all') { if (Array.isArray(value)) { - const newConfigs = value.map(toMatchConfig); + const newConfigs = value.map(config => toMatchConfig(config, key === 'all')); updatedConfig.push({ [key]: newConfigs }); } } @@ -349,8 +349,8 @@ function getLabelConfigMapFromObject(configObject) { } return labelMap; } -function toMatchConfig(config) { - const changedFilesConfig = (0, changedFiles_1.toChangedFilesMatchConfig)(config); +function toMatchConfig(config, defaultToAll = false) { + const changedFilesConfig = (0, changedFiles_1.toChangedFilesMatchConfig)(config, defaultToAll); const branchConfig = (0, branch_1.toBranchMatchConfig)(config); return Object.assign(Object.assign({}, changedFilesConfig), branchConfig); } @@ -663,7 +663,7 @@ function getChangedFiles(client, prNumber) { return changedFiles; }); } -function toChangedFilesMatchConfig(config) { +function toChangedFilesMatchConfig(config, defaultToAll = false) { if (!config['changed-files'] || !config['changed-files'].length) { return {}; } @@ -672,6 +672,16 @@ function toChangedFilesMatchConfig(config) { : [config['changed-files']]; const validChangedFilesConfigs = []; changedFilesConfigs.forEach(changedFilesConfig => { + if (typeof changedFilesConfig === 'string' || + (Array.isArray(changedFilesConfig) && + changedFilesConfig.every(config => typeof config === 'string'))) { + const key = defaultToAll ? 'allGlobsToAllFiles' : 'anyGlobToAnyFile'; + const value = typeof changedFilesConfig === 'string' + ? [changedFilesConfig] + : changedFilesConfig; + validChangedFilesConfigs.push({ [key]: value }); + return; + } if (!(0, utils_1.isObject)(changedFilesConfig)) { throw new Error(`The "changed-files" section must have a valid config structure. Please read the action documentation for more information`); } diff --git a/src/api/get-label-configs.ts b/src/api/get-label-configs.ts index 4db33f28e..219c8a795 100644 --- a/src/api/get-label-configs.ts +++ b/src/api/get-label-configs.ts @@ -83,7 +83,9 @@ export function getLabelConfigMapFromObject( // our config objects. if (key === 'any' || key === 'all') { if (Array.isArray(value)) { - const newConfigs = value.map(toMatchConfig); + const newConfigs = value.map(config => + toMatchConfig(config, key === 'all') + ); updatedConfig.push({[key]: newConfigs}); } } else if (ALLOWED_CONFIG_KEYS.includes(key)) { @@ -115,8 +117,11 @@ export function getLabelConfigMapFromObject( return labelMap; } -export function toMatchConfig(config: any): BaseMatchConfig { - const changedFilesConfig = toChangedFilesMatchConfig(config); +export function toMatchConfig( + config: any, + defaultToAll = false +): BaseMatchConfig { + const changedFilesConfig = toChangedFilesMatchConfig(config, defaultToAll); const branchConfig = toBranchMatchConfig(config); return { diff --git a/src/changedFiles.ts b/src/changedFiles.ts index a5038f96a..8ab645363 100644 --- a/src/changedFiles.ts +++ b/src/changedFiles.ts @@ -45,7 +45,8 @@ export async function getChangedFiles( } export function toChangedFilesMatchConfig( - config: any + config: any, + defaultToAll = false ): ChangedFilesMatchConfig { if (!config['changed-files'] || !config['changed-files'].length) { return {}; @@ -57,6 +58,20 @@ export function toChangedFilesMatchConfig( const validChangedFilesConfigs: ChangedFilesGlobPatternsConfig[] = []; changedFilesConfigs.forEach(changedFilesConfig => { + if ( + typeof changedFilesConfig === 'string' || + (Array.isArray(changedFilesConfig) && + changedFilesConfig.every(config => typeof config === 'string')) + ) { + const key = defaultToAll ? 'allGlobsToAllFiles' : 'anyGlobToAnyFile'; + const value = + typeof changedFilesConfig === 'string' + ? [changedFilesConfig] + : changedFilesConfig; + validChangedFilesConfigs.push({[key]: value}); + return; + } + if (!isObject(changedFilesConfig)) { throw new Error( `The "changed-files" section must have a valid config structure. Please read the action documentation for more information`