mirror of
https://github.com/dependabot/fetch-metadata.git
synced 2026-03-13 18:17:13 -04:00
Merge pull request #11 from dependabot/brrygrdn/output-single-dependency
Clarify action naming, add usage examples for actions + gh cli
This commit is contained in:
1
.github/workflows/integration.yml
vendored
1
.github/workflows/integration.yml
vendored
@@ -3,6 +3,7 @@ on: [ pull_request ]
|
||||
|
||||
jobs:
|
||||
# test action works running from the graph
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
run-action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
109
README.md
109
README.md
@@ -2,12 +2,15 @@
|
||||
<img src="https://s3.eu-west-2.amazonaws.com/dependabot-images/logo-with-name-horizontal.svg?v5" alt="Dependabot" width="336">
|
||||
</p>
|
||||
|
||||
# Dependabot Pull Request Action
|
||||
# Fetch Metadata Action
|
||||
|
||||
**Name:** `dependabot/fetch-metadata`
|
||||
|
||||
Extract information about the dependencies being updated by a Dependabot-generated PR.
|
||||
|
||||
**Name:** `dependabot/pull-request-action`
|
||||
## Usage instructions
|
||||
|
||||
Create a workflow file that contains a step that uses: dependabot/pull-request-action@v1`, e.g.
|
||||
Create a workflow file that contains a step that uses: dependabot/fetch-metadata@v1`, e.g.
|
||||
|
||||
```yaml
|
||||
-- .github/workflows/dependabot-prs.yml
|
||||
@@ -20,38 +23,96 @@ jobs:
|
||||
steps:
|
||||
- name: Fetch Dependabot metadata
|
||||
id: dependabot-metadata
|
||||
uses: dependabot/pull-request-action
|
||||
uses: dependabot/fetch-metadata
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
```
|
||||
|
||||
Subsequent actions will have access to `steps.dependabot-metadata.outputs.updated-dependencies` which will contain a
|
||||
JSON object with information about the changes, e.g.
|
||||
Subsequent actions will have access to the following outputs:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"dependencyName": "dependabot-core",
|
||||
"dependencyType": "direct:production",
|
||||
"updateType": "version-update:semver-major"
|
||||
}
|
||||
]
|
||||
```
|
||||
- `steps.dependabot-metadata.outputs.dependency-names`
|
||||
- A comma-separated list of the package names updated by the PR.
|
||||
- `steps.dependabot-metadata.outputs.dependency-type`
|
||||
- The type of dependency has determined this PR to be, e.g. `direct:production`. For all possible values, see [the `allow` documentation](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#allow).
|
||||
- `steps.dependabot-metadata.outputs.update-type`
|
||||
- The highest semver change being made by this PR, e.g. `version-update:semver-major`. For all possible values, see [the `ignore` documentation](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#ignore).
|
||||
- `steps.dependabot-metadata.outputs.updated-dependencies-json`
|
||||
- A JSON string containing the full information about each updated Dependency.
|
||||
|
||||
**Note:** This output will only be populated if the target Pull Request was opened by Dependabot and contains **only** Dependabot-created commits.
|
||||
**Note:** These outputs will only be populated if the target Pull Request was opened by Dependabot and contains
|
||||
**only** Dependabot-created commits.
|
||||
|
||||
This metadata can be used along with Action's [expression syntax](https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#functions) and the [GitHub CLI](https://github.com/cli/cli) to create
|
||||
useful automation for your Dependabot PRs.
|
||||
|
||||
### Auto-approving
|
||||
|
||||
NYI
|
||||
Since the `dependabot/fetch-metadata` Action will set a failure code if it cannot find any metadata, you can
|
||||
have a permissive auto-approval on all Dependabot PRs like so:
|
||||
|
||||
```yaml
|
||||
name: Dependabot auto-approve
|
||||
description: Auto-approve Dependabot PRs
|
||||
on: pull_request_target
|
||||
permissions:
|
||||
pull-requests: write
|
||||
jobs:
|
||||
dependabot:
|
||||
# Checking the actor will prevent your Action run failing on non-Dependabot PRs
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata
|
||||
- name: Approve a PR
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
```
|
||||
|
||||
### Enabling GitHub automerge
|
||||
|
||||
NYI
|
||||
```yaml
|
||||
name: Dependabot auto-merge
|
||||
description: Enable GitHub Automerge for patch updates on `bar`
|
||||
on: pull_request_target
|
||||
permissions:
|
||||
pull-requests: write
|
||||
jobs:
|
||||
dependabot:
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata
|
||||
- name: Enable auto-merge for Dependabot PRs # respects branch protection rules
|
||||
if: ${{contains(steps.metadata.outputs.dependency-names, "bar") && steps.metadata.outputs.update-type == "version-update:semver-patch"}}
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
```
|
||||
|
||||
## Why?
|
||||
### Labelling
|
||||
|
||||
NYI
|
||||
|
||||
## Development and release process
|
||||
|
||||
NYI
|
||||
```yaml
|
||||
name: Dependabot auto-label
|
||||
description: Label all production dependencies with the "production" label
|
||||
on: pull_request_target
|
||||
permissions:
|
||||
pull-requests: write
|
||||
jobs:
|
||||
dependabot:
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata
|
||||
- name: Add a label for all production dependencies
|
||||
if: ${{ steps.metadata.outputs.dependency-type == "direct:production" }}
|
||||
run: gh pr edit "$PR_URL" --add-label "production"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
```
|
||||
|
||||
14
action.yml
14
action.yml
@@ -1,12 +1,18 @@
|
||||
name: 'Dependabot PR Automation'
|
||||
description: 'Parse Dependabot commit metadata to automate PR handling'
|
||||
name: 'Fetch Metadata from Dependabot PRs'
|
||||
description: 'Extract information from about the dependency being updated by a Dependabot-generated PR'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
outputs:
|
||||
updated-dependencies:
|
||||
description: 'A JSON serialised hash of any metadata found in verified Dependabot commits in the PR.'
|
||||
dependency-names:
|
||||
description: 'A comma-separated list of all package names updated.'
|
||||
dependency-type:
|
||||
description: 'The type of dependency has determined this PR to be, e.g. "direct:production".'
|
||||
update-type:
|
||||
description: 'The highest semver change being made by this PR, e.g. "version-update:semver-major"'
|
||||
updated-dependencies-json:
|
||||
description: 'A JSON string containing the full information about each updated Dependency.'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
|
||||
562
dist/index.js
generated
vendored
562
dist/index.js
generated
vendored
@@ -5642,6 +5642,508 @@ function onceStrict (fn) {
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 2522:
|
||||
/***/ (function(module) {
|
||||
|
||||
/* global define */
|
||||
|
||||
(function (root, pluralize) {
|
||||
/* istanbul ignore else */
|
||||
if (true) {
|
||||
// Node.
|
||||
module.exports = pluralize();
|
||||
} else {}
|
||||
})(this, function () {
|
||||
// Rule storage - pluralize and singularize need to be run sequentially,
|
||||
// while other rules can be optimized using an object for instant lookups.
|
||||
var pluralRules = [];
|
||||
var singularRules = [];
|
||||
var uncountables = {};
|
||||
var irregularPlurals = {};
|
||||
var irregularSingles = {};
|
||||
|
||||
/**
|
||||
* Sanitize a pluralization rule to a usable regular expression.
|
||||
*
|
||||
* @param {(RegExp|string)} rule
|
||||
* @return {RegExp}
|
||||
*/
|
||||
function sanitizeRule (rule) {
|
||||
if (typeof rule === 'string') {
|
||||
return new RegExp('^' + rule + '$', 'i');
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a word token to produce a function that can replicate the case on
|
||||
* another word.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {string} token
|
||||
* @return {Function}
|
||||
*/
|
||||
function restoreCase (word, token) {
|
||||
// Tokens are an exact match.
|
||||
if (word === token) return token;
|
||||
|
||||
// Lower cased words. E.g. "hello".
|
||||
if (word === word.toLowerCase()) return token.toLowerCase();
|
||||
|
||||
// Upper cased words. E.g. "WHISKY".
|
||||
if (word === word.toUpperCase()) return token.toUpperCase();
|
||||
|
||||
// Title cased words. E.g. "Title".
|
||||
if (word[0] === word[0].toUpperCase()) {
|
||||
return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
// Lower cased words. E.g. "test".
|
||||
return token.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate a regexp string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {Array} args
|
||||
* @return {string}
|
||||
*/
|
||||
function interpolate (str, args) {
|
||||
return str.replace(/\$(\d{1,2})/g, function (match, index) {
|
||||
return args[index] || '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a word using a rule.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {Array} rule
|
||||
* @return {string}
|
||||
*/
|
||||
function replace (word, rule) {
|
||||
return word.replace(rule[0], function (match, index) {
|
||||
var result = interpolate(rule[1], arguments);
|
||||
|
||||
if (match === '') {
|
||||
return restoreCase(word[index - 1], result);
|
||||
}
|
||||
|
||||
return restoreCase(match, result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a word by passing in the word and sanitization rules.
|
||||
*
|
||||
* @param {string} token
|
||||
* @param {string} word
|
||||
* @param {Array} rules
|
||||
* @return {string}
|
||||
*/
|
||||
function sanitizeWord (token, word, rules) {
|
||||
// Empty string or doesn't need fixing.
|
||||
if (!token.length || uncountables.hasOwnProperty(token)) {
|
||||
return word;
|
||||
}
|
||||
|
||||
var len = rules.length;
|
||||
|
||||
// Iterate over the sanitization rules and use the first one to match.
|
||||
while (len--) {
|
||||
var rule = rules[len];
|
||||
|
||||
if (rule[0].test(word)) return replace(word, rule);
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a word with the updated word.
|
||||
*
|
||||
* @param {Object} replaceMap
|
||||
* @param {Object} keepMap
|
||||
* @param {Array} rules
|
||||
* @return {Function}
|
||||
*/
|
||||
function replaceWord (replaceMap, keepMap, rules) {
|
||||
return function (word) {
|
||||
// Get the correct token and case restoration functions.
|
||||
var token = word.toLowerCase();
|
||||
|
||||
// Check against the keep object map.
|
||||
if (keepMap.hasOwnProperty(token)) {
|
||||
return restoreCase(word, token);
|
||||
}
|
||||
|
||||
// Check against the replacement map for a direct word replacement.
|
||||
if (replaceMap.hasOwnProperty(token)) {
|
||||
return restoreCase(word, replaceMap[token]);
|
||||
}
|
||||
|
||||
// Run all the rules against the word.
|
||||
return sanitizeWord(token, word, rules);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a word is part of the map.
|
||||
*/
|
||||
function checkWord (replaceMap, keepMap, rules, bool) {
|
||||
return function (word) {
|
||||
var token = word.toLowerCase();
|
||||
|
||||
if (keepMap.hasOwnProperty(token)) return true;
|
||||
if (replaceMap.hasOwnProperty(token)) return false;
|
||||
|
||||
return sanitizeWord(token, token, rules) === token;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pluralize or singularize a word based on the passed in count.
|
||||
*
|
||||
* @param {string} word The word to pluralize
|
||||
* @param {number} count How many of the word exist
|
||||
* @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks)
|
||||
* @return {string}
|
||||
*/
|
||||
function pluralize (word, count, inclusive) {
|
||||
var pluralized = count === 1
|
||||
? pluralize.singular(word) : pluralize.plural(word);
|
||||
|
||||
return (inclusive ? count + ' ' : '') + pluralized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pluralize a word.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
pluralize.plural = replaceWord(
|
||||
irregularSingles, irregularPlurals, pluralRules
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if a word is plural.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
pluralize.isPlural = checkWord(
|
||||
irregularSingles, irregularPlurals, pluralRules
|
||||
);
|
||||
|
||||
/**
|
||||
* Singularize a word.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
pluralize.singular = replaceWord(
|
||||
irregularPlurals, irregularSingles, singularRules
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if a word is singular.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
pluralize.isSingular = checkWord(
|
||||
irregularPlurals, irregularSingles, singularRules
|
||||
);
|
||||
|
||||
/**
|
||||
* Add a pluralization rule to the collection.
|
||||
*
|
||||
* @param {(string|RegExp)} rule
|
||||
* @param {string} replacement
|
||||
*/
|
||||
pluralize.addPluralRule = function (rule, replacement) {
|
||||
pluralRules.push([sanitizeRule(rule), replacement]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a singularization rule to the collection.
|
||||
*
|
||||
* @param {(string|RegExp)} rule
|
||||
* @param {string} replacement
|
||||
*/
|
||||
pluralize.addSingularRule = function (rule, replacement) {
|
||||
singularRules.push([sanitizeRule(rule), replacement]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an uncountable word rule.
|
||||
*
|
||||
* @param {(string|RegExp)} word
|
||||
*/
|
||||
pluralize.addUncountableRule = function (word) {
|
||||
if (typeof word === 'string') {
|
||||
uncountables[word.toLowerCase()] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set singular and plural references for the word.
|
||||
pluralize.addPluralRule(word, '$0');
|
||||
pluralize.addSingularRule(word, '$0');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an irregular word definition.
|
||||
*
|
||||
* @param {string} single
|
||||
* @param {string} plural
|
||||
*/
|
||||
pluralize.addIrregularRule = function (single, plural) {
|
||||
plural = plural.toLowerCase();
|
||||
single = single.toLowerCase();
|
||||
|
||||
irregularSingles[single] = plural;
|
||||
irregularPlurals[plural] = single;
|
||||
};
|
||||
|
||||
/**
|
||||
* Irregular rules.
|
||||
*/
|
||||
[
|
||||
// Pronouns.
|
||||
['I', 'we'],
|
||||
['me', 'us'],
|
||||
['he', 'they'],
|
||||
['she', 'they'],
|
||||
['them', 'them'],
|
||||
['myself', 'ourselves'],
|
||||
['yourself', 'yourselves'],
|
||||
['itself', 'themselves'],
|
||||
['herself', 'themselves'],
|
||||
['himself', 'themselves'],
|
||||
['themself', 'themselves'],
|
||||
['is', 'are'],
|
||||
['was', 'were'],
|
||||
['has', 'have'],
|
||||
['this', 'these'],
|
||||
['that', 'those'],
|
||||
// Words ending in with a consonant and `o`.
|
||||
['echo', 'echoes'],
|
||||
['dingo', 'dingoes'],
|
||||
['volcano', 'volcanoes'],
|
||||
['tornado', 'tornadoes'],
|
||||
['torpedo', 'torpedoes'],
|
||||
// Ends with `us`.
|
||||
['genus', 'genera'],
|
||||
['viscus', 'viscera'],
|
||||
// Ends with `ma`.
|
||||
['stigma', 'stigmata'],
|
||||
['stoma', 'stomata'],
|
||||
['dogma', 'dogmata'],
|
||||
['lemma', 'lemmata'],
|
||||
['schema', 'schemata'],
|
||||
['anathema', 'anathemata'],
|
||||
// Other irregular rules.
|
||||
['ox', 'oxen'],
|
||||
['axe', 'axes'],
|
||||
['die', 'dice'],
|
||||
['yes', 'yeses'],
|
||||
['foot', 'feet'],
|
||||
['eave', 'eaves'],
|
||||
['goose', 'geese'],
|
||||
['tooth', 'teeth'],
|
||||
['quiz', 'quizzes'],
|
||||
['human', 'humans'],
|
||||
['proof', 'proofs'],
|
||||
['carve', 'carves'],
|
||||
['valve', 'valves'],
|
||||
['looey', 'looies'],
|
||||
['thief', 'thieves'],
|
||||
['groove', 'grooves'],
|
||||
['pickaxe', 'pickaxes'],
|
||||
['passerby', 'passersby']
|
||||
].forEach(function (rule) {
|
||||
return pluralize.addIrregularRule(rule[0], rule[1]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Pluralization rules.
|
||||
*/
|
||||
[
|
||||
[/s?$/i, 's'],
|
||||
[/[^\u0000-\u007F]$/i, '$0'],
|
||||
[/([^aeiou]ese)$/i, '$1'],
|
||||
[/(ax|test)is$/i, '$1es'],
|
||||
[/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'],
|
||||
[/(e[mn]u)s?$/i, '$1s'],
|
||||
[/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'],
|
||||
[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],
|
||||
[/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],
|
||||
[/(seraph|cherub)(?:im)?$/i, '$1im'],
|
||||
[/(her|at|gr)o$/i, '$1oes'],
|
||||
[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],
|
||||
[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],
|
||||
[/sis$/i, 'ses'],
|
||||
[/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],
|
||||
[/([^aeiouy]|qu)y$/i, '$1ies'],
|
||||
[/([^ch][ieo][ln])ey$/i, '$1ies'],
|
||||
[/(x|ch|ss|sh|zz)$/i, '$1es'],
|
||||
[/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],
|
||||
[/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'],
|
||||
[/(pe)(?:rson|ople)$/i, '$1ople'],
|
||||
[/(child)(?:ren)?$/i, '$1ren'],
|
||||
[/eaux$/i, '$0'],
|
||||
[/m[ae]n$/i, 'men'],
|
||||
['thou', 'you']
|
||||
].forEach(function (rule) {
|
||||
return pluralize.addPluralRule(rule[0], rule[1]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Singularization rules.
|
||||
*/
|
||||
[
|
||||
[/s$/i, ''],
|
||||
[/(ss)$/i, '$1'],
|
||||
[/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'],
|
||||
[/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],
|
||||
[/ies$/i, 'y'],
|
||||
[/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'],
|
||||
[/\b(mon|smil)ies$/i, '$1ey'],
|
||||
[/\b((?:tit)?m|l)ice$/i, '$1ouse'],
|
||||
[/(seraph|cherub)im$/i, '$1'],
|
||||
[/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'],
|
||||
[/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'],
|
||||
[/(movie|twelve|abuse|e[mn]u)s$/i, '$1'],
|
||||
[/(test)(?:is|es)$/i, '$1is'],
|
||||
[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],
|
||||
[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],
|
||||
[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],
|
||||
[/(alumn|alg|vertebr)ae$/i, '$1a'],
|
||||
[/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],
|
||||
[/(matr|append)ices$/i, '$1ix'],
|
||||
[/(pe)(rson|ople)$/i, '$1rson'],
|
||||
[/(child)ren$/i, '$1'],
|
||||
[/(eau)x?$/i, '$1'],
|
||||
[/men$/i, 'man']
|
||||
].forEach(function (rule) {
|
||||
return pluralize.addSingularRule(rule[0], rule[1]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Uncountable rules.
|
||||
*/
|
||||
[
|
||||
// Singular words with no plurals.
|
||||
'adulthood',
|
||||
'advice',
|
||||
'agenda',
|
||||
'aid',
|
||||
'aircraft',
|
||||
'alcohol',
|
||||
'ammo',
|
||||
'analytics',
|
||||
'anime',
|
||||
'athletics',
|
||||
'audio',
|
||||
'bison',
|
||||
'blood',
|
||||
'bream',
|
||||
'buffalo',
|
||||
'butter',
|
||||
'carp',
|
||||
'cash',
|
||||
'chassis',
|
||||
'chess',
|
||||
'clothing',
|
||||
'cod',
|
||||
'commerce',
|
||||
'cooperation',
|
||||
'corps',
|
||||
'debris',
|
||||
'diabetes',
|
||||
'digestion',
|
||||
'elk',
|
||||
'energy',
|
||||
'equipment',
|
||||
'excretion',
|
||||
'expertise',
|
||||
'firmware',
|
||||
'flounder',
|
||||
'fun',
|
||||
'gallows',
|
||||
'garbage',
|
||||
'graffiti',
|
||||
'hardware',
|
||||
'headquarters',
|
||||
'health',
|
||||
'herpes',
|
||||
'highjinks',
|
||||
'homework',
|
||||
'housework',
|
||||
'information',
|
||||
'jeans',
|
||||
'justice',
|
||||
'kudos',
|
||||
'labour',
|
||||
'literature',
|
||||
'machinery',
|
||||
'mackerel',
|
||||
'mail',
|
||||
'media',
|
||||
'mews',
|
||||
'moose',
|
||||
'music',
|
||||
'mud',
|
||||
'manga',
|
||||
'news',
|
||||
'only',
|
||||
'personnel',
|
||||
'pike',
|
||||
'plankton',
|
||||
'pliers',
|
||||
'police',
|
||||
'pollution',
|
||||
'premises',
|
||||
'rain',
|
||||
'research',
|
||||
'rice',
|
||||
'salmon',
|
||||
'scissors',
|
||||
'series',
|
||||
'sewage',
|
||||
'shambles',
|
||||
'shrimp',
|
||||
'software',
|
||||
'species',
|
||||
'staff',
|
||||
'swine',
|
||||
'tennis',
|
||||
'traffic',
|
||||
'transportation',
|
||||
'trout',
|
||||
'tuna',
|
||||
'wealth',
|
||||
'welfare',
|
||||
'whiting',
|
||||
'wildebeest',
|
||||
'wildlife',
|
||||
'you',
|
||||
/pok[eé]mon$/i,
|
||||
// Regexes.
|
||||
/[^aeiou]ese$/i, // "chinese", "japanese"
|
||||
/deer$/i, // "deer", "reindeer"
|
||||
/fish$/i, // "fish", "blowfish", "angelfish"
|
||||
/measles$/i,
|
||||
/o[iu]s$/i, // "carnivorous"
|
||||
/pox$/i, // "chickpox", "smallpox"
|
||||
/sheep$/i
|
||||
].forEach(pluralize.addUncountableRule);
|
||||
|
||||
return pluralize;
|
||||
});
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4294:
|
||||
@@ -12766,6 +13268,18 @@ module.exports = require("zlib");;
|
||||
/******/ }
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/ /* webpack/runtime/compat get default export */
|
||||
/******/ (() => {
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __nccwpck_require__.n = (module) => {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ () => (module['default']) :
|
||||
/******/ () => (module);
|
||||
/******/ __nccwpck_require__.d(getter, { a: getter });
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/define property getters */
|
||||
/******/ (() => {
|
||||
/******/ // define getter functions for harmony exports
|
||||
@@ -12890,6 +13404,46 @@ function parse(commitMessage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/pluralize/pluralize.js
|
||||
var pluralize = __nccwpck_require__(2522);
|
||||
var pluralize_default = /*#__PURE__*/__nccwpck_require__.n(pluralize);
|
||||
;// CONCATENATED MODULE: ./src/dependabot/output.ts
|
||||
|
||||
|
||||
const DEPENDENCY_TYPES_PRIORITY = [
|
||||
'direct:production',
|
||||
'direct:development',
|
||||
'indirect'
|
||||
];
|
||||
const UPDATE_TYPES_PRIORITY = [
|
||||
'version-update:semver-major',
|
||||
'version-update:semver-minor',
|
||||
'version-update:semver-patch'
|
||||
];
|
||||
function set(updatedDependencies) {
|
||||
core.info(`Outputting metadata for ${pluralize_default()('updated dependency', updatedDependencies.length, true)}`);
|
||||
core.setOutput('updated-dependencies-json', updatedDependencies);
|
||||
core.setOutput('dependency-names', updatedDependencies.map(dependency => {
|
||||
return dependency.dependencyName;
|
||||
}).join(', '));
|
||||
core.setOutput('dependency-type', maxDependencyTypes(updatedDependencies));
|
||||
core.setOutput('update-type', maxSemver(updatedDependencies));
|
||||
}
|
||||
function maxDependencyTypes(updatedDependencies) {
|
||||
const dependencyTypes = updatedDependencies.reduce(function (dependencyTypes, dependency) {
|
||||
dependencyTypes.add(dependency.dependencyType);
|
||||
return dependencyTypes;
|
||||
}, new Set());
|
||||
return DEPENDENCY_TYPES_PRIORITY.find(dependencyType => dependencyTypes.has(dependencyType)) || 'unknown';
|
||||
}
|
||||
function maxSemver(updatedDependencies) {
|
||||
const semverLevels = updatedDependencies.reduce(function (semverLevels, dependency) {
|
||||
semverLevels.add(dependency.updateType);
|
||||
return semverLevels;
|
||||
}, new Set());
|
||||
return UPDATE_TYPES_PRIORITY.find(semverLevel => semverLevels.has(semverLevel)) || null;
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/main.ts
|
||||
var main_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
@@ -12904,6 +13458,7 @@ var main_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arg
|
||||
|
||||
|
||||
|
||||
|
||||
function run() {
|
||||
return main_awaiter(this, void 0, void 0, function* () {
|
||||
const token = core.getInput('github-token');
|
||||
@@ -12921,15 +13476,14 @@ function run() {
|
||||
core.info('Parsing Dependabot metadata/');
|
||||
const updatedDependencies = parse(commitMessage);
|
||||
if (updatedDependencies.length > 0) {
|
||||
core.info("Outputting metadata to 'updated-dependencies'.");
|
||||
core.setOutput('updated-dependencies', updatedDependencies);
|
||||
set(updatedDependencies);
|
||||
}
|
||||
else {
|
||||
core.info('PR does not contain metadata, nothing to do.');
|
||||
core.setFailed('PR does not contain metadata, nothing to do.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.info('PR is not from Dependabot, nothing to do.');
|
||||
core.setFailed('PR is not from Dependabot, nothing to do.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
8385
package-lock.json
generated
8385
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.3.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
"@actions/github": "^5.0.0",
|
||||
"pluralize": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.23",
|
||||
|
||||
119
src/dependabot/output.test.ts
Normal file
119
src/dependabot/output.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as Output from './output'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
|
||||
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
|
||||
jest.spyOn(core, 'info').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
test('when given a single dependency it sets its values', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'direct:production',
|
||||
updateType: 'version-update:semver-minor'
|
||||
}
|
||||
]
|
||||
|
||||
Output.set(updatedDependencies)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Outputting metadata for 1 updated dependency')
|
||||
)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith('updated-dependencies-json', updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'coffee-rails')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:production')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-minor')
|
||||
})
|
||||
|
||||
test('when given a multiple dependencies, it uses the highest values for types', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
dependencyName: 'rspec',
|
||||
dependencyType: 'direct:development',
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-major'
|
||||
},
|
||||
{
|
||||
dependencyName: 'rspec-coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-patch'
|
||||
}
|
||||
]
|
||||
|
||||
Output.set(updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith('updated-dependencies-json', updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'rspec, coffee-rails, coffeescript, rspec-coffeescript')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:development')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-major')
|
||||
})
|
||||
|
||||
test('when the dependency has no update type', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'direct:production',
|
||||
updateType: ''
|
||||
}
|
||||
]
|
||||
|
||||
Output.set(updatedDependencies)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Outputting metadata for 1 updated dependency')
|
||||
)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith('updated-dependencies-json', updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'coffee-rails')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:production')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', null)
|
||||
})
|
||||
|
||||
test('when given a multiple dependencies, and some do not have update types', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
dependencyName: 'rspec',
|
||||
dependencyType: 'direct:development',
|
||||
updateType: ''
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: ''
|
||||
},
|
||||
{
|
||||
dependencyName: 'rspec-coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-patch'
|
||||
}
|
||||
]
|
||||
|
||||
Output.set(updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith('updated-dependencies-json', updatedDependencies)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'rspec, coffee-rails, coffeescript, rspec-coffeescript')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:development')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-minor')
|
||||
})
|
||||
44
src/dependabot/output.ts
Normal file
44
src/dependabot/output.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import Pluralize from 'pluralize'
|
||||
import * as core from '@actions/core'
|
||||
import { updatedDependency } from './update_metadata'
|
||||
|
||||
const DEPENDENCY_TYPES_PRIORITY = [
|
||||
'direct:production',
|
||||
'direct:development',
|
||||
'indirect'
|
||||
]
|
||||
const UPDATE_TYPES_PRIORITY = [
|
||||
'version-update:semver-major',
|
||||
'version-update:semver-minor',
|
||||
'version-update:semver-patch'
|
||||
]
|
||||
|
||||
export function set (updatedDependencies: Array<updatedDependency>): void {
|
||||
core.info(`Outputting metadata for ${Pluralize('updated dependency', updatedDependencies.length, true)}`)
|
||||
|
||||
core.setOutput('updated-dependencies-json', updatedDependencies)
|
||||
|
||||
core.setOutput('dependency-names', updatedDependencies.map(dependency => {
|
||||
return dependency.dependencyName
|
||||
}).join(', '))
|
||||
core.setOutput('dependency-type', maxDependencyTypes(updatedDependencies))
|
||||
core.setOutput('update-type', maxSemver(updatedDependencies))
|
||||
}
|
||||
|
||||
function maxDependencyTypes (updatedDependencies: Array<updatedDependency>): string {
|
||||
const dependencyTypes = updatedDependencies.reduce(function (dependencyTypes, dependency) {
|
||||
dependencyTypes.add(dependency.dependencyType)
|
||||
return dependencyTypes
|
||||
}, new Set())
|
||||
|
||||
return DEPENDENCY_TYPES_PRIORITY.find(dependencyType => dependencyTypes.has(dependencyType)) || 'unknown'
|
||||
}
|
||||
|
||||
function maxSemver (updatedDependencies: Array<updatedDependency>): string | null {
|
||||
const semverLevels = updatedDependencies.reduce(function (semverLevels, dependency) {
|
||||
semverLevels.add(dependency.updateType)
|
||||
return semverLevels
|
||||
}, new Set())
|
||||
|
||||
return UPDATE_TYPES_PRIORITY.find(semverLevel => semverLevels.has(semverLevel)) || null
|
||||
}
|
||||
@@ -53,7 +53,7 @@ test('it supports multiple dependencies within a single fragment', async () => {
|
||||
' dependency-type: direct:production\n' +
|
||||
' update-type: version-update:semver-minor\n' +
|
||||
'- dependency-name: coffeescript\n' +
|
||||
' dependency-type: indirect:production\n' +
|
||||
' dependency-type: indirect\n' +
|
||||
' update-type: version-update:semver-patch\n' +
|
||||
'...\n' +
|
||||
'\n' +
|
||||
@@ -68,7 +68,7 @@ test('it supports multiple dependencies within a single fragment', async () => {
|
||||
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
|
||||
|
||||
expect(updatedDependencies[1].dependencyName).toEqual('coffeescript')
|
||||
expect(updatedDependencies[1].dependencyType).toEqual('indirect:production')
|
||||
expect(updatedDependencies[1].dependencyType).toEqual('indirect')
|
||||
expect(updatedDependencies[1].updateType).toEqual('version-update:semver-patch')
|
||||
})
|
||||
|
||||
@@ -89,7 +89,7 @@ test('it only returns information within the first fragment if there are multipl
|
||||
'---\n' +
|
||||
'updated-dependencies:\n' +
|
||||
'- dependency-name: coffeescript\n' +
|
||||
' dependency-type: indirect:production\n' +
|
||||
' dependency-type: indirect\n' +
|
||||
' update-type: version-update:semver-patch\n' +
|
||||
'...\n' +
|
||||
'\n' +
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as YAML from 'yaml'
|
||||
|
||||
interface updatedDependency {
|
||||
export interface updatedDependency {
|
||||
dependencyName: string,
|
||||
dependencyType: string,
|
||||
updateType: string,
|
||||
|
||||
@@ -30,7 +30,7 @@ test('it does nothing if the PR is not verified as from Dependabot', async () =>
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('PR is not from Dependabot, nothing to do.')
|
||||
)
|
||||
})
|
||||
@@ -43,12 +43,12 @@ test('it does nothing if there is no metadata in the commit', async () => {
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('PR does not contain metadata, nothing to do.')
|
||||
)
|
||||
})
|
||||
|
||||
test('it sets the updated dependencies as an output for subsequent actions', async () => {
|
||||
test('it sets the updated dependency as an output for subsequent actions', async () => {
|
||||
const mockCommitMessage =
|
||||
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
|
||||
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
|
||||
@@ -73,10 +73,11 @@ test('it sets the updated dependencies as an output for subsequent actions', asy
|
||||
await run()
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Outputting metadata')
|
||||
expect.stringContaining('Outputting metadata for 1 updated dependency')
|
||||
)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
'updated-dependencies',
|
||||
'updated-dependencies-json',
|
||||
[
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
@@ -85,4 +86,60 @@ test('it sets the updated dependencies as an output for subsequent actions', asy
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'coffee-rails')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:production')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-minor')
|
||||
})
|
||||
|
||||
test('if there are multiple dependencies, it summarizes them', async () => {
|
||||
const mockCommitMessage =
|
||||
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
|
||||
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
|
||||
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
|
||||
'- [Commits](rails/coffee-rails@v4.0.1...v4.2.2)\n' +
|
||||
'\n' +
|
||||
'---\n' +
|
||||
'updated-dependencies:\n' +
|
||||
'- dependency-name: coffee-rails\n' +
|
||||
' dependency-type: direct:production\n' +
|
||||
' update-type: version-update:semver-minor\n' +
|
||||
'- dependency-name: coffeescript\n' +
|
||||
' dependency-type: indirect\n' +
|
||||
' update-type: version-update:semver-major\n' +
|
||||
'...\n' +
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
|
||||
jest.spyOn(core, 'getInput').mockReturnValue('mock-token')
|
||||
jest.spyOn(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
|
||||
() => Promise.resolve(mockCommitMessage)
|
||||
))
|
||||
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
|
||||
|
||||
await run()
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Outputting metadata for 2 updated dependencies')
|
||||
)
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
'updated-dependencies-json',
|
||||
[
|
||||
{
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'direct:production',
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-major'
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'coffee-rails, coffeescript')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:production')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-major')
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as verifiedCommits from './dependabot/verified_commits'
|
||||
import * as updateMetadata from './dependabot/update_metadata'
|
||||
import * as output from './dependabot/output'
|
||||
|
||||
export async function run (): Promise<void> {
|
||||
const token = core.getInput('github-token')
|
||||
@@ -27,13 +28,12 @@ export async function run (): Promise<void> {
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage)
|
||||
|
||||
if (updatedDependencies.length > 0) {
|
||||
core.info("Outputting metadata to 'updated-dependencies'.")
|
||||
core.setOutput('updated-dependencies', updatedDependencies)
|
||||
output.set(updatedDependencies)
|
||||
} else {
|
||||
core.info('PR does not contain metadata, nothing to do.')
|
||||
core.setFailed('PR does not contain metadata, nothing to do.')
|
||||
}
|
||||
} else {
|
||||
core.info('PR is not from Dependabot, nothing to do.')
|
||||
core.setFailed('PR is not from Dependabot, nothing to do.')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user