@cycjimmy/semantic-release-action 5.0.2 → 7.1.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/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@v5
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@v5
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@v5` then [semantic-release](https://github.com/semantic-release/semantic-release/) version [24.2.7](https://github.com/semantic-release/semantic-release/releases/tag/v24.2.7) 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@v5
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@v5
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@v5
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@v5
173
+ uses: cycjimmy/semantic-release-action@v7
190
174
  with:
191
175
  dry_run: true
192
176
  env:
@@ -195,14 +179,14 @@ steps:
195
179
  ```
196
180
 
197
181
  #### ci
198
- > {Optional Input Parameter} Whether to run semantic release with CI support (default true).<br>`ci` supports for **semantic-release above v16**.
182
+ > {Optional Input Parameter} Whether to run semantic release with CI support (default true).
199
183
 
200
184
  ```yaml
201
185
  steps:
202
186
  - name: Checkout
203
- uses: actions/checkout@v5
187
+ uses: actions/checkout@v7
204
188
  - name: Semantic Release
205
- uses: cycjimmy/semantic-release-action@v5
189
+ uses: cycjimmy/semantic-release-action@v7
206
190
  with:
207
191
  ci: false
208
192
  env:
@@ -218,9 +202,9 @@ The action can be used with `extends` option to extend an existing [sharable con
218
202
  ```yaml
219
203
  steps:
220
204
  - name: Checkout
221
- uses: actions/checkout@v5
205
+ uses: actions/checkout@v7
222
206
  - name: Semantic Release
223
- uses: cycjimmy/semantic-release-action@v5
207
+ uses: cycjimmy/semantic-release-action@v7
224
208
  with:
225
209
  # You can extend an existing shareable configuration.
226
210
  # And you can specify version range for the shareable configuration if you prefer.
@@ -238,9 +222,9 @@ This action run semantic release in the github provided workspace by default. Yo
238
222
  ```yaml
239
223
  steps:
240
224
  - name: Checkout
241
- uses: actions/checkout@v5
225
+ uses: actions/checkout@v7
242
226
  - name: Semantic Release
243
- uses: cycjimmy/semantic-release-action@v5
227
+ uses: cycjimmy/semantic-release-action@v7
244
228
  with:
245
229
  # You can select another working directory like a subdirectory for example.
246
230
  working_directory: ./code
@@ -255,9 +239,9 @@ The default tag format on semantic-release is `v{version}`. You can override tha
255
239
  ```yaml
256
240
  steps:
257
241
  - name: Checkout
258
- uses: actions/checkout@v5
242
+ uses: actions/checkout@v7
259
243
  - name: Semantic Release
260
- uses: cycjimmy/semantic-release-action@v5
244
+ uses: cycjimmy/semantic-release-action@v7
261
245
  with:
262
246
  tag_format: custom-v${version}
263
247
  env:
@@ -271,7 +255,7 @@ Setting this to true will unset the `GITHUB_ACTIONS` environment variable. This
271
255
  ```yaml
272
256
  steps:
273
257
  - name: Checkout
274
- uses: actions/checkout@v5
258
+ uses: actions/checkout@v7
275
259
  - name: Temporarily merge PR branch
276
260
  if: ${{ github.event_name == 'pull_request' }}
277
261
  run: |
@@ -279,7 +263,7 @@ steps:
279
263
  git config --global user.email github-actions@github.com
280
264
  git merge --no-ff origin/${{ github.event.pull_request.head.ref }} --message "${{ github.event.pull_request.title }}"
281
265
  - name: Semantic Release
282
- uses: cycjimmy/semantic-release-action@v5
266
+ uses: cycjimmy/semantic-release-action@v7
283
267
  with:
284
268
  unset_gha_env: ${{ github.event_name == 'pull_request' }}
285
269
  ci: ${{ github.event_name == 'pull_request' && false || '' }}
@@ -288,6 +272,31 @@ steps:
288
272
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
289
273
  ```
290
274
 
275
+ #### skip_npm_plugin
276
+ > {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).
277
+
278
+ ```yaml
279
+ steps:
280
+ - name: Checkout
281
+ uses: actions/checkout@v7
282
+ - name: Semantic Release
283
+ uses: cycjimmy/semantic-release-action@v7
284
+ with:
285
+ skip_npm_plugin: true
286
+ env:
287
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
288
+ ```
289
+
290
+ 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`):
291
+
292
+ - `@semantic-release/commit-analyzer`
293
+ - `@semantic-release/release-notes-generator`
294
+ - `@semantic-release/github`
295
+
296
+ 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.
297
+
298
+ **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.
299
+
291
300
  ### Outputs
292
301
  | Output Parameter | Description |
293
302
  |:-------------------------:|-----------------------------------------------------------------------------------------------------------------------------------|
@@ -308,9 +317,9 @@ steps:
308
317
  ```yaml
309
318
  steps:
310
319
  - name: Checkout
311
- uses: actions/checkout@v5
320
+ uses: actions/checkout@v7
312
321
  - name: Semantic Release
313
- uses: cycjimmy/semantic-release-action@v5
322
+ uses: cycjimmy/semantic-release-action@v7
314
323
  id: semantic # Need an `id` for output variables
315
324
  env:
316
325
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -325,6 +334,24 @@ steps:
325
334
  echo ${{ steps.semantic.outputs.new_release_patch_version }}
