first commit

This commit is contained in:
Al Azhar 2024-08-15 09:31:12 +07:00
commit 5d43a218cc
12 changed files with 36378 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

170
README.md Normal file
View File

@ -0,0 +1,170 @@
# Autoversion-action
This action will detect new version.
### Tagging: Part of a Complete Deployment Solution
This action works well in combination with:
- [actions/create-release](https://github.com/actions/create-release) (Auto-release)
- [author/action-publish](https://github.com/author/action-publish) (Auto-publish JavaScript/Node modules)
- [author/action-rollback](https://github.com/author/action-rollback) (Auto-rollback releases on failures)
## Usage
The following is an example `.gitea/workflows/main.yml` that will execute when a `push` to the `master` branch occurs.
```yaml
name: Detect Tag
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: butlerlogic/action-autotag@stable
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
```
To make this work, the workflow must have the checkout action _before_ the autotag action.
This **order** is important!
```yaml
- uses: actions/checkout@v4
- uses: butlerlogic/action-autotag@stable
```
**If the repository is not checked out first, the autotagger cannot find the source files.**
## Configuration
The `GITEA_TOKEN` **must** be provided. Without this, it is not possible to create a new tag. Make sure the autotag action looks like the following example:
```yaml
- uses: butlerlogic/action-autotag@stable
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
```
The action will automatically extract the token at runtime. **DO NOT MANUALLY ENTER YOUR TOKEN.** If you put the actual token in your workflow file, you'll make it accessible (in plaintext) to anyone who ever views the repository (it will be in your git history).
## Optional Configuration Options
There are several options to customize how the tag is created.
### root
Depending on the selected strategy, autotagger will look for the confgured identify file (i.e. `package.json`, `composer.json`, `Dockerfile`, etc) in the project root. If the file is located in a subdirectory, this option can be used to point to the correct file.
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
strategy: regex # Optional since regex_pattern is defined
root: "/path/to/subdirectory/my.file"
regex_pattern: "version=([0-9\.])"
```
The version will be extracted by scanning the content of `/path/to/subdirectory/my.file` for a string like `version=1.0.0`. See the `regex_pattern` option for more details.
### regex_pattern
An optional attribute containing the regular expression used to extract the version number.
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
regex_pattern: "version=([0-9\.]{5}([-\+][\w\.0-9]+)?)"
```
This attribute is used as the first argument of a [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp) object. The first "group" (i.e. what's in the main set of parenthesis/the whole version number) will be used as the version number. For an example, see this [working example](https://regexr.com/51r8j).
The pattern described in this example is a simplistic one. If you need a more explicit one the [complete semver pattern](https://regex101.com/r/vkijKf/1/) is:
```
^((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$
```
As of `1.1.2`, JavaScript named patterns are supported, where the group named `version` will be used to populate the tag. For example:
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
regex_pattern: "(version=)(?<version>[\d+\.]{3}([-\+][\w\.0-9]+)?)"
```
### tag_prefix
By default, [semantic versioning](https://semver.org/) is used, such as `1.0.0`. A prefix can be used to add text before the tag name. For example, if `tag_prefix` is set to `v`, then the tag would be labeled as `v1.0.0`.
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
tag_prefix: "v"
```
### tag_message
This is the annotated commit message associated with the tag. By default, a changelog will be generated from the commits between the latest tag and the current reference (HEAD). Setting this option will override the message.
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
tag_message: "Custom message goes here."
```
### commit_message_template
By default, a changelog is generated, containing the commit messages since the last release. The message is generated by applying a commit message template to each commit's data attributes.
```yaml
- uses: butlerlogic/action-autotag@1.0.0
env:
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
with:
commit_message_template: "({{sha}} by {{author}}) {{message}}"
```
Optional data points:
1. `number` The commit number (relevant to the overall list)
1. `message` The commit message.
1. `author` The author of the commit.
1. `sha` The SHA value representing the commit.
The default is `{{number}}) {{message}} ({{author}})\nSHA: {{sha}}\n`.
_Example output:_
```
1) Update README.md (coreybutler)
(SHA: c5e09fc45106a4b27b8f4598fb79811b589a4684)
2) Added metadoc capability to introspect the shell/commands. (coreybutler)
(SHA: b690be366a5636d51b1245a1b39c86102ddb8a81)
```
## Developer Notes
If you are building an action that runs after this one, be aware this action produces several [outputs](https://help.github.com/en/articles/metadata-syntax-for-github-actions#outputs):
1. `tagname` will be empty if no tag was created, or it will be the value of the new tag.
1. `tagcreated`: `yes` or `no`.
1. `version` will be the extracted/provided version.
---

32
action.yml Normal file
View File

@ -0,0 +1,32 @@
name: "autoversion"
description: "Automatically detect new tags for new versions"
author: "Al Azhar"
branding:
icon: "tag"
color: "blue"
inputs:
filepath:
description: Autotag will look for the appropriate file in in this location (relative to project root).
required: true
default: './'
regex_pattern:
description: An optional attribute containing the regular expression used to extract the version number.
required: true
tag_prefix:
description: By default, package.json uses semantic versioning, such as "1.0.0". A prefix can be used to add text before the tag name. For example, if tag_prefx is set to "v", then the tag would be labeled as "v1.0.0".
required: false
log_template:
description: "The commit message template (per commit). Default is `{{number}}) {{message}} ({{author}})\nSHA: {{sha}}\n`"
required: false
outputs:
tagname:
description: Returns the new tag value. Empty if a tag is not created.
changelog:
description: Returns list commit message from previous commit.
version:
description: The version, as defined in package.json or explicitly set in the input.
prerelease:
description: Is the new version prerelease.
runs:
using: "node20"
main: "dist/index.js"

35777
dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

3
dist/package.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

98
package-lock.json generated Normal file
View File

@ -0,0 +1,98 @@
{
"name": "autoversion",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "autoversion",
"version": "1.0.0",
"dependencies": {
"@actions/core": "^1.10.0",
"gitea-js": "1.22.0",
"semver": "^7.3.8"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1"
}
},
"node_modules/@actions/core": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz",
"integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"engines": {
"node": ">=14"
}
},
"node_modules/@vercel/ncc": {
"version": "0.38.1",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz",
"integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==",
"dev": true,
"bin": {
"ncc": "dist/ncc/cli.js"
}
},
"node_modules/gitea-js": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/gitea-js/-/gitea-js-1.22.0.tgz",
"integrity": "sha512-vG3yNU2NKX7vbsqHH5U3q0u3OmWWh3c4nvyWtx022jQEDJDZP47EoGurXCmOhzvD5AwgUV6r+lVAz+Fa1dazgg=="
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/undici": {
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
}
}
}

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "autoversion",
"version": "1.0.0",
"description": "Automatically create a tag whenever the version changes in file version",
"main": "main.js",
"scripts": {
"start": "node ./src/main.js",
"package": "ncc build ./src/main.js -o dist"
},
"keywords": [
"actions",
"node",
"setup"
],
"author": "Al Azhar",
"type": "module",
"dependencies": {
"@actions/core": "^1.10.0",
"semver": "^7.3.8",
"gitea-js": "1.22.0"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1"
}
}

36
src/lib/regex.js Normal file
View File

@ -0,0 +1,36 @@
import { existsSync, statSync, readFileSync } from 'fs'
import { resolve } from 'path'
export default class Regex {
constructor (root = './', pattern) {
root = resolve(root)
if (statSync(root).isDirectory()) {
throw new Error(`${root} is a directory. The Regex tag identification strategy requires a file.`)
}
if (!existsSync(root)) {
throw new Error(`"${root}" does not exist.`)
}
this.content = readFileSync(root).toString()
let content = pattern.exec(this.content)
if (!content) {
this._version = null
// throw new Error(`Could not find pattern matching "${pattern.toString()}" in "${root}".`)
} else if (content.groups && content.groups.version) {
this._version = content.groups.version
} else {
this._version = content[1]
}
}
get version () {
return this._version
}
get versionFound () {
return this._version !== null
}
}

34
src/lib/setup.js Normal file
View File

@ -0,0 +1,34 @@
import core from '@actions/core'
import { readdirSync } from 'fs'
import path from 'path'
export default class Setup {
static debug () {
// Metadate for debugging
// core.debug(
// ` Available environment variables:\n -> ${Object.keys(process.env)
// .map(i => i + ' :: ' + process.env[i])
// .join('\n -> ')}`
// )
const dir = readdirSync(path.resolve(process.env.GITHUB_WORKSPACE), { withFileTypes: true })
.map(entry => {
return `${entry.isDirectory() ? '> ' : ' - '}${entry.name}`
})
.join('\n')
console.log({dir})
core.debug(` Working Directory: ${process.env.GITHUB_WORKSPACE}:\n${dir}`)
}
static requireAnyEnv () {
for (const arg of arguments) {
if (!process.env.hasOwnProperty(arg)) {
return
}
}
throw new Error('At least one of the following environment variables is required: ' + Array.slice(arguments).join(', '))
}
}

