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.
@@ -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
@@ -0,0 +1,11 @@
1
+ # 1.0.0 (2024-01-10)
2
+
3
+
4
+ ### Performance Improvements
5
+
6
+ * Initial release ([85e04e1](https://github.com/4ch3los/semantic-release-additional-tags/commit/85e04e1cf4b2dba2d88cec11ebf6b6c6f1c5e890))
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * Initial commit
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
@@ -0,0 +1,7 @@
1
+ const verifyConditions = require('./lib/verify')
2
+ const success = require('./lib/success')
3
+
4
+ module.exports = {
5
+ verifyConditions,
6
+ success
7
+ }
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
+ }