Repository: release-drafter/release-drafter
Pull request #472 (2020-04-03 14:43:48)

prerelease config override by input
## What kind of change does this PR introduce?
Feature.

## Does this PR introduce a breaking change?
- [ ] Yes
- [X] No

## Description

This PR adds the possibility to override the `prerelease` config at runtime with Github Actions input and also adds a centralized method to obtain the inputs of the execution and the ability to override other config values.

With this, you won't need to create another configuration file just to set prerelease as "true".

## Scenario
My use case, following the git-flow idea, is:
- Create a feature branch from the develop branch, implement changes, commit and push;
- Create a PR to the develop branch;
- Merge the develop branch on the staging branch; 
- On Github Action workflow, create a release draft and publish for the staging environment (pre-release, pre-prod);
- After testing all changes (we have a lot of customizations for our customers, so they test it on this environment), create a PR from the staging branch to the production branch and merge;
- On Github Action workflow, create a draft and publish for the production environment (the final release);

## More info
Is this a feature that you guys want?
I'm open to suggestions on how to do this in the repo pattern. 

*Off-topic*: I'll need a feature to ignore the pre-releases at the release body generation, do I need to open another PR to that?


Patch:
diff --git a/index.js b/index.js
index a8bfced..ce48414 100644
--- a/index.js
+++ b/index.js
@@ -13,12 +13,16 @@ const core = require('@actions/core')
 
 module.exports = (app) => {
   app.on('push', async (context) => {
+    const { shouldDraft, configName, version, tag, name } = getInput()
+
     const config = await getConfig({
       app,
       context,
-      configName: core.getInput('config-name'),
+      configName,
     })
 
+    const { isPreRelease } = getInput({ config })
+
     if (config === null) return
 
     // GitHub Actions merge payloads slightly differ, in that their ref points
@@ -54,20 +58,19 @@ module.exports = (app) => {
       config,
       lastRelease,
       mergedPullRequests: sortedMergedPullRequests,
-      version: core.getInput('version') || undefined,
-      tag: core.getInput('tag') || undefined,
-      name: core.getInput('name') || undefined,
+      version,
+      tag,
+      name,
+      isPreRelease,
+      shouldDraft,
     })
 
-    const shouldDraft = core.getInput('publish').toLowerCase() !== 'true'
-
     let createOrUpdateReleaseResponse
     if (!draftRelease) {
       log({ app, context, message: 'Creating new release' })
       createOrUpdateReleaseResponse = await createRelease({
         context,
         releaseInfo,
-        shouldDraft,
         config,
       })
     } else {
@@ -76,7 +79,6 @@ module.exports = (app) => {
         context,
         draftRelease,
         releaseInfo,
-        shouldDraft,
         config,
       })
     }
@@ -85,6 +87,27 @@ module.exports = (app) => {
   })
 }
 
