mirror of
				https://github.com/ButlerLogic/action-autotag.git
				synced 2025-10-31 23:36:22 +07:00 
			
		
		
		
	Refactored to use esbuild
This commit is contained in:
		
							
								
								
									
										16
									
								
								src/lib/docker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/lib/docker.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import Regex from './regex.js' | ||||
| import path from 'path' | ||||
| import fs from 'fs' | ||||
| import core from '@actions/core' | ||||
|  | ||||
| export default class Dockerfile extends Regex { | ||||
|   constructor (root = null) { | ||||
|     root = path.join(process.env.GITHUB_WORKSPACE, root) | ||||
|  | ||||
|     if (fs.statSync(root).isDirectory()) { | ||||
|       root = path.join(root, 'Dockerfile') | ||||
|     } | ||||
|  | ||||
|     super(root, /LABEL[\s\t]+version=[\t\s+]?[\"\']?([0-9\.]+)[\"\']?/i) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/lib/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/lib/package.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| import fs from 'fs' | ||||
| import path from 'path' | ||||
|  | ||||
| export default class Package { | ||||
|   constructor (root = './') { | ||||
|     root = path.join(process.env.GITHUB_WORKSPACE, root) | ||||
|  | ||||
|     if (fs.statSync(root).isDirectory()) { | ||||
|       root = path.join(root, 'package.json') | ||||
|     } | ||||
|  | ||||
|     if (!fs.existsSync(root)) { | ||||
|       throw new Error(`package.json does not exist at ${root}.`) | ||||
|     } | ||||
|  | ||||
|     this.root = root | ||||
|     this.data = JSON.parse(fs.readFileSync(root)) | ||||
|   } | ||||
|  | ||||
|   get version () { | ||||
|     return this.data.version | ||||
|   } | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/lib/regex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/lib/regex.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| import fs from 'fs' | ||||
| import path from 'path' | ||||
|  | ||||
| export default class Regex { | ||||
|   constructor (root = './', pattern) { | ||||
|     root = path.resolve(root) | ||||
|  | ||||
|     if (fs.statSync(root).isDirectory()) { | ||||
|       throw new Error(`${root} is a directory. The Regex tag identification strategy requires a file.`) | ||||
|     } | ||||
|  | ||||
|     if (!fs.existsSync(root)) { | ||||
|       throw new Error(`"${root}" does not exist.`) | ||||
|     } | ||||
|  | ||||
|     this.content = fs.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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/lib/setup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lib/setup.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| import core from '@actions/core' | ||||
| import fs 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 = fs | ||||
|       .readdirSync(path.resolve(process.env.GITHUB_WORKSPACE), { withFileTypes: true }) | ||||
|       .map(entry => { | ||||
|         return `${entry.isDirectory() ? '> ' : '  - '}${entry.name}` | ||||
|       }) | ||||
|       .join('\n') | ||||
|  | ||||
|     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(', ')) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										176
									
								
								src/lib/tag.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								src/lib/tag.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,176 @@ | ||||
| import core from '@actions/core' | ||||
| import os from 'os' | ||||
| import gh from '@actions/github' | ||||
|  | ||||
| // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage | ||||
| const github = new gh.GitHub(process.env.GITHUB_TOKEN || process.env.INPUT_GITHUB_TOKEN) | ||||
| // Get owner and repo from context of payload that triggered the action | ||||
| const { owner, repo } = gh.context.repo | ||||
|  | ||||
| export default class Tag { | ||||
|   constructor (prefix, version, postfix) { | ||||
|     this.prefix = prefix | ||||
|     this.version = version | ||||
|     this.postfix = postfix | ||||
|     this._tags = null | ||||
|     this._message = null | ||||
|     this._exists = null | ||||
|     this._sha = '' | ||||
|     this._uri = '' | ||||
|     this._ref = '' | ||||
|   } | ||||
|  | ||||
|   get name () { | ||||
|     return `${this.prefix.trim()}${this.version.trim()}${this.postfix.trim()}` | ||||
|   } | ||||
|  | ||||
|   set message (value) { | ||||
|     if (value && value.length > 0) { | ||||
|       this._message = value | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get message() { | ||||
|     return this._message || '' | ||||
|   } | ||||
|  | ||||
|   get sha () { | ||||
|     return this._sha || '' | ||||
|   } | ||||
|  | ||||
|   get uri () { | ||||
|     return this._uri || '' | ||||
|   } | ||||
|  | ||||
|   get ref () { | ||||
|     return this._ref || '' | ||||
|   } | ||||
|  | ||||
|   get prerelease () { | ||||
|     return /([0-9\.]{5}(-[\w\.0-9]+)?)/i.test(this.version) | ||||
|   } | ||||
|  | ||||
|   get build () { | ||||
|     return /([0-9\.]{5}(\+[\w\.0-9]+)?)/i.test(this.version) | ||||
|   } | ||||
|  | ||||
|   async getMessage () { | ||||
|     if (this._message !== null) { | ||||
|       return this._message | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       let tags = await this.getTags() | ||||
|  | ||||
|       if (tags.length === 0) { | ||||
|         return `Version ${this.version}` | ||||
|       } | ||||
|  | ||||
|       const changelog = await github.repos.compareCommits({ owner, repo, base: tags.shift().name, head: 'master' }) | ||||
|  | ||||
|       const tpl = (core.getInput('commit_message_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}` | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async getTags () { | ||||
|     if (this._tags !== null) { | ||||
|       return this._tags.data | ||||
|     } | ||||
|  | ||||
|     this._tags = await github.repos.listTags({ owner, repo, per_page: 100 }) | ||||
|  | ||||
|     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 | ||||
|   } | ||||
|  | ||||
|   async push () { | ||||
|     let tagexists = await this.exists() | ||||
|  | ||||
|     if (!tagexists) { | ||||
|       // Create tag | ||||
|       const message = await this.getMessage() | ||||
|       const newTag = await github.git.createTag({ | ||||
|         owner, | ||||
|         repo, | ||||
|         tag: this.name, | ||||
|         message, | ||||
|         object: process.env.GITHUB_SHA, | ||||
|         type: 'commit' | ||||
|       }) | ||||
|  | ||||
|       this._sha = newTag.data.sha | ||||
|       core.warning(`Created new tag: ${newTag.data.tag}`) | ||||
|  | ||||
|       // Create reference | ||||
|       let newReference | ||||
|  | ||||
|       try { | ||||
|         newReference = await github.git.createRef({ | ||||
|           owner, | ||||
|           repo, | ||||
|           ref: `refs/tags/${newTag.data.tag}`, | ||||
|           sha: newTag.data.sha | ||||
|         }) | ||||
|       } catch (e) { | ||||
|         core.warning({ | ||||
|           owner, | ||||
|           repo, | ||||
|           ref: `refs/tags/${newTag.data.tag}`, | ||||
|           sha: newTag.data.sha | ||||
|         }) | ||||
|  | ||||
|         throw e | ||||
|       } | ||||
|  | ||||
|       this._uri = newReference.data.url | ||||
|       this._ref = newReference.data.ref | ||||
|       this._message = message; | ||||
|  | ||||
|       core.warning(`Reference ${newReference.data.ref} available at ${newReference.data.url}` + os.EOL) | ||||
|     } else { | ||||
|       core.warning('Cannot push tag (it already exists).') | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										127
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| import * as core from '@actions/core' | ||||
| import os from 'os' | ||||
| import semver from 'semver' | ||||
| import Setup from './lib/setup.js' | ||||
| import Package from './lib/package.js' | ||||
| import Tag from './lib/tag.js' | ||||
| import Regex from './lib/regex.js' | ||||
| import Dockerfile from './lib/docker.js' | ||||
|  | ||||
| async function run () { | ||||
|   try { | ||||
|     Setup.debug() | ||||
|     Setup.requireAnyEnv('GITHUB_TOKEN', 'INPUT_GITHUB_TOKEN') | ||||
|  | ||||
|     // Configure the default output | ||||
|     core.setOutput('tagcreated', 'no') | ||||
|  | ||||
|     // Identify the tag parsing strategy | ||||
|     const strategy = (core.getInput('regex_pattern', { required: false }) || '').trim().length > 0 ? 'regex' : ((core.getInput('strategy', { required: false }) || 'package').trim().toLowerCase()) | ||||
|     const root = core.getInput('root', { required: false }) || core.getInput('package_root', { required: false }) || (strategy === 'composer' ? './composer.json' : './') | ||||
|  | ||||
|     // If this value is true, the tag will not be pushed | ||||
|     const isDryRun = (core.getInput('dry_run', { required: false }) || '').trim().toLowerCase() === 'true' | ||||
|  | ||||
|     // Extract the version number using the supplied strategy | ||||
|     let version = core.getInput('root', { required: false }) | ||||
|     version = version === null || version.trim().length === 0 ? null : version | ||||
|     const pattern = core.getInput('regex_pattern', { required: false }) | ||||
|     const versionSemVer = semver.coerce(version) | ||||
|  | ||||
|     if (!versionSemVer) { | ||||
|       switch (strategy) { | ||||
|         case 'docker': | ||||
|           version = (new Dockerfile(root)).version | ||||
|           break | ||||
|  | ||||
|         case 'composer': | ||||
|         case 'package': | ||||
|           // Extract using the package strategy (this is the default strategy) | ||||
|           version = (new Package(root)).version | ||||
|           break | ||||
|  | ||||
|         case 'regex': | ||||
|           version = (new Regex(root, new RegExp(pattern, 'gim'))).version | ||||
|           break | ||||
|  | ||||
|         default: | ||||
|           core.setFailed(`"${strategy}" is not a recognized tagging strategy. Choose from: 'package' (package.json), 'composer' (composer.json), 'docker' (uses Dockerfile), or 'regex' (JS-based RegExp).`) | ||||
|           return | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const msg = ` using the ${strategy} extraction${strategy === 'regex' ? ' with the /' + pattern + '/gim pattern.' : ''}.` | ||||
|  | ||||
|     if (!version) { | ||||
|       throw new Error(`No version identified${msg}`) | ||||
|     } | ||||
|  | ||||
|     const minVersion = core.getInput('min_version', { required: false }) | ||||
|  | ||||
|     // Ensure that version and minVersion are valid SemVer strings | ||||
|     const minVersionSemVer = semver.coerce(minVersion) | ||||
|     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}"${msg}`) | ||||
|     core.setOutput('version', version) | ||||
|     core.debug(` Detected version ${version}`) | ||||
|  | ||||
|     // Configure a tag using the identified version | ||||
|     const tag = new Tag( | ||||
|       core.getInput('tag_prefix', { required: false }), | ||||
|       version, | ||||
|       core.getInput('tag_suffix', { required: false }) | ||||
|     ) | ||||
|  | ||||
|     if (isDryRun) { | ||||
|       core.notice(`"${tag.name}" tag was not pushed because the dry_run option was set.`) | ||||
|     } else { | ||||
|       core.info(`Attempting to create ${tag.name} tag.`) | ||||
|     } | ||||
|  | ||||
|     core.setOutput('tagrequested', tag.name) | ||||
|     core.setOutput('prerelease', tag.prerelease ? 'yes' : 'no') | ||||
|     core.setOutput('build', tag.build ? 'yes' : 'no') | ||||
|  | ||||
|     // Check for existance of tag and abort (short circuit) if it already exists. | ||||
|     if (await tag.exists()) { | ||||
|       core.setFailed(`"${tag.name}" tag already exists.` + os.EOL) | ||||
|       core.setOutput('tagname', '') | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     // The tag setter will autocorrect the message if necessary. | ||||
|     tag.message = core.getInput('tag_message', { required: false }).trim() | ||||
|  | ||||
|     if (!isDryRun) { | ||||
|       await tag.push() | ||||
|       core.setOutput('tagcreated', 'yes') | ||||
|     } | ||||
|  | ||||
|     core.setOutput('tagname', tag.name) | ||||
|     core.setOutput('tagsha', tag.sha) | ||||
|     core.setOutput('taguri', tag.uri) | ||||
|     core.setOutput('tagmessage', tag.message) | ||||
|     core.setOutput('tagref', tag.ref) | ||||
|   } catch (error) { | ||||
|     core.warning(error.message) | ||||
|     core.warning(error.stack) | ||||
|     core.setOutput('tagname', '') | ||||
|     core.setOutput('tagsha', '') | ||||
|     core.setOutput('taguri', '') | ||||
|     core.setOutput('tagmessage', '') | ||||
|     core.setOutput('tagref', '') | ||||
|     core.setOutput('tagcreated', 'no') | ||||
|   } | ||||
| } | ||||
|  | ||||
| run() | ||||
							
								
								
									
										22
									
								
								src/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| { | ||||
|   "name": "autotag-action", | ||||
|   "version": "1.1.3", | ||||
|   "private": true, | ||||
|   "description": "Automatically create a tag whenever the version changes in package.json", | ||||
|   "main": "lib/main.js", | ||||
|   "scripts": { | ||||
|     "start": "node ./main.js" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/butlerlogic/action-autotag.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "actions", | ||||
|     "node", | ||||
|     "setup" | ||||
|   ], | ||||
|   "author": "ButlerLogic", | ||||
|   "license": "MIT", | ||||
|   "type": "module" | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Corey Butler
					Corey Butler