npm package: @madk/release
Description: A release tool
Author: Michael Karlsson
Latest version: 2.0.1

# A Little Release Tool

This is a NodeJS-based release tool that helps create a release on [Conventional Commit](https://www.conventionalcommits.org/)-conventions. It can be run both locally and in CI environments. The idea is to provide a wrapper
around solid tools, that provides the basic functionality of releaseing
an application/website/library with flexibility, but with sane defaults.

> This tool is very early in development. Use at your own risk.
> - Only tested in a few contexts
> - Lacking testing across node version
> - Needs extended test cases for criticial functionality
> - Missing configuration features

A release is created by:
1. Test (default: `npm test`)
2. Bump (Uses semver/conventional commit tooling) to figure out the next version
3. Generate changelog using `conventional-changelog`
4. Build (optional, default: `npm run build`)
4. Custom updates through `updaters` (optional, see below for more info)
5. Publish (optional, default: `npm publish`)

You can also:
- Publish through SFTP
- Bump to specific versions (don't decide automatically)
- Control output through CLI arguments or environment variables/configuration
- Skip steps

## Usage


### CLI
Install it globally, `npm i -g @madk/release`, or locally, `npm i @madk/release`, and run it through [npm-scripts] (https://docs.npmjs.com/cli/v9/using-npm/scripts).

To create a simple release, just run `release`. There are multiple CLI-options
available (see below), and even more configuration abilities if you use a
configuration file.


More usage instructions:
```
Usage: release [options] [folder]

Create a release.
This runs tests, bumps and commits a version, creates a tag, generates a changelog by default.

Arguments:
  folder                        Absolute path of git repository. Defaults to the current directory. (default: "C:\\Users\\madka\\Projects\\madk-release")

Options:
  --bump <string>               Override what we are bump the version to. Could be any valid semver/conventional changelog type of release.
  --build [command]             Build the package before publish. (preset: "npm run build", env: RELEASE_BUILD)
  --publish [npm|sftp|command]  How we are publishing Can be a preset (npm/sftp) or a custom terminal-command. (preset: "npm", env: RELEASE_PUBLISH)
  --config <configPath>         Override what configuration file to use. The path should be a file relative to the <folder>. (env: RELEASE_CONFIG)
  --test [command]              Skip running tests before doing anything. (env: RELEASE_TEST)
  --skip-test                   Skip running tests before doing anything.
  -r, --dry-run                 Do not actually commit or do anything. (default: false)
  -s, --silent                  Silence all log output. This has precedence if set (default: false)
  -v, --verbose                 Output extra information. (default: false)
  -d, --debug                   Output extra debug information. (default: false)
  --skip-bump                   Skip bumping the version.
  --skip-changelog              Skip generating the changelog.
  --no-skip-ci                  Don't include "[skip ci]" in the commit messages. You can modify what the string is if you use a config file ("skipCiString").
  --skip-ci-in-tag              Add "[skip ci]" to the tag messages. You can modify what the string is if you use a config file ("skipCiString").
  --sftp-user <string>          SFTP user to use when publishing. (env: SFTP_USER)
  --sftp-host <string>          SFTP host (URL) to use when publishing. (env: SFTP_HOST)
  --sftp-remotedir <string>     SFTP directory (absolute path) to publish to. (env: SFTP_REMOTEDIR)
  --sftp-localdir <string>      Local directory to use when publishing. This should be a relative path from project folder. (env: SFTP_LOCALDIR)
  --sftp-pw <string>            SFTP password to use when publishing. (env: SFTP_PASSWORD)
  --sftp-port <number>          Port to use when publishing via SFTP. (env: SFTP_PORT)
  -h, --help                    display help for command
```

Shorthand instructions can be combined. For example, to do a verbose dry run in the current folder `release -rv`;

> CLI arguments have the precedence over configuration files

### API

```js
import { ReleaseRunner } from '@madk/release';

const rr = new ReleaseRunner({
  // ...Config
})

rr.run();
```


## Configuration

Besides the command line arguments, you can control the output of the release through configuration files (and environment variables).

To do so, create a configuration file in the project folder. This configuration file can be named one of the following (and prioritized in order).
1. ".release.cjs"
2. ".release.js"
3. "release.cjs"
4. "release.js"

You can also change what this is by passing in a configuration option through the cli or if running programmatically.
CLI:
```bash
$ release --config config/my-own-config.js
```
Programmatically:
```js
const rr = new ReleaseRunner({
  // ...
  config: 'my-own-config.js'
})

```

The file config value is a relative (child) file path from the root of the folder.

The file is javascript-based and must export a CommonJS-module:
```js
module.exports = {
  // configuration
}
```

### Build step

The build step is off by default. You can enable it either by using the
build flag (`-b, --build`), or setting the setting the `build` property in
a configuration file.

Examples:

```bash
# Build with default preset `npm run build`
$ release --build

# Build with custom command
$ release --build "npm run build:wasm"
```

```js
module.exports = {
	// set to default preset, `npm run build`
	build: true,
	// set to custom command
	build: 'npm run build:wasm',
}
```

### Bump step and "Bumpers"

The bump steps runs after successful tests. This step figures out if there is a new version. After that, the first "bumpers" are run. Bumpers makes it possible for youto provide custom functions to further update files that are not affected by the regular bump. These have to specified with a custom release-config file.

Bumpers per default is run after the bump step, but can also be set to be run after the build has been completed by setting `when` to `build`.

Example below can be used to update any files, here it is used to update files related to a WordPress-theme
```ts
// .release.cjs
const rx = /(stable\stag|version):\s*([0-9a-zA-Z-\.]*)$/gim;

const wpBump = (contents, runner) => {
	contents = contents.replace(rx, `$1: ${runner.project.nextVersion}`);

	return contents;
};

module.exports = {
	bumpers: [
		{
			file: 'readme.txt',
			callback: wpBump,
		},
		{
			file: 'style.css',
			callback: wpBump,
			skipStage: true // Skips this file as a part of the release commit
			// when: 'build' // You can modify when a bumper runs ('bump'/'build', corresponding to after those steps. If not set, it runs as 'bump')
		},
	],
}
```

### Publish step

The publish step is off by default. You can enable it either by using the
publish flag (`-p, --publish`), or setting the setting the `publish` property in
a configuration file.

Examples:

```bash
# All default actions + publish with default preset `npm publish`
$ release --publish
$ release --publish npm

# All default actions + publish with SFTP
$ release --publish sftp

# All default actions + publish with custom command
$ release --publish "node custom-publish.js"
```

```js
module.exports = {
	// Publish with default preset `npm publish`
	publish: true, // can also use 'npm'
	// Publish with SFTP
	publish: 'sftp',
	// set to custom command
	publish: 'node custom-publish.js',
}
```

#### SFTP Publishing

The SFTP publish uses [`ssh2-sftp-client`](https://www.npmjs.com/package/ssh2-sftp-client).

You can use the CLI or env-variables to configure for basics setups. For a
more advanced setup, use a configuration file and update the `sftp`-options
there. The configuration are matching that of `ssh2-sftp-client` with the
added properties:

- `localDir` - Where, relative from the project folder, the sync should happen. Default is the current directory.
- `remoteDir` - The absloute path to the remote directory we should sync to.

The publish happens through a basic synchronization of the folders:

1. Uploads files/folders that exists in `localDir` but not `remoteDir`
2. Replaces files/folders that exists in both (matching names)
3. Removes folders/files that exists in `remoteDir` but not `localDir`