109
src/lib/tag.js Normal file
View File

@ -0,0 +1,109 @@
import core from '@actions/core'
import os from 'os'
import { giteaApi } from 'gitea-js'
const gitea = giteaApi(process.env.GITEA_URL, {
token: process.env.GITEA_TOKEN, // generate one at https://gitea.example.com/user/settings/applications
customFetch: fetch,
});
// Get owner and repo from context of payload that triggered the action
const [ owner, repo ] = process.env.GITHUB_REPOSITORY.split('/')
export default class Tag {
constructor (prefix, version) {
this.prefix = prefix
this.version = version
this._tags = null
this._exists = null
}
get name () {
return `${this.prefix.trim()}${this.version.trim()}`
}
get message() {
return (async () => {
try {
let tags = await this.getTags()
if (tags.length === 0) {
return `Version ${this.version}`
}
const changelog = await gitea.repos.repoCompareDiff(owner, repo, tags.shift().name + "..." + process.env.GITHUB_REF_NAME ?? 'main' )
const tpl = (core.getInput('log_template', { required: false }) || '').trim()
return changelog.data.commits
.map(
(commit, i) => {
if (tpl.length > 0) {
return tpl
.replace(/\{\{\s?(number)\s?\}\}/gi, i + 1)
.replace(/\{\{\s?(message)\s?\}\}/gi, commit.commit.message)
.replace(/\{\{\s?(author)\s?\}\}/gi, commit.hasOwnProperty('author') ? (commit.author.hasOwnProperty('login') ? commit.author.login : '') : '')
.replace(/\{\{\s?(sha)\s?\}\}/gi, commit.sha)
.trim() + '\n'
} else {
return `${i === 0 ? '\n' : ''}${i + 1}) ${commit.commit.message}${
commit.hasOwnProperty('author')
? commit.author.hasOwnProperty('login')
? ' (' + commit.author.login + ')'
: ''
: ''
}\n(SHA: ${commit.sha})\n`
}
})
.join('\n')
} catch (e) {
core.warning('Failed to generate changelog from commits: ' + e.message + os.EOL)
return `Version ${this.version}`
}
})();
}
get prerelease () {
return /([0-9\.]{5}(-[\w\.0-9]+)?)/i.test(this.version)
}
get previous () {
return (async () => {
try {
const tags = await this.getTags()
return tags.shift().name;
} catch(e) {
return "0.0.0";
}
})();
}
async getTags () {
if (this._tags !== null) {
return this._tags.data
}
this._tags = await gitea.repos.repoListTags(owner, repo)
return this._tags.data
}
async exists () {
if (this._exists !== null) {
return this._exists
}
const currentTag = this.name
const tags = await this.getTags()
for (const tag of tags) {
if (tag.name === currentTag) {
this._exists = true
return true
}
}
this._exists = false
return false
}
}

