Filter Function Plugin Library
JavaScript plugins that enable custom filter functions for gitStream. To learn how to use these examples, read our guide on how to use gitStream plugins.
compareSemver
Compares two software version numbers (e.g., "1.2.1" or "1.2b") and determines the type of version change. The first version to be compared, and the second are passed as argument 1 and 2 or as array of 2 items. When V1 > V2 the it means and upgrade.
Returns: string
- It returns a string of either: 'major' if the major version is incremented. 'minor' if the minor version is incremented. 'patch' if the patch version is incremented. 'downgrade' if the second version is lower than the first. 'equal' if both versions are equal. 'error' if the comparison is abnormal or cannot be determined.
License: MIT
Param | Type | Default | Description |
---|---|---|---|
versions | Array.<string> | V1 and V2 in Semver format | |
[lexicographical] | boolean | false | compares each part of the version strings lexicographically instead of naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than "1.2". |
[zeroExtend] | boolean | true | changes the result if one version string has less parts than the other. In this case the shorter string will be padded with "zero" parts instead of being considered smaller. |
Example
Plugin Code: compareSemver
/**
* @module compareSemver
* @description Compares two software version numbers (e.g., "1.2.1" or "1.2b") and determines the type of version change.
* The first version to be compared, and the second are passed as argument 1 and 2 or as array of 2 items.
* When V1 > V2 the it means and upgrade.
* @param {string[]} versions - V1 and V2 in Semver format
* @param {boolean} [lexicographical=false] - compares each part of the version strings lexicographically instead of naturally;
* this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than "1.2".
* @param {boolean} [zeroExtend=true] - changes the result if one version string has less parts than the other. In
* this case the shorter string will be padded with "zero" parts instead of being considered smaller.
* @returns {string} It returns a string of either:
* 'major' if the major version is incremented.
* 'minor' if the minor version is incremented.
* 'patch' if the patch version is incremented.
* 'downgrade' if the second version is lower than the first.
* 'equal' if both versions are equal.
* 'error' if the comparison is abnormal or cannot be determined.
* @example {{ ["1.2.1", "1.2.3"] | compareSemver == "patch" }}
* @license MIT
**/
module.exports = (v1, v2, options = {}) => {
console.log("SEMVER", {v1, v2, options});
// support array as input
if (Array.isArray(v1) && v2 === undefined) {
[v1, v2] = v1; // Destructure the first two elements of the array into v1 and v2
}
const { lexicographical = false, zeroExtend = true } = options;
let v1parts = (v1 || "0").split('.');
let v2parts = (v2 || "0").split('.');
const isValidPart = x => lexicographical ? /^\d+[A-Za-zαß]*$/.test(x) : /^\d+[A-Za-zαß]?$/.test(x);
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
return 'error';
}
if (zeroExtend) {
const maxLength = Math.max(v1parts.length, v2parts.length);
v1parts = [...v1parts, ...Array(maxLength - v1parts.length).fill("0")];
v2parts = [...v2parts, ...Array(maxLength - v2parts.length).fill("0")];
}
const convertPart = x => {
const match = /[A-Za-zαß]/.exec(x);
return Number(match ? x.replace(match[0], "." + x.charCodeAt(match.index)) : x);
};
if (!lexicographical) {
v1parts = v1parts.map(convertPart);
v2parts = v2parts.map(convertPart);
}
for (let i = 0; i < v1parts.length; i++) {
if (v1parts[i] !== v2parts[i]) {
if (v1parts[i] < v2parts[i]) {
return 'downgrade';
}
switch (i) {
case 0: return 'major';
case 1: return 'minor';
case 2: return 'patch';
default: return 'error';
}
}
}
return 'equal';
}
gitStream CM Example: compareSemver
manifest:
version: 1.0
automations:
bump_minor:
if:
- {{ bump == 'minor' }}
run:
- action: approve@v1
- action: add-comment@v1
args:
comment: |
bot `minor` version bumps are approved automatically.
bump_patch:
if:
- {{ bump == 'patch' }}
run:
- action: approve@v1
- action: merge@v1
- action: add-comment@v1
args:
comment: |
bot `patch` version bumps are approved and merged automatically.
bump: {{ ["1.2.1", "1.2.3"] | compareSemver }}
extractDependabotVersionBump
Extract version bump information from Dependabot PRs description
Returns: Array.<string>
- V1 (to) and V2 (from)
License: MIT
Param | Type | Description |
---|---|---|
description | string | the PR description |
Example
Plugin Code: extractDependabotVersionBump
/**
* @module extractDependabotVersionBump
* @description Extract version bump information from Dependabot PRs description
* @param {string} description - the PR description
* @returns {string[]} V1 (to) and V2 (from)
* @example {{ pr.description | extractDependabotVersionBump | compareSemver }}
* @license MIT
**/
module.exports = (desc) => {
if (desc && desc !== '""' && desc !== "''" ) {
const matches = /Bumps.*from ([\d\.]+[A-Za-zαß]*) to ([\d\.]+[A-Za-zαß]*)/.exec(desc);
if (matches && matches.length == 3) {
var [_, from, to] = matches;
// remove trailing dot on to
if (to[to.length - 1] === ".") {
to = to.slice(0, -1);
}
return [to, from];
}
}
return null;
}
gitStream CM Example: extractDependabotVersionBump
manifest:
version: 1.0
automations:
bump_minor:
if:
- {{ bump == 'minor' }}
- {{ branch.name | includes(term="dependabot") }}
- {{ branch.author | includes(term="dependabot") }}
run:
- action: approve@v1
- action: add-comment@v1
args:
comment: |
Dependabot `minor` version bumps are approved automatically.
bump_patch:
if:
- {{ bump == 'patch' }}
- {{ branch.name | includes(term="dependabot") }}
- {{ branch.author | includes(term="dependabot") }}
run:
- action: approve@v1
- action: merge@v1
- action: add-comment@v1
args:
comment: |
Dependabot `patch` version bumps are approved and merged automatically.
bump: {{ pr.description | extractDependabotVersionBump | compareSemver }}
extractSnykVersionBump
Extract version bump information from Snyk PRs description
Returns: Array.<string>
- V1 (to) and V2 (from)
License: MIT
Param | Type | Description |
---|---|---|
description | string | the PR description |
Example
Plugin Code: extractSnykVersionBump
/**
* @module extractSnykVersionBump
* @description Extract version bump information from Snyk PRs description
* @param {string} description - the PR description
* @returns {string[]} V1 (to) and V2 (from)
* @example {{ pr.description | extractSnykVersionBump | compareSemver }}
* @license MIT
**/
module.exports = (desc) => {
if (desc && desc !== '""' && desc !== "''" ) {
const matches = /Upgrade.*from ([\d\.]+[A-Za-zαß]*) to ([\d\.]+[A-Za-zαß]*)/.exec(desc);
if (matches && matches.length == 3) {
var [_, from, to] = matches;
// remove trailing dot on to
if (to[to.length - 1] === ".") {
to = to.slice(0, -1);
}
return [to, from];
}
}
return null;
}
gitStream CM Example: extractSnykVersionBump
manifest:
version: 1.0
automations:
bump_minor:
if:
- {{ bump == 'minor' }}
- {{ branch.name | includes(term="snyk-update"") }}
- {{ branch.author | includes(term="snyk-update"") }}
run:
- action: approve@v1
- action: add-comment@v1
args:
comment: |
Snyk `minor` version bumps are approved automatically.
bump_patch:
if:
- {{ bump == 'patch' }}
- {{ branch.name | includes(term="snyk-update"") }}
- {{ branch.author | includes(term="snyk-update"") }}
run:
- action: approve@v1
- action: merge@v1
- action: add-comment@v1
args:
comment: |
Snyk `patch` version bumps are approved and merged automatically.
bump: {{ pr.description | extractSnykVersionBump | compareSemver }}
extractOrcaFindings
Extract security issues information from Orca PR reviews
Returns: Object
- Findings Findings.infrastructure_as_code: { count: null, priority: '' }, Findings.vulnerabilities: { count: null, priority: '' }, Findings.secrets: { count: null, priority: '' },
License: MIT
Param | Type | Description |
---|---|---|
PR | Object | the gitStream's PR context variable |
Example
Usage example, that adds lables based on Orca Secuirty findings.
Plugin Code: extractOrcaFindings
/**
* @module extractOrcaFindings
* @description Extract security issues information from Orca PR reviews
* @param {Object} pr - the gitStream's PR context variable
* @returns {Object} Findings
* Findings.infrastructure_as_code: { count: null, priority: '' },
* Findings.vulnerabilities: { count: null, priority: '' },
* Findings.secrets: { count: null, priority: '' },
* @example {{ pr | extractOrcaFindings }}
* @license MIT
**/
function getOrcaPropertyRating(lines, lineIdentifierRegex, findingsCellIndex) {
const matches = lines.filter(x => x.match(lineIdentifierRegex));
const [firstMatch] = matches;
const cells = firstMatch.split('|');
const [_, high, medium, low, info] = /"High"> ([\d]+).*"Medium"> ([\d]+).*"Low"> ([\d]+).*"Info"> ([\d]+)/
.exec(cells[findingsCellIndex])
.map(x => parseInt(x));
return {high, medium, low, info};
}
module.exports = (pr) => {
let orcaObject = {
infrastructure_as_code: { count: null, priority: '' },
vulnerabilities: { count: null, priority: '' },
secrets: { count: null, priority: '' },
};
// Orca comments are added as PR review
const orcaComment = pr.reviews.filter(x => x.commenter.includes('orca-security'));
if (orcaComment.length) {
const orcaCommentArray = orcaComment[orcaComment.length - 1].content.split('\n');
var priority = getOrcaPropertyRating(orcaCommentArray, /Infrastructure as Code/, 3);
orcaObject.infrastructure_as_code = {
count: priority.high + priority.medium + priority.low + priority.info,
priority,
};
var priority = getOrcaPropertyRating(orcaCommentArray, /Vulnerabilities/, 3);
orcaObject.vulnerabilities = {
count: priority.high + priority.medium + priority.low + priority.info,
priority,
};
var priority = getOrcaPropertyRating(orcaCommentArray, /Secrets/, 3);
orcaObject.secrets = {
count: priority.high + priority.medium + priority.low + priority.info,
priority,
};
}
return JSON.stringify(orcaObject);
}
gitStream CM Example: extractOrcaFindings
# -*- mode: yaml -*-
manifest:
version: 1.0
automations:
{% for item in reports %}
label_orca_{{ item.name }}:
if:
- {{ item.count > 0 }}
run:
- action: add-label@v1
args:
label: 'orca:{{ item.name }}'
{% endfor %}
orca: {{ pr | extractOrcaFindings }}
reports:
- name: introduced-cves
count: {{ orca.vulnerabilities.count }}
- name: iac-misconfigurations
count: {{ orca.infrastructure_as_code.count }}
- name: exposed-secrets
count: {{ orca.secrets.count }}
colors:
red: 'b60205'
getCodeowners
Resolves the PR's code owners based on the repository's CODEOWNERS file
Returns: Array.<string>
- user names
License: MIT
Param | Type | Description |
---|---|---|
files | Array.<string> | the gitStream's files context variable |
pr | Object | the gitStream's pr context variable |
token | string | access token with repo:read scope, used to read the CODEOWNERS file |
Example
When used, create a secret TOKEN, and add it to the workflow file, in GitHub:
jobs:
gitStream:
...
env:
CODEOWNERS: ${{ secrets.GITSTREAM_CODEOWNERS }}
steps:
- name: Evaluate Rules
uses: linear-b/gitstream-github-action@v1
Plugin Code: getCodeowners
/**
* @module getCodeowners
* @description Resolves the PR's code owners based on the repository's CODEOWNERS file
* @param {string[]} files - the gitStream's files context variable
* @param {Object} pr - the gitStream's pr context variable
* @param {string} token - access token with repo:read scope, used to read the CODEOWNERS file
* @returns {string[]} user names
* @example {{ files | getCodeowners(pr, env.CODEOWNERS_TOKEN) }}
* @license MIT
**/
const { Octokit } = require("@octokit/rest");
const ignore = require('./ignore/index.js');
async function loadCodeownersFile(owner, repo, auth) {
const octokit = new Octokit({
request: { fetch },
auth,
});
const res = await octokit.repos.getContent({
owner,
repo,
path: 'CODEOWNERS'
});
return Buffer.from(res.data.content, 'base64').toString()
}
function codeownersMapping(data) {
return data
.toString()
.split('\n')
.filter(x => x && !x.startsWith('#'))
.map(x => x.split("#")[0])
.map(x => {
const line = x.trim();
const [path, ...owners] = line.split(/\s+/);
return {path, owners};
});
}
function resolveCodeowner(mapping, file) {
const match = mapping
.slice()
.reverse()
.find(x =>
ignore()
.add(x.path)
.ignores(file)
);
if (!match) return false;
return match.owners;
}
module.exports = {
async: true,
filter: async (files, pr, token, callback) => {
const fileData = await loadCodeownersFile(pr.author, pr.repo, token);
const mapping = codeownersMapping(fileData);
const resolved = files
.map(f => resolveCodeowner(mapping, f))
.flat()
.filter(i => typeof i === 'string')
.map(u => u.replace(/^@/, ""));
const unique = [...new Set(resolved)];
return callback(null, unique);
},
}
gitStream CM Example: getCodeowners
# -*- mode: yaml -*-
manifest:
version: 1.0
automations:
senior_review:
if:
- true
run:
- action: explain-code-experts@v1
args:
gt: 10
- action: add-reviewers@v1
args:
reviewers: {{ experts | intersection(list=owners) }}
experts: {{ repo | codeExperts(gt=10) }}
owners: {{ files | codeowners(pr, env.CODEOWNERS) }}
isFlaggedUser
Returns true if the username that is passed to this function is specified in a predefined list of users. This is useful if you want gitStream automations to run only for specified users.
Returns: boolean
- Returns true if the user is specified in the flaggedUsers list, otherwise false.
License: MIT
Param | Type | Description |
---|---|---|
Input | string | The GitHub username to check. |
Example
Plugin Code: isFlaggedUser
// Add users who you want to add to the flag list.
const flaggedUsers = ["user1", "user2"];
/**
* @module isFlaggedUser
* @description Returns true if the username that is passed to this function is specified in a predefined list of users.
* This is useful if you want gitStream automations to run only for specified users.
* @param {string} Input - The GitHub username to check.
* @returns {boolean} Returns true if the user is specified in the flaggedUsers list, otherwise false.
* @example {{ pr.author | isFlaggedUser }}
* @license MIT
*/
function isFlaggedUser(username) {
if (flaggedUsers.includes(username)) {
return true;
} else {
return false;
}
};
function containsString(arr, str) {
return arr.includes(str);
};
module.exports = isFlaggedUser;
gitStream CM Example: isFlaggedUser
# -*- mode: yaml -*-
manifest:
version: 1.0
automations:
detect_flagged_user:
if:
- {{ pr.author | isFlaggedUser }}
run:
- action: add-comment@v1
args:
comment: {{ pr.author }} is a gitStream user.