@cycjimmy/semantic-release-action 6.0.0 → 7.1.1

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/README.md CHANGED
@@ -18,16 +18,16 @@ GitHub Action for [Semantic Release][semantic-url].
18
18
  ```yaml
19
19
  steps:
20
20
  - name: Checkout
21
- uses: actions/checkout@v5
21
+ uses: actions/checkout@v7
22
22
  - name: Semantic Release
23
- uses: cycjimmy/semantic-release-action@v6
23
+ uses: cycjimmy/semantic-release-action@v7
24
24
  env:
25
25
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26
26
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
27
27
  ```
28
28
 
29
29
  **IMPORTANT**: `GITHUB_TOKEN` does not have the required permissions to operate on protected branches.
30
- If you are using this action for protected branches, replace `GITHUB_TOKEN` with [Personal Access Token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). If using the `@semantic-release/git` plugin for protected branches, avoid persisting credentials as part of `actions/checkout@v5` by setting the parameter `persist-credentials: false`. This credential does not have the required permission to operate on protected branches.
30
+ If you are using this action for protected branches, replace `GITHUB_TOKEN` with [Personal Access Token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). If using the `@semantic-release/git` plugin for protected branches, avoid persisting credentials as part of `actions/checkout@v7` by setting the parameter `persist-credentials: false`. This credential does not have the required permission to operate on protected branches.
31
31
 
32
32
  #### Private Packages
33
33
 
@@ -45,27 +45,27 @@ then make sure that you configure this in your `package.json` file:
45
45
  ### Inputs
46
46
  | Input Parameter | Required | Description |
47
47
  |:-----------------:|:--------:|--------------------------------------------------------------------------------------------------------------------------|
