export const meta = {
  title: `Command Hooks`,
  description: `Execute custom scripts before, during, and after Amplify CLI commands (“amplify push”, “amplify api gql-compile”, and more).`,
};

Use Command Hooks to execute custom scripts before, during, and after Amplify CLI commands (“amplify push”, “amplify api gql-compile”, and more). This allows you to extend Amplify’s best-practice defaults to meet your organization’s specific security guidelines and operational requirements.

## Adding a command hook
<Callout>

Hooks need to be checked into source control in order to be made available across machines. `amplify pull` will not re-create the hooks folder automatically as these are considered changes that need to be committed to the repository.

</Callout>

Place your custom scripts in the `amplify/hooks` directory and set the script file name to the desired command with a pre or post designation. For example `post-add-function.js` will execute the script after `amplify add function`. For more information about the script naming convention, see [How to name command hook scripts](#how-to-name-command-hook-scripts).

For this example, let's create a hook to ensure that a minimum major Amplify CLI version is used before deployment (`amplify push`).

Let's add `pre-push.js` in the `amplify/hooks` directory with the following contents. *Note: You need to create a `amplify/hooks` folder if your Amplify project was created prior to Amplify CLI version 5.5.0*

```js
const fs = require('fs');
const parameters = JSON.parse(fs.readFileSync(0, { encoding: 'utf8' }));

// Get the running Amplify CLI major version number
const currentCLIMajorVersion = parameters.data.amplify.version.split('.')[0]
console.log('Amplify CLI major version: ', currentCLIMajorVersion)

const MINIMUM_MAJOR_AMPLIFY_CLI_VERSION = 5
console.log('Minimum required Amplify CLI major version: ', MINIMUM_MAJOR_AMPLIFY_CLI_VERSION)

if (currentCLIMajorVersion < MINIMUM_MAJOR_AMPLIFY_CLI_VERSION) {
  // Non-zero exit code will stop the Amplify CLI command's execution
  console.log('Minimum CLI version requirement not met.')
  process.exit(1)
} else {
  console.log('Minimum CLI version requirement met.')
  process.exit(0)
}
```

Next, let's run `amplify push`:
```bash
amplify push
```
```console
----- 🪝 pre-push execution start -----
Amplify CLI major version: 5
Minimum required Amplify CLI major version: 5
Minimum CLI version requirement met.
----- 🪝 pre-push execution end -----
```

## How to name command hook scripts
To hook into a command, the script file in the `amplify/hooks` directory should be named with the following naming convention:

`pre|post-<command>[-<sub-command>].extension`

- `command` (required) - Amplify command.
- `extension` (required) - by default `.js` and `.sh` are mapped to Node.js and Bash. To support more extensions or scripting runtimes, see [Adding a custom scripting runtime](#adding-a-custom-scripting-runtime).
- `sub-command` (optional) - Amplify sub-command. Can be used to increase hook specificity. Example: `pre-add-auth` and `pre-mock-api`.

The following is an exhaustive list of all commands along with their subcommands that are supported by Amplify CLI:

commands                | sub-commands **(optional)**
------------------------|------------------------
`add`                     |all categories (`api`, `auth`, etc.)<br/>`codegen`<br/>`env`<br/>
`update`                  |all categories (`api`, `auth`, etc.)<br/>`env`<br/>
`remove`                  |all categories (`api`, `auth`, etc.)<br/>`env`<br/>
`push`                    |`analytics`, `api`, `auth`, `function`, `hosting`, `interactions`, `storage`, `xr`
`pull`                    |`env`<br/>
`publish`                 |-
`delete`                  |-
`checkout`                |`env`
`list`                    |`env`
`get`                     |`env`
`mock`                    |`api`, `storage`, `function`<br/>
`build`                   |`function`<br/>
`status`                  |`notifications`<br/>
`import`                  |`auth`, `storage`, `env`<br/>
`gqlcompile`              |`api`<br/>
`addgraphqldatasource`    |`api`<br/>
`statements`              |`codegen`<br/>
`types`                  |`codegen`<br/>



*Note: Multiple hook scripts with the same filename are not allowed*

## Access parameters in hook scripts

Command hooks receive two parameters, `data` and `error`. Amplify CLI passes parameters to hook scripts as a JSON string through standard input.

### **data** parameter structure
```json
{
  "amplify": {
    "version": String,
    "environment": {
      "envName": String,
      "projectPath": String,
      "defaultEditor": String
    },
    "command": String,
    "subCommand": String,
    "argv": [ String ]
  }
}
```

- amplify
    - version - current Amplify CLI version
    - environment - current Amplify environment
        - envName - current Amplify environment name
        - projectPath - path to current Amplify project
        - defaultEditor - chosen editor in init step. Example `vscode`
    - command - hooked Amplify CLI command. Example: `push`
    - subCommand - hooked Amplify CLI subcommand or plugin. Example `auth`, `env`.
    - argv - list containing the arguments passed to Amplify CLI through the command line

### **error** parameter structure

**error** is `undefined` if no error is emitted. Otherwise, it has the following structure:
```json
{
  "message": String,
  "stack": String
}
```
- message - the error message emitted by Amplify CLI
- stack - the error stack emitted by Amplify CLI

### How to access command hook parameters in Node.js
```javascript
const fs = require('fs');
const parameters = JSON.parse(fs.readFileSync(0, { encoding: 'utf8' }));
console.log(parameters.data, parameters.error)
```

### How to access command hook parameters in Bash

First, install a JSON parser like [`jq`](https://stedolan.github.io/jq/download/).

Then, parse the parameters:
```bash
parameters=`cat`
data=$(jq -r '.data' <<< "$parameters")
error=$(jq -r '.error // empty' <<< "$parameters")
echo $data
echo $error
```

## How to conditionally stop an Amplify CLI command execution
To stop the execution of Amplify CLI, the hook scripts can exit with a non-zero exit code.

<BlockSwitcher>

<Block name="Node.js">

```js
process.exit(1)
```

</Block>

<Block name="Bash">

```bash
exit 1
```

</Block>

</BlockSwitcher>


## Advanced command hook configurations
You can optionally add `hooks-config.json` in `amplify/hooks` to configure custom scripting runtimes or manage external dependencies.

### Adding a custom scripting runtime
By default, Node.js and Bash are supported. To support additional runtimes, add `extensions` to the `hooks-config.json` file in the `amplify/hooks` folder.

An example showcasing python runtime support and different runtime settings based on operating system:
```json
{
  "extensions": {
    "py": {
      "runtime": "python3"
    },
    "js": {
      "runtime": "~/.nvm/versions/node/v14.17.1/bin/node",
      "runtime_windows": "node",
      "runtime_options": ["--require", "./payload.js"],
    }
  }
}
```
- The keys in the `extensions` ( js, py ) are values that will be used as `extension` in the [naming convention](#how-to-name-command-hook-scripts) used when naming the hook scripts.
- `runtime` (required) - symlink (`node`, `python`, `bash`) or path to executable (`~/.nvm/versions/node/v14.17.1/bin/node`).
- `runtime_windows` (optional) - windows specific symlink or path to executable.
- `runtime_options` (optional) - Array of cli options to be passed to command hooks specific to runtime.

### Managing third-party dependencies

Packages from external package managers like `npm` can be used in command hooks scripts. In this example, you'll install `axios` as a dependency for your Node.js hooks.

First, go to the hooks folder and install the `axios` dependency.
```bash
cd amplify/hooks
npm init
npm install axios
```

*Note*: If you use command hooks with Amplify Hosting CI/CD pipelines, you also need to have a `preBuild` step configured to install the hook dependencies:

```yaml
backend:
  phases:
    preBuild:
    commands:
      - cd amplify/hooks
      - npm install
frontend:
...
```

Dependency directories and files, such as `node_modules`, should be added to `ignore` in `hooks-config.json`.

```json
{
  "ignore": ["node_modules", "build"]
}
```

*Note*: All entries in `ignore` should follow the [.gitignore specification](http://git-scm.com/docs/gitignore).

You can now use `axios` in your hook scripts placed in the `amplify/hooks` directory.

### Executing different logic based on environment

When using multi-environment setup you can customize command hooks logic based on `data.amplify.environment.envName` parameter.

```javascript
/**
 * @param data { { amplify: { environment: { envName: string, projectPath: string, defaultEditor: string }, command: string, subCommand: string, argv: string[] } } }
 * @param error { { message: string, stack: string } }
 */
const hookHandler = async (data, error) => {
  if (data.amplify.environment.envName === 'prod') {
    console.log('Executing pre-add-auth hook for prod environment');
  } else if (data.amplify.environment.envName === 'dev') {
    console.log('Executing pre-add-auth hook for dev environment');
  }
};

const getParameters = async () => {
  const fs = require("fs");
  return JSON.parse(fs.readFileSync(0, { encoding: "utf8" }));
};

getParameters()
  .then((event) => hookHandler(event.data, event.error))
  .catch((err) => {
    console.error(err);
    process.exitCode = 1;
  });
```

## Using command hooks in Amplify's CI/CD pipeline
Command hooks are executed when [CI/CD builds are triggered on Amplify Hosting](https://docs.aws.amazon.com/amplify/latest/userguide/multi-environments.html). To execute hook scripts in the Amplify Hosting, add the hook scripts to the `amplify/hooks` directory and commit them to your source control.

By default only `pre-push` and `post-push` hooks will be executed on builds in Amplify Hosting.

To use scripting runtimes other than Node.js and Bash, see [Adding a custom scripting runtime](#adding-a-custom-scripting-runtime) and also update the **Build Settings** in Amplify Hosting to include the runtime executable.

In this example, you'll add a `python` hook script running in the Amplify Hosting's CI/CD pipeline:

1. Add the python hook script along with `hooks-config.json` to the hooks directory.
    ```bash
    touch amplify/hooks/hooks-config.json
    ```

2. Navigate to your app in the Amplify Hosting and select **App settings** > **Build settings** to update the `preBuild` phase to install python.
    ```yaml
    ...
    backend:
    phases:
        preBuild:
        commands:
            - yum install -y python3
    frontend:
    ...
    ```

3. Add `python3` to `hooks-config.json` and push the change to your source control to trigger the Amplify Hosting build.
    ```json
    {
        "extensions": {
            "py": { "runtime": "python3" }
        }
    }
    ```

4. You’re all set! The python command hooks will be executed in new CI/CD builds.

## Command hook limitations with Amplify Studio

Command Hooks are not executed when updating a backend via Amplify Studio.
