Repository: semantic-release/semantic-release (js)

Issue:
Cannot disable CI checks in shareable configurations
## Current behavior

`ci: false` defined in the _shareable_ configuration is not respected.

This is because the `options.ci` is normalized into `options.noCi` _before_ applying `extends`: https://github.com/semantic-release/semantic-release/blob/da75a9c60a632f75b2b45856e407c1f3dfa9fa14/lib/get-config.js#L30-L32 and the rest of the code checks only `options.noCi` (at least in the places I checked).

Workaround: add `noCi: true` in the shareable configuration.

## Expected behavior

- Should be OK to set `ci: false` in the shareable configuration - happy to PR this.
- Alternatively, [documentation should be updated](https://semantic-release.gitbook.io/semantic-release/usage/configuration#ci) to reflect that - happy, but not _too_ happy, to PR this as well.

## Environment

- **semantic-release** version: latest


Pull request description:
Normalize `ci: false` into `noCi: true` after configs get merged
This makes sure that `options.ci` is respected even when set inside a shareable config.

Closes #1728.

Patch:
diff --git a/lib/get-config.js b/lib/get-config.js
index bd40cec..c54acfe 100644
--- a/lib/get-config.js
+++ b/lib/get-config.js
@@ -27,9 +27,6 @@ module.exports = async (context, cliOptions) => {
 
   // Merge config file options and CLI/API options
   let options = {...config, ...cliOptions};
-  if (options.ci === false) {
-    options.noCi = true;
-  }
 
   const pluginsPath = {};
   let extendPaths;
@@ -87,6 +84,10 @@ module.exports = async (context, cliOptions) => {
     ...(options.branches ? {branches: castArray(options.branches)} : {}),
   };
 
+  if (options.ci === false) {
+    options.noCi = true;
+  }
+
   debug('options values: %O', options);
 
   return {options, plugins: await plugins({...context, options}, pluginsPath)};


Test patch:
diff --git a/test/get-config.test.js b/test/get-config.test.js
index 4e5b456..ecec2b3 100644
--- a/test/get-config.test.js
+++ b/test/get-config.test.js
@@ -505,10 +505,10 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
 test('Throw an Error if one of the shareable config cannot be found', async (t) => {
   // Create a git repository, set the current working directory at the root of the repo
   const {cwd} = await gitRepo();
-  const pkhOptions = {extends: ['./shareable1.json', 'non-existing-path']};
+  const pkgOptions = {extends: ['./shareable1.json', 'non-existing-path']};
   const options1 = {analyzeCommits: 'analyzeCommits'};
   // Create package.json and shareable.json in repository root
-  await outputJson(path.resolve(cwd, 'package.json'), {release: pkhOptions});
+  await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
   await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
 
   await t.throwsAsync(t.context.getConfig({cwd}), {
@@ -516,3 +516,20 @@ test('Throw an Error if one of the shareable config cannot be found', async (t)
     code: 'MODULE_NOT_FOUND',
   });
 });
+
+test('Convert "ci" option to "noCi" when set from extended config', async (t) => {
+  // Create a git repository, set the current working directory at the root of the repo
+  const {cwd} = await gitRepo();
+  const pkgOptions = {extends: './no-ci.json'};
+  const options = {
+    ci: false,
+  };
+  // Create package.json and shareable.json in repository root
+  await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
+  await outputJson(path.resolve(cwd, 'no-ci.json'), options);
+
+  const {options: result} = await t.context.getConfig({cwd});
+
+  t.is(result.ci, false);
+  t.is(result.noCi, true);
+});