48
- | semantic_version | false | Specify version range for semantic-release. [[Details](#semantic_version)] |
49
- | branches | false | The branches on which releases should happen.[[Details](#branches)]<br>Support for **semantic-release above v16**. |
50
- | branch | false | The branch on which releases should happen.[[Details](#branch)]<br>Only support for **semantic-release older than v16**. |
48
+ | semantic_version | false | Specify version range for semantic-release (v16 or above required). [[Details](#semantic_version)] |
49
+ | branches | false | The branches on which releases should happen.[[Details](#branches)]<br>Requires **semantic-release v16 or above**. |
51
50
  | extra_plugins | false | Extra plugins for pre-install. [[Details](#extra_plugins)] |
52
51
  | dry_run | false | Whether to run semantic release in `dry-run` mode. [[Details](#dry_run)] |
53
- | ci | false | Whether to run semantic release with CI support. [[Details](#ci)]<br>Support for **semantic-release above v16**. |
52
+ | ci | false | Whether to run semantic release with CI support. [[Details](#ci)] |
54
53
  | unset_gha_env | false | Whether to unset the GITHUB_ACTIONS environment variable. |
55
54
  | extends | false | Use a sharable configuration [[Details](#extends)] |
56
55
  | working_directory | false | Use another working directory for semantic release [[Details](#working_directory)] |
57
56
  | tag_format | false | Specify format of tag (useful for monorepos) |
58
57
  | repository_url | false | The Git repository url. If no repository url specified, current repository will be used by default. |
58
+ | skip_npm_plugin | false | Whether to skip the @semantic-release/npm default plugin. [[Details](#skip_npm_plugin)]<br>Useful for non-Node.js projects. |
59
59
 
60
60
  #### semantic_version
61
- > {Optional Input Parameter} Specify version range for semantic-release.
61
+ > {Optional Input Parameter} Specify version range for semantic-release.<br>The minimum supported semantic-release version is **v16**; older versions fail with an error.
62
62
 
63
63
  ```yaml
64
64
  steps:
65
65
  - name: Checkout
66
- uses: actions/checkout@v5
66
+ uses: actions/checkout@v7
67
67
  - name: Semantic Release
68
- uses: cycjimmy/semantic-release-action@v6
68
+ uses: cycjimmy/semantic-release-action@v7
69
69
  with:
70
70
  semantic_version: 19.0.5 # It is recommended to specify a version range
71
71
  # for semantic-release when using
@@ -75,20 +75,20 @@ steps:
75
75
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
76
76
  ```
77
77
 
78
- If no version range is specified with `cycjimmy/semantic-release-action@v6` then [semantic-release](https://github.com/semantic-release/semantic-release/) version [24.2.7](https://github.com/semantic-release/semantic-release/releases/tag/v25.0.2) is used.
78
+ If no version range is specified with `cycjimmy/semantic-release-action@v7` then [semantic-release@latest](https://github.com/semantic-release/semantic-release/releases) is used.
79
79
 
80
80
  #### branches
81
- > {Optional Input Parameter} The branches on which releases should happen.<br>`branches` supports for **semantic-release above v16**.
81
+ > {Optional Input Parameter} The branches on which releases should happen.<br>`branches` requires **semantic-release v16 or above**.
82
82
 
83
83
  ```yaml
84
84
  steps:
85
85
  - name: Checkout
86
- uses: actions/checkout@v5
86
+ uses: actions/checkout@v7
87
87
  - name: Semantic Release
88
- uses: cycjimmy/semantic-release-action@v6
88
+ uses: cycjimmy/semantic-release-action@v7
89
89
  with:
90
90
  semantic_version: 16
91
- # you can set branches for semantic-release above v16.
91
+ # you can set branches for semantic-release v16 or above.
92
92
  branches: |
93
93
  [
94
94
  '+([0-9])?(.{+([0-9]),x}).x',
@@ -123,25 +123,7 @@ steps:
123
123
 
124
124
  See [configuration#branches](https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches) for more information.
125
125
 
126
- #### branch
127
- > {Optional Input Parameter} Similar to parameter `branches`. The branch on which releases should happen.<br>`branch` only supports for **semantic-release older than v16**.
128
-
129
- ```yaml
130
- steps:
131
- - name: Checkout
132
- uses: actions/checkout@v5
133
- - name: Semantic Release
134
- uses: cycjimmy/semantic-release-action@v6
135
- with:
136
- semantic_version: 15.13.28
137
- # you can set branch for semantic-release older than v16.
138
- branch: your-branch
139
- env:
140
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
142
- ```
143
-
144
- It will override the `branch` attribute in your configuration file. If the attribute is not configured on both sides, the default is `master`.
126
+ **NOTE**: The `branches` input is evaluated as a JavaScript expression to support the array/object configuration above. Only pass content you trust — anyone able to edit the workflow file can already run arbitrary commands in your CI.
145
127
 
146
128
  #### extra_plugins
147
129
  > {Optional Input Parameter} Extra plugins for pre-install.
@@ -154,9 +136,9 @@ Github Action Workflow:
154
136
  ```yaml
155
137
  steps:
156
138
  - name: Checkout
157
- uses: actions/checkout@v5
139
+ uses: actions/checkout@v7
158
140
  - name: Semantic Release
159
- uses: cycjimmy/semantic-release-action@v6
141
+ uses: cycjimmy/semantic-release-action@v7
160
142
  with:
161
143
  # You can specify specifying version range for the extra plugins if you prefer.
162
144
  extra_plugins: |
@@ -169,6 +151,8 @@ steps:
169
151
 
170
152
  Similar to parameter `semantic_version`. *It is recommended to manually specify a version of semantic-release plugins to prevent errors caused.*
171
153
 
154
+ **NOTE**: `extra_plugins` accepts npm package specs (e.g. `@semantic-release/git@10.0.0` or `@semantic-release/git@>=10.0.0`) separated by whitespace or newlines. Each spec is passed to `npm install` as a single argument without a shell; tokens starting with `-` or containing quotes or similar unsafe characters are ignored and reported as a warning. Because whitespace separates specs, version ranges containing spaces (e.g. `>=1.0.0 <2.0.0`) are not supported. The same rules apply to the `extends` input.
155
+
172
156
  Release Config:
173
157
  ```diff
174
158
  plugins: [
@@ -184,9 +168,9 @@ Release Config:
184
168
  ```yaml
185
169
  steps:
186
170
  - name: Checkout
187
- uses: actions/checkout@v5
171
+ uses: actions/checkout@v7
188
172
  - name: Semantic Release
189
- uses: cycjimmy/semantic-release-action@v6
173
+ uses: cycjimmy/semantic-release-action@v7
190
174
  with:
191
175
  dry_run: true
192
176
  env:
@@ -194,15 +178,17 @@ steps:
194
178
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
195
179
  ```
196
180
 
181
+ **NOTE**: `dry-run` mode still requires write access to the repository. At startup, semantic-release verifies push permission by running `git push --dry-run` ([`verifyAuth`](https://github.com/semantic-release/semantic-release/blob/master/lib/git.js)), and GitHub enforces write access for dry-run pushes as well — nothing is actually pushed. If the credentials cannot push — for example, the default `GITHUB_TOKEN` when the job does not grant `permissions: contents: write` — the action fails with `EGITNOPERMISSION: Cannot push to the Git repository.` even in `dry-run` mode. To fix it, grant write permission to the job, e.g. `permissions: contents: write` (or use a token with write access).
182
+
197
183
  #### ci
198
- > {Optional Input Parameter} Whether to run semantic release with CI support (default true).<br>`ci` supports for **semantic-release above v16**.
184
+ > {Optional Input Parameter} Whether to run semantic release with CI support (default true).
199
185
 
200
186
  ```yaml
201
187
  steps:
202
188
  - name: Checkout
203
- uses: actions/checkout@v5
189
+ uses: actions/checkout@v7
204
190
  - name: Semantic Release
205
- uses: cycjimmy/semantic-release-action@v6
191
+ uses: cycjimmy/semantic-release-action@v7
206
192
  with:
207
193
  ci: false
208
194
  env:
@@ -218,9 +204,9 @@ The action can be used with `extends` option to extend an existing [sharable con
218
204
  ```yaml
219
205
  steps:
220
206
  - name: Checkout
221
- uses: actions/checkout@v5
207
+ uses: actions/checkout@v7
222
208
  - name: Semantic Release
223
- uses: cycjimmy/semantic-release-action@v6
209
+ uses: cycjimmy/semantic-release-action@v7
224
210
  with:
225
211
  # You can extend an existing shareable configuration.
226
212
  # And you can specify version range for the shareable configuration if you prefer.
@@ -238,9 +224,9 @@ This action run semantic release in the github provided workspace by default. Yo
238
224
  ```yaml
239
225
  steps:
240
226
  - name: Checkout
241
- uses: actions/checkout@v5
227
+ uses: actions/checkout@v7
242
228
  - name: Semantic Release
243
- uses: cycjimmy/semantic-release-action@v6
229
+ uses: cycjimmy/semantic-release-action@v7
244
230
  with:
245
231
  # You can select another working directory like a subdirectory for example.
246
232
  working_directory: ./code
@@ -255,9 +241,9 @@ The default tag format on semantic-release is `v{version}`. You can override tha
255
241
  ```yaml
256
242
  steps:
257
243
  - name: Checkout
258
- uses: actions/checkout@v5
244
+ uses: actions/checkout@v7
259
245
  - name: Semantic Release
260
- uses: cycjimmy/semantic-release-action@v6
246
+ uses: cycjimmy/semantic-release-action@v7
261
247
  with:
262
248
  tag_format: custom-v${version}
263
249
  env:
@@ -271,7 +257,7 @@ Setting this to true will unset the `GITHUB_ACTIONS` environment variable. This
271
257
  ```yaml
272
258
  steps:
273
259
  - name: Checkout
274
- uses: actions/checkout@v5
260
+ uses: actions/checkout@v7
275
261
  - name: Temporarily merge PR branch
276
262
  if: ${{ github.event_name == 'pull_request' }}
277
263
  run: |
@@ -279,7 +265,7 @@ steps:
279
265
  git config --global user.email github-actions@github.com
280
266
  git merge --no-ff origin/${{ github.event.pull_request.head.ref }} --message "${{ github.event.pull_request.title }}"
281
267
  - name: Semantic Release
282
- uses: cycjimmy/semantic-release-action@v6
268
+ uses: cycjimmy/semantic-release-action@v7
283
269
  with:
284
270
  unset_gha_env: ${{ github.event_name == 'pull_request' }}
285
271
  ci: ${{ github.event_name == 'pull_request' && false || '' }}
@@ -288,6 +274,31 @@ steps:
288
274
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
289
275
  ```
290
276
 
277
+ #### skip_npm_plugin
278
+ > {Optional Input Parameter} Whether to skip the `@semantic-release/npm` default plugin.<br>Useful for running the action in a project that is not a Node.js project (i.e. without a `package.json` file).
279
+
280
+ ```yaml
281
+ steps:
282
+ - name: Checkout
283
+ uses: actions/checkout@v7
284
+ - name: Semantic Release
285
+ uses: cycjimmy/semantic-release-action@v7
286
+ with:
287
+ skip_npm_plugin: true
288
+ env:
289
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
290
+ ```
291
+
292
+ The `@semantic-release/npm` default plugin requires a `package.json` file in the working directory. Without one, semantic-release fails with `SemanticReleaseError: Missing package.json file.` (ENOPKG) — even in `dry-run` mode. Set `skip_npm_plugin: true` to remove the npm plugin from the default plugins. The default plugins are then replaced with the following list (the [default plugins](https://semantic-release.gitbook.io/semantic-release/usage/plugins#default-plugins) of semantic-release without `@semantic-release/npm`):
293
+
294
+ - `@semantic-release/commit-analyzer`
295
+ - `@semantic-release/release-notes-generator`
296
+ - `@semantic-release/github`
297
+
298
+ Similar to the other options, it will override the `plugins` attribute in your configuration file. If you already maintain your own `plugins` array in your release configuration, you don't need this input — just omit `@semantic-release/npm` from your array.
299
+
300
+ **NOTE**: `NPM_TOKEN` is not required when the npm plugin is skipped. Also note that configuring `npmPublish: false` or `private: true` on the npm plugin does **not** avoid the ENOPKG error, as the plugin checks for a `package.json` file before taking those settings into account. The only way to release a project without a `package.json` file is to not use the npm plugin at all.
301
+
291
302
  ### Outputs
292
303
  | Output Parameter | Description |
293
304
  |:-------------------------:|-----------------------------------------------------------------------------------------------------------------------------------|
@@ -308,9 +319,9 @@ steps:
308
319
  ```yaml
309
320
  steps:
310
321
  - name: Checkout
311
- uses: actions/checkout@v5
322
+ uses: actions/checkout@v7
312
323
  - name: Semantic Release
313
- uses: cycjimmy/semantic-release-action@v6
324
+ uses: cycjimmy/semantic-release-action@v7
314
325
  id: semantic # Need an `id` for output variables
315
326
  env:
316
327
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -325,6 +336,24 @@ steps:
325
336
  echo ${{ steps.semantic.outputs.new_release_patch_version }}
326
337
  ```
327
338
 
339
+ ## Migration Notes
340
+
341
+ ### The `branch` input has been removed
342
+ The `branch` input is no longer supported. Use the `branches` input instead:
343
+
344
+ ```diff
345
+ - name: Semantic Release
346
+ uses: cycjimmy/semantic-release-action@v7
347
+ with:
348
+ - branch: your-branch
349
+ + branches: your-branch
350
+ ```
351
+
352
+ If the `branch` input is still passed, the action prints a deprecation warning and ignores it.
353
+
354
+ ### semantic-release below v16 is no longer supported
355
+ This action now requires **semantic-release v16 or above**. If the installed semantic-release version is older (for example via `semantic_version: 15`), the action fails with an error. Update the `semantic_version` input to v16 or above, or remove it to use the latest version.
356
+
328
357
  ## Changelog
329
358
  See [CHANGELOG][changelog-url].
330
359
 
package/action.yml CHANGED
@@ -7,13 +7,10 @@ branding:
7
7
  inputs:
8
8
  semantic_version:
9
9
  required: false
10
- description: 'Specify specifying version range for semantic-release. If no version range is specified, latest version will be used by default'
10
+ description: 'Specify specifying version range for semantic-release (v16 or above required). If no version range is specified, latest version will be used by default'
11
11
  branches:
12
12
  required: false
13
- description: 'The branches on which releases should happen. It will override the branches attribute in your configuration file. Support for semantic-release above v16. See https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches for more information.'
14
- branch:
15
- required: false
16
- description: 'The branch on which releases should happen. It will override the branch attribute in your configuration file. If the attribute is not configured on both sides, the default is master. Support for semantic-release older than v16.'
13
+ description: 'The branches on which releases should happen. It will override the branches attribute in your configuration file. Requires semantic-release v16 or above. See https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches for more information.'
17
14
  extra_plugins:
18
15
  required: false
19
16
  description: 'Extra plugins for pre-install. You can also specify specifying version range for the extra plugins if you prefer.'
@@ -38,6 +35,9 @@ inputs:
38
35
  repository_url:
39
36
  required: false
40
37
  description: 'The Git repository url. If no repository url specified, current repository will be used by default.'
38
+ skip_npm_plugin:
39
+ required: false
40
+ description: 'Whether to skip the @semantic-release/npm default plugin. Useful for non-Node.js projects without a package.json file. When true, the default plugins are replaced with @semantic-release/commit-analyzer, @semantic-release/release-notes-generator and @semantic-release/github. It will override the plugins attribute in your configuration file.'
41
41
  outputs:
42
42
  new_release_published:
43
43
  description: 'Whether a new release was published'
package/docs/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## [7.1.1](https://github.com/cycjimmy/semantic-release-action/compare/v7.1.0...v7.1.1) (2026-10-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ci:** improve error handling for push permission failures ([895d556](https://github.com/cycjimmy/semantic-release-action/commit/895d556be9f4f2e382d2713f135c582cfd36d619)), closes [#312](https://github.com/cycjimmy/semantic-release-action/issues/312)
7
+
8
+ # [7.1.0](https://github.com/cycjimmy/semantic-release-action/compare/v7.0.0...v7.1.0) (2026-10-09)
9
+
10
+
11
+ ### Features
12
+
13
+ * **core:** add option to skip npm plugin for non-Node.js projects ([d1038bc](https://github.com/cycjimmy/semantic-release-action/commit/d1038bc60313f92bfcba1be105b18a41e4501b18)), closes [#313](https://github.com/cycjimmy/semantic-release-action/issues/313)
14
+
15
+ # [7.0.0](https://github.com/cycjimmy/semantic-release-action/compare/v6.0.0...v7.0.0) (2026-10-09)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **ci:** improve error handling and npm package installation ([b7ca30e](https://github.com/cycjimmy/semantic-release-action/commit/b7ca30e8f5630203651f721e521e114ee905bace))
21
+ * **release:** update GitHub Actions workflow for enhanced permissions and Node.js setup ([45d484c](https://github.com/cycjimmy/semantic-release-action/commit/45d484c29bc15ed2b7cb63b79fab2024d8f5e228))
22
+
23
+
24
+ ### Features
25
+
26
+ * upgrade deps ([3341766](https://github.com/cycjimmy/semantic-release-action/commit/3341766fa2b64a1ede0f9e0fba9d8a63d57f26f2)), closes [#302](https://github.com/cycjimmy/semantic-release-action/issues/302)
27
+
28
+
29
+ ### Performance Improvements
30
+
31
+ * require semantic-release v16 or above and remove branch input ([3d0cb78](https://github.com/cycjimmy/semantic-release-action/commit/3d0cb78a35a8c728aaf92f57448f7a366a501728))
32
+
33
+
34
+ ### BREAKING CHANGES
35
+
36
+ * require semantic-release v16 or above and remove branch input
37
+ * upgrade deps and use module type
38
+
1
39
  # [6.0.0](https://github.com/cycjimmy/semantic-release-action/compare/v5.0.2...v6.0.0) (2025-11-17)
2
40
 
3
41
 
package/index.js CHANGED
@@ -1,5 +1,9 @@
1
- const exec = require('./src/_exec');
2
- const path = require('path');
1
+ import exec from './src/_exec.js';
2
+ import path, {dirname} from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
3
7
 
4
8
  const run = async () => {
5
9
  // Install Dependencies
@@ -13,7 +17,13 @@ const run = async () => {
13
17
  }
14
18
  }
15
19
 
16
- require('./src/index')();
20
+ const mod = await import('./src/index.js');
21
+ await mod.default();
17
22
  };
18
23
 
19
- run().catch(console.error);
24
+ run().catch((error) => {
25
+ console.error(error instanceof Error ? error.stack : error);
26
+
27
+ // Exit with a non-zero code so the workflow step is marked as failed
28
+ process.exitCode = 1;
29
+ });
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@cycjimmy/semantic-release-action",
3
- "version": "6.0.0",
3
+ "version": "7.1.1",
4
4
  "description": "GitHub Action for Semantic Release",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "scripts": {
7
8
  "test": "echo \"Error: no test specified\" && exit 1"
8
9
  },
@@ -21,11 +22,10 @@
21
22
  },
22
23
  "homepage": "https://github.com/cycjimmy/semantic-release-action#readme",
23
24
  "dependencies": {
24
- "@actions/core": "^1.11.1",
25
- "@actions/io": "^2.0.0",
26
- "@cycjimmy/awesome-js-funcs": "^4.0.9",
27
- "@semantic-release/changelog": "^6.0.3",
28
- "@semantic-release/git": "^10.0.1",
29
- "semantic-release": "^25.0.2"
25
+ "@actions/core": "^3.0.1",
26
+ "@actions/io": "^3.0.2",
27
+ "@semantic-release/changelog": "^7.0.0",
28
+ "@semantic-release/git": "^11.0.1",
29
+ "semantic-release": "^25.0.9"
30
30
  }
31
31
  }
package/src/_exec.js CHANGED
@@ -1,2 +1,4 @@
1
- const util = require('util');
2
- module.exports = util.promisify(require('child_process').exec);
1
+ import { promisify } from 'util';
2
+ import { exec } from 'child_process';
3
+
4
+ export default promisify(exec);
@@ -0,0 +1,22 @@
1
+ import * as core from '@actions/core';
2
+
3
+ const MINIMUM_MAJOR_VERSION = 16;
4
+
5
+ /**
6
+ * Assert the installed semantic-release version is supported (v16 or above)
7
+ * @returns {Promise<void>}
8
+ * @throws {Error} when the installed semantic-release major version is below the minimum
9
+ */
10
+ export default async () => {
11
+ const { default: { version } } = await import('semantic-release/package.json', { with: { type: 'json' } });
12
+ const majorVersion = Number(version.replace(/\..+/g, ''));
13
+ core.debug(`semantic-release version: ${version}`);
14
+
15
+ if (Number.isNaN(majorVersion) || majorVersion < MINIMUM_MAJOR_VERSION) {
16
+ throw new Error(
17
+ `Unsupported semantic-release version: ${version}. ` +
18
+ `This action requires semantic-release v${MINIMUM_MAJOR_VERSION} or above. ` +
19
+ `Update the 'semantic_version' input to v${MINIMUM_MAJOR_VERSION} or above, or remove it to use the latest version.`
20
+ );
21
+ }
22
+ };
@@ -1,9 +1,9 @@
1
- const io = require('@actions/io');
1
+ import * as io from '@actions/io';
2
2
 
3
3
  /**
4
4
  * Clean up `.npmrc` file in the repo after releasing
5
5
  * @returns {Promise<never>}
6
6
  */
7
- module.exports = async () => {
7
+ export default async () => {
8
8
  await io.rmRF('.npmrc');
9
9
  };
@@ -0,0 +1,32 @@
1
+ import * as core from '@actions/core';
2
+
3
+ const EGITNOPERMISSION_HINT =
4
+ `Push permission check failed (EGITNOPERMISSION). If you use the default GITHUB_TOKEN, ` +
5
+ `grant write access to the job, e.g. "permissions: contents: write". ` +
6
+ `This is also required in dry-run mode: semantic-release verifies push permission with "git push --dry-run". ` +
7
+ `See https://github.com/cycjimmy/semantic-release-action#dry_run`;
8
+
9
+ /**
10
+ * Check whether an error, or any aggregated sub-error, has the given code.
11
+ * @param {Error|String} error
12
+ * @param {String} code
13
+ * @returns {Boolean}
14
+ */
15
+ const hasErrorCode = (error, code) =>
16
+ error?.code === code ||
17
+ (Array.isArray(error?.errors) && error.errors.some((subError) => hasErrorCode(subError, code)));
18
+
19
+ /**
20
+ * Report a failed release to GitHub Actions, adding extra hints for known semantic-release error codes.
21
+ * @param {Error|String} error
22
+ * @returns {void}
23
+ */
24
+ const handleError = (error) => {
25
+ if (hasErrorCode(error, 'EGITNOPERMISSION')) {
26
+ core.warning(EGITNOPERMISSION_HINT);
27
+ }
28
+
29
+ core.setFailed(error);
30
+ };
31
+
32
+ export default handleError;
@@ -1,51 +1,69 @@
1
- const core = require('@actions/core');
2
- const stringToJson = require('@cycjimmy/awesome-js-funcs/cjs/typeConversion/stringToJson.cjs').default;
3
- const inputs = require('./inputs.json');
1
+ import * as core from '@actions/core';
2
+ import fs from 'fs';
3
+ import stringToJson from './stringToJson.js';
4
+ import inputs from './inputs.json' with { type: 'json' };
4
5
 
5
6
  /**
6
7
  * Handle Branches Option
7
- * @returns {{}|{branch: string}}
8
+ * @returns {{}|{branches: string|Array}}
8
9
  */
9
- exports.handleBranchesOption = () => {
10
+ export const handleBranchesOption = () => {
10
11
  const branchesOption = {};
11
12
  const branches = core.getInput(inputs.branches);
12
- const branch = core.getInput(inputs.branch);
13
-
14
13
  core.debug(`branches input: ${branches}`);
15
- core.debug(`branch input: ${branch}`);
16
14
 
17
- const semanticVersion = require('semantic-release/package.json').version;
18
- const semanticMajorVersion = Number(semanticVersion.replace(/\..+/g, ''));
19
- core.debug(`semanticMajorVersion: ${semanticMajorVersion}`);
15
+ if (!branches) {
16
+ return branchesOption;
17
+ }
20
18
 
21
- // older than v16
22
- if (semanticMajorVersion < 16) {
23
- if (!branch) {
24
- return branchesOption;
25
- }
19
+ const jsonOrStr = stringToJson('' + branches);
20
+ core.debug(`Converted branches attribute: ${JSON.stringify(jsonOrStr)}`);
21
+ branchesOption.branches = jsonOrStr;
22
+ return branchesOption;
23
+ };
26
24
 
27
- branchesOption.branch = branch;
28
- return branchesOption;
25
+ /**
26
+ * Warn about removed legacy inputs. The `branch` input is no longer declared
27
+ * in action.yml; the runner still exports it as INPUT_BRANCH when users pass it.
28
+ */
29
+ export const warnDeprecatedInputs = () => {
30
+ // hardcoded: 'branch' was removed from inputs.json
31
+ const branch = core.getInput('branch');
32
+
33
+ if (branch) {
34
+ core.warning(
35
+ `The 'branch' input is no longer supported and will be ignored. ` +
36
+ `Use the 'branches' input instead (requires semantic-release v16 or above). ` +
37
+ `See https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches`
38
+ );
29
39
  }
40
+ };
30
41
 
31
- // above v16
32
- const strNeedConvertToJson = branches || branch || '';
42
+ /**
43
+ * Warn when the working directory has no package.json and the npm default
44
+ * plugin is not skipped: @semantic-release/npm fails with ENOPKG in that case.
45
+ */
46
+ export const warnIfNoPackageJson = () => {
47
+ const skipNpmPlugin = core.getInput(inputs.skip_npm_plugin) === 'true';
33
48
 
34
- if (!strNeedConvertToJson) {
35
- return branchesOption;
49
+ if (skipNpmPlugin || fs.existsSync('package.json')) {
50
+ return;
36
51
  }
37
52
 
38
- const jsonOrStr = stringToJson('' + strNeedConvertToJson);
39
- core.debug(`Converted branches attribute: ${JSON.stringify(jsonOrStr)}`);
40
- branchesOption.branches = jsonOrStr;
41
- return branchesOption;
53
+ core.warning(
54
+ `No package.json file found in ${process.cwd()}. ` +
55
+ `The default @semantic-release/npm plugin requires one and will fail with ` +
56
+ `"Missing package.json file." (ENOPKG). ` +
57
+ `If this is not a Node.js project, set the 'skip_npm_plugin' input to true, ` +
58
+ `or define a custom 'plugins' array without @semantic-release/npm in your release configuration.`
59
+ );
42
60
  };
43
61
 
44
62
  /**
45
63
  * Handle DryRun Option
46
64
  * @returns {{}|{dryRun: boolean}}
47
65
  */
48
- exports.handleDryRunOption = () => {
66
+ export const handleDryRunOption = () => {
49
67
  const dryRun = core.getInput(inputs.dry_run);
50
68
  core.debug(`dryRun input: ${dryRun}`);
51
69
 
@@ -65,7 +83,7 @@ exports.handleDryRunOption = () => {
65
83
  * Handle Ci Option
66
84
  * @returns {{}|{ci: boolean}}
67
85
  */
68
- exports.handleCiOption = () => {
86
+ export const handleCiOption = () => {
69
87
  const ci = core.getInput(inputs.ci);
70
88
  core.debug(`ci input: ${ci}`);
71
89
 
@@ -85,7 +103,7 @@ exports.handleCiOption = () => {
85
103
  * Handle Extends Option
86
104
  * @returns {{}|{extends: Array}|{extends: String}}
87
105
  */
88
- exports.handleExtends = () => {
106
+ export const handleExtends = () => {
89
107
  const extend = core.getInput(inputs.extends);
90
108
  core.debug(`extend input: ${extend}`);
91
109
 
@@ -104,9 +122,9 @@ exports.handleExtends = () => {
104
122
  * Handle TagFormat Option
105
123
  * @returns {{}|{tagFormat: String}}
106
124
  */
107
- exports.handleTagFormat = () => {
125
+ export const handleTagFormat = () => {
108
126
  const tagFormat = core.getInput(inputs.tag_format);
109
- core.debug(`citagFormat input: ${tagFormat}`);
127
+ core.debug(`tagFormat input: ${tagFormat}`);
110
128
 
111
129
  if (tagFormat) {
112
130
  return {
@@ -119,15 +137,39 @@ exports.handleTagFormat = () => {
119
137
 
120
138
  /**
121
139
  * Handle repository-url Option
122
- * @returns {{}|{r: String}}
140
+ * @returns {{}|{repositoryUrl: String}}
123
141
  */
124
- exports.handleRepositoryUrlOption = () => {
142
+ export const handleRepositoryUrlOption = () => {
125
143
  const repositoryUrl = core.getInput(inputs.repository_url);
126
144
  core.debug(`repository_url input: ${repositoryUrl}`);
127
145
 
128
146
  if (repositoryUrl) {
129
- return { r: repositoryUrl };
147
+ return { repositoryUrl };
130
148
  } else {
131
149
  return {};
132
150
  }
133
151
  };
152
+
153
+ /**
154
+ * Handle SkipNpmPlugin Option
155
+ * @returns {{}|{plugins: string[]}}
156
+ */
157
+ export const handleSkipNpmPluginOption = () => {
158
+ const skipNpmPlugin = core.getInput(inputs.skip_npm_plugin);
159
+ core.debug(`skip_npm_plugin input: ${skipNpmPlugin}`);
160
+
161
+ switch (skipNpmPlugin) {
162
+ case 'true':
163
+ return {
164
+ plugins: [
165
+ '@semantic-release/commit-analyzer',
166
+ '@semantic-release/release-notes-generator',
167
+ '@semantic-release/github'
168
+ ]
169
+ };
170
+
171
+ case 'false':
172
+ default:
173
+ return {};
174
+ }
175
+ };
package/src/index.js CHANGED
@@ -1,18 +1,23 @@
1
- const core = require('@actions/core');
2
- const {
1
+ import * as core from '@actions/core';
2
+ import {
3
+ warnDeprecatedInputs,
4
+ warnIfNoPackageJson,
3
5
  handleBranchesOption,
4
6
  handleDryRunOption,
5
7
  handleCiOption,
6
8
  handleExtends,
7
9
  handleTagFormat,
8
10
  handleRepositoryUrlOption,
9
- } = require('./handleOptions');
10
- const setUpJob = require('./setUpJob.task');
11
- const installSpecifyingVersionSemantic = require('./installSpecifyingVersionSemantic.task');
12
- const preInstall = require('./preInstall.task');
13
- const cleanupNpmrc = require('./cleanupNpmrc.task');
14
- const windUpJob = require('./windUpJob.task');
15
- const inputs = require('./inputs.json');
11
+ handleSkipNpmPluginOption,
12
+ } from './handleOptions.js';
13
+ import handleError from './handleError.js';
14
+ import setUpJob from './setUpJob.task.js';
15
+ import installSpecifyingVersionSemantic from './installSpecifyingVersionSemantic.task.js';
16
+ import assertSemanticReleaseVersion from './assertSemanticReleaseVersion.task.js';
17
+ import preInstall from './preInstall.task.js';
18
+ import cleanupNpmrc from './cleanupNpmrc.task.js';
19
+ import windUpJob from './windUpJob.task.js';
20
+ import inputs from './inputs.json' with { type: 'json' };
16
21
 
17
22
  /**
18
23
  * Release main task
@@ -23,7 +28,10 @@ const release = async () => {
23
28
  process.chdir(core.getInput(inputs.working_directory));
24
29
  }
25
30
  await setUpJob();
31
+ warnDeprecatedInputs();
32
+ warnIfNoPackageJson();
26
33
  await installSpecifyingVersionSemantic();
34
+ await assertSemanticReleaseVersion();
27
35
  await preInstall(core.getInput(inputs.extra_plugins));
28
36
  await preInstall(core.getInput(inputs.extends));
29
37
 
@@ -33,20 +41,22 @@ const release = async () => {
33
41
  }
34
42
 
35
43
  const semanticRelease = await import('semantic-release');
44
+ const branchesOption = handleBranchesOption();
36
45
  const result = await semanticRelease.default({
37
- ...handleBranchesOption(),
46
+ ...branchesOption,
38
47
  ...handleDryRunOption(),
39
48
  ...handleCiOption(),
40
49
  ...handleExtends(),
41
50
  ...handleTagFormat(),
42
- ...handleRepositoryUrlOption()
51
+ ...handleRepositoryUrlOption(),
52
+ ...handleSkipNpmPluginOption()
43
53
  });
44
54
 
45
55
  await cleanupNpmrc();
46
56
  await windUpJob(result);
47
57
  };
48
58
 
49
- module.exports = () => {
59
+ export default async () => {
50
60
  core.debug('Initialization successful');
51
- release().catch(core.setFailed);
61
+ release().catch(handleError);
52
62
  };
package/src/inputs.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "semantic_version": "semantic_version",
3
3
  "branches": "branches",
4
- "branch": "branch",
5
4
  "extra_plugins": "extra_plugins",
6
5
  "dry_run": "dry_run",
7
6
  "ci": "ci",
@@ -9,5 +8,6 @@
9
8
  "extends": "extends",
10
9
  "working_directory": "working_directory",
11
10
  "tag_format": "tag_format",
12
- "repository_url": "repository_url"
11
+ "repository_url": "repository_url",
12
+ "skip_npm_plugin": "skip_npm_plugin"
13
13
  }
@@ -1,21 +1,36 @@
1
- const path = require('path');
2
- const core = require('@actions/core');
3
- const exec = require('./_exec');
4
- const inputs = require('./inputs.json');
1
+ import * as core from '@actions/core';
2
+ import runNpm from './runNpm.js';
3
+ import isNpmSpec from './isNpmSpec.js';
4
+ import inputs from './inputs.json' with { type: 'json' };
5
+ import path, {dirname} from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
5
10
 
6
11
  /**
7
12
  * Install Specifying Version semantic-release
8
13
  * @returns {Promise<void>}
9
14
  */
10
- module.exports = async () => {
15
+ export default async () => {
11
16
  const semantic_version = core.getInput(inputs.semantic_version);
17
+
18
+ // Reject implausible version specs early (also prevents silently falling
19
+ // back to "latest"); the spec is passed to npm without a shell
20
+ if (semantic_version && !isNpmSpec(semantic_version)) {
21
+ throw new Error(`Invalid semantic_version input: ${semantic_version}`);
22
+ }
23
+
12
24
  const versionSuffix = semantic_version
13
25
  ? `@${semantic_version}`
14
26
  : '';
15
27
 
16
- const {stdout, stderr} = await exec(`npm install semantic-release${versionSuffix} --no-audit --silent`, {
17
- cwd: path.resolve(__dirname, '..')
18
- });
28
+ const {stdout, stderr} = await runNpm(
29
+ ['install', `semantic-release${versionSuffix}`, '--no-audit', '--silent'],
30
+ {
31
+ cwd: path.resolve(__dirname, '..')
32
+ }
33
+ );
19
34
  core.debug(stdout);
20
35
  core.error(stderr);
21
36
  };
@@ -0,0 +1,8 @@
1
+ // isNpmSpec.js
2
+ // A plausible npm package/version spec: any characters except whitespace,
3
+ // quotes and other shell-like punctuation (kept as defense in depth even
4
+ // though the install commands run without a shell). A token starting with
5
+ // `-` is rejected so it can never be read as an npm option/flag.
6
+ const NPM_SPEC_PATTERN = /^[^\s'"`$;&(){}]+$/;
7
+
8
+ export default (spec) => !spec.startsWith('-') && NPM_SPEC_PATTERN.test(spec);
@@ -1,20 +1,48 @@
1
- const path = require('path');
2
- const core = require('@actions/core');
3
- const exec = require('./_exec');
1
+ import path, {dirname} from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import * as core from '@actions/core';
4
+ import runNpm from './runNpm.js';
5
+ import isNpmSpec from './isNpmSpec.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
4
9
 
5
10
  /**
6
11
  * Pre-install extra dependecies
7
12
  * @returns {Promise<void>}
8
13
  */
9
- module.exports = async extras => {
14
+ export default async extras => {
10
15
  if (!extras) {
11
16
  return Promise.resolve();
12
17
  }
13
18
 
14
- const _extras = extras.replace(/['"]/g, '').replace(/[\n\r]/g, ' ');
19
+ // Split into tokens and keep only plausible npm package specs: specs are
20
+ // passed to npm without a shell, and tokens starting with `-` are dropped
21
+ // so they can never be read as npm options
22
+ const extrasList = extras
23
+ .replace(/['"]/g, '')
24
+ .split(/\s+/)
25
+ .filter(Boolean);
26
+ const validSpecs = extrasList.filter((spec) => isNpmSpec(spec));
27
+ const invalidTokens = extrasList.filter((spec) => !isNpmSpec(spec));
28
+
29
+ if (invalidTokens.length) {
30
+ core.warning(`Ignored invalid package specs: ${invalidTokens.join(', ')}`);
31
+ }
32
+
33
+ if (!validSpecs.length) {
34
+ return Promise.resolve();
35
+ }
36
+
37
+ core.debug(`Installing extra packages: ${validSpecs.join(', ')}`);
38
+
15
39
  const silentFlag = process.env.RUNNER_DEBUG === '1' ? '' : '--silent';
40
+ const args = ['install', ...validSpecs, '--no-audit'];
41
+ if (silentFlag) {
42
+ args.push(silentFlag);
43
+ }
16
44
 
17
- const { stdout, stderr } = await exec(`npm install ${_extras} --no-audit ${silentFlag}`, {
45
+ const { stdout, stderr } = await runNpm(args, {
18
46
  cwd: path.resolve(__dirname, '..')
19
47
  });
20
48
  core.debug(stdout);
package/src/runNpm.js ADDED
@@ -0,0 +1,50 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { promisify } from 'util';
4
+ import { execFile } from 'child_process';
5
+
6
+ const execFileAsync = promisify(execFile);
7
+
8
+ // npm's cli entry bundled with the running Node distribution. Running npm
9
+ // through the current Node executable keeps the invocation shell-free on
10
+ // every platform (spawning `npm.cmd` on Windows would require a shell).
11
+ const resolveNpmCliPath = () => {
12
+ const nodeDir = path.dirname(process.execPath);
13
+
14
+ // Layout of the official Node distributions:
15
+ // - Windows: <node-dir>/node_modules/npm/bin/npm-cli.js
16
+ // - POSIX: <node-dir>/../lib/node_modules/npm/bin/npm-cli.js
17
+ const npmCliPath = process.platform === 'win32'
18
+ ? path.join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js')
19
+ : path.join(nodeDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js');
20
+
21
+ return fs.existsSync(npmCliPath) ? npmCliPath : '';
22
+ };
23
+
24
+ /**
25
+ * Run npm with the given argument list without a shell.
26
+ * Every argument is passed as a single argv element, so user-provided
27
+ * package specs can never be interpreted as shell commands.
28
+ * @param {string[]} args
29
+ * @param {Object} [options] child_process options (e.g. cwd)
30
+ * @returns {Promise<{stdout: string, stderr: string}>}
31
+ */
32
+ export default async (args, options = {}) => {
33
+ const npmCliPath = resolveNpmCliPath();
34
+
35
+ if (npmCliPath) {
36
+ return execFileAsync(process.execPath, [npmCliPath, ...args], options);
37
+ }
38
+
39
+ if (process.platform === 'win32') {
40
+ // `npm` on Windows is npm.cmd, which cannot be spawned without a shell.
41
+ // Fail with a clear message instead of an opaque ENOENT.
42
+ throw new Error(
43
+ 'Unable to locate npm-cli.js for the current Node installation on Windows. ' +
44
+ 'semantic-release-action requires Node to be installed with npm bundled.'
45
+ );
46
+ }
47
+
48
+ // Non-Windows fallback: plain `npm` executable from PATH
49
+ return execFileAsync('npm', args, options);
50
+ };
@@ -1,17 +1,22 @@
1
- const path = require('path');
2
- const core = require('@actions/core');
3
- const outputs = require('./outputs.json');
1
+ import path, { dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import * as core from '@actions/core';
4
+ import outputs from './outputs.json' with { type: 'json' };
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
4
8
 
5
9
  /**
6
10
  * setUpJob
7
11
  * @returns {Promise<void>}
8
12
  */
9
- module.exports = async () => {
10
- // set outputs default
13
+ export default async () => {
14
+ // set defaults for all outputs so they always exist
15
+ Object.values(outputs).forEach((output) => {
16
+ core.setOutput(output, '');
17
+ });
11
18
  core.setOutput(outputs.new_release_published, 'false');
12
19
 
13
20
  core.debug('action_workspace: ' + path.resolve(__dirname, '..'));
14
21
  core.debug('process.cwd: ' + process.cwd());
15
-
16
- return Promise.resolve();
17
22
  };
@@ -0,0 +1,15 @@
1
+ // stringToJson.js
2
+ // NOTE: The input string is evaluated as a JavaScript expression. This is
3
+ // required for backward compatibility with documented `branches` configs
4
+ // which use JS syntax (single-quoted strings, unquoted object keys), so it
5
+ // must only be fed with trusted input from the workflow file.
6
+ const strToJsonFunc = (str) => (new Function(`return ${str}`))();
7
+ const strToJson = (str) => {
8
+ try {
9
+ return strToJsonFunc(str);
10
+ } catch (e) {
11
+ return str;
12
+ }
13
+ };
14
+
15
+ export default (str) => strToJson(str);
@@ -1,14 +1,13 @@
1
- const core = require('@actions/core');
2
- const outputs = require('./outputs.json');
1
+ import * as core from '@actions/core';
2
+ import outputs from './outputs.json' with { type: 'json' };
3
3
 
4
4
  /**
5
5
  * windUpJob
6
6
  * @param result
7
7
  * @returns {Promise<void>}
8
8
  */
9
- module.exports = async (result) => {
10
- const resolved = await result;
11
- if (!resolved) {
9
+ export default async (result) => {
10
+ if (!result) {
12
11
  core.debug('No release published.');
13
12
  return;
14
13
  }
@@ -18,7 +17,7 @@ module.exports = async (result) => {
18
17
  commits = [],
19
18
  nextRelease = {},
20
19
  releases = [],
21
- } = resolved;
20
+ } = result;
22
21
 
23
22
  if (lastRelease?.version) {
24
23
  core.debug(`The last release was "${lastRelease.version}".`);
@@ -48,7 +47,7 @@ module.exports = async (result) => {
48
47
  core.setOutput(outputs.new_release_minor_version, minor);
49
48
  core.setOutput(outputs.new_release_patch_version, patch);
50
49
  core.setOutput(outputs.new_release_channel, channel);
51
- core.setOutput(outputs.new_release_notes, notes);
50
+ core.setOutput(outputs.new_release_notes, notes);
52
51
  core.setOutput(outputs.new_release_git_head, gitHead);
53
52
  core.setOutput(outputs.new_release_git_tag, gitTag);
54
53
  };