semantic-release-additional-tags 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +32 -0
- package/.releaserc +17 -0
- package/CHANGELOG.md +11 -0
- package/README.MD +91 -0
- package/index.js +7 -0
- package/lib/gitlab.js +43 -0
- package/lib/success.js +54 -0
- package/lib/tags.js +17 -0
- package/lib/util.js +74 -0
- package/lib/verify.js +44 -0
- package/package.json +28 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write # to be able to publish a GitHub release
|
|
13
|
+
issues: write # to be able to comment on released issues
|
|
14
|
+
pull-requests: write # to be able to comment on released pull requests
|
|
15
|
+
id-token: write # to enable use of OIDC for npm provenance
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout Code
|
|
18
|
+
uses: actions/checkout@v2
|
|
19
|
+
|
|
20
|
+
- name: Setup Node.js
|
|
21
|
+
uses: actions/setup-node@v2
|
|
22
|
+
with:
|
|
23
|
+
node-version: '20.x'
|
|
24
|
+
|
|
25
|
+
- name: Install Dependencies
|
|
26
|
+
run: npm install
|
|
27
|
+
|
|
28
|
+
- name: Semantic Release
|
|
29
|
+
env:
|
|
30
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
31
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
32
|
+
run: npx semantic-release
|
package/.releaserc
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": ["main"],
|
|
3
|
+
"plugins": [
|
|
4
|
+
"@semantic-release/commit-analyzer",
|
|
5
|
+
"@semantic-release/release-notes-generator",
|
|
6
|
+
"@semantic-release/changelog",
|
|
7
|
+
"@semantic-release/npm",
|
|
8
|
+
"@semantic-release/github",
|
|
9
|
+
[
|
|
10
|
+
"@semantic-release/git",
|
|
11
|
+
{
|
|
12
|
+
"assets": ["package.json", "CHANGELOG.md"],
|
|
13
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
]
|
|
17
|
+
}
|
package/CHANGELOG.md
ADDED
package/README.MD
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# semantic-release-additional-tags
|
|
2
|
+
<a href="https://www.npmjs.com/package/semantic-release-additional-tags">
|
|
3
|
+
<img alt="npm latest version" src="https://img.shields.io/npm/v/semantic-release-additional-tags/latest.svg">
|
|
4
|
+
</a>
|
|
5
|
+
|
|
6
|
+
A plugin for [semantic-relese](https://github.com/semantic-release/semantic-release) inspired by [semantic-release-major-tag](https://www.npmjs.com/package/semantic-release-major-tag) but with the added option to set tags via the gitlab api to support the reuse of protected tags
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Supported Steps
|
|
11
|
+
|
|
12
|
+
### verifyConditions
|
|
13
|
+
|
|
14
|
+
Verifies the provided additionalTags and tests the gitlab credentials if gitlab is enabled
|
|
15
|
+
### success
|
|
16
|
+
|
|
17
|
+
Adds the additional tags to the provided commit
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Installation
|
|
21
|
+
> npm i --save-dev semantic-release-additional-tags
|
|
22
|
+
|
|
23
|
+
or
|
|
24
|
+
> yarn add -D semantic-release-additional-tags
|
|
25
|
+
|
|
26
|
+
And follow the configuration below
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Configuration
|
|
30
|
+
Every option can be set using Environment variable or the plugin config
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```
|
|
34
|
+
{
|
|
35
|
+
"plugins": [
|
|
36
|
+
["semantic-release-additional-tags", {
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
| Config Option | Environment variable | Required | Default | Description |
|
|
46
|
+
|--------------------|----------------------|-------------------------|---------------------|---------------------------------------------------------------------------------------------------------------------------------|
|
|
47
|
+
| `additionalTags` | - | Yes | `[]` | String array containing the additional tags with possible variables `${major}`, `${minor}`, `${patcg}`. Ex.: `v${major}.latest` |
|
|
48
|
+
| `useGitlabApi` | `ADDITIONAL_TAGS_GITLAB` | No | `false` | Option to enable the use of the gitlab api to support retagging of protected tags |
|
|
49
|
+
| `gitlabToken` | `GL_TOKEN` or `GITLAB_TOKEN` | when gitlab api is used | - | Gitlab api token, with permissions to create/delete tags |
|
|
50
|
+
| `commitSha` | `CI_COMMIT_SHA` | when gitlab api is used | - | The referenced commit to be tagged, by default provided by gitlab_ci |
|
|
51
|
+
| - | `CI_API_V4_URL` | No | - | URL of the v4 api endpoint of gitlab, by default provided by gitlab ci |
|
|
52
|
+
| `gitlabUrl` | `GITLAB_URL` | No | `https://gitlab.com` | Base url of the used gitlab instance if `CI_API_V4_URL` is not set |
|
|
53
|
+
|
|
54
|
+
# Example CI Config
|
|
55
|
+
|
|
56
|
+
## Gitlab
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
stages:
|
|
60
|
+
- semantic-release
|
|
61
|
+
semantic-release:
|
|
62
|
+
stage: semantic-release
|
|
63
|
+
image: node
|
|
64
|
+
before_script:
|
|
65
|
+
- npm install semantic-release
|
|
66
|
+
- npm install @semantic-release/git
|
|
67
|
+
- npm install @semantic-release/gitlab
|
|
68
|
+
- npm install semantic-release-additional-tags
|
|
69
|
+
script:
|
|
70
|
+
# Gitlab CI Variables already set in project settings
|
|
71
|
+
# GL_TOKEN
|
|
72
|
+
- |
|
|
73
|
+
cat > .releaserc << EOF
|
|
74
|
+
{
|
|
75
|
+
"branches": ["master", "main"],
|
|
76
|
+
"debug": true,
|
|
77
|
+
"plugins": [
|
|
78
|
+
"@semantic-release/commit-analyzer",
|
|
79
|
+
"@semantic-release/gitlab",
|
|
80
|
+
["semantic-release-additional-tags", {
|
|
81
|
+
"useGitlabApi": true,
|
|
82
|
+
"additionalTags": ["latest", "v${major}.latest", "$v{major}.${minor}.latest"]
|
|
83
|
+
}]
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
EOF
|
|
87
|
+
- semantic-release
|
|
88
|
+
rules:
|
|
89
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
90
|
+
when: always
|
|
91
|
+
```
|
package/index.js
ADDED
package/lib/gitlab.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const axios = require("axios")
|
|
2
|
+
const {getEnvOrConfigOrDefault, getEnvOrConfig} = require("./util")
|
|
3
|
+
|
|
4
|
+
const gitlabRequest = async (pluginConfig, method, url, options = {}) => {
|
|
5
|
+
const gitlabUrl = getEnvOrConfigOrDefault("GITLAB_URL", "gitlabUrl", pluginConfig, "https://gitlab.com")
|
|
6
|
+
|
|
7
|
+
let apiUrl
|
|
8
|
+
|
|
9
|
+
if (process.env['CI_API_V4_URL']) {
|
|
10
|
+
apiUrl = 'CI_API_V4_URL'
|
|
11
|
+
} else {
|
|
12
|
+
apiUrl = gitlabUrl + (gitlabUrl.endsWith("/") ? "" : "/") + "api/v4"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let authToken
|
|
16
|
+
|
|
17
|
+
if (process.env['GL_TOKEN']) {
|
|
18
|
+
authToken = process.env['GL_TOKEN']
|
|
19
|
+
} else if (!(authToken = getEnvOrConfig("GITLAB_TOKEN", "gitlabToken", pluginConfig))) {
|
|
20
|
+
throw new Error("Gitlab Token not supplied(Env: GL_TOKEN/GITLAB_TOKEN, config: gitlabToken)")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let axiosOptions = {
|
|
24
|
+
url: apiUrl + (url.startsWith("/") ? "" : "/") + url,
|
|
25
|
+
method: method,
|
|
26
|
+
validateStatus: () => true,
|
|
27
|
+
...options
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if(!axiosOptions.headers) {
|
|
31
|
+
axiosOptions.headers = {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
axiosOptions.headers["PRIVATE-TOKEN"] = authToken
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
return await axios(axiosOptions)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
gitlabRequest
|
|
43
|
+
}
|
package/lib/success.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const {generateAdditionalTags} = require("./tags")
|
|
2
|
+
const {getEnvOrConfigOrDefault, getEnvOrConfig} = require("./util")
|
|
3
|
+
const {gitlabRequest} = require("./gitlab")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module.exports = async (pluginConfig, { nextRelease: { version }, logger, options: { repositoryUrl } }) => {
|
|
7
|
+
let additionalTags = generateAdditionalTags(pluginConfig, version)
|
|
8
|
+
|
|
9
|
+
logger.log("Adding additional tags", additionalTags)
|
|
10
|
+
|
|
11
|
+
if (getEnvOrConfigOrDefault("ADDITIONAL_TAGS_GITLAB", "useGitlabApi", pluginConfig, false)) {
|
|
12
|
+
const commitSha = getEnvOrConfig("CI_COMMIT_SHA", "commitSha", pluginConfig)
|
|
13
|
+
logger.log(`Using gitlab api to add tags to commit ${commitSha}`)
|
|
14
|
+
const projectID = encodeURI(getEnvOrConfig('CI_PROJECT_ID', 'projectPath', pluginConfig))
|
|
15
|
+
|
|
16
|
+
for (let additionalTag of additionalTags) {
|
|
17
|
+
logger.log(`Processing tag ${additionalTag}`)
|
|
18
|
+
const tagInfo = await gitlabRequest(pluginConfig, 'get', `/projects/${projectID}/repository/tags/${additionalTag}`)
|
|
19
|
+
|
|
20
|
+
if (tagInfo.status == 200) {
|
|
21
|
+
if (tagInfo.data.target == commitSha) {
|
|
22
|
+
logger.log("Tag set to right commit, doing nothing")
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
logger.log(`Deleting old tag ${additionalTag} => ${tagInfo.data.target}`)
|
|
26
|
+
const deleteResponse = await gitlabRequest(pluginConfig, 'delete', `/projects/${projectID}/repository/tags/${additionalTag}`)
|
|
27
|
+
if (deleteResponse.status != 204) {
|
|
28
|
+
throw new Error(`Failed to delete existing tag ${deleteResponse.status}, ${deleteResponse.data}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
logger.log("Adding new tag")
|
|
32
|
+
|
|
33
|
+
const addTagResponse = await gitlabRequest(pluginConfig, 'post', `/projects/${projectID}/repository/tags`, {
|
|
34
|
+
data: {
|
|
35
|
+
tag_name: additionalTag,
|
|
36
|
+
ref: commitSha
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (addTagResponse.status != 201) {
|
|
41
|
+
throw new Error(`Failed to create new tag ${additionalTag}, ${addTagResponse.status}, ${addTagResponse.data}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logger.log(`Tag published: ${additionalTag}`)
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
logger.log("Using git cli implementation")
|
|
48
|
+
|
|
49
|
+
for (let additionalTag of additionalTags) {
|
|
50
|
+
execSync(`git tag --force ${additionalTag}`)
|
|
51
|
+
}
|
|
52
|
+
execSync(`git push ${repositoryUrl} --force --tags`)
|
|
53
|
+
}
|
|
54
|
+
}
|
package/lib/tags.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const {getConfigOrDefault} = require("./util")
|
|
2
|
+
const generateAdditionalTags = (pluginConfig, version) => {
|
|
3
|
+
const [major, minor, patch] = version.split('.')
|
|
4
|
+
|
|
5
|
+
const additionalTags = getConfigOrDefault("additionalTags", pluginConfig, [])
|
|
6
|
+
|
|
7
|
+
return additionalTags.map(additionalTag =>
|
|
8
|
+
additionalTag
|
|
9
|
+
.replace("${major}", major)
|
|
10
|
+
.replace("${minor}", minor)
|
|
11
|
+
.replace("${patch}", patch)
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
generateAdditionalTags
|
|
17
|
+
}
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the value from env oder plugin config. Env has the higher priority. If both dont exist, throws error
|
|
3
|
+
*
|
|
4
|
+
* @param envName environment variable name
|
|
5
|
+
* @param configName config field name
|
|
6
|
+
* @param pluginConfig the plugin config
|
|
7
|
+
* @returns Returns a matching value if it exists, order: env -> config -> error
|
|
8
|
+
*/
|
|
9
|
+
const getEnvOrConfig = (envName, configName, pluginConfig) => {
|
|
10
|
+
if (process.env[envName]) {
|
|
11
|
+
return process.env[envName]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (pluginConfig[configName]) {
|
|
15
|
+
return pluginConfig[configName]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error(`No value found in env(${envName}) or pluginConfig(${configName})`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the value from env oder plugin config. Env has the higher priority. If both dont exist, returns defaultValue
|
|
23
|
+
*
|
|
24
|
+
* @param envName environment variable name
|
|
25
|
+
* @param configName config field name
|
|
26
|
+
* @param pluginConfig the plugin config
|
|
27
|
+
* @param defaultValue fallback value
|
|
28
|
+
* @returns Returns the env var, config value or fallback defaultValue
|
|
29
|
+
*/
|
|
30
|
+
const getEnvOrConfigOrDefault = (envName, configName, pluginConfig, defaultValue) => {
|
|
31
|
+
try {
|
|
32
|
+
return getEnvOrConfig(envName, configName, pluginConfig)
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return defaultValue
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the value from the plugin config, or the default value if the config does not contain the key
|
|
40
|
+
*
|
|
41
|
+
* @param configName
|
|
42
|
+
* @param pluginConfig
|
|
43
|
+
* @param defaultValue
|
|
44
|
+
* @returns {*}
|
|
45
|
+
*/
|
|
46
|
+
const getConfigOrDefault = (configName, pluginConfig, defaultValue) => {
|
|
47
|
+
if (Object.keys(pluginConfig).includes(configName)) {
|
|
48
|
+
return pluginConfig[configName]
|
|
49
|
+
}
|
|
50
|
+
return defaultValue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Unpacks nested field with specific path
|
|
55
|
+
*
|
|
56
|
+
* @param object the source Object
|
|
57
|
+
* @param path the total path joined with '.' ex. fielda.fieldb.fieldd
|
|
58
|
+
* @returns {*} the nest
|
|
59
|
+
*/
|
|
60
|
+
const unwrapObject = (object, path) => {
|
|
61
|
+
for ( let field of path.split(".")) {
|
|
62
|
+
if (object[field]) {
|
|
63
|
+
object = object[field]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return object
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
getEnvOrConfig,
|
|
71
|
+
getEnvOrConfigOrDefault,
|
|
72
|
+
getConfigOrDefault,
|
|
73
|
+
unwrapObject
|
|
74
|
+
}
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const {getEnvOrConfigOrDefault, getEnvOrConfig, unwrapObject, getConfigOrDefault} = require("./util")
|
|
2
|
+
const {gitlabRequest} = require("./gitlab")
|
|
3
|
+
|
|
4
|
+
module.exports = async (pluginConfig, { logger }) => {
|
|
5
|
+
if (getEnvOrConfigOrDefault("ADDITIONAL_TAGS_GITLAB", "useGitlabApi", pluginConfig, false)) {
|
|
6
|
+
logger.log("Gitlab tagging was enabled, testing gitlab Credentials")
|
|
7
|
+
|
|
8
|
+
getEnvOrConfig("CI_COMMIT_SHA", "commitSha", pluginConfig)
|
|
9
|
+
|
|
10
|
+
const response = await gitlabRequest(
|
|
11
|
+
pluginConfig,
|
|
12
|
+
'get',
|
|
13
|
+
`projects/${encodeURI(getEnvOrConfig('CI_PROJECT_ID', 'projectPath', pluginConfig))}`
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if (response.status != 200) {
|
|
17
|
+
logger.log("Failed to authenticate with gitlab", response.status, response.data)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let projectAccess = unwrapObject(response.data, "permissions.project_access.access_level")
|
|
21
|
+
let groupAccess = unwrapObject(response.data, "permissions.group_access.access_level")
|
|
22
|
+
|
|
23
|
+
if ((projectAccess && projectAccess >= 30) || (groupAccess && groupAccess >= 30)) {
|
|
24
|
+
logger.log("Gitlab authentication successful")
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error(`Token does not have the required permissions (projectAccess: ${projectAccess}, groupAccess: ${groupAccess})`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const additionalTags = getConfigOrDefault("additionalTags", pluginConfig, [])
|
|
31
|
+
if (!Array.isArray(additionalTags)) {
|
|
32
|
+
throw new Error("Config field additionalTags should be an array of Strings")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let additionalTag of additionalTags) {
|
|
36
|
+
if (typeof additionalTag !== 'string') {
|
|
37
|
+
throw new Error(`Additional tags should be defined as string, got ${typeof additionalTag} for ${additionalTag}`)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
logger.log(`The following additional tags were configured: ${additionalTags.join(", ")}`)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "semantic-release-additional-tags",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A semantic-release plugin, with the ability to add additional tags like v1.latest, latest and support for gitlab protected tags",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "Kai Fink",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"axios": "^1.6.5"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
13
|
+
"@semantic-release/git": "^10.0.1",
|
|
14
|
+
"@semantic-release/github": "^9.2.6",
|
|
15
|
+
"@semantic-release/npm": "^11.0.2",
|
|
16
|
+
"semantic-release": "^22.0.12"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/4ch3los/semantic-release-additional-tags.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"semantic-release",
|
|
24
|
+
"major-tags",
|
|
25
|
+
"additional-tags",
|
|
26
|
+
"tags"
|
|
27
|
+
]
|
|
28
|
+
}
|