+function getInput({ config } = {}) {
+  // Returns all the inputs that doesn't need a merge with the config file
+  if (!config) {
+    return {
+      shouldDraft: core.getInput('publish').toLowerCase() !== 'true',
+      configName: core.getInput('config-name'),
+      version: core.getInput('version') || undefined,
+      tag: core.getInput('tag') || undefined,
+      name: core.getInput('name') || undefined,
+    }
+  }
+
+  // Merges the config file with the input
+  // the input takes precedence, because it's more easy to change at runtime
+  return {
+    isPreRelease:
+      core.getInput('prerelease').toLowerCase() === 'true' ||
+      (!core.getInput('prerelease') && config.prerelease),
+  }
+}
+
 function setActionOutput(releaseResponse, { body }) {
   const {
     data: {
diff --git a/lib/releases.js b/lib/releases.js
index 0290693..ed1e3a8 100644
--- a/lib/releases.js
+++ b/lib/releases.js
@@ -221,6 +221,8 @@ module.exports.generateReleaseInfo = ({
   version = undefined,
   tag = undefined,
   name = undefined,
+  isPreRelease,
+  shouldDraft,
 }) => {
   let body = config.template
 
@@ -264,32 +266,24 @@ module.exports.generateReleaseInfo = ({
     name,
     tag,
     body,
+    prerelease: isPreRelease,
+    draft: shouldDraft,
   }
 }
 
-module.exports.createRelease = ({
-  context,
-  releaseInfo,
-  shouldDraft,
-  config,
-}) => {
+module.exports.createRelease = ({ context, releaseInfo }) => {
   return context.github.repos.createRelease(
     context.repo({
       name: releaseInfo.name,
       tag_name: releaseInfo.tag,
       body: releaseInfo.body,
-      draft: shouldDraft,
-      prerelease: config.prerelease,
+      draft: releaseInfo.draft,
+      prerelease: releaseInfo.prerelease,
     })
   )
 }
 
-module.exports.updateRelease = ({
-  context,
-  draftRelease,
-  releaseInfo,
-  shouldDraft,
-}) => {
+module.exports.updateRelease = ({ context, draftRelease, releaseInfo }) => {
   const updateReleaseParams = updateDraftReleaseParams({
     name: releaseInfo.name || draftRelease.name,
     tag_name: releaseInfo.tag || draftRelease.tag_name,
@@ -299,7 +293,8 @@ module.exports.updateRelease = ({
     context.repo({
       release_id: draftRelease.id,
       body: releaseInfo.body,
-      draft: shouldDraft,
+      draft: releaseInfo.draft,
+      prerelease: releaseInfo.prerelease,
       ...updateReleaseParams,
     })
   )


Test patch:
diff --git a/test/fixtures/config/config-with-prerelease.yml b/test/fixtures/config/config-with-prerelease.yml
new file mode 100644
index 0000000..371d384
--- /dev/null
+++ b/test/fixtures/config/config-with-prerelease.yml
@@ -0,0 +1,2 @@
+template: This is a Pre-release.
+prerelease: true
diff --git a/test/fixtures/config/config-without-prerelease.yml b/test/fixtures/config/config-without-prerelease.yml
new file mode 100644
index 0000000..32a35dd
--- /dev/null
+++ b/test/fixtures/config/config-without-prerelease.yml
@@ -0,0 +1,2 @@
+template: This isn't a Pre-release.
+prerelease: false
diff --git a/test/index.test.js b/test/index.test.js
index 4d65d44..3b9637e 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -1649,7 +1649,7 @@ Previous tag: ''
     })
   })
 
-  describe('input publish, version, tag and name overrides', () => {
+  describe('input publish, prerelease, version, tag and name overrides', () => {
     // Method with all the test's logic, to prevent duplication
     const overridesTest = async (overrides, expectedBody) => {
       let mockEnv = {}
@@ -1678,11 +1678,18 @@ Previous tag: ''
         if (overrides.publish) {
           mockEnv['INPUT_PUBLISH'] = overrides.publish
         }
+
+        if (overrides.prerelease) {
+          mockEnv['INPUT_PRERELEASE'] = overrides.prerelease
+        }
       }
 
       let restoreEnv = mockedEnv(mockEnv)
 
-      getConfigMock('config-with-input-version-template.yml')
+      getConfigMock(
+        (overrides && overrides.configName) ||
+          'config-with-input-version-template.yml'
+      )
 
       nock('https://api.github.com')
         .get('/repos/toolmantim/release-drafter-test-project/releases')
@@ -1777,6 +1784,107 @@ Previous tag: ''
       })
     })
 
+    describe('with input prerelease: true', () => {
+      it('marks the created draft as prerelease', async () => {
+        return overridesTest(
+          {
+            prerelease: 'true',
+          },
+          {
+            draft: true,
+            prerelease: true,
+          }
+        )
+      })
+    })
+
+    describe('with input prerelease: false', () => {
+      it('doesnt mark the created draft as prerelease', async () => {
+        return overridesTest(
+          {
+            prerelease: 'false',
+          },
+          {
+            draft: true,
+            prerelease: false,
+          }
+        )
+      })
+    })
+
+    describe('with input prerelease and publish: true', () => {
+      it('marks the created release as a prerelease', async () => {
+        return overridesTest(
+          {
+            prerelease: 'true',
+            publish: 'true',
+          },
+          {
+            draft: false,
+            prerelease: true,
+          }
+        )
+      })
+    })
+
+    describe('with input prerelease: true and config file prerelease: false', () => {
+      it('marks the created draft as prerelease', async () => {
+        return overridesTest(
+          {
+            prerelease: 'true',
+            configName: 'config-without-prerelease.yml',
+          },
+          {
+            draft: true,
+            prerelease: true,
+          }
+        )
+      })
+    })
+
+    describe('with input prerelease: false and config file prerelease: true', () => {
+      it('doesnt mark the created draft as prerelease', async () => {
+        return overridesTest(
+          {
+            prerelease: 'false',
+            configName: 'config-with-prerelease.yml',
+          },
+          {
+            draft: true,
+            prerelease: false,
+          }
+        )
+      })
+    })
+
+    describe('without input prerelease and config file prerelease: true', () => {
+      it('marks the created draft as a prerelease', async () => {
+        return overridesTest(
+          {
+            configName: 'config-with-prerelease.yml',
+          },
+          {
+            draft: true,
+            prerelease: true,
+          }
+        )
+      })
+    })
+
+    describe('without input prerelease and config file prerelease: false', () => {
+      it('doesnt mark the created draft as a prerelease', async () => {
+        return overridesTest(
+          {
+            configName: 'config-without-prerelease.yml',
+          },
+          {
+            draft: true,
+            prerelease: false,
+          }
+        )
+      })
+    })
+
     describe('with tag and name', () => {
       it('gets the version from the tag and forces using the tag and name', async () => {
         return overridesTest(