RFC: Runtime Hooks
See original GitHub issueThis is a Request For Comments (RFC). RFCs are intended to elicit feedback regarding a proposed change to the Amplify Framework. Please feel free to post comments or questions here.
Summary
There is a need to perform automations or tasks during lifecycle events of Amplify CLI commands. Runtime hooks help customers run custom scripts during Amplify CLI’s lifecycle events. Customers can hook into a lifecycle event by adding their script files to the hooks directory. Amplify CLI detects and executes the corresponding script during the lifecycle event.
Common Runtime Hooks Use Cases
Security checks
Customers can write scripts to scan for sensitive information before push or publish events. If there is a detected vulnerability, customer scripts can stop the execution of Amplify CLI command by exiting with a non zero code. Example of running credential scanner before push:
if keychecker --path $projectRoot; then
# no vulneribility found
exit 0
else
echo "found security vulneribility"
exit 1
fi
Running local tests
Customers can write hook scripts to run preexisting test suits. Following is an example of bash script running npm test
:
# running npm tests in pre-push script
npm test
testPassed=$?
if [ $testPassed -eq 0 ]; then
echo "npm tests passed"
exit 0
else
echo "npm tests failed"
exit 1
fi
To maintain execution consistency, especially within cross-teams, customers can use runtime hooks to test for Amplify CLI version and halt Amplify CLI commands execution if inconsistent version is present.
const process = require("process");
const data = JSON.parse(process.argv[2]);
const teamAmplifyVersion = "4.52.0";
if (teamAmplifyVersion != data.amplify.version) {
process.exit(1);
}
Hook scripts can also run linters and code formatters to ensure that codebase follows lint and format rules.
Logging and notifications for Amplify CLI events
Using hooks, customers can send email or slack messages or store logs. Customers will be able to utilize runtime parameters in their scripts to create detailed logs.
const axios = require("axios");
const process = require("process");
axios
.post(
"https://slack.com/api/chat.postMessage",
{
channel: "Amplify",
text:
"Amplify push was successfully executed!",
},
{ headers: { authorization: `Bearer ${"SLACK_API_TOKEN"}` } }
)
.then((res) => {})
.catch((err) => {});
Customer Workflow
- Amplify CLI generates a hooks directory in amplify/backend with sample hook scripts on
amplify init
. For existing projects, this folder and sample scripts will be created onamplify upgrade
. - Customers will add scripts in hooks directory following the naming convention to hook into a specific lifecycle event.
- Amplify CLI will execute the hook scripts synchronously during the corresponding event and log scripts’ errors and messages to the shell.
All the scripts in the hooks directory will be git committed by default which is different from git hooks. To support cross platform compatibility, Amplify hook scripts are not executable files.
New Amplify CLI generated directory structure after amplify init
:
-amplify
-backend
-hooks // all hooks scripts go within this directory
-pre-push.js.sample
-post-push.sh.sample
Naming convention:
Files in the hooks directory should follow the format pre|post-<command>[-<sub-commmand>].extension
.
sub-commmand
is optional and can be used to increase hook specificity. Example: pre-add-auth
and pre-mock-api
.
If there are pre-add.js
, pre-add-auth.js
, post-add.js
and post-add-auth.js
scripts present, Amplify CLI will execute in the following order:
pre-add.js
> pre-add-auth.js
> amplify add auth
> post-add-auth.js
> post-add.js
Scripting Languages and Supported Lifecycle events:
Amplify CLI will support bash(.sh) and node(.js) scripts. All scripts are auto-detected based on the extension. If there are files present with same filename and different extensions, extension with naming precedence will be executed. For customers or teams using cross platform operating system, it is recommend to use javascript over bash.
Amplify CLI will support pre and post lifecycle events for the following amplify commands and sub-commands:
- add
- update
- remove
- push
- status
- delete
- pull
- publish
- mock
- env
- codegen
- gql-compile
Parameters passed to Hook Scripts
When a lifecycle event is called, Amplify CLI passes runtime parameters into hook scripts as command line arguments. There are two JSON strings that will be passed as parameters to hook scripts: data and error. The sample scripts generated by amplify init
will have boilerplate template to showcase parsing and using these parameters. Following describes the structure of the two parameters:
data: JSON string with the following structure:
{
projectPath: String,
amplify: {
version: String,
environment: String,
command: String,
subCommands: [ String ],
options: { String: Boolean|String }
}
}
- projectPath - path to root directory of customer’s amplify project
- amplify -
- version - current Amplify version
- environment - current Amplify environment
- command - Amplify CLI command executed. Example: push
- subCommands - list of Amplify CLI subcommands or plugins executed. Example auth, storage
- options - JSON object containing command line option passed as key and either a boolean or a string as value. Example: For amplify push --yes, the options object would be { yes: true }.
error: JSON string in case Amplify CLI emits an error, null otherwise. It has the following structure:
{
"message": String,
"stack": String
}
- message: the error message emitted by Amplify CLI
- stack: the error stack emitted by Amplify CLI
Exit status
To stop the execution of Amplify CLI, the hook script can exit with a non-zero status and Amplify CLI will generate necessary logs and gracefully exit.
Third party packages and Amplify Console support
Customers can use third party packages in hook scripts given those packages are imported correctly and are accessible during execution. For instance, if a pre-push script uses jest to run tests, customers should run npm i -g jest
or npm i jest
once before Amplify CLI executes the pre-push script.
Amplify Console is automatically supported and hook scripts will be executed by the console similar to the execution on local machine.
Customers can use third party dependency and Amplify Console simultaneously by modifying amplify.yml
file to install dependency packages (backend preBuild). More information about running preBuild commands in console can be found in the Amplify docs.
Example amplify.yml
file snippet to install jest:
version: 1
env:
variables:
key: value
backend:
phases:
preBuild:
commands:
-npm install jest -g
build:
commands:
-amplifyPush --simple
Appendix
Sample generated scripts
pre-add.js.sample and post-add.sh.sample are sample scripts created on amplify init
in the hooks directory.
pre-add.js.sample
// This is a sample script created by Amplify CLI.
// To start using this script please change the filename:
// pre-add.js.sample -> pre-add.js
//
// parameters available:
// process.argv[0] - path to node executable (default)
// process.argv[1] - path to the running script file (default)
// process.argv[2] - data - String JSON argument (passed by Amplify)
// process.argv[3] - error - String JSON argument or null (passed by Amplify)
//
// exiting with a non zero status - process.exit(1)
// will result in Amplify CLI process to exit.
const process = require("process");
const error = JSON.parse(process.argv[3]);
if (error !== null) {
console.log("Amplify CLI emitted an error:", error.message);
process.exit(1);
}
const data = JSON.parse(process.argv[2]);
console.log("project root path:", data.projectPath);
console.log("Amplify CLI command:", data.amplify.command);
console.log("Amplify CLI sub-commands:", data.amplify.subCommands);
process.exit(0);
post-add.sh.sample
# This is a sample script created by Amplify CLI.
# To start using this script please change the filename:
# post-add.sh.sample -> post-add.sh
#
# parameters available:
# $0 - path to the running script file (default)
# $1 - data - String JSON argument (passed by Amplify)
# $2 - error - String JSON argument or null (passed by Amplify)
#
# exiting with a non zero status - exit 1
# will result in Amplify CLI process to exit.
if [ -z "$(which jq)" ]; then
echo "Please install jq to run the sample script."
exit 1
fi
if ! [ -z "$2" ]; then
echo "Amplify CLI emitted an error:" $(echo $2 | jq -r '.message')
exit 1
fi
projectPath=$(echo $1 | jq -r '.projectPath')
amplifyCommand=$(echo $1 | jq -r '.amplify | .command')
amplifySubCommands=$(echo $1 | jq -r '.amplify | .subCommands | .[]')
echo "project root path:" $projectPath
echo "Amplify CLI command:" $amplifyCommand
echo "Amplify CLI sub-commands:" $amplifySubCommands
exit 0
Questions for the community
- How and which particular use case(s) would be helpful to you? Are there any use cases that are missing here that Amplify team should consider?
- Which lifecycle event(s) would be most useful for you (Ex.: pre-push, post-push, pre-add-auth, etc)?
- Which values would be useful to be passed as parameters to the hook scripts?
- Which language would you prefer to use for writing hook scripts?
Issue Analytics
- State:
- Created 2 years ago
- Reactions:27
- Comments:9 (4 by maintainers)
This is awesome.
I would instantly use the following lifecycles off the top of my head:
Maybe certain configurations for functions? e.i For my use case outlined above, a
function-type
param, for example--trigger-type=cognito-trigger
creates a typescript function that already has the types for UserPoolTriggers.Bash and Node.js
Another reason I think this RFC is so great is that I think it adds a large amount of flexibility to Amplify which I’ve found in the past to be limiting.
This issue has been automatically locked since there hasn’t been any recent activity after it was closed. Please open a new issue for related bugs.
Looking for a help forum? We recommend joining the Amplify Community Discord server
*-help
channels for those types of questions.