From 2dfad92b71326ba56b0c3927f15024ffe455fc2c Mon Sep 17 00:00:00 2001
From: softprops <d.tangren@gmail.com>
Date: Mon, 26 Aug 2019 01:26:13 -0400
Subject: [PATCH] add support for loading release body from a path. fixes: #2

---
 README.md   | 45 +++++++++++++++++++++++++++++++++++++++------
 action.yml  |  4 ++++
 src/main.rs | 25 +++++++++++++++++++++++--
 3 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 2761de6..8242219 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,8 @@ A common case for GitHub releases is to upload your binary after its been valida
 Use the `with.files` input to declare a comma-separated list of glob expressions matching the files
 you wish to upload to GitHub releases. If you'd like you can just list the files by name directly.
 
+Below is an example of uploading a single asset named `Release.txt`
+
 ```yaml
 name: Main
 
@@ -66,18 +68,49 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 ```
 
+### πŸ“ External release notes
+
+Many systems exist that can help generate release notes for you. This action supports
+loading release notes from a path in your repository's build to allow for the flexibility 
+of using any changelog generator for your releases, including a human πŸ‘©β€πŸ’»
+
+```yaml
+name: Main
+
+on: push
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@master
+      - name: Generate Changeload
+        run: echo "# Good things have arrived" > ${{ github.workflow }}-CHANGELOG.txt
+      - name: Release
+        uses: docker://softprops/action-gh-release
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          body_path: ${{ github.workflow }}-CHANGELOG.txt
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+```
+
 ### πŸ’… Customizing
 
 #### inputs
 
 The following are optional as `step.with` keys
 
-| Name    | Type    | Description                                                   |
-|---------|---------|---------------------------------------------------------------|
-| `body`  | String  | Text communicating notable changes in this release            |
-| `draft` | Boolean | Indicator of whether or not this release is a draft           |
-| `files` | String  | Comma-delimited globs of paths to assets to upload for release|
-| `name`  | String  | Name of the release. defaults to tag name                     |
+| Name        | Type    | Description                                                     |
+|-------------|---------|-----------------------------------------------------------------|
+| `body`      | String  | Text communicating notable changes in this release              |
+| `body_path` | String  | Path to load text communicating notable changes in this release |
+| `draft`     | Boolean | Indicator of whether or not this release is a draft             |
+| `files`     | String  | Comma-delimited globs of paths to assets to upload for release  |
+| `name`      | String  | Name of the release. defaults to tag name                       |
+
+πŸ’‘When providing a `body` and `body_path` at the same time, `body_path` will be attempted first, then falling back on `body` if the path can not be read from.
 
 #### environment variables
 
diff --git a/action.yml b/action.yml
index 2b2837e..a4de1a2 100644
--- a/action.yml
+++ b/action.yml
@@ -7,6 +7,10 @@ inputs:
     description: 'Note-worthy description of changes in release'
     required: false
     default: 'empty'
+  body-path:
+    description: 'Path to load note-worthy description of changes in release from'
+    required: false
+    default: 'empty'
   name:
     description: 'Gives the release a custom name'
     required: false
diff --git a/src/main.rs b/src/main.rs
index fc8ecb8..a580641 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,7 +7,7 @@ use serde::Deserialize;
 use std::{
     error::Error,
     ffi::OsStr,
-    fs::File,
+    fs::{read_to_string, File},
     path::{Path, PathBuf},
 };
 
@@ -22,6 +22,7 @@ struct Config {
     // user provided
     input_name: Option<String>,
     input_body: Option<String>,
+    input_body_path: Option<PathBuf>,
     input_files: Option<Vec<String>>,
     input_draft: Option<bool>,
 }
@@ -32,16 +33,20 @@ impl Into<Release> for Config {
             github_ref,
             input_name,
             input_body,
+            input_body_path,
             input_draft,
             ..
         } = self;
         let tag_name = github_ref.trim_start_matches("refs/tags/").to_string();
         let name = input_name.clone().or_else(|| Some(tag_name.clone()));
         let draft = input_draft;
+        let body = input_body_path
+            .and_then(|path| read_to_string(path).ok())
+            .or_else(|| input_body.clone());
         Release {
             tag_name,
             name,
-            body: input_body.clone(),
+            body,
             draft,
         }
     }
@@ -163,6 +168,20 @@ mod tests {
                     ..Release::default()
                 },
             ),
+            (
+                Config {
+                    github_ref: "refs/tags/v1.0.0".into(),
+                    input_body: Some("fallback".into()),
+                    input_body_path: Some("tests/data/foo/bar.txt".into()),
+                    ..Config::default()
+                },
+                Release {
+                    tag_name: "v1.0.0".into(),
+                    name: Some("v1.0.0".into()),
+                    body: Some("release me".into()),
+                    ..Release::default()
+                },
+            ),
         ] {
             assert_eq!(expect, conf.into());
         }
@@ -193,6 +212,7 @@ mod tests {
                 ("INPUT_BODY".into(), ":)".into()),
                 ("INPUT_FILES".into(), "*.md".into()),
                 ("INPUT_DRAFT".into(), "true".into()),
+                ("INPUT_BODY_PATH".into(), "tests/data/foo/bar.txt".into()),
             ],
             Config {
                 github_token: "123".into(),
@@ -200,6 +220,7 @@ mod tests {
                 github_repository: "foo/bar".into(),
                 input_name: Some("test release".into()),
                 input_body: Some(":)".into()),
+                input_body_path: Some("tests/data/foo/bar.txt".into()),
                 input_files: Some(vec!["*.md".into()]),
                 input_draft: Some(true),
             },