Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add push-new-tag and retires #171

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,8 @@
"sourceType": "module"
},
"rules": {
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
"linebreak-style": ["error", "unix"],
"semi": ["error", "always"]
},
"plugins": [
"jest"
]
}
"plugins": ["jest"]
}
6 changes: 3 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: "weekly"
interval: 'weekly'
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "CI"
name: 'CI'
on:
pull_request:
push:
Expand All @@ -17,4 +17,4 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v3
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "RELEASE"
name: 'RELEASE'

on:
workflow_dispatch:
Expand All @@ -9,7 +9,6 @@ on:

jobs:
release:

runs-on: ubuntu-latest

steps:
Expand Down
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ You can now consume the action by referencing the v1 branch

```yaml
uses: allegro-actions/next-version@v1
```
```
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This action calculates next tag based on git history.

Supports semver tags and custom formats.

Additionally, it can also create and push the selected tag.

## Outputs

`current_tag` - latest found version tag
Expand All @@ -27,7 +29,7 @@ Supports semver tags and custom formats.
with:
tag: ${{ steps.bump.outputs.next_tag }}
current-tag: ${{ steps.bump.outputs.current_tag }}
```
```

Will output v1.0.1 (assuming v1.0.0 tag exists)

Expand All @@ -43,7 +45,7 @@ You can change prefix to handle **my-app-1.0.0** tag
uses: allegro-actions/next-version@v1
with:
prefix: 'my-app-'
```
```

Will output **my-app-1.0.1** (assuming my-app-1.0.0 tag exists)

Expand All @@ -57,7 +59,7 @@ You can add versioning to create **v1** tag
uses: allegro-actions/next-version@v1
with:
versioning: 'single-number'
```
```

Will output **v2** (assuming v1 tag exists)

Expand All @@ -73,10 +75,26 @@ You can also force next version.
uses: allegro-actions/next-version@v1
with:
force: '4.0.0'
```
```

Will always output **v4.0.0**

### push-new-tag

You can make the next tag be automatically created and pushed.

It's possible that in between selecting and pushing the tag, another process has already pushed tag with exactly the same name.
If that's a concern, you can make the action do an automatic retry of generating and pushing a new version. It's disabled by default.

```yaml
- name: push new version
id: 'bump'
uses: allegro-actions/next-version@v1
with:
push-new-tag: 'true'
retries: '1'
```

## Use cases

### Adding manual control
Expand Down Expand Up @@ -105,4 +123,4 @@ jobs:
uses: allegro-actions/next-version@v1
with:
force: ${{ github.event.inputs.forceVersion }}
```
```
128 changes: 35 additions & 93 deletions action.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,44 @@
const semver = require('semver');
const {getLatestTag} = require('./git-commands');
const generateTags = require('./generate-tags');
const gitCommands = require('./git-commands');
const { getLatestTag } = require('./git-commands');

/**
* @param prefix
* @param versioning
* @param force
* @param tagExtractor
* @returns {string|*}
@param {string} prefix
@param {string} versioning
@param {string} force
@param {string} preReleaseSuffix
@param {string} level
@param {boolean} pushNewTag
@param {number} retries
@param {(string) => string | null} tagExtractor
* @returns {GeneratedTags}
*/
module.exports = function action(
{
prefix = 'v',
versioning = 'semver',
force,
preReleaseSuffix = '',
level = 'patch'
},
tagExtractor = getLatestTag
{ prefix, versioning, force, preReleaseSuffix, level, pushNewTag, retries },
tagExtractor = getLatestTag,
) {

const latestTag = tagExtractor(prefix);
const PRERELEASE_LEVEL_NAME = 'prerelease';

if (force) return {'currentTag': latestTag || '', 'nextTag': prefix + force, 'nextVersion': force};

if (!['semver', 'single-number'].includes(versioning)) {
throw new Error(`unknown versioning '${versioning}'`);
}

if (!['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'].includes(level)) {
throw new Error(`Invalid level name: ${level}`);
}

function isPreReleaseLevel() {
return level === PRERELEASE_LEVEL_NAME;
}

if (isPreReleaseLevel() && preReleaseSuffix === '') {
throw new Error(`There is no pre release suffix for level: ${level}`);
}

if (latestTag === null) {
let calculatedPreReleaseVersion = isPreReleaseLevel() ? `-${preReleaseSuffix}.0` : '';
let calculatedNextVersion = '';

if (versioning === 'semver') calculatedNextVersion = `0.0.1${calculatedPreReleaseVersion}`;
if (versioning === 'single-number') calculatedNextVersion = `1${calculatedPreReleaseVersion}`;

return {
currentTag: '',
nextTag: `${prefix}${calculatedNextVersion}`,
nextVersion: `${calculatedNextVersion}`
};
}

if (!latestTag.startsWith(prefix)) {
throw new Error(`expecting provided tag ${latestTag} to start with ${prefix}`);
}
const version = latestTag.slice(prefix.length);

switch (versioning) {
case 'semver': {
if (!semver.valid(version)) throw new Error(`version ${version} not a valid semver string`);
return {
currentTag: latestTag,
nextTag: `${prefix}${semver.inc(version, level, preReleaseSuffix)}`,
nextVersion: semver.inc(version, level, preReleaseSuffix)
};
}
case 'single-number': {
let calculatedNextVersion = '';
const isPreReleasedTag = version.includes('-' + preReleaseSuffix);

const [singleNumberVersion, suffix] = version.split(`-${preReleaseSuffix}.`, 2);
const nextPreReleaseVersion = suffix ? parseInt(suffix, 10) + 1 : 0;

if (isPreReleasedTag && isPreReleaseLevel()) {
calculatedNextVersion = `${singleNumberVersion}-${preReleaseSuffix}.${nextPreReleaseVersion}`;
}

if (isPreReleasedTag && !isPreReleaseLevel()) {
calculatedNextVersion = parseInt(version, 10);
if (isNaN(calculatedNextVersion)) throw new Error(`version ${version} not a valid number`);
}

if (!isPreReleasedTag && isPreReleaseLevel()) {
calculatedNextVersion = `${parseInt(singleNumberVersion, 10) + 1}-${preReleaseSuffix}.${nextPreReleaseVersion}`;
let retriesLeft = clampRetries(retries);
while (retriesLeft >= 0) {
const tags = generateTags({ prefix, versioning, force, preReleaseSuffix, level }, tagExtractor);
try {
if (pushNewTag) {
gitCommands.pushNewTag(tags.nextTag);
}

if (!isPreReleasedTag && !isPreReleaseLevel()) {
calculatedNextVersion = `${parseInt(singleNumberVersion, 10) + 1}`;
return tags;
} catch (e) {
if (retriesLeft > 0) {
retriesLeft--;
} else {
throw e;
}

return {
currentTag: latestTag,
nextTag: `${prefix}${calculatedNextVersion}`,
nextVersion: `${calculatedNextVersion}`
};
}
}
};
throw new Error(`Failed to generate tags in ${retries} retries`); // Should never occur
};
/**
* @param {number} retries
* @return {number}
*/
function clampRetries(retries) {
return Math.max(0, Math.min(10, retries)) || 0;
}
Loading