326
335
  ```
327
336
 
337
+ ## Migration Notes
338
+
339
+ ### The `branch` input has been removed
340
+ The `branch` input is no longer supported. Use the `branches` input instead:
341
+
342
+ ```diff
343
+ - name: Semantic Release
344
+ uses: cycjimmy/semantic-release-action@v7
345
+ with:
346
+ - branch: your-branch
347
+ + branches: your-branch
348
+ ```
349
+
350
+ If the `branch` input is still passed, the action prints a deprecation warning and ignores it.
351
+
352
+ ### semantic-release below v16 is no longer supported
353
+ 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.
354
+
328
355
  ## Changelog
329
356
  See [CHANGELOG][changelog-url].
330
357
 
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,46 @@
1
+ # [7.1.0](https://github.com/cycjimmy/semantic-release-action/compare/v7.0.0...v7.1.0) (2026-10-09)
2
+
3
+
4
+ ### Features
5
+
6
+ * **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)
7
+
8
+ # [7.0.0](https://github.com/cycjimmy/semantic-release-action/compare/v6.0.0...v7.0.0) (2026-10-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** improve error handling and npm package installation ([b7ca30e](https://github.com/cycjimmy/semantic-release-action/commit/b7ca30e8f5630203651f721e521e114ee905bace))
14
+ * **release:** update GitHub Actions workflow for enhanced permissions and Node.js setup ([45d484c](https://github.com/cycjimmy/semantic-release-action/commit/45d484c29bc15ed2b7cb63b79fab2024d8f5e228))
15
+
16
+
17
+ ### Features
18
+
19
+ * upgrade deps ([3341766](https://github.com/cycjimmy/semantic-release-action/commit/3341766fa2b64a1ede0f9e0fba9d8a63d57f26f2)), closes [#302](https://github.com/cycjimmy/semantic-release-action/issues/302)
20
+
21
+
22
+ ### Performance Improvements
23
+
24
+ * require semantic-release v16 or above and remove branch input ([3d0cb78](https://github.com/cycjimmy/semantic-release-action/commit/3d0cb78a35a8c728aaf92f57448f7a366a501728))
25
+
26
+
27
+ ### BREAKING CHANGES
28
+
29
+ * require semantic-release v16 or above and remove branch input
30
+ * upgrade deps and use module type
31
+
32
+ # [6.0.0](https://github.com/cycjimmy/semantic-release-action/compare/v5.0.2...v6.0.0) (2025-11-17)
33
+
34
+
35
+ ### Features
36
+
37
+ * **deps:** update semantic-release to version 25 ([9246c0b](https://github.com/cycjimmy/semantic-release-action/commit/9246c0bd17405b1f20450501f39234912258bc50))
38
+
39
+
40
+ ### BREAKING CHANGES
41
+
42
+ * **deps:** Update semantic-release to version 25
43
+
1
44
  ## [5.0.2](https://github.com/cycjimmy/semantic-release-action/compare/v5.0.1...v5.0.2) (2025-10-10)
2
45
 
3
46
 
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": "5.0.2",
3
+ "version": "7.1.0",
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.10.1",
25
- "@actions/io": "^1.1.3",
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": "^24.2.9"
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
  };
@@ -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,22 @@
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 setUpJob from './setUpJob.task.js';
14
+ import installSpecifyingVersionSemantic from './installSpecifyingVersionSemantic.task.js';
15
+ import assertSemanticReleaseVersion from './assertSemanticReleaseVersion.task.js';
16
+ import preInstall from './preInstall.task.js';
17
+ import cleanupNpmrc from './cleanupNpmrc.task.js';
18
+ import windUpJob from './windUpJob.task.js';
19
+ import inputs from './inputs.json' with { type: 'json' };
16
20
 
17
21
  /**
18
22
  * Release main task
@@ -23,7 +27,10 @@ const release = async () => {
23
27
  process.chdir(core.getInput(inputs.working_directory));
24
28
  }
25
29
  await setUpJob();
30
+ warnDeprecatedInputs();
31
+ warnIfNoPackageJson();
26
32
  await installSpecifyingVersionSemantic();
33
+ await assertSemanticReleaseVersion();
27
34
  await preInstall(core.getInput(inputs.extra_plugins));
28
35
  await preInstall(core.getInput(inputs.extends));
29
36
 
@@ -33,20 +40,22 @@ const release = async () => {
33
40
  }
34
41
 
35
42
  const semanticRelease = await import('semantic-release');
43
+ const branchesOption = handleBranchesOption();
36
44
  const result = await semanticRelease.default({
37
- ...handleBranchesOption(),
45
+ ...branchesOption,
38
46
  ...handleDryRunOption(),
39
47
  ...handleCiOption(),
40
48
  ...handleExtends(),
41
49
  ...handleTagFormat(),
42
- ...handleRepositoryUrlOption()
50
+ ...handleRepositoryUrlOption(),
51
+ ...handleSkipNpmPluginOption()
43
52
  });
44
53
 
45
54
  await cleanupNpmrc();
46
55
  await windUpJob(result);
47
56
  };
48
57
 
49
- module.exports = () => {
58
+ export default async () => {
50
59
  core.debug('Initialization successful');
51
60
  release().catch(core.setFailed);
52
61
  };
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
  };