diff --git a/README.md b/README.md
index ba465c49..43ba4fa3 100644
--- a/README.md
+++ b/README.md
@@ -282,6 +282,7 @@ As seen above, we have two steps. One for a noop deploy, and one for a regular d
| `merge_deploy_mode` | `false` | `"false"` | Advanced configuration option for operations on merge commits. See the [merge commit docs](#merge-commit-workflow-strategy) below |
| `unlock_on_merge_mode` | `false` | `"false"` | Advanced configuration option for automatically releasing locks associated with a pull request when that pull request is merged. See the [unlock on merge mode](docs/unlock-on-merge.md) documentation for more details |
| `skip_completing` | `false` | `"false"` | If set to "true", skip the process of completing a deployment. You must manually create a deployment status after the deployment is complete. Default is "false" |
+| `deploy_message_path` | `false` | `".github/deployment_message.md"` | The path to a markdown file which is used as a template for custom deployment messages. Example: `".github/deployment_message.md"` |
## Outputs π€
diff --git a/__tests__/functions/check-input.test.js b/__tests__/functions/check-input.test.js
new file mode 100644
index 00000000..efd7fe6f
--- /dev/null
+++ b/__tests__/functions/check-input.test.js
@@ -0,0 +1,23 @@
+import {checkInput} from '../../src/functions/check-input'
+
+test('checks an input an finds that it is valid', async () => {
+ expect(await checkInput('production')).toStrictEqual('production')
+})
+
+test('checks an input an finds that it is valid with true/false strings', async () => {
+ expect(await checkInput('true')).toStrictEqual('true')
+
+ expect(await checkInput('false')).toStrictEqual('false')
+})
+
+test('checks an empty string input an finds that it is invalid', async () => {
+ expect(await checkInput('')).toStrictEqual(null)
+})
+
+test('checks a null object input an finds that it is invalid', async () => {
+ expect(await checkInput(null)).toStrictEqual(null)
+})
+
+test('checks a string of null input an finds that it is invalid', async () => {
+ expect(await checkInput('null')).toStrictEqual(null)
+})
diff --git a/__tests__/functions/post-deploy-message.test.js b/__tests__/functions/post-deploy-message.test.js
new file mode 100644
index 00000000..59ceb733
--- /dev/null
+++ b/__tests__/functions/post-deploy-message.test.js
@@ -0,0 +1,183 @@
+import {postDeployMessage} from '../../src/functions/post-deploy-message'
+import * as core from '@actions/core'
+import dedent from 'dedent-js'
+
+// const debugMock = jest.spyOn(core, 'debug')
+
+var context
+var environment
+var environment_url
+var environment_url_simple
+var status
+var noop
+var ref
+
+beforeEach(() => {
+ jest.clearAllMocks()
+ jest.spyOn(core, 'info').mockImplementation(() => {})
+ jest.spyOn(core, 'debug').mockImplementation(() => {})
+
+ process.env.DEPLOY_MESSAGE = null
+ process.env.INPUT_ENVIRONMENT_URL_IN_COMMENT = 'true'
+ process.env.INPUT_DEPLOY_MESSAGE_PATH = '.github/deployment_message.md'
+
+ environment = 'production'
+ environment_url = 'https://example.com'
+ environment_url_simple = 'example.com'
+ status = 'success'
+ noop = false
+ ref = 'test-ref'
+
+ context = {
+ actor: 'monalisa',
+ eventName: 'issue_comment',
+ workflow: 'test-workflow',
+ repo: {
+ owner: 'corp',
+ repo: 'test'
+ },
+ payload: {
+ comment: {
+ id: '1'
+ }
+ }
+ }
+})
+
+test('successfully constructs a post deploy message with the defaults', async () => {
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ status, // status
+ noop, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`
+ ### Deployment Results β
+
+ **${context.actor}** successfully deployed branch \`${ref}\` to **${environment}**
+
+ > **Environment URL:** [${environment_url_simple}](${environment_url})`)
+ )
+})
+
+test('successfully constructs a post deploy message with the defaults during a "noop" deploy', async () => {
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ status, // status
+ true, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`
+ ### Deployment Results β
+
+ **${context.actor}** successfully **noop** deployed branch \`${ref}\` to **${environment}**`)
+ )
+})
+
+test('successfully constructs a post deploy message with the defaults during a deployment failure', async () => {
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ 'failure', // status
+ noop, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`
+ ### Deployment Results β
+
+ **${context.actor}** had a failure when deploying branch \`${ref}\` to **${environment}**`)
+ )
+})
+
+test('successfully constructs a post deploy message with the defaults during a deployment with an unknown status', async () => {
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ 'unknown', // status
+ noop, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`
+ ### Deployment Results β οΈ
+
+ Warning: deployment status is unknown, please use caution`)
+ )
+})
+
+test('successfully constructs a post deploy message with a custom env var', async () => {
+ process.env.DEPLOY_MESSAGE = 'Deployed 1 shiny new server'
+
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ status, // status
+ noop, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`
+ ### Deployment Results β
+
+ **${context.actor}** successfully deployed branch \`${ref}\` to **${environment}**
+
+ Show Results
+
+ Deployed 1 shiny new server
+
+
+
+ > **Environment URL:** [${environment_url_simple}](${environment_url})`)
+ )
+})
+
+test('successfully constructs a post deploy message with a custom markdown file', async () => {
+ process.env.INPUT_DEPLOY_MESSAGE_PATH =
+ '__tests__/templates/test_deployment_message.md'
+ expect(
+ await postDeployMessage(
+ context, // context
+ environment, // environment
+ environment_url, // environment_url
+ status, // status
+ noop, // noop
+ ref // ref
+ )
+ ).toStrictEqual(
+ dedent(`### Deployment Results :rocket:
+
+ The following variables are available to use in this template:
+
+ - \`environment\` - The name of the environment (String)
+ - \`environment_url\` - The URL of the environment (String) {Optional}
+ - \`status\` - The status of the deployment (String) - \`success\`, \`failure\`, or \`unknown\`
+ - \`noop\` - Whether or not the deployment is a noop (Boolean)
+ - \`ref\` - The ref of the deployment (String)
+ - \`actor\` - The GitHub username of the actor who triggered the deployment (String)
+
+ Here is an example:
+
+ monalisa deployed branch \`test-ref\` to the **production** environment. This deployment was a success :rocket:.
+
+ You can view the deployment [here](https://example.com).
+
+
+
+ `)
+ )
+})
diff --git a/__tests__/functions/post-deploy.test.js b/__tests__/functions/post-deploy.test.js
index c331e938..6db25aba 100644
--- a/__tests__/functions/post-deploy.test.js
+++ b/__tests__/functions/post-deploy.test.js
@@ -3,16 +3,24 @@ import * as actionStatus from '../../src/functions/action-status'
import * as lock from '../../src/functions/lock'
import * as unlock from '../../src/functions/unlock'
import * as createDeploymentStatus from '../../src/functions/deployment'
+import * as postDeployMessage from '../../src/functions/post-deploy-message'
import * as core from '@actions/core'
const infoMock = jest.spyOn(core, 'info')
+var octokit
+var context
+
beforeEach(() => {
jest.clearAllMocks()
jest.spyOn(core, 'info').mockImplementation(() => {})
+ jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(actionStatus, 'actionStatus').mockImplementation(() => {
return undefined
})
+ jest.spyOn(postDeployMessage, 'postDeployMessage').mockImplementation(() => {
+ return 'Updated 1 server'
+ })
jest.spyOn(lock, 'lock').mockImplementation(() => {
return {lockData: {sticky: true}}
})
@@ -21,33 +29,32 @@ beforeEach(() => {
.mockImplementation(() => {
return undefined
})
- jest.spyOn(core, 'debug').mockImplementation(() => {})
-})
-const context = {
- actor: 'monalisa',
- eventName: 'issue_comment',
- workflow: 'test-workflow',
- repo: {
- owner: 'corp',
- repo: 'test'
- },
- payload: {
- comment: {
- id: '1'
+ context = {
+ actor: 'monalisa',
+ eventName: 'issue_comment',
+ workflow: 'test-workflow',
+ repo: {
+ owner: 'corp',
+ repo: 'test'
+ },
+ payload: {
+ comment: {
+ id: '1'
+ }
}
}
-}
-const octokit = {
- rest: {
- repos: {
- createDeploymentStatus: jest.fn().mockReturnValue({
- data: {}
- })
+ octokit = {
+ rest: {
+ repos: {
+ createDeploymentStatus: jest.fn().mockReturnValue({
+ data: {}
+ })
+ }
}
}
-}
+})
test('successfully completes a production branch deployment', async () => {
const actionStatusSpy = jest.spyOn(actionStatus, 'actionStatus')
@@ -62,9 +69,8 @@ test('successfully completes a production branch deployment', async () => {
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'false',
+ false,
456,
'production',
'' // environment_url
@@ -88,7 +94,7 @@ test('successfully completes a production branch deployment', async () => {
}
},
12345,
- ' ### Deployment Results β
\n\n **monalisa** successfully deployed branch `test-ref` to **production**\n\n Show Results
\n\n Deployment has created 1 new server\n\n ',
+ 'Updated 1 server',
true
)
expect(createDeploymentStatusSpy).toHaveBeenCalled()
@@ -128,9 +134,8 @@ test('successfully completes a production branch deployment with an environment
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'false',
+ false,
456,
'production',
'https://example.com', // environment_url
@@ -155,7 +160,7 @@ test('successfully completes a production branch deployment with an environment
}
},
12345,
- ' ### Deployment Results β
\n\n **monalisa** successfully deployed branch `test-ref` to **production**\n\n Show Results
\n\n Deployment has created 1 new server\n\n \n\n> **Environment URL:** [example.com](https://example.com)',
+ 'Updated 1 server',
true
)
expect(createDeploymentStatusSpy).toHaveBeenCalled()
@@ -201,9 +206,8 @@ test('successfully completes a production branch deployment and removes a non-st
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'false',
+ false,
456,
'production',
'' // environment_url
@@ -228,7 +232,7 @@ test('successfully completes a production branch deployment and removes a non-st
}
},
12345,
- ' ### Deployment Results β
\n\n **monalisa** successfully deployed branch `test-ref` to **production**\n\n Show Results
\n\n Deployment has created 1 new server\n\n ',
+ 'Updated 1 server',
true
)
expect(createDeploymentStatusSpy).toHaveBeenCalled()
@@ -273,9 +277,8 @@ test('successfully completes a noop branch deployment and removes a non-sticky l
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'true',
+ true,
456,
'production'
)
@@ -299,7 +302,7 @@ test('successfully completes a noop branch deployment and removes a non-sticky l
}
},
12345,
- ' ### Deployment Results β
\n\n **monalisa** successfully **noop** deployed branch `test-ref` to **production**\n\n Show Results
\n\n Deployment has created 1 new server\n\n ',
+ 'Updated 1 server',
true
)
expect(infoMock).toHaveBeenCalledWith(
@@ -316,9 +319,8 @@ test('successfully completes a production branch deployment with no custom messa
123,
12345,
'success',
- '',
'test-ref',
- 'false',
+ false,
456,
'production'
)
@@ -340,7 +342,7 @@ test('successfully completes a production branch deployment with no custom messa
}
},
12345,
- ' ### Deployment Results β
\n\n **monalisa** successfully deployed branch `test-ref` to **production**',
+ 'Updated 1 server',
true
)
})
@@ -353,9 +355,8 @@ test('successfully completes a noop branch deployment', async () => {
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'true',
+ true,
456,
'production'
)
@@ -370,9 +371,8 @@ test('updates with a failure for a production branch deployment', async () => {
123,
12345,
'failure',
- 'Deployment has failed to create 1 new server',
'test-ref',
- 'false',
+ false,
456,
'production'
)
@@ -387,9 +387,8 @@ test('updates with an unknown for a production branch deployment', async () => {
123,
12345,
'unknown',
- 'Deployment has failed to create 1 new server',
'test-ref',
- 'false',
+ false,
456,
'production'
)
@@ -418,9 +417,9 @@ test('fails due to no ref', async () => {
context,
octokit,
123,
+ 12345,
'success',
- 'Deployment has created 1 new server',
- ''
+ null // ref
)
} catch (e) {
expect(e.message).toBe('no ref provided')
@@ -436,9 +435,8 @@ test('fails due to no deployment_id', async () => {
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'false',
+ false,
''
)
} catch (e) {
@@ -455,9 +453,8 @@ test('fails due to no environment', async () => {
123,
12345,
'success',
- 'Deployment has created 1 new server',
'test-ref',
- 'false',
+ false,
456,
''
)
@@ -469,16 +466,7 @@ test('fails due to no environment', async () => {
test('fails due to no noop', async () => {
jest.resetAllMocks()
try {
- await postDeploy(
- context,
- octokit,
- 123,
- 12345,
- 'success',
- 'Deployment has created 1 new server',
- 'test-ref',
- ''
- )
+ await postDeploy(context, octokit, 123, 12345, 'success', 'test-ref', null)
} catch (e) {
expect(e.message).toBe('no noop value provided')
}
diff --git a/__tests__/schemas/action.schema.yml b/__tests__/schemas/action.schema.yml
index dbb0b754..1c996994 100644
--- a/__tests__/schemas/action.schema.yml
+++ b/__tests__/schemas/action.schema.yml
@@ -320,6 +320,16 @@ inputs:
default:
type: string
required: true
+ deploy_message_path:
+ description:
+ type: string
+ required: true
+ required:
+ type: boolean
+ required: true
+ default:
+ type: string
+ required: false
# outputs section
outputs:
diff --git a/__tests__/templates/test_deployment_message.md b/__tests__/templates/test_deployment_message.md
new file mode 100644
index 00000000..55694e62
--- /dev/null
+++ b/__tests__/templates/test_deployment_message.md
@@ -0,0 +1,18 @@
+### Deployment Results {{ ":rocket:" if status === "success" else ":cry:" }}
+
+The following variables are available to use in this template:
+
+- `environment` - The name of the environment (String)
+- `environment_url` - The URL of the environment (String) {Optional}
+- `status` - The status of the deployment (String) - `success`, `failure`, or `unknown`
+- `noop` - Whether or not the deployment is a noop (Boolean)
+- `ref` - The ref of the deployment (String)
+- `actor` - The GitHub username of the actor who triggered the deployment (String)
+
+Here is an example:
+
+{{ actor }} deployed branch `{{ ref }}` to the **{{ environment }}** environment. This deployment was a {{ status }} {{ ":rocket:" if status === "success" else ":cry:" }}.
+
+{% if environment_url %}You can view the deployment [here]({{ environment_url }}).{% endif %}
+
+{% if noop %}This was a noop deployment.{% endif %}
diff --git a/action.yml b/action.yml
index d7828fa5..23d839a2 100644
--- a/action.yml
+++ b/action.yml
@@ -121,6 +121,10 @@ inputs:
description: 'If set to "true", skip the process of completing a deployment. You must manually create a deployment status after the deployment is complete. Default is "false"'
required: false
default: "false"
+ deploy_message_path:
+ description: 'The path to a markdown file which is used as a template for custom deployment messages. Example: ".github/deployment_message.md"'
+ required: false
+ default: ".github/deployment_message.md"
outputs:
triggered:
description: 'The string "true" if the trigger was found, otherwise the string "false"'
diff --git a/dist/index.js b/dist/index.js
index 71568838..45cbc7a2 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -4357,6 +4357,278 @@ exports.request = request;
//# sourceMappingURL=index.js.map
+/***/ }),
+
+/***/ 3980:
+/***/ (function(module) {
+
+// MIT license (by Elan Shanker).
+(function(globals) {
+ 'use strict';
+
+ var executeSync = function(){
+ var args = Array.prototype.slice.call(arguments);
+ if (typeof args[0] === 'function'){
+ args[0].apply(null, args.splice(1));
+ }
+ };
+
+ var executeAsync = function(fn){
+ if (typeof setImmediate === 'function') {
+ setImmediate(fn);
+ } else if (typeof process !== 'undefined' && process.nextTick) {
+ process.nextTick(fn);
+ } else {
+ setTimeout(fn, 0);
+ }
+ };
+
+ var makeIterator = function (tasks) {
+ var makeCallback = function (index) {
+ var fn = function () {
+ if (tasks.length) {
+ tasks[index].apply(null, arguments);
+ }
+ return fn.next();
+ };
+ fn.next = function () {
+ return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+ };
+ return fn;
+ };
+ return makeCallback(0);
+ };
+
+ var _isArray = Array.isArray || function(maybeArray){
+ return Object.prototype.toString.call(maybeArray) === '[object Array]';
+ };
+
+ var waterfall = function (tasks, callback, forceAsync) {
+ var nextTick = forceAsync ? executeAsync : executeSync;
+ callback = callback || function () {};
+ if (!_isArray(tasks)) {
+ var err = new Error('First argument to waterfall must be an array of functions');
+ return callback(err);
+ }
+ if (!tasks.length) {
+ return callback();
+ }
+ var wrapIterator = function (iterator) {
+ return function (err) {
+ if (err) {
+ callback.apply(null, arguments);
+ callback = function () {};
+ } else {
+ var args = Array.prototype.slice.call(arguments, 1);
+ var next = iterator.next();
+ if (next) {
+ args.push(wrapIterator(next));
+ } else {
+ args.push(callback);
+ }
+ nextTick(function () {
+ iterator.apply(null, args);
+ });
+ }
+ };
+ };
+ wrapIterator(makeIterator(tasks))();
+ };
+
+ if (typeof define !== 'undefined' && define.amd) {
+ define([], function () {
+ return waterfall;
+ }); // RequireJS
+ } else if ( true && module.exports) {
+ module.exports = waterfall; // CommonJS
+ } else {
+ globals.waterfall = waterfall; //