85
src/main.js Normal file
View File

@ -0,0 +1,85 @@
import * as core from '@actions/core'
import os from 'os'
import semver from 'semver'
import Setup from './lib/setup.js'
import Tag from './lib/tag.js'
import Regex from './lib/regex.js'
async function run () {
try {
Setup.debug()
Setup.requireAnyEnv('GITEA_TOKEN', 'INPUT_GITEA_TOKEN')
// Identify the root directory to use for auto-identifying a tag version
const filepath = core.getInput('filepath', { required: true })
// Retrieve the Regex pattern
const pattern = core.getInput('regex_pattern', { required: true })
core.info(`filepath : ${ filepath }`)
core.info(`regex_pattern : ${ pattern }`)
// Extract the version number using the supplied Regex
let version = (new Regex(filepath, new RegExp(pattern, 'gim'))).version
if (!version) {
throw new Error(`No version identified extraction with the /${ pattern }/gim pattern.`)
}
core.info(`version ${ version } detected`)
const [major, minor, patch, pre] = version.split(".")
version = `${major}.${minor}.${patch}${pre ? "-beta." + pre : ""}`
core.info(`version translated to ${ version }`)
// Configure a tag using the identified version
const tag = new Tag(
core.getInput('tag_prefix', { required: false }),
version
)
// Get min
const minVersion = await tag.previous
core.info(`Previous version ${ minVersion }`)
// Ensure that version and minVersion are valid SemVer strings
const versionSemVer = semver.coerce(version, { includePrerelease: pre})
const minVersionSemVer = semver.coerce(minVersion , { includePrerelease: pre})
if (!minVersionSemVer) {
core.info(`Skipping min version check. ${minVersion} is not valid SemVer`)
}
if(!versionSemVer) {
core.info(`Skipping min version check. ${version} is not valid SemVer`)
}
if (minVersionSemVer && versionSemVer && semver.lt(versionSemVer, minVersionSemVer)) {
core.info(`Version "${version}" is lower than minimum "${minVersion}"`)
return
}
core.notice(`Recognized "${version}"`)
core.setOutput('version', version)
core.debug(` Detected version ${version}`)
// Check for existance of tag and abort (short circuit) if it already exists.
if (await tag.exists()) {
core.warning(`"${tag.name}" tag already exists.` + os.EOL)
core.setOutput('tagname', '')
return
}
// The tag setter will autocorrect the message if necessary.
core.setOutput('changelog', await tag.message)
core.setOutput('prerelease', tag.prerelease ? 'yes' : 'no')
core.setOutput('tagname', tag.name)
} catch (error) {
core.setFailed(error.message + '\n' + error.stack)
core.setOutput('tagname', '')
}
}
run()

8
src/package.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "autoversion",
"version": "1.0.0",
"description": "",
"main": "main.js",
"author": "Al Azhar",
"type": "module"
}