Compare commits
160 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9d35d648e8 | |
|
|
17d01dc92c | |
|
|
f36074fe49 | |
|
|
f40d7eefd8 | |
|
|
1bf7977a3d | |
|
|
5a16d14432 | |
|
|
71c3689659 | |
|
|
a079ba461d | |
|
|
86301c823d | |
|
|
5bd9038fd1 | |
|
|
ac8cb38838 | |
|
|
664437484d | |
|
|
87d9c10c43 | |
|
|
dbd80d2e9d | |
|
|
a1afa112d3 | |
|
|
3f9dffdaa0 | |
|
|
c4f56e7003 | |
|
|
923acce875 | |
|
|
c5ababce46 | |
|
|
e923a6115f | |
|
|
8bee2b2289 | |
|
|
92dc56ebab | |
|
|
15886d3d8c | |
|
|
f1a52e6b0e | |
|
|
672968b48d | |
|
|
ba54fd76a5 | |
|
|
4cf1d6f51d | |
|
|
c0f9097516 | |
|
|
e4770866d6 | |
|
|
aff4bff53d | |
|
|
1b8ac6a7b8 | |
|
|
438b61463e | |
|
|
917867a4e1 | |
|
|
6f05ce192f | |
|
|
74942d5347 | |
|
|
f26971f09b | |
|
|
8614426b27 | |
|
|
a5ae58c758 | |
|
|
2fca5fd609 | |
|
|
204025ad5b | |
|
|
c52d722673 | |
|
|
c791a95a0b | |
|
|
8e69e12fa1 | |
|
|
3cc0072f45 | |
|
|
772bd6d2ca | |
|
|
27a5f3aa9b | |
|
|
f05041888b | |
|
|
caef8f4d8b | |
|
|
c5cc34d0bd | |
|
|
f5c2b1475c | |
|
|
9b6fc6e3a0 | |
|
|
a21ccc490d | |
|
|
58d61ff5ab | |
|
|
aca73cf197 | |
|
|
c443da5e30 | |
|
|
745081026c | |
|
|
d02237e777 | |
|
|
ee587589b4 | |
|
|
c7025fccaf | |
|
|
77da7030b0 | |
|
|
60e1f8df56 | |
|
|
514c51bfe1 | |
|
|
08141c4e6c | |
|
|
8fe38641cc | |
|
|
ddafd83108 | |
|
|
f1313dce02 | |
|
|
ca34e578f6 | |
|
|
b0dfd30a23 | |
|
|
4d778776af | |
|
|
e0a61d83b2 | |
|
|
7654e2d2c4 | |
|
|
85ae0059c0 | |
|
|
2cfa3f0604 | |
|
|
413032fe57 | |
|
|
4b936d9f59 | |
|
|
87322e4eff | |
|
|
21f8d79d2f | |
|
|
3cc1a97bc2 | |
|
|
c627331682 | |
|
|
8dc6c0deb2 | |
|
|
f8a4419607 | |
|
|
5961cf9c4d | |
|
|
7037405dba | |
|
|
2d739a69ac | |
|
|
5c83261308 | |
|
|
63ff928afe | |
|
|
93b037fc64 | |
|
|
f9c1f5572a | |
|
|
4011b0c046 | |
|
|
27ff796862 | |
|
|
c713842b07 | |
|
|
2abed9daf5 | |
|
|
ac7062e04c | |
|
|
d2337ee650 | |
|
|
1db9b7fb61 | |
|
|
2f68951eb2 | |
|
|
d2826f6b74 | |
|
|
a500a890f9 | |
|
|
a4b921efd9 | |
|
|
243e30c098 | |
|
|
485c833698 | |
|
|
2744b2948f | |
|
|
44796f5a02 | |
|
|
158d2b0407 | |
|
|
169b215c7c | |
|
|
79e0ea271c | |
|
|
953789d88f | |
|
|
c9d97025ca | |
|
|
39fb6943f4 | |
|
|
808cb0be59 | |
|
|
bcf1629a1f | |
|
|
a2cbb4e42e | |
|
|
23d84076bf | |
|
|
6f71c79ac5 | |
|
|
4d3f3c89c4 | |
|
|
3c98f714e3 | |
|
|
c5a62eade8 | |
|
|
5a7ee2645a | |
|
|
dcaf34722b | |
|
|
247b5405c2 | |
|
|
5e9c060b75 | |
|
|
6f346ca75f | |
|
|
e4ab737554 | |
|
|
32d5841de6 | |
|
|
f80379c935 | |
|
|
335dc07cbc | |
|
|
a845af27dd | |
|
|
357d900cbc | |
|
|
b246e02acc | |
|
|
378af747dd | |
|
|
0b57d04197 | |
|
|
1ba75e94d8 | |
|
|
f498350328 | |
|
|
fcbdb4900a | |
|
|
9862d97046 | |
|
|
0c680c5a9f | |
|
|
5b82560611 | |
|
|
29406317d5 | |
|
|
3093d7874d | |
|
|
37d688c7f5 | |
|
|
61723be3c9 | |
|
|
dbe810aa0a | |
|
|
8a2b37b15b | |
|
|
e33b12f4b0 | |
|
|
84a0a2a774 | |
|
|
db3f178993 | |
|
|
eaba27db28 | |
|
|
466c73e94c | |
|
|
76537e6a66 | |
|
|
575d299bec | |
|
|
83c4cf4236 | |
|
|
00d74c7984 | |
|
|
1308fda634 | |
|
|
861ef9e87f | |
|
|
d6742e8d44 | |
|
|
891051c8d8 | |
|
|
5a380ff488 | |
|
|
7c5c50eb39 | |
|
|
5308ace8c4 | |
|
|
085ca3ed37 |
|
|
@ -0,0 +1 @@
|
|||
github: [mathieudutour]
|
||||
|
|
@ -4,13 +4,16 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
- "releases/*"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: package.json
|
||||
- run: npm ci
|
||||
- run: npm run test
|
||||
- run: npm run check
|
||||
- run: npm run build
|
||||
|
|
|
|||
|
|
@ -92,3 +92,6 @@ typings/
|
|||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
/.github/
|
||||
/.vscode/
|
||||
/lib/
|
||||
/docs/
|
||||
/*.json
|
||||
/*.js
|
||||
/*.yml
|
||||
/*.md
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"parser": "typescript"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"spellright.language": [
|
||||
"en"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"markdown",
|
||||
"latex",
|
||||
"plaintext"
|
||||
]
|
||||
}
|
||||
114
README.md
114
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# GitHub Tag Action
|
||||
|
||||
A Github Action to automatically bump and tag master, on merge, with the latest SemVer formatted version. Works on any platform.
|
||||
A GitHub Action to automatically bump and tag master, on merge, with the latest SemVer formatted version. Works on any platform.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -14,27 +14,119 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
- name: Bump version and push tag
|
||||
uses: mathieudutour/github-tag-action@v1
|
||||
id: tag_version
|
||||
uses: mathieudutour/github-tag-action@v6.2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create a GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ steps.tag_version.outputs.new_tag }}
|
||||
name: Release ${{ steps.tag_version.outputs.new_tag }}
|
||||
body: ${{ steps.tag_version.outputs.changelog }}
|
||||
```
|
||||
|
||||
### Inputs
|
||||
### 📥 Inputs
|
||||
|
||||
- **github_token** _(required)_ - Required for permission to tag the repo. Usually `${{ secrets.GITHUB_TOKEN }}`.
|
||||
- **default_bump** _(optional)_ - Which type of bump to use when none explicitly provided (default: `minor`).
|
||||
- **commit_sha** _(optional)_ - The commit SHA value to add the tag. If specified, it uses this value instead GITHUB_SHA. It could be useful when a previous step merged a branch into github.ref.
|
||||
|
||||
#### Fetch all tags
|
||||
|
||||
- **fetch_all_tags** _(optional)_ - By default, this action fetch the last 100 tags from Github. Sometimes, this is not enough and using this action will fetch all tags recursively (default: `false`).
|
||||
|
||||
#### Filter branches
|
||||
|
||||
- **release_branches** _(optional)_ - Comma separated list of branches (JavaScript regular expression accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any repository tag. Examples: `master` or `.*` or `release.*,hotfix.*,master`... (default: `master,main`).
|
||||
- **pre_release_branches** _(optional)_ - Comma separated list of branches (JavaScript regular expression accepted) that will generate the pre-release tags.
|
||||
|
||||
#### Customize the tag
|
||||
|
||||
- **default_bump** _(optional)_ - Which type of bump to use when [none is explicitly provided](#bumping) when commiting to a release branch (default: `patch`). You can also set `false` to avoid generating a new tag when none is explicitly provided. Can be `patch, minor or major`.
|
||||
- **default_prerelease_bump** _(optional)_ - Which type of bump to use when [none is explicitly provided](#bumping) when commiting to a prerelease branch (default: `prerelease`). You can also set `false` to avoid generating a new tag when none is explicitly provided. Can be `prerelease, prepatch, preminor or premajor`.
|
||||
- **custom_tag** _(optional)_ - Custom tag name. If specified, it overrides bump settings.
|
||||
- **create_annotated_tag** _(optional)_ - Boolean to create an annotated rather than a lightweight one (default: `false`).
|
||||
- **tag_prefix** _(optional)_ - A prefix to the tag name (default: `v`).
|
||||
- **release_branches** _(optional)_ - Comma separated list of branches (bash reg exp accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any tag. Examples: `master` or `.*` or `release.*,hotfix.*,master` ...
|
||||
- **append_to_pre_release_tag** _(optional)_ - A suffix to the pre-release tag name (default: `<branch>`).
|
||||
|
||||
### Outputs
|
||||
#### Customize the conventional commit messages & titles of changelog sections
|
||||
|
||||
- **new_tag** - The value of the newly created tag. Note that if there hasn't been any new commit, this will be `undefined`.
|
||||
- **previous_tag** - The value of the previous tag (or `0.0.0` if none).
|
||||
- **custom_release_rules** _(optional)_ - Comma separated list of release rules.
|
||||
|
||||
> **_Note:_** This action creates a [lightweight tag](https://developer.github.com/v3/git/refs/#create-a-reference).
|
||||
__Format__: `<keyword>:<release_type>:<changelog_section>` where `<changelog_section>` is optional and will default to [Angular's conventions](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
|
||||
|
||||
__Examples__:
|
||||
1. `hotfix:patch,pre-feat:preminor`,
|
||||
2. `bug:patch:Bug Fixes,chore:patch:Chores`
|
||||
|
||||
#### Debugging
|
||||
|
||||
- **dry_run** _(optional)_ - Do not perform tagging, just calculate next version and changelog, then exit
|
||||
|
||||
### 📤 Outputs
|
||||
|
||||
- **new_tag** - The value of the newly calculated tag. Note that if there hasn't been any new commit, this will be `undefined`.
|
||||
- **new_version** - The value of the newly created tag without the prefix. Note that if there hasn't been any new commit, this will be `undefined`.
|
||||
- **previous_tag** - The value of the previous tag (or `v0.0.0` if none). Note that if `custom_tag` is set, this will be `undefined`.
|
||||
- **previous_version** - The value of the previous tag (or `0.0.0` if none) without the prefix. Note that if `custom_tag` is set, this will be `undefined`.
|
||||
- **release_type** - The computed release type (`major`, `minor`, `patch` or `custom` - can be prefixed with `pre`).
|
||||
- **changelog** - The [conventional changelog](https://github.com/conventional-changelog/conventional-changelog) since the previous tag.
|
||||
|
||||
> **_Note:_** This action creates a [lightweight tag](https://developer.github.com/v3/git/refs/#create-a-reference) by default.
|
||||
|
||||
### Bumping
|
||||
|
||||
The action will parse the new commits since the last tag using the [semantic-release](https://github.com/semantic-release/semantic-release) conventions.
|
||||
|
||||
semantic-release uses the commit messages to determine the type of changes in the codebase. Following formalized conventions for commit messages, semantic-release automatically determines the next [semantic version](https://semver.org) number.
|
||||
|
||||
By default semantic-release uses [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines).
|
||||
|
||||
Here is an example of the release type that will be done based on a commit messages:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td> Commit message </td> <td> Release type </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```
|
||||
fix(pencil): stop graphite breaking when too much pressure applied
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>Patch Release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```
|
||||
feat(pencil): add 'graphiteWidth' option
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>Minor Release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```
|
||||
perf(pencil): remove graphiteWidth option
|
||||
|
||||
BREAKING CHANGE: The graphiteWidth option has been removed.
|
||||
The default graphite width of 10mm is always used for performance reasons.
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>Major Release</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
If no commit message contains any information, then **default_bump** will be used.
|
||||
|
||||
## Credits
|
||||
|
||||
[anothrNick/github-tag-action](https://github.com/anothrNick/github-tag-action) - a similar action using a Dockerfile
|
||||
[anothrNick/github-tag-action](https://github.com/anothrNick/github-tag-action) - a similar action using a Dockerfile (hence not working on macOS)
|
||||
|
|
|
|||
50
action.yml
50
action.yml
|
|
@ -1,29 +1,69 @@
|
|||
name: "Github Tag"
|
||||
name: "GitHub Tag"
|
||||
description: "Bump and push git tag on merge"
|
||||
author: "Mathieu Dutour"
|
||||
outputs:
|
||||
new_tag:
|
||||
description: "Generated tag"
|
||||
new_version:
|
||||
description: "Generated tag without the prefix"
|
||||
previous_tag:
|
||||
description: "Previous tag (or `0.0.0`)"
|
||||
previous_version:
|
||||
description: "The value of the previous tag (or 0.0.0 if none) without the prefix. Note that if custom_tag is set, this will be undefined."
|
||||
release_type:
|
||||
description: "The computed release type (`major`, `minor`, `patch` or `custom` - can be prefixed with `pre`)"
|
||||
changelog:
|
||||
description: "The conventional changelog since the previous tag"
|
||||
inputs:
|
||||
github_token:
|
||||
description: "Required for permission to tag the repo."
|
||||
required: true
|
||||
default_bump:
|
||||
description: "Which type of bump to use when none explicitly provided (default: `minor`)."
|
||||
description: "Which type of bump to use when none explicitly provided when commiting to a release branch (default: `patch`)."
|
||||
required: false
|
||||
default: "minor"
|
||||
default: "patch"
|
||||
default_prerelease_bump:
|
||||
description: "Which type of bump to use when none explicitly provided when commiting to a prerelease branch (default: `prerelease`)."
|
||||
required: false
|
||||
default: "prerelease"
|
||||
tag_prefix:
|
||||
description: "A prefix to the tag name (default: `v`)."
|
||||
required: false
|
||||
default: "v"
|
||||
append_to_pre_release_tag:
|
||||
description: "A suffix to a pre-release tag name (default: `<branch>`)."
|
||||
required: false
|
||||
custom_tag:
|
||||
description: "Custom tag name. If specified, it overrides bump settings."
|
||||
required: false
|
||||
custom_release_rules:
|
||||
description: "Comma separated list of release rules. Format: `<keyword>:<release_type>`. Example: `hotfix:patch,pre-feat:preminor`."
|
||||
required: false
|
||||
release_branches:
|
||||
description: "Comma separated list of branches (bash reg exp accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any tag. Examples: `master` or `.*` or `release.*,hotfix.*,master`..."
|
||||
required: false
|
||||
default: "master"
|
||||
default: "master,main"
|
||||
pre_release_branches:
|
||||
description: "Comma separated list of branches (bash reg exp accepted) that will generate pre-release tags."
|
||||
required: false
|
||||
commit_sha:
|
||||
description: "The commit SHA value to add the tag. If specified, it uses this value instead GITHUB_SHA. It could be useful when a previous step merged a branch into github.ref."
|
||||
required: false
|
||||
create_annotated_tag:
|
||||
description: "Boolean to create an annotated tag rather than lightweight."
|
||||
required: false
|
||||
default: "false"
|
||||
fetch_all_tags:
|
||||
description: "Boolean to fetch all tags for a repo (if false, only the last 100 will be fetched)."
|
||||
required: false
|
||||
default: "false"
|
||||
dry_run:
|
||||
description: "Do not perform tagging, just calculate next version and changelog, then exit."
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "node12"
|
||||
using: "node20"
|
||||
main: "lib/main.js"
|
||||
branding:
|
||||
icon: "git-merge"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"name": "github-tag-action",
|
||||
"version": "0.1.0",
|
||||
"version": "6.2.0",
|
||||
"private": true,
|
||||
"description": "A Github Action to automatically bump and tag master, on merge, with the latest SemVer formatted version.",
|
||||
"description": "A GitHub Action to automatically bump and tag master, on merge, with the latest SemVer formatted version.",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"test": "jest --testTimeout 10000",
|
||||
"check": "prettier --check ."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -16,17 +18,31 @@
|
|||
"node",
|
||||
"setup"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"author": "Mathieu Dutour",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/github": "^1.1.0",
|
||||
"semver": "^6.3.0"
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.0",
|
||||
"@actions/github": "^4.0.0",
|
||||
"@semantic-release/commit-analyzer": "^8.0.1",
|
||||
"@semantic-release/release-notes-generator": "^9.0.1",
|
||||
"conventional-changelog-conventionalcommits": "^4.6.1",
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.12.14",
|
||||
"@types/semver": "^6.2.0",
|
||||
"typescript": "^3.7.2"
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/js-yaml": "^4.0.4",
|
||||
"@types/node": "^20.11.16",
|
||||
"@types/semver": "^7.3.9",
|
||||
"jest": "^27.3.1",
|
||||
"jest-circus": "^27.3.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "2.4.1",
|
||||
"ts-jest": "^27.0.7",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
import * as core from '@actions/core';
|
||||
import { gte, inc, parse, ReleaseType, SemVer, valid } from 'semver';
|
||||
import { analyzeCommits } from '@semantic-release/commit-analyzer';
|
||||
import { generateNotes } from '@semantic-release/release-notes-generator';
|
||||
import {
|
||||
getBranchFromRef,
|
||||
isPr,
|
||||
getCommits,
|
||||
getLatestPrereleaseTag,
|
||||
getLatestTag,
|
||||
getValidTags,
|
||||
mapCustomReleaseRules,
|
||||
mergeWithDefaultChangelogRules,
|
||||
} from './utils';
|
||||
import { createTag } from './github';
|
||||
import { Await } from './ts';
|
||||
|
||||
export default async function main() {
|
||||
const defaultBump = core.getInput('default_bump') as ReleaseType | 'false';
|
||||
const defaultPreReleaseBump = core.getInput('default_prerelease_bump') as
|
||||
| ReleaseType
|
||||
| 'false';
|
||||
const tagPrefix = core.getInput('tag_prefix');
|
||||
const customTag = core.getInput('custom_tag');
|
||||
const releaseBranches = core.getInput('release_branches');
|
||||
const preReleaseBranches = core.getInput('pre_release_branches');
|
||||
const appendToPreReleaseTag = core.getInput('append_to_pre_release_tag');
|
||||
const createAnnotatedTag = /true/i.test(
|
||||
core.getInput('create_annotated_tag')
|
||||
);
|
||||
const dryRun = core.getInput('dry_run');
|
||||
const customReleaseRules = core.getInput('custom_release_rules');
|
||||
const shouldFetchAllTags = core.getInput('fetch_all_tags');
|
||||
const commitSha = core.getInput('commit_sha');
|
||||
|
||||
let mappedReleaseRules;
|
||||
if (customReleaseRules) {
|
||||
mappedReleaseRules = mapCustomReleaseRules(customReleaseRules);
|
||||
}
|
||||
|
||||
const { GITHUB_REF, GITHUB_SHA } = process.env;
|
||||
|
||||
if (!GITHUB_REF) {
|
||||
core.setFailed('Missing GITHUB_REF.');
|
||||
return;
|
||||
}
|
||||
|
||||
const commitRef = commitSha || GITHUB_SHA;
|
||||
if (!commitRef) {
|
||||
core.setFailed('Missing commit_sha or GITHUB_SHA.');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentBranch = getBranchFromRef(GITHUB_REF);
|
||||
const isReleaseBranch = releaseBranches
|
||||
.split(',')
|
||||
.some((branch) => currentBranch.match(branch));
|
||||
const isPreReleaseBranch = preReleaseBranches
|
||||
.split(',')
|
||||
.some((branch) => currentBranch.match(branch));
|
||||
const isPullRequest = isPr(GITHUB_REF);
|
||||
const isPrerelease = !isReleaseBranch && !isPullRequest && isPreReleaseBranch;
|
||||
|
||||
// Sanitize identifier according to
|
||||
// https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
|
||||
const identifier = (
|
||||
appendToPreReleaseTag ? appendToPreReleaseTag : currentBranch
|
||||
).replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
const prefixRegex = new RegExp(`^${tagPrefix}`);
|
||||
|
||||
const validTags = await getValidTags(
|
||||
prefixRegex,
|
||||
/true/i.test(shouldFetchAllTags)
|
||||
);
|
||||
const latestTag = getLatestTag(validTags, prefixRegex, tagPrefix);
|
||||
const latestPrereleaseTag = getLatestPrereleaseTag(
|
||||
validTags,
|
||||
identifier,
|
||||
prefixRegex
|
||||
);
|
||||
|
||||
let commits: Await<ReturnType<typeof getCommits>>;
|
||||
|
||||
let newVersion: string;
|
||||
|
||||
if (customTag) {
|
||||
commits = await getCommits(latestTag.commit.sha, commitRef);
|
||||
|
||||
core.setOutput('release_type', 'custom');
|
||||
newVersion = customTag;
|
||||
} else {
|
||||
let previousTag: ReturnType<typeof getLatestTag> | null;
|
||||
let previousVersion: SemVer | null;
|
||||
if (!latestPrereleaseTag) {
|
||||
previousTag = latestTag;
|
||||
} else {
|
||||
previousTag = gte(
|
||||
latestTag.name.replace(prefixRegex, ''),
|
||||
latestPrereleaseTag.name.replace(prefixRegex, '')
|
||||
)
|
||||
? latestTag
|
||||
: latestPrereleaseTag;
|
||||
}
|
||||
|
||||
if (!previousTag) {
|
||||
core.setFailed('Could not find previous tag.');
|
||||
return;
|
||||
}
|
||||
|
||||
previousVersion = parse(previousTag.name.replace(prefixRegex, ''));
|
||||
|
||||
if (!previousVersion) {
|
||||
core.setFailed('Could not parse previous tag.');
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Previous tag was ${previousTag.name}, previous version was ${previousVersion.version}.`
|
||||
);
|
||||
core.setOutput('previous_version', previousVersion.version);
|
||||
core.setOutput('previous_tag', previousTag.name);
|
||||
|
||||
commits = await getCommits(previousTag.commit.sha, commitRef);
|
||||
|
||||
let bump = await analyzeCommits(
|
||||
{
|
||||
releaseRules: mappedReleaseRules
|
||||
? // analyzeCommits doesn't appreciate rules with a section /shrug
|
||||
mappedReleaseRules.map(({ section, ...rest }) => ({ ...rest }))
|
||||
: undefined,
|
||||
},
|
||||
{ commits, logger: { log: console.info.bind(console) } }
|
||||
);
|
||||
|
||||
// Determine if we should continue with tag creation based on main vs prerelease branch
|
||||
let shouldContinue = true;
|
||||
if (isPrerelease) {
|
||||
if (!bump && defaultPreReleaseBump === 'false') {
|
||||
shouldContinue = false;
|
||||
}
|
||||
} else {
|
||||
if (!bump && defaultBump === 'false') {
|
||||
shouldContinue = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Default bump is set to false and we did not find an automatic bump
|
||||
if (!shouldContinue) {
|
||||
core.debug(
|
||||
'No commit specifies the version bump. Skipping the tag creation.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have an automatic bump for the prerelease, just set our bump as the default
|
||||
if (isPrerelease && !bump) {
|
||||
bump = defaultPreReleaseBump;
|
||||
}
|
||||
|
||||
// If somebody uses custom release rules on a prerelease branch they might create a 'preprepatch' bump.
|
||||
const preReg = /^pre/;
|
||||
if (isPrerelease && preReg.test(bump)) {
|
||||
bump = bump.replace(preReg, '');
|
||||
}
|
||||
|
||||
const releaseType: ReleaseType = isPrerelease
|
||||
? `pre${bump}`
|
||||
: bump || defaultBump;
|
||||
core.setOutput('release_type', releaseType);
|
||||
|
||||
const incrementedVersion = inc(previousVersion, releaseType, identifier);
|
||||
|
||||
if (!incrementedVersion) {
|
||||
core.setFailed('Could not increment version.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(incrementedVersion)) {
|
||||
core.setFailed(`${incrementedVersion} is not a valid semver.`);
|
||||
return;
|
||||
}
|
||||
|
||||
newVersion = incrementedVersion;
|
||||
}
|
||||
|
||||
core.info(`New version is ${newVersion}.`);
|
||||
core.setOutput('new_version', newVersion);
|
||||
|
||||
const newTag = `${tagPrefix}${newVersion}`;
|
||||
core.info(`New tag after applying prefix is ${newTag}.`);
|
||||
core.setOutput('new_tag', newTag);
|
||||
|
||||
const changelog = await generateNotes(
|
||||
{
|
||||
preset: 'conventionalcommits',
|
||||
presetConfig: {
|
||||
types: mergeWithDefaultChangelogRules(mappedReleaseRules),
|
||||
},
|
||||
},
|
||||
{
|
||||
commits,
|
||||
logger: { log: console.info.bind(console) },
|
||||
options: {
|
||||
repositoryUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`,
|
||||
},
|
||||
lastRelease: { gitTag: latestTag.name },
|
||||
nextRelease: { gitTag: newTag, version: newVersion },
|
||||
}
|
||||
);
|
||||
core.info(`Changelog is ${changelog}.`);
|
||||
core.setOutput('changelog', changelog);
|
||||
|
||||
if (!isReleaseBranch && !isPreReleaseBranch) {
|
||||
core.info(
|
||||
'This branch is neither a release nor a pre-release branch. Skipping the tag creation.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (validTags.map((tag) => tag.name).includes(newTag)) {
|
||||
core.info('This tag already exists. Skipping the tag creation.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (/true/i.test(dryRun)) {
|
||||
core.info('Dry run: not performing tag action.');
|
||||
return;
|
||||
}
|
||||
|
||||
await createTag(newTag, createAnnotatedTag, commitRef);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
type ChangelogRule = {
|
||||
/**
|
||||
* Commit type.
|
||||
* Eg: feat, fix etc.
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Section in changelog to group commits by type.
|
||||
* Eg: 'Bug Fix', 'Features' etc.
|
||||
*/
|
||||
section?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default sections & changelog rules mentioned in `conventional-changelog-angular` & `conventional-changelog-conventionalcommits`.
|
||||
* References:
|
||||
* https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/writer-opts.js
|
||||
* https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-conventionalcommits/writer-opts.js
|
||||
*/
|
||||
export const defaultChangelogRules: Readonly<Record<string, ChangelogRule>> =
|
||||
Object.freeze({
|
||||
feat: { type: 'feat', section: 'Features' },
|
||||
fix: { type: 'fix', section: 'Bug Fixes' },
|
||||
perf: { type: 'perf', section: 'Performance Improvements' },
|
||||
revert: { type: 'revert', section: 'Reverts' },
|
||||
docs: { type: 'docs', section: 'Documentation' },
|
||||
style: { type: 'style', section: 'Styles' },
|
||||
refactor: { type: 'refactor', section: 'Code Refactoring' },
|
||||
test: { type: 'test', section: 'Tests' },
|
||||
build: { type: 'build', section: 'Build Systems' },
|
||||
ci: { type: 'ci', section: 'Continuous Integration' },
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { context, getOctokit } from '@actions/github';
|
||||
import * as core from '@actions/core';
|
||||
import { Await } from './ts';
|
||||
|
||||
let octokitSingleton: ReturnType<typeof getOctokit>;
|
||||
|
||||
type Tag = {
|
||||
name: string;
|
||||
commit: {
|
||||
sha: string;
|
||||
url: string;
|
||||
};
|
||||
zipball_url: string;
|
||||
tarball_url: string;
|
||||
node_id: string;
|
||||
};
|
||||
|
||||
export function getOctokitSingleton() {
|
||||
if (octokitSingleton) {
|
||||
return octokitSingleton;
|
||||
}
|
||||
const githubToken = core.getInput('github_token');
|
||||
octokitSingleton = getOctokit(githubToken);
|
||||
return octokitSingleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all tags for a given repository recursively
|
||||
*/
|
||||
export async function listTags(
|
||||
shouldFetchAllTags = false,
|
||||
fetchedTags: Tag[] = [],
|
||||
page = 1
|
||||
): Promise<Tag[]> {
|
||||
const octokit = getOctokitSingleton();
|
||||
|
||||
const tags = await octokit.repos.listTags({
|
||||
...context.repo,
|
||||
per_page: 100,
|
||||
page,
|
||||
});
|
||||
|
||||
if (tags.data.length < 100 || shouldFetchAllTags === false) {
|
||||
return [...fetchedTags, ...tags.data];
|
||||
}
|
||||
|
||||
return listTags(shouldFetchAllTags, [...fetchedTags, ...tags.data], page + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare `headRef` to `baseRef` (i.e. baseRef...headRef)
|
||||
* @param baseRef - old commit
|
||||
* @param headRef - new commit
|
||||
*/
|
||||
export async function compareCommits(baseRef: string, headRef: string) {
|
||||
const octokit = getOctokitSingleton();
|
||||
core.debug(`Comparing commits (${baseRef}...${headRef})`);
|
||||
|
||||
const commits = await octokit.repos.compareCommits({
|
||||
...context.repo,
|
||||
base: baseRef,
|
||||
head: headRef,
|
||||
});
|
||||
|
||||
return commits.data.commits;
|
||||
}
|
||||
|
||||
export async function createTag(
|
||||
newTag: string,
|
||||
createAnnotatedTag: boolean,
|
||||
GITHUB_SHA: string
|
||||
) {
|
||||
const octokit = getOctokitSingleton();
|
||||
let annotatedTag:
|
||||
| Await<ReturnType<typeof octokit.git.createTag>>
|
||||
| undefined = undefined;
|
||||
if (createAnnotatedTag) {
|
||||
core.debug(`Creating annotated tag.`);
|
||||
annotatedTag = await octokit.git.createTag({
|
||||
...context.repo,
|
||||
tag: newTag,
|
||||
message: newTag,
|
||||
object: GITHUB_SHA,
|
||||
type: 'commit',
|
||||
});
|
||||
}
|
||||
|
||||
core.debug(`Pushing new tag to the repo.`);
|
||||
await octokit.git.createRef({
|
||||
...context.repo,
|
||||
ref: `refs/tags/${newTag}`,
|
||||
sha: annotatedTag ? annotatedTag.data.sha : GITHUB_SHA,
|
||||
});
|
||||
}
|
||||
106
src/main.ts
106
src/main.ts
|
|
@ -1,108 +1,10 @@
|
|||
import * as core from "@actions/core";
|
||||
import { exec as _exec } from "@actions/exec";
|
||||
import { context, GitHub } from "@actions/github";
|
||||
import semver, { ReleaseType } from "semver";
|
||||
|
||||
async function exec(command: string) {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
try {
|
||||
const options = {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
stdout += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
stderr += data.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const code = await _exec(command, undefined, options);
|
||||
|
||||
return {
|
||||
code,
|
||||
stdout,
|
||||
stderr
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
code: 1,
|
||||
stdout,
|
||||
stderr,
|
||||
error: err
|
||||
};
|
||||
}
|
||||
}
|
||||
import * as core from '@actions/core';
|
||||
import action from './action';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const bump: ReleaseType = core.getInput("default_bump");
|
||||
const tagPrefix = core.getInput("tag_prefix");
|
||||
const releaseBranches = core.getInput("release_branches");
|
||||
|
||||
const { GITHUB_REF, GITHUB_SHA } = process.env;
|
||||
|
||||
if (!GITHUB_REF) {
|
||||
core.setFailed("Missing GITHUB_REF");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GITHUB_SHA) {
|
||||
core.setFailed("Missing GITHUB_SHA");
|
||||
return;
|
||||
}
|
||||
|
||||
const preRelease = releaseBranches
|
||||
.split(",")
|
||||
.every(branch => !GITHUB_REF.replace("refs/heads/", "").match(branch));
|
||||
|
||||
const hasTag = !!(await exec("git tag")).stdout.trim();
|
||||
let tag = "";
|
||||
|
||||
if (hasTag) {
|
||||
const previousTagSha = (
|
||||
await exec("git rev-list --tags --max-count=1")
|
||||
).stdout.trim();
|
||||
tag = (await exec(`git describe --tags ${previousTagSha}`)).stdout.trim();
|
||||
|
||||
if (previousTagSha === GITHUB_SHA) {
|
||||
core.debug("No new commits since previous tag. Skipping...");
|
||||
core.setOutput("previous_tag", tag);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
tag = "0.0.0";
|
||||
core.setOutput("previous_tag", tag);
|
||||
}
|
||||
|
||||
const newTag = `${tagPrefix}${semver.inc(tag, bump)}${
|
||||
preRelease ? `-${GITHUB_SHA.slice(0, 7)}` : ""
|
||||
}`;
|
||||
|
||||
core.setOutput("new_tag", newTag);
|
||||
|
||||
core.debug(`New tag: ${newTag}`);
|
||||
|
||||
if (preRelease) {
|
||||
core.debug(
|
||||
"This branch is not a release branch. Skipping the tag creation."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const octokit = new GitHub(core.getInput("github_token"));
|
||||
|
||||
core.debug(`Pushing new tag to the repo`);
|
||||
|
||||
await octokit.git.createRef({
|
||||
...context.repo,
|
||||
ref: `refs/tags/${newTag}`,
|
||||
sha: GITHUB_SHA
|
||||
});
|
||||
} catch (error) {
|
||||
await action();
|
||||
} catch (error: any) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export type Await<T> = T extends {
|
||||
then(onfulfilled?: (value: infer U) => unknown): unknown;
|
||||
}
|
||||
? U
|
||||
: T;
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
import * as core from '@actions/core';
|
||||
import { prerelease, rcompare, valid } from 'semver';
|
||||
// @ts-ignore
|
||||
import DEFAULT_RELEASE_TYPES from '@semantic-release/commit-analyzer/lib/default-release-types';
|
||||
import { compareCommits, listTags } from './github';
|
||||
import { defaultChangelogRules } from './defaults';
|
||||
import { Await } from './ts';
|
||||
|
||||
type Tags = Await<ReturnType<typeof listTags>>;
|
||||
|
||||
export async function getValidTags(
|
||||
prefixRegex: RegExp,
|
||||
shouldFetchAllTags: boolean
|
||||
) {
|
||||
const tags = await listTags(shouldFetchAllTags);
|
||||
|
||||
const invalidTags = tags.filter(
|
||||
(tag) =>
|
||||
!prefixRegex.test(tag.name) || !valid(tag.name.replace(prefixRegex, ''))
|
||||
);
|
||||
|
||||
invalidTags.forEach((name) => core.debug(`Found Invalid Tag: ${name}.`));
|
||||
|
||||
const validTags = tags
|
||||
.filter(
|
||||
(tag) =>
|
||||
prefixRegex.test(tag.name) && valid(tag.name.replace(prefixRegex, ''))
|
||||
)
|
||||
.sort((a, b) =>
|
||||
rcompare(a.name.replace(prefixRegex, ''), b.name.replace(prefixRegex, ''))
|
||||
);
|
||||
|
||||
validTags.forEach((tag) => core.debug(`Found Valid Tag: ${tag.name}.`));
|
||||
|
||||
return validTags;
|
||||
}
|
||||
|
||||
export async function getCommits(
|
||||
baseRef: string,
|
||||
headRef: string
|
||||
): Promise<{ message: string; hash: string | null }[]> {
|
||||
const commits = await compareCommits(baseRef, headRef);
|
||||
|
||||
return commits
|
||||
.filter((commit) => !!commit.commit.message)
|
||||
.map((commit) => ({
|
||||
message: commit.commit.message,
|
||||
hash: commit.sha,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getBranchFromRef(ref: string) {
|
||||
return ref.replace('refs/heads/', '');
|
||||
}
|
||||
|
||||
export function isPr(ref: string) {
|
||||
return ref.includes('refs/pull/');
|
||||
}
|
||||
|
||||
export function getLatestTag(
|
||||
tags: Tags,
|
||||
prefixRegex: RegExp,
|
||||
tagPrefix: string
|
||||
) {
|
||||
return (
|
||||
tags.find(
|
||||
(tag) =>
|
||||
prefixRegex.test(tag.name) &&
|
||||
!prerelease(tag.name.replace(prefixRegex, ''))
|
||||
) || {
|
||||
name: `${tagPrefix}0.0.0`,
|
||||
commit: {
|
||||
sha: 'HEAD',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getLatestPrereleaseTag(
|
||||
tags: Tags,
|
||||
identifier: string,
|
||||
prefixRegex: RegExp
|
||||
) {
|
||||
return tags
|
||||
.filter((tag) => prerelease(tag.name.replace(prefixRegex, '')))
|
||||
.find((tag) => tag.name.replace(prefixRegex, '').match(identifier));
|
||||
}
|
||||
|
||||
export function mapCustomReleaseRules(customReleaseTypes: string) {
|
||||
const releaseRuleSeparator = ',';
|
||||
const releaseTypeSeparator = ':';
|
||||
|
||||
return customReleaseTypes
|
||||
.split(releaseRuleSeparator)
|
||||
.filter((customReleaseRule) => {
|
||||
const parts = customReleaseRule.split(releaseTypeSeparator);
|
||||
|
||||
if (parts.length < 2) {
|
||||
core.warning(
|
||||
`${customReleaseRule} is not a valid custom release definition.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const defaultRule = defaultChangelogRules[parts[0].toLowerCase()];
|
||||
if (customReleaseRule.length !== 3) {
|
||||
core.debug(
|
||||
`${customReleaseRule} doesn't mention the section for the changelog.`
|
||||
);
|
||||
core.debug(
|
||||
defaultRule
|
||||
? `Default section (${defaultRule.section}) will be used instead.`
|
||||
: "The commits matching this rule won't be included in the changelog."
|
||||
);
|
||||
}
|
||||
|
||||
if (!DEFAULT_RELEASE_TYPES.includes(parts[1])) {
|
||||
core.warning(`${parts[1]} is not a valid release type.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((customReleaseRule) => {
|
||||
const [type, release, section] =
|
||||
customReleaseRule.split(releaseTypeSeparator);
|
||||
const defaultRule = defaultChangelogRules[type.toLowerCase()];
|
||||
|
||||
return {
|
||||
type,
|
||||
release,
|
||||
section: section || defaultRule?.section,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mergeWithDefaultChangelogRules(
|
||||
mappedReleaseRules: ReturnType<typeof mapCustomReleaseRules> = []
|
||||
) {
|
||||
const mergedRules = mappedReleaseRules.reduce(
|
||||
(acc, curr) => ({
|
||||
...acc,
|
||||
[curr.type]: curr,
|
||||
}),
|
||||
{ ...defaultChangelogRules }
|
||||
);
|
||||
|
||||
return Object.values(mergedRules).filter((rule) => !!rule.section);
|
||||
}
|
||||
|
|
@ -0,0 +1,876 @@
|
|||
import action from '../src/action';
|
||||
import * as utils from '../src/utils';
|
||||
import * as github from '../src/github';
|
||||
import * as core from '@actions/core';
|
||||
import {
|
||||
loadDefaultInputs,
|
||||
setBranch,
|
||||
setCommitSha,
|
||||
setInput,
|
||||
setRepository,
|
||||
} from './helper.test';
|
||||
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'info').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'info').mockImplementation(() => {});
|
||||
|
||||
beforeAll(() => {
|
||||
setRepository('https://github.com', 'org/repo');
|
||||
});
|
||||
|
||||
const mockCreateTag = jest
|
||||
.spyOn(github, 'createTag')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const mockSetOutput = jest
|
||||
.spyOn(core, 'setOutput')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const mockSetFailed = jest.spyOn(core, 'setFailed');
|
||||
|
||||
describe('github-tag-action', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setBranch('master');
|
||||
setCommitSha('79e0ea271c26aa152beef77c3275ff7b8f8d8274');
|
||||
loadDefaultInputs();
|
||||
});
|
||||
|
||||
describe('special cases', () => {
|
||||
it('does create initial tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [{ message: 'fix: this is my first fix', hash: null }];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags: any[] = [];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v0.0.1',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create patch tag without commits', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits: any[] = [];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags: any[] = [];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v0.0.1',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not create tag without commits and default_bump set to false', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('default_bump', 'false');
|
||||
const commits: any[] = [];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).not.toBeCalled();
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag using custom release types', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('custom_release_rules', 'james:patch,bond:major');
|
||||
const commits = [
|
||||
{ message: 'james: is the new cool guy', hash: null },
|
||||
{ message: 'bond: is his last name', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v2.0.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag using custom release types but non-custom commit message', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('custom_release_rules', 'james:patch,bond:major');
|
||||
const commits = [
|
||||
{ message: 'fix: is the new cool guy', hash: null },
|
||||
{ message: 'feat: is his last name', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.3.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('release branches', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setBranch('release');
|
||||
setInput('release_branches', 'release');
|
||||
});
|
||||
|
||||
it('does create patch tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [{ message: 'fix: this is my first fix', hash: null }];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.2.4',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create minor tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{ message: 'feat: this is my first feature', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.3.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create major tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{
|
||||
message:
|
||||
'my commit message\nBREAKING CHANGE:\nthis is a breaking change',
|
||||
hash: null,
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v2.0.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag when pre-release tag is newer', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{ message: 'feat: some new feature on a release branch', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v2.1.3-prerelease.0',
|
||||
commit: { sha: '678901', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v2.1.3-prerelease.1',
|
||||
commit: { sha: '234567', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v2.2.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag with custom release rules', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('custom_release_rules', 'james:preminor');
|
||||
const commits = [
|
||||
{
|
||||
message: 'feat: some new feature on a pre-release branch',
|
||||
hash: null,
|
||||
},
|
||||
{ message: 'james: this should make a preminor', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.3.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('pre-release branches', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setBranch('prerelease');
|
||||
setInput('pre_release_branches', 'prerelease');
|
||||
});
|
||||
|
||||
it('does not create tag without commits and default_bump set to false', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('default_prerelease_bump', 'false');
|
||||
const commits: any[] = [];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).not.toBeCalled();
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create prerelease tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('default_prerelease_bump', 'prerelease');
|
||||
const commits = [{ message: 'this is my first fix', hash: null }];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.2.4-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create prepatch tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [{ message: 'fix: this is my first fix', hash: null }];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.2.4-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create preminor tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{ message: 'feat: this is my first feature', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.3.0-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create premajor tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{
|
||||
message:
|
||||
'my commit message\nBREAKING CHANGE:\nthis is a breaking change',
|
||||
hash: null,
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v2.0.0-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag when release tag is newer', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{
|
||||
message: 'feat: some new feature on a pre-release branch',
|
||||
hash: null,
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3-prerelease.0',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v3.1.2-feature.0',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v2.1.4',
|
||||
commit: { sha: '234567', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v2.2.0-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does create tag with custom release rules', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
setInput('custom_release_rules', 'james:preminor');
|
||||
const commits = [
|
||||
{
|
||||
message: 'feat: some new feature on a pre-release branch',
|
||||
hash: null,
|
||||
},
|
||||
{ message: 'james: this should make a preminor', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockCreateTag).toHaveBeenCalledWith(
|
||||
'v1.3.0-prerelease.0',
|
||||
expect.any(Boolean),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('other branches', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setBranch('development');
|
||||
setInput('pre_release_branches', 'prerelease');
|
||||
setInput('release_branches', 'release');
|
||||
});
|
||||
|
||||
it('does output patch tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [{ message: 'fix: this is my first fix', hash: null }];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockSetOutput).toHaveBeenCalledWith('new_version', '1.2.4');
|
||||
expect(mockCreateTag).not.toBeCalled();
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does output minor tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{ message: 'feat: this is my first feature', hash: null },
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockSetOutput).toHaveBeenCalledWith('new_version', '1.3.0');
|
||||
expect(mockCreateTag).not.toBeCalled();
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does output major tag', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const commits = [
|
||||
{
|
||||
message:
|
||||
'my commit message\nBREAKING CHANGE:\nthis is a breaking change',
|
||||
hash: null,
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getCommits')
|
||||
.mockImplementation(async (sha) => commits);
|
||||
|
||||
const validTags = [
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: '012345', url: '' },
|
||||
zipball_url: '',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
jest
|
||||
.spyOn(utils, 'getValidTags')
|
||||
.mockImplementation(async () => validTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
await action();
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockSetOutput).toHaveBeenCalledWith('new_version', '2.0.0');
|
||||
expect(mockCreateTag).not.toBeCalled();
|
||||
expect(mockSetFailed).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { listTags } from '../src/github';
|
||||
|
||||
jest.mock(
|
||||
'@actions/github',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
context: { repo: { owner: 'mock-owner', repo: 'mock-repo' } },
|
||||
getOctokit: jest.fn().mockReturnValue({
|
||||
repos: {
|
||||
listTags: jest.fn().mockImplementation(({ page }: { page: number }) => {
|
||||
if (page === 6) {
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
const res = [...new Array(100).keys()].map((_) => ({
|
||||
name: `v0.0.${_ + (page - 1) * 100}`,
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
}));
|
||||
|
||||
return { data: res };
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
);
|
||||
|
||||
describe('github', () => {
|
||||
it('returns all tags', async () => {
|
||||
const tags = await listTags(true);
|
||||
|
||||
expect(tags.length).toEqual(500);
|
||||
expect(tags[499]).toEqual({
|
||||
name: 'v0.0.499',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the last 100 tags', async () => {
|
||||
const tags = await listTags(true);
|
||||
|
||||
expect(tags.length).toEqual(500);
|
||||
expect(tags[99]).toEqual({
|
||||
name: 'v0.0.99',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import yaml from 'js-yaml';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function setRepository(
|
||||
GITHUB_SERVER_URL: string,
|
||||
GITHUB_REPOSITORY: string
|
||||
) {
|
||||
process.env['GITHUB_SERVER_URL'] = GITHUB_SERVER_URL;
|
||||
process.env['GITHUB_REPOSITORY'] = GITHUB_REPOSITORY;
|
||||
}
|
||||
|
||||
export function setBranch(branch: string) {
|
||||
process.env['GITHUB_REF'] = `refs/heads/${branch}`;
|
||||
}
|
||||
|
||||
export function setCommitSha(sha: string) {
|
||||
process.env['GITHUB_SHA'] = sha;
|
||||
}
|
||||
|
||||
export function setInput(key: string, value: string) {
|
||||
process.env[`INPUT_${key.toUpperCase()}`] = value;
|
||||
}
|
||||
|
||||
export function setInputs(map: { [key: string]: string }) {
|
||||
Object.keys(map).forEach((key) => setInput(key, map[key]));
|
||||
}
|
||||
|
||||
export function loadDefaultInputs() {
|
||||
const actionYaml = fs.readFileSync(
|
||||
path.join(process.cwd(), 'action.yml'),
|
||||
'utf-8'
|
||||
);
|
||||
const actionJson = yaml.load(actionYaml) as {
|
||||
inputs: { [key: string]: { default?: string } };
|
||||
};
|
||||
const defaultInputs = Object.keys(actionJson['inputs'])
|
||||
.filter((key) => actionJson['inputs'][key].default)
|
||||
.reduce(
|
||||
(obj, key) => ({ ...obj, [key]: actionJson['inputs'][key].default }),
|
||||
{}
|
||||
);
|
||||
setInputs(defaultInputs);
|
||||
}
|
||||
|
||||
// Don't know how to have this file only for test but not have 'tsc' complain. So I made it a test file...
|
||||
describe('helper', () => {
|
||||
it('works', () => {});
|
||||
});
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
import * as utils from '../src/utils';
|
||||
import { getValidTags } from '../src/utils';
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '../src/github';
|
||||
import { defaultChangelogRules } from '../src/defaults';
|
||||
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
jest.spyOn(core, 'warning').mockImplementation(() => {});
|
||||
|
||||
const regex = /^v/;
|
||||
|
||||
describe('utils', () => {
|
||||
it('extracts branch from ref', () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const remoteRef = 'refs/heads/master';
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const branch = utils.getBranchFromRef(remoteRef);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(branch).toEqual('master');
|
||||
});
|
||||
|
||||
it('test if ref is PR', () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const remoteRef = 'refs/pull/123/merge';
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const isPullRequest = utils.isPr(remoteRef);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(isPullRequest).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns valid tags', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const testTags = [
|
||||
{
|
||||
name: 'release-1.2.3',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
const mockListTags = jest
|
||||
.spyOn(github, 'listTags')
|
||||
.mockImplementation(async () => testTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const validTags = await getValidTags(regex, false);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockListTags).toHaveBeenCalled();
|
||||
expect(validTags).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns sorted tags', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const testTags = [
|
||||
{
|
||||
name: 'v1.2.4-prerelease.1',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v1.2.4-prerelease.2',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v1.2.4-prerelease.0',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'v1.2.3',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
const mockListTags = jest
|
||||
.spyOn(github, 'listTags')
|
||||
.mockImplementation(async () => testTags);
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const validTags = await getValidTags(regex, false);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockListTags).toHaveBeenCalled();
|
||||
expect(validTags[0]).toEqual({
|
||||
name: 'v1.2.4-prerelease.2',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only prefixed tags', async () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const testTags = [
|
||||
{
|
||||
name: 'app2/5.0.0',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: '7.0.0',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
{
|
||||
name: 'app1/3.0.0',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
},
|
||||
];
|
||||
const mockListTags = jest
|
||||
.spyOn(github, 'listTags')
|
||||
.mockImplementation(async () => testTags);
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const validTags = await getValidTags(/^app1\//, false);
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mockListTags).toHaveBeenCalled();
|
||||
expect(validTags).toHaveLength(1);
|
||||
expect(validTags[0]).toEqual({
|
||||
name: 'app1/3.0.0',
|
||||
commit: { sha: 'string', url: 'string' },
|
||||
zipball_url: 'string',
|
||||
tarball_url: 'string',
|
||||
node_id: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom release types', () => {
|
||||
it('maps custom release types', () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const customReleasesString =
|
||||
'james:preminor,bond:premajor,007:major:Breaking Changes,feat:minor';
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const mappedReleases = utils.mapCustomReleaseRules(customReleasesString);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mappedReleases).toEqual([
|
||||
{ type: 'james', release: 'preminor' },
|
||||
{ type: 'bond', release: 'premajor' },
|
||||
{ type: '007', release: 'major', section: 'Breaking Changes' },
|
||||
{
|
||||
type: 'feat',
|
||||
release: 'minor',
|
||||
section: defaultChangelogRules['feat'].section,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters out invalid custom release types', () => {
|
||||
/*
|
||||
* Given
|
||||
*/
|
||||
const customReleasesString = 'james:pre-release,bond:premajor';
|
||||
|
||||
/*
|
||||
* When
|
||||
*/
|
||||
const mappedReleases = utils.mapCustomReleaseRules(customReleasesString);
|
||||
|
||||
/*
|
||||
* Then
|
||||
*/
|
||||
expect(mappedReleases).toEqual([{ type: 'bond', release: 'premajor' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method: mergeWithDefaultChangelogRules', () => {
|
||||
it('combines non-existing type rules with default rules', () => {
|
||||
/**
|
||||
* Given
|
||||
*/
|
||||
const newRule = {
|
||||
type: 'james',
|
||||
release: 'major',
|
||||
section: '007 Changes',
|
||||
};
|
||||
|
||||
/**
|
||||
* When
|
||||
*/
|
||||
const result = utils.mergeWithDefaultChangelogRules([newRule]);
|
||||
|
||||
/**
|
||||
* Then
|
||||
*/
|
||||
expect(result).toEqual([
|
||||
...Object.values(defaultChangelogRules),
|
||||
newRule,
|
||||
]);
|
||||
});
|
||||
|
||||
it('overwrites existing default type rules with provided rules', () => {
|
||||
/**
|
||||
* Given
|
||||
*/
|
||||
const newRule = {
|
||||
type: 'feat',
|
||||
release: 'minor',
|
||||
section: '007 Changes',
|
||||
};
|
||||
|
||||
/**
|
||||
* When
|
||||
*/
|
||||
const result = utils.mergeWithDefaultChangelogRules([newRule]);
|
||||
const overWrittenRule = result.find((rule) => rule.type === 'feat');
|
||||
|
||||
/**
|
||||
* Then
|
||||
*/
|
||||
expect(overWrittenRule?.section).toBe(newRule.section);
|
||||
});
|
||||
|
||||
it('returns only the rules having changelog section', () => {
|
||||
/**
|
||||
* Given
|
||||
*/
|
||||
const mappedReleaseRules = [
|
||||
{ type: 'james', release: 'major', section: '007 Changes' },
|
||||
{ type: 'bond', release: 'minor', section: undefined },
|
||||
];
|
||||
|
||||
/**
|
||||
* When
|
||||
*/
|
||||
const result = utils.mergeWithDefaultChangelogRules(mappedReleaseRules);
|
||||
|
||||
/**
|
||||
* Then
|
||||
*/
|
||||
expect(result).toContainEqual(mappedReleaseRules[0]);
|
||||
expect(result).not.toContainEqual(mappedReleaseRules[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,63 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"target": "es6",
|
||||
"module": "commonjs", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/// <reference types="semver" />
|
||||
|
||||
declare module '@semantic-release/commit-analyzer' {
|
||||
export function analyzeCommits(
|
||||
config: {
|
||||
preset?: string;
|
||||
config?: string;
|
||||
parserOpts?: any;
|
||||
releaseRules?:
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
release: string;
|
||||
scope?: string;
|
||||
}[];
|
||||
presetConfig?: string;
|
||||
},
|
||||
args: {
|
||||
commits: { message: string; hash: string | null }[];
|
||||
logger: { log: (args: any) => void };
|
||||
}
|
||||
): Promise<any>;
|
||||
}
|
||||
|
||||
declare module '@semantic-release/release-notes-generator' {
|
||||
export function generateNotes(
|
||||
config: {
|
||||
preset?: string;
|
||||
config?: string;
|
||||
parserOpts?: any;
|
||||
writerOpts?: any;
|
||||
releaseRules?:
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
release: string;
|
||||
scope?: string;
|
||||
}[];
|
||||
presetConfig?: any; // Depends on used preset
|
||||
},
|
||||
args: {
|
||||
commits: { message: string; hash: string | null }[];
|
||||
logger: { log: (args: any) => void };
|
||||
options: {
|
||||
repositoryUrl: string;
|
||||
};
|
||||
lastRelease: { gitTag: string };
|
||||
nextRelease: { gitTag: string; version: string };
|
||||
}
|
||||
): Promise<string>;
|
||||
}
|
||||
Loading…
Reference in New Issue