Rush and changelog generation - Part 3
Tags: rush, node, monorepo
Published: 2022-03-02 10:48:57

The source code is on [GitHub](https://github.com/kkazala/rush-conventionalcommits/tree/ac27bc0e6d9f0f32cbdabd8adf5964a68442ac2b) so I will only make a quick walk-through here.

## The script 
- Get the last commit message (including hash). 
Following [conventional commits](https://www.conventionalcommits.org/) specification, decide whether version number should be increased. If yes:
- calculate the next version and  
- get the list of changed projects. For each of the changed projects get the last two commits and:
 - ensure that the hash of project last commit is equal to the hash of the last commit (in case rush indicates the change, but the files were not included in the last commit) and
 - ensure that the last two commit messages are not equal.
 - If the above is true, generate change files based on the commits. Append the change file to the last commit using `--amend`

### Importing modules installed by autoinstaller
Importing modules installed by the **rush-changemanager autoinstaller** requires a path to the **node_modules**.

```javascript
//...
const node_modules = path.join(__dirname, '..', 'autoinstallers/rush-changemanager/node_modules');
const rushLib = require(path.join(node_modules, '@microsoft/rush-lib'));
const rushCore = require(path.join(node_modules, '@rushstack/node-core-library'));
const gitlog = require(path.join(node_modules, 'gitlog')).default;
const recommendedBump = require(path.join(node_modules, 'recommended-bump'));
```
[Rush custom commands: execute custom script with autoinstaller dependencies](https://dev.to/kkazala/rush-custom-commands-execute-custom-script-with-autoinstaller-dependencies-3ck1).

### Recommended bump

To generate change files for commit types `fix:`, `feat:` and BREAKING CHANGE (if the commit has a footer `BREAKING CHANGE:`, or appends a `!` after the type/scope), retrieve the last commit using `gitlog`, and parse it using `recommendedBump`.

```javascript
const options = {
    repo: repoPath,
    file: repoPath,
    number: 1,
    fields: ["subject", "body", "rawBody", "authorEmail", "hash"]
}
const lastCommit = gitlog(options);
//fix, feat or BREAKING?
const { increment } = recommendedBump([lastCommit[0].rawBody]);
```

### Get changed projects

If the last commit should trigger change file generation, retrieve the names and folder paths of the changed projects using `ProjectChangeAnalyzer`.

> **Important**:  [ProjectChangeAnalyzer API](https://rushstack.io/pages/api/rush-lib.projectchangeanalyzer/) is provided as a **preview** and **may change**. **Use at your own risk**.

```javascript
const projectAnalyzer = new rushLib.ProjectChangeAnalyzer(rushConfiguration);
const terminal = new rushCore.Terminal(new rushCore.ConsoleTerminalProvider({ verboseEnabled: false }));

const changedProjects  = await projectAnalyzer.getChangedProjectsAsync({
//...
});
```

### Are the changed projects included in the last commit?

Retrieve the last two commits for each changed projects. This allows verifying that:
- the specific project was included in the last commit, and
- the last two commit messages are different. 

If everything checks, the change files are generated using `ChangeManager` class. After their content is set based on the parsed commit messages, they are committed to the repo.

```javascript
async function getChangedProjectNamesAsync(rushConfiguration) {
    const changedProjects  = await projectAnalyzer.getChangedProjectsAsync({ 
        targetBranchName: getCurrentBranch() , //detect current branch
        //...
});

    changedProjects.forEach(project => {
        rushProjects.set(project.packageName, project.projectFolder);
    });
    return rushProjects;    
}

//...
getChangedProjectNamesAsync(rushConfiguration).then((rushProjects) => {
    rushProjects.forEach((value, key) => {
    const result = parseRecentCommits(key, value, lastCommitInfo, rushConfiguration.rushJsonFolder);
    if (result) { 
        generateChangeFile(rushConfiguration, result);
        executeCommand(`git add ${rushConfiguration.changesFolder}`);
        executeCommandAsync(`git commit --no-edit --no-verify --amend `);
    }
});
```
## Git hooks
To generate the change files after `git commit`, use the `post-commit` git hook, which is is invoked after a commit is made.

### common\git-hooks\post-commit
```powershell
#!/bin/sh
node common/scripts/install-run-rush.js changefiles
```

Run `rush install` for Rush to .git/hooks/post-commit
```powershell
rush install
```

## Testing
When testing, make sure to edit files belonging to a project managed by **rush**. Changes will not be detected otherwise.

## Next steps
This is by far not a finished product. For example, when retrieving changed projects I don't evaluate  `shouldPublish` or `versionPolicy.exemptFromRushChange`.

## Source Code
You may find the source code on [GitHub](https://github.com/kkazala/rush-conventionalcommits/tree/ac27bc0e6d9f0f32cbdabd8adf5964a68442ac2b).